@@ -23,17 +23,18 @@ jobs:
2323 timeout-minutes : 1
2424 outputs :
2525 tag : " ${{ steps.tag-validate.outputs.tag }}"
26+ should_promote : " ${{ steps.check-release.outputs.should_promote }}"
2627 steps :
2728 # Harden the runner used by this workflow
2829 # yamllint disable-line rule:line-length
29- - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
30+ - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
3031 with :
3132 egress-policy : ' audit'
3233
3334 - name : ' Verify Pushed Tag'
3435 id : ' tag-validate'
3536 # yamllint disable-line rule:line-length
36- uses : lfreleng-actions/tag-push-verify-action@80e2bdbbb9ee7b67557a31705892b75e75d2859e # v0.1.1
37+ uses : lfreleng-actions/tag-push-verify-action@80e2bdbbb9ee7b67557a31705892b75e75d2859e # v0.1.1
3738 with :
3839 versioning : ' semver'
3940
4748 >> "$GITHUB_STEP_SUMMARY"
4849 exit 1
4950
51+ - name : ' Check if release exists'
52+ id : ' check-release'
53+ shell : bash
54+ env :
55+ GH_TOKEN : " ${{ secrets.GITHUB_TOKEN }}"
56+ run : |
57+ TAG="${{ steps.tag-validate.outputs.tag }}"
58+
59+ # Check if release exists and get its draft status
60+ if RELEASE_INFO=$(gh release view "$TAG" --json isDraft \
61+ 2>/dev/null); then
62+ IS_DRAFT=$(echo "$RELEASE_INFO" | jq -r '.isDraft')
63+ if [ "$IS_DRAFT" = "false" ]; then
64+ echo "should_promote=false" >> "$GITHUB_OUTPUT"
65+ echo "Published release already exists for tag $TAG, " \
66+ "skipping promotion"
67+ else
68+ echo "should_promote=true" >> "$GITHUB_OUTPUT"
69+ echo "Draft release exists for tag $TAG, " \
70+ "will proceed with promotion"
71+ fi
72+ else
73+ echo "should_promote=true" >> "$GITHUB_OUTPUT"
74+ echo "No release found for tag $TAG, will proceed with promotion"
75+ fi
76+
5077 python-build :
5178 name : ' Python Build'
5279 needs : ' tag-validate'
@@ -65,17 +92,17 @@ jobs:
6592 steps :
6693 # Harden the runner used by this workflow
6794 # yamllint disable-line rule:line-length
68- - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
95+ - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
6996 with :
7097 egress-policy : ' audit'
7198
7299 # yamllint disable-line rule:line-length
73- - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
100+ - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
74101
75102 - name : ' Build Python project'
76103 id : ' python-build'
77104 # yamllint disable-line rule:line-length
78- uses : lfreleng-actions/python-build-action@48381cece78a990a6ba93bd5924bcd40bf0d1a7d # v0.1.20
105+ uses : lfreleng-actions/python-build-action@48381cece78a990a6ba93bd5924bcd40bf0d1a7d # v0.1.20
79106 with :
80107 sigstore_sign : true
81108 attestations : true
@@ -94,16 +121,16 @@ jobs:
94121 steps :
95122 # Harden the runner used by this workflow
96123 # yamllint disable-line rule:line-length
97- - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
124+ - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
98125 with :
99126 egress-policy : ' audit'
100127
101128 # yamllint disable-line rule:line-length
102- - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
129+ - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
103130
104131 - name : ' Test Python project [PYTEST]'
105132 # yamllint disable-line rule:line-length
106- uses : lfreleng-actions/python-test-action@bdde9e4e6221e858359f9036bd4f41ab3b1af90e # v0.1.11
133+ uses : lfreleng-actions/python-test-action@bdde9e4e6221e858359f9036bd4f41ab3b1af90e # v0.1.11
107134 with :
108135 python_version : " ${{ matrix.python-version }}"
109136
@@ -121,19 +148,60 @@ jobs:
121148 steps :
122149 # Harden the runner used by this workflow
123150 # yamllint disable-line rule:line-length
124- - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
151+ - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
125152 with :
126153 egress-policy : ' audit'
127154
128155 # yamllint disable-line rule:line-length
129- - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
156+ - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
130157
131158 - name : ' Audit Python project'
132159 # yamllint disable-line rule:line-length
133- uses : lfreleng-actions/python-audit-action@bab5316468c108870eb759ef0de622bae9239aad # v0.2.2
160+ uses : lfreleng-actions/python-audit-action@bab5316468c108870eb759ef0de622bae9239aad # v0.2.2
134161 with :
135162 python_version : " ${{ matrix.python-version }}"
136163
164+ sbom :
165+ name : ' Generate SBOM'
166+ runs-on : ubuntu-latest
167+ needs : ' python-build'
168+ timeout-minutes : 10
169+ permissions :
170+ contents : read
171+ steps :
172+ # yamllint disable-line rule:line-length
173+ - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
174+
175+ - name : " Generate SBOM"
176+ id : sbom
177+ # yamllint disable-line rule:line-length
178+ uses : lfreleng-actions/python-sbom-action@ae4aca2ef28d7da4ec95049cc78be43e632d322a # v0.1.0
179+ with :
180+ include_dev : " false"
181+ sbom_format : " both"
182+
183+ - name : " Upload SBOM artifacts"
184+ # yamllint disable-line rule:line-length
185+ uses : actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
186+ with :
187+ name : sbom-files
188+ path : |
189+ sbom-cyclonedx.json
190+ sbom-cyclonedx.xml
191+ retention-days : 45
192+
193+ - name : " Security scan with Grype"
194+ # yamllint disable-line rule:line-length
195+ uses : anchore/scan-action@f6601287cdb1efc985d6b765bbf99cb4c0ac29d8 # v7.0.0
196+ with :
197+ sbom : " ${{ steps.sbom.outputs.sbom_json_path }}"
198+
199+ - name : " Summary step"
200+ run : |
201+ # Summary step
202+ echo "SBOM count: ${{ steps.sbom.outputs.component_count }}"
203+ echo "Tool used: ${{ steps.sbom.outputs.dependency_manager }}"
204+
137205 test-pypi :
138206 name : ' Test PyPI Publishing'
139207 runs-on : ' ubuntu-latest'
@@ -150,20 +218,20 @@ jobs:
150218 steps :
151219 # Harden the runner used by this workflow
152220 # yamllint disable-line rule:line-length
153- - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
221+ - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
154222 with :
155223 egress-policy : ' audit'
156224
157225 # yamllint disable-line rule:line-length
158- - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
226+ - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
159227
160228 - name : ' Test PyPI publishing'
161229 # yamllint disable-line rule:line-length
162- uses : lfreleng-actions/pypi-publish-action@81a056957ed050f8305760055b1fd8103a916989 # v0.1.1
230+ uses : lfreleng-actions/pypi-publish-action@81a056957ed050f8305760055b1fd8103a916989 # v0.1.1
163231 with :
164232 environment : ' development'
165233 tag : " ${{ needs.tag-validate.outputs.tag }}"
166- pypi_credential : " ${{ secrets.PYPI_CREDENTIAL }}"
234+ pypi_credential : " ${{ secrets.TEST_PYPI_CREDENTIAL }}"
167235
168236 pypi :
169237 name : ' Release PyPI Package'
@@ -180,26 +248,27 @@ jobs:
180248 steps :
181249 # Harden the runner used by this workflow
182250 # yamllint disable-line rule:line-length
183- - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
251+ - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
184252 with :
185253 egress-policy : ' audit'
186254
187255 # yamllint disable-line rule:line-length
188- - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
256+ - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
189257
190258 - name : ' PyPI release'
191259 # yamllint disable-line rule:line-length
192- uses : lfreleng-actions/pypi-publish-action@81a056957ed050f8305760055b1fd8103a916989 # v0.1.1
260+ uses : lfreleng-actions/pypi-publish-action@81a056957ed050f8305760055b1fd8103a916989 # v0.1.1
193261 with :
194262 environment : ' production'
195263 attestations : true
196264 tag : " ${{ needs.tag-validate.outputs.tag }}"
197265 pypi_credential : " ${{ secrets.PYPI_CREDENTIAL }}"
198266
267+
199268 promote-release :
200269 name : ' Promote Draft Release'
201270 # yamllint disable-line rule:line-length
202- if : startsWith(github.ref, 'refs/tags/')
271+ if : needs.tag-validate.outputs.should_promote == 'true'
203272 needs :
204273 - ' tag-validate'
205274 - ' pypi'
@@ -212,22 +281,50 @@ jobs:
212281 steps :
213282 # Harden the runner used by this workflow
214283 # yamllint disable-line rule:line-length
215- - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
284+ - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
216285 with :
217286 egress-policy : ' audit'
218287
219288 # yamllint disable-line rule:line-length
220- - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
289+ - uses : actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
290+
291+ - name : ' Check if release is already promoted'
292+ id : ' check-promoted'
293+ shell : bash
294+ env :
295+ GH_TOKEN : " ${{ secrets.GITHUB_TOKEN }}"
296+ run : |
297+ TAG="${{ needs.tag-validate.outputs.tag }}"
298+ if gh release view "$TAG" --json isDraft --jq '.isDraft' \
299+ 2>/dev/null | grep -q "false"; then
300+ echo "Release $TAG is already promoted, skipping promotion"
301+ echo "already_promoted=true" >> "$GITHUB_OUTPUT"
302+ else
303+ echo "Release $TAG is draft or doesn't exist, " \
304+ "proceeding with promotion"
305+ echo "already_promoted=false" >> "$GITHUB_OUTPUT"
306+ fi
221307
222308 - name : ' Promote draft release'
223309 id : ' promote-release'
310+ if : steps.check-promoted.outputs.already_promoted == 'false'
224311 # yamllint disable-line rule:line-length
225- uses : lfreleng-actions/draft-release-promote-action@d7e7df12e32fa26b28dbc2f18a12766482785399 # v0.1.2
312+ uses : lfreleng-actions/draft-release-promote-action@d7e7df12e32fa26b28dbc2f18a12766482785399 # v0.1.2
226313 with :
227314 token : " ${{ secrets.GITHUB_TOKEN }}"
228315 tag : " ${{ needs.tag-validate.outputs.tag }}"
229316 latest : true
230317
318+ - name : ' Set release URL for already promoted release'
319+ if : steps.check-promoted.outputs.already_promoted == 'true'
320+ shell : bash
321+ env :
322+ GH_TOKEN : " ${{ secrets.GITHUB_TOKEN }}"
323+ run : |
324+ TAG="${{ needs.tag-validate.outputs.tag }}"
325+ RELEASE_URL=$(gh release view "$TAG" --json url --jq '.url')
326+ echo "release_url=$RELEASE_URL" >> "$GITHUB_OUTPUT"
327+
231328 # Need to attach build artefacts to the release
232329 # This step could potentially be moved
233330 # (May be better to when/where the release is still in draft state)
@@ -238,28 +335,30 @@ jobs:
238335 - ' tag-validate'
239336 - ' python-build'
240337 - ' promote-release'
338+ # yamllint disable-line rule:line-length
339+ if : always() && (needs.promote-release.result == 'success' || needs.promote-release.result == 'skipped')
241340 permissions :
242341 contents : write # IMPORTANT: needed to edit release, attach artefacts
243342 timeout-minutes : 5
244343 steps :
245344 # Harden the runner used by this workflow
246345 # yamllint disable-line rule:line-length
247- - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
346+ - uses : step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
248347 with :
249348 egress-policy : ' audit'
250349
251350 # Note: no need for a checkout step in this job
252351
253352 - name : ' ⬇ Download build artefacts'
254353 # yamllint disable-line rule:line-length
255- uses : actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
354+ uses : actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
256355 with :
257356 name : " ${{ needs.python-build.outputs.artefact_name }}"
258357 path : " ${{ needs.python-build.outputs.artefact_path }}"
259358
260359 - name : ' Attach build artefacts to release'
261360 # yamllint disable-line rule:line-length
262- uses : alexellis/upload-assets@13926a61cdb2cb35f5fdef1c06b8b591523236d3 # 0.4.1
361+ uses : alexellis/upload-assets@13926a61cdb2cb35f5fdef1c06b8b591523236d3 # 0.4.1
263362 env :
264363 GITHUB_TOKEN : " ${{ github.token }}"
265364 with :
0 commit comments