Skip to content

Commit 12e2e94

Browse files
authored
feat: add email and skip-kokoro flags to release bot (#12050)
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 fdcdcf5 commit 12e2e94

File tree

2 files changed

+79
-21
lines changed

2 files changed

+79
-21
lines changed
8.99 MB
Binary file not shown.

.github/scripts/release_manager_merge_bot.go

Lines changed: 79 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
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+
// Note: This relies on the internal sendgmr tool and is only
27+
// supported on Cloudtop/gLinux with valid LOAS credentials.
28+
//
2329
// Prerequisites:
2430
// - Go must be installed (https://golang.org/doc/install).
2531
// - A GitHub personal access token with repo scope must be set in the GITHUB_TOKEN environment variable.
@@ -28,16 +34,18 @@
2834
//
2935
// export GITHUB_TOKEN="<your GitHub token>"
3036
// cd .github/scripts
31-
// go run ./release_manager_merge_bot.go <PR URL>
37+
// go run ./release_manager_merge_bot.go -skip-kokoro -email="user@google.com" <PR URL>
3238

3339
package main
3440

3541
import (
3642
"context"
43+
"flag"
3744
"fmt"
3845
"log"
3946
"net/url"
4047
"os"
48+
"os/exec"
4149
"strconv"
4250
"strings"
4351
"time"
@@ -52,6 +60,11 @@ var labelsToAdd = []string{"kokoro:force-run", "kokoro:run"}
5260

5361
// --- End of Configuration ---
5462

63+
var (
64+
skipKokoroOpt bool
65+
emailOpt string
66+
)
67+
5568
// parseURL parses a GitHub pull request URL and returns the owner, repository, and PR number.
5669
func parseURL(prURL string) (string, string, int, error) {
5770
parsedURL, err := url.Parse(prURL)
@@ -95,13 +108,43 @@ func getMissingLabels(ctx context.Context, client *github.Client, owner, repo st
95108
return missingLabels, nil
96109
}
97110

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

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>")
139+
flag.BoolVar(&skipKokoroOpt, "skip-kokoro", false, "Skip applying kokoro rerunning labels on failure")
140+
flag.StringVar(&emailOpt, "email", "", "Email address to send notifications to (requires Cloudtop/gLinux and LOAS/gcert)")
141+
flag.Parse()
142+
143+
args := flag.Args()
144+
if len(args) < 1 {
145+
log.Fatal("Error: Pull request URL is required. Example: go run ./release_manager_merge_bot.go [flags] <PR URL>")
103146
}
104-
prURL := os.Args[1]
147+
prURL := args[0]
105148

106149
githubToken := os.Getenv("GITHUB_TOKEN")
107150
if githubToken == "" {
@@ -110,7 +153,11 @@ func main() {
110153

111154
owner, repo, prNumber, err := parseURL(prURL)
112155
if err != nil {
113-
log.Fatalf("Error parsing URL: %v", err)
156+
fatalError("Error parsing URL: %v", err)
157+
}
158+
159+
if emailOpt != "" {
160+
log.Printf("Notifications will be sent to: %s", emailOpt)
114161
}
115162

116163
ctx := context.Background()
@@ -120,21 +167,25 @@ func main() {
120167

121168
// --- Initial Label Check ---
122169
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++
170+
if !skipKokoroOpt {
171+
log.Printf("Performing initial label check for PR #%d...", prNumber)
172+
missingLabels, err := getMissingLabels(ctx, client, owner, repo, prNumber)
173+
if err != nil {
174+
log.Printf("Warning: could not perform initial label check: %v", err)
135175
} else {
136-
log.Println("Required Kokoro labels are already present.")
176+
if len(missingLabels) > 0 {
177+
log.Println("Required Kokoro labels are missing. Adding them now...")
178+
_, _, err := client.Issues.AddLabelsToIssue(ctx, owner, repo, prNumber, missingLabels)
179+
if err != nil {
180+
log.Printf("Warning: failed to add labels: %v", err)
181+
}
182+
retryCount++
183+
} else {
184+
log.Println("Required Kokoro labels are already present.")
185+
}
137186
}
187+
} else {
188+
log.Println("Skipping initial Kokoro label check due to -skip-kokoro flag.")
138189
}
139190
// --- End of Initial Label Check ---
140191

@@ -166,8 +217,11 @@ func main() {
166217

167218
switch state {
168219
case "failure":
220+
if skipKokoroOpt {
221+
fatalError("PR #%d has failed checks and -skip-kokoro is enabled. Failing the script.", prNumber)
222+
}
169223
if retryCount >= 2 {
170-
log.Fatal("The PR has failed twice after applying the Kokoro labels. Failing the script.")
224+
fatalError("The PR has failed twice after applying the Kokoro labels. Failing the script.")
171225
}
172226
log.Println("Some checks have failed. Retrying the tests...")
173227
_, _, err := client.Issues.AddLabelsToIssue(ctx, owner, repo, prNumber, labelsToAdd)
@@ -182,9 +236,13 @@ func main() {
182236
MergeMethod: "squash",
183237
})
184238
if err != nil {
185-
log.Fatalf("Failed to merge PR: %v", err)
239+
fatalError("Failed to merge PR: %v", err)
240+
}
241+
successMsg := fmt.Sprintf("Successfully squashed and merged PR #%d: %s", prNumber, *mergeResult.Message)
242+
log.Println(successMsg)
243+
if emailOpt != "" {
244+
sendEmail(emailOpt, fmt.Sprintf("✅ PR #%d Merged Successfully", prNumber), successMsg)
186245
}
187-
log.Printf("Successfully squashed and merged PR #%d: %s", prNumber, *mergeResult.Message)
188246
return // Exit the program on success
189247
case "pending":
190248
log.Println("Some checks are still pending. Waiting for them to complete.")

0 commit comments

Comments
 (0)