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  }