Skip to content

fix(lcm): point stable latest at the promoted version digest - #2088

Merged
santos1709 merged 1 commit into
masterfrom
lcm-stable-latest-digest
Jul 29, 2026
Merged

fix(lcm): point stable latest at the promoted version digest#2088
santos1709 merged 1 commit into
masterfrom
lcm-stable-latest-digest

Conversation

@santos1709

@santos1709 santos1709 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

stable/<image>:latest was copied independently from staging/<image>:latest,

This PR tags latest from the stable version tag that was just promoted instead, so the two are the same manifest by construction, and assert the digests match before the step succeeds. Also add set -euo pipefail so a crane failure mid-loop fails the step rather than being masked by the loop's last command.

Summary by CodeRabbit

  • Bug Fixes
    • Improved image promotion reliability by promoting the specified version and assigning the stable latest tag from the same image digest.
    • Added verification to detect mismatched stable image tags and fail the promotion when inconsistencies occur.
    • Enhanced workflow error handling and success reporting.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The stable promotion workflow now copies only the requested image tag, assigns that promoted digest to latest, verifies the digests match, and reports failures explicitly.

Changes

Stable image promotion

Layer / File(s) Summary
Digest-validated image promotion
.github/workflows/promote-to-stable.yaml
The workflow enables strict shell execution, promotes the requested tag, retags it as latest, compares digests, and updates success and error messages.

Estimated code review effort: 2 (Simple) | ~10 minutes

Poem

A bunny checks the image trail,
Ensuring tags can never fail.
One digest hops to latest bright,
Strict little paws keep paths right.
“Stable!” sings the rabbit crew.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: stable latest is now tagged from the promoted version digest.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@santos1709
santos1709 added this pull request to the merge queue Jul 29, 2026
@santos1709
santos1709 removed this pull request from the merge queue due to a manual request Jul 29, 2026
stable/<image>:latest was copied independently from staging/<image>:latest,

This PR tags latest from the stable version tag that was just promoted instead, so
the two are the same manifest by construction, and assert the digests match
before the step succeeds. Also add set -euo pipefail so a crane failure
mid-loop fails the step rather than being masked by the loop's last command.
@santos1709
santos1709 force-pushed the lcm-stable-latest-digest branch from ca0fffc to fffd2fb Compare July 29, 2026 10:00
@santos1709
santos1709 enabled auto-merge July 29, 2026 10:00
@santos1709
santos1709 added this pull request to the merge queue Jul 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
.github/workflows/promote-to-stable.yaml (1)

59-63: 🗄️ Data Integrity & Integration | 🔵 Trivial

Serialize stable image promotions.

This workflow does not declare a GitHub Actions concurrency group; group: infra1-runners-arc is only the runner selector. When two promotions overlap, crane tag stable/${image}:latest can race with the digest checks, so an invocation can pass the promoted/current comparison while another run retags latest, leaving stable inconsistent. Add a stable-image promotion concurrency group, e.g. for each image/tag being promoted, or serialize these promotions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/promote-to-stable.yaml around lines 59 - 63, Add a GitHub
Actions concurrency configuration to the stable image promotion workflow, using
a group keyed by the image/tag being promoted or a shared group that serializes
all stable promotions. Keep the existing runner selection unchanged, and ensure
overlapping invocations cannot race between the crane tag operation and the
promoted/current digest comparison.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/promote-to-stable.yaml:
- Around line 51-54: Update the image-processing block around the built_images
and for loop to execute jq separately, capture its output and exit status, and
fail the step when parsing fails. Iterate over the captured jq results only
after validating the status, preserving the existing image handling behavior.
- Around line 52-53: Update the promotion step around built_images and tag to
pass inputs.images and inputs.image_tag through step-level environment
variables, then reference those quoted shell variables instead of interpolating
workflow expressions into Bash. Validate IMAGE_TAG against the allowed
registry-tag format before using it, and preserve the existing image promotion
behavior for valid inputs.
- Around line 61-66: Update the promotion verification around the promoted and
current digest checks to capture the corresponding staging image digest before
copying, then compare that staging digest with the stable tagged digest and
retain the latest-tag comparison. Include the relevant staging and stable
references in any mismatch error while preserving the existing failure behavior.

---

Nitpick comments:
In @.github/workflows/promote-to-stable.yaml:
- Around line 59-63: Add a GitHub Actions concurrency configuration to the
stable image promotion workflow, using a group keyed by the image/tag being
promoted or a shared group that serializes all stable promotions. Keep the
existing runner selection unchanged, and ensure overlapping invocations cannot
race between the crane tag operation and the promoted/current digest comparison.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 084c8c60-9076-4a32-b736-3408bea8693f

📥 Commits

Reviewing files that changed from the base of the PR and between 0fb7021 and fffd2fb.

📒 Files selected for processing (1)
  • .github/workflows/promote-to-stable.yaml

Comment on lines +51 to 54
set -euo pipefail
built_images='${{ inputs.images }}'
tag=${{ inputs.image_tag }}
for image in $(echo "$built_images" | jq -r '.[]'); do

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

bash -c 'set -euo pipefail; for image in $(printf "%s" "{bad}" | jq -r ".[]"); do :; done; echo survived'

Repository: gooddata/gooddata-ruby

Length of output: 230


🏁 Script executed:

#!/bin/bash
set -u

# Inspect the workflow around the reported lines and any relevant context.
if [ -f ".github/workflows/promote-to-stable.yaml" ]; then
  echo "== file exists =="
  wc -l ".github/workflows/promote-to-stable.yaml"
  echo "== lines 1-90 =="
  cat -n ".github/workflows/promote-to-stable.yaml" | sed -n '1,90p'
