Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions .github/scripts/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,18 @@ validate_git_remote() {
exit 1
fi

# Accept both SSH and HTTPS formats for the docker/go-sdk repository
if [[ "$actual_origin" != "$EXPECTED_ORIGIN_SSH" ]] && \
[[ "$actual_origin" != "$EXPECTED_ORIGIN_HTTPS" ]]; then
# Normalize the origin URL for comparison:
# - Strip credentials (e.g., x-access-token:***@ from CI)
# - Strip trailing .git suffix
# This handles SSH, HTTPS, and CI token-authenticated URLs
local normalized_origin
normalized_origin=$(echo "$actual_origin" | sed -E 's|https://[^@]+@|https://|' | sed 's|\.git$||')

local expected_normalized="https://github.com/docker/go-sdk"
local expected_ssh="git@github.com:docker/go-sdk"

if [[ "$normalized_origin" != "$expected_normalized" ]] && \
[[ "$normalized_origin" != "$expected_ssh" ]]; then
echo "❌ Error: Git remote 'origin' points to the wrong repository"
echo " Expected: ${EXPECTED_ORIGIN_SSH}"
echo " (or ${EXPECTED_ORIGIN_HTTPS})"
Expand Down
183 changes: 183 additions & 0 deletions .github/scripts/prepare-release-pr.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
#!/bin/bash

# =============================================================================
# Prepare Release PR (Phase 1)
# =============================================================================
# Description: Creates a release branch, runs pre-release for target module(s),
# stages changes, commits, pushes the branch, and creates a PR.
# This is Phase 1 of the two-phase release process.
#
# Usage: ./.github/scripts/prepare-release-pr.sh [module]
#
# Arguments:
# module - Name of specific module to release (optional)
# If not provided, releases all modules
#
# Environment Variables:
# BUMP_TYPE - Type of version bump (default: prerelease)
#
# Dependencies:
# - git (configured with push permissions, origin must point to docker/go-sdk)
# - gh (GitHub CLI, for creating PRs)
# - jq (for parsing go.work)
# - Docker (for semver-tool, used by pre-release.sh)
#
# =============================================================================

set -e
Comment thread
mdelapenya marked this conversation as resolved.
Outdated

# Source common functions
readonly SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "${SCRIPT_DIR}/common.sh"

# Validate git remote before doing anything
validate_git_remote

MODULE="${1:-}"
BUMP_TYPE="${BUMP_TYPE:-prerelease}"
TIMESTAMP="$(date +%Y%m%d%H%M%S)"

# Determine branch name and commit title
if [[ -n "${MODULE}" ]]; then
BRANCH_NAME="release/bump-${MODULE}-${TIMESTAMP}"
COMMIT_TITLE="chore(${MODULE}): bump version"
else
BRANCH_NAME="release/bump-versions-${TIMESTAMP}"
COMMIT_TITLE="chore(release): bump module versions"
fi
Comment thread
mdelapenya marked this conversation as resolved.
Outdated
Comment on lines +37 to +48

Copilot AI Mar 4, 2026

Copy link

Choose a reason for hiding this comment

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

MODULE is lowercased and then used to build the branch name and to run pre-release, but there is no validation that the module exists in go.work (or that <module>/version.go exists). When run locally, a typo will fail later with less actionable errors and may leave you on a newly created release branch. Consider validating the module name against get_modules (and/or checking for the version.go file) early and failing fast with a clear error message, similar to the workflow’s validation.

Copilot uses AI. Check for mistakes.

# Ensure we start from a clean, up-to-date main branch
CURRENT_BRANCH=$(git -C "${ROOT_DIR}" rev-parse --abbrev-ref HEAD)
if [[ "${CURRENT_BRANCH}" != "main" ]]; then
echo "❌ Error: Must be on the 'main' branch to create a release PR"
echo " Current branch: ${CURRENT_BRANCH}"
echo ""
echo "Switch to main first:"
echo " git checkout main"
exit 1
Comment thread
mdelapenya marked this conversation as resolved.
fi

if [[ -n "$(git -C "${ROOT_DIR}" status --porcelain)" ]]; then
echo "❌ Error: Working tree is not clean"
echo " Commit or stash your changes before running a release."
exit 1
fi

echo "Fetching latest from origin..."
git -C "${ROOT_DIR}" fetch origin main
LOCAL_SHA=$(git -C "${ROOT_DIR}" rev-parse HEAD)
REMOTE_SHA=$(git -C "${ROOT_DIR}" rev-parse origin/main)
if [[ "${LOCAL_SHA}" != "${REMOTE_SHA}" ]]; then
echo "❌ Error: Local main is not up to date with origin/main"
echo " Local: ${LOCAL_SHA}"
echo " Remote: ${REMOTE_SHA}"
echo ""
echo "Update your local main first:"
echo " git pull origin main"
exit 1
fi

echo "=== Phase 1: Prepare Release PR ==="
echo " Module: ${MODULE:-all}"
echo " Bump type: ${BUMP_TYPE}"
echo " Branch: ${BRANCH_NAME}"
echo ""

# Create release branch from up-to-date main
git checkout -b "${BRANCH_NAME}"

# Clean build directory
rm -rf "${BUILD_DIR}"
mkdir -p "${BUILD_DIR}"

# Run pre-release for target module(s)
if [[ -n "${MODULE}" ]]; then
echo "Running pre-release for module: ${MODULE}"
env DRY_RUN=false BUMP_TYPE="${BUMP_TYPE}" "${SCRIPT_DIR}/pre-release.sh" "${MODULE}"
else
echo "Running pre-release for all modules..."
MODULES=$(get_modules)
for m in $MODULES; do
echo ""
echo "--- Pre-releasing module: ${m} ---"
env DRY_RUN=false BUMP_TYPE="${BUMP_TYPE}" "${SCRIPT_DIR}/pre-release.sh" "${m}"
done
fi

# Get all modules for staging
ALL_MODULES=$(get_modules)

# Determine which modules to include in version summary
if [[ -n "${MODULE}" ]]; then
MODULES_TO_TAG="${MODULE}"
else
MODULES_TO_TAG="${ALL_MODULES}"
fi

# Stage version.go files for released modules and build commit body
commit_body=""
for m in $MODULES_TO_TAG; do
next_tag_path=$(get_next_tag "${m}")
if [[ ! -f "${next_tag_path}" ]]; then
echo "Skipping ${m} because the pre-release script did not run"
continue
fi

git add "${ROOT_DIR}/${m}/version.go"
nextTag=$(cat "${next_tag_path}")
commit_body="${commit_body}\n - ${m}: ${nextTag}"
done

# Stage go.mod and go.sum for ALL modules
for m in $ALL_MODULES; do
git add "${ROOT_DIR}/${m}/go.mod"
if [[ -f "${ROOT_DIR}/${m}/go.sum" ]]; then
git add "${ROOT_DIR}/${m}/go.sum"
fi
done

# Verify there are staged changes
if [[ -z "$(git diff --cached)" ]]; then
echo "No changes detected. Aborting."
exit 1
fi

# Commit
git commit -m "${COMMIT_TITLE}" -m "$(echo -e "${commit_body}")"

# Push the branch
git push origin "${BRANCH_NAME}"

# Build PR body
PR_BODY="## Release Version Bump

**Bump type**: \`${BUMP_TYPE}\`

### Version changes:
$(echo -e "${commit_body}")

---
This PR was created automatically by the release workflow.
Merging this PR will trigger Phase 2 (automatic tagging and Go proxy update)."

# Create PR with gh
PR_URL=$(gh pr create \
--title "${COMMIT_TITLE}" \
--body "${PR_BODY}" \
--base main \
--head "${BRANCH_NAME}" \
--label "chore" \
2>&1) || {
echo "Warning: gh pr create failed. The branch has been pushed."
echo "You can create the PR manually from: ${BRANCH_NAME}"
echo "Error: ${PR_URL}"
exit 1
}

echo ""
echo "✅ Release PR created successfully!"
echo " PR: ${PR_URL}"
echo ""
echo "Next steps:"
echo " 1. Review the PR"
echo " 2. Merge it to trigger Phase 2 (automatic tagging)"
37 changes: 13 additions & 24 deletions .github/scripts/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@
# Git Operations:
# - Adds all modified version.go and go.mod files
# - Creates commit with version bump message (e.g. chore(client): bump version to v0.1.0-alpha005)
# - Creates tag with module name and version (e.g. client/v0.1.0-alpha005)
# - Pushes changes and tags to origin
#
# Post-Release Operations:
# - Triggers Go proxy to fetch new module versions
# - Makes modules immediately available for download
# Note: This script no longer pushes to main, creates tags, or triggers the
# Go proxy. Those operations are handled by the two-phase release process:
Comment thread
mdelapenya marked this conversation as resolved.
# - Phase 1: prepare-release-pr.sh (creates a PR)
# - Phase 2: tag-release.sh (tags after PR merge)
#
Comment thread
mdelapenya marked this conversation as resolved.
# =============================================================================

Expand Down Expand Up @@ -154,28 +153,18 @@ else
exit 1 # exit with error code 1 to not proceed with the release
fi

# Create all tags after the single commit
for m in $MODULES_TO_TAG; do
next_tag_path=$(get_next_tag "${m}")
if [[ -f "${next_tag_path}" ]]; then
nextTag=$(cat "${next_tag_path}")
execute_or_echo git tag "${m}/${nextTag}"
fi
done

echo ""
echo "✅ Created commit and tags successfully"
echo "✅ Created commit successfully"
echo "Last commit:"
git_log_format='%C(auto)%h%C(reset) %s%nAuthor: %an <%ae>%nDate: %ad'
execute_or_echo git -C "${ROOT_DIR}" --no-pager log -1 --pretty=format:"${git_log_format}" --date=iso-local
echo ""
execute_or_echo git -C "${ROOT_DIR}" --no-pager tag --list --points-at HEAD
echo ""

echo "Pushing changes and tags to remote repository..."
execute_or_echo git push origin main --tags

for m in $MODULES_TO_TAG; do
nextTag=$(cat $(get_next_tag "${m}"))
curlGolangProxy "${m}" "${nextTag}"
done
echo ""
echo "=========================================="
echo "NOTE: This script no longer pushes directly to main or creates tags."
echo "Use the two-phase release process instead:"
echo " Phase 1: ./.github/scripts/prepare-release-pr.sh — creates a release PR"
echo " Phase 2: ./.github/scripts/tag-release.sh — auto-tags after PR merge"
echo "See RELEASING.md for details."
Comment thread
mdelapenya marked this conversation as resolved.
echo "=========================================="
109 changes: 109 additions & 0 deletions .github/scripts/tag-release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/bin/bash

# =============================================================================
# Tag Release (Phase 2)
# =============================================================================
# Description: Creates git tags from version.go files and triggers Go proxy.
# Runs after a release PR is merged to main.
# This is Phase 2 of the two-phase release process.
#
# Usage: ./.github/scripts/tag-release.sh [module]
#
# Arguments:
# module - Name of specific module to tag (optional)
# If not provided, tags all modules with unreleased versions
#
# Key Properties:
# - Derives versions from version.go (no dependency on .build/ files)
# - Idempotent: existing tags are skipped
# - Squash-merge safe: tags the current HEAD (merge commit)
#
# Dependencies:
# - git (configured with push permissions)
# - jq (for parsing go.work)
# - curl (for triggering Go proxy)
Comment thread
mdelapenya marked this conversation as resolved.
#
# =============================================================================

set -e
Comment thread
mdelapenya marked this conversation as resolved.
Outdated

# Source common functions
readonly SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "${SCRIPT_DIR}/common.sh"

# Validate git remote before doing anything
validate_git_remote

MODULE="${1:-}"

echo "=== Phase 2: Tag Release ==="
echo ""

# Determine which modules to process
if [[ -n "${MODULE}" ]]; then
MODULES_TO_TAG="${MODULE}"
else
MODULES_TO_TAG=$(get_modules)
fi

tags_created=0
tags_skipped=0

for m in $MODULES_TO_TAG; do
VERSION_FILE="${ROOT_DIR}/${m}/version.go"

if [[ ! -f "${VERSION_FILE}" ]]; then
Comment thread
mdelapenya marked this conversation as resolved.
echo "⚠️ Skipping ${m}: version.go not found"
continue
fi

# Read version from version.go
VERSION=$(get_version_from_file "${VERSION_FILE}")
if [[ -z "${VERSION}" ]]; then
echo "⚠️ Skipping ${m}: could not extract version from version.go"
continue
fi

# Ensure version has v prefix
if [[ ! "${VERSION}" =~ ^v ]]; then
VERSION="v${VERSION}"
fi

TAG="${m}/${VERSION}"

# Check if tag already exists (locally or remotely)
Comment thread
mdelapenya marked this conversation as resolved.
Outdated
if git tag --list | grep -q "^${TAG}$" || git ls-remote --tags origin "${TAG}" 2>/dev/null | grep -q "${TAG}"; then
Comment thread
mdelapenya marked this conversation as resolved.
Outdated
echo "⏭️ Skipping ${m}: tag ${TAG} already exists"
tags_skipped=$((tags_skipped + 1))
continue
fi
Comment on lines +94 to +100

Copilot AI Mar 4, 2026

Copy link

Choose a reason for hiding this comment

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

Remote-tag existence check can produce false positives because it searches for the substring refs/tags/${TAG}. For example, checking for client/v1.0.0 would match an existing client/v1.0.0-alpha001 tag and incorrectly skip tag creation. Use an exact ref lookup (e.g., git ls-remote --tags --refs origin "refs/tags/${TAG}") or ensure the grep matches the full ref name (end-of-line) to avoid prefix matches.

Copilot uses AI. Check for mistakes.

# Create tag on HEAD and push it individually
echo "🏷️ Creating tag: ${TAG}"
git tag "${TAG}"
git push origin "${TAG}"
Comment thread
mdelapenya marked this conversation as resolved.
Outdated
tags_created=$((tags_created + 1))

# Trigger Go proxy
echo "📦 Triggering Go proxy for ${m}@${VERSION}..."
curlGolangProxy "${m}" "${VERSION}"
echo ""
done

echo ""
echo "=== Tag Release Summary ==="
echo " Tags created: ${tags_created}"
echo " Tags skipped (already exist): ${tags_skipped}"

if [[ ${tags_created} -gt 0 ]]; then
echo ""
echo "✅ Tags created and pushed successfully!"
echo "Tags on HEAD:"
git --no-pager tag --list --points-at HEAD
elif [[ ${tags_skipped} -gt 0 ]]; then
echo ""
echo "✅ All tags already exist — nothing to do (idempotent)."
else
echo ""
echo "⚠️ No modules were processed."
Comment on lines +131 to +144

Copilot AI Mar 4, 2026

Copy link

Choose a reason for hiding this comment

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

In DRY_RUN=true mode, this script still increments tags_created and later prints "Tags created and pushed successfully!" even though execute_or_echo does not actually create/push tags. This makes dry-run output misleading. Consider either not incrementing tags_created in dry run, or using separate counters/messages for "would create" vs "created", and avoid reporting a successful push when nothing was executed.

Suggested change
echo " Tags created: ${tags_created}"
echo " Tags skipped (already exist): ${tags_skipped}"
if [[ ${tags_created} -gt 0 ]]; then
echo ""
echo "✅ Tags created and pushed successfully!"
echo "Tags on HEAD:"
git --no-pager tag --list --points-at HEAD
elif [[ ${tags_skipped} -gt 0 ]]; then
echo ""
echo "✅ All tags already exist — nothing to do (idempotent)."
else
echo ""
echo "⚠️ No modules were processed."
if [[ "${DRY_RUN:-false}" == "true" ]]; then
echo " Tags that would be created: ${tags_created}"
echo " Tags skipped (already exist): ${tags_skipped}"
if [[ ${tags_created} -gt 0 ]]; then
echo ""
echo "ℹ️ DRY RUN: No tags were actually created or pushed."
elif [[ ${tags_skipped} -gt 0 ]]; then
echo ""
echo "ℹ️ DRY RUN: All tags already exist — no changes would be made."
else
echo ""
echo "ℹ️ DRY RUN: No modules were processed."
fi
else
echo " Tags created: ${tags_created}"
echo " Tags skipped (already exist): ${tags_skipped}"
if [[ ${tags_created} -gt 0 ]]; then
echo ""
echo "✅ Tags created and pushed successfully!"
echo "Tags on HEAD:"
git --no-pager tag --list --points-at HEAD
elif [[ ${tags_skipped} -gt 0 ]]; then
echo ""
echo "✅ All tags already exist — nothing to do (idempotent)."
else
echo ""
echo "⚠️ No modules were processed."
fi

Copilot uses AI. Check for mistakes.
fi
Loading