refactor(workload): extract Instance entirely; Container is canonical
Build / build (push) Successful in 10m41s
Build / build (push) Successful in 10m41s
End-to-end extraction of the Instance concept. After this commit:
* internal/store/instances.go — DELETED
* internal/store/models.go — Instance struct gone, ProxyRoute moved here
* containers table is the single source of truth for project/stack/site
container state. instances table is dropped via DROP TABLE migration
(idempotent; re-runnable on every boot).
* Legacy tinyforge.project / tinyforge.stage / tinyforge.instance-id
Docker labels are no longer emitted; only tinyforge.workload.{id,kind},
tinyforge.role, and tinyforge.managed are stamped on new containers.
Backend rewrites:
- internal/deployer: executeDeploy + blueGreenDeploy + rollback +
promote use store.Container natively. New
removeContainer() replaces removeInstance().
enforceMaxInstances reads via
ListContainersByStageID.
- internal/reconciler: legacy tinyforge.instance-id dispatch removed;
upsertByWorkloadLabel now finds existing rows
by docker container ID first and falls back to
the deterministic workloadID:role key.
- internal/stale/scanner: Scan + new FindStaleContainers walk the
containers table; emit StaleContainer JSON.
- internal/stats/collector: ListContainers replaces ListAllInstances.
- internal/webhook/handler: workload-secret lookup tried first; falls back
to project / static_site secret column.
- internal/api: instances.go, stale.go, stats.go, stats_history.go,
projects.go, settings.go, docker.go, dns.go all read /
write through Container.
Docker layer:
- ManagedContainer exposes WorkloadID/Kind/Role from the canonical labels.
- ListContainers filters by tinyforge.managed=true.
- Network creation uses LabelManaged instead of LabelProject.
Frontend:
- Instance type is now a Container alias; .status → .state,
.last_alive_at → .last_seen_at.
- InstanceCard takes stageId as a prop (no longer derived from Instance).
- StaleContainer JSON shape rewritten: { container, workload_name, role,
days_stale }. StaleContainerCard + /containers/stale page updated.
- ProjectCard / homepage / SystemHealthCard filter by .state.
The migration loop now tolerates "no such table" alongside "duplicate
column" / "already exists" so obsolete ALTER TABLE entries targeting the
dropped instances table no-op cleanly on first boot.
Tests: store + deployer + reconciler + webhook + staticsite + notify all
still pass. Frontend svelte-check: zero errors.
This commit is contained in:
@@ -9,17 +9,10 @@ import (
|
||||
|
||||
// Labels applied to all containers managed by Tinyforge.
|
||||
//
|
||||
// Workload-shaped labels (LabelWorkloadID, LabelWorkloadKind, LabelRole,
|
||||
// LabelManaged) are the canonical set going forward and what the reconciler
|
||||
// queries by. The legacy project/stage/instance-id labels are still emitted
|
||||
// alongside them for back-compat with anything that selects on them
|
||||
// (operator runbooks, monitoring scrape rules, ad-hoc shell debugging) — they
|
||||
// will be removed once the migration soaks.
|
||||
// The legacy tinyforge.project / tinyforge.stage / tinyforge.instance-id
|
||||
// labels were removed in the workload refactor — the deployer now stamps
|
||||
// only the workload-shaped labels below at create time.
|
||||
const (
|
||||
LabelProject = "tinyforge.project"
|
||||
LabelStage = "tinyforge.stage"
|
||||
LabelInstanceID = "tinyforge.instance-id"
|
||||
|
||||
LabelManaged = "tinyforge.managed" // present on every Tinyforge-managed container
|
||||
LabelWorkloadID = "tinyforge.workload.id" // workload row primary key
|
||||
LabelWorkloadKind = "tinyforge.workload.kind" // 'project' | 'stack' | 'site'
|
||||
|
||||
@@ -39,15 +39,6 @@ type ContainerConfig struct {
|
||||
// Tinyforge management labels are added automatically via Project, Stage, and InstanceID.
|
||||
Labels map[string]string
|
||||
|
||||
// Project is the Tinyforge project name (used for labelling).
|
||||
Project string
|
||||
|
||||
// Stage is the Tinyforge stage name (used for labelling).
|
||||
Stage string
|
||||
|
||||
// InstanceID is the Tinyforge instance ID (used for labelling).
|
||||
InstanceID string
|
||||
|
||||
// WorkloadID is the unifying primitive's row ID (Workload.ID). Future
|
||||
// reconciler / global views key off this label, so it must be set on
|
||||
// every Tinyforge-managed container (project, stack, site).
|
||||
@@ -106,12 +97,7 @@ func (c *Client) CreateContainer(ctx context.Context, cfg ContainerConfig) (stri
|
||||
for k, v := range cfg.Labels {
|
||||
labels[k] = v
|
||||
}
|
||||
// Legacy labels (kept for back-compat with operator runbooks /
|
||||
// monitoring scrape rules; will be removed after the workload soak).
|
||||
labels[LabelProject] = cfg.Project
|
||||
labels[LabelStage] = cfg.Stage
|
||||
labels[LabelInstanceID] = cfg.InstanceID
|
||||
// Workload-shaped labels — canonical going forward.
|
||||
// Workload-shaped labels — the canonical Tinyforge label set.
|
||||
labels[LabelManaged] = "true"
|
||||
if cfg.WorkloadID != "" {
|
||||
labels[LabelWorkloadID] = cfg.WorkloadID
|
||||
@@ -225,26 +211,27 @@ func (c *Client) RestartContainer(ctx context.Context, containerID string, timeo
|
||||
}
|
||||
|
||||
// ManagedContainer holds summary information about a container managed by Tinyforge.
|
||||
// WorkloadID/Kind/Role are pulled from the canonical Tinyforge labels.
|
||||
type ManagedContainer struct {
|
||||
ID string
|
||||
Name string
|
||||
Image string
|
||||
Status string
|
||||
State string
|
||||
Project string
|
||||
Stage string
|
||||
InstanceID string
|
||||
Ports []uint16
|
||||
ID string
|
||||
Name string
|
||||
Image string
|
||||
Status string
|
||||
State string
|
||||
WorkloadID string
|
||||
WorkloadKind string
|
||||
Role string
|
||||
Ports []uint16
|
||||
}
|
||||
|
||||
// ListContainers returns all containers matching the given label filters.
|
||||
// Pass nil or an empty map to list all Tinyforge managed containers.
|
||||
// Label filters are key=value pairs applied as Docker label filters.
|
||||
// ListContainers returns all Tinyforge-managed containers (label
|
||||
// tinyforge.managed=true), optionally narrowed by additional label filters.
|
||||
// Returns the workload labels so callers can dispatch / display without an
|
||||
// extra inspect call.
|
||||
func (c *Client) ListContainers(ctx context.Context, labelFilters map[string]string) ([]ManagedContainer, error) {
|
||||
filterArgs := make(client.Filters)
|
||||
|
||||
// Always filter by the Tinyforge project label to only return managed containers.
|
||||
filterArgs.Add("label", LabelProject)
|
||||
filterArgs.Add("label", LabelManaged+"=true")
|
||||
|
||||
for k, v := range labelFilters {
|
||||
if v != "" {
|
||||
@@ -278,15 +265,15 @@ func (c *Client) ListContainers(ctx context.Context, labelFilters map[string]str
|
||||
}
|
||||
|
||||
result = append(result, ManagedContainer{
|
||||
ID: ctr.ID,
|
||||
Name: name,
|
||||
Image: ctr.Image,
|
||||
Status: ctr.Status,
|
||||
State: string(ctr.State),
|
||||
Project: ctr.Labels[LabelProject],
|
||||
Stage: ctr.Labels[LabelStage],
|
||||
InstanceID: ctr.Labels[LabelInstanceID],
|
||||
Ports: ports,
|
||||
ID: ctr.ID,
|
||||
Name: name,
|
||||
Image: ctr.Image,
|
||||
Status: ctr.Status,
|
||||
State: string(ctr.State),
|
||||
WorkloadID: ctr.Labels[LabelWorkloadID],
|
||||
WorkloadKind: ctr.Labels[LabelWorkloadKind],
|
||||
Role: ctr.Labels[LabelRole],
|
||||
Ports: ports,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -308,9 +295,8 @@ type ReconcileItem struct {
|
||||
|
||||
// ListAllForReconciler returns every container the daemon knows about whose
|
||||
// labels mark it as Tinyforge-managed by ANY of the supported schemes:
|
||||
// - tinyforge.managed (canonical, new)
|
||||
// - tinyforge.project / tinyforge.instance-id (legacy project)
|
||||
// - tinyforge.static-site (legacy site)
|
||||
// - tinyforge.managed (canonical — every project, stack, site we own)
|
||||
// - tinyforge.static-site (sites that predate the workload labels)
|
||||
// - com.docker.compose.project starting with "tinyforge-" (stacks)
|
||||
//
|
||||
// The Docker API does not support OR'd label filters, so we list everything
|
||||
@@ -361,9 +347,6 @@ func isTinyforgeManaged(labels map[string]string) bool {
|
||||
if labels[LabelManaged] == "true" {
|
||||
return true
|
||||
}
|
||||
if labels[LabelProject] != "" || labels[LabelInstanceID] != "" {
|
||||
return true
|
||||
}
|
||||
if _, ok := labels["tinyforge.static-site"]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ func (c *Client) EnsureNetwork(ctx context.Context, networkName string) (string,
|
||||
resp, err := c.api.NetworkCreate(ctx, networkName, client.NetworkCreateOptions{
|
||||
Driver: "bridge",
|
||||
Labels: map[string]string{
|
||||
LabelProject: "tinyforge",
|
||||
LabelManaged: "true",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user