Skip to content
Merged
221 changes: 75 additions & 146 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -432,76 +432,64 @@

# ---------------------------------------------------------------------------
# Release CLI for Docker Desktop — build, sign & push CLI + Desktop module image
# (triggers docker/inference-engine-llama.cpp: signs macOS/Windows binaries,
# pushes docker/docker-model-cli-desktop-module to Docker Hub)
# ---------------------------------------------------------------------------
release-cli-desktop:
needs: [prepare, test]
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
steps:
- name: Trigger release-cli-dd workflow
id: trigger
- name: Trigger Desktop CLI release and wait for completion
env:
GH_TOKEN: ${{ secrets.CLI_RELEASE_PAT }}
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
VERSION: ${{ needs.prepare.outputs.version }}
run: |
TRIGGER_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
echo "trigger_time=$TRIGGER_TIME" >> "$GITHUB_OUTPUT"
echo "🚀 Triggering release-cli-dd workflow at $TRIGGER_TIME"
echo " model-cli-ref: $RELEASE_TAG"
echo " tag: v$VERSION"
gh workflow run release-cli-dd.yml \
echo "🚀 Triggering Desktop CLI release workflow"

# gh workflow run returns run URL on stdout (gh v2.87.0+)
OUTPUT=$(gh workflow run release-cli-dd.yml \
--repo docker/inference-engine-llama.cpp \
-f model-cli-ref="$RELEASE_TAG" \
-f tag="v$VERSION"
echo "✅ release-cli-dd workflow triggered"
-f tag="v$VERSION")

- name: Wait for release-cli-dd to complete
env:
GH_TOKEN: ${{ secrets.CLI_RELEASE_PAT }}
TRIGGER_TIME: ${{ steps.trigger.outputs.trigger_time }}
run: |
echo "⏳ Waiting for release-cli-dd workflow to appear (triggered at $TRIGGER_TIME)..."
sleep 15
RUN_URL=$(echo "$OUTPUT" \
| grep -o 'https://github.com/[^ ]*/actions/runs/[0-9]*') || true

# Find the run created after our trigger time to avoid picking up unrelated runs
for i in $(seq 1 30); do
if [ -z "$RUN_URL" ]; then
echo "⚠️ Could not extract run URL from gh output, querying latest run..."
sleep 5
RUN_ID=$(gh run list \
--repo docker/inference-engine-llama.cpp \
--workflow release-cli-dd.yml \
--limit 5 \
--json databaseId,createdAt \
--jq "[.[] | select(.createdAt >= \"$TRIGGER_TIME\")] | sort_by(.createdAt) | last | .databaseId")
if [ -n "$RUN_ID" ] && [ "$RUN_ID" != "null" ]; then
echo "Found release-cli-dd run: $RUN_ID"
break
fi
echo " Retry $i/30..."
sleep 10
done
--limit 1 \
--json databaseId \
--jq '.[0].databaseId')
else
RUN_ID=$(echo "$RUN_URL" | grep -o '[0-9]*$')
fi

if [ -z "$RUN_ID" ] || [ "$RUN_ID" = "null" ]; then
echo "::error::Could not find release-cli-dd workflow run created after $TRIGGER_TIME"
if [ -z "$RUN_ID" ]; then
echo "::error::Failed to determine workflow run ID"
exit 1
fi

echo "⏳ Waiting for release-cli-dd run $RUN_ID to complete..."
echo "::add-mask::$RUN_ID"
echo "::add-mask::$RUN_URL"
echo "✅ Desktop CLI release workflow triggered"

echo "⏳ Waiting for Desktop CLI release to complete..."
gh run watch "$RUN_ID" \
--repo docker/inference-engine-llama.cpp \
--exit-status
echo "✅ release-cli-dd workflow completed successfully"
echo "✅ Desktop CLI release completed successfully"

# ---------------------------------------------------------------------------
# Bump docker-model version in pinata and open a PR
# ---------------------------------------------------------------------------
bump-pinata:
needs: [prepare, release-cli-desktop]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
Expand Down Expand Up @@ -544,64 +532,62 @@
draft: true

