feat(workload): add Workload/Container/App store foundation
Introduces the data layer for the Workload refactor (see docs/plans/workload-refactor.md): three new tables and store methods, no behavior changes elsewhere yet. - workloads: unifying primitive over Project/Stack/StaticSite, paired via UNIQUE(kind, ref_id). Notification + webhook config hosted here so it lives in one place across kinds. - containers: normalized index of every Tinyforge-managed container with first-class subdomain/proxy_route_id/npm_proxy_id columns (heavily queried by ListProxyRoutes / stale detection). - apps: optional grouping of workloads; schema only, no UI in v1. Foundation only — deployer surgery, reconciler, and consumer switchover land in the next commit.
This commit is contained in:
@@ -179,6 +179,60 @@ func (s *Store) runMigrations() error {
|
||||
`CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_received_at ON webhook_deliveries(received_at)`,
|
||||
}
|
||||
|
||||
// Workload refactor tables (2026-05-09). Workload is the unifying primitive
|
||||
// over Project / Stack / StaticSite; Container is the normalized index of
|
||||
// every Tinyforge-managed container; Apps is an optional grouping. These
|
||||
// live alongside (not inside) the schema constant so existing databases
|
||||
// pick them up on restart.
|
||||
workloadTables := []string{
|
||||
`CREATE TABLE IF NOT EXISTS workloads (
|
||||
id TEXT PRIMARY KEY,
|
||||
kind TEXT NOT NULL,
|
||||
ref_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
app_id TEXT NOT NULL DEFAULT '',
|
||||
notification_url TEXT NOT NULL DEFAULT '',
|
||||
notification_secret TEXT NOT NULL DEFAULT '',
|
||||
webhook_secret TEXT NOT NULL DEFAULT '',
|
||||
webhook_signing_secret TEXT NOT NULL DEFAULT '',
|
||||
webhook_require_signature INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE(kind, ref_id)
|
||||
)`,
|
||||
`CREATE TABLE IF NOT EXISTS containers (
|
||||
id TEXT PRIMARY KEY,
|
||||
workload_id TEXT NOT NULL,
|
||||
workload_kind TEXT NOT NULL,
|
||||
role TEXT NOT NULL DEFAULT '',
|
||||
container_id TEXT NOT NULL DEFAULT '',
|
||||
image_ref TEXT NOT NULL DEFAULT '',
|
||||
image_tag TEXT NOT NULL DEFAULT '',
|
||||
host TEXT NOT NULL DEFAULT 'local',
|
||||
state TEXT NOT NULL DEFAULT '',
|
||||
port INTEGER NOT NULL DEFAULT 0,
|
||||
subdomain TEXT NOT NULL DEFAULT '',
|
||||
proxy_route_id TEXT NOT NULL DEFAULT '',
|
||||
npm_proxy_id INTEGER NOT NULL DEFAULT 0,
|
||||
last_seen_at TEXT NOT NULL DEFAULT '',
|
||||
extra_json TEXT NOT NULL DEFAULT '{}',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)`,
|
||||
`CREATE TABLE IF NOT EXISTS apps (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)`,
|
||||
}
|
||||
for _, t := range workloadTables {
|
||||
if _, err := s.db.Exec(t); err != nil {
|
||||
return fmt.Errorf("create workload table: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Additive stack tables (2026-04-16). Created here rather than in the
|
||||
// schema constant so older databases pick them up on restart.
|
||||
statsTables := []string{
|
||||
@@ -290,6 +344,15 @@ func (s *Store) runMigrations() error {
|
||||
`CREATE INDEX IF NOT EXISTS idx_container_stats_container_ts ON container_stats_samples(container_id, ts)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_container_stats_ts ON container_stats_samples(ts)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_system_stats_ts ON system_stats_samples(ts)`,
|
||||
// Workload refactor indexes (2026-05-09).
|
||||
`CREATE INDEX IF NOT EXISTS idx_workloads_kind ON workloads(kind)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_workloads_app_id ON workloads(app_id) WHERE app_id != ''`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_workloads_ref ON workloads(kind, ref_id)`,
|
||||
`CREATE UNIQUE INDEX IF NOT EXISTS idx_workloads_webhook_secret ON workloads(webhook_secret) WHERE webhook_secret != ''`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_containers_workload ON containers(workload_id)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_containers_state ON containers(state)`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_containers_container_id ON containers(container_id) WHERE container_id != ''`,
|
||||
`CREATE INDEX IF NOT EXISTS idx_containers_kind ON containers(workload_kind)`,
|
||||
}
|
||||
for _, idx := range indexes {
|
||||
if _, err := s.db.Exec(idx); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user