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  }