Skip to content

Commit 725f859

Browse files
committed
feat: add email and skip-kokoro flags to release bot
This update adds the `-skip-kokoro` and `-email` flags to the release_manager_merge_bot.go script. - `-skip-kokoro`: Boolean flag to disable applying kokoro:run and kokoro:force-run labels to retrying tests on failure. - `-email`: String flag to provide an email address. When specified, the bot will use the internal `sendgmr` tool to notify the user upon success or failure of the PR merge process.
1 parent d934298 commit 725f859

File tree

1 file changed

+77
-21
lines changed

1 file changed

+77
-21
lines changed

.github/scripts/release_manager_merge_bot.go

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
// 3. If the status is "success", it will squash and merge the pull request.
2121
// 4. If the status is "pending", it will wait and check again.
2222
//
23+
// Flags:
24+
// -skip-kokoro (Optional) If set, skips applying Kokoro rerunning labels on failure.
25+
// -email (Optional) Email address to send success/failure notifications to.
26+
//
2327
// Prerequisites:
2428
// - Go must be installed (https://golang.org/doc/install).
2529
// - A GitHub personal access token with repo scope must be set in the GITHUB_TOKEN environment variable.
@@ -28,16 +32,18 @@
2832
//
2933
// export GITHUB_TOKEN="<your GitHub token>"
3034
// cd .github/scripts
31-
// go run ./release_manager_merge_bot.go <PR URL>
35+
// go run ./release_manager_merge_bot.go -skip-kokoro -email="user@google.com" <PR URL>
3236

3337
package main
3438

