Files
encapi/internal/database/statuses.go
T
unkinben 373d21a744 initial implementation: encapi ENC server + CLI
Postgres-backed External Node Classifier for Puppet, replacing Cobbler.
- encapi HTTP server (chi + pgx): read/write API + two ENC document shapes
  (reshaped for the exec terminus; cobbler-wire for enc_direct_facts.rb)
- encapi-cli: classify/node/role/status CRUD + import-cobbler seeder
- pkg/client Go SDK; unit tests across all packages (DB via testcontainers)
- Dockerfile (distroless), Makefile, nfpm RPM (encapi-cli + encapi-enc wrapper),
  Woodpecker CI, docs/cutover.md
2026-07-04 23:45:15 +10:00

76 lines
2.0 KiB
Go

package database
import (
"context"
"errors"
"fmt"
"github.com/jackc/pgx/v5"
"git.unkin.net/unkin/encapi/pkg/models"
)
// ErrNotFound is returned when a requested entity does not exist.
var ErrNotFound = errors.New("not found")
// UpsertStatus creates or updates a status (Puppet environment).
func (db *DB) UpsertStatus(ctx context.Context, s *models.Status) error {
_, err := db.Pool.Exec(ctx, `
INSERT INTO statuses (name, description)
VALUES ($1, $2)
ON CONFLICT (name) DO UPDATE
SET description = EXCLUDED.description, updated_at = NOW()
`, s.Name, s.Description)
if err != nil {
return fmt.Errorf("upsert status %q: %w", s.Name, err)
}
return nil
}
// GetStatus returns a single status or ErrNotFound.
func (db *DB) GetStatus(ctx context.Context, name string) (*models.Status, error) {
var s models.Status
err := db.Pool.QueryRow(ctx,
`SELECT name, description FROM statuses WHERE name = $1`, name,
).Scan(&s.Name, &s.Description)
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrNotFound
}
if err != nil {
return nil, fmt.Errorf("get status %q: %w", name, err)
}
return &s, nil
}
// ListStatuses returns all statuses ordered by name.
func (db *DB) ListStatuses(ctx context.Context) ([]models.Status, error) {
rows, err := db.Pool.Query(ctx, `SELECT name, description FROM statuses ORDER BY name`)
if err != nil {
return nil, fmt.Errorf("list statuses: %w", err)
}
defer rows.Close()
out := []models.Status{}
for rows.Next() {
var s models.Status
if err := rows.Scan(&s.Name, &s.Description); err != nil {
return nil, fmt.Errorf("scan status: %w", err)
}
out = append(out, s)
}
return out, rows.Err()
}
// DeleteStatus removes a status. It fails if any node still references it
// (enforced by the nodes.environment foreign key).
func (db *DB) DeleteStatus(ctx context.Context, name string) error {
tag, err := db.Pool.Exec(ctx, `DELETE FROM statuses WHERE name = $1`, name)
if err != nil {
return fmt.Errorf("delete status %q: %w", name, err)
}
if tag.RowsAffected() == 0 {
return ErrNotFound
}
return nil
}