...

Source file src/github.com/redhat-developer/odo/pkg/kclient/kclient.go

Documentation: github.com/redhat-developer/odo/pkg/kclient

     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  	// api clientsets
    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" // Required for Kube clusters which use auth plugins
    40  )
    41  
    42  const (
    43  	// errorMsg is the message for user when invalid configuration error occurs
    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  // Client is a collection of fields used for client configuration and interaction
    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  	// DynamicClient interacts with client-go's `dynamic` package. It is used
    63  	// to dynamically create service from an operator. It can take an arbitrary
    64  	// yaml and create k8s/OpenShift resource from it.
    65  	DynamicClient         dynamic.Interface
    66  	discoveryClient       discovery.DiscoveryInterface
    67  	cachedDiscoveryClient discovery.CachedDiscoveryInterface
    68  	restmapper            *restmapper.DeferredDiscoveryRESTMapper
    69  
    70  	supportedResources map[string]bool
    71  	// Is server side apply supported by cluster
    72  	// Use IsSSASupported()
    73  	isSSASupported *bool
    74  	// checkIngressSupports is used to check ingress support
    75  	// (used to prevent duplicate checks and disable check in UTs)
    76  	checkIngressSupports               bool
    77  	isNetworkingV1IngressSupported     bool
    78  	isExtensionV1Beta1IngressSupported bool
    79  
    80  	// openshift clients
    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  // New creates a new client
    91  func New() (*Client, error) {
    92  	// Inside a cluster (IBM Cloud CI for example), even if KUBECONFIG=/dev/null, the in-cluster connection would succeed
    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  // NewForConfig creates a new client with the provided configuration or initializes the configuration if none is provided
   116  func NewForConfig(config clientcmd.ClientConfig) (client *Client, err error) {
   117  	if config == nil {
   118  		// initialize client-go clients
   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  	// For the rest CLIENT, we set the QPS and Burst to high values so
   133  	// we do not receive throttling error messages when using the REST client.
   134  	// Inadvertently, this also increases the speed of which we use the REST client
   135  	// to safe values without increased error / query information.
   136  	// See issue: https://github.com/kubernetes/client-go/issues/610
   137  	// and reference implementation: https://github.com/vmware-tanzu/tanzu-framework/pull/1656
   138  	client.KubeClientConfig.QPS = defaultQPS
   139  	client.KubeClientConfig.Burst = defaultBurst
   140  
   141  	// This warning handler ensures that warnings are not duplicated
   142  	client.KubeClientConfig.WarningHandler = rest.NewWarningWriter(log.GetStderr(), rest.WarningWriterOptions{
   143  		// only print a given warning the first time we receive it
   144  		Deduplicate: true,
   145  		// highlight the output with color when the output supports it
   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  	// set the warning handler for this client to ignore warnings
   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  // GeneratePortForwardReq builds a port forward request
   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  				// don't record, just attempt again next time in case it's a transient error
   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  // IsSSASupported checks if Server Side Apply is supported by cluster
   270  // SSA was introduced in Kubernetes 1.16
   271  // If there is an error while parsing versions, it assumes that SSA is supported by cluster.
   272  // Most of clusters these days are 1.16 and up
   273  func (c *Client) IsSSASupported() bool {
   274  	// check if this was done before so we don't query cluster multiple times for the same info
   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