Repository navigation
220 lines (204 loc) · 10.4 KB
/
Copy pathrelease.yml
File metadata and controls
220 lines (204 loc) · 10.4 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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
name: Release
run-name: Releasing github.com/ConvertAPI/convertapi-go ${{ github.event.release.tag_name }}
# A release is a GitHub Release, never a merge. Merging to main only builds (build.yml). Publishing the
# Release v{semver} creates the tag, and a Go module version is nothing more than that tag: there is no
# package to build, no registry upload and no key. This workflow checks that the release is legitimate and
# complete: who published it, that the tag is SemVer, on main and fits the module path, that config.Version
# matches it and that the tests pass against the API. Then it asks the Go module proxy to fetch the tag,
# which also records its checksum in the checksum database, and asks pkg.go.dev to show it.
#
# gh release create v1.0.1 --target main --generate-notes -> github.com/ConvertAPI/convertapi-go v1.0.1
# gh release create v1.1.0-beta.1 --target main --prerelease ... -> github.com/ConvertAPI/convertapi-go v1.1.0-beta.1
#
# The tag is public the moment it exists, and once the proxy has fetched it the version is permanent, so a
# failure here does not undo a release; it says the version needs a fix-forward release (see RELEASING.md).
# Nothing here retries a failed check or moves a tag.
on:
release:
types: [published]
permissions:
contents: read
env:
# Only this account may publish a Release. GitHub cannot express this as a permission on the Team plan,
# so it is checked here; the real control is the v* tag ruleset (only repository admins create v* tags).
PUBLISHER: tomasr78
GOPROXY: https://proxy.golang.org
GOSUMDB: sum.golang.org
jobs:
release:
name: release
runs-on: ubuntu-latest
timeout-minutes: 45
environment:
name: go-proxy
url: https://pkg.go.dev/github.com/ConvertAPI/convertapi-go@${{ github.event.release.tag_name }}
concurrency:
group: release-go
cancel-in-progress: false
steps:
- name: Check who published the release
run: |
# A re-run keeps GITHUB_ACTOR as the original user; GITHUB_TRIGGERING_ACTOR is whoever pressed
# "Re-run", so both must be the publisher or anyone with write access could replay a release.
if [ "$GITHUB_ACTOR" != "$PUBLISHER" ] || [ "${GITHUB_TRIGGERING_ACTOR:-$GITHUB_ACTOR}" != "$PUBLISHER" ]; then
echo "::error::Only $PUBLISHER may publish a release - this release was published by $GITHUB_ACTOR and run by ${GITHUB_TRIGGERING_ACTOR:-$GITHUB_ACTOR}."
exit 1
fi
echo "Published by $GITHUB_ACTOR"
# The release event runs this file from the tag, so the workflow and the code come from the same commit.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Check the tag and the version
id: tag
env:
TAG: ${{ github.event.release.tag_name }}
PRERELEASE: ${{ github.event.release.prerelease }}
run: |
set -euo pipefail
# SemVer 2.0 without build metadata, which Go module versions cannot carry.
SEMVER='^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?$'
if ! [[ "$TAG" =~ $SEMVER ]]; then
echo "::error::Tag '$TAG' is not v{major}.{minor}.{patch} or v{major}.{minor}.{patch}-{pre-release}, e.g. v1.0.1 or v1.1.0-beta.1"
exit 1
fi
if [ "$GITHUB_REF" != "refs/tags/$TAG" ]; then
echo "::error::The run is on $GITHUB_REF, not on the release tag refs/tags/$TAG"
exit 1
fi
VERSION="${TAG#v}"
SHA=$(git rev-parse "refs/tags/$TAG^{commit}")
if [ "$(git rev-parse HEAD)" != "$SHA" ]; then
echo "::error::Checked out $(git rev-parse HEAD), but $TAG points at $SHA"
exit 1
fi
# Go's import compatibility rule: v2 and later live under a /vN module path, so a v2+ tag on a
# module path without that suffix is a version no `go get` can install.
MODULE=$(sed -n 's/^module \([^ ]*\).*/\1/p' go.mod)
MAJOR="${VERSION%%.*}"
if [ "$MAJOR" -ge 2 ] && [[ "$MODULE" != */v"$MAJOR" ]]; then
echo "::error file=go.mod::$TAG is major version $MAJOR, but the module path is $MODULE. A v$MAJOR release needs the module path $MODULE/v$MAJOR (see RELEASING.md)."
exit 1
elif [ "$MAJOR" -lt 2 ] && [[ "$MODULE" =~ /v[0-9]+$ ]]; then
echo "::error file=go.mod::$TAG is major version $MAJOR, but the module path $MODULE has a major version suffix"
exit 1
fi
# The GitHub "pre-release" tick and the version must agree, so a preview cannot pass for the
# latest stable release on GitHub, and a stable version cannot hide as a pre-release.
if [[ "$VERSION" == *-* ]]; then
if [ "$PRERELEASE" != true ]; then
echo "::error::$TAG is a pre-release version, so the GitHub Release must be marked as a pre-release (gh release create --prerelease)"
exit 1
fi
elif [ "$PRERELEASE" = true ]; then
echo "::error::$TAG is a stable version, but the GitHub Release is marked as a pre-release. Use a pre-release version such as ${TAG}-beta.1, or untick pre-release."
exit 1
fi
if ! git merge-base --is-ancestor "$SHA" origin/main; then
echo "::error::$TAG ($SHA) is not on main. Releases, pre-releases included, are tagged on main only."
exit 1
fi
# Go builds the tagged tree as it is, so the version the library sends in its User-Agent is the
# constant in the code, and it must be the tag's.
CONSTANT=$(sed -n 's/^const Version = "\(.*\)".*/\1/p' pkg/config/version.go)
if [ "$CONSTANT" != "$VERSION" ]; then
echo "::error file=pkg/config/version.go::config.Version is '$CONSTANT' at $TAG, not '$VERSION'. The tag is already public; release the next version with the constant bumped."
exit 1
fi
{
echo "version=$VERSION"
echo "sha=$SHA"
echo "module=$MODULE"
} >> "$GITHUB_OUTPUT"
echo "Releasing $MODULE $TAG from $SHA"
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: stable
cache: false
- name: Vet and build
run: |
set -euo pipefail
go version
go mod verify
go vet ./...
go build ./...
# Every test, the API ones included, against the staging API. The staging API answers 503 (no healthy
# upstream) in bursts of up to a minute, so the suite gets up to three attempts, 30 s apart.
- name: Test
env:
CONVERTAPI_TOKEN: ${{ secrets.CONVERTAPI_STAGING_TOKEN }}
CONVERTAPI_BASE_URL: https://stag-v2.convertapi.com
run: |
set -uo pipefail
if [ -z "$CONVERTAPI_TOKEN" ]; then
echo "::error::The CONVERTAPI_STAGING_TOKEN repository secret is not set, so the API tests cannot run."
exit 1
fi
for attempt in 1 2 3; do
go test -count=1 -race -v ./... && exit 0
echo "::warning::Attempt $attempt: the tests failed, see above"
sleep 30
done
echo "::error::The tests failed three times"
exit 1
# `go mod download` from an empty directory makes proxy.golang.org fetch the tag from GitHub and
# checks the result against sum.golang.org, as every user's first `go get` will. The proxy must then
# report the tagged commit as the version's origin. A tag the proxy has just seen can take a minute.
- name: Publish to the Go module proxy
env:
TAG: ${{ github.event.release.tag_name }}
SHA: ${{ steps.tag.outputs.sha }}
MODULE: ${{ steps.tag.outputs.module }}
run: |
set -uo pipefail
cd "$(mktemp -d)"
for _ in $(seq 1 10); do
# The Origin is the proxy's record of the commit it fetched the version from.
if JSON=$(go mod download -json "$MODULE@$TAG"); then
ORIGIN=$(jq -r '.Origin.Hash // empty' <<<"$JSON") || ORIGIN=''
if [ "$ORIGIN" = "$SHA" ]; then
jq -r '"\(.Path) \(.Version) \(.Sum)"' <<<"$JSON"
echo "The proxy serves $MODULE $TAG from $SHA, and sum.golang.org has its checksum"
exit 0
elif [ -n "$ORIGIN" ]; then
echo "::error::The proxy serves $MODULE $TAG from $ORIGIN, not from $SHA. The proxy keeps the first commit it saw; release the next version."
exit 1
fi
fi
sleep 30
done
echo "::error::proxy.golang.org has not served $MODULE $TAG after 5 minutes. Run: GOPROXY=$GOPROXY go list -m $MODULE@$TAG"
exit 1
# pkg.go.dev picks new versions up from the proxy's index, usually within minutes; the fetch request
# (the page's "Request" button) asks for it at once. The documentation site is not part of the
# release, so a slow pkg.go.dev only warns.
- name: Wait for pkg.go.dev
env:
TAG: ${{ github.event.release.tag_name }}
MODULE: ${{ steps.tag.outputs.module }}
run: |
set -uo pipefail
curl -fsS -o /dev/null -X POST "https://pkg.go.dev/fetch/$MODULE@$TAG" || true
for _ in $(seq 1 30); do
if curl -fsS -o /dev/null "https://pkg.go.dev/$MODULE@$TAG"; then
echo "https://pkg.go.dev/$MODULE@$TAG is up"
exit 0
fi
sleep 30
done
echo "::warning::pkg.go.dev has not shown $MODULE $TAG after 15 minutes. Open https://pkg.go.dev/$MODULE@$TAG and press Request."
- name: Summary
env:
TAG: ${{ github.event.release.tag_name }}
SHA: ${{ steps.tag.outputs.sha }}
MODULE: ${{ steps.tag.outputs.module }}
run: |
{
echo "| | |"
echo "|---|---|"
echo "| Module | [\`$MODULE $TAG\`](https://pkg.go.dev/$MODULE@$TAG) |"
echo "| Install | \`go get $MODULE@$TAG\` |"
echo "| Commit | \`$SHA\` |"
echo "| Tested against | https://stag-v2.convertapi.com (Go stable) |"
} >> "$GITHUB_STEP_SUMMARY"