Skip to content

ci: add gate, preflight and verification to testing image release - #718

Open
nvasiu wants to merge 2 commits into
mainfrom
gate-ecr-release
Open

nvasiu wants to merge 2 commits into
mainfrom
gate-ecr-release

Conversation

@nvasiu

@nvasiu nvasiu commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Issue #, if available:

Related to #716

Description of changes:

The ecr-release.yml action would previously run for every monorepo release, regardless if the release included a new version for the testing package or not. So it was possible for this action to publish the latest changes of the testing package before they were released. This PR updates the action to be safer.

.github/workflows/ecr-release.yml

  • Add a preflight job to parse the release tag, verify the tag matches the source, check if the version already exists in public ECR, and emit a plan.
  • Gates the build on the above preflight checks.
  • Add a post-publish verification that polls public ECR (polls 10 times with 15s wait = 150s total) and confirms that the new version was published.

.github/scripts/parse_testing_version.py

  • Separate script for parsing the testing version from a release tag.

.github/scripts/tests/test_parse_testing_version.py

  • Unit testing for the parsing script.

.github/workflows/test-parser.yml

  • Wire the new script and test into the script-test workflow.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@nvasiu
nvasiu deployed to ai-pr-review-runtime September 10, 2026 23:21 — with GitHub Actions Active
@nvasiu
nvasiu deployed to ai-pr-review-runtime September 10, 2026 23:29 — with GitHub Actions Active
Comment thread .github/workflows/ecr-release.yml Outdated
@github-actions

This comment has been minimized.

@nvasiu nvasiu changed the title ci: gate testing image publish on testing release ci: add gate, preflight and verification to testing image release Sep 11, 2026
@nvasiu
nvasiu deployed to ai-pr-review-runtime September 11, 2026 18:35 — with GitHub Actions Active
Comment thread .github/workflows/ecr-release.yml Outdated
Comment thread .github/workflows/ecr-release.yml Outdated
Comment thread .github/workflows/ecr-release.yml Outdated
Comment thread .github/scripts/parse_testing_version.py Outdated
@github-actions

This comment has been minimized.

@yaythomas yaythomas left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The preflight and verification confirm that an image tag exists. They do not confirm the image contains what the release names. The Dockerfile installs aws-durable-execution-sdk-python>=1.0.0, resolved at build time and unbounded, so two builds of one testing version can produce different images, and skip-if-exists assumes a version identifies one artifact.

(The other three points are on the relevant lines.)

Comment thread .github/workflows/ecr-release.yml Outdated
Comment thread .github/scripts/parse_testing_version.py Outdated
Comment thread .github/workflows/ecr-release.yml Outdated
@nvasiu
nvasiu deployed to ai-pr-review-runtime September 11, 2026 22:48 — with GitHub Actions Active
Comment thread .github/workflows/ecr-release.yml
Comment thread .github/workflows/ecr-release.yml Outdated
Comment thread .github/workflows/ecr-release.yml Outdated
Comment thread .github/workflows/ecr-release.yml Outdated
@github-actions

This comment has been minimized.

@nvasiu
nvasiu deployed to ai-pr-review-runtime September 14, 2026 20:51 — with GitHub Actions Active
Comment thread .github/workflows/ecr-release.yml Outdated
@github-actions

This comment has been minimized.

@nvasiu

nvasiu commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

@yaythomas
Re: #718 (review)

Yes, the preflight confirms that a tag exists, but it's not guaranteed that the image matches the release. And skip-if-exists assumes a version maps to one image, which is not true while we have an unbounded SDK dependency.

But this workflow's current policy is that if a version is already published to ECR, we won't ever overwrite it. So it shouldn't ever be a concern that the user will get different SDK versions from the same image version.

