-- OCDP Multi-Tenant Migration Script -- Adds multi-tenant fields and new tables for workspace isolation -- ===== Phase 1: Add new columns to existing tables ===== -- Add multi-tenant fields to users table ALTER TABLE users ADD COLUMN IF NOT EXISTS role VARCHAR(20) NOT NULL DEFAULT 'user'; ALTER TABLE users ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36); ALTER TABLE users ADD COLUMN IF NOT EXISTS is_active BOOLEAN NOT NULL DEFAULT TRUE; ALTER TABLE users ADD COLUMN IF NOT EXISTS must_change_password BOOLEAN NOT NULL DEFAULT FALSE; -- Add indexes for new user fields CREATE INDEX IF NOT EXISTS idx_users_role ON users(role); CREATE INDEX IF NOT EXISTS idx_users_workspace_id ON users(workspace_id); CREATE INDEX IF NOT EXISTS idx_users_is_active ON users(is_active); -- Add multi-tenant fields to clusters table ALTER TABLE clusters ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36); ALTER TABLE clusters ADD COLUMN IF NOT EXISTS owner_id VARCHAR(36); ALTER TABLE clusters ADD COLUMN IF NOT EXISTS isolation_mode VARCHAR(20) NOT NULL DEFAULT 'namespace'; ALTER TABLE clusters ADD COLUMN IF NOT EXISTS default_namespace VARCHAR(255); ALTER TABLE clusters ADD COLUMN IF NOT EXISTS is_shared BOOLEAN NOT NULL DEFAULT FALSE; -- Add index for cluster workspace CREATE INDEX IF NOT EXISTS idx_clusters_workspace_id ON clusters(workspace_id); -- Add multi-tenant fields to registries table ALTER TABLE registries ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36); ALTER TABLE registries ADD COLUMN IF NOT EXISTS owner_id VARCHAR(36); ALTER TABLE registries ADD COLUMN IF NOT EXISTS is_shared BOOLEAN NOT NULL DEFAULT FALSE; -- Add index for registry workspace CREATE INDEX IF NOT EXISTS idx_registries_workspace_id ON registries(workspace_id); -- Add multi-tenant fields to instances table ALTER TABLE instances ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36); ALTER TABLE instances ADD COLUMN IF NOT EXISTS owner_id VARCHAR(36); ALTER TABLE instances ADD COLUMN IF NOT EXISTS values_template_id VARCHAR(36); ALTER TABLE instances ADD COLUMN IF NOT EXISTS user_override_yaml TEXT; ALTER TABLE instances ADD COLUMN IF NOT EXISTS cpu_requested DECIMAL(10,2) NOT NULL DEFAULT 0; ALTER TABLE instances ADD COLUMN IF NOT EXISTS memory_requested VARCHAR(50) NOT NULL DEFAULT '0Mi'; ALTER TABLE instances ADD COLUMN IF NOT EXISTS gpu_requested DECIMAL(10,2) NOT NULL DEFAULT 0; ALTER TABLE instances ADD COLUMN IF NOT EXISTS gpu_memory_requested VARCHAR(50) NOT NULL DEFAULT '0Mi'; -- Add index for instance workspace CREATE INDEX IF NOT EXISTS idx_instances_workspace_id ON instances(workspace_id); -- ===== Phase 2: Create new tables ===== -- Create workspaces table CREATE TABLE IF NOT EXISTS workspaces ( id VARCHAR(36) PRIMARY KEY, name VARCHAR(255) NOT NULL UNIQUE, description TEXT, created_by VARCHAR(36), created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_workspaces_name ON workspaces(name); -- Create workspace_quotas table CREATE TABLE IF NOT EXISTS workspace_quotas ( id VARCHAR(36) PRIMARY KEY, workspace_id VARCHAR(36) NOT NULL, resource_type VARCHAR(50) NOT NULL, hard_limit DECIMAL(10,2) NOT NULL, soft_limit DECIMAL(10,2) NOT NULL, used DECIMAL(10,2) NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(workspace_id, resource_type), CONSTRAINT fk_workspace_quotas_workspace FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_workspace_quotas_workspace_id ON workspace_quotas(workspace_id); -- Create storage_backends table CREATE TABLE IF NOT EXISTS storage_backends ( id VARCHAR(36) PRIMARY KEY, workspace_id VARCHAR(36), owner_id VARCHAR(36), name VARCHAR(255) NOT NULL, type VARCHAR(50) NOT NULL, config JSONB NOT NULL, description TEXT, is_default BOOLEAN NOT NULL DEFAULT FALSE, is_shared BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(workspace_id, name) ); CREATE INDEX IF NOT EXISTS idx_storage_backends_workspace_id ON storage_backends(workspace_id); -- Create chart_references table CREATE TABLE IF NOT EXISTS chart_references ( id VARCHAR(36) PRIMARY KEY, workspace_id VARCHAR(36), registry_id VARCHAR(36), repository VARCHAR(500) NOT NULL, chart_name VARCHAR(255) NOT NULL, description TEXT, is_enabled BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(workspace_id, registry_id, repository) ); CREATE INDEX IF NOT EXISTS idx_chart_references_workspace_id ON chart_references(workspace_id); CREATE INDEX IF NOT EXISTS idx_chart_references_registry_id ON chart_references(registry_id); -- Create values_templates table CREATE TABLE IF NOT EXISTS values_templates ( id VARCHAR(36) PRIMARY KEY, workspace_id VARCHAR(36), owner_id VARCHAR(36), chart_reference_id VARCHAR(36), name VARCHAR(255) NOT NULL, description TEXT, values_yaml TEXT NOT NULL, version INTEGER NOT NULL DEFAULT 1, is_default BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(workspace_id, chart_reference_id, name) ); CREATE INDEX IF NOT EXISTS idx_values_templates_workspace_id ON values_templates(workspace_id); CREATE INDEX IF NOT EXISTS idx_values_templates_chart_reference_id ON values_templates(chart_reference_id); -- Create user_config_overrides table CREATE TABLE IF NOT EXISTS user_config_overrides ( id VARCHAR(36) PRIMARY KEY, workspace_id VARCHAR(36), user_id VARCHAR(36), target_type VARCHAR(50) NOT NULL, target_id VARCHAR(36), config JSONB NOT NULL, priority INTEGER NOT NULL DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_user_config_overrides_workspace_id ON user_config_overrides(workspace_id); CREATE INDEX IF NOT EXISTS idx_user_config_overrides_user_id ON user_config_overrides(user_id); -- Create audit_logs table CREATE TABLE IF NOT EXISTS audit_logs ( id VARCHAR(36) PRIMARY KEY, workspace_id VARCHAR(36), user_id VARCHAR(36), action VARCHAR(100) NOT NULL, resource_type VARCHAR(50) NOT NULL, resource_id VARCHAR(36), resource_name VARCHAR(255), details JSONB, ip_address VARCHAR(50), user_agent TEXT, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_audit_logs_workspace_id ON audit_logs(workspace_id); CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id ON audit_logs(user_id); CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON audit_logs(created_at); -- ===== Phase 3: Create admin user ===== -- Note: Default password is 'admin123' (bcrypt hash will be set by application) -- The admin user will have NULL workspace_id to indicate global access INSERT INTO users (id, username, password_hash, email, role, workspace_id, is_active, must_change_password) VALUES ( '00000000-0000-0000-0000-000000000001', 'admin', '$2a$10$placeholder', -- Replace with actual bcrypt hash in production 'admin@ocdp.local', 'admin', NULL, TRUE, TRUE ) ON CONFLICT (username) DO NOTHING; -- Update schema version INSERT INTO schema_migrations (version) VALUES ('v2.0.0-multi-tenant') ON CONFLICT (version) DO NOTHING; -- Grant permissions (adjust as needed for your setup) -- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ocdp_user; -- GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ocdp_user;