Skip to content
Closed
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
204 changes: 48 additions & 156 deletions .github/workflows/base-image.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,11 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Download platform digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
Expand Down Expand Up @@ -472,6 +477,7 @@ jobs:
- name: Create and verify multi-platform manifest
id: manifest
env:
AGENT: hermes
IMAGE: ${{ env.REGISTRY }}/nvidia/nemoclaw/hermes-sandbox-base
TAGS: ${{ steps.meta.outputs.tags }}
run: |
Expand Down Expand Up @@ -551,58 +557,16 @@ jobs:
reference="$IMAGE@$digest"
docker buildx imagetools inspect "$reference" >/dev/null

contract_dir="$RUNNER_TEMP/managed-base-contract"
mkdir -p "$contract_dir"
jq -n \
--arg amd64Digest "${platform_digests[linux/amd64]}" \
--arg amd64Reference "$IMAGE@${platform_digests[linux/amd64]}" \
--arg arm64Digest "${platform_digests[linux/arm64]}" \
--arg arm64Reference "$IMAGE@${platform_digests[linux/arm64]}" \
--arg digest "$digest" \
--arg image "$IMAGE" \
--arg reference "$reference" \
--arg revision "$GITHUB_SHA" \
--argjson runAttempt "$GITHUB_RUN_ATTEMPT" \
--argjson runId "$GITHUB_RUN_ID" \
'{
contractVersion: 1,
agent: "hermes",
image: $image,
digest: $digest,
reference: $reference,
platforms: ["linux/amd64", "linux/arm64"],
platformDigests: {
"linux/amd64": $amd64Digest,
"linux/arm64": $arm64Digest
},
platformReferences: {
"linux/amd64": $amd64Reference,
"linux/arm64": $arm64Reference
},
sourceRevision: $revision,
run: {
id: $runId,
attempt: $runAttempt
}
}' > "$contract_dir/contract.json"
jq -e \
'.contractVersion == 1
and .agent == "hermes"
and (.sourceRevision | test("^[0-9a-f]{40}$"))
and (.digest | test("^sha256:[0-9a-f]{64}$"))
and .reference == (.image + "@" + .digest)
and .platforms == ["linux/amd64", "linux/arm64"]
and (.platformDigests | keys | sort) == .platforms
and (.platformReferences | keys | sort) == .platforms
and ([
.platforms[] as $platform
| (
(.platformDigests[$platform] | test("^sha256:[0-9a-f]{64}$"))
and .platformReferences[$platform] ==
(.image + "@" + .platformDigests[$platform])
)
] | all)' \
"$contract_dir/contract.json" >/dev/null
scripts/export-managed-base-image-contract.sh \
"$AGENT" \
"$IMAGE" \
"$digest" \
"${platform_digests[linux/amd64]}" \
"${platform_digests[linux/arm64]}" \
"$GITHUB_SHA" \
"$GITHUB_RUN_ID" \
"$GITHUB_RUN_ATTEMPT" \
"$RUNNER_TEMP/managed-base-contract/contract.json"
printf 'digest=%s\n' "$digest" >> "$GITHUB_OUTPUT"

- name: Upload managed base image contract
Expand All @@ -620,6 +584,11 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Download platform digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
Expand Down Expand Up @@ -652,6 +621,7 @@ jobs:
- name: Create and verify multi-platform manifest
id: manifest
env:
AGENT: langchain-deepagents-code
IMAGE: ${{ env.REGISTRY }}/nvidia/nemoclaw/langchain-deepagents-code-sandbox-base
TAGS: ${{ steps.meta.outputs.tags }}
run: |
Expand Down Expand Up @@ -731,58 +701,16 @@ jobs:
reference="$IMAGE@$digest"
docker buildx imagetools inspect "$reference" >/dev/null

