fix(ci): resolve published upgrade baseline by digest
quality / commitlint (push) Skipped
quality / changes (push) Successful in 7s
quality / openapi (push) Successful in 30s
quality / web (push) Successful in 1m1s
quality / api (push) Successful in 1m35s
quality / linux-agent-smoke (push) Failing after 18s
quality / docker-check (push) Failing after 3m28s
CD / quality (push) Failing after 7m1s
CD / publish (push) Skipped
quality / commitlint (push) Skipped
quality / changes (push) Successful in 7s
quality / openapi (push) Successful in 30s
quality / web (push) Successful in 1m1s
quality / api (push) Successful in 1m35s
quality / linux-agent-smoke (push) Failing after 18s
quality / docker-check (push) Failing after 3m28s
CD / quality (push) Failing after 7m1s
CD / publish (push) Skipped
This commit is contained in:
1 parent
a37ffdf061
commit
4db34f94f2
4 files changed
+94
-2
No files matched your search
@@ -375,11 +375,26 @@ jobs:
|
||||
- name: Capture exact built image identity
|
||||
id: built-image
|
||||
run: echo "id=$(docker image inspect --format '{{.Id}}' "$SMOKE_IMAGE")" >> "$GITHUB_OUTPUT"
|
||||
- name: Built image runtime smoke
|
||||
- name: Verify upgrade baseline resolver
|
||||
run: sh scripts/ci/resolve-upgrade-baseline.test.sh
|
||||
- name: Resolve immutable upgrade baseline
|
||||
id: upgrade-baseline
|
||||
env:
|
||||
PREVIOUS_IMAGE: ${{ vars.EVOFW_PREVIOUS_IMAGE }}
|
||||
REQUIRE_UPGRADE: ${{ !inputs.is_pull_request }}
|
||||
FIRST_RELEASE: ${{ vars.EVOFW_FIRST_RELEASE == 'true' }}
|
||||
REPOSITORY_OWNER: ${{ github.repository_owner }}
|
||||
run: |
|
||||
set -eu
|
||||
owner_lc="$(printf '%s' "$REPOSITORY_OWNER" | tr '[:upper:]' '[:lower:]')"
|
||||
export IMAGE_REPOSITORY="git.shx.one/$owner_lc/evofw"
|
||||
baseline="$(sh scripts/ci/resolve-upgrade-baseline.sh)"
|
||||
printf 'image=%s\n' "$baseline" >> "$GITHUB_OUTPUT"
|
||||
- name: Built image runtime smoke
|
||||
env:
|
||||
PREVIOUS_IMAGE: ${{ steps.upgrade-baseline.outputs.image }}
|
||||
REQUIRE_UPGRADE: ${{ !inputs.is_pull_request }}
|
||||
FIRST_RELEASE: ${{ vars.EVOFW_FIRST_RELEASE == 'true' }}
|
||||
SMOKE_ARTIFACT_DIR: runtime-artifacts
|
||||
BUILT_IMAGE: ${{ steps.built-image.outputs.id }}
|
||||
run: sh scripts/ci/image-smoke.sh "$BUILT_IMAGE" "${PREVIOUS_IMAGE:-}"
|
||||
|
||||
@@ -37,7 +37,11 @@ REQUIRE_UPGRADE=true sh scripts/ci/image-smoke.sh evofw-smoke:local \
|
||||
git.shx.one/denozord/evofw@sha256:PREVIOUS_DIGEST
|
||||
```
|
||||
|
||||
Smoke uses disposable volumes and verifies migration/native SQLite startup, `/health`, DB-backed `/ready`, API JSON, SPA deep links, agent script delivery, runtime file placement, clean SIGTERM, restart persistence, an online backup and restoration into another volume. Supply Gitea variable `EVOFW_PREVIOUS_IMAGE` with the last deployed version/digest to exercise upgrade migrations. Every CD quality invocation now builds and runs smoke, including docs-only releases, before semantic-release can publish. `EVOFW_PREVIOUS_IMAGE` must end in an immutable `@sha256:...` digest. Missing or mutable previous images fail release validation. Only an explicitly approved first deployment may set Gitea variable `EVOFW_FIRST_RELEASE=true`; unset it immediately after that first deployment. PR/development smoke can run without upgrade validation and reports NOT RUN. The image healthcheck uses `/ready` with 40s startup grace; Compose allows 30s shutdown and rotates JSON logs (3 x 10MB).
|
||||
Smoke uses disposable volumes and verifies migration/native SQLite startup, `/health`, DB-backed `/ready`, API JSON, SPA deep links, agent script delivery, runtime file placement, clean SIGTERM, restart persistence, an online backup and restoration into another volume. Every CD quality invocation builds and runs smoke, including docs-only releases, before semantic-release can publish. The image healthcheck uses `/ready` with 40s startup grace; Compose allows 30s shutdown and rotates JSON logs (3 x 10MB).
|
||||
|
||||
Upgrade baseline selection is explicit and fail-closed. A configured Gitea variable `EVOFW_PREVIOUS_IMAGE` takes precedence and must be an immutable `repository@sha256:...` reference; use the actually deployed digest when that is the intended rehearsal baseline. Mutable overrides fail rather than silently selecting another image. When the variable is unset for a release run, `scripts/ci/resolve-upgrade-baseline.sh` pulls `git.shx.one/<lowercase-owner>/evofw:latest`, requires exactly one matching repository digest from Docker's `RepoDigests`, and passes that immutable reference to the unchanged smoke guard. This is the **previously published image**, which may differ from production. A pull failure, missing/unrelated/ambiguous digest or invalid reference fails CI; it never enables a first-release exception automatically. Registry access uses the existing anonymous/public pull path and does not expose registry secrets to PRs.
|
||||
|
||||
Only an explicitly approved first deployment may set `EVOFW_FIRST_RELEASE=true`; unset it immediately afterward. Without an explicit baseline, PR runs do not pull a published image and can perform development smoke with upgrade validation reported NOT RUN. A pinned override is honored for PRs and for explicitly configured first-release runs. Local shell syntax and the fake-Docker resolver regression passed, covering override priority, latest-to-digest resolution, pull failure, wrong/ambiguous repository digests, mutable override rejection, PR behavior and explicit first release. The real upgrade still requires CI execution; no new native pass is inferred from these checks.
|
||||
|
||||
## SQLite backup and recovery
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env sh
|
||||
# Stdout is one immutable image reference (or empty for PR/explicit first release).
|
||||
# A published baseline is not necessarily the operator's deployed production image.
|
||||
set -eu
|
||||
previous="${PREVIOUS_IMAGE:-}"
|
||||
if [ -n "$previous" ]; then
|
||||
printf '%s\n' "$previous" | grep -Eq '^[A-Za-z0-9][A-Za-z0-9._:/-]*@sha256:[0-9a-f]{64}$' || { echo 'EVOFW_PREVIOUS_IMAGE must be an immutable repository@sha256 digest reference' >&2; exit 1; }
|
||||
printf '%s\n' "$previous"
|
||||
exit 0
|
||||
fi
|
||||
if [ "${REQUIRE_UPGRADE:-false}" != true ] || [ "${FIRST_RELEASE:-false}" = true ]; then exit 0; fi
|
||||
repository="${IMAGE_REPOSITORY:?IMAGE_REPOSITORY required for published upgrade baseline}"
|
||||
printf '%s\n' "$repository" | grep -Eq '^[a-z0-9][a-z0-9._:/-]*$' || { echo 'Invalid published image repository' >&2; exit 1; }
|
||||
echo 'Resolving the previously published latest image as upgrade baseline; set EVOFW_PREVIOUS_IMAGE to test an explicitly deployed digest instead' >&2
|
||||
if ! docker pull "$repository:latest" >&2; then
|
||||
echo 'Cannot pull published upgrade baseline. Supply EVOFW_PREVIOUS_IMAGE, or explicitly configure the approved first-release exception; no automatic exception is applied.' >&2
|
||||
exit 1
|
||||
fi
|
||||
digests="$(docker image inspect --format '{{range .RepoDigests}}{{println .}}{{end}}' "$repository:latest")"
|
||||
matches="$(printf '%s\n' "$digests" | awk -v prefix="$repository@" 'index($0,prefix)==1 {print}')"
|
||||
count="$(printf '%s\n' "$matches" | awk 'NF {n++} END {print n+0}')"
|
||||
[ "$count" = 1 ] || { echo 'Published image must have exactly one matching repository digest; refusing absent, unrelated or ambiguous RepoDigests' >&2; exit 1; }
|
||||
digest="${matches#"$repository@"}"
|
||||
printf '%s\n' "$digest" | grep -Eq '^sha256:[0-9a-f]{64}$' || { echo 'Published image has an invalid repository digest' >&2; exit 1; }
|
||||
printf '%s\n' "$repository@$digest"
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env sh
|
||||
# No registry/network access: exercise the actual resolver with a fake Docker CLI.
|
||||
set -eu
|
||||
resolver="$(pwd)/scripts/ci/resolve-upgrade-baseline.sh"
|
||||
tmp="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp"' EXIT HUP INT TERM
|
||||
mkdir "$tmp/bin"
|
||||
cat >"$tmp/bin/docker" <<'DOCKER'
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
printf '%s\n' "$*" >>"$FAKE_LOG"
|
||||
case "$1" in
|
||||
pull) exit "$FAKE_PULL_EXIT" ;;
|
||||
image) printf '%s\n' "$FAKE_DIGESTS" ;;
|
||||
*) exit 98 ;;
|
||||
esac
|
||||
DOCKER
|
||||
chmod +x "$tmp/bin/docker"
|
||||
export PATH="$tmp/bin:$PATH" FAKE_LOG="$tmp/docker.log" FAKE_PULL_EXIT=0
|
||||
export IMAGE_REPOSITORY=git.shx.one/team/evofw REQUIRE_UPGRADE=true FIRST_RELEASE=false
|
||||
digest="sha256:$(printf '%064d' 0)"
|
||||
export FAKE_DIGESTS="$IMAGE_REPOSITORY@$digest" PREVIOUS_IMAGE="another.registry/deployed/app@$digest"
|
||||
sh "$resolver" >"$tmp/result"
|
||||
[ "$(cat "$tmp/result")" = "$PREVIOUS_IMAGE" ]
|
||||
[ ! -s "$FAKE_LOG" ] || { echo 'Explicit pinned override must not resolve latest' >&2; exit 1; }
|
||||
export PREVIOUS_IMAGE=''
|
||||
sh "$resolver" >"$tmp/result"
|
||||
[ "$(cat "$tmp/result")" = "$IMAGE_REPOSITORY@$digest" ]
|
||||
grep -F "pull $IMAGE_REPOSITORY:latest" "$FAKE_LOG" >/dev/null
|
||||
grep -F " $IMAGE_REPOSITORY:latest" "$FAKE_LOG" >/dev/null
|
||||
export FAKE_PULL_EXIT=27
|
||||
if sh "$resolver" >"$tmp/result" 2>/dev/null; then echo 'Missing published image must fail' >&2; exit 1; fi
|
||||
[ ! -s "$tmp/result" ]
|
||||
export FAKE_PULL_EXIT=0 FAKE_DIGESTS="wrong.registry/team/evofw@$digest"
|
||||
if sh "$resolver" >"$tmp/result" 2>/dev/null; then echo 'Unrelated RepoDigest must fail' >&2; exit 1; fi
|
||||
export FAKE_DIGESTS="$IMAGE_REPOSITORY@$digest
|
||||
$IMAGE_REPOSITORY@sha256:$(printf '%064d' 1)"
|
||||
if sh "$resolver" >"$tmp/result" 2>/dev/null; then echo 'Ambiguous RepoDigests must fail' >&2; exit 1; fi
|
||||
export PREVIOUS_IMAGE="$IMAGE_REPOSITORY:latest"
|
||||
if sh "$resolver" >"$tmp/result" 2>/dev/null; then echo 'Mutable override must fail rather than fall back' >&2; exit 1; fi
|
||||
export PREVIOUS_IMAGE='' REQUIRE_UPGRADE=false
|
||||
: >"$FAKE_LOG"
|
||||
sh "$resolver" >"$tmp/result"
|
||||
[ ! -s "$tmp/result" ] && [ ! -s "$FAKE_LOG" ]
|
||||
export REQUIRE_UPGRADE=true FIRST_RELEASE=true
|
||||
sh "$resolver" >"$tmp/result"
|
||||
[ ! -s "$tmp/result" ] && [ ! -s "$FAKE_LOG" ]
|
||||
echo 'Upgrade baseline resolver passed (override, latest digest, failure, wrong/ambiguous repo, PR and explicit first release)'
|
||||
Reference in new issue
Block a user