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
package kineetcd
import (
"context"
"errors"
"fmt"
"strings"
"time"
clientv3 "go.etcd.io/etcd/client/v3"
"go.uber.org/zap"
"github.com/johnandersen777/socialweb-computer-kcp/internal/contract/tenantstore"
)
const DefaultTimeout = 15 * time.Second
const registryPrefix = "/registry/"
type Config struct {
Timeout time.Duration
}
type Store struct {
timeout time.Duration
}
var _ tenantstore.Eraser = (*Store)(nil)
func New(cfg Config) *Store {
if cfg.Timeout <= 0 {
cfg.Timeout = DefaultTimeout
}
return &Store{timeout: cfg.Timeout}
}
func ServiceAccountKey(sa tenantstore.ServiceAccount) string {
return registryPrefix + "serviceaccounts/" + sa.Namespace + "/" + sa.Name
}
func TokenSecretPrefix(sa tenantstore.ServiceAccount) string {
return registryPrefix + "secrets/" + sa.Namespace + "/" + sa.Name + "-token-"
}
func (s *Store) EraseServiceAccount(ctx context.Context, endpoint string, sa tenantstore.ServiceAccount) (tenantstore.Erased, error) {
if endpoint == "" {
return tenantstore.Erased{}, errors.New("kineetcd: no datastore endpoint to erase the ServiceAccount from")
}
if sa.Namespace == "" || sa.Name == "" || strings.Contains(sa.Namespace+sa.Name, "/") {
return tenantstore.Erased{}, fmt.Errorf("kineetcd: %q/%q does not name one ServiceAccount", sa.Namespace, sa.Name)
}
ctx, cancel := context.WithTimeout(ctx, s.timeout)
defer cancel()
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{endpoint},
DialTimeout: s.timeout,
Context: ctx,
Logger: zap.NewNop(),
})
if err != nil {
return tenantstore.Erased{}, fmt.Errorf("kineetcd: dial the datastore at %s: %w", endpoint, err)
}
defer cli.Close()
var out tenantstore.Erased
prefix := TokenSecretPrefix(sa)
secrets, err := cli.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithKeysOnly())
if err != nil {
return out, fmt.Errorf("kineetcd: list the token Secrets of %s/%s at %s: %w", sa.Namespace, sa.Name, endpoint, err)
}
for _, kv := range secrets.Kvs {
erased, err := erase(ctx, cli, string(kv.Key), kv.ModRevision)
if err != nil {
return out, err
}
if erased {
out.Secrets = append(out.Secrets, strings.TrimPrefix(string(kv.Key), registryPrefix+"secrets/"+sa.Namespace+"/"))
}
}
key := ServiceAccountKey(sa)
account, err := cli.Get(ctx, key, clientv3.WithKeysOnly())
if err != nil {
return out, fmt.Errorf("kineetcd: read %s at %s: %w", key, endpoint, err)
}
for _, kv := range account.Kvs {
erased, err := erase(ctx, cli, string(kv.Key), kv.ModRevision)
if err != nil {
return out, err
}
out.ServiceAccount = out.ServiceAccount || erased
}
return out, nil
}
func erase(ctx context.Context, cli *clientv3.Client, key string, rev int64) (bool, error) {
resp, err := cli.Txn(ctx).
If(clientv3.Compare(clientv3.ModRevision(key), "=", rev)).
Then(clientv3.OpDelete(key)).
Else(clientv3.OpGet(key, clientv3.WithKeysOnly())).
Commit()
if err != nil {
return false, fmt.Errorf("kineetcd: delete %s at revision %d: %w", key, rev, err)
}
if resp.Succeeded {
return true, nil
}
for _, r := range resp.Responses {
if rng := r.GetResponseRange(); rng != nil && len(rng.Kvs) == 0 {
return false, nil
}
}
return false, fmt.Errorf("kineetcd: %s changed after revision %d while it was being deleted", key, rev)
}