3539
import (
3640
"context"
41+
"flag"
3742
"fmt"
3843
"log"
3944
"net/url"
4045
"os"
46+
"os/exec"
4147
"strconv"
4248
"strings"
4349
"time"
@@ -52,6 +58,11 @@ var labelsToAdd = []string{"kokoro:force-run", "kokoro:run"}
5258

5359
// --- End of Configuration ---
5460

61+
var (
62+
skipKokoroOpt bool
63+
emailOpt string
64+
)
65+
5566
// parseURL parses a GitHub pull request URL and returns the owner, repository, and PR number.
5667
func parseURL(prURL string) (string, string, int, error) {
5768
parsedURL, err := url.Parse(prURL)
@@ -95,13 +106,43 @@ func getMissingLabels(ctx context.Context, client *github.Client, owner, repo st
95106
return missingLabels, nil
96107
}
97108

109+
// sendEmail sends an email notification using the internal sendgmr tool.
110+
func sendEmail(to, subject, body string) {
111+
if to == "" {
112+
return
113+
}
114+
sendgmrPath := "/google/bin/releases/gws-sre/files/sendgmr/sendgmr"
115+
cmd := exec.Command(sendgmrPath, "--to="+to, "--subject="+subject)
116+
cmd.Stdin = strings.NewReader(body)
117+
if err := cmd.Run(); err != nil {
118+
log.Printf("Warning: Failed to send email: %v", err)
119+
} else {
120+
log.Printf("Email successfully sent to %s", to)
121+
}
122+
}
123+
124+
// fatalError logs an error message, optionally sends an email, and exits.
125+
func fatalError(format string, v ...interface{}) {
126+
msg := fmt.Sprintf(format, v...)
127+
log.Printf("Error: %s", msg)
128+
if emailOpt != "" {
129+
sendEmail(emailOpt, "❌ Release Manager Merge Bot Failed", msg)
130+
}
131+
os.Exit(1)
132+
}
133+
98134
func main() {
99135
log.Println("Starting the release manager merge bot.")
100136

101-
if len(os.Args) < 2 {
102-
log.Fatal("Error: Pull request URL is required. Example: go run ./release_manager_merge_bot.go <PR URL>")
137+
flag.BoolVar(&skipKokoroOpt, "skip-kokoro", false, "Skip applying kokoro rerunning labels on failure")
138+
flag.StringVar(&emailOpt, "email", "", "Email address to send notifications to (requires LOAS/gcert)")
139+
flag.Parse()
140+
141+
args := flag.Args()
142+
if len(args) < 1 {
143+
log.Fatal("Error: Pull request URL is required. Example: go run ./release_manager_merge_bot.go [flags] <PR URL>")
103144
}
104-
prURL := os.Args[1]
145+
prURL := args[0]
105146

106147
githubToken := os.Getenv("GITHUB_TOKEN")
107148
if githubToken == "" {
@@ -110,7 +151,11 @@ func main() {
110151

111152
owner, repo, prNumber, err := parseURL(prURL)
112153
if err != nil {
113-
log.Fatalf("Error parsing URL: %v", err)
154+
fatalError("Error parsing URL: %v", err)
155+
}
156+
157+
if emailOpt != "" {
158+
log.Printf("Notifications will be sent to: %s", emailOpt)
114159
}
115160

116161
ctx := context.Background()
@@ -120,21 +165,25 @@ func main() {
120165

121166
// --- Initial Label Check ---
122167
retryCount := 0
123-
log.Printf("Performing initial label check for PR #%d...", prNumber)
124-
missingLabels, err := getMissingLabels(ctx, client, owner, repo, prNumber)
125-
if err != nil {
126-
log.Printf("Warning: could not perform initial label check: %v", err)
127-
} else {
128-
if len(missingLabels) > 0 {
129-
log.Println("Required Kokoro labels are missing. Adding them now...")
130-
_, _, err := client.Issues.AddLabelsToIssue(ctx, owner, repo, prNumber, missingLabels)
131-
if err != nil {
132-
log.Printf("Warning: failed to add labels: %v", err)
133-
}
134-
retryCount++
168+
if !skipKokoroOpt {
169+
log.Printf("Performing initial label check for PR #%d...", prNumber)
170+
missingLabels, err := getMissingLabels(ctx, client, owner, repo, prNumber)
171+
if err != nil {
172+
log.Printf("Warning: could not perform initial label check: %v", err)
135173
} else {
136-
log.Println("Required Kokoro labels are already present.")
174+
if len(missingLabels) > 0 {
175+
log.Println("Required Kokoro labels are missing. Adding them now...")
176+
_, _, err := client.Issues.AddLabelsToIssue(ctx, owner, repo, prNumber, missingLabels)
177+
if err != nil {
178+
log.Printf("Warning: failed to add labels: %v", err)
179+
}
180+
retryCount++
181+
} else {
182+
log.Println("Required Kokoro labels are already present.")
183+
}
137184
}
185+
} else {
186+
log.Println("Skipping initial Kokoro label check due to -skip-kokoro flag.")
138187
}
139188
// --- End of Initial Label Check ---
140189

@@ -166,8 +215,11 @@ func main() {
166215

167216
switch state {
168217
case "failure":
218+
if skipKokoroOpt {
219+
fatalError("PR #%d has failed checks and -skip-kokoro is enabled. Failing the script.", prNumber)
220+
}
169221
if retryCount >= 2 {
170-
log.Fatal("The PR has failed twice after applying the Kokoro labels. Failing the script.")
222+
fatalError("The PR has failed twice after applying the Kokoro labels. Failing the script.")
171223
}
172224
log.Println("Some checks have failed. Retrying the tests...")
173225
_, _, err := client.Issues.AddLabelsToIssue(ctx, owner, repo, prNumber, labelsToAdd)
@@ -182,9 +234,13 @@ func main() {
182234
MergeMethod: "squash",
183235
})
184236
if err != nil {
185-
log.Fatalf("Failed to merge PR: %v", err)
237+
fatalError("Failed to merge PR: %v", err)
238+
}
239+
successMsg := fmt.Sprintf("Successfully squashed and merged PR #%d: %s", prNumber, *mergeResult.Message)
240+
log.Println(successMsg)
241+
if emailOpt != "" {
242+
sendEmail(emailOpt, fmt.Sprintf("✅ PR #%d Merged Successfully", prNumber), successMsg)
186243
}
187-
log.Printf("Successfully squashed and merged PR #%d: %s", prNumber, *mergeResult.Message)
188244
return // Exit the program on success
189245
case "pending":
190246
log.Println("Some checks are still pending. Waiting for them to complete.")

0 commit comments

Comments
 (0)