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 }