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