Monorepo for Tangled
tangled.org
1package spindle
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "log/slog"
8
9 "github.com/bluesky-social/indigo/atproto/syntax"
10 "tangled.org/core/spindle/db"
11 "tangled.org/core/spindle/secrets"
12)
13
14func copyRepoSecrets(ctx context.Context, mgr secrets.Manager, src, dst secrets.RepoIdentifier) (int, error) {
15 cur, err := mgr.GetSecretsUnlocked(ctx, src)
16 if err != nil {
17 return 0, fmt.Errorf("get %s: %w", src, err)
18 }
19 var step func(remaining []secrets.UnlockedSecret, copied int) (int, error)
20 step = func(remaining []secrets.UnlockedSecret, copied int) (int, error) {
21 if len(remaining) == 0 {
22 return copied, nil
23 }
24 s := remaining[0]
25 addErr := mgr.AddSecret(ctx, secrets.UnlockedSecret{
26 Repo: dst,
27 Key: s.Key,
28 Value: s.Value,
29 CreatedAt: s.CreatedAt,
30 CreatedBy: s.CreatedBy,
31 })
32 switch {
33 case addErr == nil:
34 return step(remaining[1:], copied+1)
35 case errors.Is(addErr, secrets.ErrKeyAlreadyPresent):
36 return step(remaining[1:], copied)
37 default:
38 return copied, fmt.Errorf("add %s/%s: %w", dst, s.Key, addErr)
39 }
40 }
41 return step(cur, 0)
42}
43
44func legacyKeyCandidates(owner syntax.DID, name string, rkey syntax.RecordKey) []string {
45 o := owner.String()
46 r := rkey.String()
47 switch {
48 case name == "" && r == "":
49 return nil
50 case name == "":
51 return []string{o + "/" + r}
52 case r == "" || name == r:
53 return []string{o + "/" + name}
54 default:
55 return []string{o + "/" + name, o + "/" + r}
56 }
57}
58
59func migrateLegacyRepoSecrets(ctx context.Context, d *db.DB, vault secrets.Manager, logger *slog.Logger, owner syntax.DID, name string, rkey syntax.RecordKey, repoDid syntax.DID) {
60 candidates := legacyKeyCandidates(owner, name, rkey)
61 if len(candidates) == 0 {
62 return
63 }
64 flag := "legacy-secret-copy:" + repoDid.String() + ":" + rkey.String()
65 var exists bool
66 if err := d.QueryRowContext(ctx, `select exists (select 1 from migrations where name = ?)`, flag).Scan(&exists); err != nil {
67 logger.Warn("legacy secret copy: check migration flag", "err", err)
68 return
69 }
70 if exists {
71 return
72 }
73
74 newID := secrets.RepoIdentifier(repoDid.String())
75 var step func(remaining []string, copied int) (int, error)
76 step = func(remaining []string, copied int) (int, error) {
77 if len(remaining) == 0 {
78 return copied, nil
79 }
80 oldID := secrets.RepoIdentifier(remaining[0])
81 n, err := copyRepoSecrets(ctx, vault, oldID, newID)
82 if err != nil {
83 return copied, fmt.Errorf("copy %s -> %s: %w", oldID, newID, err)
84 }
85 return step(remaining[1:], copied+n)
86 }
87 total, err := step(candidates, 0)
88 if err != nil {
89 logger.Warn("legacy secret copy failed", "err", err)
90 return
91 }
92
93 if _, err := d.ExecContext(ctx, `insert or ignore into migrations (name) values (?)`, flag); err != nil {
94 logger.Warn("legacy secret copy: mark flag failed", "err", err)
95 return
96 }
97 logger.Info("legacy secret migrate done", "owner", owner, "name", name, "rkey", rkey, "repoDid", repoDid, "candidates", candidates, "copied", total)
98}