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

     1  /*
     2  Copyright 2018 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  	"os"
    24  
    25  	"github.com/sirupsen/logrus"
    26  	"k8s.io/apimachinery/pkg/util/sets"
    27  	"sigs.k8s.io/controller-runtime/pkg/manager"
    28  	"sigs.k8s.io/prow/pkg/io"
    29  	"sigs.k8s.io/prow/pkg/pjutil/pprof"
    30  	"sigs.k8s.io/prow/pkg/resultstore"
    31  
    32  	prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    33  	"sigs.k8s.io/prow/pkg/config"
    34  	"sigs.k8s.io/prow/pkg/config/secret"
    35  	"sigs.k8s.io/prow/pkg/crier"
    36  	gcsreporter "sigs.k8s.io/prow/pkg/crier/reporters/gcs"
    37  	k8sgcsreporter "sigs.k8s.io/prow/pkg/crier/reporters/gcs/kubernetes"
    38  	gerritreporter "sigs.k8s.io/prow/pkg/crier/reporters/gerrit"
    39  	githubreporter "sigs.k8s.io/prow/pkg/crier/reporters/github"
    40  	pubsubreporter "sigs.k8s.io/prow/pkg/crier/reporters/pubsub"
    41  	resultstorereporter "sigs.k8s.io/prow/pkg/crier/reporters/resultstore"
    42  	slackreporter "sigs.k8s.io/prow/pkg/crier/reporters/slack"
    43  	prowflagutil "sigs.k8s.io/prow/pkg/flagutil"
    44  	configflagutil "sigs.k8s.io/prow/pkg/flagutil/config"
    45  	"sigs.k8s.io/prow/pkg/interrupts"
    46  	"sigs.k8s.io/prow/pkg/logrusutil"
    47  	"sigs.k8s.io/prow/pkg/metrics"
    48  	slackclient "sigs.k8s.io/prow/pkg/slack"
    49  )
    50  
    51  type options struct {
    52  	client           prowflagutil.KubernetesOptions
    53  	cookiefilePath   string
    54  	github           prowflagutil.GitHubOptions
    55  	githubEnablement prowflagutil.GitHubEnablementOptions
    56  	gerrit           prowflagutil.GerritOptions
    57  
    58  	config configflagutil.ConfigOptions
    59  
    60  	gerritWorkers         int
    61  	pubsubWorkers         int
    62  	githubWorkers         int
    63  	slackWorkers          int
    64  	blobStorageWorkers    int
    65  	k8sBlobStorageWorkers int
    66  	resultStoreWorkers    int
    67  
    68  	slackTokenFile            string
    69  	additionalSlackTokenFiles slackclient.HostsFlag
    70  
    71  	storage prowflagutil.StorageClientOptions
    72  
    73  	instrumentationOptions prowflagutil.InstrumentationOptions
    74  
    75  	k8sReportFraction float64
    76  
    77  	dryrun      bool
    78  	reportAgent string
    79  
    80  	resultstoreArtifactsDirOnly bool
    81  }
    82  
    83  func (o *options) validate() error {
    84  	if o.gerritWorkers+o.pubsubWorkers+o.githubWorkers+o.slackWorkers+o.blobStorageWorkers+o.k8sBlobStorageWorkers+o.resultStoreWorkers <= 0 {
    85  		return errors.New("crier need to have at least one report worker to start")
    86  	}
    87  
    88  	if o.k8sReportFraction < 0 || o.k8sReportFraction > 1 {
    89  		return errors.New("--kubernetes-report-fraction must be a float between 0 and 1")
    90  	}
    91  
    92  	if o.gerritWorkers > 0 {
    93  		if o.cookiefilePath == "" {
    94  			logrus.Info("--cookiefile is not set, using anonymous authentication")
    95  		}
    96  		if err := o.gerrit.Validate(o.dryrun); err != nil {
    97  			return err
    98  		}
    99  	}
   100  
   101  	if o.githubWorkers > 0 {
   102  		if err := o.github.Validate(o.dryrun); err != nil {
   103  			return err
   104  		}
   105  	}
   106  
   107  	if o.slackWorkers > 0 {
   108  		if o.slackTokenFile == "" && len(o.additionalSlackTokenFiles) == 0 {
   109  			return errors.New("one of --slack-token-file or --additional-slack-token-files must be set")
   110  		}
   111  	}
   112  
   113  	for _, opt := range []interface{ Validate(bool) error }{&o.client, &o.githubEnablement, &o.config} {
   114  		if err := opt.Validate(o.dryrun); err != nil {
   115  			return err
   116  		}
   117  	}
   118  
   119  	return nil
   120  }
   121  
   122  func (o *options) parseArgs(fs *flag.FlagSet, args []string) error {
   123  	fs.StringVar(&o.cookiefilePath, "cookiefile", "", "Path to git http.cookiefile, leave empty for anonymous")
   124  	fs.IntVar(&o.gerritWorkers, "gerrit-workers", 0, "Number of gerrit report workers (0 means disabled)")
   125  	fs.IntVar(&o.pubsubWorkers, "pubsub-workers", 0, "Number of pubsub report workers (0 means disabled)")
   126  	fs.IntVar(&o.githubWorkers, "github-workers", 0, "Number of github report workers (0 means disabled)")
   127  	fs.IntVar(&o.slackWorkers, "slack-workers", 0, "Number of Slack report workers (0 means disabled)")
   128  	fs.Var(&o.additionalSlackTokenFiles, "additional-slack-token-files", "Map of additional slack token files. example: --additional-slack-token-files=foo=/etc/foo-slack-tokens/token, repeat flag for each host")
   129  	fs.IntVar(&o.blobStorageWorkers, "blob-storage-workers", 0, "Number of blob storage report workers (0 means disabled)")
   130  	fs.IntVar(&o.k8sBlobStorageWorkers, "kubernetes-blob-storage-workers", 0, "Number of Kubernetes-specific blob storage report workers (0 means disabled)")
   131  	fs.Float64Var(&o.k8sReportFraction, "kubernetes-report-fraction", 1.0, "Approximate portion of jobs to report pod information for, if kubernetes-blob-storage-workers are enabled (0 - > none, 1.0 -> all)")
   132  	fs.StringVar(&o.slackTokenFile, "slack-token-file", "", "Path to a Slack token file")
   133  	fs.StringVar(&o.reportAgent, "report-agent", "", "Only report specified agent - empty means report to all agents (effective for github and Slack only)")
   134  	fs.IntVar(&o.resultStoreWorkers, "resultstore-workers", 0, "Number of ResultStore report workers (0 means disabled)")
   135  	fs.BoolVar(&o.resultstoreArtifactsDirOnly, "resultstore-artifacts-dir-only", false, "Report the artifacts/ dir instead of subtree files (testing)")
   136  
   137  	// TODO(krzyzacy): implement dryrun for gerrit/pubsub
   138  	fs.BoolVar(&o.dryrun, "dry-run", false, "Run in dry-run mode, not doing actual report (effective for github and Slack only)")
   139  
   140  	o.config.AddFlags(fs)
   141  	o.github.AddFlags(fs)
   142  	o.gerrit.AddFlags(fs)
   143  	o.client.AddFlags(fs)
   144  	o.storage.AddFlags(fs)
   145  	o.instrumentationOptions.AddFlags(fs)
   146  	o.githubEnablement.AddFlags(fs)
   147  
   148  	fs.Parse(args)
   149  
   150  	return o.validate()
   151  }
   152  
   153  func parseOptions() options {
   154  	var o options
   155  
   156  	if err := o.parseArgs(flag.CommandLine, os.Args[1:]); err != nil {
   157  		logrus.WithError(err).Fatal("Invalid flag options")
   158  	}
   159  
   160  	return o
   161  }
   162  
   163  func main() {
   164  	logrusutil.ComponentInit()
   165  
   166  	o := parseOptions()
   167  
   168  	pprof.Instrument(o.instrumentationOptions)
   169  
   170  	configAgent, err := o.config.ConfigAgent()
   171  	if err != nil {
   172  		logrus.WithError(err).Fatal("Error starting config agent.")
   173  	}
   174  	cfg := configAgent.Config
   175  	o.client.SetDisabledClusters(sets.New[string](cfg().DisabledClusters...))
   176  
   177  	restCfg, err := o.client.InfrastructureClusterConfig(o.dryrun)
   178  	if err != nil {
   179  		logrus.WithError(err).Fatal("Failed to get kubeconfig")
   180  	}
   181  	mgr, err := manager.New(restCfg, manager.Options{
   182  		Namespace:          cfg().ProwJobNamespace,
   183  		MetricsBindAddress: "0",
   184  	})
   185  	if err != nil {
   186  		logrus.WithError(err).Fatal("failed to create manager")
   187  	}
   188  
   189  	// The watch apimachinery doesn't support restarts, so just exit the binary if a kubeconfig changes
   190  	// to make the kubelet restart us.
   191  	if err := o.client.AddKubeconfigChangeCallback(func() {
   192  		logrus.Info("Kubeconfig changed, exiting to trigger a restart")
   193  		interrupts.Terminate()
   194  	}); err != nil {
   195  		logrus.WithError(err).Fatal("Failed to register kubeconfig change callback")
   196  	}
   197  
   198  	var hasReporter bool
   199  	if o.slackWorkers > 0 {
   200  		if cfg().SlackReporterConfigs == nil {
   201  			logrus.Fatal("slackreporter is enabled but has no config")
   202  		}
   203  		slackConfig := func(refs *prowapi.Refs) config.SlackReporter {
   204  			return cfg().SlackReporterConfigs.GetSlackReporter(refs)
   205  		}
   206  		tokensMap := make(map[string]func() []byte)
   207  		if o.slackTokenFile != "" {
   208  			tokensMap[slackreporter.DefaultHostName] = secret.GetTokenGenerator(o.slackTokenFile)
   209  			if err := secret.Add(o.slackTokenFile); err != nil {
   210  				logrus.WithError(err).Fatal("could not read slack token")
   211  			}
   212  		}
   213  		hasReporter = true
   214  		for host, additionalTokenFile := range o.additionalSlackTokenFiles {
   215  			tokensMap[host] = secret.GetTokenGenerator(additionalTokenFile)
   216  			if err := secret.Add(additionalTokenFile); err != nil {
   217  				logrus.WithError(err).Fatal("could not read slack token")
   218  			}
   219  		}
   220  		slackReporter := slackreporter.New(slackConfig, o.dryrun, tokensMap)
   221  		if err := crier.New(mgr, slackReporter, o.slackWorkers, o.githubEnablement.EnablementChecker()); err != nil {
   222  			logrus.WithError(err).Fatal("failed to construct slack reporter controller")
   223  		}
   224  	}
   225  
   226  	if o.gerritWorkers > 0 {
   227  		orgRepoConfigGetter := func() *config.GerritOrgRepoConfigs {
   228  			return cfg().Gerrit.OrgReposConfig
   229  		}
   230  		gerritReporter, err := gerritreporter.NewReporter(orgRepoConfigGetter, o.cookiefilePath, mgr.GetClient(), o.gerrit.MaxQPS, o.gerrit.MaxBurst)
   231  		if err != nil {
   232  			logrus.WithError(err).Fatal("Error starting gerrit reporter")
   233  		}
   234  
   235  		hasReporter = true
   236  		if err := crier.New(mgr, gerritReporter, o.gerritWorkers, o.githubEnablement.EnablementChecker()); err != nil {
   237  			logrus.WithError(err).Fatal("failed to construct gerrit reporter controller")
   238  		}
   239  	}
   240  
   241  	if o.pubsubWorkers > 0 {
   242  		hasReporter = true
   243  		if err := crier.New(mgr, pubsubreporter.NewReporter(cfg), o.pubsubWorkers, o.githubEnablement.EnablementChecker()); err != nil {
   244  			logrus.WithError(err).Fatal("failed to construct pubsub reporter controller")
   245  		}
   246  	}
   247  
   248  	if o.githubWorkers > 0 {
   249  		if o.github.TokenPath != "" {
   250  			if err := secret.Add(o.github.TokenPath); err != nil {
   251  				logrus.WithError(err).Fatal("Error reading GitHub credentials")
   252  			}
   253  		}
   254  
   255  		githubClient, err := o.github.GitHubClient(o.dryrun)
   256  		if err != nil {
   257  			logrus.WithError(err).Fatal("Error getting GitHub client.")
   258  		}
   259  
   260  		hasReporter = true
   261  		githubReporter := githubreporter.NewReporter(githubClient, cfg, prowapi.ProwJobAgent(o.reportAgent), mgr.GetCache())
   262  		if err := crier.New(mgr, githubReporter, o.githubWorkers, o.githubEnablement.EnablementChecker()); err != nil {
   263  			logrus.WithError(err).Fatal("failed to construct github reporter controller")
   264  		}
   265  	}
   266  
   267  	var opener io.Opener
   268  	if o.blobStorageWorkers+o.k8sBlobStorageWorkers+o.resultStoreWorkers > 0 {
   269  		opener, err = o.storage.StorageClient(context.Background())
   270  		if err != nil {
   271  			logrus.WithError(err).Fatal("Error creating opener")
   272  		}
   273  	}
   274  
   275  	if o.blobStorageWorkers > 0 || o.k8sBlobStorageWorkers > 0 {
   276  		hasReporter = true
   277  		if o.blobStorageWorkers > 0 {
   278  			if err := crier.New(mgr, gcsreporter.New(cfg, opener, o.dryrun), o.blobStorageWorkers, o.githubEnablement.EnablementChecker()); err != nil {
   279  				logrus.WithError(err).Fatal("failed to construct gcsreporter controller")
   280  			}
   281  		}
   282  
   283  		if o.k8sBlobStorageWorkers > 0 {
   284  			coreClients, err := o.client.BuildClusterCoreV1Clients(o.dryrun)
   285  			if err != nil {
   286  				logrus.WithError(err).Fatal("Error building pod client sets for Kubernetes GCS workers")
   287  			}
   288  
   289  			k8sGcsReporter := k8sgcsreporter.New(cfg, opener, k8sgcsreporter.NewK8sResourceGetter(coreClients), float32(o.k8sReportFraction), o.dryrun)
   290  			if err := crier.New(mgr, k8sGcsReporter, o.k8sBlobStorageWorkers, o.githubEnablement.EnablementChecker()); err != nil {
   291  				logrus.WithError(err).Fatal("failed to construct k8sgcsreporter controller")
   292  			}
   293  		}
   294  	}
   295  
   296  	if o.resultStoreWorkers > 0 {
   297  		hasReporter = true
   298  		conn, err := resultstore.Connect(context.Background())
   299  		if err != nil {
   300  			logrus.WithError(err).Fatal("Error connecting to resultstore")
   301  		}
   302  		uploader := resultstore.NewUploader(resultstore.NewClient(conn))
   303  		if err := crier.New(mgr, resultstorereporter.New(cfg, opener, uploader, o.resultstoreArtifactsDirOnly), o.resultStoreWorkers, o.githubEnablement.EnablementChecker()); err != nil {
   304  			logrus.WithError(err).Fatal("failed to construct resultstorereporter controller")
   305  		}
   306  	}
   307  
   308  	if !hasReporter {
   309  		logrus.Fatalf("should have at least one controller to start crier.")
   310  	}
   311  
   312  	// Push metrics to the configured prometheus pushgateway endpoint or serve them
   313  	metrics.ExposeMetrics("crier", cfg().PushGateway, o.instrumentationOptions.MetricsPort)
   314  
   315  	interrupts.Run(func(ctx context.Context) {
   316  		if err := mgr.Start(ctx); err != nil {
   317  			logrus.WithError(err).Fatal("Controller manager exited with error.")
   318  		}
   319  	})
   320  	interrupts.WaitForGracefulShutdown()
   321  	logrus.Info("Ended gracefully")
   322  }