feat: initial implementation of container management platform

This commit is contained in:
Tomas Dvorak
2026-02-16 10:18:05 +01:00
commit ffa5489dc1
167 changed files with 55910 additions and 0 deletions
+142
View File
@@ -0,0 +1,142 @@
-- Initial schema for Containr platform
-- This migration creates the core tables for users, projects, services, and deployments
-- Users table
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
avatar_url VARCHAR(500),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Projects table
CREATE TABLE IF NOT EXISTS projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
description TEXT,
owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Environments table
CREATE TABLE IF NOT EXISTS environments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(50) NOT NULL, -- 'production', 'preview', 'development'
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(name, project_id)
);
-- Services table
CREATE TABLE IF NOT EXISTS services (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
description TEXT,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
environment_id UUID NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
service_type VARCHAR(50) NOT NULL, -- 'web', 'worker', 'database', 'cron'
source_type VARCHAR(50) NOT NULL, -- 'github', 'dockerfile', 'image', 'template'
source_url VARCHAR(500),
image_name VARCHAR(500),
build_command TEXT,
start_command TEXT,
cpu_limit INTEGER,
memory_limit INTEGER, -- in MB
public_url VARCHAR(500),
health_check_url VARCHAR(500),
status VARCHAR(50) DEFAULT 'created', -- 'created', 'building', 'running', 'stopped', 'failed'
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Environment variables table
CREATE TABLE IF NOT EXISTS environment_variables (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
service_id UUID NOT NULL REFERENCES services(id) ON DELETE CASCADE,
key VARCHAR(255) NOT NULL,
value TEXT,
is_secret BOOLEAN DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(service_id, key)
);
-- Deployments table
CREATE TABLE IF NOT EXISTS deployments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
service_id UUID NOT NULL REFERENCES services(id) ON DELETE CASCADE,
version VARCHAR(100) NOT NULL,
commit_hash VARCHAR(100),
image_digest VARCHAR(500),
status VARCHAR(50) DEFAULT 'created', -- 'created', 'building', 'deploying', 'running', 'failed', 'rolled_back'
build_log TEXT,
deployment_log TEXT,
started_at TIMESTAMP WITH TIME ZONE,
completed_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Service dependencies table (for service-to-service relationships)
CREATE TABLE IF NOT EXISTS service_dependencies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
service_id UUID NOT NULL REFERENCES services(id) ON DELETE CASCADE,
depends_on_service_id UUID NOT NULL REFERENCES services(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(service_id, depends_on_service_id),
CHECK (service_id != depends_on_service_id)
);
-- Project members table (for collaboration)
CREATE TABLE IF NOT EXISTS project_members (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role VARCHAR(50) NOT NULL DEFAULT 'developer', -- 'owner', 'admin', 'developer', 'viewer'
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(project_id, user_id)
);
-- Indexes for better performance
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_projects_owner_id ON projects(owner_id);
CREATE INDEX IF NOT EXISTS idx_services_project_id ON services(project_id);
CREATE INDEX IF NOT EXISTS idx_services_environment_id ON services(environment_id);
CREATE INDEX IF NOT EXISTS idx_deployments_service_id ON deployments(service_id);
CREATE INDEX IF NOT EXISTS idx_deployments_status ON deployments(status);
CREATE INDEX IF NOT EXISTS idx_environment_variables_service_id ON environment_variables(service_id);
CREATE INDEX IF NOT EXISTS idx_project_members_project_id ON project_members(project_id);
CREATE INDEX IF NOT EXISTS idx_project_members_user_id ON project_members(user_id);
-- Update timestamp trigger function
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
-- Add update triggers to tables with updated_at columns
CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_projects_updated_at BEFORE UPDATE ON projects
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_environments_updated_at BEFORE UPDATE ON environments
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_services_updated_at BEFORE UPDATE ON services
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_environment_variables_updated_at BEFORE UPDATE ON environment_variables
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_deployments_updated_at BEFORE UPDATE ON deployments
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
+123
View File
@@ -0,0 +1,123 @@
-- Git integration schema for Containr platform
-- This migration adds tables for Git providers, repositories, and webhooks
-- Git providers table (GitHub, GitLab, Bitbucket accounts)
CREATE TABLE IF NOT EXISTS git_providers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(50) NOT NULL, -- 'github', 'gitlab', 'bitbucket'
display_name VARCHAR(255) NOT NULL, -- User-friendly name for the account
api_url VARCHAR(500) NOT NULL,
webhook_url VARCHAR(500) NOT NULL,
access_token TEXT NOT NULL, -- Encrypted in production
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(name, user_id)
);
-- Git repositories table (connected repositories)
CREATE TABLE IF NOT EXISTS git_repositories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
provider_id UUID NOT NULL REFERENCES git_providers(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
full_name VARCHAR(500) NOT NULL, -- e.g., "owner/repo-name"
description TEXT,
clone_url VARCHAR(500) NOT NULL,
webhook_url VARCHAR(500), -- Webhook URL on the Git provider
default_branch VARCHAR(100) DEFAULT 'main',
is_private BOOLEAN DEFAULT false,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(provider_id, full_name)
);
-- Git webhooks table (webhook configurations)
CREATE TABLE IF NOT EXISTS git_webhooks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES git_repositories(id) ON DELETE CASCADE,
provider_id UUID NOT NULL REFERENCES git_providers(id) ON DELETE CASCADE,
events TEXT NOT NULL, -- JSON array of webhook events
webhook_secret TEXT NOT NULL, -- Secret for validating webhook payloads
remote_webhook_id VARCHAR(255), -- Webhook ID on the Git provider
active BOOLEAN DEFAULT true,
branch_filter VARCHAR(100), -- Optional branch filter for deployments
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(repo_id, provider_id)
);
-- Git branches table (for tracking branch-specific deployments)
CREATE TABLE IF NOT EXISTS git_branches (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo_id UUID NOT NULL REFERENCES git_repositories(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, -- Branch name
last_commit_hash VARCHAR(100),
last_commit_message TEXT,
last_commit_author VARCHAR(255),
last_commit_date TIMESTAMP WITH TIME ZONE,
is_protected BOOLEAN DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(repo_id, name)
);
-- Git deployment triggers table (links webhooks to services)
CREATE TABLE IF NOT EXISTS git_deployment_triggers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
webhook_id UUID NOT NULL REFERENCES git_webhooks(id) ON DELETE CASCADE,
service_id UUID NOT NULL REFERENCES services(id) ON DELETE CASCADE,
branch VARCHAR(255) NOT NULL, -- Branch to trigger deployment for
environment VARCHAR(50) NOT NULL, -- Target environment
auto_deploy BOOLEAN DEFAULT false, -- Whether to auto-deploy on push
build_command TEXT,
start_command TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(webhook_id, service_id, branch)
);
-- Indexes for better performance
CREATE INDEX IF NOT EXISTS idx_git_providers_user_id ON git_providers(user_id);
CREATE INDEX IF NOT EXISTS idx_git_providers_name ON git_providers(name);
CREATE INDEX IF NOT EXISTS idx_git_repositories_provider_id ON git_repositories(provider_id);
CREATE INDEX IF NOT EXISTS idx_git_repositories_user_id ON git_repositories(user_id);
CREATE INDEX IF NOT EXISTS idx_git_repositories_full_name ON git_repositories(full_name);
CREATE INDEX IF NOT EXISTS idx_git_webhooks_repo_id ON git_webhooks(repo_id);
CREATE INDEX IF NOT EXISTS idx_git_webhooks_provider_id ON git_webhooks(provider_id);
CREATE INDEX IF NOT EXISTS idx_git_webhooks_active ON git_webhooks(active);
CREATE INDEX IF NOT EXISTS idx_git_branches_repo_id ON git_branches(repo_id);
CREATE INDEX IF NOT EXISTS idx_git_branches_name ON git_branches(name);
CREATE INDEX IF NOT EXISTS idx_git_deployment_triggers_webhook_id ON git_deployment_triggers(webhook_id);
CREATE INDEX IF NOT EXISTS idx_git_deployment_triggers_service_id ON git_deployment_triggers(service_id);
CREATE INDEX IF NOT EXISTS idx_git_deployment_triggers_branch ON git_deployment_triggers(branch);
-- Add update triggers to new tables
CREATE TRIGGER update_git_providers_updated_at BEFORE UPDATE ON git_providers
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_git_repositories_updated_at BEFORE UPDATE ON git_repositories
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_git_webhooks_updated_at BEFORE UPDATE ON git_webhooks
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_git_branches_updated_at BEFORE UPDATE ON git_branches
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_git_deployment_triggers_updated_at BEFORE UPDATE ON git_deployment_triggers
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Add foreign key constraint for project_members table if not exists
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'project_members_user_id_fkey'
) THEN
ALTER TABLE project_members
ADD CONSTRAINT project_members_user_id_fkey
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
END IF;
END
$$;
+178
View File
@@ -0,0 +1,178 @@
-- Agent system migration for Phase 3: Node Agent System
-- Create node_agents table
CREATE TABLE IF NOT EXISTS node_agents (
id VARCHAR(255) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
hostname VARCHAR(255) NOT NULL,
ip_address VARCHAR(45) NOT NULL,
port INTEGER NOT NULL,
status VARCHAR(50) DEFAULT 'offline',
version VARCHAR(50),
capabilities JSONB,
resources JSONB,
last_heartbeat TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
metadata JSONB
);
-- Create container_instances table
CREATE TABLE IF NOT EXISTS container_instances (
id VARCHAR(255) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
image VARCHAR(255) NOT NULL,
project_id VARCHAR(255) NOT NULL,
service_id VARCHAR(255) NOT NULL,
node_agent_id VARCHAR(255) NOT NULL,
status JSONB,
resources JSONB,
ports JSONB,
environment JSONB,
volumes JSONB,
networks JSONB,
restart_policy JSONB,
health_check JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
started_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
FOREIGN KEY (node_agent_id) REFERENCES node_agents(id) ON DELETE CASCADE
);
-- Create agent_commands table
CREATE TABLE IF NOT EXISTS agent_commands (
id VARCHAR(255) PRIMARY KEY,
type VARCHAR(100) NOT NULL,
node_agent_id VARCHAR(255) NOT NULL,
container_id VARCHAR(255),
payload JSONB,
status VARCHAR(50) DEFAULT 'pending',
result TEXT,
error TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
completed_at TIMESTAMP WITH TIME ZONE,
FOREIGN KEY (node_agent_id) REFERENCES node_agents(id) ON DELETE CASCADE,
FOREIGN KEY (container_id) REFERENCES container_instances(id) ON DELETE CASCADE
);
-- Create node_clusters table
CREATE TABLE IF NOT EXISTS node_clusters (
id VARCHAR(255) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
status VARCHAR(50) DEFAULT 'active',
total_resources JSONB,
used_resources JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create cluster_agents table to link clusters and agents
CREATE TABLE IF NOT EXISTS cluster_agents (
cluster_id VARCHAR(255) NOT NULL,
agent_id VARCHAR(255) NOT NULL,
added_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
PRIMARY KEY (cluster_id, agent_id),
FOREIGN KEY (cluster_id) REFERENCES node_clusters(id) ON DELETE CASCADE,
FOREIGN KEY (agent_id) REFERENCES node_agents(id) ON DELETE CASCADE
);
-- Create scheduling_rules table
CREATE TABLE IF NOT EXISTS scheduling_rules (
id VARCHAR(255) PRIMARY KEY,
cluster_id VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL,
selector JSONB,
weight INTEGER DEFAULT 1,
enabled BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
FOREIGN KEY (cluster_id) REFERENCES node_clusters(id) ON DELETE CASCADE
);
-- Create container_metrics table for monitoring
CREATE TABLE IF NOT EXISTS container_metrics (
id SERIAL PRIMARY KEY,
container_id VARCHAR(255) NOT NULL,
timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
cpu_usage DECIMAL(5,2),
cpu_usage_percent DECIMAL(5,2),
memory_usage BIGINT,
memory_usage_percent DECIMAL(5,2),
memory_limit BIGINT,
network_rx_bytes BIGINT,
network_tx_bytes BIGINT,
network_rx_packets BIGINT,
network_tx_packets BIGINT,
block_read_bytes BIGINT,
block_write_bytes BIGINT,
pids_current INTEGER,
pids_limit INTEGER,
FOREIGN KEY (container_id) REFERENCES container_instances(id) ON DELETE CASCADE
);
-- Create indexes for performance
CREATE INDEX IF NOT EXISTS idx_node_agents_status ON node_agents(status);
CREATE INDEX IF NOT EXISTS idx_node_agents_hostname ON node_agents(hostname);
CREATE INDEX IF NOT EXISTS idx_node_agents_ip_address ON node_agents(ip_address);
CREATE INDEX IF NOT EXISTS idx_node_agents_last_heartbeat ON node_agents(last_heartbeat);
CREATE INDEX IF NOT EXISTS idx_container_instances_project_id ON container_instances(project_id);
CREATE INDEX IF NOT EXISTS idx_container_instances_service_id ON container_instances(service_id);
CREATE INDEX IF NOT EXISTS idx_container_instances_node_agent_id ON container_instances(node_agent_id);
CREATE INDEX IF NOT EXISTS idx_container_instances_status ON container_instances USING GIN(status);
CREATE INDEX IF NOT EXISTS idx_agent_commands_node_agent_id ON agent_commands(node_agent_id);
CREATE INDEX IF NOT EXISTS idx_agent_commands_status ON agent_commands(status);
CREATE INDEX IF NOT EXISTS idx_agent_commands_type ON agent_commands(type);
CREATE INDEX IF NOT EXISTS idx_agent_commands_created_at ON agent_commands(created_at);
CREATE INDEX IF NOT EXISTS idx_container_metrics_container_id ON container_metrics(container_id);
CREATE INDEX IF NOT EXISTS idx_container_metrics_timestamp ON container_metrics(timestamp);
-- Create updated_at trigger function
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
-- Create triggers for updated_at
CREATE TRIGGER update_node_agents_updated_at BEFORE UPDATE ON node_agents
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_container_instances_updated_at BEFORE UPDATE ON container_instances
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_agent_commands_updated_at BEFORE UPDATE ON agent_commands
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_node_clusters_updated_at BEFORE UPDATE ON node_clusters
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_scheduling_rules_updated_at BEFORE UPDATE ON scheduling_rules
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Insert default cluster
INSERT INTO node_clusters (id, name, description, status, total_resources, used_resources)
VALUES (
'default-cluster',
'Default Cluster',
'Default cluster for all node agents',
'active',
'{"cpu": {"cores": 0, "allocation": 0, "usage": 0}, "memory": {"total": 0, "allocated": 0, "used": 0, "available": 0}, "storage": {"total": 0, "allocated": 0, "used": 0, "available": 0}, "network": {"interfaces": [], "bandwidth": {"inbound": 0, "outbound": 0}}}',
'{"cpu": {"cores": 0, "allocation": 0, "usage": 0}, "memory": {"total": 0, "allocated": 0, "used": 0, "available": 0}, "storage": {"total": 0, "allocated": 0, "used": 0, "available": 0}, "network": {"interfaces": [], "bandwidth": {"inbound": 0, "outbound": 0}}}'
) ON CONFLICT (id) DO NOTHING;
-- Add comment to tables
COMMENT ON TABLE node_agents IS 'Container orchestration agents that manage containers on nodes';
COMMENT ON TABLE container_instances IS 'Container instances running on node agents';
COMMENT ON TABLE agent_commands IS 'Commands sent to node agents for execution';
COMMENT ON TABLE node_clusters IS 'Clusters of node agents for resource pooling';
COMMENT ON TABLE cluster_agents IS 'Many-to-many relationship between clusters and agents';
COMMENT ON TABLE scheduling_rules IS 'Rules for scheduling containers on agents';
COMMENT ON TABLE container_metrics IS 'Metrics collected from running containers';
+333
View File
@@ -0,0 +1,333 @@
-- Metrics Schema Migration
-- This migration creates tables for storing system and service metrics
-- Enable TimescaleDB extension for time-series data (optional)
CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
-- Node metrics table
CREATE TABLE IF NOT EXISTS node_metrics (
node_id VARCHAR(255) NOT NULL,
timestamp TIMESTAMPTZ NOT NULL,
cpu_usage DECIMAL(5,2),
cpu_cores DECIMAL(10,2),
load_avg_1 DECIMAL(5,2),
load_avg_5 DECIMAL(5,2),
load_avg_15 DECIMAL(5,2),
memory_total BIGINT,
memory_used BIGINT,
memory_available BIGINT,
memory_usage_percent DECIMAL(5,2),
storage_total BIGINT,
storage_used BIGINT,
storage_available BIGINT,
storage_usage_percent DECIMAL(5,2),
network_bytes_in BIGINT,
network_bytes_out BIGINT,
network_packets_in BIGINT,
network_packets_out BIGINT,
network_connections_in INTEGER,
network_connections_out INTEGER,
network_errors_in BIGINT,
network_errors_out BIGINT,
uptime INTERVAL,
processes INTEGER,
os VARCHAR(50),
kernel VARCHAR(50),
architecture VARCHAR(20),
created_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (node_id, timestamp)
);
-- Create index for time-series queries
CREATE INDEX IF NOT EXISTS idx_node_metrics_timestamp ON node_metrics (timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_node_metrics_node_timestamp ON node_metrics (node_id, timestamp DESC);
-- Container metrics table
CREATE TABLE IF NOT EXISTS container_metrics (
node_id VARCHAR(255) NOT NULL,
timestamp TIMESTAMPTZ NOT NULL,
container_id VARCHAR(255) NOT NULL,
name VARCHAR(255),
state VARCHAR(50),
cpu DECIMAL(5,2),
memory BIGINT,
network_bytes_in BIGINT,
network_bytes_out BIGINT,
network_packets_in BIGINT,
network_packets_out BIGINT,
start_time TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (node_id, timestamp, container_id),
FOREIGN KEY (node_id, timestamp) REFERENCES node_metrics (node_id, timestamp) ON DELETE CASCADE
);
-- Service metrics table
CREATE TABLE IF NOT EXISTS service_metrics (
service_id VARCHAR(255) NOT NULL,
service_name VARCHAR(255) NOT NULL,
project_id VARCHAR(255) NOT NULL,
timestamp TIMESTAMPTZ NOT NULL,
requests_total BIGINT DEFAULT 0,
requests_success BIGINT DEFAULT 0,
requests_errors BIGINT DEFAULT 0,
requests_avg_latency DECIMAL(10,3),
requests_p95_latency DECIMAL(10,3),
requests_p99_latency DECIMAL(10,3),
requests_throughput DECIMAL(10,3),
errors_total BIGINT DEFAULT 0,
errors_rate DECIMAL(5,4),
performance_response_time DECIMAL(10,3),
performance_throughput DECIMAL(10,3),
performance_concurrency BIGINT,
performance_saturation DECIMAL(5,2),
performance_utilization DECIMAL(5,2),
resource_cpu_usage DECIMAL(5,2),
resource_memory_usage BIGINT,
resource_storage_usage BIGINT,
resource_network_usage BIGINT,
resource_score DECIMAL(5,2),
created_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (service_id, timestamp)
);
-- Create indexes for service metrics
CREATE INDEX IF NOT EXISTS idx_service_metrics_timestamp ON service_metrics (timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_service_metrics_service_timestamp ON service_metrics (service_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_service_metrics_project_timestamp ON service_metrics (project_id, timestamp DESC);
-- Instance metrics table
CREATE TABLE IF NOT EXISTS instance_metrics (
service_id VARCHAR(255) NOT NULL,
timestamp TIMESTAMPTZ NOT NULL,
instance_id VARCHAR(255) NOT NULL,
node_id VARCHAR(255),
status VARCHAR(50),
cpu DECIMAL(5,2),
memory BIGINT,
network_bytes_in BIGINT,
network_bytes_out BIGINT,
network_packets_in BIGINT,
network_packets_out BIGINT,
network_connections_in INTEGER,
network_connections_out INTEGER,
network_errors_in BIGINT,
network_errors_out BIGINT,
start_time TIMESTAMPTZ,
last_seen TIMESTAMPTZ,
health_status VARCHAR(20),
health_last_check TIMESTAMPTZ,
health_check_count INTEGER DEFAULT 0,
health_failure_count INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (service_id, timestamp, instance_id),
FOREIGN KEY (service_id, timestamp) REFERENCES service_metrics (service_id, timestamp) ON DELETE CASCADE
);
-- Service discovery table
CREATE TABLE IF NOT EXISTS service_discovery (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
service_id VARCHAR(255) NOT NULL,
service_name VARCHAR(255) NOT NULL,
project_id VARCHAR(255) NOT NULL,
instance_id VARCHAR(255) NOT NULL,
node_id VARCHAR(255),
ip_address INET NOT NULL,
port INTEGER,
status VARCHAR(50) DEFAULT 'unknown',
health_status VARCHAR(20) DEFAULT 'unknown',
labels JSONB DEFAULT '{}',
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
last_seen TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(service_id, instance_id)
);
-- Create indexes for service discovery
CREATE INDEX IF NOT EXISTS idx_service_discovery_service ON service_discovery (service_id);
CREATE INDEX IF NOT EXISTS idx_service_discovery_project ON service_discovery (project_id);
CREATE INDEX IF NOT EXISTS idx_service_discovery_name ON service_discovery (service_name);
CREATE INDEX IF NOT EXISTS idx_service_discovery_status ON service_discovery (status);
CREATE INDEX IF NOT EXISTS idx_service_discovery_ip ON service_discovery (ip_address);
CREATE INDEX IF NOT EXISTS idx_service_discovery_labels ON service_discovery USING GIN (labels);
-- DNS records table
CREATE TABLE IF NOT EXISTS dns_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
type VARCHAR(10) NOT NULL, -- A, SRV, CNAME, etc.
ttl INTEGER DEFAULT 300,
records JSONB NOT NULL, -- Array of records
priority INTEGER,
weight INTEGER,
port INTEGER,
service_id VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Create indexes for DNS records
CREATE INDEX IF NOT EXISTS idx_dns_records_name ON dns_records (name);
CREATE INDEX IF NOT EXISTS idx_dns_records_type ON dns_records (type);
CREATE INDEX IF NOT EXISTS idx_dns_records_service ON dns_records (service_id);
-- Metrics aggregation rules table
CREATE TABLE IF NOT EXISTS metrics_aggregation_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE,
metric_type VARCHAR(50) NOT NULL, -- node, service, container
aggregation_function VARCHAR(50) NOT NULL, -- avg, sum, min, max, count
interval INTERVAL NOT NULL, -- 1m, 5m, 1h, etc.
retention_period INTERVAL DEFAULT '30 days',
fields JSONB NOT NULL, -- Which fields to aggregate
filters JSONB DEFAULT '{}', -- Optional filters
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Create indexes for aggregation rules
CREATE INDEX IF NOT EXISTS idx_metrics_aggregation_rules_type ON metrics_aggregation_rules (metric_type);
-- Alert rules table
CREATE TABLE IF NOT EXISTS alert_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
description TEXT,
metric_type VARCHAR(50) NOT NULL,
metric_field VARCHAR(100) NOT NULL,
condition VARCHAR(20) NOT NULL, -- gt, lt, eq, gte, lte
threshold DECIMAL(15,4) NOT NULL,
duration INTERVAL DEFAULT '5 minutes',
severity VARCHAR(20) DEFAULT 'warning', -- critical, warning, info
enabled BOOLEAN DEFAULT true,
filters JSONB DEFAULT '{}',
notification_channels JSONB DEFAULT '[]',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Create indexes for alert rules
CREATE INDEX IF NOT EXISTS idx_alert_rules_type ON alert_rules (metric_type);
CREATE INDEX IF NOT EXISTS idx_alert_rules_enabled ON alert_rules (enabled);
-- Alert incidents table
CREATE TABLE IF NOT EXISTS alert_incidents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
rule_id UUID NOT NULL REFERENCES alert_rules (id) ON DELETE CASCADE,
metric_type VARCHAR(50) NOT NULL,
metric_field VARCHAR(100) NOT NULL,
current_value DECIMAL(15,4) NOT NULL,
threshold DECIMAL(15,4) NOT NULL,
severity VARCHAR(20) NOT NULL,
status VARCHAR(20) DEFAULT 'firing', -- firing, resolved
started_at TIMESTAMPTZ NOT NULL,
resolved_at TIMESTAMPTZ,
duration INTERVAL,
description TEXT,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Create indexes for alert incidents
CREATE INDEX IF NOT EXISTS idx_alert_incidents_rule ON alert_incidents (rule_id);
CREATE INDEX IF NOT EXISTS idx_alert_incidents_status ON alert_incidents (status);
CREATE INDEX IF NOT EXISTS idx_alert_incidents_started ON alert_incidents (started_at DESC);
-- Create TimescaleDB hypertables if TimescaleDB is available
DO $$
BEGIN
-- Only create hypertables if TimescaleDB extension is available
IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'timescaledb') THEN
PERFORM create_hypertable('node_metrics', 'timestamp', chunk_time_interval => INTERVAL '1 hour');
PERFORM create_hypertable('service_metrics', 'timestamp', chunk_time_interval => INTERVAL '1 hour');
PERFORM create_hypertable('container_metrics', 'timestamp', chunk_time_interval => INTERVAL '1 hour');
PERFORM create_hypertable('instance_metrics', 'timestamp', chunk_time_interval => INTERVAL '1 hour');
-- Create compression policies for older data
PERFORM add_compression_policy('node_metrics', INTERVAL '7 days');
PERFORM add_compression_policy('service_metrics', INTERVAL '7 days');
PERFORM add_compression_policy('container_metrics', INTERVAL '7 days');
PERFORM add_compression_policy('instance_metrics', INTERVAL '7 days');
-- Create retention policies
PERFORM add_retention_policy('node_metrics', INTERVAL '90 days');
PERFORM add_retention_policy('service_metrics', INTERVAL '90 days');
PERFORM add_retention_policy('container_metrics', INTERVAL '90 days');
PERFORM add_retention_policy('instance_metrics', INTERVAL '90 days');
END IF;
END $$;
-- Create updated_at trigger function
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
-- Create triggers for updated_at columns
CREATE TRIGGER update_service_discovery_updated_at BEFORE UPDATE ON service_discovery FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_dns_records_updated_at BEFORE UPDATE ON dns_records FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_metrics_aggregation_rules_updated_at BEFORE UPDATE ON metrics_aggregation_rules FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_alert_rules_updated_at BEFORE UPDATE ON alert_rules FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Insert default aggregation rules
INSERT INTO metrics_aggregation_rules (name, metric_type, aggregation_function, interval, fields) VALUES
('node_cpu_1m', 'node', 'avg', INTERVAL '1 minute', '{"cpu_usage": true, "memory_usage_percent": true}'),
('node_cpu_5m', 'node', 'avg', INTERVAL '5 minutes', '{"cpu_usage": true, "memory_usage_percent": true}'),
('node_cpu_1h', 'node', 'avg', INTERVAL '1 hour', '{"cpu_usage": true, "memory_usage_percent": true, "storage_usage_percent": true}'),
('service_requests_1m', 'service', 'sum', INTERVAL '1 minute', '{"requests_total": true, "requests_success": true, "requests_errors": true}'),
('service_requests_5m', 'service', 'sum', INTERVAL '5 minutes', '{"requests_total": true, "requests_success": true, "requests_errors": true}'),
('service_performance_5m', 'service', 'avg', INTERVAL '5 minutes', '{"requests_avg_latency": true, "requests_p95_latency": true, "requests_throughput": true}')
ON CONFLICT (name) DO NOTHING;
-- Insert default alert rules
INSERT INTO alert_rules (name, description, metric_type, metric_field, condition, threshold, severity) VALUES
('High CPU Usage', 'Node CPU usage is above 80%', 'node', 'cpu_usage', 'gt', 80.0, 'warning'),
('Critical CPU Usage', 'Node CPU usage is above 95%', 'node', 'cpu_usage', 'gt', 95.0, 'critical'),
('High Memory Usage', 'Node memory usage is above 85%', 'node', 'memory_usage_percent', 'gt', 85.0, 'warning'),
('Critical Memory Usage', 'Node memory usage is above 95%', 'node', 'memory_usage_percent', 'gt', 95.0, 'critical'),
('High Error Rate', 'Service error rate is above 10%', 'service', 'errors_rate', 'gt', 0.10, 'warning'),
('Critical Error Rate', 'Service error rate is above 25%', 'service', 'errors_rate', 'gt', 0.25, 'critical'),
('High Latency', 'Service P95 latency is above 1000ms', 'service', 'requests_p95_latency', 'gt', 1000.0, 'warning'),
('Critical Latency', 'Service P95 latency is above 5000ms', 'service', 'requests_p95_latency', 'gt', 5000.0, 'critical')
ON CONFLICT (name) DO NOTHING;
-- Create views for common queries
CREATE OR REPLACE VIEW node_metrics_summary AS
SELECT
node_id,
timestamp,
cpu_usage,
memory_usage_percent,
storage_usage_percent,
network_bytes_in + network_bytes_out as total_network_bytes,
load_avg_1,
uptime
FROM node_metrics
ORDER BY timestamp DESC;
CREATE OR REPLACE VIEW service_metrics_summary AS
SELECT
service_id,
service_name,
project_id,
timestamp,
requests_total,
requests_success,
requests_errors,
CASE WHEN requests_total > 0 THEN (requests_errors::DECIMAL / requests_total) ELSE 0 END as error_rate,
requests_avg_latency,
requests_p95_latency,
requests_throughput,
resource_cpu_usage,
resource_memory_usage
FROM service_metrics
ORDER BY timestamp DESC;
-- Grant permissions (adjust as needed for your setup)
-- GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO containr_app;
-- GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO containr_app;
COMMIT;
+255
View File
@@ -0,0 +1,255 @@
-- Database Services Migration
-- This migration creates tables for managed database services
-- Database Services table
CREATE TABLE IF NOT EXISTS database_services (
id VARCHAR(255) PRIMARY KEY,
user_id VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL CHECK (type IN ('postgresql', 'redis', 'mysql')),
status VARCHAR(50) NOT NULL DEFAULT 'building' CHECK (status IN ('running', 'stopped', 'building', 'error')),
version VARCHAR(50) NOT NULL,
plan VARCHAR(50) NOT NULL CHECK (plan IN ('hobby', 'starter', 'standard', 'business')),
region VARCHAR(50) NOT NULL,
connection_url TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT fk_database_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
-- Database Backups table
CREATE TABLE IF NOT EXISTS database_backups (
id VARCHAR(255) PRIMARY KEY,
database_id VARCHAR(255) NOT NULL,
size VARCHAR(50) NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'in_progress' CHECK (status IN ('completed', 'failed', 'in_progress')),
backup_path TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
completed_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT fk_backup_database FOREIGN KEY (database_id) REFERENCES database_services(id) ON DELETE CASCADE
);
-- Database Settings table
CREATE TABLE IF NOT EXISTS database_settings (
database_id VARCHAR(255) PRIMARY KEY,
max_connections INTEGER DEFAULT 100,
timeout INTEGER DEFAULT 30,
ssl_enabled BOOLEAN DEFAULT true,
logging_enabled BOOLEAN DEFAULT true,
retention_days INTEGER DEFAULT 30,
backup_enabled BOOLEAN DEFAULT true,
next_backup_time TIMESTAMP WITH TIME ZONE,
last_backup_time TIMESTAMP WITH TIME ZONE,
CONSTRAINT fk_settings_database FOREIGN KEY (database_id) REFERENCES database_services(id) ON DELETE CASCADE
);
-- Database Metrics table for storing historical metrics
CREATE TABLE IF NOT EXISTS database_metrics (
id SERIAL PRIMARY KEY,
database_id VARCHAR(255) NOT NULL,
cpu_usage DECIMAL(5,2),
memory_usage DECIMAL(5,2),
storage_usage DECIMAL(5,2),
active_connections INTEGER,
read_iops INTEGER,
write_iops INTEGER,
network_in_mbps DECIMAL(8,2),
network_out_mbps DECIMAL(8,2),
recorded_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT fk_metrics_database FOREIGN KEY (database_id) REFERENCES database_services(id) ON DELETE CASCADE
);
-- Create indexes for better performance
CREATE INDEX IF NOT EXISTS idx_database_services_user_id ON database_services(user_id);
CREATE INDEX IF NOT EXISTS idx_database_services_type ON database_services(type);
CREATE INDEX IF NOT EXISTS idx_database_services_status ON database_services(status);
CREATE INDEX IF NOT EXISTS idx_database_services_created_at ON database_services(created_at);
CREATE INDEX IF NOT EXISTS idx_database_backups_database_id ON database_backups(database_id);
CREATE INDEX IF NOT EXISTS idx_database_backups_created_at ON database_backups(created_at);
CREATE INDEX IF NOT EXISTS idx_database_backups_status ON database_backups(status);
CREATE INDEX IF NOT EXISTS idx_database_metrics_database_id ON database_metrics(database_id);
CREATE INDEX IF NOT EXISTS idx_database_metrics_recorded_at ON database_metrics(recorded_at);
-- Create trigger to update updated_at timestamp
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_database_services_updated_at
BEFORE UPDATE ON database_services
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- Insert default settings for existing databases (if any)
INSERT INTO database_settings (database_id)
SELECT id FROM database_services
WHERE id NOT IN (SELECT database_id FROM database_settings);
-- Create view for database statistics
CREATE OR REPLACE VIEW database_stats AS
SELECT
ds.id,
ds.name,
ds.type,
ds.status,
ds.plan,
ds.region,
ds.created_at,
ds.updated_at,
COUNT(db.id) as backup_count,
MAX(db.created_at) as last_backup_time,
dm.cpu_usage as latest_cpu,
dm.memory_usage as latest_memory,
dm.storage_usage as latest_storage,
dm.active_connections as latest_connections,
dm.recorded_at as metrics_updated_at
FROM database_services ds
LEFT JOIN database_backups db ON ds.id = db.database_id AND db.status = 'completed'
LEFT JOIN database_metrics dm ON ds.id = dm.database_id
LEFT JOIN LATERAL (
SELECT cpu_usage, memory_usage, storage_usage, active_connections, recorded_at
FROM database_metrics
WHERE database_id = ds.id
ORDER BY recorded_at DESC
LIMIT 1
) dm ON true
GROUP BY ds.id, ds.name, ds.type, ds.status, ds.plan, ds.region, ds.created_at, ds.updated_at,
dm.cpu_usage, dm.memory_usage, dm.storage_usage, dm.active_connections, dm.recorded_at;
-- Add RLS (Row Level Security) policies
ALTER TABLE database_services ENABLE ROW LEVEL SECURITY;
ALTER TABLE database_backups ENABLE ROW LEVEL SECURITY;
ALTER TABLE database_settings ENABLE ROW LEVEL SECURITY;
ALTER TABLE database_metrics ENABLE ROW LEVEL SECURITY;
-- Policy for database services - users can only see their own databases
CREATE POLICY "Users can view their own database services" ON database_services
FOR SELECT USING (user_id = current_setting('app.current_user_id', true)::VARCHAR);
CREATE POLICY "Users can insert their own database services" ON database_services
FOR INSERT WITH CHECK (user_id = current_setting('app.current_user_id', true)::VARCHAR);
CREATE POLICY "Users can update their own database services" ON database_services
FOR UPDATE USING (user_id = current_setting('app.current_user_id', true)::VARCHAR);
CREATE POLICY "Users can delete their own database services" ON database_services
FOR DELETE USING (user_id = current_setting('app.current_user_id', true)::VARCHAR);
-- Policies for backups (inherited from database services)
CREATE POLICY "Users can view backups of their own databases" ON database_backups
FOR SELECT USING (
database_id IN (
SELECT id FROM database_services
WHERE user_id = current_setting('app.current_user_id', true)::VARCHAR
)
);
CREATE POLICY "Users can insert backups for their own databases" ON database_backups
FOR INSERT WITH CHECK (
database_id IN (
SELECT id FROM database_services
WHERE user_id = current_setting('app.current_user_id', true)::VARCHAR
)
);
-- Policies for settings (inherited from database services)
CREATE POLICY "Users can view settings of their own databases" ON database_settings
FOR SELECT USING (
database_id IN (
SELECT id FROM database_services
WHERE user_id = current_setting('app.current_user_id', true)::VARCHAR
)
);
CREATE POLICY "Users can update settings of their own databases" ON database_settings
FOR UPDATE USING (
database_id IN (
SELECT id FROM database_services
WHERE user_id = current_setting('app.current_user_id', true)::VARCHAR
)
);
-- Policies for metrics (inherited from database services)
CREATE POLICY "Users can view metrics of their own databases" ON database_metrics
FOR SELECT USING (
database_id IN (
SELECT id FROM database_services
WHERE user_id = current_setting('app.current_user_id', true)::VARCHAR
)
);
CREATE POLICY "Users can insert metrics for their own databases" ON database_metrics
FOR INSERT WITH CHECK (
database_id IN (
SELECT id FROM database_services
WHERE user_id = current_setting('app.current_user_id', true)::VARCHAR
)
);
-- Grant permissions
GRANT SELECT, INSERT, UPDATE, DELETE ON database_services TO authenticated_users;
GRANT SELECT, INSERT, UPDATE ON database_backups TO authenticated_users;
GRANT SELECT, UPDATE ON database_settings TO authenticated_users;
GRANT SELECT, INSERT ON database_metrics TO authenticated_users;
GRANT SELECT ON database_stats TO authenticated_users;
-- Create function to clean up old metrics (older than 30 days)
CREATE OR REPLACE FUNCTION cleanup_old_metrics()
RETURNS void AS $$
BEGIN
DELETE FROM database_metrics
WHERE recorded_at < NOW() - INTERVAL '30 days';
END;
$$ LANGUAGE plpgsql;
-- Create function to schedule next backup
CREATE OR REPLACE FUNCTION schedule_next_backup(database_id_param VARCHAR(255))
RETURNS void AS $$
BEGIN
UPDATE database_settings
SET next_backup_time = NOW() + INTERVAL '24 hours'
WHERE database_id = database_id_param;
END;
$$ LANGUAGE plpgsql;
-- Create function to update backup status and schedule next backup
CREATE OR REPLACE FUNCTION complete_backup(backup_id_param VARCHAR(255, success_param BOOLEAN))
RETURNS void AS $$
DECLARE
db_id VARCHAR(255);
BEGIN
-- Get database_id from backup
SELECT database_id INTO db_id FROM database_backups WHERE id = backup_id_param;
IF db_id IS NOT NULL THEN
-- Update backup completion time if successful
IF success_param THEN
UPDATE database_backups
SET status = 'completed', completed_at = NOW()
WHERE id = backup_id_param;
-- Update last_backup_time in settings
UPDATE database_settings
SET last_backup_time = NOW()
WHERE database_id = db_id;
-- Schedule next backup
PERFORM schedule_next_backup(db_id);
ELSE
UPDATE database_backups
SET status = 'failed'
WHERE id = backup_id_param;
END IF;
END IF;
END;
$$ LANGUAGE plpgsql;
+54
View File
@@ -0,0 +1,54 @@
-- Add preview environments table
CREATE TABLE IF NOT EXISTS preview_environments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
service_id UUID NOT NULL REFERENCES services(id) ON DELETE CASCADE,
branch_name VARCHAR(255) NOT NULL,
pr_number INTEGER, -- Optional: Pull request number if applicable
environment VARCHAR(255) NOT NULL UNIQUE, -- e.g., preview-feature-branch-20240101-120000
status VARCHAR(50) NOT NULL DEFAULT 'building' CHECK (status IN ('building', 'running', 'failed', 'stopped', 'expired')),
url TEXT, -- Preview environment URL
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create indexes for better performance
CREATE INDEX IF NOT EXISTS idx_preview_environments_project_id ON preview_environments(project_id);
CREATE INDEX IF NOT EXISTS idx_preview_environments_service_id ON preview_environments(service_id);
CREATE INDEX IF NOT EXISTS idx_preview_environments_branch_name ON preview_environments(branch_name);
CREATE INDEX IF NOT EXISTS idx_preview_environments_status ON preview_environments(status);
CREATE INDEX IF NOT EXISTS idx_preview_environments_expires_at ON preview_environments(expires_at);
-- Add trigger to update updated_at timestamp
CREATE OR REPLACE FUNCTION update_preview_environments_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER preview_environments_updated_at
BEFORE UPDATE ON preview_environments
FOR EACH ROW
EXECUTE FUNCTION update_preview_environments_updated_at();
-- Add unique constraint to prevent duplicate preview environments for same service and branch
CREATE UNIQUE INDEX IF NOT EXISTS idx_preview_environments_unique_active
ON preview_environments(service_id, branch_name)
WHERE status NOT IN ('expired', 'stopped');
-- Add comments for documentation
COMMENT ON TABLE preview_environments IS 'Preview environments for branch-based deployments';
COMMENT ON COLUMN preview_environments.id IS 'Unique identifier for the preview environment';
COMMENT ON COLUMN preview_environments.project_id IS 'Reference to the project';
COMMENT ON COLUMN preview_environments.service_id IS 'Reference to the service';
COMMENT ON COLUMN preview_environments.branch_name IS 'Git branch name';
COMMENT ON COLUMN preview_environments.pr_number IS 'Pull request number (optional)';
COMMENT ON COLUMN preview_environments.environment IS 'Environment name (e.g., preview-feature-branch-20240101-120000)';
COMMENT ON COLUMN preview_environments.status IS 'Current status of the preview environment';
COMMENT ON COLUMN preview_environments.url IS 'URL where the preview environment is accessible';
COMMENT ON COLUMN preview_environments.expires_at IS 'When the preview environment expires';
COMMENT ON COLUMN preview_environments.created_at IS 'When the preview environment was created';
COMMENT ON COLUMN preview_environments.updated_at IS 'When the preview environment was last updated';
+224
View File
@@ -0,0 +1,224 @@
-- Security Features Migration
-- This migration adds tables for security scanning, compliance, and audit logging
-- Security scans table
CREATE TABLE IF NOT EXISTS security_scans (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
service_id UUID REFERENCES services(id) ON DELETE CASCADE,
scan_type VARCHAR(50) NOT NULL CHECK (scan_type IN ('dependency', 'configuration', 'comprehensive')),
status VARCHAR(50) NOT NULL DEFAULT 'running' CHECK (status IN ('running', 'completed', 'failed')),
started_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
completed_at TIMESTAMP WITH TIME ZONE,
summary JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
-- Vulnerabilities table
CREATE TABLE IF NOT EXISTS vulnerabilities (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
type VARCHAR(50) NOT NULL CHECK (type IN ('dependency', 'configuration', 'code')),
severity VARCHAR(20) NOT NULL CHECK (severity IN ('critical', 'high', 'medium', 'low')),
title VARCHAR(255) NOT NULL,
description TEXT,
service_id UUID REFERENCES services(id) ON DELETE CASCADE,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
status VARCHAR(50) NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'resolved', 'ignored')),
found_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
resolved_at TIMESTAMP WITH TIME ZONE,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
-- Compliance frameworks table
CREATE TABLE IF NOT EXISTS compliance_frameworks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
version VARCHAR(20) NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
-- Compliance controls table
CREATE TABLE IF NOT EXISTS compliance_controls (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
framework_id UUID NOT NULL REFERENCES compliance_frameworks(id) ON DELETE CASCADE,
code VARCHAR(50) NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
category VARCHAR(100),
requirement TEXT,
test_procedure TEXT,
status VARCHAR(50) NOT NULL DEFAULT 'pending' CHECK (status IN ('compliant', 'non_compliant', 'not_applicable', 'pending')),
last_assessed TIMESTAMP WITH TIME ZONE,
evidence TEXT,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
UNIQUE(framework_id, code)
);
-- Compliance reports table
CREATE TABLE IF NOT EXISTS compliance_reports (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
framework_id UUID NOT NULL REFERENCES compliance_frameworks(id) ON DELETE CASCADE,
assessment_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
assessor VARCHAR(255),
overall_status VARCHAR(50) NOT NULL CHECK (overall_status IN ('compliant', 'partially_compliant', 'non_compliant', 'in_progress')),
score INTEGER NOT NULL DEFAULT 0 CHECK (score >= 0 AND score <= 100),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
-- Compliance risks table
CREATE TABLE IF NOT EXISTS compliance_risks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
report_id UUID NOT NULL REFERENCES compliance_reports(id) ON DELETE CASCADE,
control_id UUID NOT NULL REFERENCES compliance_controls(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
description TEXT,
impact VARCHAR(20) NOT NULL CHECK (impact IN ('high', 'medium', 'low')),
likelihood VARCHAR(20) NOT NULL CHECK (likelihood IN ('high', 'medium', 'low')),
mitigation TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
-- Audit logs table
CREATE TABLE IF NOT EXISTS audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
action VARCHAR(100) NOT NULL,
resource VARCHAR(100) NOT NULL,
resource_id UUID,
details JSONB DEFAULT '{}',
ip_address INET,
user_agent TEXT,
success BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
-- Data retention policies table
CREATE TABLE IF NOT EXISTS data_retention_policies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(100) NOT NULL UNIQUE,
data_type VARCHAR(100) NOT NULL,
retention_period INTERVAL NOT NULL,
action VARCHAR(50) NOT NULL CHECK (action IN ('delete', 'anonymize', 'archive')),
enabled BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
-- Anonymized data table
CREATE TABLE IF NOT EXISTS anonymized_data (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
original_id UUID NOT NULL,
anonymized_id VARCHAR(255) NOT NULL UNIQUE,
data_type VARCHAR(100) NOT NULL,
anonymized_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
retained_data TEXT, -- Encrypted non-sensitive data
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
-- Indexes for performance
CREATE INDEX IF NOT EXISTS idx_security_scans_project_id ON security_scans(project_id);
CREATE INDEX IF NOT EXISTS idx_security_scans_status ON security_scans(status);
CREATE INDEX IF NOT EXISTS idx_security_scans_started_at ON security_scans(started_at);
CREATE INDEX IF NOT EXISTS idx_vulnerabilities_project_id ON vulnerabilities(project_id);
CREATE INDEX IF NOT EXISTS idx_vulnerabilities_service_id ON vulnerabilities(service_id);
CREATE INDEX IF NOT EXISTS idx_vulnerabilities_severity ON vulnerabilities(severity);
CREATE INDEX IF NOT EXISTS idx_vulnerabilities_status ON vulnerabilities(status);
CREATE INDEX IF NOT EXISTS idx_vulnerabilities_found_at ON vulnerabilities(found_at);
CREATE INDEX IF NOT EXISTS idx_compliance_controls_framework_id ON compliance_controls(framework_id);
CREATE INDEX IF NOT EXISTS idx_compliance_controls_status ON compliance_controls(status);
CREATE INDEX IF NOT EXISTS idx_compliance_controls_last_assessed ON compliance_controls(last_assessed);
CREATE INDEX IF NOT EXISTS idx_compliance_reports_project_id ON compliance_reports(project_id);
CREATE INDEX IF NOT EXISTS idx_compliance_reports_framework_id ON compliance_reports(framework_id);
CREATE INDEX IF NOT EXISTS idx_compliance_reports_assessment_date ON compliance_reports(assessment_date);
CREATE INDEX IF NOT EXISTS idx_compliance_risks_report_id ON compliance_risks(report_id);
CREATE INDEX IF NOT EXISTS idx_compliance_risks_control_id ON compliance_risks(control_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_timestamp ON audit_logs(timestamp);
CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id ON audit_logs(user_id);
CREATE INDEX IF NOT EXISTS idx_audit_logs_action ON audit_logs(action);
CREATE INDEX IF NOT EXISTS idx_audit_logs_resource ON audit_logs(resource);
CREATE INDEX IF NOT EXISTS idx_anonymized_data_original_id ON anonymized_data(original_id);
CREATE INDEX IF NOT EXISTS idx_anonymized_data_anonymized_id ON anonymized_data(anonymized_id);
CREATE INDEX IF NOT EXISTS idx_anonymized_data_data_type ON anonymized_data(data_type);
-- Insert default data retention policies
INSERT INTO data_retention_policies (name, data_type, retention_period, action, enabled) VALUES
('User Data Retention', 'user_data', INTERVAL '2 years', 'anonymize', true),
('Analytics Data Retention', 'analytics_data', INTERVAL '6 months', 'delete', true),
('Log Data Retention', 'log_data', INTERVAL '90 days', 'delete', true),
('Deleted User Data', 'deleted_user_data', INTERVAL '30 days', 'delete', true),
('Audit Log Retention', 'audit_log', INTERVAL '1 year', 'archive', true)
ON CONFLICT (name) DO NOTHING;
-- Insert default GDPR framework
INSERT INTO compliance_frameworks (id, name, description, version, enabled) VALUES
('gen_random_uuid()', 'GDPR', 'General Data Protection Regulation compliance framework', '1.0', true)
ON CONFLICT (name) DO UPDATE SET version = '1.0', enabled = true;
-- Update updated_at trigger function
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ language 'plpgsql';
-- Create triggers for updated_at
CREATE TRIGGER update_security_scans_updated_at BEFORE UPDATE ON security_scans FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_vulnerabilities_updated_at BEFORE UPDATE ON vulnerabilities FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_compliance_frameworks_updated_at BEFORE UPDATE ON compliance_frameworks FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_compliance_controls_updated_at BEFORE UPDATE ON compliance_controls FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_compliance_reports_updated_at BEFORE UPDATE ON compliance_reports FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_compliance_risks_updated_at BEFORE UPDATE ON compliance_risks FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_data_retention_policies_updated_at BEFORE UPDATE ON data_retention_policies FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- Row Level Security (RLS) for audit logs
ALTER TABLE audit_logs ENABLE ROW LEVEL SECURITY;
-- Policy for audit logs - users can only see their own audit logs
CREATE POLICY audit_logs_user_policy ON audit_logs
FOR SELECT USING (user_id = current_setting('app.current_user_id')::UUID);
-- Policy for audit logs - no direct inserts (only through application)
CREATE POLICY audit_logs_insert_policy ON audit_logs
FOR INSERT WITH CHECK (false);
-- Policy for audit logs - no direct updates (audit logs are immutable)
CREATE POLICY audit_logs_update_policy ON audit_logs
FOR UPDATE WITH CHECK (false);
-- Row Level Security for anonymized data
ALTER TABLE anonymized_data ENABLE ROW LEVEL SECURITY;
-- Policy for anonymized data - restricted access
CREATE POLICY anonymized_data_policy ON anonymized_data
FOR SELECT USING (current_setting('app.is_admin', true)::BOOLEAN);
-- Add comments for documentation
COMMENT ON TABLE security_scans IS 'Security scan records for vulnerability assessment';
COMMENT ON TABLE vulnerabilities IS 'Security vulnerabilities found during scans';
COMMENT ON TABLE compliance_frameworks IS 'Compliance frameworks like GDPR, SOC2, etc.';
COMMENT ON TABLE compliance_controls IS 'Individual controls within compliance frameworks';
COMMENT ON TABLE compliance_reports IS 'Compliance assessment reports';
COMMENT ON TABLE compliance_risks IS 'Risk assessments for compliance gaps';
COMMENT ON TABLE audit_logs IS 'Security audit trail for all sensitive operations';
COMMENT ON TABLE data_retention_policies IS 'Data retention and deletion policies';
COMMENT ON TABLE anonymized_data IS 'Anonymized user data for privacy compliance';
@@ -0,0 +1,81 @@
-- Performance Optimization Migration
-- This migration adds additional indexes and optimizations for better query performance
-- Composite indexes for common query patterns
CREATE INDEX IF NOT EXISTS idx_projects_owner_updated ON projects(owner_id, updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_services_project_env ON services(project_id, environment_id);
CREATE INDEX IF NOT EXISTS idx_services_status_project ON services(status, project_id);
CREATE INDEX IF NOT EXISTS idx_deployments_service_status_created ON deployments(service_id, status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_deployments_service_created ON deployments(service_id, created_at DESC);
-- Partial indexes for better performance on filtered queries
CREATE INDEX IF NOT EXISTS idx_active_deployments ON deployments(service_id, created_at DESC)
WHERE status IN ('running', 'deploying');
CREATE INDEX IF NOT EXISTS idx_running_services ON services(project_id, updated_at DESC)
WHERE status = 'running';
-- Environment variables optimization
CREATE INDEX IF NOT EXISTS idx_env_vars_service_key ON environment_variables(service_id, key);
-- Service dependencies optimization
CREATE INDEX IF NOT EXISTS idx_service_deps_service ON service_dependencies(service_id);
CREATE INDEX IF NOT EXISTS idx_service_deps_depends_on ON service_dependencies(depends_on_service_id);
-- Project members optimization for role-based queries
CREATE INDEX IF NOT EXISTS idx_project_members_role ON project_members(project_id, role);
-- Add table statistics for better query planning
ANALYZE projects;
ANALYZE services;
ANALYZE deployments;
ANALYZE environment_variables;
ANALYZE service_dependencies;
ANALYZE project_members;
ANALYZE users;
ANALYZE environments;
-- Create a view for project statistics to optimize dashboard queries
CREATE OR REPLACE VIEW project_stats AS
SELECT
p.id,
p.name,
p.description,
p.owner_id,
p.created_at,
p.updated_at,
COUNT(DISTINCT s.id) as service_count,
COUNT(DISTINCT d.id) as deployment_count,
COUNT(DISTINCT CASE WHEN s.status = 'running' THEN s.id END) as running_services,
MAX(d.created_at) as last_deployment
FROM projects p
LEFT JOIN services s ON p.id = s.project_id
LEFT JOIN deployments d ON s.id = d.service_id
GROUP BY p.id, p.name, p.description, p.owner_id, p.created_at, p.updated_at;
-- Create index on the view for better performance
CREATE INDEX IF NOT EXISTS idx_project_stats_id ON project_stats(id);
-- Function to get project statistics efficiently
CREATE OR REPLACE FUNCTION get_project_stats(project_uuid UUID)
RETURNS TABLE(
service_count BIGINT,
deployment_count BIGINT,
running_services BIGINT,
last_deployment TIMESTAMP WITH TIME ZONE
) AS $$
BEGIN
RETURN QUERY
SELECT
COUNT(DISTINCT s.id),
COUNT(DISTINCT d.id),
COUNT(DISTINCT CASE WHEN s.status = 'running' THEN s.id END),
MAX(d.created_at)
FROM services s
LEFT JOIN deployments d ON s.id = d.service_id
WHERE s.project_id = project_uuid;
END;
$$ LANGUAGE plpgsql;
-- Add comment for documentation
COMMENT ON MIGRATION IS 'Performance optimization with additional indexes and statistics views';