github.com/verrazzano/verrazzano@v1.7.1/pkg/k8sutil/k8sutil.go (about)

     1  // Copyright (c) 2021, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package k8sutil
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  
    15  	certmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1"
    16  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    17  	istiov1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3"
    18  	istioClient "istio.io/client-go/pkg/clientset/versioned"
    19  	v1 "k8s.io/api/core/v1"
    20  	networkingv1 "k8s.io/api/networking/v1"
    21  	apiextv1Client "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    22  	apiextv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
    23  	kerrs "k8s.io/apimachinery/pkg/api/errors"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/types"
    26  	k8sversionutil "k8s.io/apimachinery/pkg/util/version"
    27  	"k8s.io/client-go/dynamic"
    28  	"k8s.io/client-go/kubernetes"
    29  	"k8s.io/client-go/kubernetes/scheme"
    30  	appsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
    31  	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    32  	"k8s.io/client-go/rest"
    33  	"k8s.io/client-go/tools/clientcmd"
    34  	"k8s.io/client-go/tools/remotecommand"
    35  	"k8s.io/client-go/util/homedir"
    36  	controllerruntime "sigs.k8s.io/controller-runtime"
    37  	"sigs.k8s.io/controller-runtime/pkg/client"
    38  )
    39  
    40  // EnvVarKubeConfig Name of Environment Variable for KUBECONFIG
    41  const EnvVarKubeConfig = "KUBECONFIG"
    42  
    43  // EnvVarTestKubeConfig Name of Environment Variable for test KUBECONFIG
    44  const EnvVarTestKubeConfig = "TEST_KUBECONFIG"
    45  
    46  const APIServerBurst = 150
    47  const APIServerQPS = 100
    48  
    49  type ClientConfigFunc func() (*rest.Config, kubernetes.Interface, error)
    50  
    51  var ClientConfig ClientConfigFunc = func() (*rest.Config, kubernetes.Interface, error) {
    52  	cfg, err := GetConfigFromController()
    53  	if err != nil {
    54  		return nil, nil, err
    55  	}
    56  	c, err := kubernetes.NewForConfig(cfg)
    57  	if err != nil {
    58  		return nil, nil, err
    59  	}
    60  	return cfg, c, nil
    61  }
    62  
    63  // fakeClient is for unit testing
    64  var fakeClient kubernetes.Interface
    65  
    66  // SetFakeClient for unit tests
    67  func SetFakeClient(client kubernetes.Interface) {
    68  	fakeClient = client
    69  }
    70  
    71  // ClearFakeClient for unit tests
    72  func ClearFakeClient() {
    73  	fakeClient = nil
    74  }
    75  
    76  // GetConfigFromController get the config from the Controller Runtime and set the default QPS and burst.
    77  func GetConfigFromController() (*rest.Config, error) {
    78  	cfg, err := controllerruntime.GetConfig()
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	setConfigQPSBurst(cfg)
    83  	return cfg, nil
    84  }
    85  
    86  // GetConfigOrDieFromController get the config from the Controller Runtime and set the default QPS and burst.
    87  func GetConfigOrDieFromController() *rest.Config {
    88  	cfg := controllerruntime.GetConfigOrDie()
    89  	setConfigQPSBurst(cfg)
    90  	return cfg
    91  }
    92  
    93  // GetKubeConfigLocation Helper function to obtain the default kubeConfig location
    94  func GetKubeConfigLocation() (string, error) {
    95  	if testKubeConfig := os.Getenv(EnvVarTestKubeConfig); len(testKubeConfig) > 0 {
    96  		return testKubeConfig, nil
    97  	}
    98  
    99  	if kubeConfig := os.Getenv(EnvVarKubeConfig); len(kubeConfig) > 0 {
   100  		return kubeConfig, nil
   101  	}
   102  
   103  	if home := homedir.HomeDir(); home != "" {
   104  		return filepath.Join(home, ".kube", "config"), nil
   105  	}
   106  
   107  	return "", errors.New("unable to find kubeconfig")
   108  
   109  }
   110  
   111  // GetKubeConfigGivenPath GetKubeConfig will get the kubeconfig from the given kubeconfigPath
   112  func GetKubeConfigGivenPath(kubeconfigPath string) (*rest.Config, error) {
   113  	return BuildKubeConfig(kubeconfigPath)
   114  }
   115  
   116  func BuildKubeConfig(kubeconfig string) (*rest.Config, error) {
   117  	config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	setConfigQPSBurst(config)
   122  	return config, nil
   123  }
   124  
   125  // GetKubeConfig Returns kubeconfig from KUBECONFIG env var if set
   126  // Else from default location ~/.kube/config
   127  func GetKubeConfig() (*rest.Config, error) {
   128  	var config *rest.Config
   129  	kubeConfigLoc, err := GetKubeConfigLocation()
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	config, err = clientcmd.BuildConfigFromFlags("", kubeConfigLoc)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	setConfigQPSBurst(config)
   138  	return config, nil
   139  }
   140  
   141  // GetKubeConfigGivenPathAndContext returns a rest.Config given a kubeConfig and kubeContext.
   142  func GetKubeConfigGivenPathAndContext(kubeConfigPath string, kubeContext string) (*rest.Config, error) {
   143  	// If no values passed, call default GetKubeConfig
   144  	if len(kubeConfigPath) == 0 && len(kubeContext) == 0 {
   145  		return GetKubeConfig()
   146  	}
   147  
   148  	// Default the value of kubeConfigLoc?
   149  	var err error
   150  	if len(kubeConfigPath) == 0 {
   151  		kubeConfigPath, err = GetKubeConfigLocation()
   152  		if err != nil {
   153  			return nil, err
   154  		}
   155  	}
   156  
   157  	config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
   158  		&clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeConfigPath},
   159  		&clientcmd.ConfigOverrides{CurrentContext: kubeContext}).ClientConfig()
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  	setConfigQPSBurst(config)
   164  	return config, nil
   165  }
   166  
   167  // GetKubernetesClientset returns the Kubernetes clientset for the cluster set in the environment
   168  func GetKubernetesClientset() (*kubernetes.Clientset, error) {
   169  	// use the current context in the kubeconfig
   170  	var clientset *kubernetes.Clientset
   171  	config, err := GetKubeConfig()
   172  	if err != nil {
   173  		return clientset, err
   174  	}
   175  	return GetKubernetesClientsetWithConfig(config)
   176  }
   177  
   178  // GetKubernetesClientsetOrDie returns the kubernetes clientset, panic if it cannot be created.
   179  func GetKubernetesClientsetOrDie() *kubernetes.Clientset {
   180  	clientset, err := GetKubernetesClientset()
   181  	if err != nil {
   182  		panic(err)
   183  	}
   184  	return clientset
   185  }
   186  
   187  // GetKubernetesClientsetWithConfig returns the Kubernetes clientset for the given configuration
   188  func GetKubernetesClientsetWithConfig(config *rest.Config) (*kubernetes.Clientset, error) {
   189  	var clientset *kubernetes.Clientset
   190  	clientset, err := kubernetes.NewForConfig(config)
   191  	return clientset, err
   192  }
   193  
   194  // GetCoreV1Func is the function to return the CoreV1Interface
   195  var GetCoreV1Func = GetCoreV1Client
   196  
   197  // GetCoreV1Client returns the CoreV1Interface
   198  func GetCoreV1Client(log ...vzlog.VerrazzanoLogger) (corev1.CoreV1Interface, error) {
   199  	goClient, err := GetGoClient(log...)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	return goClient.CoreV1(), nil
   204  }
   205  
   206  func ResetCoreV1Client() {
   207  	GetCoreV1Func = GetCoreV1Client
   208  }
   209  
   210  // GetAPIExtV1ClientFunc is the function to return the ApiextensionsV1Interface
   211  var GetAPIExtV1ClientFunc = GetAPIExtV1Client
   212  
   213  // ResetGetAPIExtV1ClientFunc for unit testing, to reset any overrides to GetAPIExtV1ClientFunc
   214  func ResetGetAPIExtV1ClientFunc() {
   215  	GetAPIExtV1ClientFunc = GetAPIExtV1Client
   216  }
   217  
   218  // GetAPIExtV1Client returns the ApiextensionsV1Interface
   219  func GetAPIExtV1Client() (apiextv1.ApiextensionsV1Interface, error) {
   220  	goClient, err := GetAPIExtGoClient()
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  	return goClient.ApiextensionsV1(), nil
   225  }
   226  
   227  // GetAppsV1Func is the function the AppsV1Interface
   228  var GetAppsV1Func = GetAppsV1Client
   229  
   230  // GetAppsV1Client returns the AppsV1Interface
   231  func GetAppsV1Client(log ...vzlog.VerrazzanoLogger) (appsv1.AppsV1Interface, error) {
   232  	goClient, err := GetGoClient(log...)
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  	return goClient.AppsV1(), nil
   237  }
   238  
   239  // GetDynamicClientFunc is the function to return the Dynamic Interface
   240  var GetDynamicClientFunc = GetDynamicClient
   241  
   242  // GetDynamicClient returns the Dynamic Interface
   243  func GetDynamicClient() (dynamic.Interface, error) {
   244  	config, err := GetConfigFromController()
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  	dynClient, err := dynamic.NewForConfig(config)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	return dynClient, nil
   253  }
   254  
   255  // GetIstioClientset returns the clientset object for Istio
   256  func GetIstioClientset() (*istioClient.Clientset, error) {
   257  	kubeConfigLoc, err := GetKubeConfigLocation()
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  	return GetIstioClientsetInCluster(kubeConfigLoc)
   262  }
   263  
   264  // GetIstioClientsetInCluster returns the clientset object for Istio
   265  func GetIstioClientsetInCluster(kubeconfigPath string) (*istioClient.Clientset, error) {
   266  	var cs *istioClient.Clientset
   267  	kubeConfig, err := GetKubeConfigGivenPath(kubeconfigPath)
   268  	if err != nil {
   269  		return cs, err
   270  	}
   271  	cs, err = istioClient.NewForConfig(kubeConfig)
   272  	return cs, err
   273  }
   274  
   275  // GetCertManagerClienset returns the clientset object for CertManager
   276  func GetCertManagerClienset() (*certmanagerv1.CertmanagerV1Client, error) {
   277  	kubeConfigLoc, err := GetKubeConfigLocation()
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  	return GetCertManagerClientsetInCluster(kubeConfigLoc)
   282  }
   283  
   284  // GetCertManagerClientsetInCluster returns the clientset object for CertManager
   285  func GetCertManagerClientsetInCluster(kubeconfigPath string) (*certmanagerv1.CertmanagerV1Client, error) {
   286  	var cs *certmanagerv1.CertmanagerV1Client
   287  	kubeConfig, err := GetKubeConfigGivenPath(kubeconfigPath)
   288  	if err != nil {
   289  		return cs, err
   290  	}
   291  	cs, err = certmanagerv1.NewForConfig(kubeConfig)
   292  	return cs, err
   293  }
   294  
   295  // GetHostnameFromGateway returns the host name from the application gateway that was
   296  // created for the ApplicationConfiguration with name appConfigName from list of input gateways. If
   297  // the input list of gateways is not provided, it is fetched from the kubernetes cluster
   298  func GetHostnameFromGateway(namespace string, appConfigName string, gateways ...*istiov1alpha3.Gateway) (string, error) {
   299  	var config string
   300  	kubeConfigLoc, err := GetKubeConfigLocation()
   301  	if err != nil {
   302  		return config, err
   303  	}
   304  	return GetHostnameFromGatewayInCluster(namespace, appConfigName, kubeConfigLoc, gateways...)
   305  }
   306  
   307  // GetHostnameFromGatewayInCluster returns the host name from the application gateway that was
   308  // created for the ApplicationConfiguration with name appConfigName from list of input gateways. If
   309  // the input list of gateways is not provided, it is fetched from the kubernetes cluster
   310  func GetHostnameFromGatewayInCluster(namespace string, appConfigName string, kubeconfigPath string, gateways ...*istiov1alpha3.Gateway) (string, error) {
   311  	if len(gateways) == 0 {
   312  		cs, err := GetIstioClientsetInCluster(kubeconfigPath)
   313  		if err != nil {
   314  			fmt.Printf("Could not get istio clientset: %v", err)
   315  			return "", err
   316  		}
   317  
   318  		gatewayList, err := cs.NetworkingV1alpha3().Gateways(namespace).List(context.TODO(), metav1.ListOptions{})
   319  		if err != nil {
   320  			fmt.Printf("Could not list application ingress gateways: %v", err)
   321  			return "", err
   322  		}
   323  
   324  		gateways = gatewayList.Items
   325  	}
   326  
   327  	// if an optional appConfigName is provided, construct the gateway name from the namespace and
   328  	// appConfigName and look for that specific gateway, otherwise just use the first gateway
   329  	gatewayName := ""
   330  	if len(appConfigName) > 0 {
   331  		gatewayName = fmt.Sprintf("%s-%s-gw", namespace, appConfigName)
   332  	}
   333  
   334  	for _, gateway := range gateways {
   335  		if len(gatewayName) > 0 && gatewayName != gateway.ObjectMeta.Name {
   336  			continue
   337  		}
   338  
   339  		fmt.Printf("Found an app ingress gateway with name: %s\n", gateway.ObjectMeta.Name)
   340  		if len(gateway.Spec.Servers) > 0 && len(gateway.Spec.Servers[0].Hosts) > 0 {
   341  			return gateway.Spec.Servers[0].Hosts[0], nil
   342  		}
   343  	}
   344  
   345  	// this can happen if the app gateway has not been created yet, the caller should
   346  	// keep retrying, and eventually we should get a gateway with a host
   347  	fmt.Printf("Could not find host in application ingress gateways in namespace: %s\n", namespace)
   348  	return "", nil
   349  }
   350  
   351  // NewPodExecutor is to be overridden during unit tests
   352  var NewPodExecutor = remotecommand.NewSPDYExecutor
   353  
   354  // ExecPod runs a remote command a pod, returning the stdout and stderr of the command.
   355  func ExecPod(client kubernetes.Interface, cfg *rest.Config, pod *v1.Pod, container string, command []string) (string, string, error) {
   356  	stdout := &bytes.Buffer{}
   357  	stderr := &bytes.Buffer{}
   358  	request := client.
   359  		CoreV1().
   360  		RESTClient().
   361  		Post().
   362  		Namespace(pod.Namespace).
   363  		Resource("pods").
   364  		Name(pod.Name).
   365  		SubResource("exec").
   366  		VersionedParams(&v1.PodExecOptions{
   367  			Container: container,
   368  			Command:   command,
   369  			Stdin:     false,
   370  			Stdout:    true,
   371  			Stderr:    true,
   372  			TTY:       true,
   373  		}, scheme.ParameterCodec)
   374  	executor, err := NewPodExecutor(cfg, "POST", request.URL())
   375  	if err != nil {
   376  		return "", "", err
   377  	}
   378  	err = executor.Stream(remotecommand.StreamOptions{
   379  		Stdout: stdout,
   380  		Stderr: stderr,
   381  	})
   382  	if err != nil {
   383  		return "", "", fmt.Errorf("error running command %s on %v/%v: %v", command, pod.Namespace, pod.Name, err)
   384  	}
   385  
   386  	return stdout.String(), stderr.String(), nil
   387  }
   388  
   389  // ExecPodNoTty runs a remote command a pod, returning the stdout and stderr of the command.
   390  func ExecPodNoTty(client kubernetes.Interface, cfg *rest.Config, pod *v1.Pod, container string, command []string) (string, string, error) {
   391  	stdout := &bytes.Buffer{}
   392  	stderr := &bytes.Buffer{}
   393  	request := client.
   394  		CoreV1().
   395  		RESTClient().
   396  		Post().
   397  		Namespace(pod.Namespace).
   398  		Resource("pods").
   399  		Name(pod.Name).
   400  		SubResource("exec").
   401  		VersionedParams(&v1.PodExecOptions{
   402  			Container: container,
   403  			Command:   command,
   404  			Stdin:     false,
   405  			Stdout:    true,
   406  			Stderr:    true,
   407  			TTY:       false,
   408  		}, scheme.ParameterCodec)
   409  	executor, err := NewPodExecutor(cfg, "POST", request.URL())
   410  	if err != nil {
   411  		return "", "", err
   412  	}
   413  	err = executor.Stream(remotecommand.StreamOptions{
   414  		Stdout: stdout,
   415  		Stderr: stderr,
   416  	})
   417  	if err != nil {
   418  		return "", "", fmt.Errorf("error running command %s on %v/%v: %v", command, pod.Namespace, pod.Name, err)
   419  	}
   420  
   421  	return stdout.String(), stderr.String(), nil
   422  }
   423  
   424  // GetGoClient returns a go-client
   425  func GetGoClient(log ...vzlog.VerrazzanoLogger) (kubernetes.Interface, error) {
   426  	if fakeClient != nil {
   427  		return fakeClient, nil
   428  	}
   429  	var logger vzlog.VerrazzanoLogger
   430  	if len(log) > 0 {
   431  		logger = log[0]
   432  	}
   433  	config, err := buildRESTConfig(logger)
   434  	if err != nil {
   435  		return nil, err
   436  	}
   437  	kubeClient, err := kubernetes.NewForConfig(config)
   438  	if err != nil {
   439  		if logger != nil {
   440  			logger.Errorf("Failed to get clientset: %v", err)
   441  		}
   442  		return nil, err
   443  	}
   444  
   445  	return kubeClient, err
   446  }
   447  
   448  // GetAPIExtGoClient returns an API Extensions go-client
   449  func GetAPIExtGoClient() (apiextv1Client.Interface, error) {
   450  	config, err := buildRESTConfig(nil)
   451  	if err != nil {
   452  		return nil, err
   453  	}
   454  	apiextClient, err := apiextv1Client.NewForConfig(config)
   455  	if err != nil {
   456  		return nil, err
   457  	}
   458  	return apiextClient, err
   459  }
   460  
   461  func buildRESTConfig(logger vzlog.VerrazzanoLogger) (*rest.Config, error) {
   462  	config, err := GetConfigFromController()
   463  	if err != nil {
   464  		if logger != nil {
   465  			logger.Errorf("Failed to get kubeconfig: %v", err)
   466  		}
   467  		return nil, err
   468  	}
   469  	return config, nil
   470  }
   471  
   472  // GetDynamicClientInCluster returns a dynamic client needed to access Unstructured data
   473  func GetDynamicClientInCluster(kubeconfigPath string) (dynamic.Interface, error) {
   474  	config, err := GetKubeConfigGivenPath(kubeconfigPath)
   475  	if err != nil {
   476  		return nil, err
   477  	}
   478  	return dynamic.NewForConfig(config)
   479  }
   480  
   481  // GetURLForIngress returns the url for an Ingress
   482  func GetURLForIngress(client client.Client, name string, namespace string, scheme string) (string, error) {
   483  	var ingress = &networkingv1.Ingress{}
   484  	err := client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, ingress)
   485  	if err != nil {
   486  		return "", fmt.Errorf("unable to fetch ingress %s/%s, %v", name, namespace, err)
   487  	}
   488  	return fmt.Sprintf("%s://%s", scheme, ingress.Spec.Rules[0].Host), nil
   489  }
   490  
   491  // GetRunningPodForLabel returns the reference of a running pod that matches the given label
   492  func GetRunningPodForLabel(c client.Client, label string, namespace string, log ...vzlog.VerrazzanoLogger) (*v1.Pod, error) {
   493  	var logger vzlog.VerrazzanoLogger
   494  	if len(log) > 0 {
   495  		logger = log[0]
   496  	} else {
   497  		logger = vzlog.DefaultLogger()
   498  	}
   499  
   500  	pods := &v1.PodList{}
   501  	labelPair := strings.Split(label, "=")
   502  	err := c.List(context.Background(), pods, client.MatchingLabels{labelPair[0]: labelPair[1]})
   503  
   504  	if err != nil {
   505  		return nil, logger.ErrorfThrottledNewErr("Failed getting running pods for label %s in namespace %s, error: %v", label, namespace, err.Error())
   506  	}
   507  
   508  	if !(len(pods.Items) > 0) {
   509  		return nil, logger.ErrorfThrottledNewErr("Invalid running pod list for label %s in namespace %s", label, namespace)
   510  	}
   511  
   512  	for _, pod := range pods.Items {
   513  		if pod.Status.Phase == v1.PodRunning {
   514  			return &pod, nil
   515  		}
   516  	}
   517  
   518  	return nil, logger.ErrorfThrottledNewErr("No running pod for label %s in namespace %s", label, namespace)
   519  }
   520  
   521  // ErrorIfDeploymentExists reports error if any of the Deployments exists
   522  func ErrorIfDeploymentExists(namespace string, names ...string) error {
   523  	appsCli, err := GetAppsV1Func()
   524  	if err != nil {
   525  		return err
   526  	}
   527  	deployList, err := appsCli.Deployments(namespace).List(context.TODO(), metav1.ListOptions{})
   528  	if err != nil && !kerrs.IsNotFound(err) {
   529  		return err
   530  
   531  	}
   532  	for _, d := range deployList.Items {
   533  		for _, n := range names {
   534  			if d.Name == n {
   535  				return fmt.Errorf("existing Deployment %s in namespace %s", d.Name, namespace)
   536  			}
   537  		}
   538  	}
   539  	return nil
   540  }
   541  
   542  // ErrorIfServiceExists reports error if any of the Services exists
   543  func ErrorIfServiceExists(namespace string, names ...string) error {
   544  	cli, err := GetCoreV1Func()
   545  	if err != nil {
   546  		return err
   547  	}
   548  	serviceList, err := cli.Services(namespace).List(context.TODO(), metav1.ListOptions{})
   549  	if err != nil && !kerrs.IsNotFound(err) {
   550  		return err
   551  
   552  	}
   553  	for _, s := range serviceList.Items {
   554  		for _, n := range names {
   555  			if s.Name == n {
   556  				return fmt.Errorf("existing Service %s in namespace %s", s.Name, namespace)
   557  			}
   558  		}
   559  	}
   560  	return nil
   561  }
   562  
   563  func setConfigQPSBurst(config *rest.Config) {
   564  	config.Burst = APIServerBurst
   565  	config.QPS = APIServerQPS
   566  }
   567  
   568  // GetKubernetesVersion returns the version of Kubernetes cluster in which operator is deployed
   569  func GetKubernetesVersion() (string, error) {
   570  	config, err := GetConfigFromController()
   571  	if err != nil {
   572  		return "", fmt.Errorf("Failed to get kubernetes client config %v", err.Error())
   573  	}
   574  
   575  	client, err := kubernetes.NewForConfig(config)
   576  	if err != nil {
   577  		return "", fmt.Errorf("Failed to get kubernetes client %v", err.Error())
   578  	}
   579  
   580  	versionInfo, err := client.ServerVersion()
   581  	if err != nil {
   582  		return "", fmt.Errorf("Failed to get kubernetes version %v", err.Error())
   583  	}
   584  	return versionInfo.String(), nil
   585  }
   586  
   587  func IsMinimumk8sVersion(expectedK8sVersion string) (bool, error) {
   588  	version, err := GetKubernetesVersion()
   589  	if err != nil {
   590  		return false, fmt.Errorf("Failed to get the kubernetes version: %v", err)
   591  	}
   592  	k8sVersion, err := k8sversionutil.ParseSemantic(version)
   593  	if err != nil {
   594  		return false, fmt.Errorf("Failed to parse Kubernetes version %q: %v", k8sVersion, err)
   595  	}
   596  	parsedExpectedK8sVersion := k8sversionutil.MustParseSemantic(expectedK8sVersion)
   597  	if k8sVersion.AtLeast(parsedExpectedK8sVersion) {
   598  		return true, nil
   599  	}
   600  	return false, nil
   601  }