github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/cmd/jenkins-operator/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 "crypto/tls" 21 "crypto/x509" 22 "errors" 23 "flag" 24 "fmt" 25 "io/ioutil" 26 "net/http" 27 "net/url" 28 "os" 29 "os/signal" 30 "syscall" 31 "time" 32 33 "github.com/NYTimes/gziphandler" 34 "github.com/prometheus/client_golang/prometheus/promhttp" 35 "github.com/sirupsen/logrus" 36 "k8s.io/apimachinery/pkg/labels" 37 38 "k8s.io/test-infra/pkg/flagutil" 39 "k8s.io/test-infra/prow/config" 40 "k8s.io/test-infra/prow/config/secret" 41 prowflagutil "k8s.io/test-infra/prow/flagutil" 42 "k8s.io/test-infra/prow/jenkins" 43 "k8s.io/test-infra/prow/kube" 44 "k8s.io/test-infra/prow/logrusutil" 45 m "k8s.io/test-infra/prow/metrics" 46 ) 47 48 type options struct { 49 configPath string 50 jobConfigPath string 51 selector string 52 totURL string 53 54 jenkinsURL string 55 jenkinsUserName string 56 jenkinsTokenFile string 57 jenkinsBearerTokenFile string 58 certFile string 59 keyFile string 60 caCertFile string 61 csrfProtect bool 62 63 dryRun bool 64 kubernetes prowflagutil.KubernetesOptions 65 github prowflagutil.GitHubOptions 66 } 67 68 func (o *options) Validate() error { 69 for _, group := range []flagutil.OptionGroup{&o.kubernetes, &o.github} { 70 if err := group.Validate(o.dryRun); err != nil { 71 return err 72 } 73 } 74 75 if _, err := url.ParseRequestURI(o.jenkinsURL); err != nil { 76 return fmt.Errorf("invalid -jenkins-url URI: %q", o.jenkinsURL) 77 } 78 79 if o.jenkinsTokenFile == "" && o.jenkinsBearerTokenFile == "" { 80 return errors.New("either --jenkins-token-file or --jenkins-bearer-token-file must be set") 81 } else if o.jenkinsTokenFile != "" && o.jenkinsBearerTokenFile != "" { 82 return errors.New("only one of --jenkins-token-file or --jenkins-bearer-token-file can be set") 83 } 84 85 var transportSecretsProvided int 86 if o.certFile == "" { 87 transportSecretsProvided = transportSecretsProvided + 1 88 } 89 if o.keyFile == "" { 90 transportSecretsProvided = transportSecretsProvided + 1 91 } 92 if o.caCertFile == "" { 93 transportSecretsProvided = transportSecretsProvided + 1 94 } 95 if transportSecretsProvided != 0 && transportSecretsProvided != 3 { 96 return errors.New("either --cert-file, --key-file, and --ca-cert-file must all be provided or none of them must be provided") 97 } 98 return nil 99 } 100 101 func gatherOptions() options { 102 o := options{} 103 fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError) 104 fs.StringVar(&o.configPath, "config-path", "/etc/config/config.yaml", "Path to config.yaml.") 105 fs.StringVar(&o.jobConfigPath, "job-config-path", "", "Path to prow job configs.") 106 fs.StringVar(&o.selector, "label-selector", kube.EmptySelector, "Label selector to be applied in prowjobs. See https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors for constructing a label selector.") 107 fs.StringVar(&o.totURL, "tot-url", "", "Tot URL") 108 109 fs.StringVar(&o.jenkinsURL, "jenkins-url", "http://jenkins-proxy", "Jenkins URL") 110 fs.StringVar(&o.jenkinsUserName, "jenkins-user", "jenkins-trigger", "Jenkins username") 111 fs.StringVar(&o.jenkinsTokenFile, "jenkins-token-file", "", "Path to the file containing the Jenkins API token.") 112 fs.StringVar(&o.jenkinsBearerTokenFile, "jenkins-bearer-token-file", "", "Path to the file containing the Jenkins API bearer token.") 113 fs.StringVar(&o.certFile, "cert-file", "", "Path to a PEM-encoded certificate file.") 114 fs.StringVar(&o.keyFile, "key-file", "", "Path to a PEM-encoded key file.") 115 fs.StringVar(&o.caCertFile, "ca-cert-file", "", "Path to a PEM-encoded CA certificate file.") 116 fs.BoolVar(&o.csrfProtect, "csrf-protect", false, "Request a CSRF protection token from Jenkins that will be used in all subsequent requests to Jenkins.") 117 118 fs.BoolVar(&o.dryRun, "dry-run", true, "Whether or not to make mutating API calls to GitHub/Kubernetes/Jenkins.") 119 for _, group := range []flagutil.OptionGroup{&o.kubernetes, &o.github} { 120 group.AddFlags(fs) 121 } 122 fs.Parse(os.Args[1:]) 123 return o 124 } 125 126 func main() { 127 o := gatherOptions() 128 if err := o.Validate(); err != nil { 129 logrus.Fatalf("Invalid options: %v", err) 130 } 131 logrus.SetFormatter( 132 logrusutil.NewDefaultFieldsFormatter(nil, logrus.Fields{"component": "jenkins-operator"}), 133 ) 134 135 if _, err := labels.Parse(o.selector); err != nil { 136 logrus.WithError(err).Fatal("Error parsing label selector.") 137 } 138 139 configAgent := &config.Agent{} 140 if err := configAgent.Start(o.configPath, o.jobConfigPath); err != nil { 141 logrus.WithError(err).Fatal("Error starting config agent.") 142 } 143 144 kubeClient, err := o.kubernetes.Client(configAgent.Config().ProwJobNamespace, o.dryRun) 145 if err != nil { 146 logrus.WithError(err).Fatal("Error getting kube client.") 147 } 148 149 ac := &jenkins.AuthConfig{ 150 CSRFProtect: o.csrfProtect, 151 } 152 153 var tokens []string 154 tokens = append(tokens, o.github.TokenPath) 155 156 if o.jenkinsTokenFile != "" { 157 tokens = append(tokens, o.jenkinsTokenFile) 158 } 159 160 if o.jenkinsBearerTokenFile != "" { 161 tokens = append(tokens, o.jenkinsBearerTokenFile) 162 } 163 164 // Start the secret agent. 165 secretAgent := &secret.Agent{} 166 if err := secretAgent.Start(tokens); err != nil { 167 logrus.WithError(err).Fatal("Error starting secrets agent.") 168 } 169 170 if o.jenkinsTokenFile != "" { 171 ac.Basic = &jenkins.BasicAuthConfig{ 172 User: o.jenkinsUserName, 173 GetToken: secretAgent.GetTokenGenerator(o.jenkinsTokenFile), 174 } 175 } else if o.jenkinsBearerTokenFile != "" { 176 ac.BearerToken = &jenkins.BearerTokenAuthConfig{ 177 GetToken: secretAgent.GetTokenGenerator(o.jenkinsBearerTokenFile), 178 } 179 } 180 var tlsConfig *tls.Config 181 if o.certFile != "" && o.keyFile != "" { 182 config, err := loadCerts(o.certFile, o.keyFile, o.caCertFile) 183 if err != nil { 184 logrus.WithError(err).Fatalf("Could not read certificate files.") 185 } 186 tlsConfig = config 187 } 188 metrics := jenkins.NewMetrics() 189 jc, err := jenkins.NewClient(o.jenkinsURL, o.dryRun, tlsConfig, ac, nil, metrics.ClientMetrics) 190 if err != nil { 191 logrus.WithError(err).Fatalf("Could not setup Jenkins client.") 192 } 193 194 githubClient, err := o.github.GitHubClient(secretAgent, o.dryRun) 195 if err != nil { 196 logrus.WithError(err).Fatal("Error getting GitHub client.") 197 } 198 199 c, err := jenkins.NewController(kubeClient, jc, githubClient, nil, configAgent, o.totURL, o.selector) 200 if err != nil { 201 logrus.WithError(err).Fatal("Failed to instantiate Jenkins controller.") 202 } 203 204 // Push metrics to the configured prometheus pushgateway endpoint. 205 pushGateway := configAgent.Config().PushGateway 206 if pushGateway.Endpoint != "" { 207 go m.PushMetrics("jenkins-operator", pushGateway.Endpoint, pushGateway.Interval) 208 } 209 // Serve Jenkins logs here and proxy deck to use this endpoint 210 // instead of baking agent-specific logic in deck. This func also 211 // serves prometheus metrics. 212 go serve(jc) 213 // gather metrics for the jobs handled by the jenkins controller. 214 go gather(c) 215 216 tick := time.Tick(30 * time.Second) 217 sig := make(chan os.Signal, 1) 218 signal.Notify(sig, os.Interrupt, syscall.SIGTERM) 219 220 for { 221 select { 222 case <-tick: 223 start := time.Now() 224 if err := c.Sync(); err != nil { 225 logrus.WithError(err).Error("Error syncing.") 226 } 227 duration := time.Since(start) 228 logrus.WithField("duration", fmt.Sprintf("%v", duration)).Info("Synced") 229 metrics.ResyncPeriod.Observe(duration.Seconds()) 230 case <-sig: 231 logrus.Info("Jenkins operator is shutting down...") 232 return 233 } 234 } 235 } 236 237 func loadCerts(certFile, keyFile, caCertFile string) (*tls.Config, error) { 238 cert, err := tls.LoadX509KeyPair(certFile, keyFile) 239 if err != nil { 240 return nil, err 241 } 242 243 tlsConfig := &tls.Config{ 244 Certificates: []tls.Certificate{cert}, 245 } 246 247 if caCertFile != "" { 248 caCert, err := ioutil.ReadFile(caCertFile) 249 if err != nil { 250 return nil, err 251 } 252 caCertPool := x509.NewCertPool() 253 caCertPool.AppendCertsFromPEM(caCert) 254 tlsConfig.RootCAs = caCertPool 255 } 256 257 tlsConfig.BuildNameToCertificate() 258 return tlsConfig, nil 259 } 260 261 // serve starts a http server and serves Jenkins logs 262 // and prometheus metrics. Meant to be called inside 263 // a goroutine. 264 func serve(jc *jenkins.Client) { 265 http.Handle("/", gziphandler.GzipHandler(handleLog(jc))) 266 http.Handle("/metrics", promhttp.Handler()) 267 logrus.WithError(http.ListenAndServe(":8080", nil)).Fatal("ListenAndServe returned.") 268 } 269 270 // gather metrics from the jenkins controller. 271 // Meant to be called inside a goroutine. 272 func gather(c *jenkins.Controller) { 273 tick := time.Tick(30 * time.Second) 274 sig := make(chan os.Signal, 1) 275 signal.Notify(sig, os.Interrupt, syscall.SIGTERM) 276 277 for { 278 select { 279 case <-tick: 280 start := time.Now() 281 c.SyncMetrics() 282 logrus.WithField("metrics-duration", fmt.Sprintf("%v", time.Since(start))).Debug("Metrics synced") 283 case <-sig: 284 logrus.Debug("Jenkins operator gatherer is shutting down...") 285 return 286 } 287 } 288 }