diff --git a/admission/cocoonset_validator.go b/admission/cocoonset_validator.go index ec43418..bdc1cc0 100644 --- a/admission/cocoonset_validator.go +++ b/admission/cocoonset_validator.go @@ -171,12 +171,12 @@ func validateConnType(path string, ct cocoonv1.ConnType) string { return fmt.Sprintf("%s.connType must be ssh, rdp, vnc, or adb, got %q", path, ct) } -// cloneImageError rejects clone-mode images ParseRef cannot split: the snapshot pull joins repo[:tag] under a fixed registry base. +// cloneImageError rejects clone-mode images ParseRef cannot split: the snapshot pull joins repo[:tag] or repo@digest under a fixed registry base. func cloneImageError(path, mode, image string) string { if mode != string(cocoonv1.AgentModeClone) || image == "" || ociutil.IsRelativeRef(image) { return "" } - return fmt.Sprintf("%s.image %q must be repo[:tag] when mode is clone (no registry port or digest)", path, image) + return fmt.Sprintf("%s.image %q must be repo[:tag] or repo@sha256: when mode is clone (no registry port)", path, image) } // firecrackerModeError rejects firecracker with mode != run: an FC restore freezes the guest MAC+IP, so clones land on a dead lease. diff --git a/admission/cocoonset_validator_test.go b/admission/cocoonset_validator_test.go index 0bad571..09c6f34 100644 --- a/admission/cocoonset_validator_test.go +++ b/admission/cocoonset_validator_test.go @@ -47,12 +47,27 @@ func TestValidateCocoonSetSpec(t *testing.T) { wantContains: []string{"agent.mode"}, }, { - name: "rejects clone-mode digest image", + name: "rejects clone-mode malformed digest image", cs: &cocoonv1.CocoonSet{Spec: cocoonv1.CocoonSetSpec{ Agent: cocoonv1.AgentSpec{Image: "ubuntu@sha256:deadbeef"}, }}, wantContains: []string{"spec.agent.image", "must be repo[:tag]"}, }, + { + name: "accepts clone-mode digest image", + cs: &cocoonv1.CocoonSet{Spec: cocoonv1.CocoonSetSpec{ + Agent: cocoonv1.AgentSpec{Image: "simular/ubuntu@sha256:" + strings.Repeat("ab", 32)}, + }}, + }, + { + name: "accepts clone-mode toolbox digest image", + cs: &cocoonv1.CocoonSet{Spec: cocoonv1.CocoonSetSpec{ + Agent: cocoonv1.AgentSpec{Image: "x"}, + Toolboxes: []cocoonv1.ToolboxSpec{ + {Name: "tb", Mode: cocoonv1.ToolboxModeClone, Image: "tools@sha256:" + strings.Repeat("cd", 32)}, + }, + }}, + }, { name: "rejects clone-mode registry-port image", cs: &cocoonv1.CocoonSet{Spec: cocoonv1.CocoonSetSpec{ diff --git a/docs/validation.md b/docs/validation.md index 44d8161..7116bbe 100644 --- a/docs/validation.md +++ b/docs/validation.md @@ -21,7 +21,7 @@ validation cannot express: - `spec.toolboxes[*]` non-static modes require `image`, and get the same `os`/`connType`/`backend` enum checks and firecracker-Windows/cloudimg-URL rules as `spec.agent` - `spec.toolboxes[*].backend` must match `spec.agent.backend` (static toolboxes skip this check) - `spec.toolboxes[*]` static-mode entries: `connType` may be left unset (falls back to `vnc` when `vncPort` is set, otherwise by OS: Windows→rdp, Android→adb, anything else→ssh); a non-empty value must be one of `ssh` / `rdp` / `vnc` / `adb` -- clone-mode images (`spec.agent.image`, `spec.toolboxes[*].image`) must be a relative `repo[:tag]` with a lowercase repo path — a registry port, a digest, and uppercase repo characters are rejected (a bare registry host such as `ghcr.io/...` is accepted; the tag keeps the OCI tag character set), because the snapshot pull path resolves images under the org registry base and has no external-ref fallback +- clone-mode images (`spec.agent.image`, `spec.toolboxes[*].image`) must be a relative `repo[:tag]` or `repo@sha256:<64 hex>` (or `sha512`) with a lowercase repo path — a registry port, a malformed digest, a tag and a digest together, and uppercase repo characters are rejected (a bare registry host such as `ghcr.io/...` is accepted; the tag keeps the OCI tag character set), because the snapshot pull path resolves images under the org registry base and has no external-ref fallback - `spec.snapshotPolicy ∈ {always, main-only, never}` - `spec.hibernatePolicy ∈ {retain, release}` diff --git a/go.mod b/go.mod index a60d598..4d7c687 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/cocoonstack/cocoon-webhook go 1.27.1 require ( - github.com/cocoonstack/cocoon-common v0.3.6 + github.com/cocoonstack/cocoon-common v0.3.7-0.20260928172801-b03e52d1f721 github.com/projecteru2/core v0.1.5 github.com/prometheus/client_golang v1.24.1 github.com/prometheus/client_model v0.6.3 diff --git a/go.sum b/go.sum index 449ead6..15206f1 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 h1:ASDL+UJcILM github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506/go.mod h1:Mw7HqKr2kdtu6aYGn3tPmAftiP3QPX63LdK/zcariIo= github.com/cockroachdb/redact v1.1.8 h1:8eVLLj6juKxiKrAEw2b8cJvNqWq++U8WOfQFuL7KTaA= github.com/cockroachdb/redact v1.1.8/go.mod h1:GceHHpJ0rMDpYARL5In88Alq/xMBUtVlz7Qxix6ZVkw= -github.com/cocoonstack/cocoon-common v0.3.6 h1:iRrIyeRXHgYuwKYDOB6TjidiZL1oeyfOYkTGXDSD/PU= -github.com/cocoonstack/cocoon-common v0.3.6/go.mod h1:Gw/zBYqntoxpcT//2rBcXHQz6JErin/IsTsEmoQdGr0= +github.com/cocoonstack/cocoon-common v0.3.7-0.20260928172801-b03e52d1f721 h1:xUtBRurq3bzpL2D0P0Apeiq4jjplNLOD/Kbd6G14WwM= +github.com/cocoonstack/cocoon-common v0.3.7-0.20260928172801-b03e52d1f721/go.mod h1:Gw/zBYqntoxpcT//2rBcXHQz6JErin/IsTsEmoQdGr0= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=