Skip to content

feat: add release automation script#1992

Open
TerryHowe wants to merge 3 commits intooras-project:mainfrom
TerryHowe:feature/release-script
Open

feat: add release automation script#1992
TerryHowe wants to merge 3 commits intooras-project:mainfrom
TerryHowe:feature/release-script

Conversation

@TerryHowe
Copy link
Copy Markdown
Member

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, checks gh/gpg prerequisites, updates internal/version/version.go (sets Version, clears BuildMetadata), creates branch chore/release-v<version>, commits, pushes, creates a PR titled "bump: tag and release ORAS CLI v<version>" targeting main (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 tag v<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 the release-ghcr and release-github GitHub Actions workflows until they complete, runs make fetch-dist to download artifacts, verifies checksums with shasum, and tests the linux/amd64 binary reports the expected version.

  • publish <version> - Runs make sign to GPG-sign artifacts, verifies all GPG signatures, uploads .asc files 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-run flag for safe testing
  • ORAS_REMOTE env var to configure the upstream remote (default: upstream)
  • Color-coded output for readability
  • Confirmation prompts before all destructive actions
  • Slack templates for vote and announcement

Makefile Targets

Also adds convenience Makefile targets: release-prep, release-tag, release-validate, release-publish.

Test plan

  • Run scripts/release.sh --help to verify usage output
  • Run scripts/release.sh --dry-run prep 1.3.0 to verify prep phase logic
  • Run scripts/release.sh --dry-run tag 1.3.0 abc1234 to verify tag phase logic
  • Run scripts/release.sh --dry-run validate 1.3.0 to verify validate phase logic
  • Run scripts/release.sh --dry-run publish 1.3.0 to verify publish phase logic
  • Verify make help shows the new release targets

Signed-off-by: Terry Howe <terrylhowe@gmail.com>
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.21%. Comparing base (a69a6aa) to head (34a0f9b).
⚠️ Report is 8 commits behind head on main.

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.sh implementing prep, tag, validate, and publish release phases (with --dry-run support).
  • 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.

Comment on lines +144 to +145
info "Pushing branch '${branch}' to origin..."
run git push origin "$branch"
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
info "Pushing branch '${branch}' to origin..."
run git push origin "$branch"
info "Pushing branch '${branch}' to ${REMOTE}..."
run git push "$REMOTE" "$branch"

Copilot uses AI. Check for mistakes.
local version="$1"
local sha="$2"
validate_semver "$version"

Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
# Ensure GPG, GitHub auth, and remote are correctly configured before tagging
check_prerequisites

Copilot uses AI. Check for mistakes.
Comment on lines +303 to +304
if ! shasum -a 256 -c "oras_${version}_checksums.txt" --ignore-missing; then
fatal "Checksum verification failed!"
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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."

Copilot uses AI. Check for mistakes.

# Sign artifacts
info "Signing artifacts..."
run make sign
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
run make sign
run make SHELL=/bin/bash sign

Copilot uses AI. Check for mistakes.
Comment on lines +361 to +367
# 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
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +417 to +420
# Clean up
info "Cleaning up _dist/ directory..."
run rm -rf _dist/

Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +121 to +129
# 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}"

Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +347 to +368
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."
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
Comment on lines +412 to +413
if [[ "$DRY_RUN" != "true" ]]; then
gh workflow run snap.yml --repo "$REPO" --ref "v${version}" 2>/dev/null || \
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
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 || \

Copilot uses AI. Check for mistakes.
- 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>
Copy link
Copy Markdown
Contributor

@sabre1041 sabre1041 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I addition, should there be a variable for oras-project/oras?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants