Skip to content

Commit c0c380a

Browse files
Make the release workflow create the tag and GitHub release
1 parent 5e8200e commit c0c380a

1 file changed

Lines changed: 126 additions & 13 deletions

File tree

.github/workflows/release.yml

Lines changed: 126 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,142 @@
11
name: Release
22

3-
# Publishes the crate to crates.io when a version tag is pushed.
3+
# Manually triggered full release: validates the version, runs the test gate,
4+
# publishes the crate to crates.io, creates the vX.Y.Z tag, and publishes a
5+
# GitHub Release whose notes are taken from the matching section of
6+
# CHANGELOG.md, with the packaged .crate file attached.
7+
#
8+
# The CHANGELOG must already contain a "## [X.Y.Z] - <date>" section and
9+
# Cargo.toml must already carry the version being released.
410
on:
5-
push:
6-
tags:
7-
- "v*"
11+
workflow_dispatch:
12+
inputs:
13+
version:
14+
description: 'Release version, e.g. 1.2.0 or v1.2.0'
15+
required: true
16+
type: string
17+
18+
permissions:
19+
contents: write
20+
21+
concurrency:
22+
group: release
23+
cancel-in-progress: false
824

925
env:
1026
CARGO_TERM_COLOR: always
1127

1228
jobs:
13-
publish:
14-
name: Publish to crates.io
29+
release:
1530
runs-on: ubuntu-latest
1631
steps:
1732
- uses: actions/checkout@v4
33+
with:
34+
fetch-depth: 0
35+
1836
- uses: dtolnay/rust-toolchain@stable
19-
- name: Verify the tag matches the crate version
37+
- uses: Swatinem/rust-cache@v2
38+
39+
- name: Validate version
40+
# The raw input is passed through the environment (never interpolated
41+
# into the shell) and strictly validated before any further use.
42+
env:
43+
VERSION_INPUT: ${{ inputs.version }}
44+
run: |
45+
set -euo pipefail
46+
version="${VERSION_INPUT#v}"
47+
if ! printf '%s' "$version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$'; then
48+
echo "::error::Invalid version '$VERSION_INPUT'. Expected semver such as 1.2.0 or v1.2.0."
49+
exit 1
50+
fi
51+
echo "VERSION=$version" >> "$GITHUB_ENV"
52+
echo "TAG=v$version" >> "$GITHUB_ENV"
53+
54+
- name: Ensure the version matches Cargo.toml
55+
run: |
56+
set -euo pipefail
57+
crate_version="$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')"
58+
if [ "$crate_version" != "$VERSION" ]; then
59+
echo "::error::Cargo.toml carries version $crate_version but the release input is $VERSION."
60+
exit 1
61+
fi
62+
63+
- name: Ensure tag does not already exist
2064
run: |
21-
version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')
22-
test "v$version" = "${GITHUB_REF_NAME}" || {
23-
echo "tag ${GITHUB_REF_NAME} does not match crate version $version" >&2
65+
set -euo pipefail
66+
if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
67+
echo "::error::Tag $TAG already exists locally."
2468
exit 1
25-
}
26-
- name: Publish
27-
run: cargo publish
69+
fi
70+
if git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null 2>&1; then
71+
echo "::error::Tag $TAG already exists on the remote."
72+
exit 1
73+
fi
74+
75+
- name: Extract changelog section
76+
run: |
77+
set -euo pipefail
78+
awk -v ver="$VERSION" '
79+
$0 ~ ("^## \\[" ver "\\]") { capture=1; next }
80+
capture && (/^## / || /^\[[^][]+\]:[[:space:]]/) { exit }
81+
capture { print }
82+
' CHANGELOG.md | sed -e '/./,$!d' > release-notes.md
83+
if [ ! -s release-notes.md ]; then
84+
echo "::error::No changelog section found for [$VERSION] in CHANGELOG.md."
85+
exit 1
86+
fi
87+
echo "Release notes for $TAG:"
88+
echo "----------------------------------------"
89+
cat release-notes.md
90+
echo "----------------------------------------"
91+
92+
- name: Run local checks (format, lints, unit and behavior tests)
93+
run: |
94+
cargo fmt --check
95+
cargo clippy --all-targets -- -D warnings
96+
cargo test
97+
98+
- name: Run system tests (live API)
99+
env:
100+
IPREGISTRY_API_KEY: ${{ secrets.IPREGISTRY_API_KEY }}
101+
run: |
102+
set -euo pipefail
103+
if [ -z "${IPREGISTRY_API_KEY:-}" ]; then
104+
echo "::error::IPREGISTRY_API_KEY secret is required to run system tests before a release."
105+
exit 1
106+
fi
107+
cargo test --test integration -- --ignored
108+
109+
- name: Package the crate
110+
run: |
111+
set -euo pipefail
112+
cargo package
113+
test -f "target/package/ipregistry-$VERSION.crate"
114+
115+
- name: Publish to crates.io
116+
# Published before the tag and GitHub Release are created: a crates.io
117+
# publish cannot be repeated for the same version, whereas the steps
118+
# below are trivially re-runnable if anything after this point fails.
28119
env:
29120
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
121+
run: cargo publish --no-verify # already verified by the package step
122+
123+
- name: Create tag and GitHub release
124+
# The default Actions token (GITHUB_TOKEN) is blocked by an organization
125+
# rule from creating v*-prefixed tags. Provide a RELEASE_TOKEN secret (a
126+
# PAT owned by a user allowed to create such tags, with Contents: write)
127+
# so the tag and release are created as that user.
128+
env:
129+
GH_TOKEN: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}
130+
run: |
131+
set -euo pipefail
132+
prerelease=""
133+
case "$VERSION" in
134+
*-*) prerelease="--prerelease" ;;
135+
esac
136+
gh release create "$TAG" \
137+
--target "$GITHUB_SHA" \
138+
--title "$TAG" \
139+
--notes-file release-notes.md \
140+
$prerelease \
141+
"target/package/ipregistry-$VERSION.crate"
142+
echo "Released $TAG: ${{ github.server_url }}/${{ github.repository }}/releases/tag/$TAG"

0 commit comments

Comments
 (0)