package spindle import ( "context" "errors" "fmt" "log/slog" "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/spindle/db" "tangled.org/core/spindle/secrets" ) func copyRepoSecrets(ctx context.Context, mgr secrets.Manager, src, dst secrets.RepoIdentifier) (int, error) { cur, err := mgr.GetSecretsUnlocked(ctx, src) if err != nil { return 0, fmt.Errorf("get %s: %w", src, err) } var step func(remaining []secrets.UnlockedSecret, copied int) (int, error) step = func(remaining []secrets.UnlockedSecret, copied int) (int, error) { if len(remaining) == 0 { return copied, nil } s := remaining[0] addErr := mgr.AddSecret(ctx, secrets.UnlockedSecret{ Repo: dst, Key: s.Key, Value: s.Value, CreatedAt: s.CreatedAt, CreatedBy: s.CreatedBy, }) switch { case addErr == nil: return step(remaining[1:], copied+1) case errors.Is(addErr, secrets.ErrKeyAlreadyPresent): return step(remaining[1:], copied) default: return copied, fmt.Errorf("add %s/%s: %w", dst, s.Key, addErr) } } return step(cur, 0) } func legacyKeyCandidates(owner syntax.DID, name string, rkey syntax.RecordKey) []string { o := owner.String() r := rkey.String() switch { case name == "" && r == "": return nil case name == "": return []string{o + "/" + r} case r == "" || name == r: return []string{o + "/" + name} default: return []string{o + "/" + name, o + "/" + r} } } func migrateLegacyRepoSecrets(ctx context.Context, d *db.DB, vault secrets.Manager, logger *slog.Logger, owner syntax.DID, name string, rkey syntax.RecordKey, repoDid syntax.DID) { candidates := legacyKeyCandidates(owner, name, rkey) if len(candidates) == 0 { return } flag := "legacy-secret-copy:" + repoDid.String() + ":" + rkey.String() var exists bool if err := d.QueryRowContext(ctx, `select exists (select 1 from migrations where name = ?)`, flag).Scan(&exists); err != nil { logger.Warn("legacy secret copy: check migration flag", "err", err) return } if exists { return } newID := secrets.RepoIdentifier(repoDid.String()) var step func(remaining []string, copied int) (int, error) step = func(remaining []string, copied int) (int, error) { if len(remaining) == 0 { return copied, nil } oldID := secrets.RepoIdentifier(remaining[0]) n, err := copyRepoSecrets(ctx, vault, oldID, newID) if err != nil { return copied, fmt.Errorf("copy %s -> %s: %w", oldID, newID, err) } return step(remaining[1:], copied+n) } total, err := step(candidates, 0) if err != nil { logger.Warn("legacy secret copy failed", "err", err) return } if _, err := d.ExecContext(ctx, `insert or ignore into migrations (name) values (?)`, flag); err != nil { logger.Warn("legacy secret copy: mark flag failed", "err", err) return } logger.Info("legacy secret migrate done", "owner", owner, "name", name, "rkey", rkey, "repoDid", repoDid, "candidates", candidates, "copied", total) }