github.com/abayer/test-infra@v0.0.5/prow/external-plugins/needs-rebase/main.go (about) 1 /* 2 Copyright 2017 The Kubernetes 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 "net/http" 24 "net/url" 25 "os/signal" 26 "strconv" 27 "syscall" 28 "time" 29 30 "github.com/sirupsen/logrus" 31 32 "k8s.io/test-infra/prow/config" 33 "k8s.io/test-infra/prow/external-plugins/needs-rebase/plugin" 34 "k8s.io/test-infra/prow/flagutil" 35 "k8s.io/test-infra/prow/github" 36 "k8s.io/test-infra/prow/hook" 37 "k8s.io/test-infra/prow/pluginhelp/externalplugins" 38 "k8s.io/test-infra/prow/plugins" 39 ) 40 41 var ( 42 port = flag.Int("port", 8888, "Port to listen on.") 43 dryRun = flag.Bool("dry-run", true, "Dry run for testing. Uses API tokens but does not mutate.") 44 pluginConfig = flag.String("plugin-config", "/etc/plugins/plugins.yaml", "Path to plugin config file.") 45 githubEndpoint = flagutil.NewStrings("https://api.github.com") 46 githubTokenFile = flag.String("github-token-file", "/etc/github/oauth", "Path to the file containing the GitHub OAuth secret.") 47 webhookSecretFile = flag.String("hmac-secret-file", "/etc/webhook/hmac", "Path to the file containing the GitHub HMAC secret.") 48 updatePeriod = flag.Duration("update-period", time.Hour*24, "Period duration for periodic scans of all PRs.") 49 ) 50 51 func init() { 52 flag.Var(&githubEndpoint, "github-endpoint", "GitHub's API endpoint.") 53 } 54 55 func main() { 56 flag.Parse() 57 logrus.SetFormatter(&logrus.JSONFormatter{}) 58 // TODO: Use global option from the prow config. 59 logrus.SetLevel(logrus.InfoLevel) 60 log := logrus.StandardLogger().WithField("plugin", "needs-rebase") 61 62 // Ignore SIGTERM so that we don't drop hooks when the pod is removed. 63 // We'll get SIGTERM first and then SIGKILL after our graceful termination 64 // deadline. 65 signal.Ignore(syscall.SIGTERM) 66 67 secretAgent := &config.SecretAgent{} 68 if err := secretAgent.Start([]string{*githubTokenFile, *webhookSecretFile}); err != nil { 69 logrus.WithError(err).Fatal("Error starting secrets agent.") 70 } 71 72 var err error 73 for _, ep := range githubEndpoint.Strings() { 74 _, err = url.ParseRequestURI(ep) 75 if err != nil { 76 logrus.WithError(err).Fatalf("Invalid --endpoint URL %q.", ep) 77 } 78 } 79 80 pa := &plugins.PluginAgent{} 81 if err := pa.Start(*pluginConfig); err != nil { 82 log.WithError(err).Fatalf("Error loading plugin config from %q.", *pluginConfig) 83 } 84 85 githubClient := github.NewClient(secretAgent.GetTokenGenerator(*githubTokenFile), githubEndpoint.Strings()...) 86 if *dryRun { 87 githubClient = github.NewDryRunClient(secretAgent.GetTokenGenerator(*githubTokenFile), githubEndpoint.Strings()...) 88 } 89 githubClient.Throttle(360, 360) 90 91 server := &Server{ 92 tokenGenerator: secretAgent.GetTokenGenerator(*webhookSecretFile), 93 ghc: githubClient, 94 log: log, 95 } 96 97 go periodicUpdate(log, pa, githubClient, *updatePeriod) 98 99 http.Handle("/", server) 100 externalplugins.ServeExternalPluginHelp(http.DefaultServeMux, log, plugin.HelpProvider) 101 logrus.Fatal(http.ListenAndServe(":"+strconv.Itoa(*port), nil)) 102 } 103 104 // Server implements http.Handler. It validates incoming GitHub webhooks and 105 // then dispatches them to the appropriate plugins. 106 type Server struct { 107 tokenGenerator func() []byte 108 ghc *github.Client 109 log *logrus.Entry 110 } 111 112 // ServeHTTP validates an incoming webhook and puts it into the event channel. 113 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 114 // TODO: Move webhook handling logic out of hook binary so that we don't have to import all 115 // plugins just to validate the webhook. 116 eventType, eventGUID, payload, ok := hook.ValidateWebhook(w, r, s.tokenGenerator()) 117 if !ok { 118 return 119 } 120 fmt.Fprint(w, "Event received. Have a nice day.") 121 122 if err := s.handleEvent(eventType, eventGUID, payload); err != nil { 123 logrus.WithError(err).Error("Error parsing event.") 124 } 125 } 126 127 func (s *Server) handleEvent(eventType, eventGUID string, payload []byte) error { 128 l := s.log.WithFields( 129 logrus.Fields{ 130 "event-type": eventType, 131 github.EventGUID: eventGUID, 132 }, 133 ) 134 switch eventType { 135 case "pull_request": 136 var pre github.PullRequestEvent 137 if err := json.Unmarshal(payload, &pre); err != nil { 138 return err 139 } 140 go func() { 141 if err := plugin.HandleEvent(l, s.ghc, &pre); err != nil { 142 l.Info("Error handling event.") 143 } 144 }() 145 default: 146 s.log.Debugf("received an event of type %q but didn't ask for it", eventType) 147 } 148 return nil 149 } 150 151 func periodicUpdate(log *logrus.Entry, pa *plugins.PluginAgent, ghc *github.Client, period time.Duration) { 152 update := func() { 153 start := time.Now() 154 if err := plugin.HandleAll(log, ghc, pa.Config()); err != nil { 155 log.WithError(err).Error("Error during periodic update of all PRs.") 156 } 157 log.WithField("duration", fmt.Sprintf("%v", time.Since(start))).Info("Periodic update complete.") 158 } 159 160 update() 161 for range time.Tick(period) { 162 update() 163 } 164 }