Skip to content

Commit 44d8ed0

Browse files
committed
ci: add preflight and verification to testing image release
1 parent a7ee0a9 commit 44d8ed0

4 files changed

Lines changed: 230 additions & 12 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import re
5+
6+
# Matches a testing-v<x.y.z> entry in the release tag, stopping at a comma
7+
# so combined tags like "sdk-v2.0.0,testing-v2.0.0" resolve to the testing part.
8+
_PATTERN = re.compile(r"testing-v[0-9]+\.[0-9]+\.[0-9]+([^,]*)")
9+
10+
11+
def parse_testing_version(release_tag: str) -> str:
12+
"""Return the testing version named by the release tag, or empty if none."""
13+
match = _PATTERN.search(release_tag)
14+
if not match:
15+
return ""
16+
return match.group(0).removeprefix("testing-v")
17+
18+
19+
def main():
20+
release_tag = os.environ.get("RELEASE_TAG", "")
21+
tag_version = parse_testing_version(release_tag)
22+
23+
github_output = os.environ.get("GITHUB_OUTPUT")
24+
if github_output:
25+
with open(github_output, "a", encoding="utf-8") as f:
26+
f.write(f"tag_version={tag_version}\n")
27+
28+
print(tag_version)
29+
30+
31+
if __name__ == "__main__":
32+
main()
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import sys
5+
6+
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
7+
8+
from parse_testing_version import parse_testing_version
9+
10+
11+
def test_parse_testing_version():
12+
test_cases = [
13+
# Testing-only tag enables the job
14+
("testing-v2.0.0", "2.0.0"),
15+
("testing-v1.2.1", "1.2.1"),
16+
# Combined comma-separated tags resolve to the testing version
17+
("sdk-v2.0.0,testing-v2.0.0", "2.0.0"),
18+
("testing-v2.0.0,sdk-v2.1.0", "2.0.0"),
19+
("otel-v1.0.0,testing-v1.2.1,sdk-v2.0.0", "1.2.1"),
20+
# SDK-only or OTel-only tags do not enable the job
21+
("sdk-v2.1.0", ""),
22+
("otel-v1.0.0", ""),
23+
("sdk-v2.0.0,otel-v1.0.0", ""),
24+
# No release tag
25+
("", ""),
26+
("v2.0.0", ""),
27+
("random-text", ""),
28+
# Pre-release suffix is kept up to the comma boundary
29+
("testing-v2.0.0rc1", "2.0.0rc1"),
30+
("testing-v2.0.0-beta,sdk-v1.0.0", "2.0.0-beta"),
31+
]
32+
33+
for input_text, expected in test_cases:
34+
result = parse_testing_version(input_text)
35+
# Assert is expected in test functions
36+
assert result == expected, ( # noqa: S101
37+
f"Expected '{expected}' but got '{result}' for input: {input_text}"
38+
)
39+
40+
41+
if __name__ == "__main__":
42+
test_parse_testing_version()
43+
sys.exit(0)

.github/workflows/ecr-release.yml

Lines changed: 151 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,106 @@ env:
1313
ecr_repository_name: durable-functions/aws-durable-execution-emulator
1414

