Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ TOOLS_GO_VERSION ?= $(shell $(AWK) '/^go / { print $$2 }' go/go.mod)
export GOTOOLCHAIN=go$(TOOLS_GO_VERSION)

# Version information for the build
LDFLAGS := "-X github.com/$(DOCKER_REPO)/go/core/internal/version.Version=$(VERSION) \
-X github.com/$(DOCKER_REPO)/go/core/internal/version.GitCommit=$(GIT_COMMIT) \
-X github.com/$(DOCKER_REPO)/go/core/internal/version.BuildDate=$(BUILD_DATE)"
LDFLAGS := -X github.com/$(DOCKER_REPO)/go/core/internal/version.Version=$(VERSION) \
-X github.com/$(DOCKER_REPO)/go/core/internal/version.GitCommit=$(GIT_COMMIT) \
-X github.com/$(DOCKER_REPO)/go/core/internal/version.BuildDate=$(BUILD_DATE)

#tools versions
TOOLS_UV_VERSION ?= 0.10.4
Expand All @@ -89,7 +89,7 @@ TOOLS_PYTHON_VERSION ?= 3.13

# build args
TOOLS_IMAGE_BUILD_ARGS = --build-arg VERSION=$(VERSION)
TOOLS_IMAGE_BUILD_ARGS += --build-arg LDFLAGS=$(LDFLAGS)
TOOLS_IMAGE_BUILD_ARGS += --build-arg LDFLAGS="$(LDFLAGS)"
TOOLS_IMAGE_BUILD_ARGS += --build-arg DOCKER_REPO=$(DOCKER_REPO)
TOOLS_IMAGE_BUILD_ARGS += --build-arg DOCKER_REGISTRY=$(DOCKER_REGISTRY)
TOOLS_IMAGE_BUILD_ARGS += --build-arg BASE_IMAGE_REGISTRY=$(BASE_IMAGE_REGISTRY)
Expand Down Expand Up @@ -197,7 +197,7 @@ build-all: buildx-create

