Files
enclava/backend/app/models/plugin.py
2025-11-20 11:11:18 +01:00

381 lines
14 KiB
Python

"""
Plugin System Database Models
Defines the database schema for the isolated plugin architecture
"""
from sqlalchemy import (
Column,
Integer,
String,
Text,
DateTime,
Boolean,
JSON,
ForeignKey,
Index,
)
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
import uuid
from app.db.database import Base
class Plugin(Base):
"""Plugin registry - tracks all installed plugins"""
__tablename__ = "plugins"
# Primary identification
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String(100), unique=True, nullable=False, index=True)
slug = Column(
String(100), unique=True, nullable=False, index=True
) # URL-safe identifier
# Metadata
display_name = Column(String(200), nullable=False)
description = Column(Text)
version = Column(String(50), nullable=False)
author = Column(String(200))
homepage = Column(String(500))
repository = Column(String(500))
# Plugin file information
package_path = Column(String(500), nullable=False) # Path to plugin package
manifest_hash = Column(String(64), nullable=False) # SHA256 of manifest file
package_hash = Column(String(64), nullable=False) # SHA256 of plugin package
# Status and lifecycle
status = Column(String(20), nullable=False, default="installed", index=True)
# Statuses: installing, installed, enabled, disabled, error, uninstalling
enabled = Column(Boolean, default=False, nullable=False, index=True)
auto_enable = Column(Boolean, default=False, nullable=False)
# Installation tracking
installed_at = Column(DateTime, nullable=False, default=func.now())
enabled_at = Column(DateTime)
last_updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
installed_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
# Configuration and requirements
manifest_data = Column(JSON) # Complete plugin.yaml content
config_schema = Column(JSON) # JSON schema for plugin configuration
default_config = Column(JSON) # Default configuration values
# Security and permissions
required_permissions = Column(JSON) # List of required permission scopes
api_scopes = Column(JSON) # Required API access scopes
resource_limits = Column(JSON) # Memory, CPU, storage limits
# Database isolation
database_name = Column(String(100), unique=True) # Isolated database name
database_url = Column(String(1000)) # Connection string for plugin database
# Error tracking
last_error = Column(Text)
error_count = Column(Integer, default=0)
last_error_at = Column(DateTime)
# Relationships
installed_by_user = relationship("User", back_populates="installed_plugins")
configurations = relationship(
"PluginConfiguration", back_populates="plugin", cascade="all, delete-orphan"
)
instances = relationship(
"PluginInstance", back_populates="plugin", cascade="all, delete-orphan"
)
audit_logs = relationship(
"PluginAuditLog", back_populates="plugin", cascade="all, delete-orphan"
)
cron_jobs = relationship(
"PluginCronJob", back_populates="plugin", cascade="all, delete-orphan"
)
# Indexes for performance
__table_args__ = (
Index("idx_plugin_status_enabled", "status", "enabled"),
Index("idx_plugin_user_status", "installed_by_user_id", "status"),
)
class PluginConfiguration(Base):
"""Plugin configuration instances - per user/environment configs"""
__tablename__ = "plugin_configurations"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
plugin_id = Column(UUID(as_uuid=True), ForeignKey("plugins.id"), nullable=False)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
# Configuration data
name = Column(String(200), nullable=False) # Human-readable config name
description = Column(Text)
config_data = Column(JSON, nullable=False) # Non-sensitive configuration values
encrypted_data = Column(Text) # Encrypted sensitive fields (JSON string)
schema_version = Column(String(50)) # Schema version for migration support
is_active = Column(Boolean, default=False, nullable=False)
is_default = Column(Boolean, default=False, nullable=False)
# Metadata
created_at = Column(DateTime, nullable=False, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
created_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
# Relationships
plugin = relationship("Plugin", back_populates="configurations")
user = relationship("User", foreign_keys=[user_id])
created_by_user = relationship("User", foreign_keys=[created_by_user_id])
# Constraints
__table_args__ = (
Index("idx_plugin_config_user_active", "plugin_id", "user_id", "is_active"),
)
class PluginInstance(Base):
"""Plugin runtime instances - tracks running plugin processes"""
__tablename__ = "plugin_instances"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
plugin_id = Column(UUID(as_uuid=True), ForeignKey("plugins.id"), nullable=False)
configuration_id = Column(
UUID(as_uuid=True), ForeignKey("plugin_configurations.id")
)
# Runtime information
instance_name = Column(String(200), nullable=False)
process_id = Column(String(100)) # Docker container ID or process ID
status = Column(String(20), nullable=False, default="starting", index=True)
# Statuses: starting, running, stopping, stopped, error, crashed
# Performance tracking
start_time = Column(DateTime, nullable=False, default=func.now())
last_heartbeat = Column(DateTime, default=func.now())
stop_time = Column(DateTime)
restart_count = Column(Integer, default=0)
# Resource usage
memory_usage_mb = Column(Integer)
cpu_usage_percent = Column(Integer)
# Health monitoring
health_status = Column(String(20), default="unknown") # healthy, warning, critical
health_message = Column(Text)
last_health_check = Column(DateTime)
# Error tracking
last_error = Column(Text)
error_count = Column(Integer, default=0)
# Relationships
plugin = relationship("Plugin", back_populates="instances")
configuration = relationship("PluginConfiguration")
__table_args__ = (Index("idx_plugin_instance_status", "plugin_id", "status"),)
class PluginAuditLog(Base):
"""Audit logging for all plugin activities"""
__tablename__ = "plugin_audit_logs"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
plugin_id = Column(UUID(as_uuid=True), ForeignKey("plugins.id"), nullable=False)
instance_id = Column(UUID(as_uuid=True), ForeignKey("plugin_instances.id"))
# Event details
event_type = Column(
String(50), nullable=False, index=True
) # api_call, config_change, error, etc.
action = Column(String(100), nullable=False)
resource = Column(String(200)) # Resource being accessed
# Context information
user_id = Column(Integer, ForeignKey("users.id"))
api_key_id = Column(Integer, ForeignKey("api_keys.id"))
ip_address = Column(String(45)) # IPv4 or IPv6
user_agent = Column(String(500))
# Request/response data
request_data = Column(JSON) # Sanitized request data
response_status = Column(Integer)
response_data = Column(JSON) # Sanitized response data
# Performance metrics
duration_ms = Column(Integer)
# Status and errors
success = Column(Boolean, nullable=False, index=True)
error_message = Column(Text)
# Timestamps
timestamp = Column(DateTime, nullable=False, default=func.now(), index=True)
# Relationships
plugin = relationship("Plugin", back_populates="audit_logs")
instance = relationship("PluginInstance")
user = relationship("User")
api_key = relationship("APIKey")
__table_args__ = (
Index("idx_plugin_audit_plugin_time", "plugin_id", "timestamp"),
Index("idx_plugin_audit_user_time", "user_id", "timestamp"),
Index("idx_plugin_audit_event_type", "event_type", "timestamp"),
)
class PluginCronJob(Base):
"""Plugin scheduled jobs and cron tasks"""
__tablename__ = "plugin_cron_jobs"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
plugin_id = Column(UUID(as_uuid=True), ForeignKey("plugins.id"), nullable=False)
# Job identification
job_name = Column(String(200), nullable=False)
job_id = Column(
String(100), nullable=False, unique=True, index=True
) # Unique scheduler ID
# Schedule configuration
schedule = Column(String(100), nullable=False) # Cron expression
timezone = Column(String(50), default="UTC")
enabled = Column(Boolean, default=True, nullable=False, index=True)
# Job details
description = Column(Text)
function_name = Column(String(200), nullable=False) # Plugin function to call
job_data = Column(JSON) # Parameters for the job function
# Execution tracking
last_run_at = Column(DateTime)
next_run_at = Column(DateTime, index=True)
last_duration_ms = Column(Integer)
run_count = Column(Integer, default=0)
success_count = Column(Integer, default=0)
error_count = Column(Integer, default=0)
# Error handling
last_error = Column(Text)
last_error_at = Column(DateTime)
max_retries = Column(Integer, default=3)
retry_delay_seconds = Column(Integer, default=60)
# Lifecycle
created_at = Column(DateTime, nullable=False, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
created_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
# Relationships
plugin = relationship("Plugin", back_populates="cron_jobs")
created_by_user = relationship("User")
__table_args__ = (
Index("idx_plugin_cron_next_run", "enabled", "next_run_at"),
Index("idx_plugin_cron_plugin", "plugin_id", "enabled"),
)
class PluginAPIGateway(Base):
"""API gateway configuration for plugin routing"""
__tablename__ = "plugin_api_gateways"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
plugin_id = Column(
UUID(as_uuid=True), ForeignKey("plugins.id"), nullable=False, unique=True
)
# API routing configuration
base_path = Column(
String(200), nullable=False, unique=True
) # /api/v1/plugins/zammad
internal_url = Column(String(500), nullable=False) # http://plugin-zammad:8000
# Security settings
require_authentication = Column(Boolean, default=True, nullable=False)
allowed_methods = Column(
JSON, default=["GET", "POST", "PUT", "DELETE"]
) # HTTP methods
rate_limit_per_minute = Column(Integer, default=60)
rate_limit_per_hour = Column(Integer, default=1000)
# CORS settings
cors_enabled = Column(Boolean, default=True, nullable=False)
cors_origins = Column(JSON, default=["*"])
cors_methods = Column(JSON, default=["GET", "POST", "PUT", "DELETE", "OPTIONS"])
cors_headers = Column(JSON, default=["*"])
# Circuit breaker settings
circuit_breaker_enabled = Column(Boolean, default=True, nullable=False)
failure_threshold = Column(Integer, default=5)
recovery_timeout_seconds = Column(Integer, default=60)
# Monitoring
enabled = Column(Boolean, default=True, nullable=False, index=True)
last_health_check = Column(DateTime)
health_status = Column(String(20), default="unknown") # healthy, unhealthy, timeout
# Timestamps
created_at = Column(DateTime, nullable=False, default=func.now())
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
# Relationships
plugin = relationship("Plugin")
# Add relationships to existing User model (import this in user.py)
"""
Add to User model:
installed_plugins = relationship("Plugin", back_populates="installed_by_user")
"""
# Add relationships to existing APIKey model (import this in api_key.py)
"""
Add to APIKey model:
plugin_audit_logs = relationship("PluginAuditLog", back_populates="api_key")
"""
class PluginPermission(Base):
"""Plugin permission grants - tracks user permissions for plugins"""
__tablename__ = "plugin_permissions"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
plugin_id = Column(UUID(as_uuid=True), ForeignKey("plugins.id"), nullable=False)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
# Permission details
permission_name = Column(
String(200), nullable=False
) # e.g., 'chatbot:invoke', 'rag:query'
granted = Column(
Boolean, default=True, nullable=False
) # True=granted, False=revoked
# Grant/revoke tracking
granted_at = Column(DateTime, nullable=False, default=func.now())
granted_by_user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
revoked_at = Column(DateTime)
revoked_by_user_id = Column(Integer, ForeignKey("users.id"))
# Metadata
reason = Column(Text) # Reason for grant/revocation
expires_at = Column(DateTime) # Optional expiration time
# Relationships
plugin = relationship("Plugin")
user = relationship("User", foreign_keys=[user_id])
granted_by_user = relationship("User", foreign_keys=[granted_by_user_id])
revoked_by_user = relationship("User", foreign_keys=[revoked_by_user_id])
__table_args__ = (
Index("idx_plugin_permission_user_plugin", "user_id", "plugin_id"),
Index("idx_plugin_permission_plugin_name", "plugin_id", "permission_name"),
Index("idx_plugin_permission_active", "plugin_id", "user_id", "granted"),
)