From c15960f9c7ed35e84a0135549a5509252ee2104b Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Wed, 29 May 2024 15:32:36 +0200 Subject: [PATCH] test: zarf init state --- src/pkg/cluster/state.go | 25 ++++- src/pkg/cluster/state_test.go | 169 +++++++++++++++++++++++++++++++++- 2 files changed, 191 insertions(+), 3 deletions(-) diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index 83af027217..fbcccf7989 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -64,6 +64,9 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO if err != nil { return err } + if len(nodeList.Items) == 0 { + return fmt.Errorf("cannot init Zarf state in empty cluster") + } namespaceList, err := c.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) if err != nil { return err @@ -90,6 +93,10 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO } // Mark existing namespaces as ignored for the zarf agent to prevent mutating resources we don't own. for _, namespace := range namespaces.Items { + // Skip Zarf namespace if it already exists. + if namespace.Name == ZarfNamespaceName { + continue + } spinner.Updatef("Marking existing namespace %s as ignored by Zarf Agent", namespace.Name) if namespace.Labels == nil { // Ensure label map exists to avoid nil panic @@ -107,8 +114,22 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO // Try to create the zarf namespace. spinner.Updatef("Creating the Zarf namespace") zarfNamespace := NewZarfManagedNamespace(ZarfNamespaceName) - if _, err := c.CreateNamespace(ctx, zarfNamespace); err != nil { - return fmt.Errorf("unable to create the zarf namespace: %w", err) + err = func() error { + _, err := c.Clientset.CoreV1().Namespaces().Create(ctx, zarfNamespace, metav1.CreateOptions{}) + if err != nil && !kerrors.IsAlreadyExists(err) { + return fmt.Errorf("unable to create the Zarf namespace: %w", err) + } + if err == nil { + return nil + } + _, err = c.Clientset.CoreV1().Namespaces().Update(ctx, zarfNamespace, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("unable to update the Zarf namespace: %w", err) + } + return nil + }() + if err != nil { + return err } // Wait up to 2 minutes for the default service account to be created. diff --git a/src/pkg/cluster/state_test.go b/src/pkg/cluster/state_test.go index 72f10cd873..77418d1104 100644 --- a/src/pkg/cluster/state_test.go +++ b/src/pkg/cluster/state_test.go @@ -5,16 +5,183 @@ package cluster import ( + "context" "fmt" "testing" + "time" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" "github.com/defenseunicorns/pkg/helpers" + + "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/pki" "github.com/defenseunicorns/zarf/src/types" - "github.com/stretchr/testify/require" ) +func TestZarfInitState(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + initOpts types.ZarfInitOptions + nodes []corev1.Node + namespaces []corev1.Namespace + secrets []corev1.Secret + expectedErr string + }{ + { + name: "no nodes in cluster", + expectedErr: "cannot init Zarf state in empty cluster", + }, + { + name: "no namespaces exist", + initOpts: types.ZarfInitOptions{}, + nodes: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node", + }, + }, + }, + }, + { + name: "namespaces exists", + nodes: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node", + }, + }, + }, + namespaces: []corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "kube-system", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + }, + }, + }, + }, + { + name: "Zarf namespace exists", + nodes: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node", + }, + }, + }, + namespaces: []corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: ZarfNamespaceName, + }, + }, + }, + }, + { + name: "Zarf state exists", + nodes: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node", + }, + }, + }, + namespaces: []corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: ZarfNamespaceName, + }, + }, + }, + secrets: []corev1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: ZarfNamespaceName, + Name: ZarfStateSecretName, + }, + }, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ctx := context.Background() + cs := fake.NewSimpleClientset() + for _, node := range tt.nodes { + _, err := cs.CoreV1().Nodes().Create(ctx, &node, metav1.CreateOptions{}) + require.NoError(t, err) + } + for _, namespace := range tt.namespaces { + _, err := cs.CoreV1().Namespaces().Create(ctx, &namespace, metav1.CreateOptions{}) + require.NoError(t, err) + } + for _, secret := range tt.secrets { + _, err := cs.CoreV1().Secrets(secret.ObjectMeta.Namespace).Create(ctx, &secret, metav1.CreateOptions{}) + require.NoError(t, err) + } + c := &Cluster{ + &k8s.K8s{ + Clientset: cs, + Log: func(s string, a ...any) {}, + }, + } + + // Create default service account in Zarf namespace + go func() { + for { + time.Sleep(1 * time.Second) + ns, err := cs.CoreV1().Namespaces().Get(ctx, ZarfNamespaceName, metav1.GetOptions{}) + if err != nil { + continue + } + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + Name: "default", + }, + } + cs.CoreV1().ServiceAccounts(ns.Name).Create(ctx, sa, metav1.CreateOptions{}) + break + } + }() + + err := c.InitZarfState(ctx, tt.initOpts) + if tt.expectedErr != "" { + require.EqualError(t, err, tt.expectedErr) + return + } + require.NoError(t, err) + zarfNs, err := cs.CoreV1().Namespaces().Get(ctx, ZarfNamespaceName, metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, map[string]string{"app.kubernetes.io/managed-by": "zarf"}, zarfNs.Labels) + _, err = cs.CoreV1().Secrets(zarfNs.Name).Get(ctx, ZarfStateSecretName, metav1.GetOptions{}) + require.NoError(t, err) + for _, ns := range tt.namespaces { + if ns.Name == zarfNs.Name { + continue + } + ns, err := cs.CoreV1().Namespaces().Get(ctx, ns.Name, metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, map[string]string{k8s.AgentLabel: "ignore"}, ns.Labels) + } + }) + } +} + // TODO: Change password gen method to make testing possible. func TestMergeZarfStateRegistry(t *testing.T) { t.Parallel()