package hostbox import ( "context" "database/sql" "encoding/json" "errors" "path/filepath" "github.com/carlosframework/platform/internal/hostguard" ) type StopInventoryDB struct { db *sql.DB remote JournalCall shared bool } func (d *StopInventoryDB) Close() error { if d == nil { return nil } if d.remote != nil || d.shared { return nil } return d.db.Close() } func (d *StopInventoryDB) WriteStopInventory(ctx context.Context, in StopInventory) error { if d == nil { return ErrUnsupported } if d.remote != nil { prior, err := journalCall(ctx, d.remote, JournalRequest{Op: "inventory-version", Target: hostguard.FenceTarget{Host: in.Host, Incarnation: in.Incarnation, Stamp: in.InstallationStamp}}) if err != nil { return err } request := JournalRequest{Op: "inventory-write", Inventory: in, Revision: prior.Revision} _, err = journalCall(ctx, d.remote, request) return err } if !component(in.Host) || in.Incarnation == "" || in.InstallationStamp == 0 || in.Box == "" || in.Enrollment == "" || in.DirectoryRevision == "" || (len(in.Entries) == 0 && !in.Complete) { return ErrTypedInventory } for _, a := range in.Entries { if !component(a.ArtifactID) || !component(a.ManagedRoot) || !filepath.IsLocal(a.RelativePath) || a.Ownership == "" || (a.Materialization != "intended" && a.Materialization != "materialized") { return ErrTypedInventory } } b, err := json.Marshal(in) if err != nil { return err } tx, commit, rollback, err := journalTransaction(ctx, d.db) if err != nil { return err } defer rollback() 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 { return err } request, _ := ctx.Value(journalRequestKey{}).(string) 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 { return err } return commit() } func (d *StopInventoryDB) ReadStopInventory(ctx context.Context, host string) (StopInventory, error) { if d == nil { return StopInventory{}, ErrUnsupported } if d.remote != nil { out, err := journalCall(ctx, d.remote, JournalRequest{Op: "inventory-read", Target: hostguard.FenceTarget{Host: host}}) return out.Inventory, err } var b []byte err := journalExecutorFor(ctx, d.db).QueryRowContext(ctx, "SELECT body FROM stop_inventory WHERE host=? ORDER BY stamp DESC LIMIT 1", host).Scan(&b) if err != nil { return StopInventory{}, err } var in StopInventory err = json.Unmarshal(b, &in) return in, err } func (d *StopInventoryDB) ReadExactStopInventory(ctx context.Context, t hostguard.FenceTarget) (StopInventory, error) { if d == nil { return StopInventory{}, ErrUnsupported } if d.remote != nil { out, err := journalCall(ctx, d.remote, JournalRequest{Op: "inventory-exact", Target: t}) return out.Inventory, err } var b []byte 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) if err != nil { return StopInventory{}, err } var in StopInventory err = json.Unmarshal(b, &in) return in, err } func (d *StopInventoryDB) ReadInstallation(ctx context.Context, host, incarnation string, stamp uint64) (StopInventory, error) { return d.ReadExactStopInventory(ctx, hostguard.FenceTarget{Host: host, Incarnation: incarnation, Stamp: stamp}) } func (d *StopInventoryDB) inventoryVersion(ctx context.Context, target hostguard.FenceTarget) (JournalResponse, error) { var out JournalResponse 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)) if errors.Is(err, sql.ErrNoRows) { return out, nil } return out, err } type inventoryScanner struct{ out *StopInventory } func newInventoryBytes(out *StopInventory) *inventoryScanner { return &inventoryScanner{out} } func (s *inventoryScanner) Scan(value any) error { raw, ok := value.([]byte) if !ok { return ErrTypedInventory } return json.Unmarshal(raw, s.out) }