- 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
265 lines
7.3 KiB
Go
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
|
|
}
|