From 22cd8b20fc87fc9926d253429b24f0bd0cd3d634 Mon Sep 17 00:00:00 2001 From: Korenevskiy Denis Date: Tue, 4 Aug 2026 12:04:46 +0200 Subject: [PATCH 1/3] fix(merge-beta): keep bare branch name for the self-ref rewrite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When no local source branch exists, merge-beta.sh reassigned SOURCE_BRANCH to "origin/", so the self-ref rewrite sed searched for the non-existent tag "@origin/v4-beta" and silently rewrote nothing — leaving @v4-beta milaboratory/github-ci self-refs on the target branch. This bites any local `merge-beta.sh` run (CI happens to have a local v4-beta branch). Split the mergeable ref (new SOURCE_REF, may be origin/) from the bare branch name (SOURCE_BRANCH) that the `@` rewrite depends on. Found while promoting v4-beta -> v4 for MILAB-6707. --- merge-beta.sh | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/merge-beta.sh b/merge-beta.sh index 8906807a..36a03696 100755 --- a/merge-beta.sh +++ b/merge-beta.sh @@ -56,17 +56,26 @@ else git checkout -b "${MERGE_BRANCH}" fi +# Keep two distinct values: +# SOURCE_BRANCH — the bare branch NAME (e.g. "v4-beta"), used verbatim in the +# `@${SOURCE_BRANCH}` self-ref rewrite below. It must never be +# prefixed with "origin/", or the sed searches for the +# non-existent tag "@origin/v4-beta" and silently rewrites +# nothing (leaving @v4-beta self-refs on the target branch). +# SOURCE_REF — the REF to merge from. When no local branch exists we merge +# the remote-tracking ref "origin/${SOURCE_BRANCH}". if git branch | grep -qE " ${SOURCE_BRANCH}( |$)"; then echo "Found source branch in local repository, syncing it with remote..." git fetch origin "${SOURCE_BRANCH}:${SOURCE_BRANCH}" || true + SOURCE_REF="${SOURCE_BRANCH}" else echo "No source branch found in local repository, using remote..." - SOURCE_BRANCH="origin/${SOURCE_BRANCH}" + SOURCE_REF="origin/${SOURCE_BRANCH}" fi git merge \ - --message "Merge ${SOURCE_BRANCH} into ${TARGET_BRANCH}" \ - "${SOURCE_BRANCH}" \ + --message "Merge ${SOURCE_REF} into ${TARGET_BRANCH}" \ + "${SOURCE_REF}" \ --strategy-option theirs # Replace @v4-beta -> @v4 in milaboratory/github-ci self-refs only. From d6207bd17dfb258b81836545470a26898d7528bf Mon Sep 17 00:00:00 2001 From: xnacly <47723417+xnacly@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:23:38 +0200 Subject: [PATCH 2/3] MILAB-6714: verify a block's docker images exist before publishing A block's .sw.json descriptors carry the image tag the backend pulls at runtime, and the build writes them whether or not the push happened. A misconfigured package (a stray "private": true gates pl-pkg auto-push) therefore produced a green build and a block that 404s on first run, with nothing in CI saying so. New composite action resolves every docker tag in the repo's own descriptors against the registry and blocks publication when one is missing. It checks the pull address recorded in the descriptor, not the push alias, so it also covers any CDN mapping in front of the registry. node_modules is excluded: dependency images are not this block's to guarantee. Opt out per block with verify-docker-images: false. --- .github/workflows/node-simple-pnpm.yaml | 19 ++++ .../monorepo/verify-docker-images/action.yaml | 89 +++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 blocks/monorepo/verify-docker-images/action.yaml diff --git a/.github/workflows/node-simple-pnpm.yaml b/.github/workflows/node-simple-pnpm.yaml index 46bc96ae..3e99e4f1 100644 --- a/.github/workflows/node-simple-pnpm.yaml +++ b/.github/workflows/node-simple-pnpm.yaml @@ -394,6 +394,16 @@ on: required: false default: true + verify-docker-images: + description: | + Before publishing, check that every docker image referenced by the + block's entrypoint descriptors is present in the registry. + Guards against publishing a block whose images were built but never + pushed, which only surfaces as a runtime 404. + type: boolean + required: false + default: true + notify-slack: description: | Enable Slack notifications @@ -1074,6 +1084,15 @@ jobs: test-coverage-reports: ${{ inputs.test-coverage-reports }} test-results-reports: ${{ inputs.test-results-reports }} + # Gate publication on the images actually being in the registry. Runs on the + # publish path only: on PRs the descriptors are not shipped anywhere, and a + # branch build may legitimately not push. + - name: Verify referenced docker images exist + if: github.ref_name == 'main' + && steps.check-changes.outputs.has-changes == '0' + && inputs.verify-docker-images + uses: milaboratory/github-ci/blocks/monorepo/verify-docker-images@v4-beta + - name: Perform security scan checks before publication uses: milaboratory/github-ci/actions/docker/scan-pnpm-repo@v4-beta diff --git a/blocks/monorepo/verify-docker-images/action.yaml b/blocks/monorepo/verify-docker-images/action.yaml new file mode 100644 index 00000000..a06b05f3 --- /dev/null +++ b/blocks/monorepo/verify-docker-images/action.yaml @@ -0,0 +1,89 @@ +name: Verify referenced docker images exist +author: 'MiLaboratories' +description: | + Check that every docker image referenced by a block's built entrypoint + descriptors is actually present in the registry. + + A block's `.sw.json` descriptors carry the image tag the backend will pull at + runtime. The build writes those descriptors whether or not the push happened, + so a misconfigured package (historically a stray `"private": true`, which + gates pl-pkg auto-push) yields a green build and a block that 404s on first + run. This step closes that gap before publication. + + The tag in the descriptor is the PULL address, which may differ from the push + alias (PL_DOCKER_REGISTRY_PUSH_TO). Verifying the pull address is deliberate: + it is what the backend resolves, so it covers the push and any CDN mapping in + front of the registry. + +inputs: + fail-on-missing: + description: | + Fail the step when a referenced image is missing. + Set to 'false' to report without blocking. + required: false + default: 'true' + +runs: + using: "composite" + + steps: + - name: Verify referenced docker images exist + env: + FAIL_ON_MISSING: ${{ inputs.fail-on-missing }} + shell: bash + run: | + set -euo pipefail + + # Repo-owned descriptors only. node_modules holds descriptors belonging to + # published dependencies (SDK runenvs); their images are not this block's + # to guarantee, and failing on them would block a release on upstream state. + mapfile -t descriptors < <(find . -name node_modules -prune -o -name '*.sw.json' -print | sort) + + if [ ${#descriptors[@]} -eq 0 ]; then + echo "No .sw.json descriptors found. Nothing to verify." + exit 0 + fi + echo "Scanning ${#descriptors[@]} entrypoint descriptor(s)." + + mapfile -t tags < <(jq -r 'select(.docker != null and .docker.tag != null) | .docker.tag' "${descriptors[@]}" | sort -u) + + if [ ${#tags[@]} -eq 0 ]; then + echo "No docker-backed entrypoints. Nothing to verify." + exit 0 + fi + + missing=() + for tag in "${tags[@]}"; do + if docker manifest inspect "${tag}" >/dev/null 2>&1; then + echo " ok ${tag}" + else + echo " MISSING ${tag}" + missing+=( "${tag}" ) + fi + done + + echo "Checked ${#tags[@]} image(s), ${#missing[@]} missing." + if [ ${#missing[@]} -eq 0 ]; then + exit 0 + fi + + { + echo "### Referenced docker images missing from the registry" + echo + echo "The build wrote entrypoint descriptors pointing at images that were never pushed." + echo "A block published in this state fails at runtime when the backend pulls them." + echo + for tag in "${missing[@]}"; do echo "- \`${tag}\`"; done + echo + echo "Most common cause: \`\"private\": true\` in the software package.json." + echo "pl-pkg gates docker auto-push on \`!isPrivate\`, so the image is built and" + echo "referenced but never uploaded. Software packages must not be private." + } >> "${GITHUB_STEP_SUMMARY}" + + if [ "${FAIL_ON_MISSING}" != "true" ]; then + echo "::warning::${#missing[@]} referenced docker image(s) missing from the registry" + exit 0 + fi + + echo "::error::${#missing[@]} referenced docker image(s) missing from the registry" + exit 1 From a4e774150875010bf643835bdd6039bb7075d67c Mon Sep 17 00:00:00 2001 From: xnacly <47723417+xnacly@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:23:38 +0200 Subject: [PATCH 3/3] MILAB-6714: probe for an available registry inspect command docker manifest inspect has no precedent in this repo, and the check gates every block publish. Fall back to docker buildx imagetools inspect, and fail loudly if neither exists rather than silently treating every image as missing. --- blocks/monorepo/verify-docker-images/action.yaml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/blocks/monorepo/verify-docker-images/action.yaml b/blocks/monorepo/verify-docker-images/action.yaml index a06b05f3..05ff2f1f 100644 --- a/blocks/monorepo/verify-docker-images/action.yaml +++ b/blocks/monorepo/verify-docker-images/action.yaml @@ -52,9 +52,21 @@ runs: exit 0 fi + # Resolve the inspect command once. Both talk to the registry directly and + # reuse the docker logins this workflow already performed; which one exists + # depends on the runner's docker CLI. + if docker manifest inspect --help >/dev/null 2>&1; then + image_exists() { docker manifest inspect "${1}" >/dev/null 2>&1; } + elif docker buildx imagetools inspect --help >/dev/null 2>&1; then + image_exists() { docker buildx imagetools inspect "${1}" >/dev/null 2>&1; } + else + echo "::error::no registry inspect command available (tried 'docker manifest inspect' and 'docker buildx imagetools inspect')" + exit 1 + fi + missing=() for tag in "${tags[@]}"; do - if docker manifest inspect "${tag}" >/dev/null 2>&1; then + if image_exists "${tag}"; then echo " ok ${tag}" else echo " MISSING ${tag}"