182 lines
5.7 KiB
Go
182 lines
5.7 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const providerAccountDeprecatedMissingReason = "missing_from_latest_batch"
|
|
|
|
func SyncProviderAccountsFromLatestImportBatches(ctx context.Context, store *DB) error {
|
|
if store == nil {
|
|
return fmt.Errorf("store is required")
|
|
}
|
|
batches, err := store.ImportBatches().ListLatestReconcilable(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, batch := range batches {
|
|
if err := SyncProviderAccountsFromImportBatch(ctx, store, batch.ID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func SyncProviderAccountsFromImportBatch(ctx context.Context, store *DB, batchID int64) error {
|
|
if store == nil {
|
|
return fmt.Errorf("store is required")
|
|
}
|
|
if batchID <= 0 {
|
|
return fmt.Errorf("batch_id is required")
|
|
}
|
|
|
|
batch, err := store.ImportBatches().GetByID(ctx, batchID)
|
|
if err != nil {
|
|
return fmt.Errorf("get import batch %d: %w", batchID, err)
|
|
}
|
|
switch strings.TrimSpace(batch.BatchStatus) {
|
|
case "succeeded", "partially_succeeded":
|
|
default:
|
|
return nil
|
|
}
|
|
resources, err := store.ManagedResources().GetByBatchID(ctx, batchID)
|
|
if err != nil {
|
|
return fmt.Errorf("get managed resources for batch %d: %w", batchID, err)
|
|
}
|
|
items, err := store.ImportBatchItems().GetByBatchID(ctx, batchID)
|
|
if err != nil {
|
|
return fmt.Errorf("get import batch items for batch %d: %w", batchID, err)
|
|
}
|
|
|
|
nowText := time.Now().UTC().Format(time.RFC3339)
|
|
shadowGroupID := ""
|
|
for _, resource := range resources {
|
|
if strings.TrimSpace(resource.ResourceType) == "group" {
|
|
shadowGroupID = strings.TrimSpace(resource.HostResourceID)
|
|
break
|
|
}
|
|
}
|
|
|
|
accountResources := make([]ManagedResource, 0)
|
|
for _, resource := range resources {
|
|
if strings.TrimSpace(resource.ResourceType) == "account" {
|
|
accountResources = append(accountResources, resource)
|
|
}
|
|
}
|
|
itemByAccountID, unmatchedItems := indexBatchItemsByAccountID(items)
|
|
keepAccountIDs := make([]string, 0, len(accountResources))
|
|
for index, resource := range accountResources {
|
|
hostAccountID := strings.TrimSpace(resource.HostResourceID)
|
|
if hostAccountID == "" {
|
|
continue
|
|
}
|
|
keepAccountIDs = append(keepAccountIDs, hostAccountID)
|
|
match, ok := itemByAccountID[hostAccountID]
|
|
if !ok && index < len(unmatchedItems) {
|
|
match = unmatchedItems[index]
|
|
}
|
|
row := ProviderAccount{
|
|
HostID: batch.HostID,
|
|
ProviderID: batch.ProviderID,
|
|
ShadowGroupID: shadowGroupID,
|
|
HostAccountID: hostAccountID,
|
|
KeyFingerprint: fallbackString(match.KeyFingerprint, "legacy:"+hostAccountID),
|
|
AccountName: fallbackString(resource.ResourceName, hostAccountID),
|
|
AccountStatus: providerAccountStatusFromLegacy(match.AccountStatus),
|
|
LastProbeStatus: strings.TrimSpace(match.ProbeStatus),
|
|
LastProbeAt: nowText,
|
|
}
|
|
if existing, err := store.ProviderAccounts().GetByHostIDAndAccountID(ctx, batch.HostID, hostAccountID); err == nil {
|
|
if strings.TrimSpace(existing.RouteID) != "" {
|
|
row.RouteID = existing.RouteID
|
|
}
|
|
if strings.TrimSpace(existing.ShadowGroupID) != "" {
|
|
row.ShadowGroupID = existing.ShadowGroupID
|
|
}
|
|
preserveManagedProviderAccountStatus(&row, existing)
|
|
}
|
|
if _, err := store.ProviderAccounts().Upsert(ctx, row); err != nil {
|
|
return fmt.Errorf("upsert provider account %q from batch %d: %w", hostAccountID, batchID, err)
|
|
}
|
|
}
|
|
if err := store.ProviderAccounts().DeprecateMissingForScope(ctx, batch.ProviderID, batch.HostID, keepAccountIDs, providerAccountDeprecatedMissingReason); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type legacyBatchAccountProjection struct {
|
|
KeyFingerprint string
|
|
AccountStatus string
|
|
ProbeStatus string
|
|
AccountID string
|
|
}
|
|
|
|
func indexBatchItemsByAccountID(items []ImportBatchItem) (map[string]legacyBatchAccountProjection, []legacyBatchAccountProjection) {
|
|
indexed := make(map[string]legacyBatchAccountProjection, len(items))
|
|
unmatched := make([]legacyBatchAccountProjection, 0, len(items))
|
|
for _, item := range items {
|
|
projection := legacyBatchAccountProjection{
|
|
KeyFingerprint: strings.TrimSpace(item.KeyFingerprint),
|
|
AccountStatus: strings.TrimSpace(item.AccountStatus),
|
|
}
|
|
var payload map[string]any
|
|
if err := json.Unmarshal([]byte(defaultJSON(strings.TrimSpace(item.ProbeSummaryJSON), "{}")), &payload); err == nil {
|
|
if value, ok := payload["probe_status"].(string); ok {
|
|
projection.ProbeStatus = strings.TrimSpace(value)
|
|
}
|
|
if value, ok := payload["account_id"].(string); ok {
|
|
projection.AccountID = strings.TrimSpace(value)
|
|
}
|
|
}
|
|
if projection.AccountID != "" {
|
|
indexed[projection.AccountID] = projection
|
|
continue
|
|
}
|
|
unmatched = append(unmatched, projection)
|
|
}
|
|
return indexed, unmatched
|
|
}
|
|
|
|
func providerAccountStatusFromLegacy(accountStatus string) string {
|
|
switch strings.TrimSpace(accountStatus) {
|
|
case "passed", "warning":
|
|
return ProviderAccountStatusActive
|
|
case ProviderAccountStatusDisabled:
|
|
return ProviderAccountStatusDisabled
|
|
case ProviderAccountStatusDeprecated:
|
|
return ProviderAccountStatusDeprecated
|
|
default:
|
|
return ProviderAccountStatusBroken
|
|
}
|
|
}
|
|
|
|
func fallbackString(values ...string) string {
|
|
for _, value := range values {
|
|
if trimmed := strings.TrimSpace(value); trimmed != "" {
|
|
return trimmed
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func preserveManagedProviderAccountStatus(row *ProviderAccount, existing ProviderAccount) {
|
|
if row == nil {
|
|
return
|
|
}
|
|
switch strings.TrimSpace(existing.AccountStatus) {
|
|
case ProviderAccountStatusDisabled:
|
|
row.AccountStatus = ProviderAccountStatusDisabled
|
|
row.DisabledReason = strings.TrimSpace(existing.DisabledReason)
|
|
case ProviderAccountStatusDeprecated:
|
|
if strings.TrimSpace(existing.DisabledReason) != providerAccountDeprecatedMissingReason {
|
|
row.AccountStatus = ProviderAccountStatusDeprecated
|
|
row.DisabledReason = strings.TrimSpace(existing.DisabledReason)
|
|
}
|
|
}
|
|
}
|