github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/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 "os" 25 "os/signal" 26 "strconv" 27 "syscall" 28 "time" 29 30 "github.com/sirupsen/logrus" 31 32 "k8s.io/test-infra/pkg/flagutil" 33 "k8s.io/test-infra/prow/config/secret" 34 "k8s.io/test-infra/prow/external-plugins/needs-rebase/plugin" 35 prowflagutil "k8s.io/test-infra/prow/flagutil" 36 "k8s.io/test-infra/prow/github" 37 "k8s.io/test-infra/prow/labels" 38 39 // TODO: Remove the need for this import; it's currently required to allow the plugin config loader to function correctly (it expects plugins to be initialised) 40 // See https://github.com/kubernetes/test-infra/pull/8933#issuecomment-411511180 41 _ "k8s.io/test-infra/prow/hook" 42 "k8s.io/test-infra/prow/pluginhelp/externalplugins" 43 "k8s.io/test-infra/prow/plugins" 44 ) 45 46 type options struct { 47 port int 48 49 pluginConfig string 50 dryRun bool 51 github prowflagutil.GitHubOptions 52 53 updatePeriod time.Duration 54 55 webhookSecretFile string 56 } 57 58 func (o *options) Validate() error { 59 for _, group := range []flagutil.OptionGroup{&o.github} { 60 if err := group.Validate(o.dryRun); err != nil { 61 return err 62 } 63 } 64 65 return nil 66 } 67 68 func gatherOptions() options { 69 o := options{} 70 fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError) 71 fs.IntVar(&o.port, "port", 8888, "Port to listen on.") 72 fs.StringVar(&o.pluginConfig, "plugin-config", "/etc/plugins/plugins.yaml", "Path to plugin config file.") 73 fs.BoolVar(&o.dryRun, "dry-run", true, "Dry run for testing. Uses API tokens but does not mutate.") 74 fs.DurationVar(&o.updatePeriod, "update-period", time.Hour*24, "Period duration for periodic scans of all PRs.") 75 fs.StringVar(&o.webhookSecretFile, "hmac-secret-file", "/etc/webhook/hmac", "Path to the file containing the GitHub HMAC secret.") 76 77 for _, group := range []flagutil.OptionGroup{&o.github} { 78 group.AddFlags(fs) 79 } 80 fs.Parse(os.Args[1:]) 81 return o 82 } 83 84 func main() { 85 o := gatherOptions() 86 if err := o.Validate(); err != nil { 87 logrus.Fatalf("Invalid options: %v", err) 88 } 89 90 logrus.SetFormatter(&logrus.JSONFormatter{}) 91 // TODO: Use global option from the prow config. 92 logrus.SetLevel(logrus.InfoLevel) 93 log := logrus.StandardLogger().WithField("plugin", labels.NeedsRebase) 94 95 // Ignore SIGTERM so that we don't drop hooks when the pod is removed. 96 // We'll get SIGTERM first and then SIGKILL after our graceful termination 97 // deadline. 98 signal.Ignore(syscall.SIGTERM) 99 100 secretAgent := &secret.Agent{} 101 if err := secretAgent.Start([]string{o.github.TokenPath, o.webhookSecretFile}); err != nil { 102 logrus.WithError(err).Fatal("Error starting secrets agent.") 103 } 104 105 pa := &plugins.ConfigAgent{} 106 if err := pa.Start(o.pluginConfig); err != nil { 107 log.WithError(err).Fatalf("Error loading plugin config from %q.", o.pluginConfig) 108 } 109 110 githubClient, err := o.github.GitHubClient(secretAgent, o.dryRun) 111 if err != nil { 112 logrus.WithError(err).Fatal("Error getting GitHub client.") 113 } 114 githubClient.Throttle(360, 360) 115 116 server := &Server{ 117 tokenGenerator: secretAgent.GetTokenGenerator(o.webhookSecretFile), 118 ghc: githubClient, 119 log: log, 120 } 121 122 go periodicUpdate(log, pa, githubClient, o.updatePeriod) 123 124 http.Handle("/", server) 125 externalplugins.ServeExternalPluginHelp(http.DefaultServeMux, log, plugin.HelpProvider) 126 logrus.Fatal(http.ListenAndServe(":"+strconv.Itoa(o.port), nil)) 127 } 128 129 // Server implements http.Handler. It validates incoming GitHub webhooks and 130 // then dispatches them to the appropriate plugins. 131 type Server struct { 132 tokenGenerator func() []byte 133 ghc *github.Client 134 log *logrus.Entry 135 } 136 137 // ServeHTTP validates an incoming webhook and puts it into the event channel. 138 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 139 // TODO: Move webhook handling logic out of hook binary so that we don't have to import all 140 // plugins just to validate the webhook. 141 eventType, eventGUID, payload, ok, _ := github.ValidateWebhook(w, r, s.tokenGenerator()) 142 if !ok { 143 return 144 } 145 fmt.Fprint(w, "Event received. Have a nice day.") 146 147 if err := s.handleEvent(eventType, eventGUID, payload); err != nil { 148 logrus.WithError(err).Error("Error parsing event.") 149 } 150 } 151 152 func (s *Server) handleEvent(eventType, eventGUID string, payload []byte) error { 153 l := s.log.WithFields( 154 logrus.Fields{ 155 "event-type": eventType, 156 github.EventGUID: eventGUID, 157 }, 158 ) 159 switch eventType { 160 case "pull_request": 161 var pre github.PullRequestEvent 162 if err := json.Unmarshal(payload, &pre); err != nil { 163 return err 164 } 165 go func() { 166 if err := plugin.HandleEvent(l, s.ghc, &pre); err != nil { 167 l.Info("Error handling event.") 168 } 169 }() 170 default: 171 s.log.Debugf("received an event of type %q but didn't ask for it", eventType) 172 } 173 return nil 174 } 175 176 func periodicUpdate(log *logrus.Entry, pa *plugins.ConfigAgent, ghc *github.Client, period time.Duration) { 177 update := func() { 178 start := time.Now() 179 if err := plugin.HandleAll(log, ghc, pa.Config()); err != nil { 180 log.WithError(err).Error("Error during periodic update of all PRs.") 181 } 182 log.WithField("duration", fmt.Sprintf("%v", time.Since(start))).Info("Periodic update complete.") 183 } 184 185 update() 186 for range time.Tick(period) { 187 update() 188 } 189 }