1515
jobs:
16+
preflight:
17+
# Decide whether to publish before building anything.
18+
runs-on: ubuntu-latest
19+
permissions:
20+
contents: read
21+
id-token: write # ECR Public reads require an assumed role
22+
outputs:
23+
should_publish: ${{ steps.plan.outputs.should_publish }}
24+
version: ${{ steps.plan.outputs.version }}
25+
steps:
26+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
27+
with:
28+
ref: ${{ github.event.release.tag_name }}
29+
30+
- name: Parse testing version from the release tag
31+
id: tag
32+
shell: bash
33+
env:
34+
RELEASE_TAG: ${{ github.event.release.tag_name }}
35+
run: |
36+
tag_version="$(python .github/scripts/parse_testing_version.py)"
37+
if [[ -z "$tag_version" ]]; then
38+
echo "Release tag does not name the testing package. Nothing to publish."
39+
else
40+
echo "tag names testing version: $tag_version"
41+
fi
42+
43+
- name: Verify the tag version matches the source
44+
id: verify
45+
if: steps.tag.outputs.tag_version != ''
46+
shell: bash
47+
env:
48+
TAG_VERSION: ${{ steps.tag.outputs.tag_version }}
49+
ABOUT_PATH: ${{ env.package_path }}/src/aws_durable_execution_sdk_python_testing/__about__.py
50+
run: |
51+
source_version="$(grep "^__version__" "$ABOUT_PATH" | cut -d'"' -f2)"
52+
echo "source version: $source_version"
53+
if [[ "$TAG_VERSION" != "$source_version" ]]; then
54+
echo "::error::Release tag names testing-v$TAG_VERSION but __about__.py is $source_version. Aborting before any publish."
55+
exit 1
56+
fi
57+
echo "version=$source_version" >> "$GITHUB_OUTPUT"
58+
59+
- name: Configure AWS Credentials
60+
if: steps.tag.outputs.tag_version != ''
61+
uses: aws-actions/configure-aws-credentials@cbe3b392738ccf3f987d68400dafcf4b0624a56c # v6.2.4
62+
with:
63+
role-to-assume: ${{ secrets.ECR_UPLOAD_IAM_ROLE_ARN }}
64+
aws-region: ${{ env.aws_region }}
65+
66+
- name: Check whether the image tag already exists
67+
id: exists
68+
if: steps.tag.outputs.tag_version != ''
69+
shell: bash
70+
env:
71+
VERSION: ${{ steps.verify.outputs.version }}
72+
ECR_REPOSITORY: ${{ env.ecr_repository_name }}
73+
run: |
74+
repo_name="${ECR_REPOSITORY##*/}"
75+
if aws ecr-public describe-images \
76+
--region "${{ env.aws_region }}" \
77+
--repository-name "$repo_name" \
78+
--image-ids imageTag="v${VERSION}" >/dev/null 2>&1; then
79+
echo "exists=true" >> "$GITHUB_OUTPUT"
80+
else
81+
echo "exists=false" >> "$GITHUB_OUTPUT"
82+
fi
83+
84+
- name: Emit release plan
85+
id: plan
86+
shell: bash
87+
env:
88+
TAG_VERSION: ${{ steps.tag.outputs.tag_version }}
89+
VERSION: ${{ steps.verify.outputs.version }}
90+
EXISTS: ${{ steps.exists.outputs.exists }}
91+
run: |
92+
echo "## Emulator image release plan" >> "$GITHUB_STEP_SUMMARY"
93+
94+
if [[ -z "$TAG_VERSION" ]]; then
95+
echo "- decision: **skip** (release does not name the testing package)" >> "$GITHUB_STEP_SUMMARY"
96+
echo "should_publish=false" >> "$GITHUB_OUTPUT"
97+
echo "version=" >> "$GITHUB_OUTPUT"
98+
exit 0
99+
fi
100+
101+
echo "- testing version: $VERSION" >> "$GITHUB_STEP_SUMMARY"
102+
if [[ "$EXISTS" == "true" ]]; then
103+
echo "- image tag v$VERSION: already present in public ECR" >> "$GITHUB_STEP_SUMMARY"
104+
echo "- decision: **skip** (version already published)" >> "$GITHUB_STEP_SUMMARY"
105+
echo "should_publish=false" >> "$GITHUB_OUTPUT"
106+
else
107+
echo "- image tag v$VERSION: not present in public ECR" >> "$GITHUB_STEP_SUMMARY"
108+
echo "- decision: **publish**" >> "$GITHUB_STEP_SUMMARY"
109+
echo "should_publish=true" >> "$GITHUB_OUTPUT"
110+
fi
111+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
112+
16113
build-and-upload-image-to-ecr:
17-
# Only publish when the release includes a new testing package version.
18-
if: contains(github.event.release.tag_name, 'testing-v')
114+
needs: preflight
115+
if: needs.preflight.outputs.should_publish == 'true'
19116
runs-on: ubuntu-latest
20117
permissions:
21118
contents: read
@@ -24,7 +121,7 @@ jobs:
24121
full_image_arm64: ${{ steps.build-publish.outputs.full_image_arm64 }}
25122
full_image_x86_64: ${{ steps.build-publish.outputs.full_image_x86_64 }}
26123
ecr_registry_repository: ${{ steps.build-publish.outputs.ecr_registry_repository }}
27-
version: ${{ steps.version.outputs.VERSION }}
124+
version: ${{ needs.preflight.outputs.version }}
28125
strategy:
29126
matrix:
30127
include:
@@ -56,13 +153,6 @@ jobs:
56153
working-directory: ${{ env.package_path }}
57154
run: hatch build
58155

