diff --git a/go/core/pkg/app/app.go b/go/core/pkg/app/app.go index c8c6efd35..2ab1dd1f6 100644 --- a/go/core/pkg/app/app.go +++ b/go/core/pkg/app/app.go @@ -203,6 +203,8 @@ func (cfg *Config) SetFlags(commandLine *flag.FlagSet) { commandLine.StringVar(&agent_translator.PythonADKFullImageDigest, "app-full-image-digest", agent_translator.PythonADKFullImageDigest, "Manifest digest (sha256:...) for the full Python agent runtime image used by sandbox agents. Defaults to the digest baked in at build time; override when a mirrored registry re-assigns digests.") commandLine.StringVar(&agent_translator.GoADKImageDigest, "golang-adk-image-digest", agent_translator.GoADKImageDigest, "Manifest digest (sha256:...) for the Go agent runtime image used by sandbox agents. Defaults to the digest baked in at build time; override when a mirrored registry re-assigns digests.") commandLine.StringVar(&agent_translator.GoADKFullImageDigest, "golang-adk-full-image-digest", agent_translator.GoADKFullImageDigest, "Manifest digest (sha256:...) for the full Go agent runtime image used by sandbox agents. Defaults to the digest baked in at build time; override when a mirrored registry re-assigns digests.") + commandLine.StringVar(&substrate.AcpSandboxOpenClawImageDigest, "acp-sandbox-openclaw-image-digest", substrate.AcpSandboxOpenClawImageDigest, "Manifest digest (sha256:...) for the acp-sandbox OpenClaw Substrate workload image. Defaults to the digest baked in at build time; override when a mirrored registry re-assigns digests.") + commandLine.StringVar(&substrate.AcpSandboxHermesImageDigest, "acp-sandbox-hermes-image-digest", substrate.AcpSandboxHermesImageDigest, "Manifest digest (sha256:...) for the acp-sandbox Hermes Substrate workload image. Defaults to the digest baked in at build time; override when a mirrored registry re-assigns digests.") commandLine.StringVar(&agent_translator.DefaultSkillsInitImageConfig.Registry, "skills-init-image-registry", agent_translator.DefaultSkillsInitImageConfig.Registry, "The registry to use for the skills init image.") commandLine.StringVar(&agent_translator.DefaultSkillsInitImageConfig.Tag, "skills-init-image-tag", agent_translator.DefaultSkillsInitImageConfig.Tag, "The tag to use for the skills init image.") commandLine.StringVar(&agent_translator.DefaultSkillsInitImageConfig.PullPolicy, "skills-init-image-pull-policy", agent_translator.DefaultSkillsInitImageConfig.PullPolicy, "The pull policy to use for the skills init image.") diff --git a/go/core/pkg/sandboxbackend/substrate/constants.go b/go/core/pkg/sandboxbackend/substrate/constants.go index ee2e29444..2973d36ce 100644 --- a/go/core/pkg/sandboxbackend/substrate/constants.go +++ b/go/core/pkg/sandboxbackend/substrate/constants.go @@ -21,9 +21,12 @@ const ( // AcpSandboxOpenClawImageDigest and AcpSandboxHermesImageDigest are the // link-time-injected image digests (sha256:...) for the acp-sandbox workload -// images, set via -X ...substrate.AcpSandbox*ImageDigest=... They are empty in -// source and in unit tests, in which case resolution returns an error rather -// than an unpinned ref. +// images, set via -X ...substrate.AcpSandbox*ImageDigest=... They can be +// overridden at runtime via --acp-sandbox-openclaw-image-digest / +// --acp-sandbox-hermes-image-digest (env ACP_SANDBOX_OPENCLAW_IMAGE_DIGEST / +// ACP_SANDBOX_HERMES_IMAGE_DIGEST) for private/mirrored registries that +// re-assign manifest digests. They are empty in source and in unit tests, in +// which case resolution returns an error rather than an unpinned ref. var ( AcpSandboxOpenClawImageDigest string AcpSandboxHermesImageDigest string @@ -56,16 +59,17 @@ func acpSandboxHermesImage(cfg acpSandboxImageConfig) (string, error) { // resolve composes the digest-pinned ref registry/repo/name@sha256:... for an // acp-sandbox target. Substrate admission requires a digest, so a missing -// link-time digest is a hard error: the controller must be rebuilt after -// pushing the acp-sandbox images, or the harness/cluster must specify an -// explicit digest-pinned workload image. A missing registry or repository is -// likewise an error, since both are required to form a resolvable ref. +// digest is a hard error: the controller must be rebuilt after pushing the +// acp-sandbox images, the digest must be overridden via ---image-digest, +// or the harness/cluster must specify an explicit digest-pinned workload +// image. A missing registry or repository is likewise an error, since both +// are required to form a resolvable ref. func (cfg acpSandboxImageConfig) resolve(name, digest string) (string, error) { digest = strings.TrimSpace(digest) if digest == "" { return "", fmt.Errorf( - "acp-sandbox %s image digest is not set at link time; rebuild the controller after pushing the acp-sandbox images (or set a digest-pinned Substrate.WorkloadImage)", - name, + "acp-sandbox %s image digest is not set; rebuild the controller after pushing the acp-sandbox images, or override it via --%s-image-digest (or set a digest-pinned Substrate.WorkloadImage)", + name, name, ) } if !strings.HasPrefix(digest, "sha256:") { diff --git a/go/core/pkg/sandboxbackend/substrate/constants_test.go b/go/core/pkg/sandboxbackend/substrate/constants_test.go index 52f69e536..42138ffbb 100644 --- a/go/core/pkg/sandboxbackend/substrate/constants_test.go +++ b/go/core/pkg/sandboxbackend/substrate/constants_test.go @@ -71,9 +71,13 @@ func TestAcpSandboxImageResolve(t *testing.T) { t.Run("errors when digest missing", func(t *testing.T) { cfg := acpSandboxImageConfig{Registry: "ghcr.io", Repository: "kagent-dev/kagent/app"} - if _, err := cfg.resolve("acp-sandbox-openclaw", " "); err == nil { + _, err := cfg.resolve("acp-sandbox-openclaw", " ") + if err == nil { t.Fatal("expected error for missing digest") } + if !strings.Contains(err.Error(), "--acp-sandbox-openclaw-image-digest") { + t.Fatalf("error should point at the override flag, got: %v", err) + } }) t.Run("errors when registry missing", func(t *testing.T) { diff --git a/helm/kagent/templates/controller-configmap.yaml b/helm/kagent/templates/controller-configmap.yaml index 110902341..515e8b63b 100644 --- a/helm/kagent/templates/controller-configmap.yaml +++ b/helm/kagent/templates/controller-configmap.yaml @@ -19,6 +19,12 @@ data: IMAGE_REGISTRY: {{ .Values.controller.agentImage.registry | default .Values.registry | quote }} IMAGE_REPOSITORY: {{ .Values.controller.agentImage.repository | quote }} IMAGE_TAG: {{ coalesce .Values.controller.agentImage.tag .Values.tag .Chart.Version | quote }} + {{- with .Values.controller.agentImage.digest }} + APP_IMAGE_DIGEST: {{ . | quote }} + {{- end }} + {{- with .Values.controller.agentImage.fullDigest }} + APP_FULL_IMAGE_DIGEST: {{ . | quote }} + {{- end }} SKILLS_INIT_IMAGE_PULL_POLICY: {{ .Values.controller.skillsInitImage.pullPolicy | default .Values.imagePullPolicy | quote }} SKILLS_INIT_IMAGE_REGISTRY: {{ .Values.controller.skillsInitImage.registry | default .Values.registry | quote }} SKILLS_INIT_IMAGE_REPOSITORY: {{ .Values.controller.skillsInitImage.repository | quote }} @@ -27,6 +33,20 @@ data: GO_IMAGE_REGISTRY: {{ .Values.controller.goAgentImage.registry | default .Values.registry | quote }} GO_IMAGE_REPOSITORY: {{ .Values.controller.goAgentImage.repository | quote }} GO_IMAGE_TAG: {{ coalesce .Values.controller.goAgentImage.tag .Values.tag .Chart.Version | quote }} + {{- with .Values.controller.goAgentImage.digest }} + GOLANG_ADK_IMAGE_DIGEST: {{ . | quote }} + {{- end }} + {{- with .Values.controller.goAgentImage.fullDigest }} + GOLANG_ADK_FULL_IMAGE_DIGEST: {{ . | quote }} + {{- end }} + {{- with .Values.controller.acpSandboxImages }} + {{- with .openclawDigest }} + ACP_SANDBOX_OPENCLAW_IMAGE_DIGEST: {{ . | quote }} + {{- end }} + {{- with .hermesDigest }} + ACP_SANDBOX_HERMES_IMAGE_DIGEST: {{ . | quote }} + {{- end }} + {{- end }} LEADER_ELECT: {{ include "kagent.leaderElectionEnabled" . | quote }} # OpenTelemetry Configuration OTEL_TRACING_ENABLED: {{ .Values.otel.tracing.enabled | quote }} diff --git a/helm/kagent/tests/image-digest-overrides_test.yaml b/helm/kagent/tests/image-digest-overrides_test.yaml new file mode 100644 index 000000000..b6dc46aaf --- /dev/null +++ b/helm/kagent/tests/image-digest-overrides_test.yaml @@ -0,0 +1,72 @@ +suite: test runtime image digest overrides in the controller ConfigMap +templates: + - controller-configmap.yaml +tests: + - it: should not emit digest overrides by default + asserts: + - notExists: + path: data.APP_IMAGE_DIGEST + - notExists: + path: data.APP_FULL_IMAGE_DIGEST + - notExists: + path: data.GOLANG_ADK_IMAGE_DIGEST + - notExists: + path: data.GOLANG_ADK_FULL_IMAGE_DIGEST + - notExists: + path: data.ACP_SANDBOX_OPENCLAW_IMAGE_DIGEST + - notExists: + path: data.ACP_SANDBOX_HERMES_IMAGE_DIGEST + + - it: should emit Python runtime digest overrides when set + set: + controller: + agentImage: + digest: sha256:1111111111111111111111111111111111111111111111111111111111111111 + fullDigest: sha256:2222222222222222222222222222222222222222222222222222222222222222 + asserts: + - equal: + path: data.APP_IMAGE_DIGEST + value: sha256:1111111111111111111111111111111111111111111111111111111111111111 + - equal: + path: data.APP_FULL_IMAGE_DIGEST + value: sha256:2222222222222222222222222222222222222222222222222222222222222222 + + - it: should emit Go runtime digest overrides when set + set: + controller: + goAgentImage: + digest: sha256:3333333333333333333333333333333333333333333333333333333333333333 + fullDigest: sha256:4444444444444444444444444444444444444444444444444444444444444444 + asserts: + - equal: + path: data.GOLANG_ADK_IMAGE_DIGEST + value: sha256:3333333333333333333333333333333333333333333333333333333333333333 + - equal: + path: data.GOLANG_ADK_FULL_IMAGE_DIGEST + value: sha256:4444444444444444444444444444444444444444444444444444444444444444 + + - it: should emit acp-sandbox digest overrides when set + set: + controller: + acpSandboxImages: + openclawDigest: sha256:5555555555555555555555555555555555555555555555555555555555555555 + hermesDigest: sha256:6666666666666666666666666666666666666666666666666666666666666666 + asserts: + - equal: + path: data.ACP_SANDBOX_OPENCLAW_IMAGE_DIGEST + value: sha256:5555555555555555555555555555555555555555555555555555555555555555 + - equal: + path: data.ACP_SANDBOX_HERMES_IMAGE_DIGEST + value: sha256:6666666666666666666666666666666666666666666666666666666666666666 + + - it: should allow setting only one acp-sandbox digest + set: + controller: + acpSandboxImages: + openclawDigest: sha256:7777777777777777777777777777777777777777777777777777777777777777 + asserts: + - equal: + path: data.ACP_SANDBOX_OPENCLAW_IMAGE_DIGEST + value: sha256:7777777777777777777777777777777777777777777777777777777777777777 + - notExists: + path: data.ACP_SANDBOX_HERMES_IMAGE_DIGEST diff --git a/helm/kagent/values.yaml b/helm/kagent/values.yaml index 76f7b56bd..f92d77c4c 100644 --- a/helm/kagent/values.yaml +++ b/helm/kagent/values.yaml @@ -210,6 +210,15 @@ controller: pullPolicy: "" # -- Image pull secret name set on agent pods created by the controller pullSecret: "" + # -- Manifest digest (sha256:...) override for the Python agent runtime + # image used by sandbox agents (regular agents reference the image by tag). + # Defaults to the digest baked into the controller binary at build time; + # set this when serving the images from a private/mirrored registry that + # re-assigns manifest digests. + digest: "" + # -- Manifest digest (sha256:...) override for the full-variant Python + # agent runtime image used by sandbox agents. See `digest` above. + fullDigest: "" # -- The image used by the skills-init container to clone skills from Git and pull OCI skill images. skillsInitImage: registry: "" @@ -222,6 +231,26 @@ controller: repository: kagent-dev/kagent/golang-adk tag: "" # Will default to global, then Chart version pullPolicy: "" + # -- Manifest digest (sha256:...) override for the Go agent runtime image + # used by sandbox agents (regular agents reference the image by tag). + # Defaults to the digest baked into the controller binary at build time; + # set this when serving the images from a private/mirrored registry that + # re-assigns manifest digests. + digest: "" + # -- Manifest digest (sha256:...) override for the full-variant Go agent + # runtime image used by sandbox agents. See `digest` above. + fullDigest: "" + # -- Digest overrides for the Substrate acp-sandbox workload images. Their + # registry/repository derive from agentImage (registry + repository parent + # path), and Substrate admission requires digest-pinned refs, so the digests + # default to the ones baked into the controller binary at build time. Set + # these when serving the images from a private/mirrored registry that + # re-assigns manifest digests. + acpSandboxImages: + # -- Manifest digest (sha256:...) override for the acp-sandbox-openclaw image. + openclawDigest: "" + # -- Manifest digest (sha256:...) override for the acp-sandbox-hermes image. + hermesDigest: "" # -- @deprecated Removed in 0.10.0. The A2A SDK now handles SSE buffering and timeouts # internally. These values have no effect and will be removed in a future release. streaming: null