github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/cmd/hook/main.go (about) 1 /* 2 Copyright 2016 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 "context" 21 "flag" 22 "net/http" 23 "os" 24 "os/signal" 25 "strconv" 26 "syscall" 27 "time" 28 29 "github.com/prometheus/client_golang/prometheus/promhttp" 30 "github.com/sirupsen/logrus" 31 32 "k8s.io/test-infra/pkg/flagutil" 33 "k8s.io/test-infra/prow/config" 34 "k8s.io/test-infra/prow/config/secret" 35 prowflagutil "k8s.io/test-infra/prow/flagutil" 36 "k8s.io/test-infra/prow/hook" 37 "k8s.io/test-infra/prow/logrusutil" 38 "k8s.io/test-infra/prow/metrics" 39 pluginhelp "k8s.io/test-infra/prow/pluginhelp/hook" 40 "k8s.io/test-infra/prow/plugins" 41 "k8s.io/test-infra/prow/slack" 42 ) 43 44 type options struct { 45 port int 46 47 configPath string 48 jobConfigPath string 49 pluginConfig string 50 51 dryRun bool 52 gracePeriod time.Duration 53 kubernetes prowflagutil.KubernetesOptions 54 github prowflagutil.GitHubOptions 55 56 webhookSecretFile string 57 slackTokenFile string 58 } 59 60 func (o *options) Validate() error { 61 for _, group := range []flagutil.OptionGroup{&o.kubernetes, &o.github} { 62 if err := group.Validate(o.dryRun); err != nil { 63 return 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 75 fs.StringVar(&o.configPath, "config-path", "/etc/config/config.yaml", "Path to config.yaml.") 76 fs.StringVar(&o.jobConfigPath, "job-config-path", "", "Path to prow job configs.") 77 fs.StringVar(&o.pluginConfig, "plugin-config", "/etc/plugins/plugins.yaml", "Path to plugin config file.") 78 79 fs.BoolVar(&o.dryRun, "dry-run", true, "Dry run for testing. Uses API tokens but does not mutate.") 80 fs.DurationVar(&o.gracePeriod, "grace-period", 180*time.Second, "On shutdown, try to handle remaining events for the specified duration. ") 81 for _, group := range []flagutil.OptionGroup{&o.kubernetes, &o.github} { 82 group.AddFlags(fs) 83 } 84 85 fs.StringVar(&o.webhookSecretFile, "hmac-secret-file", "/etc/webhook/hmac", "Path to the file containing the GitHub HMAC secret.") 86 fs.StringVar(&o.slackTokenFile, "slack-token-file", "", "Path to the file containing the Slack token to use.") 87 fs.Parse(os.Args[1:]) 88 return o 89 } 90 91 func main() { 92 o := gatherOptions() 93 if err := o.Validate(); err != nil { 94 logrus.Fatalf("Invalid options: %v", err) 95 } 96 logrus.SetFormatter(logrusutil.NewDefaultFieldsFormatter(nil, logrus.Fields{"component": "hook"})) 97 98 configAgent := &config.Agent{} 99 if err := configAgent.Start(o.configPath, o.jobConfigPath); err != nil { 100 logrus.WithError(err).Fatal("Error starting config agent.") 101 } 102 103 var tokens []string 104 105 // Append the path of hmac and github secrets. 106 tokens = append(tokens, o.github.TokenPath) 107 tokens = append(tokens, o.webhookSecretFile) 108 109 // This is necessary since slack token is optional. 110 if o.slackTokenFile != "" { 111 tokens = append(tokens, o.slackTokenFile) 112 } 113 114 secretAgent := &secret.Agent{} 115 if err := secretAgent.Start(tokens); err != nil { 116 logrus.WithError(err).Fatal("Error starting secrets agent.") 117 } 118 119 githubClient, err := o.github.GitHubClient(secretAgent, o.dryRun) 120 if err != nil { 121 logrus.WithError(err).Fatal("Error getting GitHub client.") 122 } 123 gitClient, err := o.github.GitClient(secretAgent, o.dryRun) 124 if err != nil { 125 logrus.WithError(err).Fatal("Error getting Git client.") 126 } 127 defer gitClient.Clean() 128 129 kubeClient, err := o.kubernetes.Client(configAgent.Config().ProwJobNamespace, o.dryRun) 130 if err != nil { 131 logrus.WithError(err).Fatal("Error getting Kubernetes client.") 132 } 133 134 var slackClient *slack.Client 135 if !o.dryRun && string(secretAgent.GetSecret(o.slackTokenFile)) != "" { 136 logrus.Info("Using real slack client.") 137 slackClient = slack.NewClient(secretAgent.GetTokenGenerator(o.slackTokenFile)) 138 } 139 if slackClient == nil { 140 logrus.Info("Using fake slack client.") 141 slackClient = slack.NewFakeClient() 142 } 143 144 clientAgent := &plugins.ClientAgent{ 145 GitHubClient: githubClient, 146 KubeClient: kubeClient, 147 GitClient: gitClient, 148 SlackClient: slackClient, 149 } 150 151 pluginAgent := &plugins.ConfigAgent{} 152 if err := pluginAgent.Start(o.pluginConfig); err != nil { 153 logrus.WithError(err).Fatal("Error starting plugins.") 154 } 155 156 promMetrics := hook.NewMetrics() 157 158 // Push metrics to the configured prometheus pushgateway endpoint. 159 pushGateway := configAgent.Config().PushGateway 160 if pushGateway.Endpoint != "" { 161 go metrics.PushMetrics("hook", pushGateway.Endpoint, pushGateway.Interval) 162 } 163 164 server := &hook.Server{ 165 ClientAgent: clientAgent, 166 ConfigAgent: configAgent, 167 Plugins: pluginAgent, 168 Metrics: promMetrics, 169 TokenGenerator: secretAgent.GetTokenGenerator(o.webhookSecretFile), 170 } 171 defer server.GracefulShutdown() 172 173 // Return 200 on / for health checks. 174 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {}) 175 http.Handle("/metrics", promhttp.Handler()) 176 // For /hook, handle a webhook normally. 177 http.Handle("/hook", server) 178 // Serve plugin help information from /plugin-help. 179 http.Handle("/plugin-help", pluginhelp.NewHelpAgent(pluginAgent, githubClient)) 180 181 httpServer := &http.Server{Addr: ":" + strconv.Itoa(o.port)} 182 183 // Shutdown gracefully on SIGTERM or SIGINT 184 sig := make(chan os.Signal, 1) 185 signal.Notify(sig, os.Interrupt, syscall.SIGTERM) 186 go func() { 187 <-sig 188 logrus.Info("Hook is shutting down...") 189 ctx, cancel := context.WithTimeout(context.Background(), o.gracePeriod) 190 defer cancel() 191 httpServer.Shutdown(ctx) 192 }() 193 194 logrus.WithError(httpServer.ListenAndServe()).Warn("Server exited.") 195 }