Repository navigation
167 lines (155 loc) · 6.69 KB
/
Copy pathrelease.yml
File metadata and controls
167 lines (155 loc) · 6.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
name: Release
on:
release:
types: [published]
jobs:
release:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
issues: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: '3.13'
- name: Install build tooling
uses: $/.github/actions/setup-hatch
- name: Get Version
id: version
env:
REF_NAME: ${{ github.ref_name }}
run: |
RAW_VERSION=$(hatch version)
echo "VERSION=$RAW_VERSION" >> $GITHUB_ENV
if [ "v$RAW_VERSION" != "$REF_NAME" ]; then
echo "Error: Git tag ($REF_NAME) does not match hatch version (v$RAW_VERSION)"
exit 1
fi
- name: Check if version exists on PyPI
id: version_check
env:
VERSION: ${{ env.VERSION }}
run: |
if curl -s -f https://pypi.org/pypi/socketsecurity/$VERSION/json > /dev/null; then
echo "Version ${VERSION} already exists on PyPI"
echo "pypi_exists=true" >> $GITHUB_OUTPUT
else
echo "Version ${VERSION} not found on PyPI - proceeding with PyPI deployment"
echo "pypi_exists=false" >> $GITHUB_OUTPUT
fi
- name: Check Docker image existence
id: docker_check
env:
VERSION: ${{ env.VERSION }}
run: |
if curl -s -f "https://hub.docker.com/v2/repositories/socketdev/cli/tags/${VERSION}" > /dev/null; then
echo "Docker image socketdev/cli:${VERSION} already exists"
echo "docker_exists=true" >> $GITHUB_OUTPUT
else
echo "docker_exists=false" >> $GITHUB_OUTPUT
fi
- name: Build package
if: steps.version_check.outputs.pypi_exists != 'true'
run: |
pip install hatchling
hatch build
- name: Publish to PyPI
if: steps.version_check.outputs.pypi_exists != 'true'
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
- name: Set up Docker publishing
uses: $/.github/actions/setup-docker
with:
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Verify package is installable
id: verify_package
env:
VERSION: ${{ env.VERSION }}
run: |
# The first lookup can race PyPI's Simple-index propagation, and a delayed
# CDN purge can leave the index stale well after a successful upload.
# pip caches HTTP responses by default, so without --no-cache-dir every
# retry can reuse that initial stale response instead of checking whether
# the release has appeared. Budget: 30 minutes.
MAX_ATTEMPTS=60
for i in $(seq 1 "$MAX_ATTEMPTS"); do
if python -m pip install \
--no-cache-dir \
--index-url https://pypi.org/simple/ \
"socketsecurity==${VERSION}"; then
echo "Package ${VERSION} is now available and installable on PyPI"
python -m pip uninstall -y socketsecurity
echo "success=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if curl -s -f "https://pypi.org/pypi/socketsecurity/${VERSION}/json" > /dev/null; then
echo "Release ${VERSION} exists on PyPI (JSON API) but is not in the Simple index yet - CDN propagation delay"
fi
if [ "$i" -lt "$MAX_ATTEMPTS" ]; then
echo "Attempt $i: Package not yet installable, waiting 30s... (${i}/${MAX_ATTEMPTS})"
sleep 30
fi
done
echo "success=false" >> "$GITHUB_OUTPUT"
exit 1
- name: Build & Push Docker
if: |
steps.verify_package.outputs.success == 'true' &&
steps.docker_check.outputs.docker_exists != 'true'
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
env:
VERSION: ${{ env.VERSION }}
with:
push: true
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
tags: |
socketdev/cli:latest
socketdev/cli:${{ env.VERSION }}
build-args: |
CLI_VERSION=${{ env.VERSION }}
# Releases are immutable, so a failed run leaves a published GitHub release whose
# tag can never be reused, with nothing on PyPI or Docker Hub behind it. A failed
# run only notifies whoever published the release, so raise it where the team
# will see it.
- name: Open an issue for the failed release
if: failure()
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
VERSION: ${{ env.VERSION }}
with:
script: |
const release = context.payload.release;
const runUrl =
`${context.serverUrl}/${context.repo.owner}/${context.repo.repo}` +
`/actions/runs/${context.runId}`;
const body = [
`@SocketDev/customer-engineering the release workflow for [${release.tag_name}](${release.html_url}) ` +
`(published by @${release.author.login}) failed: ${runUrl}`,
'',
`- Tag: \`${release.tag_name}\``,
`- Package version at that commit: \`${process.env.VERSION || 'unknown'}\``,
'',
'Check the run for which step failed. If it failed before "Publish to PyPI", nothing reached PyPI or ' +
'Docker Hub, but the GitHub release is immutable and its tag can never be reused.',
'',
'Before publishing another release:',
'',
'1. Edit this release\'s notes to say it was not published and name the release that replaces it.',
'2. If the tag and the package version disagree, merge a PR that bumps the version to one with no ' +
'existing tag (the Version Check workflow enforces this), then publish a release whose tag matches ' +
'that version exactly. Do not pick the next tag number from the releases list.',
].join('\n');
const created = await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Release ${release.tag_name} failed`,
body,
});
core.error(`Release failed; opened #${created.data.number}`);