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 }