Skip to content
Merged
Changes from all commits
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
111 changes: 76 additions & 35 deletions util/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
// -interactive
// Ask for confirmation before proceeding to each step. When enabled,
// the release process for branches runs sequentially rather than in parallel.
// -libraries-bom-update-optional
// Make the gapic-generator-java-bom and libraries-bom PR checks optional.
// When enabled, the script will skip these PR checks and their dependent
// workflows if the corresponding PRs are not found.
//
// Requirements:
// - The GitHub CLI (`gh`) must be installed and authenticated. Run `gh auth login`
Expand All @@ -72,6 +76,7 @@ import (
var emailOpt string
var bootMaxOpt string
var interactiveOpt bool
var librariesBomOptionalOpt bool

// main is the entry point of the script. It parses flags, sets up the parallel release process
// for the specified branches, and handles the sequential README update for the main branch.
Expand All @@ -80,6 +85,7 @@ func main() {
flag.StringVar(&emailOpt, "email", "", "Email address to send notifications to (requires LOAS/gcert)")
flag.StringVar(&bootMaxOpt, "boot-max", "", "Newly supported Spring Boot max version for compatibilityRange (e.g. 4.1.0-M1)")
flag.BoolVar(&interactiveOpt, "interactive", false, "Ask for confirmation before proceeding to each step")
flag.BoolVar(&librariesBomOptionalOpt, "libraries-bom-update-optional", false, "Make gapic-generator-java-bom PR check optional")
flag.Parse()

branches := strings.Split(*branchesOpt, ",")
Expand Down Expand Up @@ -153,41 +159,59 @@ func runReleaseForMain() {
fmt.Printf("%s ▶️ STEP %d: Finding gapic-generator-java-bom upgrade PR...\n", prefix, step)
gapicPR := findPR(branch, "gapic-generator-java-bom in:title is:open")
if gapicPR == "" {
fatalError(branch, "Could not find an open gapic-generator-java-bom PR. Exiting.")
if librariesBomOptionalOpt {
fmt.Printf("%s ℹ️ Skipping gapic-generator-java-bom upgrade PR (optional).\n", prefix)
} else {
fatalError(branch, "Could not find an open gapic-generator-java-bom PR. Exiting.")
}
} else {
fmt.Printf("%s ✅ Found gapic-generator PR: #%s\n", prefix, gapicPR)
approveWorkflowRuns(branch, gapicPR)
approvePR(branch, gapicPR)
enableAutoMerge(branch, gapicPR)
waitForAutoMerge(branch, gapicPR)
}
fmt.Printf("%s ✅ Found gapic-generator PR: #%s\n", prefix, gapicPR)
approveWorkflowRuns(branch, gapicPR)
approvePR(branch, gapicPR)
enableAutoMerge(branch, gapicPR)
waitForAutoMerge(branch, gapicPR)
step++

// Find libraries-bom PR and Trigger Rebase
confirmStep(branch, "Find libraries-bom PR and trigger rebase")
fmt.Printf("\n%s ▶️ STEP %d: Finding libraries-bom PR and triggering rebase...\n", prefix, step)
bomPR := findPR(branch, "libraries-bom in:title is:open")
if bomPR == "" {
fatalError(branch, "Could not find an open libraries-bom PR.")
if librariesBomOptionalOpt {
fmt.Printf("%s ℹ️ Skipping libraries-bom upgrade PR (optional).\n", prefix)
} else {
fatalError(branch, "Could not find an open libraries-bom PR.")
}
} else {
fmt.Printf("%s ✅ Found libraries-bom PR: #%s\n", prefix, bomPR)
checkRebaseBox(branch, bomPR)
}
fmt.Printf("%s ✅ Found libraries-bom PR: #%s\n", prefix, bomPR)
checkRebaseBox(branch, bomPR)
step++

// Trigger AutoConfigs Workflow
confirmStep(branch, "Trigger Generate Spring Auto-Configurations workflow")
fmt.Printf("\n%s ▶️ STEP %d: Triggering Generate Spring Auto-Configurations workflow...\n", prefix, step)
triggerAutoConfigs(branch, bomPR)
waitForBotCommit(branch, bomPR)
verifyBotCommit(branch, bomPR)
if bomPR != "" {
confirmStep(branch, "Trigger Generate Spring Auto-Configurations workflow")
fmt.Printf("\n%s ▶️ STEP %d: Triggering Generate Spring Auto-Configurations workflow...\n", prefix, step)
triggerAutoConfigs(branch, bomPR)
waitForBotCommit(branch, bomPR)
verifyBotCommit(branch, bomPR)
} else {
fmt.Printf("\n%s ℹ️ Skipping Generate Spring Auto-Configurations (no libraries-bom PR).\n", prefix)
}
step++

// Merge the libraries-bom PR
confirmStep(branch, "Merge the completed libraries-bom PR")
fmt.Printf("\n%s ▶️ STEP %d: Merging the completed libraries-bom PR...\n", prefix, step)
approveWorkflowRuns(branch, bomPR)
approvePR(branch, bomPR)
enableAutoMerge(branch, bomPR)
waitForAutoMerge(branch, bomPR)
if bomPR != "" {
confirmStep(branch, "Merge the completed libraries-bom PR")
fmt.Printf("\n%s ▶️ STEP %d: Merging the completed libraries-bom PR...\n", prefix, step)
approveWorkflowRuns(branch, bomPR)
approvePR(branch, bomPR)
enableAutoMerge(branch, bomPR)
waitForAutoMerge(branch, bomPR)
} else {
fmt.Printf("\n%s ℹ️ Skipping Merging libraries-bom PR (no libraries-bom PR).\n", prefix)
}
step++

// Wait for and merge Release PR (and Snapshot)
Expand Down Expand Up @@ -228,17 +252,21 @@ func runReleaseForBranch(branch string) {
prefix := fmt.Sprintf("[%s]", branch)
step := 1

// Merge gapic-generator-java-bom PR
// Merge libraries-bom PR
confirmStep(branch, "Find and merge libraries-bom upgrade PR")
fmt.Printf("%s ▶️ STEP %d: Finding libraries-bom upgrade PR...\n", prefix, step)
bomPR := findPR(branch, "libraries-bom in:title is:open")
if bomPR == "" {
fatalError(branch, "Could not find an open libraries-bom PR. Exiting.")
if librariesBomOptionalOpt {
fmt.Printf("%s ℹ️ Skipping libraries-bom upgrade PR (optional).\n", prefix)
} else {
fatalError(branch, "Could not find an open libraries-bom PR. Exiting.")
}
} else {
fmt.Printf("%s ✅ Found libraries-bom PR: #%s\n", prefix, bomPR)
approvePR(branch, bomPR)
waitForMerge(branch, bomPR)
}
fmt.Printf("%s ✅ Found libraries-bom PR: #%s\n", prefix, bomPR)

approvePR(branch, bomPR)
waitForMerge(branch, bomPR)
step++

// Merge libraries-bom PR
Expand Down Expand Up @@ -728,25 +756,38 @@ func pollForPR(branch, query string, excludeSnapshot bool) string {
// verifyMavenCentral polls Maven Central until the specified version is available.
func verifyMavenCentral(branch, version string) {
prefix := fmt.Sprintf("[%s]", branch)
mavenURL := fmt.Sprintf("https://central.sonatype.com/artifact/com.google.cloud/spring-cloud-gcp/%s", version)
overviewURL := "https://central.sonatype.com/artifact/com.google.cloud/spring-cloud-gcp/overview"
// We check the actual repository instead of the web UI to avoid false positives from soft 404s.
repoURL := fmt.Sprintf("https://repo1.maven.org/maven2/com/google/cloud/spring-cloud-gcp/%s/spring-cloud-gcp-%s.pom", version, version)
metadataURL := "https://repo1.maven.org/maven2/com/google/cloud/spring-cloud-gcp/maven-metadata.xml"
searchURL := fmt.Sprintf("https://central.sonatype.com/artifact/com.google.cloud/spring-cloud-gcp/%s", version)

fmt.Printf("%s 🔗 Version URL: %s\n", prefix, mavenURL)
fmt.Printf("%s 🔗 Overview URL: %s\n", prefix, overviewURL)
fmt.Printf("%s 🔗 Repository POM: %s\n", prefix, repoURL)
fmt.Printf("%s 🔗 Metadata URL: %s\n", prefix, metadataURL)
fmt.Printf("%s 🔗 Search Portal: %s\n", prefix, searchURL)

success := false
maxWait := 6 * time.Hour
start := time.Now()

for i := 0; i < 72; i++ {
elapsed := time.Since(start).Round(time.Second)
fmt.Printf("\r%s ⏳ Polling central.sonatype.com up to %v... (Elapsed: %v)", prefix, maxWait, elapsed)
fmt.Printf("\r%s ⏳ Polling Maven Central up to %v... (Elapsed: %v)", prefix, maxWait, elapsed)

resp, err := http.Get(mavenURL)
// 1. Check if the specific POM file exists
resp, err := http.Get(repoURL)
if err == nil && resp.StatusCode == 200 {
success = true
resp.Body.Close()
break

// 2. Double check that it's also indexed in the metadata
metaResp, metaErr := http.Get(metadataURL)
if metaErr == nil && metaResp.StatusCode == 200 {
body, _ := io.ReadAll(metaResp.Body)
metaResp.Body.Close()
if strings.Contains(string(body), "<version>"+version+"</version>") {
success = true
break
}
}
}
if resp != nil {
resp.Body.Close()
Expand All @@ -758,7 +799,7 @@ func verifyMavenCentral(branch, version string) {
if success {
fmt.Printf("%s 🎉 SUCCESS! Version %s is now live on Maven Central!\n", prefix, version)
if emailOpt != "" {
emailBody := fmt.Sprintf("The Spring Cloud GCP release for %s (branch %s) is complete.\n\nMaven Central link: %s", version, branch, mavenURL)
emailBody := fmt.Sprintf("The Spring Cloud GCP release for %s (branch %s) is complete.\n\nMaven Central link: %s", version, branch, searchURL)
sendEmail(emailOpt, fmt.Sprintf("✅ Spring Cloud GCP Release %s Complete", version), emailBody)
}
} else {
Expand Down
Loading