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
package baosync
import (
"context"
"errors"
"fmt"
"strings"
"github.com/johnandersen777/socialweb-computer-kcp/internal/vault/openbao"
)
type Report struct {
Applied []Op
Conflicts []string
Err error
}
func (r Report) Changed() bool { return len(r.Applied) > 0 }
func (r Report) Summary() string {
if len(r.Applied) == 0 && len(r.Conflicts) == 0 && r.Err == nil {
return "in sync"
}
var b strings.Builder
counts := map[OpKind]int{}
var order []OpKind
for _, o := range r.Applied {
if counts[o.Kind] == 0 {
order = append(order, o.Kind)
}
counts[o.Kind]++
}
for i, k := range order {
if i > 0 {
b.WriteString(", ")
}
fmt.Fprintf(&b, "%s x%d", k, counts[k])
}
if len(r.Conflicts) > 0 {
if b.Len() > 0 {
b.WriteString("; ")
}
fmt.Fprintf(&b, "%d conflict(s): %s", len(r.Conflicts), strings.Join(r.Conflicts, "; "))
}
if r.Err != nil {
if b.Len() > 0 {
b.WriteString("; ")
}
b.WriteString("errors: " + r.Err.Error())
}
return b.String()
}
func enableSecret(ctx context.Context, c *openbao.Client, o Op) error {
var err error
switch o.Type {
case MountKV2:
err = c.EnableKV(ctx, o.Target, 2)
case MountKV:
err = c.EnableKV(ctx, o.Target, 1)
case MountPKI:
err = c.MountPKI(ctx, o.Target)
default:
err = fmt.Errorf("baosync: no way to enable a %s mount", o.Type)
}
if err == nil && len(o.Body) > 0 {
err = c.TuneMount(ctx, o.Target, o.Body)
}
return err
}
func applyOp(ctx context.Context, c *openbao.Client, o Op) error {
switch o.Kind {
case OpEnableSecret:
return enableSecret(ctx, c, o)
case OpEnableAuth:
return c.EnableAuth(ctx, o.Target, o.Type)
case OpDisableAuth:
return c.DisableAuth(ctx, o.Target)
case OpPutPolicy:
return c.PutPolicy(ctx, o.Target, o.HCL)
case OpWriteObject:
return c.Write(ctx, o.Target, o.Body)
case OpDeleteObject, OpDeleteKV:
return c.Delete(ctx, o.Target)
case OpDeletePolicy:
return c.DeletePolicy(ctx, o.Target)
case OpDisableSecret:
return c.DisableMount(ctx, o.Target)
case OpRegister:
return c.Write(ctx, "sys/plugins/catalog/"+o.Target, o.Body)
case OpReload:
return c.Write(ctx, "sys/plugins/reload/backend", o.Body)
case OpDeregister:
return c.Delete(ctx, "sys/plugins/catalog/"+o.Target)
}
return fmt.Errorf("baosync: unknown op %s", o.Kind)
}
func Apply(ctx context.Context, c *openbao.Client, p Plan) Report {
r := Report{Conflicts: p.Conflicts}
var errs []error
for _, o := range p.Ops {
if err := applyOp(ctx, c, o); err != nil {
errs = append(errs, fmt.Errorf("%s: %w", o, err))
continue
}
r.Applied = append(r.Applied, o)
}
r.Err = errors.Join(errs...)
return r
}
func Sync(ctx context.Context, c *openbao.Client, d Desired) (Report, error) {
if err := d.Validate(); err != nil {
return Report{}, err
}
a, err := Read(ctx, c, d)
if err != nil {
return Report{}, err
}
return Apply(ctx, c, Diff(d, a)), nil
}