# ---------------------------------------------------------------------------
# Release CLI for Docker CE — build .deb/.rpm packages and deploy to download.docker.com
# (triggers docker/packaging → docker/release-repo)
# Release CLI for Docker CE — build .deb/.rpm packages and deploy
#
# Triggers packaging, waits for it, then triggers the deploy workflow.
# The verify-docker-ce job (which requires manual approval via the
# "release-repo-deploy" environment) runs after this to confirm the
# deploy completed successfully.
# ---------------------------------------------------------------------------
release-cli-docker-ce:
release-cli-docker-ce-trigger:
if: ${{ !inputs.skipPackaging }}
needs: [prepare, release-cli-desktop]
runs-on: ubuntu-latest
timeout-minutes: 360
permissions:
contents: read
outputs:
packaging_image: ${{ steps.packaging.outputs.packaging_image }}
steps:
- name: Trigger release-model workflow in packaging repo
id: trigger
- name: Trigger packaging and wait for completion
id: packaging
env:
GH_TOKEN: ${{ secrets.CLI_RELEASE_PAT }}
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
VERSION: ${{ needs.prepare.outputs.version }}
run: |
TRIGGER_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
echo "trigger_time=$TRIGGER_TIME" >> "$GITHUB_OUTPUT"
echo "📦 Triggering release-model workflow in docker/packaging at $TRIGGER_TIME"
echo " ref: $RELEASE_TAG"
gh workflow run release-model.yml \
echo "📦 Triggering packaging workflow"

# gh workflow run returns run URL on stdout (gh v2.87.0+)
OUTPUT=$(gh workflow run release-model.yml \
--repo docker/packaging \
-f ref="$RELEASE_TAG"
echo "✅ release-model workflow triggered in docker/packaging"
-f ref="$RELEASE_TAG")

- name: Wait for packaging workflow to complete
id: packaging
env:
GH_TOKEN: ${{ secrets.CLI_RELEASE_PAT }}
VERSION: ${{ needs.prepare.outputs.version }}
TRIGGER_TIME: ${{ steps.trigger.outputs.trigger_time }}
run: |
echo "⏳ Waiting for packaging workflow to appear (triggered at $TRIGGER_TIME)..."
sleep 15
RUN_URL=$(echo "$OUTPUT" \
| grep -o 'https://github.com/[^ ]*/actions/runs/[0-9]*') || true

# Find the run created after our trigger time to avoid picking up unrelated runs
for i in $(seq 1 30); do
if [ -z "$RUN_URL" ]; then
echo "⚠️ Could not extract run URL from gh output, querying latest run..."
sleep 5
RUN_ID=$(gh run list \
--repo docker/packaging \
--workflow release-model.yml \
--limit 5 \
--json databaseId,createdAt \
--jq "[.[] | select(.createdAt >= \"$TRIGGER_TIME\")] | sort_by(.createdAt) | last | .databaseId")
if [ -n "$RUN_ID" ] && [ "$RUN_ID" != "null" ]; then
echo "Found packaging run: $RUN_ID"
break
fi
echo " Retry $i/30..."
sleep 10
done
--limit 1 \
--json databaseId \
--jq '.[0].databaseId')
else
RUN_ID=$(echo "$RUN_URL" | grep -o '[0-9]*$')
fi

if [ -z "$RUN_ID" ] || [ "$RUN_ID" = "null" ]; then
echo "::error::Could not find packaging workflow run created after $TRIGGER_TIME"
if [ -z "$RUN_ID" ]; then
echo "::error::Failed to determine packaging workflow run ID"
exit 1
fi

echo "⏳ Waiting for packaging run $RUN_ID to complete..."
echo "::add-mask::$RUN_ID"
echo "::add-mask::$RUN_URL"
echo "✅ Packaging workflow triggered"

echo "⏳ Waiting for packaging to complete..."
gh run watch "$RUN_ID" \
--repo docker/packaging \
--exit-status
Expand All @@ -613,111 +599,54 @@
--jq '.number')

PACKAGING_IMAGE="dockereng/packaging:model-v${VERSION}-${RUN_NUMBER}"
echo "📦 Packaging image: $PACKAGING_IMAGE"
echo "::add-mask::$PACKAGING_IMAGE"
echo "packaging_image=$PACKAGING_IMAGE" >> "$GITHUB_OUTPUT"
echo "✅ Packaging workflow completed successfully"

