-
Notifications
You must be signed in to change notification settings - Fork 373
Use native Control Plane image copy in production promotion #756
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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/<pid>/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 '_<commit>' suffix." | ||||||||||||||||||||||||||||
| exit 1 | ||||||||||||||||||||||||||||
| fi | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| latest_number="$( | ||||||||||||||||||||||||||||
| cpln image query --org "${CPLN_ORG_PRODUCTION}" --prop "name~${PRODUCTION_APP_NAME}:" -o json | | ||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the production org has more than the CLI's default result window of matching images, this query can miss the current highest numbered tag and compute a duplicate or stale Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The |
||||||||||||||||||||||||||||
| jq -r --arg prefix "${PRODUCTION_APP_NAME}:" \ | ||||||||||||||||||||||||||||
| '[.items[].name | select(startswith($prefix)) | (try capture("^[^:]+:(?<number>[0-9]+)") catch empty) | .number | tonumber] | max // 0' | ||||||||||||||||||||||||||||
| )" | ||||||||||||||||||||||||||||
|
Comment on lines
+376
to
+380
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If
Suggested change
|
||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In the normal promotion job, the earlier setup action leaves the production secret in Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| 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_PROFILE="${upstream_profile}" docker manifest inspect "${source_image_ref}" >/dev/null && | ||||||||||||||||||||||||||||
| cpln image copy "${STAGING_IMAGE}" \ | ||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Comment on lines
395
to
+397
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Consider moving the auth check (and the
Suggested change
|
||||||||||||||||||||||||||||
| --profile "${upstream_profile}" \ | ||||||||||||||||||||||||||||
| --org "${CPLN_ORG_STAGING}" \ | ||||||||||||||||||||||||||||
| --to-profile default \ | ||||||||||||||||||||||||||||
| --to-org "${CPLN_ORG_PRODUCTION}" \ | ||||||||||||||||||||||||||||
| --to-name "${production_image}"; 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,21 +585,23 @@ 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 }} | ||||||||||||||||||||||||||||
| COPIED_IMAGE: ${{ steps.copy-image.outputs.image }} | ||||||||||||||||||||||||||||
| shell: bash | ||||||||||||||||||||||||||||
| run: | | ||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||
| echo "## Promotion Summary" | ||||||||||||||||||||||||||||
| echo | ||||||||||||||||||||||||||||
| if [[ "${HEALTHY}" == "true" ]]; then | ||||||||||||||||||||||||||||
| echo "✅ Status: deployment successful" | ||||||||||||||||||||||||||||
| deployed_image="${COPIED_IMAGE}" | ||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||
| echo "❌ Status: deployment failed" | ||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the copy-image step exits before emitting its output (e.g., fails during the
Suggested change
|
||||||||||||||||||||||||||||
| 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: | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
staging_commitextraction breaks for app names containing underscores${STAGING_IMAGE##*_}strips the longest*_-prefixed match, meaning it operates on the entire string including the app-name portion before the colon. If the staging app name contains underscores (e.g.,my_staging_app:3_abc1234), the extraction producesabc1234(correct here), but formy_staging_app:latestit would produceapp:latest— a value that passes the guard checks ("app:latest" != "my_staging_app:latest"and it is non-empty) but is an incorrect commit suffix embedded inproduction_image. CPLN app names conventionally use hyphens rather than underscores, so in practice this edge case is unlikely, but the extraction logic does not enforce this constraint.