From ee3cc1b6f4216afc4d4ca37776b9aee4b5917748 Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 1 Sep 2026 15:13:33 +0200 Subject: [PATCH] Pin the example modules to images that exist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit novox/hq 04-ISSUES/025. Every image reference in every example module was sixty-four zeros — eighteen of them across five modules. Each parsed, resolved, and composed into a declaration a host accepts, and none could ever have started: the machine reaches `docker pull` and stops. That is why those modules were written and not running, and no check saw it because every check passed. The host validates the shape of a reference and nothing more, which is correct: verifying a digest exists means reaching a registry, and that is the one thing a host must never have to do. So the last place that could catch this is the wrong place to try. The guard therefore sits where a declaration is composed, not where a manifest is parsed. A file in a repository is allowed to await a pin — the design already says the manifest in a repository names artifacts while the manifest the mesh holds names digests, and the bundle works exactly that way. What must never happen is a placeholder reaching a machine, and composing is the last moment before one does. Twelve third-party images resolved to real digests without pulling anything, which is also the mechanism the open issue needs. Two discoveries came free: mailu publishes to ghcr rather than Docker Hub, so seven references named repositories that do not exist at all; and it renamed roundcube to webmail, so that one would have failed even with the right registry. What stays a placeholder is the mesh's own provisioner images, which genuinely have no digest until built and pushed — the bundle's problem, legitimately unresolved here. The stand-in consumer now stands in with a real image rather than an invented one. --- examples/modules/gitea.json | 2 +- examples/modules/keycloak.json | 2 +- examples/modules/mailu.json | 18 +++++++++--------- examples/modules/minio.json | 2 +- examples/modules/object-store.json | 2 +- examples/modules/photos.json | 2 +- examples/modules/postgres.json | 2 +- internal/catalogue/declaration.go | 29 +++++++++++++++++++++++++++++ 8 files changed, 44 insertions(+), 15 deletions(-) diff --git a/examples/modules/gitea.json b/examples/modules/gitea.json index 537ba43..dd033fa 100644 --- a/examples/modules/gitea.json +++ b/examples/modules/gitea.json @@ -26,7 +26,7 @@ "content": "GITEA__security__INTERNAL_TOKEN=${secret:internal-token}\nGITEA__database__DB_TYPE=postgres\nGITEA__database__HOST=${bound:postgres-database:at}:${bound:postgres-database:port}\nGITEA__database__NAME=gitea\nGITEA__database__USER=${bound:postgres-database:as}\nGITEA__database__PASSWD=${secret:postgres-database}\n"}, {"id": "server", "type": "container", "name": "gitea", - "image": "gitea@sha256:0000000000000000000000000000000000000000000000000000000000000000", + "image": "gitea/gitea@sha256:dfc61e347c8b582df918f4556401bf2cecdfbdb56c5282ae9488dd76fca3e41c", "env": {"DB_TYPE": "postgres", "USER_UID": "1000", "USER_GID": "1000"}, "env-file": ["/var/lib/gitea/server.env"], "ports": ["3000:3000", "2222:22"], diff --git a/examples/modules/keycloak.json b/examples/modules/keycloak.json index 43bc664..9aab8f9 100644 --- a/examples/modules/keycloak.json +++ b/examples/modules/keycloak.json @@ -30,7 +30,7 @@ {"id": "net", "type": "network", "name": "keycloak"}, {"id": "server", "type": "container", "name": "keycloak", - "image": "keycloak@sha256:0000000000000000000000000000000000000000000000000000000000000000", + "image": "quay.io/keycloak/keycloak@sha256:ecd43971114b0c764f8a3288dddab73f98cb473daccc4feaffe4dc14adeaf866", "network": "keycloak", "args": ["start-dev"], "env": {"KC_DB": "postgres", "KC_HTTP_ENABLED": "true", "KC_HEALTH_ENABLED": "true"}, diff --git a/examples/modules/mailu.json b/examples/modules/mailu.json index bfc3de7..a156f2d 100644 --- a/examples/modules/mailu.json +++ b/examples/modules/mailu.json @@ -31,24 +31,24 @@ {"id": "net", "type": "network", "name": "mailu"}, {"id": "resolver", "type": "container", "name": "mailu-resolver", - "image": "mailu-unbound@sha256:0000000000000000000000000000000000000000000000000000000000000000", + "image": "ghcr.io/mailu/unbound@sha256:142aaad82ad1b0d5b59a5f1303778dba61a3e0a540f5d969c48862bcc99f6f5d", "network": "mailu", "env-file": ["/var/lib/mailu/secret.env"]}, {"id": "redis", "type": "container", "name": "mailu-redis", - "image": "redis@sha256:0000000000000000000000000000000000000000000000000000000000000000", + "image": "redis@sha256:1db42ccef14898aa29bae778452d567534b59c107129cbc1163fb552de184d3c", "network": "mailu", "volumes": ["/services/mailu/data/redis:/data"]}, {"id": "admindb", "type": "container", "name": "mailu-admindb", - "image": "postgres@sha256:0000000000000000000000000000000000000000000000000000000000000000", + "image": "postgres@sha256:7456ef82e5f5bc43d997f4781bbd7c0d6389bff397564649a356e206ba473aee", "network": "mailu", "env": {"PGDATA": "/var/lib/postgresql/data/pgdata"}, "env-file": ["/var/lib/mailu/database.env"], "volumes": ["/services/mailu/data/data/psql_admindb/pgdata:/var/lib/postgresql/data/pgdata"]}, {"id": "admin", "type": "container", "name": "mailu-admin", - "image": "mailu-admin@sha256:0000000000000000000000000000000000000000000000000000000000000000", + "image": "ghcr.io/mailu/admin@sha256:dcac20e9cbdad560faef9653b1b5ac0d9266f4098dc00f0e7f0d35f4e70ed8f1", "network": "mailu", "env-file": ["/var/lib/mailu/secret.env", "/var/lib/mailu/database.env", "/var/lib/mailu/admin.env"], "volumes": [ @@ -57,7 +57,7 @@ ]}, {"id": "imap", "type": "container", "name": "mailu-imap", - "image": "mailu-dovecot@sha256:0000000000000000000000000000000000000000000000000000000000000000", + "image": "ghcr.io/mailu/dovecot@sha256:46d18ba51032be8ebd6841aa49c1ef8762c729038c5fd86a081b5b884d478af9", "network": "mailu", "env-file": ["/var/lib/mailu/secret.env"], "volumes": [ @@ -66,25 +66,25 @@ ]}, {"id": "smtp", "type": "container", "name": "mailu-smtp", - "image": "mailu-postfix@sha256:0000000000000000000000000000000000000000000000000000000000000000", + "image": "ghcr.io/mailu/postfix@sha256:bbf882880f68849511710b35237a933f3fe80c4b28bf48ff20205dbd1f1433d7", "network": "mailu", "env-file": ["/var/lib/mailu/secret.env"], "volumes": ["/services/mailu/data/mailqueue:/queue"]}, {"id": "antispam", "type": "container", "name": "mailu-antispam", - "image": "mailu-rspamd@sha256:0000000000000000000000000000000000000000000000000000000000000000", + "image": "ghcr.io/mailu/rspamd@sha256:e87ab93dd252cc69499caa5317dd10d445fd4291a7ecf6bca09793c7d475a0c8", "network": "mailu", "env-file": ["/var/lib/mailu/secret.env"], "volumes": ["/services/mailu/data/filter:/var/lib/rspamd"]}, {"id": "webmail", "type": "container", "name": "mailu-webmail", - "image": "mailu-roundcube@sha256:0000000000000000000000000000000000000000000000000000000000000000", + "image": "ghcr.io/mailu/webmail@sha256:076b720fc766e58a97321cdb700e887c2008d6d323685fe59f323088333059dc", "network": "mailu", "env-file": ["/var/lib/mailu/secret.env"], "volumes": ["/services/mailu/data/webmail:/data"]}, {"id": "front", "type": "container", "name": "mailu-front", - "image": "mailu-nginx@sha256:0000000000000000000000000000000000000000000000000000000000000000", + "image": "ghcr.io/mailu/nginx@sha256:09f28ab6d36367fcacc7994f7021f132ac845bdc05f04bf80906102d11aaa057", "network": "mailu", "env-file": ["/var/lib/mailu/secret.env"], "ports": ["25:25", "465:465", "587:587", "993:993", "7080:80"], diff --git a/examples/modules/minio.json b/examples/modules/minio.json index 4ac22d3..6b73c12 100644 --- a/examples/modules/minio.json +++ b/examples/modules/minio.json @@ -28,7 +28,7 @@ {"id": "net", "type": "network", "name": "minio"}, {"id": "server", "type": "container", "name": "minio", - "image": "minio@sha256:0000000000000000000000000000000000000000000000000000000000000000", + "image": "minio/minio@sha256:aefec8a86702aff0b0dcfdd9284bd7ab7c5631cbf9be63275799e6edcb30dfa2", "network": "minio", "args": ["server", "/data", "--console-address", ":9001"], "env-file": ["/var/lib/minio/root.env"], diff --git a/examples/modules/object-store.json b/examples/modules/object-store.json index 85b2600..c2ede0b 100644 --- a/examples/modules/object-store.json +++ b/examples/modules/object-store.json @@ -28,7 +28,7 @@ {"id": "grants", "type": "directory", "path": "/var/lib/objectstore/grants", "mode": "0700"}, {"id": "store", "type": "container", "name": "mesh-store", - "image": "minio/minio@sha256:0000000000000000000000000000000000000000000000000000000000000000", + "image": "minio/minio@sha256:aefec8a86702aff0b0dcfdd9284bd7ab7c5631cbf9be63275799e6edcb30dfa2", "args": ["server", "/data"], "env": {"MINIO_ROOT_USER": "meshroot"}, "ports": ["9000:9000"], diff --git a/examples/modules/photos.json b/examples/modules/photos.json index 22eee33..30a303b 100644 --- a/examples/modules/photos.json +++ b/examples/modules/photos.json @@ -15,7 +15,7 @@ {"id": "config", "type": "directory", "path": "/etc/photos", "mode": "0750"}, {"id": "app", "type": "container", "name": "photos", - "image": "photos@sha256:0000000000000000000000000000000000000000000000000000000000000000", + "image": "alpine@sha256:c64c687cbea9300178b30c95835354e34c4e4febc4badfe27102879de0483b5e", "env": { "PHOTOS_STORE": "/etc/photos/store.json", "PHOTOS_STORE_SECRET_FILE": "/etc/photos/store.secret" diff --git a/examples/modules/postgres.json b/examples/modules/postgres.json index af6ac1f..2422811 100644 --- a/examples/modules/postgres.json +++ b/examples/modules/postgres.json @@ -30,7 +30,7 @@ {"id": "net", "type": "network", "name": "postgres"}, {"id": "server", "type": "container", "name": "postgres", - "image": "postgres@sha256:0000000000000000000000000000000000000000000000000000000000000000", + "image": "postgres@sha256:7456ef82e5f5bc43d997f4781bbd7c0d6389bff397564649a356e206ba473aee", "network": "postgres", "env": {"POSTGRES_USER": "postgres", "POSTGRES_DB": "postgres"}, "env-file": ["/var/lib/postgres/superuser.env"], diff --git a/internal/catalogue/declaration.go b/internal/catalogue/declaration.go index 6ce1391..7395288 100644 --- a/internal/catalogue/declaration.go +++ b/internal/catalogue/declaration.go @@ -342,6 +342,9 @@ func (r Resolution) Declaration(with Rendering) ([]map[string]any, error) { if err := boundInto(copied, known, m.Module); err != nil { return nil, err } + if err := pinned(copied, m.Module); err != nil { + return nil, err + } copied["id"] = m.Module + "." + fmt.Sprint(resource["id"]) // A service saying what it reflects names resources within its own module, so those // are prefixed too or they would point at nothing. @@ -638,3 +641,29 @@ func withMeshNames(resources []map[string]any, names map[string]string) []map[st } return out } + +// pinned refuses an image that is not really pinned, on its way to a machine. +// +// **Here and not at parse** (novox/hq 04-ISSUES/025). A manifest in a repository names artifacts +// and the manifest the mesh holds names digests — they are deliberately not the same document, so +// a file awaiting a pin is legitimate exactly as the bundle's is. What must never happen is a +// placeholder reaching a machine, and this is the last moment before one does. +// +// The host checks only the *shape* of a reference, and cannot do more: verifying a digest exists +// means reaching a registry, which is the one thing a host must never have to do. So sixty-four +// zeros satisfies every gate in the system and stops on the machine at `docker pull` — which is +// how eighteen of them shipped across five modules that parse, resolve and compose cleanly. +func pinned(resource map[string]any, module string) error { + image, ok := resource["image"].(string) + if !ok { + return nil + } + _, digest, found := strings.Cut(image, "@") + if !found || strings.Trim(strings.TrimPrefix(digest, "sha256:"), "0") != "" { + return nil + } + return fmt.Errorf( + "%s would send %v to a machine pinned to a placeholder digest, which is never a real "+ + "image — it would be fetched and fail there. Resolve the tag to a digest first", + module, resource["id"]) +}