- name: Trigger release-repo plugin workflow
id: trigger_release_repo
- name: Trigger deploy workflow
env:
GH_TOKEN: ${{ secrets.CLI_RELEASE_PAT }}
VERSION: ${{ needs.prepare.outputs.version }}
PACKAGING_IMAGE: ${{ steps.packaging.outputs.packaging_image }}
run: |
TRIGGER_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
echo "trigger_time=$TRIGGER_TIME" >> "$GITHUB_OUTPUT"
echo "🚀 Triggering plugin release in docker/release-repo at $TRIGGER_TIME"
echo " packaging_image: $PACKAGING_IMAGE"
echo " model_version: $VERSION"
echo " channel: stable"
echo " release_live: true"
echo "🚀 Triggering deploy workflow"
gh workflow run plugin.yml \
--repo docker/release-repo \
--ref production \
-f packaging_image="$PACKAGING_IMAGE" \
-f model_version="$VERSION" \
-f channel=stable \
-f release_live=true
echo "✅ Plugin release workflow triggered in docker/release-repo"

- name: Wait for release-repo plugin workflow to complete
env:
GH_TOKEN: ${{ secrets.CLI_RELEASE_PAT }}
TRIGGER_TIME: ${{ steps.trigger_release_repo.outputs.trigger_time }}
run: |
echo "⏳ Waiting for release-repo plugin workflow to appear (triggered at $TRIGGER_TIME)..."
sleep 15

# Find the run created after our trigger time to avoid picking up unrelated runs
for i in $(seq 1 30); do
RUN_ID=$(gh run list \
--repo docker/release-repo \
--workflow plugin.yml \
--limit 5 \
--json databaseId,createdAt \
--jq "[.[] | select(.createdAt >= \"$TRIGGER_TIME\")] | sort_by(.createdAt) | last | .databaseId")
if [ -n "$RUN_ID" ] && [ "$RUN_ID" != "null" ]; then
echo "Found release-repo run: $RUN_ID"
break
fi
echo " Retry $i/30..."
sleep 10
done

if [ -z "$RUN_ID" ] || [ "$RUN_ID" = "null" ]; then
echo "::error::Could not find release-repo plugin workflow run created after $TRIGGER_TIME"
exit 1
fi

echo "⏳ Waiting for release-repo run $RUN_ID to complete (includes manual deploy-to-live approval)..."
gh run watch "$RUN_ID" \
--repo docker/release-repo \
--exit-status
echo "✅ Release-repo plugin workflow completed successfully"

- name: Post summary
env:
VERSION: ${{ needs.prepare.outputs.version }}
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
PACKAGING_IMAGE: ${{ steps.packaging.outputs.packaging_image }}
run: |
cat >> "$GITHUB_STEP_SUMMARY" <<-SUMMARY
## 📦 Docker CE Packaging & Release

| Step | Status |
|------|--------|
| Packaging image | \`${PACKAGING_IMAGE}\` |
| Model version | \`${VERSION}\` |
| Release channel | \`stable\` |
| Deploy to live | ✅ Yes |
| Release tag | \`${RELEASE_TAG}\` |

The plugin release workflow has been triggered in [docker/release-repo](https://github.com/docker/release-repo/actions/workflows/plugin.yml).
SUMMARY
echo "✅ Deploy workflow triggered"

# ---------------------------------------------------------------------------
# Verify Docker CE installation and server version — install Docker CE,
# start the released model-runner image, and use `docker model version`
# to confirm both client and server versions match the release tag.
# Verify Docker CE installation and server version — requires manual
# approval via the "release-repo-deploy" environment. Before approving,
# ensure the deploy workflow in docker/release-repo completed successfully.
#
# Once approved, installs Docker CE, starts the released model-runner
# image, and uses `docker model version` to confirm both client and
# server versions match the release tag.
# ---------------------------------------------------------------------------
verify-docker-ce:
needs: [prepare, release-cli-docker-ce, build]
needs: [prepare, release-cli-docker-ce-trigger, build]
runs-on: ubuntu-latest
timeout-minutes: 15
environment: release-repo-deploy
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

- name: Verify client and server versions
run: |
chmod +x scripts/test-docker-ce-installation.sh
./scripts/test-docker-ce-installation.sh "${{ needs.prepare.outputs.release_tag }}"

# ---------------------------------------------------------------------------
# Create GitHub Release with AI-generated release notes
# ---------------------------------------------------------------------------
github-release:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
needs: [prepare, release-notes, build, release-cli-desktop]
needs: [prepare, release-notes, build, release-cli-desktop, bump-pinata, verify-docker-ce]
if: ${{ !cancelled() && !contains(needs.*.result, 'failure') }}
runs-on: ubuntu-latest
permissions:
contents: write
Expand Down
Loading