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" ) // InstanceRepository PostgreSQL 实例仓储实现 type InstanceRepository struct { db *DB } // NewInstanceRepository 创建 PostgreSQL 实例仓储 func NewInstanceRepository(db *DB) repository.InstanceRepository { return &InstanceRepository{db: db} } // Create 创建实例 func (r *InstanceRepository) Create(ctx context.Context, instance *entity.Instance) error { if instance.ID == "" { instance.ID = uuid.New().String() } // 将 Values 转换为 JSON valuesJSON, err := json.Marshal(instance.Values) if err != nil { return fmt.Errorf("failed to marshal values: %w", err) } query := ` INSERT INTO instances (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) ` _, err = r.db.conn.ExecContext(ctx, query, instance.ID, 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 } // GetByID 根据 ID 获取实例 func (r *InstanceRepository) GetByID(ctx context.Context, id string) (*entity.Instance, error) { query := ` SELECT 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 id = $1 ` instance := &entity.Instance{} var ( valuesJSON []byte statusReason sql.NullString lastOperation sql.NullString lastError sql.NullString ) err := r.db.conn.QueryRowContext(ctx, query, id).Scan( &instance.ID, &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 == sql.ErrNoRows { return nil, entity.ErrInstanceNotFound } if err != nil { return nil, fmt.Errorf("failed to get instance: %w", err) } // 解析 JSON Values 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 } // GetByClusterAndName 根据集群 ID 和名称获取实例 func (r *InstanceRepository) GetByClusterAndName(ctx context.Context, clusterID, name string) (*entity.Instance, error) { query := ` SELECT 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 ` instance := &entity.Instance{} var ( valuesJSON []byte statusReason sql.NullString lastOperation sql.NullString lastError sql.NullString ) err := r.db.conn.QueryRowContext(ctx, query, clusterID, name).Scan( &instance.ID, &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 == sql.ErrNoRows { return nil, entity.ErrInstanceNotFound } if err != nil { return nil, fmt.Errorf("failed to get instance: %w", err) } // 解析 JSON Values 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 } // Update 更新实例 func (r *InstanceRepository) Update(ctx context.Context, instance *entity.Instance) error { instance.UpdatedAt = time.Now() // 将 Values 转换为 JSON valuesJSON, err := json.Marshal(instance.Values) if err != nil { return fmt.Errorf("failed to marshal values: %w", err) } query := ` UPDATE instances SET cluster_id = $1, name = $2, namespace = $3, registry_id = $4, repository = $5, chart = $6, version = $7, description = $8, values = $9, values_yaml = $10, status = $11, status_reason = $12, last_operation = $13, last_error = $14, revision = $15, updated_at = $16 WHERE id = $17 ` result, err := r.db.conn.ExecContext(ctx, query, 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 } // Delete 删除实例 func (r *InstanceRepository) Delete(ctx context.Context, id string) error { query := `DELETE FROM instances WHERE id = $1` result, err := r.db.conn.ExecContext(ctx, query, 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 } // ListByCluster 列出指定集群的所有实例 func (r *InstanceRepository) ListByCluster(ctx context.Context, clusterID string) ([]*entity.Instance, error) { query := ` SELECT 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 ORDER BY created_at DESC ` rows, err := r.db.conn.QueryContext(ctx, query, clusterID) 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 := &entity.Instance{} var ( valuesJSON []byte statusReason sql.NullString lastOperation sql.NullString lastError sql.NullString ) err := rows.Scan( &instance.ID, &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) } // 解析 JSON Values 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 } instances = append(instances, instance) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("rows iteration error: %w", err) } return instances, nil } // List 列出所有实例 func (r *InstanceRepository) List(ctx context.Context) ([]*entity.Instance, error) { query := ` SELECT 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 ORDER BY created_at DESC ` rows, err := r.db.conn.QueryContext(ctx, query) 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 := &entity.Instance{} var ( valuesJSON []byte statusReason sql.NullString lastOperation sql.NullString lastError sql.NullString ) err := rows.Scan( &instance.ID, &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) } // 解析 JSON Values 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 } instances = append(instances, instance) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("rows iteration error: %w", err) } return instances, nil }