9bb4b09a04
CI / Lint + build + test (push) Has been cancelled
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.
127 lines
3.2 KiB
Go
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
|
|
}
|