But for the sake of reproducing the image on the ECR in the future / visiblity into what SDK version the image is using, we could:

  • Pin the SDK dependency in the Dockerfile.
  • Or label images with which SDK version they are using (doesn't prevent different images per version, but lets us detect them).

I think either of these option would need some more discussion, and are out of scope for this PR. But if we want to implement either of those, they would be compatible with this PR.

@nvasiu
nvasiu deployed to ai-pr-review-runtime September 18, 2026 20:31 — with GitHub Actions Active
Comment thread .github/workflows/ecr-release.yml Outdated
Comment thread .github/scripts/latest_testing_version.py Outdated
Comment thread .github/workflows/ecr-release.yml
@github-actions

This comment has been minimized.

@nvasiu
nvasiu deployed to ai-pr-review-runtime September 21, 2026 20:35 — with GitHub Actions Active
Comment thread .github/workflows/ecr-release.yml
Comment thread .github/workflows/ecr-release.yml Outdated
Comment thread .github/scripts/latest_testing_version.py Outdated
@github-actions

This comment has been minimized.

@nvasiu
nvasiu deployed to ai-pr-review-runtime September 25, 2026 21:51 — with GitHub Actions Active
docker manifest create "$base:v$VERSION" "$image_x86_64" "$image_arm64"
docker manifest annotate "$base:v$VERSION" "$image_arm64" --arch arm64 --os linux
docker manifest annotate "$base:v$VERSION" "$image_x86_64" --arch amd64 --os linux
docker manifest push "$base:v$VERSION"

This comment was marked as outdated.

# A lookup that fails (rather than returns nothing) must not fall
# through to an unconditional push: that would let a transient error
# downgrade latest. Distinguish absent from failed and fail closed.
if latest_digest="$(aws ecr-public describe-images \

This comment was marked as outdated.

if: always() && needs.create-ecr-manifest-per-arch.result == 'success'
# Serialize the latest update so two releases do not race it.
concurrency:
group: ecr-release-latest

This comment was marked as outdated.

Comment thread .github/scripts/current_latest_version.py Outdated
Comment thread .github/scripts/tests/test_current_latest_version.py Outdated
@github-actions

This comment has been minimized.

@nvasiu
nvasiu deployed to ai-pr-review-runtime September 25, 2026 22:48 — with GitHub Actions Active
# scheme change or a hand-pushed tag). We cannot prove we are newer,
# so refuse to overwrite latest and leave it for manual review.
current="$(python .github/scripts/current_latest_version.py $latest_tags)"
if [[ -z "$current" ]]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex AI review · Finding arf_v1_c6j4c4462phnp2lrkscrhlm65q

[P1] This rejects the registry state produced by the workflow being replaced. Previously, the version manifest used x86_64→arm64 order while latest used arm64→x86_64, so their order-sensitive OCI digests differ and the latest digest has no vX.Y.Z tag. The first testing release after merge therefore exits here and reruns remain stuck. Bootstrap by matching legacy child-platform digests and retagging the exact version manifest, or migrate latest before enabling this guard.

docker manifest create "$base:v$VERSION" "$image_x86_64" "$image_arm64"
docker manifest annotate "$base:v$VERSION" "$image_arm64" --arch arm64 --os linux
docker manifest annotate "$base:v$VERSION" "$image_x86_64" --arch amd64 --os linux
docker manifest push "$base:v$VERSION"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex AI review · Finding arf_v1_pvjgcb3ats4msus2hcvg553pvr

[P1] The earlier preflight check is not a publication lock. Concurrent deliveries for the same testing version can both build through mutable per-architecture tags and reach this push, silently replacing an already-published release or creating a mixed manifest. Use run-scoped architecture tags, then acquire a per-version lock and recheck existence before publishing the immutable version tag.

# A lookup that fails (rather than returns nothing) must not fall
# through to an unconditional push: that would let a transient error
# downgrade latest. Distinguish absent from failed and fail closed.
if latest_digest="$(aws ecr-public describe-images \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex AI review · Finding arf_v1_otspk2nucjnfic2v5zdpbwiemx

[P1] Serializing jobs does not make this ECR Public read fresh. After v3 updates latest, an older v2 job can observe stale v1 state, overwrite latest, and then pass verification against another stale read. Use a strongly consistent compare-and-swap high-water mark or a single reconciler that cannot publish a version below the recorded maximum.

Comment on lines +267 to +269
concurrency:
group: ecr-release-latest
cancel-in-progress: false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex AI review · Finding arf_v1_hinp33mqfxeksg2dtq6ovnhvhu

[P1] GitHub concurrency retains only one pending job; a newer arrival replaces the existing pending updater even with cancel-in-progress: false. If v3 is running, v4 is pending, and v2 arrives, v4 is canceled and v2 subsequently no-ops against v3, leaving latest behind. Use a queue that preserves every update, or make every surviving job reconcile the highest published immutable version.

@github-actions

Copy link
Copy Markdown
Contributor

Codex AI review

Four P1 issues remain in the ECR release state machine. Parser tests do not cover the affected legacy and concurrent registry transitions.

Reviewed commit 3f48987c4ff9c0455773c53a1af5c399f1d7d76f. Workflow run

@nvasiu

nvasiu commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

@yaythomas
Re: Codex comments on concurrency

What this workflow currently does:

  • On a new testing release:
  • Parse the tag and confirm it matches the source
  • Build and publish the per-arch images
  • Create the version manifest
  • Advance latest if the published version is newer

The above Codex comments describe problems that can only happen if multiple releases run at the same time (runs overwriting the version manifest, an older release updating latest and GitHub concurrency lock dropping a queued release). A real fix would require some real lock with external infra, which is out of scope for this PR.

But in practice we release one version at a time, and can cancel concurrent workflows if needed. And this workflow will self correct on the next release if an error does happen. I think we can go ahead with this PR in order to block our SAM issues that are dependent on an emulator release, and leave the full lock solution for later. Thoughts?

@yaythomas yaythomas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you @nvasiu!

I checked the Codex finding about legacy manifest order (line 358) against public ECR. latest and v1.2.1 currently share one digest, so the first release after merge will not get stuck.

On concurrency, agree that a real lock is out of scope. One cheap change would close most of the gap, though (inline on line 267).

One comment on a file outside this PR: could we add a note to RELEASING.md that only a testing-v tag publishes the emulator image? An sdk-v tag with a testing version bump publishes the package to PyPI but builds no image.

# A release-tag component naming the testing package: testing-v<x.y.z> with an
# optional pre-release suffix. Anchored and matched against a single comma-split
# component so prefixes like "mytesting-v1.2.1" are rejected.
_COMPONENT = re.compile(r"testing-v([0-9]+\.[0-9]+\.[0-9]+[0-9A-Za-z.-]*)\Z")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The two scripts accept different tag grammars. parse_testing_version.py accepts testing-v1.2.3.4 and testing-v1.2.3-x86_64. current_latest_version.py rejects both.

  • A v1.2.3.4 release would move latest to a tag that current_latest_version.py cannot read. Every later release would then refuse to update latest.
  • A testing-v1.2.3-x86_64 tag would publish a version tag with the same name as an existing per-architecture image tag.

Please could both scripts share one regex, and could you add a testing-v1.2.3.4 test case?

needs: [preflight, create-ecr-manifest-per-arch]
if: always() && needs.create-ecr-manifest-per-arch.result == 'success'
# Serialize the latest update so two releases do not race it.
concurrency:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The concurrency group runs one latest update at a time. GitHub still drops a waiting job when another one arrives. Example: v3 is running, v4 is waiting, and a v2 backport arrives. GitHub cancels v4. v2 then does nothing, because latest already points at v3. So latest stays behind v4.

Please could this job point latest at the highest vX.Y.Z tag in ECR, instead of checking whether this run's version is newer? Then any job that runs repairs latest. A short comment here would help too: the group runs updates one at a time but does not keep every waiting job, and ECR reads can be stale.

exit 0
fi

docker manifest create "$base" "$base:v$VERSION-x86_64" "$base:v$VERSION-arm64"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

latest is rebuilt from the per-architecture tags, and those tags can be overwritten. Copying the published index would make latest byte-for-byte identical to the version tag. The version lookup through the latest digest depends on that:

docker buildx imagetools create --tag "$base:latest" "$base:v$VERSION"

ECR_REGISTRY: ${{ steps.login-ecr-public.outputs.registry }}
ECR_REPOSITORY: ${{ env.ecr_repository_name }}
run: |
python -m pip install --upgrade packaging

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please could packaging be pinned, like hatch==1.16.5 at line 160? These jobs hold ECR push credentials. (Same at line 405.)

fi
fi

if [[ -n "$current" ]] && ! python -c "import sys; from packaging.version import Version; sys.exit(0 if Version('$VERSION') > Version('${current#v}') else 1)"; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should a pre-release such as 2.1.0rc1 move latest? If not, we could skip the update when Version(VERSION).is_prerelease is true.

@@ -89,46 +197,252 @@ jobs:
create-ecr-manifest-per-arch:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This job runs checkout and setup-python, but its script only uses aws and docker. Can both steps be dropped?

env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
tag_version="$(python .github/scripts/parse_testing_version.py)"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The preflight job calls python without setup-python. The other jobs pin 3.13. Could we add it here to match?

from parse_testing_version import parse_testing_version


def test_parse_testing_version():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could this use @pytest.mark.parametrize, like test_current_latest_version.py? The _run_standalone runner in that file can also go, since test-parser.yml runs pytest.

This branch was successfully deployed

1 active deployment
ai-pr-review-runtime — 3f48987c Deployed Sep 25, 2026 by nvasiu via ai-pr-review / Codex review / Generate Codex review #1095
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