59-
- name: Get version from __about__.py
60-
id: version
61-
run: |
62-
VERSION=$(grep "^__version__" "${{ env.package_path }}/src/aws_durable_execution_sdk_python_testing/__about__.py" | cut -d'"' -f2)
63-
echo "VERSION=$VERSION"
64-
echo "VERSION=${VERSION}" >> "$GITHUB_OUTPUT"
65-
66156
- name: Configure AWS Credentials
67157
uses: aws-actions/configure-aws-credentials@cbe3b392738ccf3f987d68400dafcf4b0624a56c # v6.2.4
68158
with:
@@ -81,7 +171,7 @@ jobs:
81171
env:
82172
ECR_REGISTRY: ${{ steps.login-ecr-public.outputs.registry }}
83173
ECR_REPOSITORY: ${{ env.ecr_repository_name }}
84-
PER_ARCH_IMAGE_TAG: v${{ steps.version.outputs.VERSION }}-${{ matrix.arch }}
174+
PER_ARCH_IMAGE_TAG: v${{ needs.preflight.outputs.version }}-${{ matrix.arch }}
85175
run: |
86176
docker build --platform "${{ matrix.platform }}" --provenance false "${{ env.package_path }}" -f "${{ env.package_path }}/Dockerfile" -t "$ECR_REGISTRY/$ECR_REPOSITORY:$PER_ARCH_IMAGE_TAG"
87177
docker push "$ECR_REGISTRY/$ECR_REPOSITORY:$PER_ARCH_IMAGE_TAG"
@@ -134,3 +224,53 @@ jobs:
134224
--arch amd64 \
135225
--os linux
136226
docker manifest push "${{ needs.build-and-upload-image-to-ecr.outputs.ecr_registry_repository }}"
227+
228+
verify-publish:
229+
needs: [preflight, create-ecr-manifest-per-arch]
230+
runs-on: ubuntu-latest
231+
permissions:
232+
id-token: write
233+
steps:
234+
- name: Configure AWS Credentials
235+
uses: aws-actions/configure-aws-credentials@cbe3b392738ccf3f987d68400dafcf4b0624a56c # v6.2.4
236+
with:
237+
role-to-assume: ${{ secrets.ECR_UPLOAD_IAM_ROLE_ARN }}
238+
aws-region: ${{ env.aws_region }}
239+
240+
- name: Verify the version and latest tags on public ECR
241+
shell: bash
242+
env:
243+
VERSION: ${{ needs.preflight.outputs.version }}
244+
ECR_REPOSITORY: ${{ env.ecr_repository_name }}
245+
run: |
246+
repo_name="${ECR_REPOSITORY##*/}"
247+
version_digest=""
248+
for attempt in $(seq 1 10); do
249+
version_digest="$(aws ecr-public describe-images \
250+
--region "${{ env.aws_region }}" \
251+
--repository-name "$repo_name" \
252+
--image-ids imageTag="v${VERSION}" \
253+
--query 'imageDetails[0].imageDigest' --output text 2>/dev/null || true)"
254+
if [[ -n "$version_digest" && "$version_digest" != "None" ]]; then
255+
echo "v$VERSION is visible in public ECR ($version_digest)."
256+
break
257+
fi
258+
echo "attempt $attempt: v$VERSION not visible in public ECR yet, retrying."
259+
version_digest=""
260+
sleep 15
261+
done
262+
if [[ -z "$version_digest" ]]; then
263+
echo "::error::v$VERSION did not become visible in public ECR after publishing."
264+
exit 1
265+
fi
266+
267+
latest_digest="$(aws ecr-public describe-images \
268+
--region "${{ env.aws_region }}" \
269+
--repository-name "$repo_name" \
270+
--image-ids imageTag="latest" \
271+
--query 'imageDetails[0].imageDigest' --output text 2>/dev/null || true)"
272+
if [[ "$latest_digest" != "$version_digest" ]]; then
273+
echo "::error::latest on public ECR points at $latest_digest but v$VERSION is $version_digest."
274+
exit 1
275+
fi
276+
echo "latest on public ECR points at v$VERSION."

.github/workflows/test-parser.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
- '.github/scripts/build_lambda_layer.py'
77
- '.github/scripts/check_otel_wheel_dependencies.py'
88
- '.github/scripts/parse_sdk_branch.py'
9+
- '.github/scripts/parse_testing_version.py'
910
- '.github/scripts/tests/**'
1011
- '.github/workflows/ai-pr-review.yml'
1112
- '.github/workflows/opentelemetry-conformance-tests.yml'
@@ -16,6 +17,7 @@ on:
1617
- '.github/scripts/build_lambda_layer.py'
1718
- '.github/scripts/check_otel_wheel_dependencies.py'
1819
- '.github/scripts/parse_sdk_branch.py'
20+
- '.github/scripts/parse_testing_version.py'
1921
- '.github/scripts/tests/**'
2022
- '.github/workflows/ai-pr-review.yml'
2123
- '.github/workflows/opentelemetry-conformance-tests.yml'
@@ -40,4 +42,5 @@ jobs:
4042
.github/scripts/tests/test_build_lambda_layer.py \
4143
.github/scripts/tests/test_check_otel_wheel_dependencies.py \
4244
.github/scripts/tests/test_opentelemetry_conformance_workflow.py \
43-
.github/scripts/tests/test_parse_sdk_branch.py
45+
.github/scripts/tests/test_parse_sdk_branch.py \
46+
.github/scripts/tests/test_parse_testing_version.py

0 commit comments

Comments
 (0)