else
  echo "file not found"
fi

echo "== bash pipe/subshell behavior probe =="
bash -c 'set -euo pipefail; for image in $(printf "%s" "{bad}" | jq -r ".[]"); do :; done; echo survived' || true
echo "== bash function-local set vs pipeline probe =="
bash -c 'set -euo pipefail
run() { for image in $(printf "%s" "{bad}" | jq -r ".[]"); do :; done; echo survived_after_function; }
run
echo survived_after_call' || true
echo "== bash -c exit probe =="
bash -c "set -euo pipefail; for image in \$(printf '%s' '{bad}' | jq -r '.[]'); do :; done; echo survived" || true

Repository: gooddata/gooddata-ruby

Length of output: 4050


Capture the jq status before iterating.

jq parses {bad} and exits non-zero, but the $(...) subshell status is not observed by the for list, so set -euo pipefail does not fail the promotion step. Save jq output/status before for, then loop over captured results.

🧰 Tools
🪛 zizmor (1.28.0)

[error] 52-52: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 53-53: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/promote-to-stable.yaml around lines 51 - 54, Update the
image-processing block around the built_images and for loop to execute jq
separately, capture its output and exit status, and fail the step when parsing
fails. Iterate over the captured jq results only after validating the status,
preserving the existing image handling behavior.

Comment on lines 52 to 53
built_images='${{ inputs.images }}'
tag=${{ inputs.image_tag }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

rg -n -C 6 'workflow_dispatch|workflow_call|pull_request|inputs\.(images|image_tag)' .github/workflows/promote-to-stable.yaml

Repository: gooddata/gooddata-ruby

Length of output: 989


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
path = Path(".github/workflows/promote-to-stable.yaml")
text = path.read_text()
print("file exists:", path.exists())
for i, line in enumerate(text.splitlines(), 1):
    print(f"{i:03d}: {line}")
PY

Repository: gooddata/gooddata-ruby

Length of output: 3465


🌐 Web query:

GitHub Actions workflow_call inputs expression evaluation substituted into run string before execution

💡 Result:

Yes, GitHub Actions expressions wrapped in ${{ }} syntax are evaluated and substituted into the run command string before the shell script is executed on the runner [1][2][3]. When you use the ${{ }} syntax directly within a run step, the GitHub Actions runner processes the expression, converts it into its string representation, and performs a literal text substitution into the command block [1][3]. This occurs before the runner generates and executes the temporary shell script [1][3]. Security Implications Because this substitution happens before the shell interprets the command, using ${{ }} for inputs directly inside a run string is a primary vector for shell command injection [1][2]. An attacker who controls the input value can inject characters (such as ", ;, or &) to break out of intended quotes or command structures, allowing them to execute arbitrary commands on the runner [1][3]. Recommended Best Practice To safely handle inputs in run steps, you should use environment variables instead of direct ${{ }} substitution [2]. By mapping the input to an environment variable in the env block, the value is passed to the shell process safely as data rather than being interpreted as part of the command's raw text [2]. Example of the vulnerable approach: - name: Vulnerable step run: echo "${{ inputs.my-input }}" # Unsafe if input contains shell metacharacters Example of the recommended approach: - name: Secure step env: MY_INPUT: ${{ inputs.my-input }} run: echo "$MY_INPUT" # Safe, as the value is treated as a variable string

Citations:


Do not interpolate workflow inputs directly into shell source.

${{ inputs.image_tag }} is substituted into the runner script before Bash executes it, so a tag containing shell metacharacters can inject commands during image promotion. Map both inputs.images and inputs.image_tag via step-level env, quote the shell variables, and validate IMAGE_TAG before using it as a registry tag.

🧰 Tools
🪛 zizmor (1.28.0)

[error] 52-52: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[error] 53-53: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/promote-to-stable.yaml around lines 52 - 53, Update the
promotion step around built_images and tag to pass inputs.images and
inputs.image_tag through step-level environment variables, then reference those
quoted shell variables instead of interpolating workflow expressions into Bash.
Validate IMAGE_TAG against the allowed registry-tag format before using it, and
preserve the existing image promotion behavior for valid inputs.

Source: Linters/SAST tools

Comment on lines +61 to +66
promoted=$(crane digest "${INFRA_REPO_URL}/stable/${image}:${tag}")
current=$(crane digest "${INFRA_REPO_URL}/stable/${image}:latest")
if [[ "${promoted}" != "${current}" ]]; then
echo "::error::stable/${image}:latest (${current}) != :${tag} (${promoted})"
exit 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Verify the staging digest as well.

The current check only compares stable/${image}:${tag} with stable/${image}:latest; it never verifies the stated staging-to-stable digest contract. Capture the staging digest before copying and include it in the comparison.

Proposed fix
+            staging=$(crane digest "${INFRA_REPO_URL}/staging/${image}:${tag}")
             crane cp \
               "${INFRA_REPO_URL}/staging/${image}:${tag}" \
               "${INFRA_REPO_URL}/stable/${image}:${tag}"
...
-            if [[ "${promoted}" != "${current}" ]]; then
+            if [[ "${staging}" != "${promoted}" || "${promoted}" != "${current}" ]]; then
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/promote-to-stable.yaml around lines 61 - 66, Update the
promotion verification around the promoted and current digest checks to capture
the corresponding staging image digest before copying, then compare that staging
digest with the stable tagged digest and retain the latest-tag comparison.
Include the relevant staging and stable references in any mismatch error while
preserving the existing failure behavior.

Merged via the queue into master with commit 97a77ca Jul 29, 2026
27 of 28 checks passed
@santos1709
santos1709 deleted the lcm-stable-latest-digest branch July 29, 2026 10:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants