Skip to content

Instantly share code, notes, and snippets.

@derat
Created December 14, 2021 18:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save derat/fc5b16bff50ccb1380d3a603616c432a to your computer and use it in GitHub Desktop.
Save derat/fc5b16bff50ccb1380d3a603616c432a to your computer and use it in GitHub Desktop.
Patch https://github.com/GoogleCloudPlatform/cloud-build-notifiers at 2f9eebce378bf38964345d674e0969b9afe9ccbc
From 1c79a506deda796d6280b0648697bd4f2b1b181b Mon Sep 17 00:00:00 2001
From: Daniel Erat <omitted>
Date: Sat, 11 Dec 2021 18:54:23 -0400
Subject: [PATCH] Make SMTP notifier include additional info from build tags.
Update the SMTP notifier to include more details in the
email messages that it sends. Subjects now look like
"[project-id] trigger-name SUCCESS (short-build-id)", and
the table in the body also includes the trigger name,
commit, build ID, and start and end times.
The trigger name and commit don't appear to be included in
the Build protobuf that's sent over Pub/Sub
(https://pkg.go.dev/google.golang.org/genproto/googleapis/devtools/cloudbuild/v1#Build),
so they're passed via tags in the Cloud Build YAML
configuration, i.e.
tags:
- commit-$COMMIT_SHA
- trigger-$TRIGGER_NAME
A container that runs the modified notifier can be created
by symlinking smtp/Dockerfile into the repository root and
running a command like the following from there:
gcloud --project <project-id> builds submit \
--tag gcr.io/<project-id>/smtp-notifier
setup.sh is also updated to allow a non-standard image to be
specified via the IMAGE_PATH environment variable, i.e. the
value passed to --tag in the previous command. For example:
CLOUDSDK_CORE_PROJECT=<project-id> \
IMAGE_PATH=gcr.io/<project-id>/smtp-notifier:latest \
./setup.sh smtp /path/to/smtp-notifier.yaml <secret-name>
---
setup.sh | 4 +++-
smtp/main.go | 55 ++++++++++++++++++++++++++++++++++++++++++++---
smtp/main_test.go | 32 ++++++++++++++++++++++++++-
3 files changed, 86 insertions(+), 5 deletions(-)
diff --git a/setup.sh b/setup.sh
index 60bb35b..e7b219e 100755
--- a/setup.sh
+++ b/setup.sh
@@ -90,7 +90,9 @@ main() {
DESTINATION_BUCKET_NAME="${PROJECT_ID}-notifiers-config"
DESTINATION_BUCKET_URI="gs://${DESTINATION_BUCKET_NAME}"
DESTINATION_CONFIG_PATH="${DESTINATION_BUCKET_URI}/${SOURCE_CONFIG_BASENAME}"
- IMAGE_PATH="us-east1-docker.pkg.dev/gcb-release/cloud-build-notifiers/${NOTIFIER_TYPE}:latest"
+ if [ -z "$IMAGE_PATH" ]; then
+ IMAGE_PATH="us-east1-docker.pkg.dev/gcb-release/cloud-build-notifiers/${NOTIFIER_TYPE}:latest"
+ fi
SERVICE_NAME="${NOTIFIER_TYPE}-notifier"
SUBSCRIPTION_NAME="${NOTIFIER_TYPE}-subscription"
INVOKER_SA="cloud-run-pubsub-invoker@${PROJECT_ID}.iam.gserviceaccount.com"
diff --git a/smtp/main.go b/smtp/main.go
index ab6d163..9039b29 100644
--- a/smtp/main.go
+++ b/smtp/main.go
@@ -22,11 +22,13 @@ import (
"net/smtp"
"strings"
"text/template"
+ "time"
"github.com/GoogleCloudPlatform/cloud-build-notifiers/lib/notifiers"
log "github.com/golang/glog"
"github.com/golang/protobuf/proto"
cbpb "google.golang.org/genproto/googleapis/devtools/cloudbuild/v1"
+ tspb "google.golang.org/protobuf/types/known/timestamppb"
)
const (
@@ -57,7 +59,7 @@ func (s *smtpNotifier) SetUp(ctx context.Context, cfg *notifiers.Config, sg noti
}
s.filter = prd
- tmpl, err := template.New("email_template").Parse(htmlBody)
+ tmpl, err := template.New("email_template").Funcs(templateFuncs).Parse(htmlBody)
if err != nil {
return fmt.Errorf("failed to parse HTML email template: %w", err)
}
@@ -158,6 +160,31 @@ func (s *smtpNotifier) sendSMTPNotification(build *cbpb.Build) error {
return nil
}
+// getTag tries to extract a value from build's tags.
+// Given a name "foo", it will look for a tag prefixed with "foo-" and return
+// the rest of the tag. If no matching tag is found, def is returned.
+// Tags apparently must be matched by "^[\\w][\\w.-]{0,127}$" (per the failure
+// message when you try to start a build with an invalid tag).
+func getTag(build *cbpb.Build, name, def string) string {
+ pre := name + "-"
+ for _, tag := range build.Tags {
+ if strings.HasPrefix(tag, pre) {
+ return tag[len(pre):]
+ }
+ }
+ return def
+}
+
+// templateFuncs should be added before parsing the htmlBody template.
+var templateFuncs = template.FuncMap{
+ "tag": func(build *cbpb.Build, name string) string {
+ return getTag(build, name, "")
+ },
+ "time": func(ts tspb.Timestamp) string {
+ return ts.AsTime().Format(time.RFC1123Z)
+ },
+}
+
func (s *smtpNotifier) buildEmail(build *cbpb.Build) (string, error) {
logURL, err := notifiers.AddUTMParams(build.LogUrl, notifiers.EmailMedium)
if err != nil {
@@ -170,7 +197,9 @@ func (s *smtpNotifier) buildEmail(build *cbpb.Build) (string, error) {
return "", err
}
- subject := fmt.Sprintf("Cloud Build [%s]: %s", build.ProjectId, build.Id)
+ subject := fmt.Sprintf("[%s] %s %s (build %s)",
+ build.ProjectId, getTag(build, "trigger", "[unknown]"),
+ build.Status, strings.Split(build.Id, "-")[0])
header := make(map[string]string)
if s.mcfg.from != s.mcfg.sender {
@@ -221,12 +250,32 @@ const htmlBody = `<!doctype html>
<div class="card-content white">
<table class="bordered">
<tbody>
+ <tr>
+ <td>Trigger</td>
+ <td>{{tag . "trigger"}}</td>
+ </tr>
+ <tr>
+ <td>Commit</td>
+ <td>{{tag . "commit"}}</td>
+ </tr>
+ <tr>
+ <td>Build</td>
+ <td>{{.Id}}</td>
+ </tr>
+ <tr>
+ <td>Start</td>
+ <td>{{time .StartTime}}</td>
+ </tr>
+ <tr>
+ <td>End</td>
+ <td>{{time .FinishTime}}</td>
+ </tr>
<tr>
<td>Status</td>
<td>{{.Status}}</td>
</tr>
<tr>
- <td>Log URL</td>
+ <td>Log</td>
<td><a href="{{.LogUrl}}">Click Here</a></td>
</tr>
</tbody>
diff --git a/smtp/main_test.go b/smtp/main_test.go
index 92d31c3..e0d73a3 100644
--- a/smtp/main_test.go
+++ b/smtp/main_test.go
@@ -20,10 +20,12 @@ import (
"strings"
"testing"
"text/template"
+ "time"
"github.com/GoogleCloudPlatform/cloud-build-notifiers/lib/notifiers"
"github.com/google/go-cmp/cmp"
cbpb "google.golang.org/genproto/googleapis/devtools/cloudbuild/v1"
+ tspb "google.golang.org/protobuf/types/known/timestamppb"
"gopkg.in/yaml.v2"
)
@@ -153,17 +155,32 @@ spec:
}
func TestDefaultEmailTemplate(t *testing.T) {
- tmpl, err := template.New("email_template").Parse(htmlBody)
+ tmpl, err := template.New("email_template").Funcs(templateFuncs).Parse(htmlBody)
if err != nil {
t.Fatalf("template.Parse failed: %v", err)
}
+ makeTimestamp := func(s string) *tspb.Timestamp {
+ tt, err := time.Parse(time.RFC3339, s)
+ if err != nil {
+ t.Fatalf("Failed parsing %q: %v", s, err)
+ }
+ return tspb.New(tt)
+ }
+
build := &cbpb.Build{
Id: "some-build-id",
ProjectId: "my-project-id",
BuildTriggerId: "some-trigger-id",
Status: cbpb.Build_SUCCESS,
LogUrl: "https://some.example.com/log/url",
+ StartTime: makeTimestamp("2021-12-11T20:02:31Z"),
+ FinishTime: makeTimestamp("2021-12-11T20:04:51Z"),
+ Tags: []string{
+ "trigger-some-trigger",
+ "branch-some-branch",
+ "commit-some-commit",
+ },
}
body := new(bytes.Buffer)
@@ -182,4 +199,17 @@ func TestDefaultEmailTemplate(t *testing.T) {
if !strings.Contains(body.String(), `<a href="https://some.example.com/log/url">`) {
t.Error("missing Log URL")
}
+
+ for _, tc := range []struct {
+ want, desc string
+ }{
+ {`<td>some-trigger</td>`, "trigger"},
+ {`<td>some-commit</td>`, "commit"},
+ {`<td>Sat, 11 Dec 2021 20:02:31 +0000</td>`, "start"},
+ {`<td>Sat, 11 Dec 2021 20:04:51 +0000</td>`, "finish"},
+ } {
+ if !strings.Contains(body.String(), tc.want) {
+ t.Errorf("missing %v", tc.desc)
+ }
+ }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment