1 /* 2 Copyright 2017 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 config 18 19 import ( 20 "flag" 21 "fmt" 22 "os" 23 "os/user" 24 "path/filepath" 25 26 "k8s.io/client-go/rest" 27 "k8s.io/client-go/tools/clientcmd" 28 clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 29 logf "sigs.k8s.io/controller-runtime/pkg/internal/log" 30 ) 31 32 // KubeconfigFlagName is the name of the kubeconfig flag 33 const KubeconfigFlagName = "kubeconfig" 34 35 var ( 36 kubeconfig string 37 log = logf.RuntimeLog.WithName("client").WithName("config") 38 ) 39 40 // init registers the "kubeconfig" flag to the default command line FlagSet. 41 // TODO: This should be removed, as it potentially leads to redefined flag errors for users, if they already 42 // have registered the "kubeconfig" flag to the command line FlagSet in other parts of their code. 43 func init() { 44 RegisterFlags(flag.CommandLine) 45 } 46 47 // RegisterFlags registers flag variables to the given FlagSet if not already registered. 48 // It uses the default command line FlagSet, if none is provided. Currently, it only registers the kubeconfig flag. 49 func RegisterFlags(fs *flag.FlagSet) { 50 if fs == nil { 51 fs = flag.CommandLine 52 } 53 if f := fs.Lookup(KubeconfigFlagName); f != nil { 54 kubeconfig = f.Value.String() 55 } else { 56 fs.StringVar(&kubeconfig, KubeconfigFlagName, "", "Paths to a kubeconfig. Only required if out-of-cluster.") 57 } 58 } 59 60 // GetConfig creates a *rest.Config for talking to a Kubernetes API server. 61 // If --kubeconfig is set, will use the kubeconfig file at that location. Otherwise will assume running 62 // in cluster and use the cluster provided kubeconfig. 63 // 64 // It also applies saner defaults for QPS and burst based on the Kubernetes 65 // controller manager defaults (20 QPS, 30 burst) 66 // 67 // Config precedence: 68 // 69 // * --kubeconfig flag pointing at a file 70 // 71 // * KUBECONFIG environment variable pointing at a file 72 // 73 // * In-cluster config if running in cluster 74 // 75 // * $HOME/.kube/config if exists. 76 func GetConfig() (*rest.Config, error) { 77 return GetConfigWithContext("") 78 } 79 80 // GetConfigWithContext creates a *rest.Config for talking to a Kubernetes API server with a specific context. 81 // If --kubeconfig is set, will use the kubeconfig file at that location. Otherwise will assume running 82 // in cluster and use the cluster provided kubeconfig. 83 // 84 // It also applies saner defaults for QPS and burst based on the Kubernetes 85 // controller manager defaults (20 QPS, 30 burst) 86 // 87 // Config precedence: 88 // 89 // * --kubeconfig flag pointing at a file 90 // 91 // * KUBECONFIG environment variable pointing at a file 92 // 93 // * In-cluster config if running in cluster 94 // 95 // * $HOME/.kube/config if exists. 96 func GetConfigWithContext(context string) (*rest.Config, error) { 97 cfg, err := loadConfig(context) 98 if err != nil { 99 return nil, err 100 } 101 if cfg.QPS == 0.0 { 102 cfg.QPS = 20.0 103 } 104 if cfg.Burst == 0 { 105 cfg.Burst = 30 106 } 107 return cfg, nil 108 } 109 110 // loadInClusterConfig is a function used to load the in-cluster 111 // Kubernetes client config. This variable makes is possible to 112 // test the precedence of loading the config. 113 var loadInClusterConfig = rest.InClusterConfig 114 115 // loadConfig loads a REST Config as per the rules specified in GetConfig. 116 func loadConfig(context string) (config *rest.Config, configErr error) { 117 // If a flag is specified with the config location, use that 118 if len(kubeconfig) > 0 { 119 return loadConfigWithContext("", &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}, context) 120 } 121 122 // If the recommended kubeconfig env variable is not specified, 123 // try the in-cluster config. 124 kubeconfigPath := os.Getenv(clientcmd.RecommendedConfigPathEnvVar) 125 if len(kubeconfigPath) == 0 { 126 c, err := loadInClusterConfig() 127 if err == nil { 128 return c, nil 129 } 130 131 defer func() { 132 if configErr != nil { 133 log.Error(err, "unable to load in-cluster config") 134 } 135 }() 136 } 137 138 // If the recommended kubeconfig env variable is set, or there 139 // is no in-cluster config, try the default recommended locations. 140 // 141 // NOTE: For default config file locations, upstream only checks 142 // $HOME for the user's home directory, but we can also try 143 // os/user.HomeDir when $HOME is unset. 144 // 145 // TODO(jlanford): could this be done upstream? 146 loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() 147 if _, ok := os.LookupEnv("HOME"); !ok { 148 u, err := user.Current() 149 if err != nil { 150 return nil, fmt.Errorf("could not get current user: %w", err) 151 } 152 loadingRules.Precedence = append(loadingRules.Precedence, filepath.Join(u.HomeDir, clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName)) 153 } 154 155 return loadConfigWithContext("", loadingRules, context) 156 } 157 158 func loadConfigWithContext(apiServerURL string, loader clientcmd.ClientConfigLoader, context string) (*rest.Config, error) { 159 return clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 160 loader, 161 &clientcmd.ConfigOverrides{ 162 ClusterInfo: clientcmdapi.Cluster{ 163 Server: apiServerURL, 164 }, 165 CurrentContext: context, 166 }).ClientConfig() 167 } 168 169 // GetConfigOrDie creates a *rest.Config for talking to a Kubernetes apiserver. 170 // If --kubeconfig is set, will use the kubeconfig file at that location. Otherwise will assume running 171 // in cluster and use the cluster provided kubeconfig. 172 // 173 // Will log an error and exit if there is an error creating the rest.Config. 174 func GetConfigOrDie() *rest.Config { 175 config, err := GetConfig() 176 if err != nil { 177 log.Error(err, "unable to get kubeconfig") 178 os.Exit(1) 179 } 180 return config 181 } 182