carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package hostbox
2
3import (
4 "context"
5 "database/sql"
6 "encoding/json"
7 "errors"
8 "path/filepath"
9
10 "github.com/carlosframework/platform/internal/hostguard"
11)
12
13type StopInventoryDB struct {
14 db *sql.DB
15 remote JournalCall
16 shared bool
17}
18
19func (d *StopInventoryDB) Close() error {
20 if d == nil {
21 return nil
22 }
23 if d.remote != nil || d.shared {
24 return nil
25 }
26 return d.db.Close()
27}
28func (d *StopInventoryDB) WriteStopInventory(ctx context.Context, in StopInventory) error {
29 if d == nil {
30 return ErrUnsupported
31 }
32 if d.remote != nil {
33 prior, err := journalCall(ctx, d.remote, JournalRequest{Op: "inventory-version", Target: hostguard.FenceTarget{Host: in.Host, Incarnation: in.Incarnation, Stamp: in.InstallationStamp}})
34 if err != nil {
35 return err
36 }
37 request := JournalRequest{Op: "inventory-write", Inventory: in, Revision: prior.Revision}
38 _, err = journalCall(ctx, d.remote, request)
39 return err
40 }
41
42 if !component(in.Host) || in.Incarnation == "" || in.InstallationStamp == 0 || in.Box == "" || in.Enrollment == "" || in.DirectoryRevision == "" || (len(in.Entries) == 0 && !in.Complete) {
43 return ErrTypedInventory
44 }
45 for _, a := range in.Entries {
46 if !component(a.ArtifactID) || !component(a.ManagedRoot) || !filepath.IsLocal(a.RelativePath) || a.Ownership == "" || (a.Materialization != "intended" && a.Materialization != "materialized") {
47 return ErrTypedInventory
48 }
49 }
50 b, err := json.Marshal(in)
51 if err != nil {
52 return err
53 }
54 tx, commit, rollback, err := journalTransaction(ctx, d.db)
55 if err != nil {
56 return err
57 }
58 defer rollback()
59 if _, err = tx.ExecContext(ctx, "INSERT INTO stop_inventory(host,incarnation,stamp,body) VALUES(?,?,?,?) ON CONFLICT(host,incarnation,stamp) DO UPDATE SET body=excluded.body", in.Host, in.Incarnation, in.InstallationStamp, b); err != nil {
60 return err
61 }
62 request, _ := ctx.Value(journalRequestKey{}).(string)
63 if _, err = tx.ExecContext(ctx, "INSERT INTO journal_inventory_versions(host,incarnation,stamp,revision,request) VALUES(?,?,?,1,?) ON CONFLICT(host,incarnation,stamp) DO UPDATE SET revision=revision+1,request=excluded.request", in.Host, in.Incarnation, in.InstallationStamp, request); err != nil {
64 return err
65 }
66 return commit()
67}
68func (d *StopInventoryDB) ReadStopInventory(ctx context.Context, host string) (StopInventory, error) {
69 if d == nil {
70 return StopInventory{}, ErrUnsupported
71 }
72 if d.remote != nil {
73 out, err := journalCall(ctx, d.remote, JournalRequest{Op: "inventory-read", Target: hostguard.FenceTarget{Host: host}})
74 return out.Inventory, err
75 }
76
77 var b []byte
78 err := journalExecutorFor(ctx, d.db).QueryRowContext(ctx, "SELECT body FROM stop_inventory WHERE host=? ORDER BY stamp DESC LIMIT 1", host).Scan(&b)
79 if err != nil {
80 return StopInventory{}, err
81 }
82 var in StopInventory
83 err = json.Unmarshal(b, &in)
84 return in, err
85}
86func (d *StopInventoryDB) ReadExactStopInventory(ctx context.Context, t hostguard.FenceTarget) (StopInventory, error) {
87 if d == nil {
88 return StopInventory{}, ErrUnsupported
89 }
90 if d.remote != nil {
91 out, err := journalCall(ctx, d.remote, JournalRequest{Op: "inventory-exact", Target: t})
92 return out.Inventory, err
93 }
94
95 var b []byte
96 err := journalExecutorFor(ctx, d.db).QueryRowContext(ctx, "SELECT body FROM stop_inventory WHERE host=? AND incarnation=? AND stamp=?", t.Host, t.Incarnation, t.Stamp).Scan(&b)
97 if err != nil {
98 return StopInventory{}, err
99 }
100 var in StopInventory
101 err = json.Unmarshal(b, &in)
102 return in, err
103}
104
105func (d *StopInventoryDB) ReadInstallation(ctx context.Context, host, incarnation string, stamp uint64) (StopInventory, error) {
106 return d.ReadExactStopInventory(ctx, hostguard.FenceTarget{Host: host, Incarnation: incarnation, Stamp: stamp})
107}
108
109func (d *StopInventoryDB) inventoryVersion(ctx context.Context, target hostguard.FenceTarget) (JournalResponse, error) {
110 var out JournalResponse
111 err := journalExecutorFor(ctx, d.db).QueryRowContext(ctx, "SELECT v.revision,v.request,i.body FROM journal_inventory_versions v JOIN stop_inventory i ON i.host=v.host AND i.incarnation=v.incarnation AND i.stamp=v.stamp WHERE v.host=? AND v.incarnation=? AND v.stamp=?", target.Host, target.Incarnation, target.Stamp).Scan(&out.Revision, &out.LastRequest, newInventoryBytes(&out.Inventory))
112 if errors.Is(err, sql.ErrNoRows) {
113 return out, nil
114 }
115 return out, err
116}
117
118type inventoryScanner struct{ out *StopInventory }
119
120func newInventoryBytes(out *StopInventory) *inventoryScanner { return &inventoryScanner{out} }
121func (s *inventoryScanner) Scan(value any) error {
122 raw, ok := value.([]byte)
123 if !ok {
124 return ErrTypedInventory
125 }
126 return json.Unmarshal(raw, s.out)
127}
128