githubissues/main.go (124 lines of code) (raw):

// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "bytes" "context" "encoding/json" "fmt" "net/http" "strings" "text/template" "github.com/GoogleCloudPlatform/cloud-build-notifiers/lib/notifiers" log "github.com/golang/glog" cbpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" ) const ( githubTokenSecretName = "githubToken" githubApiEndpoint = "https://api.github.com/repos" ) func main() { if err := notifiers.Main(new(githubissuesNotifier)); err != nil { log.Fatalf("fatal error: %v", err) } } type githubissuesNotifier struct { filter notifiers.EventFilter tmpl *template.Template githubToken string githubRepo string br notifiers.BindingResolver tmplView *notifiers.TemplateView } type githubissuesMessage struct { Title string `json:"title"` Body *notifiers.Template `json:"body"` } func (g *githubissuesNotifier) SetUp(ctx context.Context, cfg *notifiers.Config, issueTemplate string, sg notifiers.SecretGetter, br notifiers.BindingResolver) error { prd, err := notifiers.MakeCELPredicate(cfg.Spec.Notification.Filter) if err != nil { return fmt.Errorf("failed to make a CEL predicate: %w", err) } g.filter = prd g.br = br repo, ok := cfg.Spec.Notification.Delivery["githubRepo"].(string) if !ok { return fmt.Errorf("expected delivery config %v to have string field `githubRepo`", cfg.Spec.Notification.Delivery) } g.githubRepo = repo tmpl, err := template.New("issue_template").Parse(issueTemplate) if err != nil { return fmt.Errorf("failed to parse issue body template: %w", err) } g.tmpl = tmpl wuRef, err := notifiers.GetSecretRef(cfg.Spec.Notification.Delivery, githubTokenSecretName) if err != nil { return fmt.Errorf("failed to get Secret ref from delivery config (%v) field %q: %w", cfg.Spec.Notification.Delivery, githubTokenSecretName, err) } wuResource, err := notifiers.FindSecretResourceName(cfg.Spec.Secrets, wuRef) if err != nil { return fmt.Errorf("failed to find Secret for ref %q: %w", wuRef, err) } wu, err := sg.GetSecret(ctx, wuResource) if err != nil { return fmt.Errorf("failed to get token secret: %w", err) } g.githubToken = wu return nil } func (g *githubissuesNotifier) SendNotification(ctx context.Context, build *cbpb.Build) error { if !g.filter.Apply(ctx, build) { log.V(2).Infof("not sending response for event (build id = %s, status = %v)", build.Id, build.Status) return nil } repo := GetGithubRepo(build) if repo == "" { log.Warningf("could not determine GitHub repository from build, skipping notification") return nil } webhookURL := fmt.Sprintf("%s/%s/issues", githubApiEndpoint, repo) log.Infof("sending GitHub Issue webhook for Build %q (status: %q) to url %q", build.Id, build.Status, webhookURL) bindings, err := g.br.Resolve(ctx, nil, build) if err != nil { log.Errorf("failed to resolve bindings :%v", err) } g.tmplView = &notifiers.TemplateView{ Build: &notifiers.BuildView{Build: build}, Params: bindings, } logURL, err := notifiers.AddUTMParams(build.LogUrl, notifiers.HTTPMedium) if err != nil { return fmt.Errorf("failed to add UTM params: %w", err) } build.LogUrl = logURL payload := new(bytes.Buffer) var buf bytes.Buffer if err := g.tmpl.Execute(&buf, g.tmplView); err != nil { return err } err = json.NewEncoder(payload).Encode(buf) if err != nil { return fmt.Errorf("failed to encode payload: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, webhookURL, strings.NewReader(buf.String())) if err != nil { return fmt.Errorf("failed to create a new HTTP request: %w", err) } req.Header.Set("Accept", "application/vnd.github.v3+json") req.Header.Set("Authorization", fmt.Sprintf("token %s", g.githubToken)) req.Header.Set("User-Agent", "GCB-Notifier/0.1 (http)") resp, err := http.DefaultClient.Do(req) if err != nil { return fmt.Errorf("failed to make HTTP request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { log.Warningf("got a non-OK response status %q (%d) from %q", resp.Status, resp.StatusCode, webhookURL) } log.V(2).Infoln("send HTTP request successfully") return nil } func GetGithubRepo(build *cbpb.Build) string { if build.Substitutions != nil && build.Substitutions["REPO_FULL_NAME"] != "" { // return repo full name if it's available // e.g. "GoogleCloudPlatform/cloud-build-notifiers" return build.Substitutions["REPO_FULL_NAME"] } return "" }