diff --git a/site/astro.config.ts b/site/astro.config.ts
index 6e54f621a0..ac3b8dec24 100644
--- a/site/astro.config.ts
+++ b/site/astro.config.ts
@@ -25,6 +25,19 @@ export default defineConfig({
integrations: [
starlight({
title: "Zarf",
+ head: [
+ {
+ tag: "script",
+ content: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
+ new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
+ j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
+ 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
+ })(window,document,'script','dataLayer','G-N1XZ8ZXCWL');`,
+ },
+ ],
+ components: {
+ SkipLink: "./src/components/SkipLink.astro",
+ },
social: {
github: "https://github.com/defenseunicorns/zarf",
slack: "https://kubernetes.slack.com/archives/C03B6BJAUJ3",
diff --git a/site/src/components/SkipLink.astro b/site/src/components/SkipLink.astro
new file mode 100644
index 0000000000..7d863429e4
--- /dev/null
+++ b/site/src/components/SkipLink.astro
@@ -0,0 +1,18 @@
+---
+import type { Props } from '@astrojs/starlight/props'
+import Default from '@astrojs/starlight/components/SkipLink.astro'
+---
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pkg/cluster/distro.go b/src/pkg/cluster/distro.go
new file mode 100644
index 0000000000..8c90ccfc49
--- /dev/null
+++ b/src/pkg/cluster/distro.go
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
+
+// Package cluster contains Zarf-specific cluster management functions.
+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 k, v := range labels {
+ // kubectl get nodes --selector node.kubernetes.io/instance-type=k3s for K3s
+ if k == "node.kubernetes.io/instance-type" && v == "k3s" {
+ return DistroIsK3s
+ }
+ // kubectl get nodes --selector microk8s.io/cluster=true for MicroK8s
+ if k == "microk8s.io/cluster" && v == "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
+}
diff --git a/src/pkg/cluster/distro_test.go b/src/pkg/cluster/distro_test.go
new file mode 100644
index 0000000000..88e7711493
--- /dev/null
+++ b/src/pkg/cluster/distro_test.go
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
+
+// Package cluster contains Zarf-specific cluster management functions.
+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)
+ })
+ }
+}
diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go
index a0414f8793..39efa83008 100644
--- a/src/pkg/cluster/state.go
+++ b/src/pkg/cluster/state.go
@@ -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/config"
+ "github.com/defenseunicorns/zarf/src/config/lang"
"github.com/defenseunicorns/zarf/src/pkg/k8s"
"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.
@@ -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)
}
@@ -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"
}
diff --git a/src/pkg/k8s/info.go b/src/pkg/k8s/info.go
index edb655b964..1a19a837e1 100644
--- a/src/pkg/k8s/info.go
+++ b/src/pkg/k8s/info.go
@@ -8,113 +8,9 @@ import (
"context"
"errors"
"fmt"
- "regexp"
-
"strings"
)
-// 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 (k *K8s) DetectDistro(ctx context.Context) (string, error) {
- 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/`)
-
- nodes, err := k.GetNodes(ctx)
- if err != nil {
- return DistroIsUnknown, errors.New("error getting cluster nodes")
- }
-
- // All nodes should be the same for what we are looking for
- node := nodes.Items[0]
-
- // 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, nil
- }
-
- // Regex explanation: https://regex101.com/r/le7PRB/1
- // https://github.com/kubernetes-sigs/kind/pull/1805
- if kindNodeRegex.MatchString(node.Spec.ProviderID) {
- return DistroIsKind, nil
- }
-
- // https://github.com/kubernetes/cloud-provider-aws/blob/454ed784c33b974c873c7d762f9d30e7c4caf935/pkg/providers/v2/instances.go#L234
- if eksNodeRegex.MatchString(node.Spec.ProviderID) {
- return DistroIsEKS, nil
- }
-
- if gkeNodeRegex.MatchString(node.Spec.ProviderID) {
- return DistroIsGKE, nil
- }
-
- // 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, nil
- }
-
- 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, nil
- }
- // kubectl get nodes --selector microk8s.io/cluster=true for MicroK8s
- if label == "microk8s.io/cluster=true" {
- return DistroIsMicroK8s, nil
- }
- }
-
- if node.GetName() == "docker-desktop" {
- return DistroIsDockerDesktop, nil
- }
-
- for _, images := range node.Status.Images {
- for _, image := range images.Names {
- if rke2Regex.MatchString(image) {
- return DistroIsRKE2, nil
- }
- if tkgRegex.MatchString(image) {
- return DistroIsTKG, nil
- }
- }
- }
-
- namespaces, err := k.GetNamespaces(ctx)
- if err != nil {
- return DistroIsUnknown, errors.New("error getting namespace list")
- }
-
- // kubectl get ns eksa-system for EKS Anywhere
- for _, namespace := range namespaces.Items {
- if namespace.Name == "eksa-system" {
- return DistroIsEKSAnywhere, nil
- }
- }
-
- return DistroIsUnknown, nil
-}
-
// GetArchitectures returns the cluster system architectures if found.
func (k *K8s) GetArchitectures(ctx context.Context) ([]string, error) {
nodes, err := k.GetNodes(ctx)