contract_dir="$RUNNER_TEMP/managed-base-contract"
mkdir -p "$contract_dir"
jq -n \
--arg amd64Digest "${platform_digests[linux/amd64]}" \
--arg amd64Reference "$IMAGE@${platform_digests[linux/amd64]}" \
--arg arm64Digest "${platform_digests[linux/arm64]}" \
--arg arm64Reference "$IMAGE@${platform_digests[linux/arm64]}" \
--arg digest "$digest" \
--arg image "$IMAGE" \
--arg reference "$reference" \
--arg revision "$GITHUB_SHA" \
--argjson runAttempt "$GITHUB_RUN_ATTEMPT" \
--argjson runId "$GITHUB_RUN_ID" \
'{
contractVersion: 1,
agent: "langchain-deepagents-code",
image: $image,
digest: $digest,
reference: $reference,
platforms: ["linux/amd64", "linux/arm64"],
platformDigests: {
"linux/amd64": $amd64Digest,
"linux/arm64": $arm64Digest
},
platformReferences: {
"linux/amd64": $amd64Reference,
"linux/arm64": $arm64Reference
},
sourceRevision: $revision,
run: {
id: $runId,
attempt: $runAttempt
}
}' > "$contract_dir/contract.json"
jq -e \
'.contractVersion == 1
and .agent == "langchain-deepagents-code"
and (.sourceRevision | test("^[0-9a-f]{40}$"))
and (.digest | test("^sha256:[0-9a-f]{64}$"))
and .reference == (.image + "@" + .digest)
and .platforms == ["linux/amd64", "linux/arm64"]
and (.platformDigests | keys | sort) == .platforms
and (.platformReferences | keys | sort) == .platforms
and ([
.platforms[] as $platform
| (
(.platformDigests[$platform] | test("^sha256:[0-9a-f]{64}$"))
and .platformReferences[$platform] ==
(.image + "@" + .platformDigests[$platform])
)
] | all)' \
"$contract_dir/contract.json" >/dev/null
scripts/export-managed-base-image-contract.sh \
"$AGENT" \
"$IMAGE" \
"$digest" \
"${platform_digests[linux/amd64]}" \
"${platform_digests[linux/arm64]}" \
"$GITHUB_SHA" \
"$GITHUB_RUN_ID" \
"$GITHUB_RUN_ATTEMPT" \
"$RUNNER_TEMP/managed-base-contract/contract.json"
printf 'digest=%s\n' "$digest" >> "$GITHUB_OUTPUT"

- name: Upload managed base image contract
Expand All @@ -802,6 +730,11 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Download platform digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
Expand Down Expand Up @@ -834,6 +767,7 @@ jobs:
- name: Create and verify multi-platform manifest
id: manifest
env:
AGENT: openclaw
IMAGE: ${{ env.REGISTRY }}/nvidia/nemoclaw/sandbox-base
TAGS: ${{ steps.meta.outputs.tags }}
run: |
Expand Down Expand Up @@ -913,58 +847,16 @@ jobs:
reference="$IMAGE@$digest"
docker buildx imagetools inspect "$reference" >/dev/null

contract_dir="$RUNNER_TEMP/managed-base-contract"
mkdir -p "$contract_dir"
jq -n \
--arg amd64Digest "${platform_digests[linux/amd64]}" \
--arg amd64Reference "$IMAGE@${platform_digests[linux/amd64]}" \
--arg arm64Digest "${platform_digests[linux/arm64]}" \
--arg arm64Reference "$IMAGE@${platform_digests[linux/arm64]}" \
--arg digest "$digest" \
--arg image "$IMAGE" \
--arg reference "$reference" \
--arg revision "$GITHUB_SHA" \
--argjson runAttempt "$GITHUB_RUN_ATTEMPT" \
--argjson runId "$GITHUB_RUN_ID" \
'{
contractVersion: 1,
agent: "openclaw",
image: $image,
digest: $digest,
reference: $reference,
platforms: ["linux/amd64", "linux/arm64"],
platformDigests: {
"linux/amd64": $amd64Digest,
"linux/arm64": $arm64Digest
},
platformReferences: {
"linux/amd64": $amd64Reference,
"linux/arm64": $arm64Reference
},
sourceRevision: $revision,
run: {
id: $runId,
attempt: $runAttempt
}
}' > "$contract_dir/contract.json"
jq -e \
'.contractVersion == 1
and .agent == "openclaw"
and (.sourceRevision | test("^[0-9a-f]{40}$"))
and (.digest | test("^sha256:[0-9a-f]{64}$"))
and .reference == (.image + "@" + .digest)
and .platforms == ["linux/amd64", "linux/arm64"]
and (.platformDigests | keys | sort) == .platforms
and (.platformReferences | keys | sort) == .platforms
and ([
.platforms[] as $platform
| (
(.platformDigests[$platform] | test("^sha256:[0-9a-f]{64}$"))
and .platformReferences[$platform] ==
(.image + "@" + .platformDigests[$platform])
)
] | all)' \
"$contract_dir/contract.json" >/dev/null
scripts/export-managed-base-image-contract.sh \
"$AGENT" \
"$IMAGE" \
"$digest" \
"${platform_digests[linux/amd64]}" \
"${platform_digests[linux/arm64]}" \
"$GITHUB_SHA" \
"$GITHUB_RUN_ID" \
"$GITHUB_RUN_ATTEMPT" \
"$RUNNER_TEMP/managed-base-contract/contract.json"
printf 'digest=%s\n' "$digest" >> "$GITHUB_OUTPUT"

- name: Upload managed base image contract
Expand Down
76 changes: 76 additions & 0 deletions scripts/export-managed-base-image-contract.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

set -euo pipefail

if [ "$#" -ne 9 ]; then
echo "Usage: $0 AGENT IMAGE DIGEST AMD64_DIGEST ARM64_DIGEST SOURCE_REVISION RUN_ID RUN_ATTEMPT OUTPUT" >&2
exit 2
fi

