sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/kube/config.go (about) 1 /* 2 Copyright 2019 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 kube 18 19 import ( 20 "errors" 21 "fmt" 22 "os" 23 "path/filepath" 24 "strings" 25 26 "github.com/sirupsen/logrus" 27 "k8s.io/apimachinery/pkg/util/sets" 28 "k8s.io/client-go/rest" 29 "k8s.io/client-go/tools/clientcmd" 30 31 "sigs.k8s.io/prow/pkg/version" 32 ) 33 34 func kubeConfigs(loader clientcmd.ClientConfigLoader) (map[string]rest.Config, string, error) { 35 cfg, err := loader.Load() 36 if err != nil { 37 return nil, "", fmt.Errorf("failed to load: %w", err) 38 } 39 configs := map[string]rest.Config{} 40 for context := range cfg.Contexts { 41 contextCfg, err := clientcmd.NewNonInteractiveClientConfig(*cfg, context, &clientcmd.ConfigOverrides{}, loader).ClientConfig() 42 if err != nil { 43 return nil, "", fmt.Errorf("create %s client: %w", context, err) 44 } 45 contextCfg.UserAgent = version.UserAgent() 46 configs[context] = *contextCfg 47 logrus.Infof("Parsed kubeconfig context: %s", context) 48 } 49 return configs, cfg.CurrentContext, nil 50 } 51 52 func mergeConfigs(local *rest.Config, foreign map[string]rest.Config, currentContext string) (map[string]rest.Config, error) { 53 ret := map[string]rest.Config{} 54 for ctx, cfg := range foreign { 55 ret[ctx] = cfg 56 } 57 if local != nil { 58 ret[InClusterContext] = *local 59 } else if currentContext != "" { 60 ret[InClusterContext] = ret[currentContext] 61 } else { 62 return nil, errors.New("no prow cluster access: in-cluster current kubecfg context required") 63 } 64 if len(ret) == 0 { 65 return nil, errors.New("no client contexts found") 66 } 67 if _, ok := ret[DefaultClusterAlias]; !ok { 68 ret[DefaultClusterAlias] = ret[InClusterContext] 69 } 70 return ret, nil 71 } 72 73 // LoadClusterConfigs loads rest.Configs for creation of clients according to the given options. 74 // Errors are returned if a file/dir is specified in the options and invalid or if no valid contexts are found. 75 func LoadClusterConfigs(opts *Options) (map[string]rest.Config, error) { 76 77 logrus.Infof("Loading cluster contexts...") 78 // This will work if we are running inside kubernetes 79 localCfg, err := rest.InClusterConfig() 80 if err != nil { 81 logrus.WithError(err).Warn("Could not create in-cluster config (expected when running outside the cluster).") 82 } else { 83 localCfg.UserAgent = version.UserAgent() 84 } 85 if localCfg != nil && opts.projectedTokenFile != "" { 86 localCfg.BearerToken = "" 87 localCfg.BearerTokenFile = opts.projectedTokenFile 88 logrus.WithField("tokenfile", opts.projectedTokenFile).Info("Using projected token file") 89 } 90 91 var candidates []string 92 if opts.file != "" { 93 candidates = append(candidates, opts.file) 94 } 95 if opts.dir != "" { 96 files, err := os.ReadDir(opts.dir) 97 if err != nil { 98 return nil, fmt.Errorf("kubecfg dir: %w", err) 99 } 100 for _, file := range files { 101 filename := file.Name() 102 if file.IsDir() { 103 logrus.WithField("dir", filename).Info("Ignored directory") 104 continue 105 } 106 if strings.HasPrefix(filename, "..") { 107 logrus.WithField("filename", filename).Info("Ignored file starting with double dots") 108 continue 109 } 110 if !strings.HasSuffix(filename, opts.suffix) { 111 logrus.WithField("filename", filename).WithField("suffix", opts.suffix).Info("Ignored file without suffix") 112 continue 113 } 114 candidates = append(candidates, filepath.Join(opts.dir, filename)) 115 } 116 } 117 118 allKubeCfgs := map[string]rest.Config{} 119 var currentContext string 120 if len(candidates) == 0 { 121 // loading from the defaults, e.g., ${KUBECONFIG} 122 if allKubeCfgs, currentContext, err = kubeConfigs(clientcmd.NewDefaultClientConfigLoadingRules()); err != nil { 123 logrus.WithError(err).Warn("Cannot load kubecfg") 124 } 125 } else { 126 for _, candidate := range candidates { 127 logrus.Infof("Loading kubeconfig from: %q", candidate) 128 kubeCfgs, tempCurrentContext, err := kubeConfigs(&clientcmd.ClientConfigLoadingRules{ExplicitPath: candidate}) 129 if err != nil { 130 return nil, fmt.Errorf("fail to load kubecfg from %q: %w", candidate, err) 131 } 132 if !opts.disabledClusters.Has(tempCurrentContext) { 133 currentContext = tempCurrentContext 134 } 135 for c, k := range kubeCfgs { 136 if _, ok := allKubeCfgs[c]; ok { 137 return nil, fmt.Errorf("context %s occurred more than once in kubeconfig dir %q", c, opts.dir) 138 } 139 allKubeCfgs[c] = k 140 } 141 } 142 } 143 144 for _, disabledCluster := range opts.disabledClusters.UnsortedList() { 145 delete(allKubeCfgs, disabledCluster) 146 logrus.WithField("disabledCluster", disabledCluster).Info("Removed kubeconfig for disabled cluster") 147 } 148 149 if opts.noInClusterConfig { 150 return allKubeCfgs, nil 151 } 152 return mergeConfigs(localCfg, allKubeCfgs, currentContext) 153 } 154 155 // Options defines how to load kubeconfigs files 156 type Options struct { 157 file string 158 dir string 159 suffix string 160 projectedTokenFile string 161 noInClusterConfig bool 162 disabledClusters sets.Set[string] 163 } 164 165 type ConfigOptions func(*Options) 166 167 // ConfigDir configures the directory containing kubeconfig files 168 func ConfigDir(dir string) ConfigOptions { 169 return func(kc *Options) { 170 kc.dir = dir 171 } 172 } 173 174 // DisabledClusters configures the set of disabled build cluster names. 175 // They will be ignored as context names while loading kubeconfig files. 176 func DisabledClusters(disabledClusters sets.Set[string]) ConfigOptions { 177 return func(kc *Options) { 178 kc.disabledClusters = disabledClusters 179 } 180 } 181 182 // ConfigSuffix configures the suffix of the file in directory containing kubeconfig files 183 func ConfigSuffix(suffix string) ConfigOptions { 184 return func(kc *Options) { 185 kc.suffix = suffix 186 } 187 } 188 189 // ConfigFile configures the path to a kubeconfig file 190 func ConfigFile(file string) ConfigOptions { 191 return func(kc *Options) { 192 kc.file = file 193 } 194 } 195 196 // ConfigProjectedTokenFile configures the path to a projectedToken file 197 func ConfigProjectedTokenFile(projectedTokenFile string) ConfigOptions { 198 return func(kc *Options) { 199 kc.projectedTokenFile = projectedTokenFile 200 } 201 } 202 203 // NoInClusterConfig indicates that there is no InCluster Config to load 204 func NoInClusterConfig(noInClusterConfig bool) ConfigOptions { 205 return func(kc *Options) { 206 kc.noInClusterConfig = noInClusterConfig 207 } 208 } 209 210 // NewConfig builds Options according to the given ConfigOptions 211 func NewConfig(opts ...ConfigOptions) *Options { 212 kc := &Options{} 213 for _, opt := range opts { 214 opt(kc) 215 } 216 return kc 217 }