github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/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 "net/http" 26 "net/url" 27 "os" 28 "time" 29 30 "github.com/NYTimes/gziphandler" 31 "github.com/sirupsen/logrus" 32 "k8s.io/apimachinery/pkg/labels" 33 "sigs.k8s.io/prow/pkg/pjutil/pprof" 34 35 "sigs.k8s.io/prow/pkg/config/secret" 36 "sigs.k8s.io/prow/pkg/flagutil" 37 prowflagutil "sigs.k8s.io/prow/pkg/flagutil" 38 configflagutil "sigs.k8s.io/prow/pkg/flagutil/config" 39 "sigs.k8s.io/prow/pkg/interrupts" 40 "sigs.k8s.io/prow/pkg/jenkins" 41 "sigs.k8s.io/prow/pkg/logrusutil" 42 m "sigs.k8s.io/prow/pkg/metrics" 43 ) 44 45 type options struct { 46 config configflagutil.ConfigOptions 47 selector string 48 totURL string 49 50 jenkinsURL string 51 jenkinsUserName string 52 jenkinsTokenFile string 53 jenkinsBearerTokenFile string 54 certFile string 55 keyFile string 56 caCertFile string 57 csrfProtect bool 58 skipReport bool 59 60 dryRun bool 61 kubernetes prowflagutil.KubernetesOptions 62 github prowflagutil.GitHubOptions 63 instrumentationOptions prowflagutil.InstrumentationOptions 64 } 65 66 func (o *options) Validate() error { 67 for _, group := range []flagutil.OptionGroup{&o.kubernetes, &o.github, &o.config} { 68 if err := group.Validate(o.dryRun); err != nil { 69 return err 70 } 71 } 72 73 if _, err := url.ParseRequestURI(o.jenkinsURL); err != nil { 74 return fmt.Errorf("invalid -jenkins-url URI: %q", o.jenkinsURL) 75 } 76 77 if o.jenkinsTokenFile == "" && o.jenkinsBearerTokenFile == "" { 78 return errors.New("either --jenkins-token-file or --jenkins-bearer-token-file must be set") 79 } else if o.jenkinsTokenFile != "" && o.jenkinsBearerTokenFile != "" { 80 return errors.New("only one of --jenkins-token-file or --jenkins-bearer-token-file can be set") 81 } 82 83 var transportSecretsProvided int 84 if o.certFile == "" { 85 transportSecretsProvided = transportSecretsProvided + 1 86 } 87 if o.keyFile == "" { 88 transportSecretsProvided = transportSecretsProvided + 1 89 } 90 if o.caCertFile == "" { 91 transportSecretsProvided = transportSecretsProvided + 1 92 } 93 if transportSecretsProvided != 0 && transportSecretsProvided != 3 { 94 return errors.New("either --cert-file, --key-file, and --ca-cert-file must all be provided or none of them must be provided") 95 } 96 return nil 97 } 98 99 func gatherOptions() options { 100 o := options{config: configflagutil.ConfigOptions{ConfigPath: "/etc/config/config.yaml"}} 101 fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError) 102 fs.StringVar(&o.selector, "label-selector", labels.Everything().String(), "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.") 103 fs.StringVar(&o.totURL, "tot-url", "", "Tot URL") 104 105 fs.StringVar(&o.jenkinsURL, "jenkins-url", "http://jenkins-proxy", "Jenkins URL") 106 fs.StringVar(&o.jenkinsUserName, "jenkins-user", "jenkins-trigger", "Jenkins username") 107 fs.StringVar(&o.jenkinsTokenFile, "jenkins-token-file", "", "Path to the file containing the Jenkins API token.") 108 fs.StringVar(&o.jenkinsBearerTokenFile, "jenkins-bearer-token-file", "", "Path to the file containing the Jenkins API bearer token.") 109 fs.StringVar(&o.certFile, "cert-file", "", "Path to a PEM-encoded certificate file.") 110 fs.StringVar(&o.keyFile, "key-file", "", "Path to a PEM-encoded key file.") 111 fs.StringVar(&o.caCertFile, "ca-cert-file", "", "Path to a PEM-encoded CA certificate file.") 112 fs.BoolVar(&o.csrfProtect, "csrf-protect", false, "Request a CSRF protection token from Jenkins that will be used in all subsequent requests to Jenkins.") 113 114 fs.BoolVar(&o.skipReport, "skip-report", false, "Whether or not to ignore report with githubClient") 115 fs.BoolVar(&o.dryRun, "dry-run", true, "Whether or not to make mutating API calls to GitHub/Kubernetes/Jenkins.") 116 for _, group := range []flagutil.OptionGroup{&o.kubernetes, &o.github, &o.instrumentationOptions, &o.config} { 117 group.AddFlags(fs) 118 } 119 fs.Parse(os.Args[1:]) 120 return o 121 } 122 123 func main() { 124 logrusutil.ComponentInit() 125 126 o := gatherOptions() 127 if err := o.Validate(); err != nil { 128 logrus.Fatalf("Invalid options: %v", err) 129 } 130 131 defer interrupts.WaitForGracefulShutdown() 132 133 pprof.Instrument(o.instrumentationOptions) 134 135 if _, err := labels.Parse(o.selector); err != nil { 136 logrus.WithError(err).Fatal("Error parsing label selector.") 137 } 138 139 configAgent, err := o.config.ConfigAgent() 140 if err != nil { 141 logrus.WithError(err).Fatal("Error starting config agent.") 142 } 143 cfg := configAgent.Config 144 145 prowJobClient, err := o.kubernetes.ProwJobClient(configAgent.Config().ProwJobNamespace, o.dryRun) 146 if err != nil { 147 logrus.WithError(err).Fatal("Error getting ProwJob client for the infrastructure cluster.") 148 } 149 150 ac := &jenkins.AuthConfig{ 151 CSRFProtect: o.csrfProtect, 152 } 153 154 var tokens []string 155 tokens = append(tokens, o.github.TokenPath) 156 157 if o.jenkinsTokenFile != "" { 158 tokens = append(tokens, o.jenkinsTokenFile) 159 } 160 161 if o.jenkinsBearerTokenFile != "" { 162 tokens = append(tokens, o.jenkinsBearerTokenFile) 163 } 164 165 // Start the secret agent. 166 if err := secret.Add(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: secret.GetTokenGenerator(o.jenkinsTokenFile), 174 } 175 } else if o.jenkinsBearerTokenFile != "" { 176 ac.BearerToken = &jenkins.BearerTokenAuthConfig{ 177 GetToken: secret.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(o.dryRun) 195 if err != nil { 196 logrus.WithError(err).Fatal("Error getting GitHub client.") 197 } 198 199 c, err := jenkins.NewController(prowJobClient, jc, githubClient, nil, cfg, o.totURL, o.selector, o.skipReport) 200 if err != nil { 201 logrus.WithError(err).Fatal("Failed to instantiate Jenkins controller.") 202 } 203 204 // Expose prometheus metrics 205 m.ExposeMetrics("jenkins-operator", cfg().PushGateway, o.instrumentationOptions.MetricsPort) 206 207 // Serve Jenkins logs here and proxy deck to use this endpoint 208 // instead of baking agent-specific logic in deck 209 logMux := http.NewServeMux() 210 logMux.Handle("/", gziphandler.GzipHandler(handleLog(jc))) 211 server := &http.Server{Addr: ":8080", Handler: logMux} 212 interrupts.ListenAndServe(server, 5*time.Second) 213 214 // gather metrics for the jobs handled by the jenkins controller. 215 interrupts.TickLiteral(func() { 216 start := time.Now() 217 c.SyncMetrics() 218 logrus.WithField("metrics-duration", fmt.Sprintf("%v", time.Since(start))).Debug("Metrics synced") 219 }, 30*time.Second) 220 221 // run the controller 222 interrupts.TickLiteral(func() { 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 }, 30*time.Second) 231 } 232 233 func loadCerts(certFile, keyFile, caCertFile string) (*tls.Config, error) { 234 cert, err := tls.LoadX509KeyPair(certFile, keyFile) 235 if err != nil { 236 return nil, err 237 } 238 239 tlsConfig := &tls.Config{ 240 Certificates: []tls.Certificate{cert}, 241 } 242 243 if caCertFile != "" { 244 caCert, err := os.ReadFile(caCertFile) 245 if err != nil { 246 return nil, err 247 } 248 caCertPool := x509.NewCertPool() 249 caCertPool.AppendCertsFromPEM(caCert) 250 tlsConfig.RootCAs = caCertPool 251 } 252 253 return tlsConfig, nil 254 }