- Add Workspace domain (entity, repository, service, handler, DTO) - Add multi-tenant K8s client with tenant binding and quota management - Add K8s diagnostics client (instance diagnostics) - Add authorization middleware (authz package) - Restructure frontend to feature-based architecture (features/) - Add User Management page in configuration - Add AccessDenied page and route guards - Refactor shared components (form inputs, layout, UI) - Update Tailwind config for new design system - Add comprehensive documentation (docs/, tasks/, plans) - Improve cluster service with better kubeconfig handling - Add tests for crypto, config, helm client, tenant binding
263 lines
9.7 KiB
Go
263 lines
9.7 KiB
Go
package postgres
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
_ "github.com/lib/pq"
|
|
)
|
|
|
|
// DB 数据库连接包装器
|
|
type DB struct {
|
|
conn *sql.DB
|
|
}
|
|
|
|
// NewDB 创建新的数据库连接
|
|
func NewDB(connString string) (*DB, error) {
|
|
if connString == "" {
|
|
return nil, fmt.Errorf("database connection string cannot be empty")
|
|
}
|
|
|
|
conn, err := sql.Open("postgres", connString)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open database: %w", err)
|
|
}
|
|
|
|
// 配置连接池
|
|
conn.SetMaxOpenConns(25)
|
|
conn.SetMaxIdleConns(5)
|
|
conn.SetConnMaxLifetime(5 * time.Minute)
|
|
|
|
// 测试连接
|
|
if err := conn.Ping(); err != nil {
|
|
return nil, fmt.Errorf("failed to ping database: %w", err)
|
|
}
|
|
|
|
return &DB{conn: conn}, nil
|
|
}
|
|
|
|
// Close 关闭数据库连接
|
|
func (db *DB) Close() error {
|
|
if db.conn != nil {
|
|
return db.conn.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetConn 获取底层连接(用于事务等高级操作)
|
|
func (db *DB) GetConn() *sql.DB {
|
|
return db.conn
|
|
}
|
|
|
|
// InitSchema 初始化数据库 schema
|
|
func (db *DB) InitSchema() error {
|
|
schema := `
|
|
-- Workspaces 表
|
|
CREATE TABLE IF NOT EXISTS workspaces (
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL UNIQUE,
|
|
status VARCHAR(50) NOT NULL DEFAULT 'active',
|
|
k8s_namespace VARCHAR(255) NOT NULL,
|
|
k8s_sa_name VARCHAR(255) NOT NULL,
|
|
default_cluster_id VARCHAR(36),
|
|
quota_cpu VARCHAR(50),
|
|
quota_memory VARCHAR(50),
|
|
quota_gpu VARCHAR(50),
|
|
quota_gpu_memory VARCHAR(50),
|
|
created_by VARCHAR(36),
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
ALTER TABLE workspaces
|
|
ADD COLUMN IF NOT EXISTS default_cluster_id VARCHAR(36),
|
|
ADD COLUMN IF NOT EXISTS quota_cpu VARCHAR(50),
|
|
ADD COLUMN IF NOT EXISTS quota_memory VARCHAR(50),
|
|
ADD COLUMN IF NOT EXISTS quota_gpu VARCHAR(50),
|
|
ADD COLUMN IF NOT EXISTS quota_gpu_memory VARCHAR(50);
|
|
|
|
INSERT INTO workspaces (id, name, status, k8s_namespace, k8s_sa_name, created_at, updated_at)
|
|
VALUES ('00000000-0000-0000-0000-000000000010', 'default', 'active', 'ocdp-ws-default', 'ocdp-ws-default', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
ON CONFLICT (id) DO NOTHING;
|
|
|
|
-- Users 表
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
username VARCHAR(255) NOT NULL UNIQUE,
|
|
password_hash TEXT NOT NULL,
|
|
email VARCHAR(255) NOT NULL,
|
|
role VARCHAR(50) NOT NULL DEFAULT 'user',
|
|
workspace_id VARCHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000010',
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
must_change_password BOOLEAN NOT NULL DEFAULT FALSE,
|
|
revoked_after TIMESTAMP NOT NULL DEFAULT '1970-01-01 00:00:00',
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
ALTER TABLE users
|
|
ADD COLUMN IF NOT EXISTS role VARCHAR(50) NOT NULL DEFAULT 'user',
|
|
ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000010',
|
|
ADD COLUMN IF NOT EXISTS is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
ADD COLUMN IF NOT EXISTS must_change_password BOOLEAN NOT NULL DEFAULT FALSE,
|
|
ADD COLUMN IF NOT EXISTS revoked_after TIMESTAMP NOT NULL DEFAULT '1970-01-01 00:00:00';
|
|
|
|
UPDATE users SET role = 'admin' WHERE username = 'admin';
|
|
UPDATE users SET workspace_id = '00000000-0000-0000-0000-000000000010' WHERE workspace_id = '';
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
|
CREATE INDEX IF NOT EXISTS idx_users_workspace ON users(workspace_id);
|
|
CREATE INDEX IF NOT EXISTS idx_users_revoked_after ON users(revoked_after);
|
|
|
|
-- Clusters 表
|
|
CREATE TABLE IF NOT EXISTS clusters (
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
workspace_id VARCHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000010',
|
|
owner_id VARCHAR(36) NOT NULL DEFAULT '',
|
|
visibility VARCHAR(50) NOT NULL DEFAULT 'private',
|
|
name VARCHAR(255) NOT NULL UNIQUE,
|
|
host TEXT NOT NULL,
|
|
ca_data TEXT,
|
|
cert_data TEXT,
|
|
key_data TEXT,
|
|
token TEXT,
|
|
description TEXT,
|
|
default_namespace VARCHAR(255),
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
ALTER TABLE clusters
|
|
ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000010',
|
|
ADD COLUMN IF NOT EXISTS owner_id VARCHAR(36) NOT NULL DEFAULT '',
|
|
ADD COLUMN IF NOT EXISTS visibility VARCHAR(50) NOT NULL DEFAULT 'private',
|
|
ADD COLUMN IF NOT EXISTS default_namespace VARCHAR(255);
|
|
UPDATE clusters SET visibility = 'global_shared' WHERE visibility = 'private' AND owner_id = '';
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_clusters_name ON clusters(name);
|
|
CREATE INDEX IF NOT EXISTS idx_clusters_workspace ON clusters(workspace_id);
|
|
CREATE INDEX IF NOT EXISTS idx_clusters_owner ON clusters(owner_id);
|
|
CREATE INDEX IF NOT EXISTS idx_clusters_visibility ON clusters(visibility);
|
|
|
|
-- Registries 表
|
|
CREATE TABLE IF NOT EXISTS registries (
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
workspace_id VARCHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000010',
|
|
owner_id VARCHAR(36) NOT NULL DEFAULT '',
|
|
visibility VARCHAR(50) NOT NULL DEFAULT 'private',
|
|
name VARCHAR(255) NOT NULL UNIQUE,
|
|
url TEXT NOT NULL,
|
|
description TEXT,
|
|
username VARCHAR(255),
|
|
password TEXT,
|
|
insecure BOOLEAN DEFAULT FALSE,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
ALTER TABLE registries
|
|
ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000010',
|
|
ADD COLUMN IF NOT EXISTS owner_id VARCHAR(36) NOT NULL DEFAULT '',
|
|
ADD COLUMN IF NOT EXISTS visibility VARCHAR(50) NOT NULL DEFAULT 'private';
|
|
UPDATE registries SET visibility = 'global_shared' WHERE visibility = 'private' AND owner_id = '';
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_registries_name ON registries(name);
|
|
CREATE INDEX IF NOT EXISTS idx_registries_workspace ON registries(workspace_id);
|
|
CREATE INDEX IF NOT EXISTS idx_registries_owner ON registries(owner_id);
|
|
CREATE INDEX IF NOT EXISTS idx_registries_visibility ON registries(visibility);
|
|
|
|
-- Instances 表
|
|
CREATE TABLE IF NOT EXISTS instances (
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
workspace_id VARCHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000010',
|
|
owner_id VARCHAR(36) NOT NULL DEFAULT '',
|
|
cluster_id VARCHAR(36) NOT NULL,
|
|
name VARCHAR(255) NOT NULL,
|
|
namespace VARCHAR(255) NOT NULL,
|
|
registry_id VARCHAR(36) NOT NULL,
|
|
repository TEXT NOT NULL,
|
|
chart VARCHAR(255) NOT NULL,
|
|
version VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
values JSONB,
|
|
values_yaml TEXT,
|
|
status VARCHAR(50) NOT NULL,
|
|
status_reason TEXT,
|
|
last_operation VARCHAR(50),
|
|
last_error TEXT,
|
|
revision INTEGER NOT NULL DEFAULT 1,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT fk_cluster FOREIGN KEY (cluster_id) REFERENCES clusters(id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_registry FOREIGN KEY (registry_id) REFERENCES registries(id) ON DELETE CASCADE,
|
|
CONSTRAINT unique_cluster_name UNIQUE (cluster_id, name, namespace)
|
|
);
|
|
|
|
ALTER TABLE instances
|
|
ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000010',
|
|
ADD COLUMN IF NOT EXISTS owner_id VARCHAR(36) NOT NULL DEFAULT '';
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_instances_cluster ON instances(cluster_id);
|
|
CREATE INDEX IF NOT EXISTS idx_instances_registry ON instances(registry_id);
|
|
CREATE INDEX IF NOT EXISTS idx_instances_name ON instances(name);
|
|
CREATE INDEX IF NOT EXISTS idx_instances_workspace ON instances(workspace_id);
|
|
CREATE INDEX IF NOT EXISTS idx_instances_owner ON instances(owner_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS workspace_cluster_bindings (
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
workspace_id VARCHAR(36) NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
cluster_id VARCHAR(36) NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
|
|
namespace VARCHAR(255) NOT NULL,
|
|
service_account VARCHAR(255) NOT NULL,
|
|
quota_cpu VARCHAR(50),
|
|
quota_memory VARCHAR(50),
|
|
quota_gpu VARCHAR(50),
|
|
quota_gpu_memory VARCHAR(50),
|
|
status VARCHAR(50) NOT NULL DEFAULT 'active',
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE (workspace_id, cluster_id)
|
|
);
|
|
ALTER TABLE workspace_cluster_bindings
|
|
ADD COLUMN IF NOT EXISTS quota_gpu_memory VARCHAR(50);
|
|
CREATE INDEX IF NOT EXISTS idx_workspace_cluster_bindings_workspace ON workspace_cluster_bindings(workspace_id);
|
|
CREATE INDEX IF NOT EXISTS idx_workspace_cluster_bindings_cluster ON workspace_cluster_bindings(cluster_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS workspace_quotas (
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
workspace_id VARCHAR(36) NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
resource_type VARCHAR(50) NOT NULL,
|
|
hard_limit VARCHAR(100) NOT NULL,
|
|
soft_limit VARCHAR(100),
|
|
used VARCHAR(100),
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE (workspace_id, resource_type)
|
|
);
|
|
|
|
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 ON audit_logs(workspace_id);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_user ON audit_logs(user_id);
|
|
`
|
|
|
|
_, err := db.conn.Exec(schema)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to initialize schema: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|