1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package providermain
import (
"context"
"errors"
"fmt"
"io/fs"
"log/slog"
"os"
"path/filepath"
"slices"
"github.com/johnandersen777/socialweb-computer-kcp/internal/clusterprovider"
"github.com/johnandersen777/socialweb-computer-kcp/internal/portallocator"
"github.com/johnandersen777/socialweb-computer-kcp/internal/subnetallocator"
)
type legacyKeys struct {
StateDir string
Ports *portallocator.Allocator
Networks *subnetallocator.Allocator
Log *slog.Logger
}
func migrateLegacyKeys(ctx context.Context, registry clusterprovider.Clusters, d legacyKeys) error {
if d.Log == nil {
d.Log = slog.Default()
}
refs, err := registry.Refs(ctx)
if err != nil {
return fmt.Errorf(
"clusterprovider: listing the Cluster CRs before the legacy-key migration: %w. The "+
"migration renames a state dir, a node dir and a mint credential from the bare "+
"cluster name to the cluster's own key, and a list that failed is not an empty "+
"tree: it would leave every legacy directory unclaimed and every later pass "+
"reading an empty state dir over a cluster that is running", err)
}
byName := map[string][]clusterprovider.Ref{}
for _, ref := range refs {
byName[ref.Name] = append(byName[ref.Name], ref)
}
names := make([]string, 0, len(byName))
for name := range byName {
names = append(names, name)
}
slices.Sort(names)
for _, name := range names {
group := byName[name]
if len(group) != 1 {
homes := make([]string, 0, len(group))
for _, ref := range group {
homes = append(homes, ref.Home)
}
d.Log.Warn("cluster provider: two or more home workspaces hold a Cluster with this "+
"name, so the legacy directories named by it belong to no one cluster and are left "+
"where they are",
"cluster", name, "homes", homes)
continue
}
key, err := group[0].Key()
if err != nil {
return err
}
if err := migrateOne(d, name, key); err != nil {
return err
}
}
return nil
}
func migrateOne(d legacyKeys, name, key string) error {
if name == key {
return nil
}
instance, err := clusterprovider.InstanceName(name)
if err != nil {
return err
}
instanceKey, err := clusterprovider.InstanceName(key)
if err != nil {
return err
}
for _, move := range []struct{ parent, from, to string }{
{d.StateDir, instance, instanceKey},
{d.StateDir, name, key},
} {
if move.parent == "" || move.from == move.to {
continue
}
if err := renameLegacy(d.Log, move.parent, move.from, move.to, name, key); err != nil {
return err
}
}
if d.Ports != nil {
moved, err := d.Ports.Rekey(name, key)
if err != nil {
return fmt.Errorf("clusterprovider: re-keying the port leases of %s to %s: %w", name, key, err)
}
if moved > 0 {
d.Log.Info("cluster provider: re-keyed the port leases of a legacy cluster name",
"cluster", name, "key", key, "leases", moved)
}
}
if d.Networks != nil {
moved, err := d.Networks.Rekey(name, key)
if err != nil {
return fmt.Errorf("clusterprovider: re-keying the subnet lease of %s to %s: %w", name, key, err)
}
if moved > 0 {
d.Log.Info("cluster provider: re-keyed the subnet lease of a legacy cluster name",
"cluster", name, "key", key, "leases", moved)
}
}
return nil
}
func renameLegacy(log *slog.Logger, parent, from, to, name, key string) error {
fromPath := filepath.Join(parent, from)
toPath := filepath.Join(parent, to)
if _, err := os.Stat(fromPath); err != nil {
if errors.Is(err, fs.ErrNotExist) {
return nil
}
return fmt.Errorf("clusterprovider: reading %s before the legacy-key migration: %w", fromPath, err)
}
if _, err := os.Stat(toPath); err == nil {
log.Warn("cluster provider: a legacy directory and this cluster's keyed directory both "+
"exist, so the legacy one is left exactly where it is and nothing is merged into the "+
"keyed one",
"cluster", name, "key", key, "legacy", fromPath, "keyed", toPath)
return nil
}
if err := os.Rename(fromPath, toPath); err != nil {
return fmt.Errorf("clusterprovider: renaming %s to %s: %w. The cluster's identity inside "+
"this provider is the key, and a state dir still named by the bare cluster name is one "+
"no later pass finds: the node records, the instance token and the identity bundle "+
"would all read as absent, and the first pass would provision over a cluster that is "+
"already running", fromPath, toPath, err)
}
log.Info("cluster provider: migrated a directory named by a bare cluster name to this "+
"cluster's key", "cluster", name, "key", key, "from", fromPath, "to", toPath)
return nil
}