Files
kserve/hack/release/create-branch-tag.sh
2026-04-28 18:37:13 -04:00

549 lines
16 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Helper script to create KServe releases
set -eo pipefail
# Detect the upstream remote (kserve/kserve)
detect_upstream_remote() {
# Check all remotes for kserve/kserve
for remote in $(git remote); do
local url=$(git remote get-url "$remote" 2>/dev/null || echo "")
if [[ "$url" == *"kserve/kserve"* ]]; then
echo "$remote"
return 0
fi
done
# Not found
echo ""
return 1
}
# ============================================================
# Color codes for output
# ============================================================
RED='\033[31m'
GREEN='\033[32m'
YELLOW='\033[33m'
BLUE='\033[34m'
NC='\033[0m' # No Color
# ============================================================
# Global variables
# ============================================================
VERSION=""
DRY_RUN=true
VALIDATE_ONLY=false
GITHUB_ACTIONS_MODE=false
BASE_VERSION=""
RC=""
BRANCH=""
RELEASE_TYPE=""
# ============================================================
# Helper functions
# ============================================================
print_error() {
echo -e "${RED}❌ $1${NC}"
}
print_success() {
echo -e "${GREEN}✅ $1${NC}"
}
print_info() {
echo -e "${BLUE}ℹ️ $1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_section() {
echo ""
echo -e "${GREEN}$1${NC}"
}
usage() {
cat <<EOF
Usage: $0 <version> [OPTIONS]
Create a KServe release (branch and tag)
Default behavior is dry-run (validate + show plan). Use --execute to actually create.
Arguments:
version Release version (e.g., v0.17.0-rc0, v0.17.0-rc1, v0.17.0)
Options:
--execute Actually create branch and tag (default is dry-run)
--validate-only Only run validation checks and exit
-h, --help Show this help message
Examples:
# Dry-run (default): validate and show execution plan
$0 v0.17.0-rc0
# Execute: create RC0 (branch + tag)
$0 v0.17.0-rc0 --execute
# Execute: create RC1 (tag only)
$0 v0.17.0-rc1 --execute
# Execute: create final release (tag only)
$0 v0.17.0 --execute
Release Types:
v0.17.0-rc0 → RC0 (creates release-0.17 branch + tag)
v0.17.0-rc1 → RC1+ (creates tag on existing branch)
v0.17.0 → Final (creates tag on existing branch)
Note:
- This script creates branches and tags only
- Use publish-release.sh to create the GitHub Release
- Requires push access to kserve/kserve repository
EOF
exit 0
}
# ============================================================
# Validation functions
# ============================================================
validate_version_format() {
print_section "🔍 Phase 1: Validating version format..."
local pattern="^v[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?$"
if [[ ! $VERSION =~ $pattern ]]; then
print_error "Invalid version format: $VERSION"
echo ""
echo "Valid formats:"
echo " - v0.17.0-rc0 (Release Candidate 0)"
echo " - v0.17.0-rc1 (Release Candidate 1)"
echo " - v0.17.0 (Final Release)"
exit 1
fi
print_success "Version format is valid"
}
parse_version() {
print_section "🔍 Parsing version components..."
# Remove leading 'v'
local version_no_v=$(echo "$VERSION" | sed 's/^v//')
# Extract base version (e.g., 0.17.0)
BASE_VERSION=$(echo "$version_no_v" | sed 's/-rc[0-9]*$//')
# Extract major.minor only for branch name (e.g., 0.17)
# KServe uses release-0.16, release-0.17 (not release-0.16.0)
local major_minor=$(echo "$BASE_VERSION" | cut -d. -f1,2)
# Extract RC suffix (e.g., rc0, rc1, or empty for final)
RC=$(echo "$version_no_v" | grep -o 'rc[0-9]*$' || echo "")
# Determine release type
if [[ "$RC" == "rc0" ]]; then
RELEASE_TYPE="RC0 (Initial Release Candidate)"
elif [[ "$RC" =~ ^rc[1-9][0-9]*$ ]]; then
RELEASE_TYPE="RC${RC#rc}+ (Bug Fix Release Candidate)"
elif [[ -z "$RC" ]]; then
RELEASE_TYPE="Final Release"
else
print_error "Invalid RC format: $RC"
exit 1
fi
BRANCH="release-${major_minor}"
echo " Base Version: $BASE_VERSION"
echo " RC Suffix: ${RC:-none}"
echo " Release Type: $RELEASE_TYPE"
echo " Target Branch: $BRANCH"
print_success "Version parsed successfully"
}
validate_kserve_deps() {
print_section "🔍 Phase 2: Validating kserve-deps.env..."
if [[ ! -f "kserve-deps.env" ]]; then
print_error "kserve-deps.env file not found!"
exit 1
fi
local current_version=$(grep KSERVE_VERSION= kserve-deps.env | cut -d= -f2)
if [[ "$current_version" != "$VERSION" ]]; then
print_error "Version mismatch!"
echo " Input version: $VERSION"
echo " kserve-deps.env: $current_version"
echo ""
echo "Please run prepare-for-release.sh first:"
echo " ./hack/release/prepare-for-release.sh <prior_version> <new_version>"
exit 1
fi
print_success "kserve-deps.env version matches: $current_version"
}
validate_tag_duplicate() {
print_section "🔍 Phase 3: Checking for duplicate tag on upstream..."
if git ls-remote --tags "$UPSTREAM_REMOTE" "$VERSION" 2>/dev/null | grep -q "refs/tags/$VERSION"; then
print_error "Tag $VERSION already exists on $UPSTREAM_REMOTE!"
echo ""
echo "Check the tag on upstream:"
echo " git ls-remote --tags $UPSTREAM_REMOTE $VERSION"
exit 1
fi
# Clean up stale local tag that doesn't exist on upstream
if git rev-parse "$VERSION" >/dev/null 2>&1; then
print_warning "Tag $VERSION exists locally but NOT on $UPSTREAM_REMOTE. Deleting stale local tag."
git tag -d "$VERSION"
fi
print_success "Tag $VERSION does not exist on $UPSTREAM_REMOTE (OK)"
}
validate_github_release_duplicate() {
print_section "🔍 Phase 4: Checking for duplicate GitHub Release..."
# Skip actual check if --github-actions is not set
if [[ "$GITHUB_ACTIONS_MODE" == false ]]; then
print_info "GitHub Release check requires --github-actions flag"
return
fi
# Check if gh CLI is available
if ! command -v gh >/dev/null 2>&1; then
print_warning "gh CLI not found, skipping GitHub Release duplicate check"
print_info "Install gh CLI to enable this check: https://cli.github.com/"
return
fi
# Check if release exists
if gh release view "$VERSION" --repo kserve/kserve >/dev/null 2>&1; then
print_error "GitHub Release $VERSION already exists!"
echo ""
echo "Existing release information:"
gh release view "$VERSION" --repo kserve/kserve --json name,tagName,isPrerelease,createdAt,url \
--template '{{printf " Name: %s\n Tag: %s\n Pre-release: %v\n Created: %s\n URL: %s\n" .name .tagName .isPrerelease .createdAt .url}}'
exit 1
fi
print_success "GitHub Release $VERSION does not exist (OK)"
}
validate_branch_rc0() {
if [[ "$RC" != "rc0" ]]; then
return
fi
print_section "🔍 Phase 5: Checking release branch (RC0 mode)..."
if git ls-remote --heads "$UPSTREAM_REMOTE" "$BRANCH" 2>/dev/null | grep -q "$BRANCH"; then
print_error "Branch $BRANCH already exists!"
echo " RC0 should create a new release branch."
echo ""
echo "If you want to create RC1+, use version like: v${BASE_VERSION}-rc1"
exit 1
fi
print_success "Branch $BRANCH does not exist (OK for RC0)"
}
validate_branch_rc1_plus() {
if [[ "$RC" == "rc0" ]]; then
return
fi
print_section "🔍 Phase 6: Checking release branch (RC1+/Final mode)..."
if ! git ls-remote --heads "$UPSTREAM_REMOTE" "$BRANCH" 2>/dev/null | grep -q "$BRANCH"; then
print_error "Branch $BRANCH does not exist!"
echo " RC1+ and Final releases require an existing release branch."
echo ""
echo "Please create RC0 first: v${BASE_VERSION}-rc0"
exit 1
fi
print_success "Branch $BRANCH exists (OK for RC1+/Final)"
}
# ============================================================
# Execution functions
# ============================================================
show_dry_run_plan() {
echo ""
echo "=================================================="
echo -e "${BLUE}🔍 DRY-RUN MODE - No changes will be made${NC}"
echo "=================================================="
echo ""
echo "📋 Release Information:"
echo " Version: $VERSION"
echo " Type: $RELEASE_TYPE"
echo " Branch: $BRANCH"
if [[ "$RC" == "rc0" ]]; then
echo " Base: master"
else
echo " Base: $BRANCH (existing)"
fi
echo ""
echo "🔨 Actions to be performed by this script:"
if [[ "$RC" == "rc0" ]]; then
echo " 1. Create release branch: $BRANCH (from master)"
echo " 2. Push branch to remote"
echo " 3. Create tag: $VERSION"
echo " 4. Push tag to remote"
else
echo " 1. Checkout existing branch: $BRANCH"
echo " 2. Create tag: $VERSION"
echo " 3. Push tag to remote"
fi
echo ""
echo "🔨 Additional actions (performed by GitHub Actions):"
if [[ -n "$RC" ]]; then
echo " - Create GitHub pre-release: $VERSION"
else
echo " - Create GitHub final release: $VERSION"
fi
echo ""
echo "=================================================="
print_success "All validations passed!"
echo "=================================================="
echo ""
echo "🚀 To execute this release, run:"
echo " ./hack/release/create-branch-tag.sh $VERSION --execute"
echo ""
}
handle_error() {
echo ""
echo "=================================================="
print_error "Error occurred! Rolling back..."
echo "=================================================="
# Delete tag if created
if git rev-parse "$VERSION" >/dev/null 2>&1; then
echo "Deleting local tag $VERSION..."
git tag -d "$VERSION" 2>/dev/null || true
fi
if git ls-remote --tags "$UPSTREAM_REMOTE" 2>/dev/null | grep -q "refs/tags/$VERSION"; then
echo "Deleting remote tag $VERSION..."
git push --delete "$UPSTREAM_REMOTE" "$VERSION" 2>/dev/null || true
fi
# Delete branch if created (RC0 only)
if [[ "$RC" == "rc0" ]]; then
if git ls-remote --heads "$UPSTREAM_REMOTE" "$BRANCH" 2>/dev/null | grep -q "$BRANCH"; then
echo "Deleting remote branch $BRANCH..."
git push --delete "$UPSTREAM_REMOTE" "$BRANCH" 2>/dev/null || true
fi
fi
echo "=================================================="
print_error "Rollback completed"
echo "=================================================="
exit 1
}
create_rc0() {
print_section "Step 1: Creating release branch from master..."
git checkout master
git pull "$UPSTREAM_REMOTE" master
git checkout -b "$BRANCH"
git push "$UPSTREAM_REMOTE" "$BRANCH"
print_success "Created and pushed branch: $BRANCH"
}
create_rc1_plus_or_final() {
print_section "Step 1: Checking out existing release branch..."
git fetch "$UPSTREAM_REMOTE" "$BRANCH"
git checkout "$BRANCH"
git pull "$UPSTREAM_REMOTE" "$BRANCH"
print_success "Checked out branch: $BRANCH"
}
create_tag() {
print_section "Step 2: Creating tag..."
git tag "$VERSION"
git push "$UPSTREAM_REMOTE" "$VERSION"
print_success "Created and pushed tag: $VERSION"
}
execute_release() {
echo ""
echo "=================================================="
print_info "🚀 EXECUTION MODE - Creating release..."
echo "=================================================="
echo ""
# Setup error handler
trap 'handle_error' ERR
# Configure git
if ! git config user.email >/dev/null 2>&1; then
git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
fi
# Execute based on release type
if [[ "$RC" == "rc0" ]]; then
create_rc0
else
create_rc1_plus_or_final
fi
create_tag
echo ""
echo "=================================================="
print_success "Branch and tag created successfully!"
echo "=================================================="
echo ""
echo "Branch: $BRANCH"
echo "Tag: $VERSION"
echo ""
echo "Next steps:"
echo " ✅ Branch and tag created successfully"
echo ""
echo " Next: Create GitHub Release (requires project lead)"
echo " URL: https://github.com/kserve/kserve/releases/new?tag=$VERSION"
echo ""
}
# ============================================================
# Main script
# ============================================================
main() {
# Check if running from repository root
if [[ ! -f "kserve-deps.env" ]]; then
print_error "This script must be run from the repository root directory"
exit 1
fi
# Parse arguments
if [[ $# -eq 0 ]] || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then
usage
fi
VERSION="$1"
shift
while [[ $# -gt 0 ]]; do
case $1 in
--execute)
DRY_RUN=false
shift
;;
--dry-run)
# Default behavior, kept for explicit usage
DRY_RUN=true
shift
;;
--validate-only)
VALIDATE_ONLY=true
shift
;;
-h|--help)
usage
;;
*)
print_error "Unknown option: $1"
usage
;;
esac
done
# Validate option combinations
if [[ "$DRY_RUN" == true ]] && [[ "$VALIDATE_ONLY" == true ]]; then
print_error "Cannot use --dry-run and --validate-only together"
echo ""
echo "Choose one:"
echo " --validate-only : Only run validations"
echo " --dry-run : Run validations and show execution plan"
exit 1
fi
# Print header
echo "=================================================="
echo "KServe Release Automation"
echo "=================================================="
echo "Version: $VERSION"
echo "Dry-run: $DRY_RUN"
echo "Validate-only: $VALIDATE_ONLY"
echo "GitHub Actions Mode: $GITHUB_ACTIONS_MODE"
echo "=================================================="
# Determine upstream remote
if [[ "${GITHUB_ACTIONS:-false}" == "true" ]]; then
# GitHub Actions always uses origin
UPSTREAM_REMOTE="origin"
echo "Using remote: $UPSTREAM_REMOTE (GitHub Actions)"
else
# Auto-detect for local/fork usage
UPSTREAM_REMOTE=$(detect_upstream_remote)
if [[ -z "$UPSTREAM_REMOTE" ]]; then
echo ""
print_error "No remote pointing to kserve/kserve found!"
echo ""
echo "Please add upstream remote:"
echo " git remote add upstream [email protected]:kserve/kserve.git"
echo ""
echo "Or if using HTTPS:"
echo " git remote add upstream https://github.com/kserve/kserve.git"
echo ""
echo "Or run with --github-actions flag to use origin remote"
exit 1
fi
echo "Using remote: $UPSTREAM_REMOTE (auto-detected)"
fi
echo "=================================================="
# Run validations
validate_version_format
parse_version
validate_kserve_deps
validate_tag_duplicate
validate_github_release_duplicate
validate_branch_rc0
validate_branch_rc1_plus
# Exit if validate-only mode
if [[ "$VALIDATE_ONLY" == true ]]; then
echo ""
print_success "All validations passed!"
exit 0
fi
# Show dry-run plan and exit if dry-run mode
if [[ "$DRY_RUN" == true ]]; then
show_dry_run_plan
exit 0
fi
# Safety: dry-run is recommended for first-time usage
# Run without --dry-run to actually create branch and tag
# Execute release
execute_release
}
main "$@"