apply: container and network resources, over the container runtime
Extends the applier past the filesystem to the two types the workloads need: a container and the private network it joins. The workloads are the bulk of what a cutover re-declares (research 009), so this is what makes a workload manifest actually appliable. - container: run/reconcile/remove over the runtime. Up to date means a container that is ours (a spec-hash label matches this exact declaration) AND running; anything else — a changed spec, a stopped container, or a foreign one the old control plane left by that name — is recreated into ours. Safe because a container carries no state: its data is in bind-mounted directories declared separately, and recreating it never touches them. Read-back asks the runtime whether it is actually running on the declared spec, because 'started' only means the runtime returned. - network: create if absent, adopt if present, remove only what it created. - The runtime is driven through a Runner, faked in unit tests and exercised for real in a smoke test that stands a container up, proves idempotency, and tears it down — skipped, never failed, where the runtime is absent. The store's per-resource reference generalises from a path to a ref: a path for files and directories, a name for containers and networks. Verified end to end through the binary: a container on a bind mount, then dropped from the declaration — the container is removed and the data directory survives, which is the migration property itself. Still deferred: sealed secrets, and package/service/archive/user/action — refused whole until built, never half-applied. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
@@ -26,11 +26,21 @@ type Applier interface {
|
||||
Remove(rec Record) error
|
||||
}
|
||||
|
||||
// Appliers is the set of types this host can apply, keyed by type name.
|
||||
// Appliers is the set of types this host can apply, keyed by type name, using the real container
|
||||
// runtime.
|
||||
func Appliers() map[string]Applier {
|
||||
return appliersWith(execRunner)
|
||||
}
|
||||
|
||||
// appliersWith builds the applier set against a given runtime runner. The filesystem appliers
|
||||
// ignore it; the container and network ones drive the runtime through it, which is where a test
|
||||
// substitutes a fake.
|
||||
func appliersWith(run Runner) map[string]Applier {
|
||||
return map[string]Applier{
|
||||
"directory": directoryApplier{},
|
||||
"file": fileApplier{},
|
||||
"network": networkApplier{run: run},
|
||||
"container": containerApplier{run: run},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,12 +92,12 @@ func (directoryApplier) Remove(rec Record) error {
|
||||
// os.Remove, never RemoveAll: it fails on a non-empty directory, and that failure is the
|
||||
// point. A directory the host created but that now holds something is not the host's to
|
||||
// delete — data outlives the mesh that declared it (ADR 0030).
|
||||
err := os.Remove(rec.Path)
|
||||
err := os.Remove(rec.Ref)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("removing directory %s (left in place): %w", rec.Path, err)
|
||||
return fmt.Errorf("removing directory %s (left in place): %w", rec.Ref, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -148,12 +158,12 @@ func (fileApplier) Apply(r Resource) (bool, error) {
|
||||
}
|
||||
|
||||
func (fileApplier) Remove(rec Record) error {
|
||||
err := os.Remove(rec.Path)
|
||||
err := os.Remove(rec.Ref)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("removing file %s: %w", rec.Path, err)
|
||||
return fmt.Errorf("removing file %s: %w", rec.Ref, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user