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 }