github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/k8s/common.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: 2021-Present The Jackal Authors 3 4 // Package k8s provides a client for interacting with a Kubernetes cluster. 5 package k8s 6 7 import ( 8 "fmt" 9 "time" 10 11 v1 "k8s.io/api/core/v1" 12 "k8s.io/klog/v2" 13 14 "github.com/go-logr/logr/funcr" 15 "k8s.io/client-go/kubernetes" 16 17 // Include the cloud auth plugins 18 _ "k8s.io/client-go/plugin/pkg/client/auth" 19 "k8s.io/client-go/rest" 20 "k8s.io/client-go/tools/clientcmd" 21 ) 22 23 // New creates a new K8s client. 24 func New(logger Log, defaultLabels Labels) (*K8s, error) { 25 klog.SetLogger(funcr.New(func(_, args string) { 26 logger(args) 27 }, funcr.Options{})) 28 29 config, clientset, err := connect() 30 if err != nil { 31 return nil, fmt.Errorf("failed to connect to k8s cluster: %w", err) 32 } 33 34 return &K8s{ 35 RestConfig: config, 36 Clientset: clientset, 37 Log: logger, 38 Labels: defaultLabels, 39 }, nil 40 } 41 42 // NewWithWait is a convenience function that creates a new K8s client and waits for the cluster to be healthy. 43 func NewWithWait(logger Log, defaultLabels Labels, timeout time.Duration) (*K8s, error) { 44 k, err := New(logger, defaultLabels) 45 if err != nil { 46 return nil, err 47 } 48 49 return k, k.WaitForHealthyCluster(timeout) 50 } 51 52 // WaitForHealthyCluster checks for an available K8s cluster every second until timeout. 53 func (k *K8s) WaitForHealthyCluster(timeout time.Duration) error { 54 var err error 55 var nodes *v1.NodeList 56 var pods *v1.PodList 57 expired := time.After(timeout) 58 59 for { 60 select { 61 // on timeout abort 62 case <-expired: 63 return fmt.Errorf("timed out waiting for cluster to report healthy") 64 65 // after delay, try running 66 default: 67 if k.RestConfig == nil || k.Clientset == nil { 68 config, clientset, err := connect() 69 if err != nil { 70 k.Log("Cluster connection not available yet: %w", err) 71 continue 72 } 73 74 k.RestConfig = config 75 k.Clientset = clientset 76 } 77 78 // Make sure there is at least one running Node 79 nodes, err = k.GetNodes() 80 if err != nil || len(nodes.Items) < 1 { 81 k.Log("No nodes reporting healthy yet: %#v\n", err) 82 continue 83 } 84 85 // Get the cluster pod list 86 if pods, err = k.GetAllPods(); err != nil { 87 k.Log("Could not get the pod list: %w", err) 88 continue 89 } 90 91 // Check that at least one pod is in the 'succeeded' or 'running' state 92 for _, pod := range pods.Items { 93 // If a valid pod is found, return no error 94 if pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodRunning { 95 return nil 96 } 97 } 98 99 k.Log("No pods reported 'succeeded' or 'running' state yet.") 100 } 101 102 // delay check 1 seconds 103 time.Sleep(1 * time.Second) 104 } 105 } 106 107 // Use the K8s "client-go" library to get the currently active kube context, in the same way that 108 // "kubectl" gets it if no extra config flags like "--kubeconfig" are passed. 109 func connect() (config *rest.Config, clientset *kubernetes.Clientset, err error) { 110 // Build the config from the currently active kube context in the default way that the k8s client-go gets it, which 111 // is to look at the KUBECONFIG env var 112 config, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig( 113 clientcmd.NewDefaultClientConfigLoadingRules(), 114 &clientcmd.ConfigOverrides{}).ClientConfig() 115 116 if err != nil { 117 return nil, nil, err 118 } 119 120 clientset, err = kubernetes.NewForConfig(config) 121 if err != nil { 122 return nil, nil, err 123 } 124 125 return config, clientset, nil 126 }