- store/sqlite: 75.4% (repos + db coverage) - host/sub2api: 80.8% (httptest mock server, pure function tests) - app: 74.2% (handler error paths, NewActionSet closures) - pack: 72.4% - provision: 75.2% - access: 77.3% - config: 94.7% (lookup mock tests) All tests pass: build, vet, race, coverage gates.
151 lines
4.7 KiB
Go
151 lines
4.7 KiB
Go
package provision
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"sub2api-cn-relay-manager/internal/store/sqlite"
|
|
)
|
|
|
|
type ProviderQuery struct {
|
|
ProviderID string
|
|
PackID string
|
|
}
|
|
|
|
type ProviderSnapshot struct {
|
|
Host sqlite.Host
|
|
Pack sqlite.Pack
|
|
Provider sqlite.Provider
|
|
Batch sqlite.ImportBatch
|
|
ManagedResources []sqlite.ManagedResource
|
|
AccessClosures []sqlite.AccessClosureRecord
|
|
ReconcileRuns []sqlite.ReconcileRun
|
|
ProviderStatus string
|
|
LatestAccessStatus string
|
|
LatestReconcileStatus string
|
|
LatestReconcileSummary map[string]any
|
|
}
|
|
|
|
type ProviderStatusService struct {
|
|
store *sqlite.DB
|
|
}
|
|
|
|
func NewProviderStatusService(store *sqlite.DB) *ProviderStatusService {
|
|
return &ProviderStatusService{store: store}
|
|
}
|
|
|
|
func (s *ProviderStatusService) GetStatus(ctx context.Context, query ProviderQuery) (ProviderSnapshot, error) {
|
|
return s.snapshot(ctx, query)
|
|
}
|
|
|
|
func (s *ProviderStatusService) GetResources(ctx context.Context, query ProviderQuery) (ProviderSnapshot, error) {
|
|
return s.snapshot(ctx, query)
|
|
}
|
|
|
|
func (s *ProviderStatusService) snapshot(ctx context.Context, query ProviderQuery) (ProviderSnapshot, error) {
|
|
if s == nil || s.store == nil {
|
|
return ProviderSnapshot{}, fmt.Errorf("store is required")
|
|
}
|
|
provider, err := s.resolveProvider(ctx, query)
|
|
if err != nil {
|
|
return ProviderSnapshot{}, err
|
|
}
|
|
packRow, err := s.store.Packs().GetByID(ctx, provider.PackID)
|
|
if err != nil {
|
|
return ProviderSnapshot{}, err
|
|
}
|
|
batchRow, err := s.store.ImportBatches().GetLatestByProviderID(ctx, provider.ID)
|
|
if err != nil {
|
|
return ProviderSnapshot{}, err
|
|
}
|
|
hostRow, err := s.store.Hosts().GetByID(ctx, batchRow.HostID)
|
|
if err != nil {
|
|
return ProviderSnapshot{}, err
|
|
}
|
|
managedResources, err := s.store.ManagedResources().GetByBatchID(ctx, batchRow.ID)
|
|
if err != nil {
|
|
return ProviderSnapshot{}, err
|
|
}
|
|
accessClosures, err := s.store.AccessClosures().GetByBatchID(ctx, batchRow.ID)
|
|
if err != nil {
|
|
return ProviderSnapshot{}, err
|
|
}
|
|
reconcileRuns, err := s.store.ReconcileRuns().GetByProviderID(ctx, provider.ID)
|
|
if err != nil {
|
|
return ProviderSnapshot{}, err
|
|
}
|
|
latestAccessStatus := batchRow.AccessStatus
|
|
if len(accessClosures) > 0 {
|
|
latestAccessStatus = firstNonEmpty(accessClosures[len(accessClosures)-1].Status, latestAccessStatus)
|
|
}
|
|
latestReconcileStatus := "not_run"
|
|
latestReconcileSummary := map[string]any{}
|
|
if len(reconcileRuns) > 0 {
|
|
latestReconcileStatus = firstNonEmpty(reconcileRuns[0].Status, latestReconcileStatus)
|
|
if strings.TrimSpace(reconcileRuns[0].SummaryJSON) != "" {
|
|
if err := json.Unmarshal([]byte(reconcileRuns[0].SummaryJSON), &latestReconcileSummary); err != nil {
|
|
return ProviderSnapshot{}, fmt.Errorf("decode reconcile summary: %w", err)
|
|
}
|
|
}
|
|
}
|
|
providerStatus := deriveProviderStatus(batchRow.BatchStatus, latestReconcileStatus)
|
|
return ProviderSnapshot{
|
|
Host: hostRow,
|
|
Pack: packRow,
|
|
Provider: provider,
|
|
Batch: batchRow,
|
|
ManagedResources: managedResources,
|
|
AccessClosures: accessClosures,
|
|
ReconcileRuns: reconcileRuns,
|
|
ProviderStatus: providerStatus,
|
|
LatestAccessStatus: latestAccessStatus,
|
|
LatestReconcileStatus: latestReconcileStatus,
|
|
LatestReconcileSummary: latestReconcileSummary,
|
|
}, nil
|
|
}
|
|
|
|
func (s *ProviderStatusService) resolveProvider(ctx context.Context, query ProviderQuery) (sqlite.Provider, error) {
|
|
providerID := strings.TrimSpace(query.ProviderID)
|
|
packID := strings.TrimSpace(query.PackID)
|
|
if providerID == "" {
|
|
return sqlite.Provider{}, fmt.Errorf("provider_id is required")
|
|
}
|
|
if packID != "" {
|
|
packRow, err := s.store.Packs().GetByPackID(ctx, packID)
|
|
if err != nil {
|
|
return sqlite.Provider{}, err
|
|
}
|
|
return s.store.Providers().GetByPackIDAndProviderID(ctx, packRow.ID, providerID)
|
|
}
|
|
providers, err := s.store.Providers().ListByProviderID(ctx, providerID)
|
|
if err != nil {
|
|
return sqlite.Provider{}, err
|
|
}
|
|
if len(providers) == 0 {
|
|
return sqlite.Provider{}, fmt.Errorf("provider %q not found", providerID)
|
|
}
|
|
if len(providers) > 1 {
|
|
return sqlite.Provider{}, fmt.Errorf("provider %q exists in multiple packs; pack_id is required", providerID)
|
|
}
|
|
return providers[0], nil
|
|
}
|
|
|
|
func deriveProviderStatus(batchStatus, reconcileStatus string) string {
|
|
reconcileStatus = strings.TrimSpace(reconcileStatus)
|
|
if reconcileStatus != "" && reconcileStatus != "not_run" {
|
|
return reconcileStatus
|
|
}
|
|
switch strings.TrimSpace(batchStatus) {
|
|
case BatchStatusSucceeded:
|
|
return ProviderStatusActive
|
|
case BatchStatusFailed:
|
|
return ProviderStatusFailed
|
|
case "running":
|
|
return "running"
|
|
default:
|
|
return firstNonEmpty(batchStatus, "unknown")
|
|
}
|
|
}
|