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  }