github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/cmd/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 "strconv" 26 "time" 27 28 "github.com/sirupsen/logrus" 29 "sigs.k8s.io/prow/cmd/external-plugins/needs-rebase/plugin" 30 "sigs.k8s.io/prow/pkg/config/secret" 31 "sigs.k8s.io/prow/pkg/flagutil" 32 prowflagutil "sigs.k8s.io/prow/pkg/flagutil" 33 pluginsflagutil "sigs.k8s.io/prow/pkg/flagutil/plugins" 34 "sigs.k8s.io/prow/pkg/github" 35 "sigs.k8s.io/prow/pkg/interrupts" 36 "sigs.k8s.io/prow/pkg/labels" 37 "sigs.k8s.io/prow/pkg/logrusutil" 38 "sigs.k8s.io/prow/pkg/pjutil" 39 "sigs.k8s.io/prow/pkg/pluginhelp/externalplugins" 40 ) 41 42 type options struct { 43 port int 44 45 pluginsConfig pluginsflagutil.PluginOptions 46 dryRun bool 47 github prowflagutil.GitHubOptions 48 instrumentationOptions prowflagutil.InstrumentationOptions 49 logLevel string 50 51 updatePeriod time.Duration 52 53 webhookSecretFile string 54 55 cacheValidTime int 56 } 57 58 const defaultHourlyTokens = 360 59 60 func (o *options) Validate() error { 61 for idx, group := range []flagutil.OptionGroup{&o.github} { 62 if err := group.Validate(o.dryRun); err != nil { 63 return fmt.Errorf("%d: %w", idx, err) 64 } 65 } 66 67 return nil 68 } 69 70 func gatherOptions() options { 71 o := options{} 72 fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError) 73 fs.IntVar(&o.port, "port", 8888, "Port to listen on.") 74 fs.BoolVar(&o.dryRun, "dry-run", true, "Dry run for testing. Uses API tokens but does not mutate.") 75 fs.DurationVar(&o.updatePeriod, "update-period", time.Hour*24, "Period duration for periodic scans of all PRs.") 76 fs.StringVar(&o.webhookSecretFile, "hmac-secret-file", "/etc/webhook/hmac", "Path to the file containing the GitHub HMAC secret.") 77 fs.StringVar(&o.logLevel, "log-level", "debug", fmt.Sprintf("Log level is one of %v.", logrus.AllLevels)) 78 fs.IntVar(&o.cacheValidTime, "cache-valid-time", 0, "Do not re-check PR mergeability for comment events within this time (seconds)") 79 80 o.github.AddCustomizedFlags(fs, prowflagutil.ThrottlerDefaults(defaultHourlyTokens, defaultHourlyTokens)) 81 82 o.pluginsConfig.PluginConfigPathDefault = "/etc/plugins/plugins.yaml" 83 for _, group := range []flagutil.OptionGroup{&o.instrumentationOptions, &o.pluginsConfig} { 84 group.AddFlags(fs) 85 } 86 fs.Parse(os.Args[1:]) 87 return o 88 } 89 90 func main() { 91 logrusutil.ComponentInit() 92 o := gatherOptions() 93 if err := o.Validate(); err != nil { 94 logrus.Fatalf("Invalid options: %v", err) 95 } 96 97 logLevel, err := logrus.ParseLevel(o.logLevel) 98 if err != nil { 99 logrus.WithError(err).Fatal("Failed to parse loglevel") 100 } 101 logrus.SetLevel(logLevel) 102 log := logrus.StandardLogger().WithField("plugin", labels.NeedsRebase) 103 104 if err := secret.Add(o.webhookSecretFile); err != nil { 105 logrus.WithError(err).Fatal("Error starting secrets agent.") 106 } 107 108 pa, err := o.pluginsConfig.PluginAgent() 109 if err != nil { 110 log.WithError(err).Fatal("Error loading plugin config") 111 } 112 113 githubClient, err := o.github.GitHubClient(o.dryRun) 114 if err != nil { 115 logrus.WithError(err).Fatal("Error getting GitHub client.") 116 } 117 118 issueCache := plugin.NewCache(o.cacheValidTime) 119 120 server := &Server{ 121 tokenGenerator: secret.GetTokenGenerator(o.webhookSecretFile), 122 ghc: githubClient, 123 log: log, 124 issueCache: issueCache, 125 } 126 127 defer interrupts.WaitForGracefulShutdown() 128 129 interrupts.TickLiteral(func() { 130 start := time.Now() 131 if err := plugin.HandleAll(log, githubClient, pa.Config(), o.github.AppID != "", issueCache); err != nil { 132 log.WithError(err).Error("Error during periodic update of all PRs.") 133 } 134 log.WithField("duration", fmt.Sprintf("%v", time.Since(start))).Info("Periodic update complete.") 135 }, o.updatePeriod) 136 137 health := pjutil.NewHealthOnPort(o.instrumentationOptions.HealthPort) 138 health.ServeReady() 139 140 mux := http.NewServeMux() 141 mux.Handle("/", server) 142 externalplugins.ServeExternalPluginHelp(mux, log, plugin.HelpProvider) 143 httpServer := &http.Server{Addr: ":" + strconv.Itoa(o.port), Handler: mux} 144 interrupts.ListenAndServe(httpServer, 5*time.Second) 145 } 146 147 // Server implements http.Handler. It validates incoming GitHub webhooks and 148 // then dispatches them to the appropriate plugins. 149 type Server struct { 150 tokenGenerator func() []byte 151 ghc github.Client 152 log *logrus.Entry 153 issueCache *plugin.Cache 154 } 155 156 // ServeHTTP validates an incoming webhook and puts it into the event channel. 157 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 158 // TODO: Move webhook handling logic out of hook binary so that we don't have to import all 159 // plugins just to validate the webhook. 160 eventType, eventGUID, payload, ok, _ := github.ValidateWebhook(w, r, s.tokenGenerator) 161 if !ok { 162 return 163 } 164 fmt.Fprint(w, "Event received. Have a nice day.") 165 166 if err := s.handleEvent(eventType, eventGUID, payload); err != nil { 167 logrus.WithError(err).Error("Error parsing event.") 168 } 169 } 170 171 func (s *Server) handleEvent(eventType, eventGUID string, payload []byte) error { 172 l := s.log.WithFields( 173 logrus.Fields{ 174 "event-type": eventType, 175 github.EventGUID: eventGUID, 176 }, 177 ) 178 switch eventType { 179 case "pull_request": 180 var pre github.PullRequestEvent 181 if err := json.Unmarshal(payload, &pre); err != nil { 182 return err 183 } 184 go func() { 185 if err := plugin.HandlePullRequestEvent(l, s.ghc, &pre); err != nil { 186 l.WithField("event-type", eventType).WithError(err).Info("Error handling event.") 187 } 188 }() 189 case "issue_comment": 190 var ice github.IssueCommentEvent 191 if err := json.Unmarshal(payload, &ice); err != nil { 192 return err 193 } 194 go func() { 195 if err := plugin.HandleIssueCommentEvent(l, s.ghc, &ice, s.issueCache); err != nil { 196 l.WithField("event-type", eventType).WithError(err).Info("Error handling event.") 197 } 198 }() 199 default: 200 s.log.Debugf("received an event of type %q but didn't ask for it", eventType) 201 } 202 return nil 203 }