github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/cmd/config-bootstrapper/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  	"errors"
    21  	"flag"
    22  	"os"
    23  	"path/filepath"
    24  
    25  	"github.com/sirupsen/logrus"
    26  	"k8s.io/client-go/kubernetes"
    27  	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    28  	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // support gcp users in .kube/config
    29  
    30  	"sigs.k8s.io/prow/pkg/config"
    31  	prowflagutil "sigs.k8s.io/prow/pkg/flagutil"
    32  	configflagutil "sigs.k8s.io/prow/pkg/flagutil/config"
    33  	pluginsflagutil "sigs.k8s.io/prow/pkg/flagutil/plugins"
    34  	"sigs.k8s.io/prow/pkg/github"
    35  	_ "sigs.k8s.io/prow/pkg/hook/plugin-imports"
    36  	"sigs.k8s.io/prow/pkg/logrusutil"
    37  	"sigs.k8s.io/prow/pkg/plugins"
    38  	"sigs.k8s.io/prow/pkg/plugins/updateconfig"
    39  )
    40  
    41  const bootstrapMode = true
    42  
    43  type options struct {
    44  	sourcePaths prowflagutil.Strings
    45  
    46  	config        configflagutil.ConfigOptions
    47  	pluginsConfig pluginsflagutil.PluginOptions
    48  
    49  	dryRun     bool
    50  	kubernetes prowflagutil.KubernetesOptions
    51  }
    52  
    53  func gatherOptions() options {
    54  	o := options{config: configflagutil.ConfigOptions{ConfigPath: "/etc/config/config.yaml"}}
    55  	fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
    56  
    57  	fs.Var(&o.sourcePaths, "source-path", "Path to root of source directory to use for config updates. Can be set multiple times.")
    58  
    59  	fs.BoolVar(&o.dryRun, "dry-run", true, "Whether or not to make mutating API calls to GitHub.")
    60  	o.config.AddFlags(fs)
    61  	o.pluginsConfig.PluginConfigPathDefault = "/etc/plugins/plugins.yaml"
    62  	o.pluginsConfig.AddFlags(fs)
    63  	o.kubernetes.AddFlags(fs)
    64  
    65  	fs.Parse(os.Args[1:])
    66  	return o
    67  }
    68  
    69  func (o *options) Validate() error {
    70  	if len(o.sourcePaths.Strings()) == 0 {
    71  		return errors.New("--source-path must be provided at least once")
    72  	}
    73  
    74  	for _, validate := range []interface{ Validate(bool) error }{&o.kubernetes, &o.config, &o.pluginsConfig} {
    75  		if err := validate.Validate(o.dryRun); err != nil {
    76  			return err
    77  		}
    78  	}
    79  
    80  	return nil
    81  }
    82  
    83  type osFileGetter struct {
    84  	roots []string
    85  }
    86  
    87  // GetFile returns the content of a file from disk, searching through all known roots.
    88  // We assume that no two roots will contain the same relative path inside of them, as such
    89  // a configuration would be racy and unsupported in the updateconfig plugin anyway.
    90  func (g *osFileGetter) GetFile(filename string) ([]byte, error) {
    91  	var loadErr error
    92  	for _, root := range g.roots {
    93  		candidatePath := filepath.Join(root, filename)
    94  		if _, err := os.Stat(candidatePath); err == nil {
    95  			// we found the file under this root
    96  			return os.ReadFile(candidatePath)
    97  		} else if !os.IsNotExist(err) {
    98  			// record this for later in case we can't find the file
    99  			loadErr = err
   100  		}
   101  	}
   102  	// file was found under no root
   103  	return nil, loadErr
   104  }
   105  
   106  func run(sourcePaths []string, defaultNamespace string, configUpdater plugins.ConfigUpdater, client kubernetes.Interface, buildClusterCoreV1Clients map[string]corev1.CoreV1Interface) int {
   107  	var errors int
   108  	// act like the whole repo just got committed
   109  	var changes []github.PullRequestChange
   110  	var version string
   111  
   112  	// Pretend that all files found in the sourcePaths are newly added
   113  	// (github.PullRequestFileAdded). This is because we are running against a
   114  	// static set of files and are not sure how these files came to be. So we
   115  	// pretend that these files are newly added.
   116  	for _, sourcePath := range sourcePaths {
   117  
   118  		versionFilePath := filepath.Join(sourcePath, config.ConfigVersionFileName)
   119  		if _, errAccess := os.Stat(versionFilePath); errAccess == nil {
   120  			content, err := os.ReadFile(versionFilePath)
   121  			if err != nil {
   122  				logrus.WithError(err).Warn("failed to read versionfile")
   123  			} else if version == "" {
   124  				version = string(content)
   125  			}
   126  		}
   127  
   128  		filepath.Walk(sourcePath, func(path string, info os.FileInfo, err error) error {
   129  			if info.IsDir() {
   130  				return nil
   131  			}
   132  			// we know path will be below sourcePaths, but we can't
   133  			// communicate that to the filepath module. We can ignore
   134  			// this error as we can be certain it won't occur
   135  			if relPath, err := filepath.Rel(sourcePath, path); err == nil {
   136  				changes = append(changes, github.PullRequestChange{
   137  					Filename: relPath,
   138  					Status:   github.PullRequestFileAdded,
   139  				})
   140  				logrus.Infof("added to mock change: %s", relPath)
   141  			} else {
   142  				logrus.WithError(err).Warn("unexpected error determining relative path to file")
   143  				errors++
   144  			}
   145  			return nil
   146  		})
   147  	}
   148  
   149  	for cm, data := range updateconfig.FilterChanges(configUpdater, changes, defaultNamespace, bootstrapMode, logrus.NewEntry(logrus.StandardLogger())) {
   150  		logger := logrus.WithFields(logrus.Fields{"configmap": map[string]string{"name": cm.Name, "namespace": cm.Namespace, "cluster": cm.Cluster}})
   151  		configMapClient, err := updateconfig.GetConfigMapClient(client.CoreV1(), cm.Namespace, buildClusterCoreV1Clients, cm.Cluster)
   152  		if err != nil {
   153  			errors++
   154  			logrus.WithError(err).Errorf("Failed to find configMap client")
   155  			continue
   156  		}
   157  		if err := updateconfig.Update(&osFileGetter{roots: sourcePaths}, configMapClient, cm.Name, cm.Namespace, data, bootstrapMode, nil, logger, version); err != nil {
   158  			logger.WithError(err).Error("failed to update config on cluster")
   159  			errors++
   160  		} else {
   161  			logger.Info("Successfully processed configmap")
   162  		}
   163  	}
   164  	return errors
   165  }
   166  
   167  func main() {
   168  	logrusutil.ComponentInit()
   169  
   170  	o := gatherOptions()
   171  	if err := o.Validate(); err != nil {
   172  		logrus.WithError(err).Fatal("Invalid options")
   173  	}
   174  
   175  	configAgent, err := o.config.ConfigAgent()
   176  	if err != nil {
   177  		logrus.WithError(err).Fatal("Error starting config agent.")
   178  	}
   179  
   180  	pluginAgent, err := o.pluginsConfig.PluginAgent()
   181  	if err != nil {
   182  		logrus.WithError(err).Fatal("Error starting plugin configuration agent.")
   183  	}
   184  
   185  	client, err := o.kubernetes.InfrastructureClusterClient(o.dryRun)
   186  	if err != nil {
   187  		logrus.WithError(err).Fatal("Error getting Kubernetes client.")
   188  	}
   189  
   190  	buildClusterCoreV1Clients, err := o.kubernetes.BuildClusterCoreV1Clients(o.dryRun)
   191  	if err != nil {
   192  		logrus.WithError(err).Fatal("Error getting Kubernetes clients for build cluster.")
   193  	}
   194  
   195  	if errors := run(o.sourcePaths.Strings(), configAgent.Config().ProwJobNamespace, pluginAgent.Config().ConfigUpdater, client, buildClusterCoreV1Clients); errors > 0 {
   196  		logrus.WithField("fail-count", errors).Fatalf("errors occurred during update")
   197  	}
   198  }