Skip to content

Commit

Permalink
test: add unit tests for detect distro
Browse files Browse the repository at this point in the history
  • Loading branch information
phillebaba committed May 20, 2024
1 parent 5bf7e01 commit 10c788d
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 121 deletions.
101 changes: 101 additions & 0 deletions src/pkg/cluster/distro.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors

// Package k8s provides a client for interacting with a Kubernetes cluster.
package cluster

import (
"regexp"

corev1 "k8s.io/api/core/v1"
)

// List of supported distros via distro detection.
const (
DistroIsUnknown = "unknown"
DistroIsK3s = "k3s"
DistroIsK3d = "k3d"
DistroIsKind = "kind"
DistroIsMicroK8s = "microk8s"
DistroIsEKS = "eks"
DistroIsEKSAnywhere = "eksanywhere"
DistroIsDockerDesktop = "dockerdesktop"
DistroIsGKE = "gke"
DistroIsAKS = "aks"
DistroIsRKE2 = "rke2"
DistroIsTKG = "tkg"
)

// DetectDistro returns the matching distro or unknown if not found.
func detectDistro(node corev1.Node, namespaces []corev1.Namespace) string {
kindNodeRegex := regexp.MustCompile(`^kind://`)
k3dNodeRegex := regexp.MustCompile(`^k3s://k3d-`)
eksNodeRegex := regexp.MustCompile(`^aws:///`)
gkeNodeRegex := regexp.MustCompile(`^gce://`)
aksNodeRegex := regexp.MustCompile(`^azure:///subscriptions`)
rke2Regex := regexp.MustCompile(`^rancher/rancher-agent:v2`)
tkgRegex := regexp.MustCompile(`^projects\.registry\.vmware\.com/tkg/tanzu_core/`)

// Regex explanation: https://regex101.com/r/TIUQVe/1
// https://github.com/rancher/k3d/blob/v5.2.2/cmd/node/nodeCreate.go#L187
if k3dNodeRegex.MatchString(node.Spec.ProviderID) {
return DistroIsK3d
}

// Regex explanation: https://regex101.com/r/le7PRB/1
// https://github.com/kubernetes-sigs/kind/pull/1805
if kindNodeRegex.MatchString(node.Spec.ProviderID) {
return DistroIsKind
}

// https://github.com/kubernetes/cloud-provider-aws/blob/454ed784c33b974c873c7d762f9d30e7c4caf935/pkg/providers/v2/instances.go#L234
if eksNodeRegex.MatchString(node.Spec.ProviderID) {
return DistroIsEKS
}

if gkeNodeRegex.MatchString(node.Spec.ProviderID) {
return DistroIsGKE
}

// https://github.com/kubernetes/kubernetes/blob/v1.23.4/staging/src/k8s.io/legacy-cloud-providers/azure/azure_wrap.go#L46
if aksNodeRegex.MatchString(node.Spec.ProviderID) {
return DistroIsAKS
}

labels := node.GetLabels()
for _, label := range labels {
// kubectl get nodes --selector node.kubernetes.io/instance-type=k3s for K3s
if label == "node.kubernetes.io/instance-type=k3s" {
return DistroIsK3s
}
// kubectl get nodes --selector microk8s.io/cluster=true for MicroK8s
if label == "microk8s.io/cluster=true" {
return DistroIsMicroK8s
}
}

if node.GetName() == "docker-desktop" {
return DistroIsDockerDesktop
}

// TODO: Find a new detection method, by default the amount of images in the node status is limited.
for _, images := range node.Status.Images {
for _, image := range images.Names {
if rke2Regex.MatchString(image) {
return DistroIsRKE2
}
if tkgRegex.MatchString(image) {
return DistroIsTKG
}
}
}

// kubectl get ns eksa-system for EKS Anywhere
for _, namespace := range namespaces {
if namespace.Name == "eksa-system" {
return DistroIsEKSAnywhere
}
}

return DistroIsUnknown
}
181 changes: 181 additions & 0 deletions src/pkg/cluster/distro_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors

// Package k8s provides a client for interacting with a Kubernetes cluster.
package cluster

