1 package kclient
2
3 import (
4 "errors"
5 "fmt"
6 "os"
7 "strings"
8
9 "github.com/redhat-developer/odo/pkg/log"
10 "k8s.io/kubectl/pkg/util/term"
11
12 "k8s.io/cli-runtime/pkg/genericclioptions"
13
14 "github.com/blang/semver"
15 "github.com/redhat-developer/odo/pkg/platform"
16
17 kerrors "k8s.io/apimachinery/pkg/api/errors"
18 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19 "k8s.io/apimachinery/pkg/runtime"
20 "k8s.io/client-go/discovery"
21 "k8s.io/client-go/discovery/cached/memory"
22 "k8s.io/client-go/dynamic"
23 "k8s.io/client-go/dynamic/fake"
24 "k8s.io/client-go/kubernetes"
25 "k8s.io/client-go/rest"
26 "k8s.io/client-go/restmapper"
27 "k8s.io/client-go/tools/clientcmd"
28 "k8s.io/klog"
29
30
31 servicecatalogclienset "github.com/kubernetes-sigs/service-catalog/pkg/client/clientset_generated/clientset/typed/servicecatalog/v1beta1"
32 configclientset "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
33 projectclientset "github.com/openshift/client-go/project/clientset/versioned/typed/project/v1"
34 routeclientset "github.com/openshift/client-go/route/clientset/versioned/typed/route/v1"
35 userclientset "github.com/openshift/client-go/user/clientset/versioned/typed/user/v1"
36 operatorsclientset "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/typed/operators/v1alpha1"
37 appsclientset "k8s.io/client-go/kubernetes/typed/apps/v1"
38
39 _ "k8s.io/client-go/plugin/pkg/client/auth"
40 )
41
42 const (
43
44 errorMsg = `
45 Please ensure you have an active kubernetes context to your cluster.
46 Consult your Kubernetes distribution's documentation for more details.
47 Error: %w
48 `
49 defaultQPS = 200
50 defaultBurst = 200
51 )
52
53
54 type Client struct {
55 KubeClient kubernetes.Interface
56 KubeConfig clientcmd.ClientConfig
57 KubeClientConfig *rest.Config
58 Namespace string
59 OperatorClient *operatorsclientset.OperatorsV1alpha1Client
60 appsClient appsclientset.AppsV1Interface
61 serviceCatalogClient servicecatalogclienset.ServicecatalogV1beta1Interface
62
63
64
65 DynamicClient dynamic.Interface
66 discoveryClient discovery.DiscoveryInterface
67 cachedDiscoveryClient discovery.CachedDiscoveryInterface
68 restmapper *restmapper.DeferredDiscoveryRESTMapper
69
70 supportedResources map[string]bool
71
72
73 isSSASupported *bool
74
75
76 checkIngressSupports bool
77 isNetworkingV1IngressSupported bool
78 isExtensionV1Beta1IngressSupported bool
79
80
81 userClient userclientset.UserV1Interface
82 projectClient projectclientset.ProjectV1Interface
83 routeClient routeclientset.RouteV1Interface
84 configClient *configclientset.ConfigV1Client
85 }
86
87 var _ ClientInterface = (*Client)(nil)
88 var _ platform.Client = (*Client)(nil)
89
90
91 func New() (*Client, error) {
92
93 if os.Getenv("KUBECONFIG") == "/dev/null" {
94 return nil, errors.New("access to Kubernetes cluster is disabled by KUBECONFIG=/dev/null")
95 }
96 return NewForConfig(nil)
97 }
98
99 func (c *Client) GetClient() kubernetes.Interface {
100 return c.KubeClient
101 }
102
103 func (c *Client) GetConfig() clientcmd.ClientConfig {
104 return c.KubeConfig
105 }
106
107 func (c *Client) GetClientConfig() *rest.Config {
108 return c.KubeClientConfig
109 }
110
111 func (c *Client) GetDynamicClient() dynamic.Interface {
112 return c.DynamicClient
113 }
114
115
116 func NewForConfig(config clientcmd.ClientConfig) (client *Client, err error) {
117 if config == nil {
118
119 loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
120 configOverrides := &clientcmd.ConfigOverrides{}
121 config = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
122 }
123
124 client = new(Client)
125 client.KubeConfig = config
126
127 client.KubeClientConfig, err = client.KubeConfig.ClientConfig()
128 if err != nil {
129 return nil, fmt.Errorf(errorMsg, err)
130 }
131
132
133
134
135
136
137
138 client.KubeClientConfig.QPS = defaultQPS
139 client.KubeClientConfig.Burst = defaultBurst
140
141
142 client.KubeClientConfig.WarningHandler = rest.NewWarningWriter(log.GetStderr(), rest.WarningWriterOptions{
143
144 Deduplicate: true,
145
146 Color: term.AllowsColorOutput(log.GetStderr()),
147 })
148
149 client.KubeClient, err = kubernetes.NewForConfig(client.KubeClientConfig)
150 if err != nil {
151 return nil, err
152 }
153
154 client.Namespace, _, err = client.KubeConfig.Namespace()
155 if err != nil {
156 return nil, err
157 }
158
159 client.OperatorClient, err = operatorsclientset.NewForConfig(client.KubeClientConfig)
160 if err != nil {
161 return nil, err
162 }
163
164 noWarningConfig := rest.CopyConfig(client.KubeClientConfig)
165
166 noWarningConfig.WarningHandler = rest.NoWarnings{}
167 client.DynamicClient, err = dynamic.NewForConfig(noWarningConfig)
168 if err != nil {
169 return nil, err
170 }
171
172 client.appsClient, err = appsclientset.NewForConfig(client.KubeClientConfig)
173 if err != nil {
174 return nil, err
175 }
176
177 client.serviceCatalogClient, err = servicecatalogclienset.NewForConfig(client.KubeClientConfig)
178 if err != nil {
179 return nil, err
180 }
181
182 client.discoveryClient, err = discovery.NewDiscoveryClientForConfig(client.KubeClientConfig)
183 if err != nil {
184 return nil, err
185 }
186
187 config_flags := genericclioptions.NewConfigFlags(true)
188 client.cachedDiscoveryClient, err = config_flags.ToDiscoveryClient()
189 if err != nil {
190 return nil, err
191 }
192
193 client.restmapper = restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(client.discoveryClient))
194
195 client.checkIngressSupports = true
196
197 client.userClient, err = userclientset.NewForConfig(client.KubeClientConfig)
198 if err != nil {
199 return nil, err
200 }
201
202 client.projectClient, err = projectclientset.NewForConfig(client.KubeClientConfig)
203 if err != nil {
204 return nil, err
205 }
206
207 client.routeClient, err = routeclientset.NewForConfig(client.KubeClientConfig)
208 if err != nil {
209 return nil, err
210 }
211 client.configClient, err = configclientset.NewForConfig(client.KubeClientConfig)
212 if err != nil {
213 return nil, err
214 }
215 return client, nil
216 }
217
218
219 func (c *Client) GeneratePortForwardReq(podName string) *rest.Request {
220 return c.KubeClient.CoreV1().RESTClient().
221 Post().
222 Resource("pods").
223 Namespace(c.Namespace).
224 Name(podName).
225 SubResource("portforward")
226 }
227
228 func (c *Client) SetDiscoveryInterface(client discovery.DiscoveryInterface) {
229 c.discoveryClient = client
230 }
231
232 func (c *Client) SetDynamicClient(scheme *runtime.Scheme, objects ...runtime.Object) {
233 c.DynamicClient = fake.NewSimpleDynamicClient(scheme, objects...)
234 }
235
236 func (c *Client) IsResourceSupported(apiGroup, apiVersion, resourceName string) (bool, error) {
237 klog.V(4).Infof("Checking if %q resource is supported", resourceName)
238
239 if c.supportedResources == nil {
240 c.supportedResources = make(map[string]bool, 7)
241 }
242 groupVersion := metav1.GroupVersion{Group: apiGroup, Version: apiVersion}.String()
243 resource := metav1.GroupVersionResource{Group: apiGroup, Version: apiVersion, Resource: resourceName}
244 groupVersionResource := resource.String()
245
246 supported, found := c.supportedResources[groupVersionResource]
247 if !found {
248 list, err := c.discoveryClient.ServerResourcesForGroupVersion(groupVersion)
249 if err != nil {
250 if kerrors.IsNotFound(err) {
251 supported = false
252 } else {
253
254 return false, err
255 }
256 } else {
257 for _, resources := range list.APIResources {
258 if resources.Name == resourceName {
259 supported = true
260 break
261 }
262 }
263 }
264 c.supportedResources[groupVersionResource] = supported
265 }
266 return supported, nil
267 }
268
269
270
271
272
273 func (c *Client) IsSSASupported() bool {
274
275 if c.isSSASupported == nil {
276 versionWithSSA, err := semver.Make("1.16.0")
277 if err != nil {
278 klog.Warningf("unable to parse version %q", err)
279 }
280
281 kVersion, err := c.discoveryClient.ServerVersion()
282 if err != nil {
283 klog.Warningf("unable to get k8s server version %q", err)
284 return true
285 }
286 klog.V(4).Infof("Kubernetes version is %q", kVersion.String())
287
288 cleanupVersion := strings.TrimLeft(kVersion.String(), "v")
289 serverVersion, err := semver.Make(cleanupVersion)
290 if err != nil {
291 klog.Warningf("unable to parse k8s server version %q", err)
292 return true
293 }
294
295 isSSASupported := versionWithSSA.LE(serverVersion)
296 c.isSSASupported = &isSSASupported
297
298 klog.V(4).Infof("Cluster has support for SSA: %t", *c.isSSASupported)
299 }
300 return *c.isSSASupported
301
302 }
303
View as plain text