github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/k8s/info.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  	"errors"
     9  	"fmt"
    10  	"regexp"
    11  
    12  	"strings"
    13  )
    14  
    15  // List of supported distros via distro detection.
    16  const (
    17  	DistroIsUnknown       = "unknown"
    18  	DistroIsK3s           = "k3s"
    19  	DistroIsK3d           = "k3d"
    20  	DistroIsKind          = "kind"
    21  	DistroIsMicroK8s      = "microk8s"
    22  	DistroIsEKS           = "eks"
    23  	DistroIsEKSAnywhere   = "eksanywhere"
    24  	DistroIsDockerDesktop = "dockerdesktop"
    25  	DistroIsGKE           = "gke"
    26  	DistroIsAKS           = "aks"
    27  	DistroIsRKE2          = "rke2"
    28  	DistroIsTKG           = "tkg"
    29  )
    30  
    31  // DetectDistro returns the matching distro or unknown if not found.
    32  func (k *K8s) DetectDistro() (string, error) {
    33  	kindNodeRegex := regexp.MustCompile(`^kind://`)
    34  	k3dNodeRegex := regexp.MustCompile(`^k3s://k3d-`)
    35  	eksNodeRegex := regexp.MustCompile(`^aws:///`)
    36  	gkeNodeRegex := regexp.MustCompile(`^gce://`)
    37  	aksNodeRegex := regexp.MustCompile(`^azure:///subscriptions`)
    38  	rke2Regex := regexp.MustCompile(`^rancher/rancher-agent:v2`)
    39  	tkgRegex := regexp.MustCompile(`^projects\.registry\.vmware\.com/tkg/tanzu_core/`)
    40  
    41  	nodes, err := k.GetNodes()
    42  	if err != nil {
    43  		return DistroIsUnknown, errors.New("error getting cluster nodes")
    44  	}
    45  
    46  	// All nodes should be the same for what we are looking for
    47  	node := nodes.Items[0]
    48  
    49  	// Regex explanation: https://regex101.com/r/TIUQVe/1
    50  	// https://github.com/rancher/k3d/blob/v5.2.2/cmd/node/nodeCreate.go#L187
    51  	if k3dNodeRegex.MatchString(node.Spec.ProviderID) {
    52  		return DistroIsK3d, nil
    53  	}
    54  
    55  	// Regex explanation: https://regex101.com/r/le7PRB/1
    56  	// https://github.com/kubernetes-sigs/kind/pull/1805
    57  	if kindNodeRegex.MatchString(node.Spec.ProviderID) {
    58  		return DistroIsKind, nil
    59  	}
    60  
    61  	// https://github.com/kubernetes/cloud-provider-aws/blob/454ed784c33b974c873c7d762f9d30e7c4caf935/pkg/providers/v2/instances.go#L234
    62  	if eksNodeRegex.MatchString(node.Spec.ProviderID) {
    63  		return DistroIsEKS, nil
    64  	}
    65  
    66  	if gkeNodeRegex.MatchString(node.Spec.ProviderID) {
    67  		return DistroIsGKE, nil
    68  	}
    69  
    70  	// https://github.com/kubernetes/kubernetes/blob/v1.23.4/staging/src/k8s.io/legacy-cloud-providers/azure/azure_wrap.go#L46
    71  	if aksNodeRegex.MatchString(node.Spec.ProviderID) {
    72  		return DistroIsAKS, nil
    73  	}
    74  
    75  	labels := node.GetLabels()
    76  	for _, label := range labels {
    77  		// kubectl get nodes --selector node.kubernetes.io/instance-type=k3s for K3s
    78  		if label == "node.kubernetes.io/instance-type=k3s" {
    79  			return DistroIsK3s, nil
    80  		}
    81  		// kubectl get nodes --selector microk8s.io/cluster=true for MicroK8s
    82  		if label == "microk8s.io/cluster=true" {
    83  			return DistroIsMicroK8s, nil
    84  		}
    85  	}
    86  
    87  	if node.GetName() == "docker-desktop" {
    88  		return DistroIsDockerDesktop, nil
    89  	}
    90  
    91  	for _, images := range node.Status.Images {
    92  		for _, image := range images.Names {
    93  			if rke2Regex.MatchString(image) {
    94  				return DistroIsRKE2, nil
    95  			}
    96  			if tkgRegex.MatchString(image) {
    97  				return DistroIsTKG, nil
    98  			}
    99  		}
   100  	}
   101  
   102  	namespaces, err := k.GetNamespaces()
   103  	if err != nil {
   104  		return DistroIsUnknown, errors.New("error getting namespace list")
   105  	}
   106  
   107  	// kubectl get ns eksa-system for EKS Anywhere
   108  	for _, namespace := range namespaces.Items {
   109  		if namespace.Name == "eksa-system" {
   110  			return DistroIsEKSAnywhere, nil
   111  		}
   112  	}
   113  
   114  	return DistroIsUnknown, nil
   115  }
   116  
   117  // GetArchitectures returns the cluster system architectures if found.
   118  func (k *K8s) GetArchitectures() ([]string, error) {
   119  	nodes, err := k.GetNodes()
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	if len(nodes.Items) == 0 {
   125  		return nil, errors.New("could not identify node architecture")
   126  	}
   127  
   128  	archMap := map[string]bool{}
   129  
   130  	for _, node := range nodes.Items {
   131  		archMap[node.Status.NodeInfo.Architecture] = true
   132  	}
   133  
   134  	architectures := []string{}
   135  
   136  	for arch := range archMap {
   137  		architectures = append(architectures, arch)
   138  	}
   139  
   140  	return architectures, nil
   141  }
   142  
   143  // GetServerVersion retrieves and returns the k8s revision.
   144  func (k *K8s) GetServerVersion() (version string, err error) {
   145  	versionInfo, err := k.Clientset.Discovery().ServerVersion()
   146  	if err != nil {
   147  		return "", fmt.Errorf("unable to get Kubernetes version from the cluster : %w", err)
   148  	}
   149  
   150  	return versionInfo.String(), nil
   151  }
   152  
   153  // MakeLabels is a helper to format a map of label key and value pairs into a single string for use as a selector.
   154  func MakeLabels(labels map[string]string) string {
   155  	var out []string
   156  	for key, value := range labels {
   157  		out = append(out, fmt.Sprintf("%s=%s", key, value))
   158  	}
   159  	return strings.Join(out, ",")
   160  }