diff --git a/go.mod b/go.mod index 1275140145..137284437c 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ go 1.21.8 replace github.com/xeipuuv/gojsonschema => github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6 require ( - cuelang.org/go v0.7.0 github.com/AlecAivazis/survey/v2 v2.3.7 github.com/Masterminds/semver/v3 v3.2.1 github.com/agnivade/levenshtein v1.1.1 @@ -62,6 +61,8 @@ require ( sigs.k8s.io/yaml v1.4.0 ) +require cuelang.org/go v0.7.0 // indirect + require ( atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect diff --git a/src/internal/packager/helm/post-render.go b/src/internal/packager/helm/post-render.go index da71335201..f4789fdcca 100644 --- a/src/internal/packager/helm/post-render.go +++ b/src/internal/packager/helm/post-render.go @@ -11,6 +11,7 @@ import ( "os" "path/filepath" "reflect" + "slices" "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config" @@ -119,12 +120,15 @@ func (r *renderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) { func (r *renderer) adoptAndUpdateNamespaces(ctx context.Context) error { c := r.cluster - existingNamespaces, _ := c.GetNamespaces(ctx) + namespaceList, err := r.cluster.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) + if err != nil { + return err + } for name, namespace := range r.namespaces { // Check to see if this namespace already exists var existingNamespace bool - for _, serverNamespace := range existingNamespaces.Items { + for _, serverNamespace := range namespaceList.Items { if serverNamespace.Name == name { existingNamespace = true } @@ -132,16 +136,19 @@ func (r *renderer) adoptAndUpdateNamespaces(ctx context.Context) error { if !existingNamespace { // This is a new namespace, add it - if _, err := c.CreateNamespace(ctx, namespace); err != nil { + _, err := c.Clientset.CoreV1().Namespaces().Create(ctx, namespace, metav1.CreateOptions{}) + if err != nil { return fmt.Errorf("unable to create the missing namespace %s", name) } } else if r.cfg.DeployOpts.AdoptExistingResources { - if r.cluster.IsInitialNamespace(name) { - // If this is a K8s initial namespace, refuse to adopt it + // Refuse to adopt namespace if it is one of four initial Kubernetes namespaces. + // https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#initial-namespaces + if slices.Contains([]string{"default", "kube-node-lease", "kube-public", "kube-system"}, name) { message.Warnf("Refusing to adopt the initial namespace: %s", name) } else { // This is an existing namespace to adopt - if _, err := c.UpdateNamespace(ctx, namespace); err != nil { + _, err := c.Clientset.CoreV1().Namespaces().Update(ctx, namespace, metav1.UpdateOptions{}) + if err != nil { return fmt.Errorf("unable to adopt the existing namespace %s", name) } } diff --git a/src/pkg/cluster/namespace.go b/src/pkg/cluster/namespace.go index 56a8566834..f7e776146d 100644 --- a/src/pkg/cluster/namespace.go +++ b/src/pkg/cluster/namespace.go @@ -6,10 +6,12 @@ package cluster import ( "context" + "time" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -18,7 +20,30 @@ func (c *Cluster) DeleteZarfNamespace(ctx context.Context) error { spinner := message.NewProgressSpinner("Deleting the zarf namespace from this cluster") defer spinner.Stop() - return c.DeleteNamespace(ctx, ZarfNamespaceName) + err := c.Clientset.CoreV1().Namespaces().Delete(ctx, ZarfNamespaceName, metav1.DeleteOptions{}) + if kerrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + timer := time.NewTimer(0) + defer timer.Stop() + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + _, err := c.Clientset.CoreV1().Namespaces().Get(ctx, ZarfNamespaceName, metav1.GetOptions{}) + if kerrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + timer.Reset(1 * time.Second) + } + } } // NewZarfManagedNamespace returns a corev1.Namespace with Zarf-managed labels diff --git a/src/pkg/cluster/secrets.go b/src/pkg/cluster/secrets.go index 8bd604250c..a7190c8037 100644 --- a/src/pkg/cluster/secrets.go +++ b/src/pkg/cluster/secrets.go @@ -11,6 +11,7 @@ import ( "reflect" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/k8s" @@ -80,11 +81,12 @@ func (c *Cluster) UpdateZarfManagedImageSecrets(ctx context.Context, state *type spinner := message.NewProgressSpinner("Updating existing Zarf-managed image secrets") defer spinner.Stop() - if namespaces, err := c.GetNamespaces(ctx); err != nil { + 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 namespaces.Items { + for _, namespace := range namespaceList.Items { currentRegistrySecret, err := c.GetSecret(ctx, namespace.Name, config.ZarfImagePullSecretName) if err != nil { continue @@ -115,11 +117,12 @@ func (c *Cluster) UpdateZarfManagedGitSecrets(ctx context.Context, state *types. spinner := message.NewProgressSpinner("Updating existing Zarf-managed git secrets") defer spinner.Stop() - if namespaces, err := c.GetNamespaces(ctx); err != nil { + 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 namespaces.Items { + for _, namespace := range namespaceList.Items { currentGitSecret, err := c.GetSecret(ctx, namespace.Name, config.ZarfGitServerSecretName) if err != nil { continue diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index fbcccf7989..03ac17e32c 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -87,16 +87,12 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO // Setup zarf agent PKI state.AgentTLS = pki.GeneratePKI(config.ZarfAgentHost) - namespaces, err := c.GetNamespaces(ctx) + namespaceList, err := c.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) if err != nil { return fmt.Errorf("unable to get the Kubernetes namespaces: %w", err) } // 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 - } + for _, namespace := range namespaceList.Items { 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 @@ -105,7 +101,8 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO // This label will tell the Zarf Agent to ignore this namespace. namespace.Labels[k8s.AgentLabel] = "ignore" namespaceCopy := namespace - if _, err = c.UpdateNamespace(ctx, &namespaceCopy); err != nil { + _, err := c.Clientset.CoreV1().Namespaces().Update(ctx, &namespaceCopy, metav1.UpdateOptions{}) + if err != nil { // This is not a hard failure, but we should log it. message.WarnErrf(err, "Unable to mark the namespace %s as ignored by Zarf Agent", namespace.Name) } diff --git a/src/pkg/cluster/zarf.go b/src/pkg/cluster/zarf.go index e37810269c..921268e6a6 100644 --- a/src/pkg/cluster/zarf.go +++ b/src/pkg/cluster/zarf.go @@ -72,15 +72,17 @@ func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces(ctx context.Context) { LabelSelector: k8s.ZarfManagedByLabel + "=zarf", } - if namespaces, err := c.GetNamespaces(ctx); err != nil { + namespaceList, err := c.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) + if err != nil { spinner.Errorf(err, "Unable to get k8s namespaces") } else { - for _, namespace := range namespaces.Items { + for _, namespace := range namespaceList.Items { if _, ok := namespace.Labels[k8s.AgentLabel]; ok { spinner.Updatef("Removing Zarf Agent label for namespace %s", namespace.Name) delete(namespace.Labels, k8s.AgentLabel) namespaceCopy := namespace - if _, err = c.UpdateNamespace(ctx, &namespaceCopy); err != nil { + _, err := c.Clientset.CoreV1().Namespaces().Update(ctx, &namespaceCopy, metav1.UpdateOptions{}) + if err != nil { // This is not a hard failure, but we should log it spinner.Errorf(err, "Unable to update the namespace labels for %s", namespace.Name) } diff --git a/src/pkg/k8s/namespace.go b/src/pkg/k8s/namespace.go deleted file mode 100644 index 2f1d43d019..0000000000 --- a/src/pkg/k8s/namespace.go +++ /dev/null @@ -1,79 +0,0 @@ -// 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 k8s - -import ( - "context" - "time" - - "cuelang.org/go/pkg/strings" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// GetNamespaces returns a list of namespaces in the cluster. -func (k *K8s) GetNamespaces(ctx context.Context) (*corev1.NamespaceList, error) { - metaOptions := metav1.ListOptions{} - return k.Clientset.CoreV1().Namespaces().List(ctx, metaOptions) -} - -// UpdateNamespace updates the given namespace in the cluster. -func (k *K8s) UpdateNamespace(ctx context.Context, namespace *corev1.Namespace) (*corev1.Namespace, error) { - updateOptions := metav1.UpdateOptions{} - return k.Clientset.CoreV1().Namespaces().Update(ctx, namespace, updateOptions) -} - -// CreateNamespace creates the given namespace or returns it if it already exists in the cluster. -func (k *K8s) CreateNamespace(ctx context.Context, namespace *corev1.Namespace) (*corev1.Namespace, error) { - metaOptions := metav1.GetOptions{} - createOptions := metav1.CreateOptions{} - - match, err := k.Clientset.CoreV1().Namespaces().Get(ctx, namespace.Name, metaOptions) - - if err != nil || match.Name != namespace.Name { - return k.Clientset.CoreV1().Namespaces().Create(ctx, namespace, createOptions) - } - - return match, err -} - -// DeleteNamespace deletes the given namespace from the cluster. -func (k *K8s) DeleteNamespace(ctx context.Context, name string) error { - // Attempt to delete the namespace immediately - gracePeriod := int64(0) - err := k.Clientset.CoreV1().Namespaces().Delete(ctx, name, metav1.DeleteOptions{GracePeriodSeconds: &gracePeriod}) - if err != nil && !errors.IsNotFound(err) { - return err - } - - timer := time.NewTimer(0) - defer timer.Stop() - - for { - select { - case <-ctx.Done(): - return ctx.Err() - case <-timer.C: - _, err := k.Clientset.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{}) - if errors.IsNotFound(err) { - return nil - } - - timer.Reset(1 * time.Second) - } - } -} - -// IsInitialNamespace returns true if the given namespace name is an initial k8s namespace: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#initial-namespaces -func (k *K8s) IsInitialNamespace(name string) bool { - if name == "default" { - return true - } else if strings.HasPrefix(name, "kube-") { - return true - } - - return false -} diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index 2805c96620..b7268bb8ca 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -17,7 +17,12 @@ import ( "sync" "time" + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + 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/internal/packager/git" @@ -32,7 +37,6 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/packager/filters" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/types" - corev1 "k8s.io/api/core/v1" ) func (p *Packager) resetRegistryHPA(ctx context.Context) { @@ -451,7 +455,21 @@ func (p *Packager) setupState(ctx context.Context) (err error) { // Try to create the zarf namespace spinner.Updatef("Creating the Zarf namespace") zarfNamespace := cluster.NewZarfManagedNamespace(cluster.ZarfNamespaceName) - if _, err := p.cluster.CreateNamespace(ctx, zarfNamespace); err != nil { + err := func() error { + _, err := p.cluster.Clientset.CoreV1().Namespaces().Create(ctx, zarfNamespace, metav1.CreateOptions{}) + if err != nil && !kerrors.IsAlreadyExists(err) { + return err + } + if err == nil { + return nil + } + _, err = p.cluster.Clientset.CoreV1().Namespaces().Update(ctx, zarfNamespace, metav1.UpdateOptions{}) + if err != nil { + return err + } + return nil + }() + if err != nil { spinner.Fatalf(err, "Unable to create the zarf namespace") } }