Skip to content

Commit 9dd9fde

Browse files
authored
feat(release): release process using PRs (no push to main allowed) (#143)
1 parent 611da09 commit 9dd9fde

10 files changed

Lines changed: 714 additions & 378 deletions

File tree

.github/scripts/common.sh

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,18 @@ validate_git_remote() {
8181
exit 1
8282
fi
8383

84-
# Accept both SSH and HTTPS formats for the docker/go-sdk repository
85-
if [[ "$actual_origin" != "$EXPECTED_ORIGIN_SSH" ]] && \
86-
[[ "$actual_origin" != "$EXPECTED_ORIGIN_HTTPS" ]]; then
84+
# Normalize the origin URL for comparison:
85+
# - Strip credentials (e.g., x-access-token:***@ from CI)
86+
# - Strip trailing .git suffix
87+
# This handles SSH, HTTPS, and CI token-authenticated URLs
88+
local normalized_origin
89+
normalized_origin=$(echo "$actual_origin" | sed -E 's|https://[^@]+@|https://|' | sed 's|\.git$||')
90+
91+
local expected_normalized="https://github.com/docker/go-sdk"
92+
local expected_ssh="git@github.com:docker/go-sdk"
93+
94+
if [[ "$normalized_origin" != "$expected_normalized" ]] && \
95+
[[ "$normalized_origin" != "$expected_ssh" ]]; then
8796
echo "❌ Error: Git remote 'origin' points to the wrong repository"
8897
echo " Expected: ${EXPECTED_ORIGIN_SSH}"
8998
echo " (or ${EXPECTED_ORIGIN_HTTPS})"
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
#!/bin/bash
2+
3+
# =============================================================================
4+
# Prepare Release PR (Phase 1)
5+
# =============================================================================
6+
# Description: Creates a release branch, runs pre-release for target module(s),
7+
# stages changes, commits, pushes the branch, and creates a PR.
8+
# This is Phase 1 of the two-phase release process.
9+
#
10+
# Usage: ./.github/scripts/prepare-release-pr.sh [module]
11+
#
12+
# Arguments:
13+
# module - Name of specific module to release (optional)
14+
# If not provided, releases all modules
15+
#
16+
# Environment Variables:
17+
# BUMP_TYPE - Type of version bump (default: prerelease)
18+
#
19+
# Dependencies:
20+
# - git (configured with push permissions, origin must point to docker/go-sdk)
21+
# - go (for go.work parsing and go mod tidy)
22+
# - gh (GitHub CLI, for creating PRs)
23+
# - jq (for parsing go.work)
24+
# - Docker (for semver-tool, used by pre-release.sh)
25+
#
26+
# =============================================================================
27+
28+
set -eo pipefail
29+
30+
# Source common functions
31+
readonly SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
32+
source "${SCRIPT_DIR}/common.sh"
33+
34+
# Validate git remote before doing anything
35+
validate_git_remote
36+
37+
MODULE=$(echo "${1:-}" | tr '[:upper:]' '[:lower:]')
38+
BUMP_TYPE="${BUMP_TYPE:-prerelease}"
39+
TIMESTAMP="$(date +%Y%m%d%H%M%S)"
40+
41+
# Determine branch name and commit title
42+
if [[ -n "${MODULE}" ]]; then
43+
BRANCH_NAME="release/bump-${MODULE}-${TIMESTAMP}"
44+
COMMIT_TITLE="chore(${MODULE}): bump version"
45+
else
46+
BRANCH_NAME="release/bump-versions-${TIMESTAMP}"
47+
COMMIT_TITLE="chore(release): bump module versions"
48+
fi
49+
50+
# Ensure we start from a clean, up-to-date main branch
51+
CURRENT_BRANCH=$(git -C "${ROOT_DIR}" rev-parse --abbrev-ref HEAD)
52+
if [[ "${CURRENT_BRANCH}" != "main" ]]; then
53+
echo "❌ Error: Must be on the 'main' branch to create a release PR"
54+
echo " Current branch: ${CURRENT_BRANCH}"
55+
echo ""
56+
echo "Switch to main first:"
57+
echo " git checkout main"
58+
exit 1
59+
fi
60+
61+
if [[ -n "$(git -C "${ROOT_DIR}" status --porcelain)" ]]; then
62+
echo "❌ Error: Working tree is not clean"
63+
echo " Commit or stash your changes before running a release."
64+
exit 1
65+
fi
66+
67+
echo "Fetching latest from origin..."
68+
git -C "${ROOT_DIR}" fetch origin main
69+
LOCAL_SHA=$(git -C "${ROOT_DIR}" rev-parse HEAD)
70+
REMOTE_SHA=$(git -C "${ROOT_DIR}" rev-parse origin/main)
71+
if [[ "${LOCAL_SHA}" != "${REMOTE_SHA}" ]]; then
72+
echo "❌ Error: Local main is not up to date with origin/main"
73+
echo " Local: ${LOCAL_SHA}"
74+
echo " Remote: ${REMOTE_SHA}"
75+
echo ""
76+
echo "Update your local main first:"
77+
echo " git pull origin main"
78+
exit 1
79+
fi
80+
81+
echo "=== Phase 1: Prepare Release PR ==="
82+
echo " Module: ${MODULE:-all}"
83+
echo " Bump type: ${BUMP_TYPE}"
84+
echo " Branch: ${BRANCH_NAME}"
85+
echo ""
86+
87+
# Create release branch from up-to-date main
88+
git checkout -b "${BRANCH_NAME}"
89+
90+
# Clean build directory
91+
rm -rf "${BUILD_DIR}"
92+
mkdir -p "${BUILD_DIR}"
93+
94+
# Run pre-release for target module(s)
95+
if [[ -n "${MODULE}" ]]; then
96+
echo "Running pre-release for module: ${MODULE}"
97+
env DRY_RUN=false BUMP_TYPE="${BUMP_TYPE}" "${SCRIPT_DIR}/pre-release.sh" "${MODULE}"
98+
else
99+
echo "Running pre-release for all modules..."
100+
MODULES=$(get_modules)
101+
for m in $MODULES; do
102+
echo ""
103+
echo "--- Pre-releasing module: ${m} ---"
104+
env DRY_RUN=false BUMP_TYPE="${BUMP_TYPE}" "${SCRIPT_DIR}/pre-release.sh" "${m}"
105+
done
106+
fi
107+
108+
# Get all modules for staging
109+
ALL_MODULES=$(get_modules)
110+
111+
# Determine which modules to include in version summary
112+
if [[ -n "${MODULE}" ]]; then
113+
MODULES_TO_TAG="${MODULE}"
114+
else
115+
MODULES_TO_TAG="${ALL_MODULES}"
116+
fi
117+
118+
# Stage version.go files for released modules and build commit body
119+
commit_body=""
120+
for m in $MODULES_TO_TAG; do
121+
next_tag_path=$(get_next_tag "${m}")
122+
if [[ ! -f "${next_tag_path}" ]]; then
123+
echo "Skipping ${m} because the pre-release script did not run"
124+
continue
125+
fi
126+
127+
git add "${ROOT_DIR}/${m}/version.go"
128+
nextTag=$(cat "${next_tag_path}")
129+
commit_body="${commit_body}\n - ${m}: ${nextTag}"
130+
done
131+
132+
# Stage go.mod and go.sum for ALL modules
133+
for m in $ALL_MODULES; do
134+
git add "${ROOT_DIR}/${m}/go.mod"
135+
if [[ -f "${ROOT_DIR}/${m}/go.sum" ]]; then
136+
git add "${ROOT_DIR}/${m}/go.sum"
137+
fi
138+
done
139+
140+
# Verify there are staged changes
141+
if [[ -z "$(git diff --cached)" ]]; then
142+
echo "No changes detected. Aborting."
143+
exit 1
144+
fi
145+
146+
# Commit
147+
git commit -m "${COMMIT_TITLE}" -m "$(echo -e "${commit_body}")"
148+
149+
# Push the branch
150+
git push origin "${BRANCH_NAME}"
151+
152+
# Build PR body
153+
PR_BODY="## Release Version Bump
154+
155+
**Bump type**: \`${BUMP_TYPE}\`
156+
157+
### Version changes:
158+
$(echo -e "${commit_body}")
159+
160+
---
161+
This PR was created automatically by the release workflow.
162+
Merging this PR will trigger Phase 2 (automatic tagging and Go proxy update)."
163+
164+
# Create PR with gh
165+
PR_URL=$(gh pr create \
166+
--title "${COMMIT_TITLE}" \
167+
--body "${PR_BODY}" \
168+
--base main \
169+
--head "${BRANCH_NAME}" \
170+
--label "chore" \
171+
2>&1) || {
172+
echo "Warning: gh pr create failed. The branch has been pushed."
173+
echo "You can create the PR manually from: ${BRANCH_NAME}"
174+
echo "Error: ${PR_URL}"
175+
exit 1
176+
}
177+
178+
echo ""
179+
echo "✅ Release PR created successfully!"
180+
echo " PR: ${PR_URL}"
181+
echo ""
182+
echo "Next steps:"
183+
echo " 1. Review the PR"
184+
echo " 2. Merge it to trigger Phase 2 (automatic tagging)"

.github/scripts/release.sh

Lines changed: 29 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
#!/bin/bash
22

33
# =============================================================================
4-
# Release Finalizer
4+
# Release Committer
55
# =============================================================================
6-
# Description: Commits and tags version changes for modules, then triggers
7-
# Go proxy to make the new versions available for download
6+
# Description: Stages and commits version changes for modules.
87
# This script is typically run after pre-release.sh has
9-
# updated module versions
8+
# updated module versions. It creates a local commit only —
9+
# pushing, tagging, and Go proxy notification are handled by
10+
# prepare-release-pr.sh (Phase 1) and tag-release.sh (Phase 2).
1011
#
1112
# Usage: ./.github/scripts/release.sh [module]
1213
#
@@ -16,7 +17,7 @@
1617
#
1718
# Environment Variables:
1819
# DRY_RUN - Enable dry run mode (default: true)
19-
# When true, shows what would be committed and tagged without actually doing it
20+
# When true, shows what would be committed without actually doing it
2021
#
2122
# Examples:
2223
# ./.github/scripts/release.sh
@@ -26,18 +27,17 @@
2627
#
2728
# Dependencies:
2829
# - git (configured with push permissions)
30+
# - go (for go.work parsing via 'go work edit -json')
2931
# - jq (for parsing go.work)
30-
# - curl (for triggering Go proxy)
3132
#
3233
# Git Operations:
3334
# - Adds all modified version.go and go.mod files
34-
# - Creates commit with version bump message (e.g. chore(client): bump version to v0.1.0-alpha005)
35-
# - Creates tag with module name and version (e.g. client/v0.1.0-alpha005)
36-
# - Pushes changes and tags to origin
35+
# - Creates commit with version bump message (e.g. chore(client): bump version)
3736
#
38-
# Post-Release Operations:
39-
# - Triggers Go proxy to fetch new module versions
40-
# - Makes modules immediately available for download
37+
# Note: This script no longer pushes to main, creates tags, or triggers the
38+
# Go proxy. Those operations are handled by the two-phase release process:
39+
# - Phase 1: prepare-release-pr.sh (creates a PR)
40+
# - Phase 2: tag-release.sh (tags after PR merge)
4141
#
4242
# =============================================================================
4343

@@ -67,9 +67,8 @@ fi
6767
ALL_MODULES=$(get_modules)
6868

6969
commit_body=""
70-
tags_to_create=""
7170

72-
# Determine which modules to tag
71+
# Determine which modules to process
7372
if [[ -n "${MODULE}" ]]; then
7473
MODULES_TO_TAG="${MODULE}"
7574
else
@@ -92,7 +91,6 @@ for m in $MODULES_TO_TAG; do
9291
nextTag=$(cat "${next_tag_path}")
9392
echo "Next tag for ${m}: ${nextTag}"
9493
commit_body="${commit_body}\n - ${m}: ${nextTag}"
95-
tags_to_create="${tags_to_create} ${m}/${nextTag}"
9694
done
9795

9896
# Stage go.mod and go.sum for ALL modules (they all need to reference the new version)
@@ -106,18 +104,13 @@ done
106104
if [[ "${DRY_RUN}" == "true" ]]; then
107105
echo ""
108106
echo "=========================================="
109-
echo "DRY RUN MODE - No changes will be made"
107+
echo "DRY RUN MODE - No git changes will be made"
110108
echo "=========================================="
111109
echo ""
112-
echo "Would create commit:"
110+
echo "Would create commit (local only, no push):"
113111
echo " Title: ${commit_title}"
114112
echo " Body: $(echo -e "${commit_body}")"
115113
echo ""
116-
echo "Would create tags:"
117-
for t in $tags_to_create; do
118-
echo " ${t}"
119-
done
120-
echo ""
121114
echo "Files that would be committed:"
122115
for m in $MODULES_TO_TAG; do
123116
next_tag_path=$(get_next_tag "${m}")
@@ -140,7 +133,11 @@ if [[ "${DRY_RUN}" == "true" ]]; then
140133
done
141134
echo ""
142135
echo "=========================================="
143-
echo "To perform the actual release, run:"
136+
echo "NOTE: This script only creates a local commit."
137+
echo "Tags and pushing are handled by the two-phase release process."
138+
echo "See RELEASING.md for details."
139+
echo ""
140+
echo "To perform the actual commit, run:"
144141
echo " DRY_RUN=false $0 $@"
145142
echo "=========================================="
146143
exit 0
@@ -154,28 +151,18 @@ else
154151
exit 1 # exit with error code 1 to not proceed with the release
155152
fi
156153

157-
# Create all tags after the single commit
158-
for m in $MODULES_TO_TAG; do
159-
next_tag_path=$(get_next_tag "${m}")
160-
if [[ -f "${next_tag_path}" ]]; then
161-
nextTag=$(cat "${next_tag_path}")
162-
execute_or_echo git tag "${m}/${nextTag}"
163-
fi
164-
done
165-
166154
echo ""
167-
echo "✅ Created commit and tags successfully"
155+
echo "✅ Created commit successfully"
168156
echo "Last commit:"
169157
git_log_format='%C(auto)%h%C(reset) %s%nAuthor: %an <%ae>%nDate: %ad'
170158
execute_or_echo git -C "${ROOT_DIR}" --no-pager log -1 --pretty=format:"${git_log_format}" --date=iso-local
171159
echo ""
172-
execute_or_echo git -C "${ROOT_DIR}" --no-pager tag --list --points-at HEAD
173-
echo ""
174-
175-
echo "Pushing changes and tags to remote repository..."
176-
execute_or_echo git push origin main --tags
177160

178-
for m in $MODULES_TO_TAG; do
179-
nextTag=$(cat $(get_next_tag "${m}"))
180-
curlGolangProxy "${m}" "${nextTag}"
181-
done
161+
echo ""
162+
echo "=========================================="
163+
echo "NOTE: This script no longer pushes directly to main or creates tags."
164+
echo "Use the two-phase release process instead:"
165+
echo " Phase 1: ./.github/scripts/prepare-release-pr.sh — creates a release PR"
166+
echo " Phase 2: ./.github/scripts/tag-release.sh — auto-tags after PR merge"
167+
echo "See RELEASING.md for details."
168+
echo "=========================================="

0 commit comments

Comments
 (0)