diff --git a/src/cmd/tools/zarf.go b/src/cmd/tools/zarf.go index 7c6ba91e88..39c622c714 100644 --- a/src/cmd/tools/zarf.go +++ b/src/cmd/tools/zarf.go @@ -141,10 +141,16 @@ var updateCredsCmd = &cobra.Command{ if confirm { // Update registry and git pull secrets if slices.Contains(args, message.RegistryKey) { - c.UpdateZarfManagedImageSecrets(ctx, newState) + err := c.UpdateZarfManagedImageSecrets(ctx, newState) + if err != nil { + return err + } } if slices.Contains(args, message.GitKey) { - c.UpdateZarfManagedGitSecrets(ctx, newState) + err := c.UpdateZarfManagedGitSecrets(ctx, newState) + if err != nil { + return err + } } // Update artifact token (if internal) diff --git a/src/pkg/cluster/secrets.go b/src/pkg/cluster/secrets.go index 43c3402b64..aa693c6a44 100644 --- a/src/pkg/cluster/secrets.go +++ b/src/pkg/cluster/secrets.go @@ -13,6 +13,7 @@ import ( "maps" corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/zarf-dev/zarf/src/config" @@ -112,78 +113,79 @@ func (c *Cluster) GenerateGitPullCreds(namespace, name string, gitServerInfo typ } // UpdateZarfManagedImageSecrets updates all Zarf-managed image secrets in all namespaces based on state -// TODO: Refactor to return errors properly. -func (c *Cluster) UpdateZarfManagedImageSecrets(ctx context.Context, state *types.ZarfState) { +func (c *Cluster) UpdateZarfManagedImageSecrets(ctx context.Context, state *types.ZarfState) error { spinner := message.NewProgressSpinner("Updating existing Zarf-managed image secrets") defer spinner.Stop() namespaceList, err := c.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) if err != nil { - spinner.Errorf(err, "Unable to get k8s namespaces") - } else { - // Update all image pull secrets - for _, namespace := range namespaceList.Items { - currentRegistrySecret, err := c.Clientset.CoreV1().Secrets(namespace.Name).Get(ctx, config.ZarfImagePullSecretName, metav1.GetOptions{}) - if err != nil { - continue - } - - // Check if this is a Zarf managed secret or is in a namespace the Zarf agent will take action in - if currentRegistrySecret.Labels[ZarfManagedByLabel] == "zarf" || - (namespace.Labels[AgentLabel] != "skip" && namespace.Labels[AgentLabel] != "ignore") { - spinner.Updatef("Updating existing Zarf-managed image secret for namespace: '%s'", namespace.Name) - - newRegistrySecret, err := c.GenerateRegistryPullCreds(ctx, namespace.Name, config.ZarfImagePullSecretName, state.RegistryInfo) - if err != nil { - message.WarnErrf(err, "Unable to generate registry creds") - continue - } - if !maps.EqualFunc(currentRegistrySecret.Data, newRegistrySecret.Data, func(v1, v2 []byte) bool { return bytes.Equal(v1, v2) }) { - _, err := c.Clientset.CoreV1().Secrets(newRegistrySecret.Namespace).Update(ctx, newRegistrySecret, metav1.UpdateOptions{}) - if err != nil { - message.WarnErrf(err, "Problem creating registry secret for the %s namespace", namespace.Name) - } - } - } + return err + } + // Update all image pull secrets + for _, namespace := range namespaceList.Items { + currentRegistrySecret, err := c.Clientset.CoreV1().Secrets(namespace.Name).Get(ctx, config.ZarfImagePullSecretName, metav1.GetOptions{}) + if kerrors.IsNotFound(err) { + continue + } + if err != nil { + return err + } + // Skip if namespace is skipped and secret is not managed by Zarf. + if currentRegistrySecret.Labels[ZarfManagedByLabel] != "zarf" && (namespace.Labels[AgentLabel] == "skip" || namespace.Labels[AgentLabel] == "ignore") { + continue + } + newRegistrySecret, err := c.GenerateRegistryPullCreds(ctx, namespace.Name, config.ZarfImagePullSecretName, state.RegistryInfo) + if err != nil { + return err + } + if maps.EqualFunc(currentRegistrySecret.Data, newRegistrySecret.Data, func(v1, v2 []byte) bool { return bytes.Equal(v1, v2) }) { + continue + } + spinner.Updatef("Updating existing Zarf-managed image secret for namespace: '%s'", namespace.Name) + _, err = c.Clientset.CoreV1().Secrets(newRegistrySecret.Namespace).Update(ctx, newRegistrySecret, metav1.UpdateOptions{}) + if err != nil { + return err } - spinner.Success() } + + spinner.Success() + return nil } // UpdateZarfManagedGitSecrets updates all Zarf-managed git secrets in all namespaces based on state -// TODO: Refactor to return errors properly. -func (c *Cluster) UpdateZarfManagedGitSecrets(ctx context.Context, state *types.ZarfState) { +func (c *Cluster) UpdateZarfManagedGitSecrets(ctx context.Context, state *types.ZarfState) error { spinner := message.NewProgressSpinner("Updating existing Zarf-managed git secrets") defer spinner.Stop() namespaceList, err := c.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) if err != nil { - spinner.Errorf(err, "Unable to get k8s namespaces") - } else { - // Update all git pull secrets - for _, namespace := range namespaceList.Items { - currentGitSecret, err := c.Clientset.CoreV1().Secrets(namespace.Name).Get(ctx, config.ZarfGitServerSecretName, metav1.GetOptions{}) - if err != nil { - continue - } - - // Check if this is a Zarf managed secret or is in a namespace the Zarf agent will take action in - if currentGitSecret.Labels[ZarfManagedByLabel] == "zarf" || - (namespace.Labels[AgentLabel] != "skip" && namespace.Labels[AgentLabel] != "ignore") { - spinner.Updatef("Updating existing Zarf-managed git secret for namespace: '%s'", namespace.Name) - - // Create the secret - newGitSecret := c.GenerateGitPullCreds(namespace.Name, config.ZarfGitServerSecretName, state.GitServer) - if !maps.Equal(currentGitSecret.StringData, newGitSecret.StringData) { - _, err := c.Clientset.CoreV1().Secrets(newGitSecret.Namespace).Update(ctx, newGitSecret, metav1.UpdateOptions{}) - if err != nil { - message.WarnErrf(err, "Problem creating git server secret for the %s namespace", namespace.Name) - } - } - } + return err + } + for _, namespace := range namespaceList.Items { + currentGitSecret, err := c.Clientset.CoreV1().Secrets(namespace.Name).Get(ctx, config.ZarfGitServerSecretName, metav1.GetOptions{}) + if kerrors.IsNotFound(err) { + continue + } + if err != nil { + continue + } + // Skip if namespace is skipped and secret is not managed by Zarf. + if currentGitSecret.Labels[ZarfManagedByLabel] != "zarf" && (namespace.Labels[AgentLabel] == "skip" || namespace.Labels[AgentLabel] == "ignore") { + continue + } + newGitSecret := c.GenerateGitPullCreds(namespace.Name, config.ZarfGitServerSecretName, state.GitServer) + if maps.Equal(currentGitSecret.StringData, newGitSecret.StringData) { + continue + } + spinner.Updatef("Updating existing Zarf-managed git secret for namespace: %s", namespace.Name) + _, err = c.Clientset.CoreV1().Secrets(newGitSecret.Namespace).Update(ctx, newGitSecret, metav1.UpdateOptions{}) + if err != nil { + return err } - spinner.Success() } + + spinner.Success() + return nil } // GetServiceInfoFromRegistryAddress gets the service info for a registry address if it is a NodePort diff --git a/src/pkg/cluster/secrets_test.go b/src/pkg/cluster/secrets_test.go index 80cd33b933..0ee731dfe9 100644 --- a/src/pkg/cluster/secrets_test.go +++ b/src/pkg/cluster/secrets_test.go @@ -4,7 +4,6 @@ package cluster import ( - "context" "testing" "github.com/stretchr/testify/require" @@ -12,114 +11,197 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" + "github.com/zarf-dev/zarf/src/config" + "github.com/zarf-dev/zarf/src/test/testutil" "github.com/zarf-dev/zarf/src/types" ) -func TestGenerateRegistryPullCredsWithOutSvc(t *testing.T) { - c := &Cluster{Clientset: fake.NewSimpleClientset()} - ctx := context.Background() - ri := types.RegistryInfo{ - PullUsername: "pull-user", - PullPassword: "pull-password", - Address: "example.com", - } - secret, err := c.GenerateRegistryPullCreds(ctx, "foo", "bar", ri) - require.NoError(t, err) - expectedSecret := corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "bar", - Namespace: "foo", - Labels: map[string]string{ - ZarfManagedByLabel: "zarf", - }, - }, - Type: corev1.SecretTypeDockerConfigJson, - Data: map[string][]byte{ - ".dockerconfigjson": []byte(`{"auths":{"example.com":{"auth":"cHVsbC11c2VyOnB1bGwtcGFzc3dvcmQ="}}}`), - }, - } - require.Equal(t, expectedSecret, *secret) -} +func TestUpdateZarfManagedSecrets(t *testing.T) { + ctx := testutil.TestContext(t) -func TestGenerateRegistryPullCredsWithSvc(t *testing.T) { - c := &Cluster{Clientset: fake.NewSimpleClientset()} - ctx := context.Background() - svc := &corev1.Service{ - ObjectMeta: metav1.ObjectMeta{ - Name: "good-service", - Namespace: "whatever", + tests := []struct { + name string + namespaceLabels map[string]string + secretLabels map[string]string + updatedImageSecret bool + updatedGitSecret bool + }{ + { + name: "modify", + updatedImageSecret: true, + updatedGitSecret: true, }, - Spec: corev1.ServiceSpec{ - Type: corev1.ServiceTypeNodePort, - Ports: []corev1.ServicePort{ - { - NodePort: 30001, - Port: 3333, - }, + { + name: "skip namespace", + namespaceLabels: map[string]string{ + AgentLabel: "skip", }, - ClusterIP: "10.11.12.13", }, - } - - _, err := c.Clientset.CoreV1().Services("whatever").Create(ctx, svc, metav1.CreateOptions{}) - require.NoError(t, err) - - ri := types.RegistryInfo{ - PullUsername: "pull-user", - PullPassword: "pull-password", - Address: "127.0.0.1:30001", - } - secret, err := c.GenerateRegistryPullCreds(ctx, "foo", "bar", ri) - require.NoError(t, err) - expectedSecret := corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Secret", + { + name: "ignore namespace", + namespaceLabels: map[string]string{ + AgentLabel: "ignore", + }, }, - ObjectMeta: metav1.ObjectMeta{ - Name: "bar", - Namespace: "foo", - Labels: map[string]string{ + { + name: "skip namespace managed secret", + namespaceLabels: map[string]string{ + AgentLabel: "skip", + }, + secretLabels: map[string]string{ ZarfManagedByLabel: "zarf", }, + updatedImageSecret: true, + updatedGitSecret: true, }, - Type: corev1.SecretTypeDockerConfigJson, - Data: map[string][]byte{ - ".dockerconfigjson": []byte(`{"auths":{"10.11.12.13:3333":{"auth":"cHVsbC11c2VyOnB1bGwtcGFzc3dvcmQ="},"127.0.0.1:30001":{"auth":"cHVsbC11c2VyOnB1bGwtcGFzc3dvcmQ="}}}`), - }, - } - require.Equal(t, expectedSecret, *secret) -} - -func TestGenerateGitPullCreds(t *testing.T) { - c := &Cluster{} - gi := types.GitServerInfo{ - PullUsername: "pull-user", - PullPassword: "pull-password", - } - secret := c.GenerateGitPullCreds("foo", "bar", gi) - expectedSecret := corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "bar", - Namespace: "foo", - Labels: map[string]string{ + { + name: "ignore namespace managed secret", + namespaceLabels: map[string]string{ + AgentLabel: "ignore", + }, + secretLabels: map[string]string{ ZarfManagedByLabel: "zarf", }, + updatedImageSecret: true, + updatedGitSecret: true, }, - Type: corev1.SecretTypeOpaque, - Data: map[string][]byte{}, - StringData: map[string]string{ - "username": "pull-user", - "password": "pull-password", - }, } - require.Equal(t, expectedSecret, *secret) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Cluster{ + Clientset: fake.NewSimpleClientset(), + } + + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Labels: tt.namespaceLabels, + }, + } + _, err := c.Clientset.CoreV1().Namespaces().Create(ctx, namespace, metav1.CreateOptions{}) + require.NoError(t, err) + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "good-service", + Namespace: namespace.ObjectMeta.Name, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Ports: []corev1.ServicePort{ + { + NodePort: 30001, + Port: 3333, + }, + }, + ClusterIP: "10.11.12.13", + }, + } + _, err = c.Clientset.CoreV1().Services(namespace.ObjectMeta.Name).Create(ctx, svc, metav1.CreateOptions{}) + require.NoError(t, err) + imageSecret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: config.ZarfImagePullSecretName, + Namespace: namespace.ObjectMeta.Name, + Labels: tt.secretLabels, + }, + } + _, err = c.Clientset.CoreV1().Secrets(imageSecret.ObjectMeta.Namespace).Create(ctx, imageSecret, metav1.CreateOptions{}) + require.NoError(t, err) + gitSecret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: config.ZarfGitServerSecretName, + Namespace: namespace.ObjectMeta.Name, + Labels: tt.secretLabels, + }, + } + _, err = c.Clientset.CoreV1().Secrets(gitSecret.ObjectMeta.Namespace).Create(ctx, gitSecret, metav1.CreateOptions{}) + require.NoError(t, err) + + state := &types.ZarfState{ + GitServer: types.GitServerInfo{ + PullUsername: "pull-user", + PullPassword: "pull-password", + }, + RegistryInfo: types.RegistryInfo{ + PullUsername: "pull-user", + PullPassword: "pull-password", + Address: "127.0.0.1:30001", + }, + } + err = c.UpdateZarfManagedImageSecrets(ctx, state) + require.NoError(t, err) + err = c.UpdateZarfManagedGitSecrets(ctx, state) + require.NoError(t, err) + + // Make sure no new namespaces or secrets have been created. + namespaceList, err := c.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.Len(t, namespaceList.Items, 1) + for _, ns := range namespaceList.Items { + secretList, err := c.Clientset.CoreV1().Secrets(ns.ObjectMeta.Name).List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.Len(t, secretList.Items, 2) + } + + // Check image registry secret + updatedImageSecret, err := c.Clientset.CoreV1().Secrets(namespace.ObjectMeta.Name).Get(ctx, config.ZarfImagePullSecretName, metav1.GetOptions{}) + require.NoError(t, err) + expectedImageSecret := corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: config.ZarfImagePullSecretName, + Namespace: namespace.ObjectMeta.Name, + Labels: map[string]string{ + ZarfManagedByLabel: "zarf", + }, + }, + Type: corev1.SecretTypeDockerConfigJson, + Data: map[string][]byte{ + ".dockerconfigjson": []byte(`{"auths":{"10.11.12.13:3333":{"auth":"cHVsbC11c2VyOnB1bGwtcGFzc3dvcmQ="},"127.0.0.1:30001":{"auth":"cHVsbC11c2VyOnB1bGwtcGFzc3dvcmQ="}}}`), + }, + } + if !tt.updatedImageSecret { + expectedImageSecret = *imageSecret + } + require.Equal(t, expectedImageSecret, *updatedImageSecret) + + // Check git secret + updatedGitSecret, err := c.Clientset.CoreV1().Secrets(namespace.ObjectMeta.Name).Get(ctx, config.ZarfGitServerSecretName, metav1.GetOptions{}) + require.NoError(t, err) + expectedGitSecret := corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: config.ZarfGitServerSecretName, + Namespace: namespace.ObjectMeta.Name, + Labels: map[string]string{ + ZarfManagedByLabel: "zarf", + }, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{}, + StringData: map[string]string{ + "username": state.GitServer.PullUsername, + "password": state.GitServer.PullPassword, + }, + } + if !tt.updatedGitSecret { + expectedGitSecret = *gitSecret + } + require.Equal(t, expectedGitSecret, *updatedGitSecret) + }) + } }