From 52f535e0999dfb4c7a4c756e167443e56c07b52f Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Tue, 2 Jun 2026 11:00:48 -1000 Subject: [PATCH 1/2] Use native Control Plane image copy in production promotion --- .../cpflow-promote-staging-to-production.yml | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cpflow-promote-staging-to-production.yml b/.github/workflows/cpflow-promote-staging-to-production.yml index 4425aff9..260e3415 100644 --- a/.github/workflows/cpflow-promote-staging-to-production.yml +++ b/.github/workflows/cpflow-promote-staging-to-production.yml @@ -336,10 +336,10 @@ jobs: echo "image=${staging_image}" >> "$GITHUB_OUTPUT" - name: Copy image from staging + id: copy-image env: # Pass the upstream token via env rather than `-t` so it doesn't appear in /proc//cmdline. CPLN_TOKEN_STAGING: ${{ secrets.CPLN_TOKEN_STAGING }} - CPLN_UPSTREAM_TOKEN: ${{ secrets.CPLN_TOKEN_STAGING }} PRODUCTION_APP_NAME: ${{ vars.PRODUCTION_APP_NAME }} CPLN_ORG_STAGING: ${{ vars.CPLN_ORG_STAGING }} CPLN_ORG_PRODUCTION: ${{ vars.CPLN_ORG_PRODUCTION }} @@ -367,9 +367,39 @@ jobs: exit 1 fi + staging_commit="${STAGING_IMAGE##*_}" + if [[ "${staging_commit}" == "${STAGING_IMAGE}" || -z "${staging_commit}" ]]; then + echo "::error::Staging image '${STAGING_IMAGE}' does not include the expected '_' suffix." + exit 1 + fi + + latest_number="$( + cpln image query --org "${CPLN_ORG_PRODUCTION}" --prop "name~${PRODUCTION_APP_NAME}:" -o json | + jq -r --arg prefix "${PRODUCTION_APP_NAME}:" \ + '[.items[].name | select(startswith($prefix)) | (try capture("^[^:]+:(?[0-9]+)") catch empty) | .number | tonumber] | max // 0' + )" + production_image="${PRODUCTION_APP_NAME}:$((latest_number + 1))_${staging_commit}" + source_image_ref="${CPLN_ORG_STAGING}.registry.cpln.io/${STAGING_IMAGE}" + + upstream_profile="upstream-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + cleanup_upstream_profile() { + cpln profile delete "${upstream_profile}" >/dev/null 2>&1 || true + } + trap cleanup_upstream_profile EXIT + + cpln profile create "${upstream_profile}" --token "${CPLN_TOKEN_STAGING}" >/dev/null + CPLN_PROFILE="${upstream_profile}" cpln image docker-login --org "${CPLN_ORG_STAGING}" >/dev/null + CPLN_PROFILE="${upstream_profile}" docker manifest inspect "${source_image_ref}" >/dev/null + copy_status=1 for attempt in $(seq 1 "${copy_image_attempts}"); do - if cpflow copy-image-from-upstream -a "${PRODUCTION_APP_NAME}" --org "${CPLN_ORG_PRODUCTION}" --image "${STAGING_IMAGE}"; then + if cpln image copy "${STAGING_IMAGE}" \ + --profile "${upstream_profile}" \ + --org "${CPLN_ORG_STAGING}" \ + --to-profile default \ + --to-org "${CPLN_ORG_PRODUCTION}" \ + --to-name "${production_image}" \ + --cleanup; then copy_status=0 break else @@ -389,6 +419,8 @@ jobs: exit "${copy_status}" fi + echo "image=${production_image}" >> "$GITHUB_OUTPUT" + - name: Deploy image to production env: PRODUCTION_APP_NAME: ${{ vars.PRODUCTION_APP_NAME }} @@ -553,7 +585,7 @@ jobs: HEALTHY: ${{ steps.health-check.outputs.healthy }} PREVIOUS_IMAGE: ${{ steps.capture-current.outputs.current_image }} PREVIOUS_VERSION: ${{ steps.capture-current.outputs.current_version }} - DEPLOYED_IMAGE: ${{ steps.staging-image.outputs.image }} + DEPLOYED_IMAGE: ${{ steps.copy-image.outputs.image }} shell: bash run: | { From 316555de33023d9f7be8c941e40ffd18013990ee Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Tue, 2 Jun 2026 11:19:11 -1000 Subject: [PATCH 2/2] Address native promotion copy review feedback --- .../cpflow-promote-staging-to-production.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cpflow-promote-staging-to-production.yml b/.github/workflows/cpflow-promote-staging-to-production.yml index 260e3415..e9e52eb7 100644 --- a/.github/workflows/cpflow-promote-staging-to-production.yml +++ b/.github/workflows/cpflow-promote-staging-to-production.yml @@ -387,19 +387,19 @@ jobs: } trap cleanup_upstream_profile EXIT - cpln profile create "${upstream_profile}" --token "${CPLN_TOKEN_STAGING}" >/dev/null + cleanup_upstream_profile + CPLN_TOKEN="${CPLN_TOKEN_STAGING}" cpln profile create "${upstream_profile}" >/dev/null CPLN_PROFILE="${upstream_profile}" cpln image docker-login --org "${CPLN_ORG_STAGING}" >/dev/null - CPLN_PROFILE="${upstream_profile}" docker manifest inspect "${source_image_ref}" >/dev/null copy_status=1 for attempt in $(seq 1 "${copy_image_attempts}"); do - if cpln image copy "${STAGING_IMAGE}" \ + if CPLN_PROFILE="${upstream_profile}" docker manifest inspect "${source_image_ref}" >/dev/null && + cpln image copy "${STAGING_IMAGE}" \ --profile "${upstream_profile}" \ --org "${CPLN_ORG_STAGING}" \ --to-profile default \ --to-org "${CPLN_ORG_PRODUCTION}" \ - --to-name "${production_image}" \ - --cleanup; then + --to-name "${production_image}"; then copy_status=0 break else @@ -585,7 +585,7 @@ jobs: HEALTHY: ${{ steps.health-check.outputs.healthy }} PREVIOUS_IMAGE: ${{ steps.capture-current.outputs.current_image }} PREVIOUS_VERSION: ${{ steps.capture-current.outputs.current_version }} - DEPLOYED_IMAGE: ${{ steps.copy-image.outputs.image }} + COPIED_IMAGE: ${{ steps.copy-image.outputs.image }} shell: bash run: | { @@ -593,13 +593,15 @@ jobs: echo if [[ "${HEALTHY}" == "true" ]]; then echo "✅ Status: deployment successful" + deployed_image="${COPIED_IMAGE}" else echo "❌ Status: deployment failed" + deployed_image="${PREVIOUS_IMAGE}" fi echo echo "Previous image: \`${PREVIOUS_IMAGE}\`" echo "Previous version: ${PREVIOUS_VERSION}" - echo "Deployed image: \`${DEPLOYED_IMAGE}\`" + echo "Deployed image: \`${deployed_image}\`" } >> "$GITHUB_STEP_SUMMARY" create-github-release: