-
Notifications
You must be signed in to change notification settings - Fork 55
fix(release): make the release checklist able to fail #840
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
base: develop
Are you sure you want to change the base?
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 |
|---|---|---|
|
|
@@ -13,7 +13,25 @@ Set the version once, and define the digest helper every step below uses: | |
|
|
||
| ```bash | ||
| export VER=v2.1.0 # the tag you just pushed | ||
| dg() { docker buildx imagetools inspect --raw "$1" 2>/dev/null | sha256sum | awk '{print "sha256:"$1}'; } | ||
|
|
||
| # Resolve a tag's manifest digest. Returns non-zero and prints nothing when the | ||
| # tag does not exist — do NOT pipe inspect straight into sha256sum: on a failed | ||
| # lookup it hashes empty input and returns | ||
| # sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, | ||
| # a real-looking digest. Steps 2-4 would then report a missing image as present, | ||
| # and two missing tags would compare equal and pass. | ||
| dg() { | ||
| local raw | ||
| raw=$(docker buildx imagetools inspect --raw "$1" 2>/dev/null) || return 1 | ||
| [ -n "$raw" ] || return 1 | ||
| printf '%s' "$raw" | sha256sum | awk '{print "sha256:"$1}' | ||
| } | ||
| ``` | ||
|
|
||
| Check the helper itself before trusting it — this must print `MISSING`: | ||
|
|
||
| ```bash | ||
| dg ghcr.io/linagora/openrag:v0.0.0-does-not-exist || echo MISSING | ||
| ``` | ||
|
|
||
| > **Why `--raw | sha256sum` and not `--format '{{.Manifest.Digest}}'`:** buildx | ||
|
|
@@ -61,10 +79,19 @@ gh run view "$RUN_ID" --json jobs \ | |
| **FAIL** on any `skipped` — that is the v2.0.1 bug recurring. A hard gate: | ||
|
|
||
| ```bash | ||
| gh run view "$RUN_ID" --json jobs --jq '[.jobs[] | select(.conclusion != "success")] | length' | ||
| # must print 0 | ||
| bad=$(gh run view "$RUN_ID" --json jobs \ | ||
| --jq '[.jobs[] | select(.conclusion != "success")] | length') || exit 1 | ||
| if [ "$bad" -ne 0 ]; then | ||
| echo "FAIL: $bad job(s) did not conclude success — do not continue" >&2 | ||
| exit 1 | ||
| fi | ||
| echo "OK: every job concluded success" | ||
| ``` | ||
|
|
||
| Written as a gate, not a print: a command that only reports the count still | ||
| exits 0 when the count is non-zero, so a release could continue straight past a | ||
| skipped build job — the very thing this step exists to stop. | ||
|
|
||
| If `verify-tag` failed loudly, the tag is not an ancestor of `origin/main` — | ||
| fix the tag placement, do not rerun. | ||
|
|
||
|
|
@@ -110,7 +137,13 @@ back-filled from a different build. | |
| for pair in "ghcr.io/linagora/openrag linagoraai/openrag" \ | ||
| "ghcr.io/linagora/openrag-admin-ui linagoraai/openrag-admin-ui"; do | ||
| set -- $pair; a=$(dg "$1:$VER"); b=$(dg "$2:$VER") | ||
| [ "$a" = "$b" ] && echo "OK $1 == $2" || echo "MISMATCH $1=$a $2=$b" | ||
| # The -n guards matter: without them two MISSING tags are both empty, compare | ||
| # equal, and print OK. | ||
| if [ -n "$a" ] && [ -n "$b" ] && [ "$a" = "$b" ]; then | ||
| echo "OK $1 == $2" | ||
| else | ||
| echo "MISMATCH $1=${a:-MISSING} $2=${b:-MISSING}" | ||
| fi | ||
| done | ||
| ``` | ||
|
|
||
|
|
@@ -120,12 +153,21 @@ done | |
|
|
||
| Steps 2–4 read metadata. This proves the bytes are actually fetchable. | ||
|
|
||
| `RepoDigests` entries are `repo@sha256:…`, while `dg` returns a bare | ||
| `sha256:…` — strip the repository prefix before comparing, or the two can never | ||
| match literally. | ||
|
|
||
| ```bash | ||
| docker pull "linagoraai/openrag:$VER" | ||
| docker image inspect "linagoraai/openrag:$VER" --format '{{index .RepoDigests 0}}' | ||
| pulled=$(docker image inspect "linagoraai/openrag:$VER" \ | ||
| --format '{{index .RepoDigests 0}}' | cut -d@ -f2) | ||
| registry=$(dg "linagoraai/openrag:$VER") || { echo "FAIL: tag not in registry" >&2; exit 1; } | ||
| [ "$pulled" = "$registry" ] \ | ||
|
Comment on lines
161
to
+165
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. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== file excerpt =="
sed -n '140,175p' .github/RELEASING.md 2>/dev/null || true
echo "== nearby command context =="
git show HEAD:.github/RELEASING.md 2>/dev/null | sed -n '140,175p' || true
echo "== repo docker/digest refs =="
rg -n "docker (pull|image inspect)|RepoDigests|linagoraai/openrag|registry|verify" .github/RELEASING.md README.md 2>/dev/null || trueRepository: linagora/openrag Length of output: 5259 🌐 Web query:
💡 Result: The RepoDigests field in Docker image metadata is a list of content-addressable digests of image manifests from which a local image was referenced [1]. Because this field is a collection of all registry/repository locations from which an image has been pulled or pushed, it can contain multiple entries if the same image ID exists under different repository names or registry paths [2][3]. It is important to understand the following regarding RepoDigests: 1. Order and Indexing: The order of entries in the RepoDigests list is not guaranteed to align with the specific image tag or repository used during a recent pull [2]. Attempting to retrieve a specific entry using index 0 (e.g., {{index.RepoDigests 0}}) is unreliable if multiple digests are present, as the first entry may not correspond to the image name you used to pull the image [2]. 2. Nature of the Field: RepoDigests are attributes of where an image is stored rather than immutable properties of the image itself [3]. They record the source manifest digest at the time of the pull operation [3]. Consequently, the repository portion of the digest string is derived from the registry/repository path used during that pull and may not match the repository name you are currently working with if the image was pulled from or pushed to multiple locations [2][3]. 3. deduplication: Docker has implemented fixes in recent versions to prevent duplicate entries from appearing in the RepoDigests list, ensuring that each unique reference is stored only once [4][5][6]. To reliably work with these values, avoid relying on positional indexing. If you need a specific digest for a particular repository, you should filter or iterate through the list using external tools (such as grep or platform-specific template logic) to match the desired registry path string, rather than assuming the first element is the correct one [2]. Citations:
🌐 Web query:
💡 Result: The Docker CLI Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
text = Path(".github/RELEASING.md").read_text()
lines = text.splitlines()
print("contains ignored pull:", "docker pull" in text and "||" in "\n".join(lines[158:164]))
print("line 161-165:")
for i in range(157, min(166, len(lines))):
print(f"{i+1}: {lines[i]}")
PY
if command -v docker >/dev/null 2>&1; then
echo "== local Docker availability =="
docker version 2>&1 | sed -n '1,20p'
else
echo "== local Docker availability: not installed =="
fiRepository: linagora/openrag Length of output: 661 Fail on Docker pull errors and compare only the matching repository digest.
🤖 Prompt for AI Agents |
||
| && echo "OK: pulled digest matches the registry ($pulled)" \ | ||
| || { echo "FAIL: pulled=$pulled registry=$registry" >&2; exit 1; } | ||
| ``` | ||
|
|
||
| **PASS**: the printed digest equals the Docker Hub digest from step 2. | ||
| **PASS**: `OK`. | ||
|
|
||
| ## 6. The image contains the released code | ||
|
|
||
|
|
@@ -166,14 +208,53 @@ The chart and compose pins are part of the release surface; shipping them | |
| pointing at the previous version is a silent regression for anyone deploying | ||
| from the tag. | ||
|
|
||
| Compare against `$VER` exactly. A filter that merely matches something | ||
| version-shaped is satisfied by a stale pin left at the previous release — which | ||
| is the regression this step is meant to catch. | ||
|
|
||
| ```bash | ||
| fail=0 | ||
| # appVersion must be $VER without its leading v | ||
| want_app=${VER#v} | ||
| got_app=$(git show "$VER:infra/charts/openrag-stack/Chart.yaml" \ | ||
| | awk -F'"' '/^appVersion:/{print $2}') | ||
| [ "$got_app" = "$want_app" ] \ | ||
| && echo "OK appVersion=$got_app" \ | ||
| || { echo "FAIL appVersion=$got_app want=$want_app"; fail=1; } | ||
|
|
||
| # Each OpenRag image in the chart, checked by repository. Do NOT just count | ||
| # version-shaped tags: values.yaml also pins third-party images (vllm, milvus, | ||
| # infinity) whose versions have nothing to do with this release. | ||
| # An empty result means the values layout changed and this check no longer finds | ||
| # the pin — that is a FAIL, not a pass. | ||
| for repo in 'linagora/openrag-ray' 'linagoraai/openrag-admin-ui' 'linagoraai/openrag'; do | ||
| got=$(git show "$VER:infra/charts/openrag-stack/values.yaml" \ | ||
| | grep -A4 "repository: \"$repo\"$" \ | ||
| | awk -F'"' '/^[[:space:]]*tag:/{print $2; exit}') | ||
| [ "$got" = "$VER" ] \ | ||
| && echo "OK $repo -> $got" \ | ||
| || { echo "FAIL $repo -> ${got:-NOT FOUND} (want $VER)"; fail=1; } | ||
| done | ||
|
|
||
| # compose pins (2 expected: openrag, openrag-admin-ui) | ||
| cpins=$(git show "$VER:infra/compose/docker-compose.yaml" \ | ||
| | grep -cE "image: linagoraai/openrag(-admin-ui)?:$VER$") | ||
| [ "$cpins" -eq 2 ] \ | ||
| && echo "OK 2 compose pins at $VER" \ | ||
| || { echo "FAIL $cpins compose pins at $VER (expected 2)"; fail=1; } | ||
|
Comment on lines
+239
to
+244
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. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Validate each compose image exactly.
The count also does not require one Use fixed-string or field-based matching. Require exactly one active pin for each expected repository, and reject extra relevant pins. 🤖 Prompt for AI Agents |
||
|
|
||
| [ "$fail" -eq 0 ] && echo "step 8 PASS" || { echo "step 8 FAIL" >&2; exit 1; } | ||
| ``` | ||
|
|
||
| Chart `version` is bumped independently of `appVersion` (it tracks chart | ||
| changes, not the app release), so check it by eye against the previous release | ||
| rather than against `$VER`: | ||
|
|
||
| ```bash | ||
| git show "$VER:infra/charts/openrag-stack/Chart.yaml" | grep -E '^(version|appVersion)' | ||
| git show "$VER:infra/charts/openrag-stack/values.yaml" | grep -nE 'tag: "v[0-9]' | ||
| git show "$VER:infra/compose/docker-compose.yaml" | grep -nE 'image: linagoraai/' | ||
| git show "$VER:infra/charts/openrag-stack/Chart.yaml" | grep -E '^version:' | ||
| ``` | ||
|
|
||
| **PASS**: every OpenRag image pin reads `$VER`, `appVersion` matches, chart | ||
| `version` was bumped. | ||
| **PASS**: `step 8 PASS`, and chart `version` moved. | ||
|
|
||
| --- | ||
|
|
||
|
|
||
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.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Require all four release jobs before continuing.
This gate checks only the conclusions of returned jobs. If a required job is absent,
badis still0when the remaining jobs succeed. Assert thatverify-tag,build-and-push-image,build-and-push-image-ray, andbuild-and-push-image-admin-uiare present before checking their conclusions.🤖 Prompt for AI Agents