1
16
17 package genericclioptions
18
19 import (
20 "os"
21 "path/filepath"
22 "regexp"
23 "strings"
24 "sync"
25 "time"
26
27 "github.com/spf13/pflag"
28
29 "k8s.io/apimachinery/pkg/api/meta"
30 "k8s.io/cli-runtime/pkg/genericiooptions"
31 "k8s.io/cli-runtime/pkg/printers"
32 "k8s.io/client-go/discovery"
33 diskcached "k8s.io/client-go/discovery/cached/disk"
34 "k8s.io/client-go/rest"
35 "k8s.io/client-go/restmapper"
36 "k8s.io/client-go/tools/clientcmd"
37 "k8s.io/client-go/util/homedir"
38 utilpointer "k8s.io/utils/pointer"
39 )
40
41 const (
42 flagClusterName = "cluster"
43 flagAuthInfoName = "user"
44 flagContext = "context"
45 flagNamespace = "namespace"
46 flagAPIServer = "server"
47 flagTLSServerName = "tls-server-name"
48 flagInsecure = "insecure-skip-tls-verify"
49 flagCertFile = "client-certificate"
50 flagKeyFile = "client-key"
51 flagCAFile = "certificate-authority"
52 flagBearerToken = "token"
53 flagImpersonate = "as"
54 flagImpersonateUID = "as-uid"
55 flagImpersonateGroup = "as-group"
56 flagUsername = "username"
57 flagPassword = "password"
58 flagTimeout = "request-timeout"
59 flagCacheDir = "cache-dir"
60 flagDisableCompression = "disable-compression"
61 )
62
63
64
65
66 type RESTClientGetter interface {
67
68 ToRESTConfig() (*rest.Config, error)
69
70 ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error)
71
72 ToRESTMapper() (meta.RESTMapper, error)
73
74 ToRawKubeConfigLoader() clientcmd.ClientConfig
75 }
76
77 var _ RESTClientGetter = &ConfigFlags{}
78
79
80
81 type ConfigFlags struct {
82 CacheDir *string
83 KubeConfig *string
84
85
86 ClusterName *string
87 AuthInfoName *string
88 Context *string
89 Namespace *string
90 APIServer *string
91 TLSServerName *string
92 Insecure *bool
93 CertFile *string
94 KeyFile *string
95 CAFile *string
96 BearerToken *string
97 Impersonate *string
98 ImpersonateUID *string
99 ImpersonateGroup *[]string
100 Username *string
101 Password *string
102 Timeout *string
103 DisableCompression *bool
104
105
106 WrapConfigFn func(*rest.Config) *rest.Config
107
108 clientConfig clientcmd.ClientConfig
109 clientConfigLock sync.Mutex
110
111 restMapper meta.RESTMapper
112 restMapperLock sync.Mutex
113
114 discoveryClient discovery.CachedDiscoveryInterface
115 discoveryClientLock sync.Mutex
116
117
118
119
120 usePersistentConfig bool
121
122
123 discoveryBurst int
124
125
126 discoveryQPS float32
127
128
129 warningPrinter *printers.WarningPrinter
130 }
131
132
133
134
135
136
137 func (f *ConfigFlags) ToRESTConfig() (*rest.Config, error) {
138 c, err := f.ToRawKubeConfigLoader().ClientConfig()
139 if err != nil {
140 return nil, err
141 }
142 if f.WrapConfigFn != nil {
143 return f.WrapConfigFn(c), nil
144 }
145 return c, nil
146 }
147
148
149
150
151 func (f *ConfigFlags) ToRawKubeConfigLoader() clientcmd.ClientConfig {
152 if f.usePersistentConfig {
153 return f.toRawKubePersistentConfigLoader()
154 }
155 return f.toRawKubeConfigLoader()
156 }
157
158 func (f *ConfigFlags) toRawKubeConfigLoader() clientcmd.ClientConfig {
159 loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
160
161
162 loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
163
164 if f.KubeConfig != nil {
165 loadingRules.ExplicitPath = *f.KubeConfig
166 }
167
168 overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults}
169
170
171 if f.CertFile != nil {
172 overrides.AuthInfo.ClientCertificate = *f.CertFile
173 }
174 if f.KeyFile != nil {
175 overrides.AuthInfo.ClientKey = *f.KeyFile
176 }
177 if f.BearerToken != nil {
178 overrides.AuthInfo.Token = *f.BearerToken
179 }
180 if f.Impersonate != nil {
181 overrides.AuthInfo.Impersonate = *f.Impersonate
182 }
183 if f.ImpersonateUID != nil {
184 overrides.AuthInfo.ImpersonateUID = *f.ImpersonateUID
185 }
186 if f.ImpersonateGroup != nil {
187 overrides.AuthInfo.ImpersonateGroups = *f.ImpersonateGroup
188 }
189 if f.Username != nil {
190 overrides.AuthInfo.Username = *f.Username
191 }
192 if f.Password != nil {
193 overrides.AuthInfo.Password = *f.Password
194 }
195
196
197 if f.APIServer != nil {
198 overrides.ClusterInfo.Server = *f.APIServer
199 }
200 if f.TLSServerName != nil {
201 overrides.ClusterInfo.TLSServerName = *f.TLSServerName
202 }
203 if f.CAFile != nil {
204 overrides.ClusterInfo.CertificateAuthority = *f.CAFile
205 }
206 if f.Insecure != nil {
207 overrides.ClusterInfo.InsecureSkipTLSVerify = *f.Insecure
208 }
209 if f.DisableCompression != nil {
210 overrides.ClusterInfo.DisableCompression = *f.DisableCompression
211 }
212
213
214 if f.Context != nil {
215 overrides.CurrentContext = *f.Context
216 }
217 if f.ClusterName != nil {
218 overrides.Context.Cluster = *f.ClusterName
219 }
220 if f.AuthInfoName != nil {
221 overrides.Context.AuthInfo = *f.AuthInfoName
222 }
223 if f.Namespace != nil {
224 overrides.Context.Namespace = *f.Namespace
225 }
226
227 if f.Timeout != nil {
228 overrides.Timeout = *f.Timeout
229 }
230
231
232 if f.Password == nil {
233 return &clientConfig{clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides)}
234 }
235 return &clientConfig{clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin)}
236 }
237
238
239
240 func (f *ConfigFlags) toRawKubePersistentConfigLoader() clientcmd.ClientConfig {
241 f.clientConfigLock.Lock()
242 defer f.clientConfigLock.Unlock()
243
244 if f.clientConfig == nil {
245 f.clientConfig = f.toRawKubeConfigLoader()
246 }
247
248 return f.clientConfig
249 }
250
251
252
253
254 func (f *ConfigFlags) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
255 if f.usePersistentConfig {
256 return f.toPersistentDiscoveryClient()
257 }
258 return f.toDiscoveryClient()
259 }
260
261 func (f *ConfigFlags) toPersistentDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
262 f.discoveryClientLock.Lock()
263 defer f.discoveryClientLock.Unlock()
264
265 if f.discoveryClient == nil {
266 discoveryClient, err := f.toDiscoveryClient()
267 if err != nil {
268 return nil, err
269 }
270 f.discoveryClient = discoveryClient
271 }
272 return f.discoveryClient, nil
273 }
274
275 func (f *ConfigFlags) toDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
276 config, err := f.ToRESTConfig()
277 if err != nil {
278 return nil, err
279 }
280
281 config.Burst = f.discoveryBurst
282 config.QPS = f.discoveryQPS
283
284 cacheDir := getDefaultCacheDir()
285
286
287
288
289
290 if f.CacheDir != nil && *f.CacheDir != "" && *f.CacheDir != getDefaultCacheDir() {
291 cacheDir = *f.CacheDir
292 }
293
294 httpCacheDir := filepath.Join(cacheDir, "http")
295 discoveryCacheDir := computeDiscoverCacheDir(filepath.Join(cacheDir, "discovery"), config.Host)
296
297 return diskcached.NewCachedDiscoveryClientForConfig(config, discoveryCacheDir, httpCacheDir, time.Duration(6*time.Hour))
298 }
299
300
301
302
303 func getDefaultCacheDir() string {
304 if kcd := os.Getenv("KUBECACHEDIR"); kcd != "" {
305 return kcd
306 }
307
308 return filepath.Join(homedir.HomeDir(), ".kube", "cache")
309 }
310
311
312 func (f *ConfigFlags) ToRESTMapper() (meta.RESTMapper, error) {
313 if f.usePersistentConfig {
314 return f.toPersistentRESTMapper()
315 }
316 return f.toRESTMapper()
317 }
318
319 func (f *ConfigFlags) toPersistentRESTMapper() (meta.RESTMapper, error) {
320 f.restMapperLock.Lock()
321 defer f.restMapperLock.Unlock()
322
323 if f.restMapper == nil {
324 restMapper, err := f.toRESTMapper()
325 if err != nil {
326 return nil, err
327 }
328 f.restMapper = restMapper
329 }
330 return f.restMapper, nil
331 }
332
333 func (f *ConfigFlags) toRESTMapper() (meta.RESTMapper, error) {
334 discoveryClient, err := f.ToDiscoveryClient()
335 if err != nil {
336 return nil, err
337 }
338
339 mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
340 expander := restmapper.NewShortcutExpander(mapper, discoveryClient, func(a string) {
341 if f.warningPrinter != nil {
342 f.warningPrinter.Print(a)
343 }
344 })
345 return expander, nil
346 }
347
348
349 func (f *ConfigFlags) AddFlags(flags *pflag.FlagSet) {
350 if f.KubeConfig != nil {
351 flags.StringVar(f.KubeConfig, "kubeconfig", *f.KubeConfig, "Path to the kubeconfig file to use for CLI requests.")
352 }
353 if f.CacheDir != nil {
354 flags.StringVar(f.CacheDir, flagCacheDir, *f.CacheDir, "Default cache directory")
355 }
356
357
358 if f.CertFile != nil {
359 flags.StringVar(f.CertFile, flagCertFile, *f.CertFile, "Path to a client certificate file for TLS")
360 }
361 if f.KeyFile != nil {
362 flags.StringVar(f.KeyFile, flagKeyFile, *f.KeyFile, "Path to a client key file for TLS")
363 }
364 if f.BearerToken != nil {
365 flags.StringVar(f.BearerToken, flagBearerToken, *f.BearerToken, "Bearer token for authentication to the API server")
366 }
367 if f.Impersonate != nil {
368 flags.StringVar(f.Impersonate, flagImpersonate, *f.Impersonate, "Username to impersonate for the operation. User could be a regular user or a service account in a namespace.")
369 }
370 if f.ImpersonateUID != nil {
371 flags.StringVar(f.ImpersonateUID, flagImpersonateUID, *f.ImpersonateUID, "UID to impersonate for the operation.")
372 }
373 if f.ImpersonateGroup != nil {
374 flags.StringArrayVar(f.ImpersonateGroup, flagImpersonateGroup, *f.ImpersonateGroup, "Group to impersonate for the operation, this flag can be repeated to specify multiple groups.")
375 }
376 if f.Username != nil {
377 flags.StringVar(f.Username, flagUsername, *f.Username, "Username for basic authentication to the API server")
378 }
379 if f.Password != nil {
380 flags.StringVar(f.Password, flagPassword, *f.Password, "Password for basic authentication to the API server")
381 }
382 if f.ClusterName != nil {
383 flags.StringVar(f.ClusterName, flagClusterName, *f.ClusterName, "The name of the kubeconfig cluster to use")
384 }
385 if f.AuthInfoName != nil {
386 flags.StringVar(f.AuthInfoName, flagAuthInfoName, *f.AuthInfoName, "The name of the kubeconfig user to use")
387 }
388 if f.Namespace != nil {
389 flags.StringVarP(f.Namespace, flagNamespace, "n", *f.Namespace, "If present, the namespace scope for this CLI request")
390 }
391 if f.Context != nil {
392 flags.StringVar(f.Context, flagContext, *f.Context, "The name of the kubeconfig context to use")
393 }
394
395 if f.APIServer != nil {
396 flags.StringVarP(f.APIServer, flagAPIServer, "s", *f.APIServer, "The address and port of the Kubernetes API server")
397 }
398 if f.TLSServerName != nil {
399 flags.StringVar(f.TLSServerName, flagTLSServerName, *f.TLSServerName, "Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used")
400 }
401 if f.Insecure != nil {
402 flags.BoolVar(f.Insecure, flagInsecure, *f.Insecure, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure")
403 }
404 if f.CAFile != nil {
405 flags.StringVar(f.CAFile, flagCAFile, *f.CAFile, "Path to a cert file for the certificate authority")
406 }
407 if f.Timeout != nil {
408 flags.StringVar(f.Timeout, flagTimeout, *f.Timeout, "The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests.")
409 }
410 if f.DisableCompression != nil {
411 flags.BoolVar(f.DisableCompression, flagDisableCompression, *f.DisableCompression, "If true, opt-out of response compression for all requests to the server")
412 }
413 }
414
415
416 func (f *ConfigFlags) WithDeprecatedPasswordFlag() *ConfigFlags {
417 f.Username = utilpointer.String("")
418 f.Password = utilpointer.String("")
419 return f
420 }
421
422
423 func (f *ConfigFlags) WithDiscoveryBurst(discoveryBurst int) *ConfigFlags {
424 f.discoveryBurst = discoveryBurst
425 return f
426 }
427
428
429 func (f *ConfigFlags) WithDiscoveryQPS(discoveryQPS float32) *ConfigFlags {
430 f.discoveryQPS = discoveryQPS
431 return f
432 }
433
434
435 func (f *ConfigFlags) WithWrapConfigFn(wrapConfigFn func(*rest.Config) *rest.Config) *ConfigFlags {
436 f.WrapConfigFn = wrapConfigFn
437 return f
438 }
439
440
441 func (f *ConfigFlags) WithWarningPrinter(ioStreams genericiooptions.IOStreams) *ConfigFlags {
442 f.warningPrinter = printers.NewWarningPrinter(ioStreams.ErrOut, printers.WarningPrinterOptions{Color: printers.AllowsColorOutput(ioStreams.ErrOut)})
443 return f
444 }
445
446
447 func NewConfigFlags(usePersistentConfig bool) *ConfigFlags {
448 impersonateGroup := []string{}
449 insecure := false
450 disableCompression := false
451
452 return &ConfigFlags{
453 Insecure: &insecure,
454 Timeout: utilpointer.String("0"),
455 KubeConfig: utilpointer.String(""),
456
457 CacheDir: utilpointer.String(getDefaultCacheDir()),
458 ClusterName: utilpointer.String(""),
459 AuthInfoName: utilpointer.String(""),
460 Context: utilpointer.String(""),
461 Namespace: utilpointer.String(""),
462 APIServer: utilpointer.String(""),
463 TLSServerName: utilpointer.String(""),
464 CertFile: utilpointer.String(""),
465 KeyFile: utilpointer.String(""),
466 CAFile: utilpointer.String(""),
467 BearerToken: utilpointer.String(""),
468 Impersonate: utilpointer.String(""),
469 ImpersonateUID: utilpointer.String(""),
470 ImpersonateGroup: &impersonateGroup,
471 DisableCompression: &disableCompression,
472
473 usePersistentConfig: usePersistentConfig,
474
475
476
477 discoveryBurst: 300,
478 }
479 }
480
481
482 var overlyCautiousIllegalFileCharacters = regexp.MustCompile(`[^(\w/.)]`)
483
484
485 func computeDiscoverCacheDir(parentDir, host string) string {
486
487 schemelessHost := strings.Replace(strings.Replace(host, "https://", "", 1), "http://", "", 1)
488
489 safeHost := overlyCautiousIllegalFileCharacters.ReplaceAllString(schemelessHost, "_")
490 return filepath.Join(parentDir, safeHost)
491 }
492
View as plain text