.PHONY: build
build: ## Build and push all component images
build: buildx-create build-controller build-ui build-app build-golang-adk build-golang-adk-full build-skills-init
build: buildx-create build-ui build-skills-init build-golang-adk build-golang-adk-full build-app build-controller
@echo "Build completed successfully."
@echo "Controller Image: $(CONTROLLER_IMG)"
@echo "UI Image: $(UI_IMG)"
Expand Down Expand Up @@ -241,9 +241,18 @@ controller-manifests: ## Regenerate CRD manifests and copy them into the Helm ch
cp go/api/config/crd/bases/* helm/kagent-crds/templates/

.PHONY: build-controller
build-controller: ## Build and push the controller image
build-controller: buildx-create controller-manifests
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) --build-arg BUILD_PACKAGE=core/cmd/controller/main.go -t $(CONTROLLER_IMG) -f go/Dockerfile ./go
build-controller: ## Build and push the controller image (embeds agent runtime digests via scripts/controller-digest-ldflags.sh)
build-controller: buildx-create controller-manifests build-app build-golang-adk build-golang-adk-full
@set -e; \
DIGEST_LDFLAGS=$$(CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) \
APP_IMG=$(APP_IMG) \
GOLANG_ADK_IMG=$(GOLANG_ADK_IMG) \
GOLANG_ADK_FULL_IMG=$(GOLANG_ADK_FULL_IMG) \
./scripts/controller-digest-ldflags.sh); \
$(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) \
--build-arg LDFLAGS="$(LDFLAGS)$$DIGEST_LDFLAGS" \
--build-arg BUILD_PACKAGE=core/cmd/controller/main.go \
-t $(CONTROLLER_IMG) -f go/Dockerfile ./go
$(DOCKER_PUSH) $(CONTROLLER_IMG)

.PHONY: build-ui
Expand Down Expand Up @@ -556,4 +565,3 @@ prune-images: ## Remove old kagent images and dangling images from the local dae
grep -v ":$(VERSION) " | grep kagent | grep -v '<none>' | awk '{print $$2}' | xargs -r $(CONTAINER_RUNTIME) rmi || :
$(CONTAINER_RUNTIME) images --filter dangling=true -q | xargs -r $(CONTAINER_RUNTIME) rmi || :


14 changes: 14 additions & 0 deletions go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,20 @@ docker build --build-arg BUILD_PACKAGE=adk/cmd/main.go -t golang-adk .

In practice, use the root Makefile targets (`make build-controller`, `make build-golang-adk`).

### Agent runtime image digests

The controller embeds OCI manifest digests for agent workload images at **link time** so declarative agents are deployed with `@sha256:...` refs instead of tags. Substrate ActorTemplates require digest-pinned images.

| Image | Makefile target | Injected into |
|---|---|---|
| `app` (Python runtime) | `build-app` | `PythonADKImageDigest` |
| `golang-adk` | `build-golang-adk` | `GoADKImageDigest` |
| `golang-adk-full` | `build-golang-adk-full` | `GoADKFullImageDigest` |

`make build-controller` builds those three images first, runs [`scripts/controller-digest-ldflags.sh`](../scripts/controller-digest-ldflags.sh) to inspect their digests from the registry, and passes the result via `LDFLAGS` (same mechanism as version/git metadata).

`kagent-adk` is not included — it is only a build-time base for `app`, not a deployed agent runtime.

## Quick Testing with Oneshot

The `adk/examples/oneshot` tool lets you test agent configs locally:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@ func (c ImageConfig) Image() string {
return fmt.Sprintf("%s/%s:%s", c.Registry, c.Repository, c.Tag)
}

func normalizeImageDigest(digest string) string {
digest = strings.TrimSpace(digest)
if digest == "" {
return ""
}
if strings.HasPrefix(digest, "sha256:") {
return digest
}
return "sha256:" + strings.TrimPrefix(digest, "sha256:")
}

var DefaultImageConfig = ImageConfig{
Registry: "cr.kagent.dev",
Tag: version.Get().Version,
Expand All @@ -96,6 +107,12 @@ var DefaultImageConfig = ImageConfig{
Repository: "kagent-dev/kagent/app",
}

// PythonADKImageDigest, GoADKImageDigest, and GoADKFullImageDigest are set at
// controller link time from the pushed runtime image manifest digests.
var PythonADKImageDigest string
var GoADKImageDigest string
var GoADKFullImageDigest string

// DefaultSkillsInitImageConfig is the image config for the skills-init container
// that clones skill repositories from Git and pulls OCI skill images.
var DefaultSkillsInitImageConfig = ImageConfig{
Expand Down
49 changes: 42 additions & 7 deletions go/core/internal/controller/translator/agent/deployments.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,33 @@ func validateExtraContainers(containers []corev1.Container) error {
return nil
}

func resolvePythonRuntimeImage(registry string) (string, error) {
repo := DefaultImageConfig.Repository
if d := normalizeImageDigest(PythonADKImageDigest); d != "" {
return fmt.Sprintf("%s/%s@%s", registry, repo, d), nil
}
return "", fmt.Errorf(
"app image digest is not set at link time; rebuild the controller after pushing agent runtime images",
)
}

func resolveGoRuntimeImage(registry string, full bool) (string, error) {
repo := getRuntimeImageRepository(v1alpha2.DeclarativeRuntime_Go)
digest := GoADKImageDigest
imageLabel := "golang-adk"
if full {
digest = GoADKFullImageDigest
imageLabel = "golang-adk-full"
}
if d := normalizeImageDigest(digest); d != "" {
return fmt.Sprintf("%s/%s@%s", registry, repo, d), nil
}
return "", fmt.Errorf(
"%s image digest is not set at link time; rebuild the controller after pushing agent runtime images",
imageLabel,
)
}

func resolveInlineDeployment(agent v1alpha2.AgentObject, mdd *modelDeploymentData) (*resolvedDeployment, error) {
specRef := agent.GetAgentSpec()
// Defaults
Expand Down Expand Up @@ -157,15 +184,23 @@ func resolveInlineDeployment(agent v1alpha2.AgentObject, mdd *modelDeploymentDat
registry = spec.ImageRegistry
}

repository := getRuntimeImageRepository(runtime)

tag := DefaultImageConfig.Tag
if runtime == v1alpha2.DeclarativeRuntime_Go && needsSRTSettings(agent, specRef.Sandbox) {
tag += "-full"
var image string
full := runtime == v1alpha2.DeclarativeRuntime_Go && needsSRTSettings(agent, specRef.Sandbox)
switch runtime {
case v1alpha2.DeclarativeRuntime_Go:
var err error
image, err = resolveGoRuntimeImage(registry, full)
if err != nil {
return nil, err
}
default:
var err error
image, err = resolvePythonRuntimeImage(registry)
if err != nil {
return nil, err
}
}

image := fmt.Sprintf("%s/%s:%s", registry, repository, tag)

imagePullPolicy := corev1.PullPolicy(DefaultImageConfig.PullPolicy)
if spec.ImagePullPolicy != "" {
imagePullPolicy = corev1.PullPolicy(spec.ImagePullPolicy)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package agent_test

import (
"os"
"testing"

translator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent"
)

func TestMain(m *testing.M) {
translator.PythonADKImageDigest = "sha256:test-app"
translator.GoADKImageDigest = "sha256:test-go-base"
translator.GoADKFullImageDigest = "sha256:test-go-full"
os.Exit(m.Run())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package agent

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestImageConfigImage(t *testing.T) {
cfg := ImageConfig{
Registry: "cr.kagent.dev",
Repository: "kagent-dev/kagent/app",
Tag: "v1.0.0",
}
require.Equal(t, "cr.kagent.dev/kagent-dev/kagent/app:v1.0.0", cfg.Image())
}

func TestResolveGoRuntimeImageWithDigest(t *testing.T) {
originalBase := GoADKImageDigest
originalFull := GoADKFullImageDigest
t.Cleanup(func() {
GoADKImageDigest = originalBase
GoADKFullImageDigest = originalFull
})
GoADKImageDigest = "sha256:go-base"
GoADKFullImageDigest = "sha256:go-full"

got, err := resolveGoRuntimeImage("localhost:5001", false)
require.NoError(t, err)
require.Equal(t, "localhost:5001/kagent-dev/kagent/golang-adk@sha256:go-base", got)

got, err = resolveGoRuntimeImage("localhost:5001", true)
require.NoError(t, err)
require.Equal(t, "localhost:5001/kagent-dev/kagent/golang-adk@sha256:go-full", got)
}

func TestResolveGoRuntimeImageWithoutDigest(t *testing.T) {
originalBase := GoADKImageDigest
originalFull := GoADKFullImageDigest
t.Cleanup(func() {
GoADKImageDigest = originalBase
GoADKFullImageDigest = originalFull
})
GoADKImageDigest = ""
GoADKFullImageDigest = ""

_, err := resolveGoRuntimeImage("localhost:5001", false)
require.Error(t, err)
require.Contains(t, err.Error(), "golang-adk")

_, err = resolveGoRuntimeImage("localhost:5001", true)
require.Error(t, err)
require.Contains(t, err.Error(), "golang-adk-full")
}

func TestResolvePythonRuntimeImageWithDigest(t *testing.T) {
original := PythonADKImageDigest
t.Cleanup(func() {
PythonADKImageDigest = original
})
PythonADKImageDigest = "sha256:app-digest"

got, err := resolvePythonRuntimeImage("cr.kagent.dev")
require.NoError(t, err)
require.Equal(t, "cr.kagent.dev/kagent-dev/kagent/app@sha256:app-digest", got)
}

func TestPythonADKImageDigestSupportsLinkerFlag(t *testing.T) {
// PythonADKImageDigest must be a package-level string var so
// scripts/controller-digest-ldflags.sh can inject it via -ldflags -X.
original := PythonADKImageDigest
t.Cleanup(func() {
PythonADKImageDigest = original
})
PythonADKImageDigest = "sha256:link-time-check"
require.Equal(t, "sha256:link-time-check", PythonADKImageDigest)
}

func TestResolvePythonRuntimeImageWithoutDigest(t *testing.T) {
original := PythonADKImageDigest
t.Cleanup(func() {
PythonADKImageDigest = original
})
PythonADKImageDigest = ""

_, err := resolvePythonRuntimeImage("cr.kagent.dev")
require.Error(t, err)
require.Contains(t, err.Error(), "app")
}
Loading
Loading