import (
"testing"

"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestDetectDistro(t *testing.T) {
t.Parallel()

tests := []struct {
distro string
node corev1.Node
namespaces []corev1.Namespace
}{
{
distro: DistroIsUnknown,
node: corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"foo": "bar",
},
},
Spec: corev1.NodeSpec{
ProviderID: "hello world",
},
},
namespaces: []corev1.Namespace{
{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
},
},
},
},
{
distro: DistroIsK3s,
node: corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"": "node.kubernetes.io/instance-type=k3s",
},
},
},
},
{
distro: DistroIsK3d,
node: corev1.Node{
Spec: corev1.NodeSpec{
ProviderID: "k3s://k3d-k3s-default-server-0",
},
},
},
{
distro: DistroIsKind,
node: corev1.Node{
Spec: corev1.NodeSpec{
ProviderID: "kind://docker/kind/kind-control-plane",
},
},
},
{
distro: DistroIsMicroK8s,
node: corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"": "microk8s.io/cluster=true",
},
},
},
},
{
distro: DistroIsEKS,
node: corev1.Node{
Spec: corev1.NodeSpec{
ProviderID: "aws:////i-112bac41a19da1819",
},
},
},
{
distro: DistroIsEKSAnywhere,
namespaces: []corev1.Namespace{
{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "eksa-system",
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "baz",
},
},
},
},
{
distro: DistroIsDockerDesktop,
node: corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "docker-desktop",
},
},
},
{
distro: DistroIsGKE,
node: corev1.Node{
Spec: corev1.NodeSpec{
ProviderID: "gce://kthw-239419/us-central1-f/gk3-autopilot-cluster-1-pool-2-e87e560a-7gvw",
},
},
},
{
distro: DistroIsAKS,
node: corev1.Node{
Spec: corev1.NodeSpec{
ProviderID: "azure:///subscriptions/9107f2fb-e486-a434-a948-52e2929b6f18/resourceGroups/MC_rg_capz-managed-aks_eastus/providers/Microsoft.Compute/virtualMachineScaleSets/aks-agentpool0-10226072-vmss/virtualMachines/0",
},
},
},
{
distro: DistroIsRKE2,
node: corev1.Node{
Status: corev1.NodeStatus{
Images: []corev1.ContainerImage{
{
Names: []string{"docker.io/library/ubuntu:latest"},
},
{
Names: []string{"rancher/rancher-agent:v2"},
},
},
},
},
},
{
distro: DistroIsTKG,
node: corev1.Node{
Status: corev1.NodeStatus{
Images: []corev1.ContainerImage{
{
Names: []string{"docker.io/library/ubuntu:latest"},
},
{
Names: []string{"projects.registry.vmware.com/tkg/tanzu_core/"},
},
},
},
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.distro, func(t *testing.T) {
t.Parallel()

distro := detectDistro(tt.node, tt.namespaces)
require.Equal(t, tt.distro, distro)
})
}
}
37 changes: 20 additions & 17 deletions src/pkg/cluster/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,20 @@ import (
"context"
"encoding/json"
"fmt"
"time"

"slices"
"time"

"github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/config/lang"
"github.com/defenseunicorns/zarf/src/types"
"github.com/fatih/color"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/defenseunicorns/pkg/helpers"
"github.com/defenseunicorns/zarf/src/pkg/k8s"

"github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/config/lang"
"github.com/defenseunicorns/zarf/src/pkg/message"
"github.com/defenseunicorns/zarf/src/pkg/pki"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/defenseunicorns/zarf/src/types"
)

// Zarf Cluster Constants.
Expand Down Expand Up @@ -54,20 +53,24 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO
state = &types.ZarfState{}
spinner.Updatef("New cluster, no prior Zarf deployments found")

// If the K3s component is being deployed, skip distro detection.
if initOptions.ApplianceMode {
distro = k8s.DistroIsK3s
// If the K3s component is being deployed, skip distro detection.
distro = DistroIsK3s
state.ZarfAppliance = true
} else {
// Otherwise, trying to detect the K8s distro type.
distro, err = c.DetectDistro(ctx)
nodeList, err := c.Clientset.CoreV1().Nodes().List(ctx, metav1.ListOptions{})
if err != nil {
return err
}
namespaceList, err := c.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
if err != nil {
// This is a basic failure right now but likely could be polished to provide user guidance to resolve.
return fmt.Errorf("unable to connect to the cluster to verify the distro: %w", err)
return err
}
distro = detectDistro(nodeList.Items[0], namespaceList.Items)
}

if distro != k8s.DistroIsUnknown {
if distro != DistroIsUnknown {
spinner.Updatef("Detected K8s distro %s", distro)
}

Expand Down Expand Up @@ -144,13 +147,13 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO
}

switch state.Distro {
case k8s.DistroIsK3s, k8s.DistroIsK3d:
case DistroIsK3s, DistroIsK3d:
state.StorageClass = "local-path"

case k8s.DistroIsKind, k8s.DistroIsGKE:
case DistroIsKind, DistroIsGKE:
state.StorageClass = "standard"

case k8s.DistroIsDockerDesktop:
case DistroIsDockerDesktop:
state.StorageClass = "hostpath"
}

Expand Down
Loading

0 comments on commit 10c788d

Please sign in to comment.