github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/cmd/tide/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 "context" 21 "flag" 22 "net/http" 23 "os" 24 "os/signal" 25 "strconv" 26 "syscall" 27 "time" 28 29 "github.com/sirupsen/logrus" 30 31 "k8s.io/test-infra/pkg/flagutil" 32 "k8s.io/test-infra/prow/config" 33 "k8s.io/test-infra/prow/config/secret" 34 prowflagutil "k8s.io/test-infra/prow/flagutil" 35 "k8s.io/test-infra/prow/logrusutil" 36 "k8s.io/test-infra/prow/metrics" 37 "k8s.io/test-infra/prow/tide" 38 ) 39 40 type options struct { 41 port int 42 43 configPath string 44 jobConfigPath string 45 46 syncThrottle int 47 statusThrottle int 48 49 dryRun bool 50 runOnce bool 51 kubernetes prowflagutil.KubernetesOptions 52 github prowflagutil.GitHubOptions 53 } 54 55 func (o *options) Validate() error { 56 for _, group := range []flagutil.OptionGroup{&o.kubernetes, &o.github} { 57 if err := group.Validate(o.dryRun); err != nil { 58 return err 59 } 60 } 61 62 return nil 63 } 64 65 func gatherOptions() options { 66 o := options{} 67 fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError) 68 fs.IntVar(&o.port, "port", 8888, "Port to listen on.") 69 fs.StringVar(&o.configPath, "config-path", "/etc/config/config.yaml", "Path to config.yaml.") 70 fs.StringVar(&o.jobConfigPath, "job-config-path", "", "Path to prow job configs.") 71 fs.BoolVar(&o.dryRun, "dry-run", true, "Whether to mutate any real-world state.") 72 fs.BoolVar(&o.runOnce, "run-once", false, "If true, run only once then quit.") 73 for _, group := range []flagutil.OptionGroup{&o.kubernetes, &o.github} { 74 group.AddFlags(fs) 75 } 76 fs.IntVar(&o.syncThrottle, "sync-hourly-tokens", 800, "The maximum number of tokens per hour to be used by the sync controller.") 77 fs.IntVar(&o.statusThrottle, "status-hourly-tokens", 400, "The maximum number of tokens per hour to be used by the status controller.") 78 79 fs.Parse(os.Args[1:]) 80 return o 81 } 82 83 func main() { 84 o := gatherOptions() 85 if err := o.Validate(); err != nil { 86 logrus.Fatalf("Invalid options: %v", err) 87 } 88 89 logrus.SetFormatter( 90 logrusutil.NewDefaultFieldsFormatter(nil, logrus.Fields{"component": "tide"}), 91 ) 92 93 configAgent := &config.Agent{} 94 if err := configAgent.Start(o.configPath, o.jobConfigPath); err != nil { 95 logrus.WithError(err).Fatal("Error starting config agent.") 96 } 97 98 secretAgent := &secret.Agent{} 99 if err := secretAgent.Start([]string{o.github.TokenPath}); err != nil { 100 logrus.WithError(err).Fatal("Error starting secrets agent.") 101 } 102 103 githubSync, err := o.github.GitHubClient(secretAgent, o.dryRun) 104 if err != nil { 105 logrus.WithError(err).Fatal("Error getting GitHub client.") 106 } 107 108 githubStatus, err := o.github.GitHubClient(secretAgent, o.dryRun) 109 if err != nil { 110 logrus.WithError(err).Fatal("Error getting GitHub client.") 111 } 112 113 // The sync loop should be allowed more tokens than the status loop because 114 // it has to list all PRs in the pool every loop while the status loop only 115 // has to list changed PRs every loop. 116 // The sync loop should have a much lower burst allowance than the status 117 // loop which may need to update many statuses upon restarting Tide after 118 // changing the context format or starting Tide on a new repo. 119 githubSync.Throttle(o.syncThrottle, 3*tokensPerIteration(o.syncThrottle, configAgent.Config().Tide.SyncPeriod)) 120 githubStatus.Throttle(o.statusThrottle, o.statusThrottle/2) 121 122 gitClient, err := o.github.GitClient(secretAgent, o.dryRun) 123 if err != nil { 124 logrus.WithError(err).Fatal("Error getting Git client.") 125 } 126 defer gitClient.Clean() 127 128 kubeClient, err := o.kubernetes.Client(configAgent.Config().ProwJobNamespace, o.dryRun) 129 if err != nil { 130 logrus.WithError(err).Fatal("Error getting Kubernetes client.") 131 } 132 133 c := tide.NewController(githubSync, githubStatus, kubeClient, configAgent, gitClient, nil) 134 defer c.Shutdown() 135 http.Handle("/", c) 136 http.Handle("/history", c.History) 137 server := &http.Server{Addr: ":" + strconv.Itoa(o.port)} 138 139 // Push metrics to the configured prometheus pushgateway endpoint. 140 pushGateway := configAgent.Config().PushGateway 141 if pushGateway.Endpoint != "" { 142 go metrics.PushMetrics("tide", pushGateway.Endpoint, pushGateway.Interval) 143 } 144 145 start := time.Now() 146 sync(c) 147 if o.runOnce { 148 return 149 } 150 go func() { 151 sig := make(chan os.Signal, 1) 152 signal.Notify(sig, os.Interrupt, syscall.SIGTERM) 153 for { 154 select { 155 case <-time.After(time.Until(start.Add(configAgent.Config().Tide.SyncPeriod))): 156 start = time.Now() 157 sync(c) 158 case <-sig: 159 logrus.Info("Tide is shutting down...") 160 // Shutdown the http server with a 10s timeout then return to execute 161 // deferred c.Shutdown() 162 ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) 163 defer cancel() // frees ctx resources 164 server.Shutdown(ctx) 165 return 166 } 167 } 168 }() 169 logrus.WithError(server.ListenAndServe()).Warn("Tide HTTP server stopped.") 170 } 171 172 func sync(c *tide.Controller) { 173 if err := c.Sync(); err != nil { 174 logrus.WithError(err).Error("Error syncing.") 175 } 176 } 177 178 func tokensPerIteration(hourlyTokens int, iterPeriod time.Duration) int { 179 tokenRate := float64(hourlyTokens) / float64(time.Hour) 180 return int(tokenRate * float64(iterPeriod)) 181 }