readonly agent="$1"
readonly image="$2"
readonly digest="$3"
readonly amd64_digest="$4"
readonly arm64_digest="$5"
readonly source_revision="$6"
readonly run_id="$7"
readonly run_attempt="$8"
readonly output="$9"
readonly reference="${image}@${digest}"

mkdir -p "$(dirname -- "$output")"
jq -n \
--arg agent "$agent" \
--arg amd64Digest "$amd64_digest" \
--arg amd64Reference "$image@$amd64_digest" \
--arg arm64Digest "$arm64_digest" \
--arg arm64Reference "$image@$arm64_digest" \
--arg digest "$digest" \
--arg image "$image" \
--arg reference "$reference" \
--arg revision "$source_revision" \
--argjson runAttempt "$run_attempt" \
--argjson runId "$run_id" \
'{
contractVersion: 1,
agent: $agent,
image: $image,
digest: $digest,
reference: $reference,
platforms: ["linux/amd64", "linux/arm64"],
platformDigests: {
"linux/amd64": $amd64Digest,
"linux/arm64": $arm64Digest
},
platformReferences: {
"linux/amd64": $amd64Reference,
"linux/arm64": $arm64Reference
},
sourceRevision: $revision,
run: {
id: $runId,
attempt: $runAttempt
}
}' >"$output"

jq -e \
--arg agent "$agent" \
'.contractVersion == 1
and .agent == $agent
and (.sourceRevision | test("^[0-9a-f]{40}$"))
and (.digest | test("^sha256:[0-9a-f]{64}$"))
and .reference == (.image + "@" + .digest)
and .platforms == ["linux/amd64", "linux/arm64"]
and (.platformDigests | keys | sort) == .platforms
and (.platformReferences | keys | sort) == .platforms
and ([
.platforms[] as $platform
| (
(.platformDigests[$platform] | test("^sha256:[0-9a-f]{64}$"))
and .platformReferences[$platform] ==
(.image + "@" + .platformDigests[$platform])
)
] | all)' \
"$output" >/dev/null
6 changes: 6 additions & 0 deletions test/dcode-base-image-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ describe("base-image publication behavior", () => {
expect(metadata?.with?.tags).toContain("type=raw,value=latest");
expect(metadata?.with?.tags).toContain("type=ref,event=tag");
expect(metadata?.with?.tags).toContain("type=sha,prefix=,format=short");
expect(createManifest?.env?.AGENT).toBe("openclaw");
const openClawManifestScript = createManifest?.run ?? "";
expect(openClawManifestScript).toContain('"${#digest_files[@]}" -ne 2');
expect(openClawManifestScript).toContain("^(amd64|arm64)-([0-9a-f]{64})$");
Expand All @@ -461,6 +462,7 @@ describe("base-image publication behavior", () => {
'docker buildx imagetools create "${tag_args[@]}" "${sources[@]}"',
);
expect(openClawManifestScript).toContain('"amd64,arm64"');
expect(openClawManifestScript).toContain("scripts/export-managed-base-image-contract.sh");
for (const step of (manifestJob?.steps ?? []).filter((step) => step.uses)) {
expect(step.uses, step.name).toMatch(FULL_SHA_ACTION);
}
Expand All @@ -470,13 +472,15 @@ describe("base-image publication behavior", () => {
const publishers = publisherJobs(workflow);
const imagePublishers = [
{
agent: "hermes",
platformJobName: "build-hermes-platforms",
manifestJobName: "build-and-push-hermes",
manifestName: "Build and push Hermes base image",
artifactPattern: "hermes-base-digest-*",
image: "${{ env.REGISTRY }}/nvidia/nemoclaw/hermes-sandbox-base",
},
{
agent: "langchain-deepagents-code",
platformJobName: "build-dcode-platforms",
manifestJobName: "build-and-push-dcode",
manifestName: "Build and push Deep Agents Code base image",
Expand Down Expand Up @@ -564,6 +568,7 @@ describe("base-image publication behavior", () => {
expect(metadata?.with?.tags).toContain("type=raw,value=latest");
expect(metadata?.with?.tags).toContain("type=ref,event=tag");
expect(metadata?.with?.tags).toContain("type=sha,prefix=,format=short");
expect(createManifest?.env?.AGENT).toBe(imagePublisher.agent);
expect(createManifest?.env?.IMAGE).toBe(imagePublisher.image);
const manifestScript = createManifest?.run ?? "";
expect(manifestScript).toContain('"${#digest_files[@]}" -ne 2');
Expand All @@ -578,6 +583,7 @@ describe("base-image publication behavior", () => {
'docker buildx imagetools create "${tag_args[@]}" "${sources[@]}"',
);
expect(manifestScript).toContain('"amd64,arm64"');
expect(manifestScript).toContain("scripts/export-managed-base-image-contract.sh");
for (const step of (manifestJob?.steps ?? []).filter((step) => step.uses)) {
expect(step.uses, step.name).toMatch(FULL_SHA_ACTION);
}
Expand Down
Loading
Loading