Files
Vetting/internal/store/artifacts.go
T
josh 9bb4b09a04
CI / Lint + build + test (push) Has been cancelled
Initial commit: full Phases 1-6 implementation
Post-repair hardware validation pipeline for Proxmox cluster hosts.
Go orchestrator + in-image agent + mkosi live image + bundled dnsmasq
PXE + SQLite + HTMX/SSE UI + notify registry + janitor + full docs.
2026-04-17 21:32:10 -04:00

127 lines
3.2 KiB
Go

package store
import (
"context"
"database/sql"
"fmt"
"vetting/internal/model"
)
type Artifact struct {
ID int64
RunID int64
StageID *int64
Kind string // inventory|spec_diff|hold_key|report|log|fio|iperf|smart
Path string
SHA256 string
SizeBytes int64
}
type Artifacts struct {
DB *sql.DB
}
func (a *Artifacts) Create(ctx context.Context, art Artifact) (int64, error) {
res, err := a.DB.ExecContext(ctx, `
INSERT INTO artifacts(run_id, stage_id, kind, path, sha256, size_bytes)
VALUES(?,?,?,?,?,?)
`, art.RunID, nullInt64(art.StageID), art.Kind, art.Path, art.SHA256, art.SizeBytes)
if err != nil {
return 0, fmt.Errorf("insert artifact: %w", err)
}
return res.LastInsertId()
}
// DeleteForRun removes every artifact row for a run. Returns the rows
// that were deleted so the caller can unlink the on-disk files. Used by
// the janitor; ordinary flow treats artifacts as append-only.
func (a *Artifacts) DeleteForRun(ctx context.Context, runID int64) ([]Artifact, error) {
arts, err := a.ListForRun(ctx, runID)
if err != nil {
return nil, err
}
if _, err := a.DB.ExecContext(ctx, `DELETE FROM artifacts WHERE run_id = ?`, runID); err != nil {
return nil, fmt.Errorf("delete artifacts for run %d: %w", runID, err)
}
return arts, nil
}
func (a *Artifacts) ListForRun(ctx context.Context, runID int64) ([]Artifact, error) {
rows, err := a.DB.QueryContext(ctx, `
SELECT id, run_id, stage_id, kind, path, sha256, size_bytes
FROM artifacts WHERE run_id = ? ORDER BY id
`, runID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []Artifact
for rows.Next() {
var ar Artifact
var stageID sql.NullInt64
if err := rows.Scan(&ar.ID, &ar.RunID, &stageID, &ar.Kind, &ar.Path, &ar.SHA256, &ar.SizeBytes); err != nil {
return nil, err
}
if stageID.Valid {
v := stageID.Int64
ar.StageID = &v
}
out = append(out, ar)
}
return out, rows.Err()
}
type SpecDiffs struct {
DB *sql.DB
}
func (s *SpecDiffs) ReplaceForRun(ctx context.Context, runID int64, diffs []model.SpecDiff) error {
tx, err := s.DB.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() { _ = tx.Rollback() }()
if _, err := tx.ExecContext(ctx, `DELETE FROM spec_diffs WHERE run_id = ?`, runID); err != nil {
return err
}
for _, d := range diffs {
if _, err := tx.ExecContext(ctx, `
INSERT INTO spec_diffs(run_id, field, expected, actual, severity, ignored)
VALUES(?,?,?,?,?,?)
`, runID, d.Field, d.Expected, d.Actual, d.Severity, 0); err != nil {
return err
}
}
return tx.Commit()
}
func (s *SpecDiffs) ListForRun(ctx context.Context, runID int64) ([]model.SpecDiff, error) {
rows, err := s.DB.QueryContext(ctx, `
SELECT id, run_id, field, COALESCE(expected,''), COALESCE(actual,''), severity, ignored
FROM spec_diffs WHERE run_id = ? ORDER BY id
`, runID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []model.SpecDiff
for rows.Next() {
var d model.SpecDiff
var ignored int
if err := rows.Scan(&d.ID, &d.RunID, &d.Field, &d.Expected, &d.Actual, &d.Severity, &ignored); err != nil {
return nil, err
}
d.Ignored = ignored != 0
out = append(out, d)
}
return out, rows.Err()
}
func nullInt64(p *int64) any {
if p == nil {
return nil
}
return *p
}