feat: add release automation script#1992
feat: add release automation script#1992TerryHowe wants to merge 3 commits intooras-project:mainfrom
Conversation
Signed-off-by: Terry Howe <terrylhowe@gmail.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1992 +/- ##
==========================================
+ Coverage 87.18% 87.21% +0.03%
==========================================
Files 143 143
Lines 5539 5539
==========================================
+ Hits 4829 4831 +2
+ Misses 423 421 -2
Partials 287 287 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Adds a new scripts/release.sh automation script to codify the ORAS CLI release process into repeatable phases, and exposes those phases via make targets for convenience.
Changes:
- Added
scripts/release.shimplementingprep,tag,validate, andpublishrelease phases (with--dry-runsupport). - Added Makefile targets (
release-prep,release-tag,release-validate,release-publish) to invoke the script.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
scripts/release.sh |
New bash script that automates version bump PR creation, tagging, CI/artifact validation, and publishing/signing steps. |
Makefile |
Adds convenience targets to run the release script phases via make. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
scripts/release.sh
Outdated
| info "Pushing branch '${branch}' to origin..." | ||
| run git push origin "$branch" |
There was a problem hiding this comment.
check_prerequisites validates the configured $REMOTE, but phase_prep pushes the branch to origin. This can fail if origin isn’t configured (or if a different push remote is intended). Either validate origin exists too, or make the push remote configurable and use it consistently.
| info "Pushing branch '${branch}' to origin..." | |
| run git push origin "$branch" | |
| info "Pushing branch '${branch}' to ${REMOTE}..." | |
| run git push "$REMOTE" "$branch" |
| local version="$1" | ||
| local sha="$2" | ||
| validate_semver "$version" | ||
|
|
There was a problem hiding this comment.
phase_tag creates a signed tag but doesn’t run prerequisite checks (gpg availability/secret key, gh auth, remote exists). This makes failures harder to diagnose and can result in partial state (e.g., local tag created but not pushed). Consider reusing check_prerequisites here (or splitting it into phase-specific checks).
| # Ensure GPG, GitHub auth, and remote are correctly configured before tagging | |
| check_prerequisites |
scripts/release.sh
Outdated
| if ! shasum -a 256 -c "oras_${version}_checksums.txt" --ignore-missing; then | ||
| fatal "Checksum verification failed!" |
There was a problem hiding this comment.
Checksum verification hard-depends on shasum, which is commonly missing on many Linux distros (where sha256sum is standard). Add a prerequisite check and/or a portable fallback (prefer sha256sum -c when available) so validate works reliably across environments.
| if ! shasum -a 256 -c "oras_${version}_checksums.txt" --ignore-missing; then | |
| fatal "Checksum verification failed!" | |
| local checksum_file="oras_${version}_checksums.txt" | |
| if command -v sha256sum >/dev/null 2>&1; then | |
| if ! sha256sum -c "$checksum_file" --ignore-missing; then | |
| fatal "Checksum verification failed!" | |
| fi | |
| elif command -v shasum >/dev/null 2>&1; then | |
| if ! shasum -a 256 -c "$checksum_file" --ignore-missing; then | |
| fatal "Checksum verification failed!" | |
| fi | |
| else | |
| fatal "Neither sha256sum nor shasum is available; cannot verify checksums." |
scripts/release.sh
Outdated
|
|
||
| # Sign artifacts | ||
| info "Signing artifacts..." | ||
| run make sign |
There was a problem hiding this comment.
phase_publish runs make sign, but the current sign target in the Makefile uses a brace glob (_dist/*.{gz,txt}) which is not POSIX and will fail under /bin/sh on many systems (e.g., dash on Ubuntu). To avoid release failures, either update the Makefile target to be POSIX-safe or invoke it with a known bash shell (or sign files directly here).
| run make sign | |
| run make SHELL=/bin/bash sign |
scripts/release.sh
Outdated
| # Upload .asc files to GitHub release | ||
| info "Uploading signature files to GitHub release..." | ||
| if [[ "$DRY_RUN" != "true" ]]; then | ||
| for sig in _dist/*.asc; do | ||
| info "Uploading $(basename "$sig")..." | ||
| gh release upload "v${version}" "$sig" --repo "$REPO" --clobber | ||
| done |
There was a problem hiding this comment.
Same globbing issue as above: uploading _dist/*.asc without checking for matches can attempt to upload a literal _dist/*.asc when no signatures exist. Guard the loop with a file-existence check (or nullglob) and fail early with a clear message if signatures weren’t generated.
| # Clean up | ||
| info "Cleaning up _dist/ directory..." | ||
| run rm -rf _dist/ | ||
|
|
There was a problem hiding this comment.
The script removes _dist/ via rm -rf without a confirmation prompt, but the PR description states there are confirmation prompts before all destructive actions. Either add a confirm step here (and for other destructive operations) or adjust the documentation/description to match the actual behavior.
scripts/release.sh
Outdated
| # Ensure we are up-to-date | ||
| info "Fetching latest from ${REMOTE}..." | ||
| run git fetch "$REMOTE" | ||
|
|
||
| # Create release branch | ||
| local branch="chore/release-v${version}" | ||
| info "Creating branch '${branch}' from '${REMOTE}/${base_branch}'..." | ||
| run git checkout -b "$branch" "${REMOTE}/${base_branch}" | ||
|
|
There was a problem hiding this comment.
phase_prep creates a new branch and commit without first verifying the working tree/index are clean. If there are local changes, the checkout/commit can fail or accidentally include unrelated changes in the release PR. Add a guard (e.g., git diff --quiet and git diff --cached --quiet) and abort with a clear message if the tree isn’t clean.
scripts/release.sh
Outdated
| local failed=false | ||
| for sig in _dist/*.asc; do | ||
| local original="${sig%.asc}" | ||
| if ! gpg --verify "$sig" "$original" 2>/dev/null; then | ||
| error "GPG verification failed for: ${original}" | ||
| failed=true | ||
| fi | ||
| done | ||
| if [[ "$failed" == "true" ]]; then | ||
| fatal "One or more GPG signature verifications failed." | ||
| fi | ||
| success "All GPG signatures verified." | ||
| fi | ||
|
|
||
| # Upload .asc files to GitHub release | ||
| info "Uploading signature files to GitHub release..." | ||
| if [[ "$DRY_RUN" != "true" ]]; then | ||
| for sig in _dist/*.asc; do | ||
| info "Uploading $(basename "$sig")..." | ||
| gh release upload "v${version}" "$sig" --repo "$REPO" --clobber | ||
| done | ||
| success "Signature files uploaded." |
There was a problem hiding this comment.
The glob _dist/*.asc is used without enabling nullglob or checking for matches. If no signature files exist, bash will iterate once with the literal string _dist/*.asc, causing confusing GPG errors. Add an explicit check for existing .asc files (or enable shopt -s nullglob locally) before looping.
| local failed=false | |
| for sig in _dist/*.asc; do | |
| local original="${sig%.asc}" | |
| if ! gpg --verify "$sig" "$original" 2>/dev/null; then | |
| error "GPG verification failed for: ${original}" | |
| failed=true | |
| fi | |
| done | |
| if [[ "$failed" == "true" ]]; then | |
| fatal "One or more GPG signature verifications failed." | |
| fi | |
| success "All GPG signatures verified." | |
| fi | |
| # Upload .asc files to GitHub release | |
| info "Uploading signature files to GitHub release..." | |
| if [[ "$DRY_RUN" != "true" ]]; then | |
| for sig in _dist/*.asc; do | |
| info "Uploading $(basename "$sig")..." | |
| gh release upload "v${version}" "$sig" --repo "$REPO" --clobber | |
| done | |
| success "Signature files uploaded." | |
| if compgen -G "_dist/*.asc" > /dev/null; then | |
| local failed=false | |
| for sig in _dist/*.asc; do | |
| local original="${sig%.asc}" | |
| if ! gpg --verify "$sig" "$original" 2>/dev/null; then | |
| error "GPG verification failed for: ${original}" | |
| failed=true | |
| fi | |
| done | |
| if [[ "$failed" == "true" ]]; then | |
| fatal "One or more GPG signature verifications failed." | |
| fi | |
| success "All GPG signatures verified." | |
| else | |
| info "No GPG signature files found in _dist; skipping verification." | |
| fi | |
| fi | |
| # Upload .asc files to GitHub release | |
| info "Uploading signature files to GitHub release..." | |
| if [[ "$DRY_RUN" != "true" ]]; then | |
| if compgen -G "_dist/*.asc" > /dev/null; then | |
| for sig in _dist/*.asc; do | |
| info "Uploading $(basename "$sig")..." | |
| gh release upload "v${version}" "$sig" --repo "$REPO" --clobber | |
| done | |
| success "Signature files uploaded." | |
| else | |
| info "No signature files found in _dist; skipping upload." | |
| fi |
scripts/release.sh
Outdated
| if [[ "$DRY_RUN" != "true" ]]; then | ||
| gh workflow run snap.yml --repo "$REPO" --ref "v${version}" 2>/dev/null || \ |
There was a problem hiding this comment.
The snap workflow trigger looks incorrect: the repo has .github/workflows/release-snap.yml (workflow name release-snap) and it requires workflow_dispatch inputs (version is required). gh workflow run snap.yml will fail and currently doesn’t pass the required inputs. Update this to run the correct workflow and supply inputs (e.g., version=v${version} and an isStable value derived from whether the version has a prerelease suffix).
| if [[ "$DRY_RUN" != "true" ]]; then | |
| gh workflow run snap.yml --repo "$REPO" --ref "v${version}" 2>/dev/null || \ | |
| # Determine whether this is a stable release (no prerelease suffix) | |
| is_stable="true" | |
| if [[ "$version" == *"-"* ]]; then | |
| is_stable="false" | |
| fi | |
| if [[ "$DRY_RUN" != "true" ]]; then | |
| gh workflow run release-snap.yml \ | |
| --repo "$REPO" \ | |
| --ref "v${version}" \ | |
| --field "version=v${version}" \ | |
| --field "isStable=${is_stable}" \ | |
| 2>/dev/null || \ |
- Extract check_prerequisites() as shared function called from both do_prep and do_tag, ensuring GPG/gh/remote are validated before tagging - Add dirty working tree guard in do_prep before creating branch - Add portable checksum verification: prefer sha256sum over shasum with fallback and fatal error if neither is available - Use make SHELL=/bin/bash sign to avoid POSIX brace glob issue - Fix .asc upload to avoid glob expansion in dry-run mode - Fix snap workflow: use release-snap.yml with required version and isStable fields, pass --ref for correct trigger Signed-off-by: Terry Howe <terrylhowe@gmail.com>
Signed-off-by: Terry Howe <terrylhowe@gmail.com>
sabre1041
left a comment
There was a problem hiding this comment.
Overall looks really good. one small comment
| for obj in ${TARGET_OBJS} ; do \ | ||
| curl -sSL -o oras_${VERSION}_$${obj} https://github.com/oras-project/oras/releases/download/v${VERSION}/oras_${VERSION}_$${obj} ; \ | ||
| done | ||
| gh release download v${VERSION} --repo oras-project/oras --dir _dist --clobber |
There was a problem hiding this comment.
We are introducing a new required tool for releasing the project. This new required dependency should be documented along with verifying that it is installed
There was a problem hiding this comment.
I addition, should there be a variable for oras-project/oras?
Summary
Adds
scripts/release.sh, a bash release automation script that codifies the manual steps from https://oras.land/community/release-process into four repeatable phases.Phases
prep <version>- Validates semver format, checksgh/gpgprerequisites, updatesinternal/version/version.go(sets Version, clears BuildMetadata), creates branchchore/release-v<version>, commits, pushes, creates a PR titled "bump: tag and release ORAS CLI v<version>" targetingmain(or the release branch for patches), and prints the commit SHA with a Slack vote template.tag <version> <sha>- Validates that the commit at the given SHA contains the correct version and cleared BuildMetadata, creates a signed git tagv<version>, pushes the tag, and creates a release branch (e.g.release-X.Y) for new minor versions (patch==0, no pre-release suffix).validate <version>- Polls therelease-ghcrandrelease-githubGitHub Actions workflows until they complete, runsmake fetch-distto download artifacts, verifies checksums withshasum, and tests thelinux/amd64binary reports the expected version.publish <version>- Runsmake signto GPG-sign artifacts, verifies all GPG signatures, uploads.ascfiles to the GitHub release, appends signing key verification instructions to release notes, publishes the release (--draft=false), triggers the snap workflow, cleans up_dist/, and prints a post-release Slack template.Features
--dry-runflag for safe testingORAS_REMOTEenv var to configure the upstream remote (default:upstream)Makefile Targets
Also adds convenience Makefile targets:
release-prep,release-tag,release-validate,release-publish.Test plan
scripts/release.sh --helpto verify usage outputscripts/release.sh --dry-run prep 1.3.0to verify prep phase logicscripts/release.sh --dry-run tag 1.3.0 abc1234to verify tag phase logicscripts/release.sh --dry-run validate 1.3.0to verify validate phase logicscripts/release.sh --dry-run publish 1.3.0to verify publish phase logicmake helpshows the new release targets