sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/cmd/prow-controller-manager/main.go (about)

     1  /*
     2  Copyright 2020 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  	"errors"
    22  	"flag"
    23  	"fmt"
    24  	"os"
    25  
    26  	"github.com/sirupsen/logrus"
    27  	uberzap "go.uber.org/zap"
    28  	"k8s.io/apimachinery/pkg/labels"
    29  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    30  	"k8s.io/apimachinery/pkg/util/sets"
    31  	ctrlruntimelog "sigs.k8s.io/controller-runtime/pkg/log"
    32  	"sigs.k8s.io/controller-runtime/pkg/log/zap"
    33  	"sigs.k8s.io/controller-runtime/pkg/manager"
    34  	"sigs.k8s.io/prow/pkg/pjutil"
    35  	"sigs.k8s.io/prow/pkg/pjutil/pprof"
    36  	"sigs.k8s.io/prow/pkg/scheduler"
    37  
    38  	"sigs.k8s.io/prow/pkg/flagutil"
    39  	prowflagutil "sigs.k8s.io/prow/pkg/flagutil"
    40  	configflagutil "sigs.k8s.io/prow/pkg/flagutil/config"
    41  	"sigs.k8s.io/prow/pkg/interrupts"
    42  	"sigs.k8s.io/prow/pkg/io"
    43  	"sigs.k8s.io/prow/pkg/logrusutil"
    44  	"sigs.k8s.io/prow/pkg/metrics"
    45  	"sigs.k8s.io/prow/pkg/plank"
    46  
    47  	_ "sigs.k8s.io/prow/pkg/version"
    48  )
    49  
    50  var allControllers = sets.New(plank.ControllerName, scheduler.ControllerName)
    51  
    52  type options struct {
    53  	totURL string
    54  
    55  	config             configflagutil.ConfigOptions
    56  	selector           string
    57  	enabledControllers prowflagutil.Strings
    58  
    59  	dryRun                 bool
    60  	kubernetes             prowflagutil.KubernetesOptions
    61  	github                 prowflagutil.GitHubOptions // TODO(fejta): remove
    62  	instrumentationOptions prowflagutil.InstrumentationOptions
    63  	storage                prowflagutil.StorageClientOptions
    64  }
    65  
    66  func gatherOptions(fs *flag.FlagSet, args ...string) options {
    67  	var o options
    68  	o.enabledControllers = prowflagutil.NewStrings(plank.ControllerName)
    69  	fs.StringVar(&o.totURL, "tot-url", "", "Tot URL")
    70  
    71  	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.")
    72  	fs.Var(&o.enabledControllers, "enable-controller", fmt.Sprintf("Controllers to enable. Can be passed multiple times. Defaults to controllers: %s", plank.ControllerName))
    73  
    74  	fs.BoolVar(&o.dryRun, "dry-run", true, "Whether or not to make mutating API calls to GitHub.")
    75  	for _, group := range []flagutil.OptionGroup{&o.kubernetes, &o.github, &o.instrumentationOptions, &o.config, &o.storage} {
    76  		group.AddFlags(fs)
    77  	}
    78  
    79  	fs.Parse(args)
    80  	return o
    81  }
    82  
    83  func (o *options) Validate() error {
    84  	o.github.AllowAnonymous = true
    85  
    86  	var errs []error
    87  	for _, group := range []flagutil.OptionGroup{&o.kubernetes, &o.github, &o.instrumentationOptions, &o.config, &o.storage} {
    88  		if err := group.Validate(o.dryRun); err != nil {
    89  			errs = append(errs, err)
    90  		}
    91  	}
    92  
    93  	for _, enabledController := range o.enabledControllers.Strings() {
    94  		if !allControllers.Has(enabledController) {
    95  			errs = append(errs, fmt.Errorf("unknown controller %s was configured via --enabled-controller", enabledController))
    96  		}
    97  	}
    98  
    99  	if n := len(allControllers.Intersection(sets.New(o.enabledControllers.Strings()...))); n == 0 {
   100  		errs = append(errs, errors.New("no controllers configured"))
   101  	}
   102  
   103  	if _, err := labels.Parse(o.selector); err != nil {
   104  		errs = append(errs, fmt.Errorf("parse label selector: %w", err))
   105  	}
   106  
   107  	return utilerrors.NewAggregate(errs)
   108  }
   109  
   110  func main() {
   111  	logrusutil.ComponentInit()
   112  
   113  	o := gatherOptions(flag.NewFlagSet(os.Args[0], flag.ExitOnError), os.Args[1:]...)
   114  	if err := o.Validate(); err != nil {
   115  		logrus.WithError(err).Fatal("Invalid options")
   116  	}
   117  
   118  	defer interrupts.WaitForGracefulShutdown()
   119  
   120  	health := pjutil.NewHealthOnPort(o.instrumentationOptions.HealthPort) // Start liveness endpoint
   121  	pprof.Instrument(o.instrumentationOptions)
   122  
   123  	configAgent, err := o.config.ConfigAgent()
   124  	if err != nil {
   125  		logrus.WithError(err).Fatal("Error starting config agent.")
   126  	}
   127  	cfg := configAgent.Config
   128  	o.kubernetes.SetDisabledClusters(sets.New(cfg().DisabledClusters...))
   129  
   130  	var logOpts []zap.Opts
   131  	if cfg().LogLevel == "debug" {
   132  		logOpts = append(logOpts, func(o *zap.Options) {
   133  			lvl := uberzap.NewAtomicLevelAt(uberzap.DebugLevel)
   134  			o.Level = &lvl
   135  		})
   136  	}
   137  	ctrlruntimelog.SetLogger(zap.New(logOpts...))
   138  
   139  	infrastructureClusterConfig, err := o.kubernetes.InfrastructureClusterConfig(o.dryRun)
   140  	if err != nil {
   141  		logrus.WithError(err).Fatal("Error getting infrastructure cluster config.")
   142  	}
   143  	opts := manager.Options{
   144  		MetricsBindAddress:      "0",
   145  		Namespace:               cfg().ProwJobNamespace,
   146  		LeaderElection:          true,
   147  		LeaderElectionNamespace: cfg().ProwJobNamespace,
   148  		LeaderElectionID:        "prow-controller-manager-leader-lock",
   149  	}
   150  	mgr, err := manager.New(infrastructureClusterConfig, opts)
   151  	if err != nil {
   152  		logrus.WithError(err).Fatal("Error creating manager")
   153  	}
   154  
   155  	// The watch apimachinery doesn't support restarts, so just exit the
   156  	// binary if a build cluster can be connected later.
   157  	callBack := func() {
   158  		logrus.Info("Build cluster that failed to connect initially now worked, exiting to trigger a restart.")
   159  		interrupts.Terminate()
   160  	}
   161  
   162  	buildClusterManagers, err := o.kubernetes.BuildClusterManagers(o.dryRun,
   163  		plank.RequiredTestPodVerbs(),
   164  		callBack,
   165  		func(o *manager.Options) {
   166  			o.Namespace = cfg().PodNamespace
   167  		},
   168  	)
   169  	if err != nil {
   170  		logrus.WithError(err).Error("Failed to construct build cluster managers. Please check that the kubeconfig secrets are correct, and that RBAC roles on the build cluster allow Prow's service account to list pods on it.")
   171  	}
   172  
   173  	for buildClusterName, buildClusterManager := range buildClusterManagers {
   174  		if err := mgr.Add(buildClusterManager); err != nil {
   175  			logrus.WithError(err).WithFields(logrus.Fields{
   176  				"cluster": buildClusterName,
   177  			}).Fatalf("Failed to add build cluster manager to main manager")
   178  		}
   179  	}
   180  
   181  	opener, err := io.NewOpener(context.Background(), o.storage.GCSCredentialsFile, o.storage.S3CredentialsFile)
   182  	if err != nil {
   183  		logrus.WithError(err).Fatal("Error creating opener")
   184  	}
   185  
   186  	// The watch apimachinery doesn't support restarts, so just exit the binary if a kubeconfig changes
   187  	// to make the kubelet restart us.
   188  	if err := o.kubernetes.AddKubeconfigChangeCallback(func() {
   189  		logrus.Info("Kubeconfig changed, exiting to trigger a restart")
   190  		interrupts.Terminate()
   191  	}); err != nil {
   192  		logrus.WithError(err).Fatal("Failed to register kubeconfig change callback")
   193  	}
   194  
   195  	enabledControllersSet := sets.New(o.enabledControllers.Strings()...)
   196  	knownClusters, err := o.kubernetes.KnownClusters(o.dryRun)
   197  	if err != nil {
   198  		logrus.WithError(err).Fatal("Failed to resolve known clusters in kubeconfig.")
   199  	}
   200  
   201  	if enabledControllersSet.Has(plank.ControllerName) {
   202  		if err := plank.Add(mgr, buildClusterManagers, knownClusters, cfg, opener, o.totURL, o.selector); err != nil {
   203  			logrus.WithError(err).Fatal("Failed to add plank to manager")
   204  		}
   205  	}
   206  
   207  	if enabledControllersSet.Has(scheduler.ControllerName) {
   208  		if err := scheduler.Add(mgr, cfg, 1); err != nil {
   209  			logrus.WithError(err).Fatal("Failed to add scheduler to manager")
   210  		}
   211  	}
   212  
   213  	// Expose prometheus metrics
   214  	metrics.ExposeMetrics("plank", cfg().PushGateway, o.instrumentationOptions.MetricsPort)
   215  	// Serve readiness endpoint
   216  	health.ServeReady()
   217  
   218  	if err := mgr.Start(interrupts.Context()); err != nil {
   219  		logrus.WithError(err).Fatal("failed to start manager")
   220  	}
   221  
   222  	logrus.Info("Controller ended gracefully")
   223  }