github.com/GoogleContainerTools/skaffold@v1.39.18/webhook/webhook.go (about) 1 /* 2 Copyright 2019 The Skaffold Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "encoding/json" 21 "flag" 22 "fmt" 23 "log" 24 "net/http" 25 26 "github.com/google/go-github/github" 27 appsv1 "k8s.io/api/apps/v1" 28 29 "github.com/GoogleContainerTools/skaffold/pkg/webhook/constants" 30 "github.com/GoogleContainerTools/skaffold/pkg/webhook/gcs" 31 pkggithub "github.com/GoogleContainerTools/skaffold/pkg/webhook/github" 32 "github.com/GoogleContainerTools/skaffold/pkg/webhook/kubernetes" 33 "github.com/GoogleContainerTools/skaffold/pkg/webhook/labels" 34 ) 35 36 const ( 37 port = ":8080" 38 ) 39 40 func main() { 41 // Setup the serve route to receive github events 42 http.HandleFunc("/receive", handleGithubEvent) 43 flag.Parse() 44 // Start the server 45 log.Println("Listening...") 46 log.Fatal(http.ListenAndServe(port, nil)) 47 } 48 49 func handleGithubEvent(w http.ResponseWriter, r *http.Request) { 50 eventType := r.Header.Get(constants.GithubEventHeader) 51 if eventType != constants.PullRequestEvent { 52 return 53 } 54 event := new(github.PullRequestEvent) 55 if err := json.NewDecoder(r.Body).Decode(event); err != nil { 56 log.Printf("error decoding pr event: %v", err) 57 } 58 if err := handlePullRequestEvent(event); err != nil { 59 commentOnGithub(event, "Error creating deployment, please see controller logs for details.") 60 log.Printf("error handling pr event: %v", err) 61 } 62 } 63 64 func handlePullRequestEvent(event *github.PullRequestEvent) error { 65 log.Printf("handling pull request event: %+v", event) 66 // Cleanup any deployments if PR was merged or closed 67 if event.GetAction() == constants.ClosedAction { 68 return kubernetes.CleanupDeployment(event) 69 } 70 71 // Only continue if the docs-modifications label was added 72 if event.GetAction() != constants.LabeledAction { 73 return nil 74 } 75 76 prNumber := event.GetNumber() 77 78 if event.PullRequest.GetState() != constants.OpenState { 79 log.Printf("Pull request %d is either merged or closed, skipping docs deployment", prNumber) 80 return nil 81 } 82 83 if !labels.DocsLabelExists(event.GetPullRequest().Labels) { 84 log.Printf("Label %s not found on PR %d", constants.DocsLabel, prNumber) 85 return nil 86 } 87 88 // If a PR was relabeled, we need to first cleanup preexisting deployments 89 if err := kubernetes.CleanupDeployment(event); err != nil { 90 return fmt.Errorf("cleaning up deployment: %w", err) 91 } 92 93 // Create service for the PR and get the associated external IP 94 log.Printf("Label %s found on PR %d, creating service", constants.DocsLabel, prNumber) 95 svc, err := kubernetes.CreateService(event) 96 if err != nil { 97 return fmt.Errorf("creating service: %w", err) 98 } 99 100 ip, err := kubernetes.GetExternalIP(svc) 101 if err != nil { 102 return fmt.Errorf("getting external IP: %w", err) 103 } 104 105 // Create a deployment which maps to the service 106 log.Printf("Creating deployment for pull request %d", prNumber) 107 deployment, err := kubernetes.CreateDeployment(event, svc, ip) 108 if err != nil { 109 return fmt.Errorf("creating deployment for PR %d: %w", prNumber, err) 110 } 111 response := succeeded 112 if err := kubernetes.WaitForDeploymentToStabilize(deployment, ip); err != nil { 113 log.Printf("Deployment didn't stabilize, commenting with failure message...") 114 response = failed 115 } 116 117 msg, err := response(deployment, event, ip) 118 if err != nil { 119 return fmt.Errorf("getting github message: %w", err) 120 } 121 122 if err := commentOnGithub(event, msg); err != nil { 123 return fmt.Errorf("commenting on github: %w", err) 124 } 125 126 return nil 127 } 128 129 func succeeded(d *appsv1.Deployment, event *github.PullRequestEvent, ip string) (string, error) { 130 baseURL := kubernetes.BaseURL(ip) 131 return fmt.Sprintf("Please visit [%s](%s) to view changes to the docs.", baseURL, baseURL), nil 132 } 133 134 func failed(d *appsv1.Deployment, event *github.PullRequestEvent, ip string) (string, error) { 135 name, err := gcs.UploadDeploymentLogsToBucket(d, event.GetNumber()) 136 if err != nil { 137 return "", fmt.Errorf("uploading logs to bucket: %w", err) 138 } 139 url := fmt.Sprintf("https://storage.googleapis.com/%s/%s", constants.LogsGCSBucket, name) 140 return fmt.Sprintf("Error creating deployment %s, please visit %s to view logs.", d.Name, url), nil 141 } 142 143 func commentOnGithub(event *github.PullRequestEvent, msg string) error { 144 githubClient := pkggithub.NewClient() 145 if err := githubClient.CommentOnPR(event, msg); err != nil { 146 return fmt.Errorf("commenting on PR %d: %w", event.GetNumber(), err) 147 } 148 if err := githubClient.RemoveLabelFromPR(event, constants.DocsLabel); err != nil { 149 return fmt.Errorf("removing %s label from PR %d: %w", constants.DocsLabel, event.GetNumber(), err) 150 } 151 return nil 152 }