Files
ocdp-go/backend/internal/adapter/output/persistence/postgres/instance_repository.go
Ivan087 7f238a3168 refactor: full-stack restructure with multi-tenancy, workspace management, and K8s diagnostics
- 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
2026-05-12 16:15:14 +08:00

265 lines
7.3 KiB
Go

package postgres
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"time"
"github.com/google/uuid"
"github.com/ocdp/cluster-service/internal/domain/entity"
"github.com/ocdp/cluster-service/internal/domain/repository"
)
type InstanceRepository struct {
db *DB
}
func NewInstanceRepository(db *DB) repository.InstanceRepository {
return &InstanceRepository{db: db}
}
func (r *InstanceRepository) Create(ctx context.Context, instance *entity.Instance) error {
if instance.ID == "" {
instance.ID = uuid.New().String()
}
valuesJSON, err := json.Marshal(instance.Values)
if err != nil {
return fmt.Errorf("failed to marshal values: %w", err)
}
query := `
INSERT INTO instances
(id, workspace_id, owner_id, cluster_id, name, namespace, registry_id, repository, chart, version,
description, values, values_yaml, status, status_reason, last_operation, last_error, revision, created_at, updated_at)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20)
`
_, err = r.db.conn.ExecContext(ctx, query,
instance.ID,
instance.WorkspaceID,
instance.OwnerID,
instance.ClusterID,
instance.Name,
instance.Namespace,
instance.RegistryID,
instance.Repository,
instance.Chart,
instance.Version,
instance.Description,
valuesJSON,
instance.ValuesYAML,
instance.Status,
instance.StatusReason,
instance.LastOperation,
instance.LastError,
instance.Revision,
instance.CreatedAt,
instance.UpdatedAt,
)
if err != nil {
return fmt.Errorf("failed to create instance: %w", err)
}
return nil
}
func (r *InstanceRepository) GetByID(ctx context.Context, id string) (*entity.Instance, error) {
return r.get(ctx, "id = $1", id)
}
func (r *InstanceRepository) GetByClusterAndName(ctx context.Context, clusterID, name string) (*entity.Instance, error) {
query := `
SELECT id, workspace_id, owner_id, cluster_id, name, namespace, registry_id, repository, chart, version,
description, values, values_yaml, status, status_reason, last_operation, last_error,
revision, created_at, updated_at
FROM instances
WHERE cluster_id = $1 AND name = $2
`
rows, err := r.db.conn.QueryContext(ctx, query, clusterID, name)
if err != nil {
return nil, fmt.Errorf("failed to get instance: %w", err)
}
defer rows.Close()
if !rows.Next() {
return nil, entity.ErrInstanceNotFound
}
return r.scanInstance(rows)
}
func (r *InstanceRepository) get(ctx context.Context, where string, arg interface{}) (*entity.Instance, error) {
query := fmt.Sprintf(`
SELECT id, workspace_id, owner_id, cluster_id, name, namespace, registry_id, repository, chart, version,
description, values, values_yaml, status, status_reason, last_operation, last_error,
revision, created_at, updated_at
FROM instances
WHERE %s
`, where)
rows, err := r.db.conn.QueryContext(ctx, query, arg)
if err != nil {
return nil, fmt.Errorf("failed to get instance: %w", err)
}
defer rows.Close()
if !rows.Next() {
return nil, entity.ErrInstanceNotFound
}
return r.scanInstance(rows)
}
func (r *InstanceRepository) Update(ctx context.Context, instance *entity.Instance) error {
instance.UpdatedAt = time.Now()
valuesJSON, err := json.Marshal(instance.Values)
if err != nil {
return fmt.Errorf("failed to marshal values: %w", err)
}
query := `
UPDATE instances
SET workspace_id = $1, owner_id = $2, cluster_id = $3, name = $4, namespace = $5,
registry_id = $6, repository = $7, chart = $8, version = $9, description = $10,
values = $11, values_yaml = $12, status = $13, status_reason = $14,
last_operation = $15, last_error = $16, revision = $17, updated_at = $18
WHERE id = $19
`
result, err := r.db.conn.ExecContext(ctx, query,
instance.WorkspaceID,
instance.OwnerID,
instance.ClusterID,
instance.Name,
instance.Namespace,
instance.RegistryID,
instance.Repository,
instance.Chart,
instance.Version,
instance.Description,
valuesJSON,
instance.ValuesYAML,
instance.Status,
instance.StatusReason,
instance.LastOperation,
instance.LastError,
instance.Revision,
instance.UpdatedAt,
instance.ID,
)
if err != nil {
return fmt.Errorf("failed to update instance: %w", err)
}
rows, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get affected rows: %w", err)
}
if rows == 0 {
return entity.ErrInstanceNotFound
}
return nil
}
func (r *InstanceRepository) Delete(ctx context.Context, id string) error {
result, err := r.db.conn.ExecContext(ctx, `DELETE FROM instances WHERE id = $1`, id)
if err != nil {
return fmt.Errorf("failed to delete instance: %w", err)
}
rows, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get affected rows: %w", err)
}
if rows == 0 {
return entity.ErrInstanceNotFound
}
return nil
}
func (r *InstanceRepository) ListByCluster(ctx context.Context, clusterID string) ([]*entity.Instance, error) {
return r.list(ctx, "WHERE cluster_id = $1", clusterID)
}
func (r *InstanceRepository) List(ctx context.Context) ([]*entity.Instance, error) {
return r.list(ctx, "", nil)
}
func (r *InstanceRepository) list(ctx context.Context, where string, arg interface{}) ([]*entity.Instance, error) {
query := `
SELECT id, workspace_id, owner_id, cluster_id, name, namespace, registry_id, repository, chart, version,
description, values, values_yaml, status, status_reason, last_operation, last_error,
revision, created_at, updated_at
FROM instances
` + where + `
ORDER BY created_at DESC
`
var rows *sql.Rows
var err error
if where == "" {
rows, err = r.db.conn.QueryContext(ctx, query)
} else {
rows, err = r.db.conn.QueryContext(ctx, query, arg)
}
if err != nil {
return nil, fmt.Errorf("failed to list instances: %w", err)
}
defer rows.Close()
instances := make([]*entity.Instance, 0)
for rows.Next() {
instance, err := r.scanInstance(rows)
if err != nil {
return nil, err
}
instances = append(instances, instance)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("rows iteration error: %w", err)
}
return instances, nil
}
type instanceScanner interface {
Scan(dest ...interface{}) error
}
func (r *InstanceRepository) scanInstance(scanner instanceScanner) (*entity.Instance, error) {
instance := &entity.Instance{}
var (
valuesJSON []byte
statusReason sql.NullString
lastOperation sql.NullString
lastError sql.NullString
)
err := scanner.Scan(
&instance.ID,
&instance.WorkspaceID,
&instance.OwnerID,
&instance.ClusterID,
&instance.Name,
&instance.Namespace,
&instance.RegistryID,
&instance.Repository,
&instance.Chart,
&instance.Version,
&instance.Description,
&valuesJSON,
&instance.ValuesYAML,
&instance.Status,
&statusReason,
&lastOperation,
&lastError,
&instance.Revision,
&instance.CreatedAt,
&instance.UpdatedAt,
)
if err != nil {
return nil, fmt.Errorf("failed to scan instance: %w", err)
}
if len(valuesJSON) > 0 {
if err := json.Unmarshal(valuesJSON, &instance.Values); err != nil {
return nil, fmt.Errorf("failed to unmarshal values: %w", err)
}
}
if statusReason.Valid {
instance.StatusReason = statusReason.String
}
if lastOperation.Valid {
instance.LastOperation = entity.InstanceOperation(lastOperation.String)
}
if lastError.Valid {
instance.LastError = lastError.String
}
return instance, nil
}