A container does not take restart-on, and nine of them did
This is what stopped the forge. The host refused the whole declaration: resource "postgres.server": a container does not use "restart-on", and it is set. Refused rather than ignored `restart-on` belongs to a service. I put it on containers this morning so one would pick up a rotated credential — nine times across seven modules — and nothing between the manifest and the machine said a word. The control plane composed it happily; the parser accepted it; the manifest tests passed. The only thing that knew was the host, five steps downstream, and hearing from it cost a seventeen-minute run. The host was right twice over. It refused, and it refused *everything*, because applying the parts it understood would leave a machine that looks configured and is not. One misplaced key therefore stops a module dead, which is the correct severity and an argument for catching it where it is written. So the shapes and their keys are now written down here and checked. They are duplicated from another repository deliberately — this is its wire format, like the shape of a grant file — and a contract with two copies and no check is a contract until somebody edits one. What this does not fix is why I reached for it: a container cannot follow a file. Filed separately.
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -569,3 +570,63 @@ func stringsOfTest(v any) []string {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// A resource uses only the keys its shape has.
|
||||
//
|
||||
// **The host is the only thing that knew, and it is five steps downstream.** A container carrying
|
||||
// `restart-on` — which belongs to a service — composed into a declaration without complaint, was
|
||||
// pushed, and was refused on the machine. The host refused *the whole declaration*, correctly,
|
||||
// because applying the parts it understood would leave a machine that looks configured and is
|
||||
// not. So one misplaced key stopped a module dead, and the only place that said so was a log on a
|
||||
// lab machine after a seventeen-minute run.
|
||||
//
|
||||
// Nine of them had shipped across seven modules.
|
||||
//
|
||||
// The lists are written out rather than imported: the host is another repository and this is its
|
||||
// wire format, like the shape of a grant file. Duplicated deliberately, and checked — a contract
|
||||
// with two copies and no check is a contract until somebody edits one.
|
||||
func TestAResourceUsesOnlyTheKeysItsShapeHas(t *testing.T) {
|
||||
common := []string{"id", "type"}
|
||||
shapes := map[string][]string{
|
||||
"file": {"path", "content", "bytes", "sealed", "secrets", "mode", "owner"},
|
||||
"directory": {"path", "mode", "owner"},
|
||||
"container": {"name", "image", "env", "env-file", "ports", "volumes", "args", "hosts", "network", "artifact"},
|
||||
"service": {"unit", "state", "boot", "restart-on"},
|
||||
"package": {"package", "state"},
|
||||
"network": {"name"},
|
||||
"archive": {"path", "artifact", "digest", "owner", "mode"},
|
||||
"user": {"name", "shell", "groups", "home"},
|
||||
"action": {"command", "verify", "in"},
|
||||
}
|
||||
|
||||
found, _ := filepath.Glob("*.json")
|
||||
var checked int
|
||||
for _, name := range found {
|
||||
for _, r := range read(t, name).Resources {
|
||||
kind := fmt.Sprint(r["type"])
|
||||
allowed, known := shapes[kind]
|
||||
if !known {
|
||||
t.Errorf("%s: %v is a %q, which is not a shape the mesh has", name, r["id"], kind)
|
||||
continue
|
||||
}
|
||||
for key := range r {
|
||||
checked++
|
||||
// `merge` and `protected` are read by the control plane and removed before a
|
||||
// machine sees them, so they are legal here and unknown to the host.
|
||||
if key == "merge" || key == "protected" {
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(common, key) && !slices.Contains(allowed, key) {
|
||||
t.Errorf(
|
||||
"%s: %v is a %s and carries %q, which that shape does not have. It would "+
|
||||
"compose cleanly and be refused on the machine — and the host refuses "+
|
||||
"the whole declaration, so this stops the module entirely",
|
||||
name, r["id"], kind, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if checked == 0 {
|
||||
t.Fatal("no example declares a resource, so this test proves nothing")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user