ironclaw_image_published #380
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build & Push | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Version tag to publish (for example 0.24.0). Leave empty to use default tag (staging).' | |
| required: false | |
| type: string | |
| default: '' | |
| force: | |
| description: 'Force rebuild even if the version tag already exists' | |
| required: false | |
| type: boolean | |
| default: false | |
| push: | |
| branches: [main] | |
| tags: ["v*"] | |
| repository_dispatch: | |
| types: [ironclaw_image_published] | |
| env: | |
| IMAGE_NAME: nearaidev/ironclaw-dind | |
| DEFAULT_TAG: staging | |
| jobs: | |
| build: | |
| name: Build & Push | |
| runs-on: [self-hosted, sysbox] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Resolve build version and image | |
| id: resolve | |
| run: | | |
| RAW_TAG="" | |
| if [[ -n "${{ inputs.version }}" && "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| RAW_TAG="${{ inputs.version }}" | |
| elif [[ "${{ github.event_name }}" == "repository_dispatch" && -n "${{ github.event.client_payload.version }}" ]]; then | |
| RAW_TAG="${{ github.event.client_payload.version }}" | |
| else | |
| RAW_TAG="${{ env.DEFAULT_TAG }}" | |
| fi | |
| if [[ ! "$RAW_TAG" =~ ^[A-Za-z0-9_.-]+$ ]]; then | |
| echo "Invalid docker tag '$RAW_TAG'. Allowed characters: [A-Za-z0-9_.-]" | |
| exit 1 | |
| fi | |
| echo "tag=$RAW_TAG" >> "$GITHUB_OUTPUT" | |
| echo "image=nearaidev/ironclaw:$RAW_TAG" >> "$GITHUB_OUTPUT" | |
| echo "worker_image=nearaidev/ironclaw-worker:$RAW_TAG" >> "$GITHUB_OUTPUT" | |
| - name: Log in to Docker Hub | |
| uses: docker/login-action@v3 | |
| with: | |
| username: ${{ vars.DOCKER_REGISTRY_USER }} | |
| password: ${{ secrets.DOCKER_REGISTRY_TOKEN }} | |
| - name: Pull ironclaw image and detect version | |
| id: version | |
| run: | | |
| docker pull "${{ steps.resolve.outputs.image }}" | |
| VERSION=$(docker run --rm "${{ steps.resolve.outputs.image }}" --version | awk '{print $2}') | |
| echo "version=${VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "Detected IronClaw ${VERSION}" | |
| - name: Get upstream digest | |
| id: digest | |
| run: | | |
| UPSTREAM_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "${{ steps.resolve.outputs.image }}" | cut -d@ -f2) | |
| echo "upstream=$UPSTREAM_DIGEST" >> "$GITHUB_OUTPUT" | |
| echo "Upstream digest: $UPSTREAM_DIGEST" | |
| - name: Check if rebuild needed (schedule only) | |
| id: check | |
| if: github.event_name == 'schedule' | |
| run: | | |
| CURRENT_DIGEST="" | |
| if docker pull "${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}" > /dev/null 2>&1; then | |
| CURRENT_DIGEST=$(docker inspect --format='{{index .Config.Labels "ironclaw.source.digest"}}' "${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}" 2>/dev/null || echo "") | |
| fi | |
| echo "Current source digest: ${CURRENT_DIGEST:-<none>}" | |
| echo "Upstream digest: ${{ steps.digest.outputs.upstream }}" | |
| if [[ "$CURRENT_DIGEST" == "${{ steps.digest.outputs.upstream }}" ]]; then | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "No changes detected — skipping rebuild" | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| echo "Upstream changed — rebuilding" | |
| fi | |
| - name: Check existing version tag (skip unless forced) | |
| id: tag_guard | |
| if: steps.check.outputs.skip != 'true' | |
| run: | | |
| FORCE=false | |
| if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ inputs.force }}" == "true" ]]; then | |
| FORCE=true | |
| fi | |
| echo "force=$FORCE" >> "$GITHUB_OUTPUT" | |
| TAG="${{ steps.resolve.outputs.tag }}" | |
| if [[ "$TAG" == "${{ env.DEFAULT_TAG }}" ]]; then | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| echo "Default tag '${{ env.DEFAULT_TAG }}' is mutable; skipping immutable tag check." | |
| exit 0 | |
| fi | |
| if docker manifest inspect "${{ env.IMAGE_NAME }}:${TAG}" > /dev/null 2>&1; then | |
| if [[ "$FORCE" == "true" ]]; then | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| echo "Version tag already exists, but force=true so build will continue." | |
| else | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| echo "Version tag already exists, skipping build: ${{ env.IMAGE_NAME }}:${TAG}" | |
| fi | |
| else | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Build DinD image | |
| if: steps.check.outputs.skip != 'true' && steps.tag_guard.outputs.skip != 'true' | |
| env: | |
| SANDBOX_IMAGE: ${{ steps.resolve.outputs.worker_image }} | |
| run: | | |
| bash ./scripts/build-dind-image.sh \ | |
| "${{ env.IMAGE_NAME }}:${{ steps.resolve.outputs.tag }}" \ | |
| "${{ steps.resolve.outputs.image }}" \ | |
| "${{ steps.digest.outputs.upstream }}" | |
| - name: Push | |
| if: steps.check.outputs.skip != 'true' && steps.tag_guard.outputs.skip != 'true' | |
| run: | | |
| docker push "${{ env.IMAGE_NAME }}:${{ steps.resolve.outputs.tag }}" | |
| # Staging orchestrator allowlist: register immutable version tags only (not :staging) | |
| - name: Register image on staging orchestrator allowlist | |
| if: >- | |
| steps.check.outputs.skip != 'true' && | |
| steps.tag_guard.outputs.skip != 'true' && | |
| steps.resolve.outputs.tag != env.DEFAULT_TAG | |
| env: | |
| ORCH_URL: https://api.agents-staging.near.ai | |
| ORCH_ADMIN_SECRET: ${{ secrets.AGENTS_STG_ORCHESTRATOR_ADMIN_SECRET }} | |
| IMAGE_REF: docker.io/${{ env.IMAGE_NAME }}:${{ steps.resolve.outputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| if [[ -z "${ORCH_ADMIN_SECRET:-}" ]]; then | |
| echo "Skipping orchestrator allowlist (AGENTS_STG_ORCHESTRATOR_ADMIN_SECRET not set)." | |
| exit 0 | |
| fi | |
| python3 scripts/register_orchestrator_allowlist.py \ | |
| --no-resolve-digests \ | |
| --label "Ironclaw DinD (${{ steps.resolve.outputs.tag }})" \ | |
| --note-suffix "CI ${GITHUB_RUN_ID}" | |
| - name: Resolve pushed ironclaw-dind digest | |
| id: dind_digest | |
| if: steps.check.outputs.skip != 'true' && steps.tag_guard.outputs.skip != 'true' | |
| run: | | |
| DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "${{ env.IMAGE_NAME }}:${{ steps.resolve.outputs.tag }}" 2>/dev/null | cut -d@ -f2) | |
| if [[ -z "$DIGEST" ]]; then | |
| DIGEST="N/A" | |
| fi | |
| echo "digest=$DIGEST" >> "$GITHUB_OUTPUT" | |
| - name: Summary | |
| if: steps.check.outputs.skip != 'true' && steps.tag_guard.outputs.skip != 'true' | |
| run: | | |
| { | |
| echo "## ironclaw-dind" | |
| echo "" | |
| echo "- tag: \`${{ env.IMAGE_NAME }}:${{ steps.resolve.outputs.tag }}\`" | |
| echo "- ironclaw: \`${{ steps.resolve.outputs.image }}\` (v${{ steps.version.outputs.version }})" | |
| echo "- ironclaw-worker: \`${{ steps.resolve.outputs.worker_image }}\`" | |
| echo "- source digest: \`${{ steps.digest.outputs.upstream }}\`" | |
| echo "- ironclaw-dind digest: \`${{ steps.dind_digest.outputs.digest }}\`" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Summary (skipped) | |
| if: steps.check.outputs.skip == 'true' || steps.tag_guard.outputs.skip == 'true' | |
| run: | | |
| { | |
| echo "## ironclaw-dind — skipped" | |
| echo "" | |
| if [[ "${{ steps.check.outputs.skip }}" == "true" ]]; then | |
| echo "Upstream \`${{ steps.resolve.outputs.image }}\` digest unchanged." | |
| else | |
| echo "Version tag already exists and force was not set: \`${{ env.IMAGE_NAME }}:${{ steps.resolve.outputs.tag }}\`" | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" |