1/* 2Copyright 2018 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package util 18 19import ( 20 "sync" 21 22 "github.com/spf13/pflag" 23 24 "k8s.io/apimachinery/pkg/api/meta" 25 "k8s.io/apimachinery/pkg/runtime/schema" 26 "k8s.io/client-go/discovery" 27 "k8s.io/client-go/rest" 28 "k8s.io/client-go/tools/clientcmd" 29 "k8s.io/kubectl/pkg/scheme" 30 31 "k8s.io/cli-runtime/pkg/genericclioptions" 32 "k8s.io/component-base/version" 33) 34 35const ( 36 flagMatchBinaryVersion = "match-server-version" 37) 38 39// MatchVersionFlags is for setting the "match server version" function. 40type MatchVersionFlags struct { 41 Delegate genericclioptions.RESTClientGetter 42 43 RequireMatchedServerVersion bool 44 checkServerVersion sync.Once 45 matchesServerVersionErr error 46} 47 48var _ genericclioptions.RESTClientGetter = &MatchVersionFlags{} 49 50func (f *MatchVersionFlags) checkMatchingServerVersion() error { 51 f.checkServerVersion.Do(func() { 52 if !f.RequireMatchedServerVersion { 53 return 54 } 55 discoveryClient, err := f.Delegate.ToDiscoveryClient() 56 if err != nil { 57 f.matchesServerVersionErr = err 58 return 59 } 60 f.matchesServerVersionErr = discovery.MatchesServerVersion(version.Get(), discoveryClient) 61 }) 62 63 return f.matchesServerVersionErr 64} 65 66// ToRESTConfig implements RESTClientGetter. 67// Returns a REST client configuration based on a provided path 68// to a .kubeconfig file, loading rules, and config flag overrides. 69// Expects the AddFlags method to have been called. 70func (f *MatchVersionFlags) ToRESTConfig() (*rest.Config, error) { 71 if err := f.checkMatchingServerVersion(); err != nil { 72 return nil, err 73 } 74 clientConfig, err := f.Delegate.ToRESTConfig() 75 if err != nil { 76 return nil, err 77 } 78 // TODO we should not have to do this. It smacks of something going wrong. 79 setKubernetesDefaults(clientConfig) 80 return clientConfig, nil 81} 82 83func (f *MatchVersionFlags) ToRawKubeConfigLoader() clientcmd.ClientConfig { 84 return f.Delegate.ToRawKubeConfigLoader() 85} 86 87func (f *MatchVersionFlags) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { 88 if err := f.checkMatchingServerVersion(); err != nil { 89 return nil, err 90 } 91 return f.Delegate.ToDiscoveryClient() 92} 93 94// ToRESTMapper returns a mapper. 95func (f *MatchVersionFlags) ToRESTMapper() (meta.RESTMapper, error) { 96 if err := f.checkMatchingServerVersion(); err != nil { 97 return nil, err 98 } 99 return f.Delegate.ToRESTMapper() 100} 101 102func (f *MatchVersionFlags) AddFlags(flags *pflag.FlagSet) { 103 flags.BoolVar(&f.RequireMatchedServerVersion, flagMatchBinaryVersion, f.RequireMatchedServerVersion, "Require server version to match client version") 104} 105 106func NewMatchVersionFlags(delegate genericclioptions.RESTClientGetter) *MatchVersionFlags { 107 return &MatchVersionFlags{ 108 Delegate: delegate, 109 } 110} 111 112// setKubernetesDefaults sets default values on the provided client config for accessing the 113// Kubernetes API or returns an error if any of the defaults are impossible or invalid. 114// TODO this isn't what we want. Each clientset should be setting defaults as it sees fit. 115func setKubernetesDefaults(config *rest.Config) error { 116 // TODO remove this hack. This is allowing the GetOptions to be serialized. 117 config.GroupVersion = &schema.GroupVersion{Group: "", Version: "v1"} 118 119 if config.APIPath == "" { 120 config.APIPath = "/api" 121 } 122 if config.NegotiatedSerializer == nil { 123 // This codec factory ensures the resources are not converted. Therefore, resources 124 // will not be round-tripped through internal versions. Defaulting does not happen 125 // on the client. 126 config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() 127 } 128 return rest.SetKubernetesDefaults(config) 129} 130