diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 93759bdb25..b77cc86c1c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,7 +33,7 @@ repos: entry: golangci-lint run --enable-only goimports --fix types: [go] language: golang - pass_filenames: true + pass_filenames: false - id: lint name: golangci-lint go lint entry: golangci-lint run diff --git a/src/config/config.go b/src/config/config.go index 8aedce6abb..f603e317a2 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -29,7 +29,6 @@ const ( ZarfConnectAnnotationDescription = "zarf.dev/connect-description" ZarfConnectAnnotationURL = "zarf.dev/connect-url" - ZarfManagedByLabel = "app.kubernetes.io/managed-by" ZarfCleanupScriptsPath = "/opt/zarf" ZarfPackagePrefix = "zarf-package-" diff --git a/src/internal/packager/helm/post-render.go b/src/internal/packager/helm/post-render.go index 8b99cd14f0..4be00a1b11 100644 --- a/src/internal/packager/helm/post-render.go +++ b/src/internal/packager/helm/post-render.go @@ -14,6 +14,7 @@ import ( "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/types" @@ -21,6 +22,8 @@ import ( corev1 "k8s.io/api/core/v1" "sigs.k8s.io/yaml" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" ) @@ -34,16 +37,27 @@ type renderer struct { func (h *Helm) newRenderer() (*renderer, error) { message.Debugf("helm.NewRenderer()") - namespaces := make(map[string]*corev1.Namespace) - if h.cluster != nil { - namespaces[h.chart.Namespace] = h.cluster.NewZarfManagedNamespace(h.chart.Namespace) + rend := &renderer{ + Helm: h, + connectStrings: types.ConnectStrings{}, + namespaces: map[string]*corev1.Namespace{}, + } + if h.cluster == nil { + return rend, nil } - return &renderer{ - Helm: h, - connectStrings: make(types.ConnectStrings), - namespaces: namespaces, - }, nil + namespace, err := h.cluster.Clientset.CoreV1().Namespaces().Get(context.TODO(), h.chart.Namespace, metav1.GetOptions{}) + if err != nil && !kerrors.IsNotFound(err) { + return nil, fmt.Errorf("unable to check for existing namespace %q in cluster: %w", h.chart.Namespace, err) + } + if kerrors.IsNotFound(err) { + rend.namespaces[h.chart.Namespace] = cluster.NewZarfManagedNamespace(h.chart.Namespace) + } else if h.cfg.DeployOpts.AdoptExistingResources { + namespace.Labels = cluster.AdoptZarfManagedLabels(namespace.Labels) + rend.namespaces[h.chart.Namespace] = namespace + } + + return rend, nil } func (r *renderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) { @@ -169,22 +183,15 @@ func (r *renderer) editHelmResources(ctx context.Context, resources []releaseuti switch rawData.GetKind() { case "Namespace": - var namespace corev1.Namespace + namespace := &corev1.Namespace{} // parse the namespace resource so it can be applied out-of-band by zarf instead of helm to avoid helm ns shenanigans - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(rawData.UnstructuredContent(), &namespace); err != nil { + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(rawData.UnstructuredContent(), namespace); err != nil { message.WarnErrf(err, "could not parse namespace %s", rawData.GetName()) } else { message.Debugf("Matched helm namespace %s for zarf annotation", namespace.Name) - if namespace.Labels == nil { - // Ensure label map exists to avoid nil panic - namespace.Labels = make(map[string]string) - } - // Now track this namespace by zarf - namespace.Labels[config.ZarfManagedByLabel] = "zarf" - namespace.Labels["zarf-helm-release"] = r.chart.ReleaseName - + namespace.Labels = cluster.AdoptZarfManagedLabels(namespace.Labels) // Add it to the stack - r.namespaces[namespace.Name] = &namespace + r.namespaces[namespace.Name] = namespace } // skip so we can strip namespaces from helm's brain continue @@ -209,7 +216,7 @@ func (r *renderer) editHelmResources(ctx context.Context, resources []releaseuti namespace := rawData.GetNamespace() if _, exists := r.namespaces[namespace]; !exists && namespace != "" { // if this is the first time seeing this ns, we need to track that to create it as well - r.namespaces[namespace] = r.cluster.NewZarfManagedNamespace(namespace) + r.namespaces[namespace] = cluster.NewZarfManagedNamespace(namespace) } // If we have been asked to adopt existing resources, process those now as well diff --git a/src/pkg/cluster/common.go b/src/pkg/cluster/common.go index 8b2153999b..e0a295cd3c 100644 --- a/src/pkg/cluster/common.go +++ b/src/pkg/cluster/common.go @@ -8,7 +8,6 @@ import ( "context" "time" - "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" ) @@ -21,13 +20,8 @@ type Cluster struct { const ( // DefaultTimeout is the default time to wait for a cluster to be ready. DefaultTimeout = 30 * time.Second - agentLabel = "zarf.dev/agent" ) -var labels = k8s.Labels{ - config.ZarfManagedByLabel: "zarf", -} - // NewClusterWithWait creates a new Cluster instance and waits for the given timeout for the cluster to be ready. func NewClusterWithWait(ctx context.Context) (*Cluster, error) { spinner := message.NewProgressSpinner("Waiting for cluster connection") @@ -36,7 +30,7 @@ func NewClusterWithWait(ctx context.Context) (*Cluster, error) { c := &Cluster{} var err error - c.K8s, err = k8s.New(message.Debugf, labels) + c.K8s, err = k8s.New(message.Debugf) if err != nil { return nil, err } @@ -56,7 +50,7 @@ func NewCluster() (*Cluster, error) { c := &Cluster{} var err error - c.K8s, err = k8s.New(message.Debugf, labels) + c.K8s, err = k8s.New(message.Debugf) if err != nil { return nil, err } diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index 264a39648e..3abd010b8f 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -330,7 +330,7 @@ func (c *Cluster) buildInjectionPod(node, image string, payloadConfigmaps []stri pod.Labels["app"] = "zarf-injector" // Ensure zarf agent doesn't break the injector on future runs - pod.Labels[agentLabel] = "ignore" + pod.Labels[k8s.AgentLabel] = "ignore" // Bind the pod to the node the image was found on pod.Spec.NodeName = node diff --git a/src/pkg/cluster/namespace.go b/src/pkg/cluster/namespace.go index a7209936b3..56a8566834 100644 --- a/src/pkg/cluster/namespace.go +++ b/src/pkg/cluster/namespace.go @@ -7,7 +7,10 @@ package cluster import ( "context" + "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // DeleteZarfNamespace deletes the Zarf namespace from the connected cluster. @@ -17,3 +20,28 @@ func (c *Cluster) DeleteZarfNamespace(ctx context.Context) error { return c.DeleteNamespace(ctx, ZarfNamespaceName) } + +// NewZarfManagedNamespace returns a corev1.Namespace with Zarf-managed labels +func NewZarfManagedNamespace(name string) *corev1.Namespace { + namespace := &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Namespace", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + namespace.Labels = AdoptZarfManagedLabels(namespace.Labels) + return namespace +} + +// AdoptZarfManagedLabels adds & deletes the necessary labels that signal to Zarf it should manage a namespace +func AdoptZarfManagedLabels(labels map[string]string) map[string]string { + if labels == nil { + labels = make(map[string]string) + } + labels[k8s.ZarfManagedByLabel] = "zarf" + delete(labels, k8s.AgentLabel) + return labels +} diff --git a/src/pkg/cluster/secrets.go b/src/pkg/cluster/secrets.go index 17183d5e62..4b58d841d0 100644 --- a/src/pkg/cluster/secrets.go +++ b/src/pkg/cluster/secrets.go @@ -13,6 +13,7 @@ import ( corev1 "k8s.io/api/core/v1" "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" ) @@ -89,8 +90,8 @@ func (c *Cluster) UpdateZarfManagedImageSecrets(ctx context.Context, state *type } // Check if this is a Zarf managed secret or is in a namespace the Zarf agent will take action in - if currentRegistrySecret.Labels[config.ZarfManagedByLabel] == "zarf" || - (namespace.Labels[agentLabel] != "skip" && namespace.Labels[agentLabel] != "ignore") { + if currentRegistrySecret.Labels[k8s.ZarfManagedByLabel] == "zarf" || + (namespace.Labels[k8s.AgentLabel] != "skip" && namespace.Labels[k8s.AgentLabel] != "ignore") { spinner.Updatef("Updating existing Zarf-managed image secret for namespace: '%s'", namespace.Name) // Create the secret @@ -123,8 +124,8 @@ func (c *Cluster) UpdateZarfManagedGitSecrets(ctx context.Context, state *types. } // Check if this is a Zarf managed secret or is in a namespace the Zarf agent will take action in - if currentGitSecret.Labels[config.ZarfManagedByLabel] == "zarf" || - (namespace.Labels[agentLabel] != "skip" && namespace.Labels[agentLabel] != "ignore") { + if currentGitSecret.Labels[k8s.ZarfManagedByLabel] == "zarf" || + (namespace.Labels[k8s.AgentLabel] != "skip" && namespace.Labels[k8s.AgentLabel] != "ignore") { spinner.Updatef("Updating existing Zarf-managed git secret for namespace: '%s'", namespace.Name) // Create the secret diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index a2585c4361..2d51aa38d4 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -92,7 +92,7 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO namespace.Labels = make(map[string]string) } // This label will tell the Zarf Agent to ignore this namespace. - namespace.Labels[agentLabel] = "ignore" + namespace.Labels[k8s.AgentLabel] = "ignore" namespaceCopy := namespace if _, err = c.UpdateNamespace(ctx, &namespaceCopy); err != nil { // This is not a hard failure, but we should log it. @@ -102,7 +102,7 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO // Try to create the zarf namespace. spinner.Updatef("Creating the Zarf namespace") - zarfNamespace := c.NewZarfManagedNamespace(ZarfNamespaceName) + zarfNamespace := NewZarfManagedNamespace(ZarfNamespaceName) if _, err := c.CreateNamespace(ctx, zarfNamespace); err != nil { return fmt.Errorf("unable to create the zarf namespace: %w", err) } @@ -244,7 +244,7 @@ func (c *Cluster) SaveZarfState(ctx context.Context, state *types.ZarfState) err Name: ZarfStateSecretName, Namespace: ZarfNamespaceName, Labels: map[string]string{ - config.ZarfManagedByLabel: "zarf", + k8s.ZarfManagedByLabel: "zarf", }, }, Type: corev1.SecretTypeOpaque, diff --git a/src/pkg/cluster/zarf.go b/src/pkg/cluster/zarf.go index 3522dde6b5..4e558c6f67 100644 --- a/src/pkg/cluster/zarf.go +++ b/src/pkg/cluster/zarf.go @@ -12,6 +12,7 @@ import ( "time" "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" autoscalingV2 "k8s.io/api/autoscaling/v2" @@ -68,16 +69,16 @@ func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces(ctx context.Context) { deleteOptions := metav1.DeleteOptions{} listOptions := metav1.ListOptions{ - LabelSelector: config.ZarfManagedByLabel + "=zarf", + LabelSelector: k8s.ZarfManagedByLabel + "=zarf", } if namespaces, err := c.GetNamespaces(ctx); err != nil { spinner.Errorf(err, "Unable to get k8s namespaces") } else { for _, namespace := range namespaces.Items { - if _, ok := namespace.Labels[agentLabel]; ok { + if _, ok := namespace.Labels[k8s.AgentLabel]; ok { spinner.Updatef("Removing Zarf Agent label for namespace %s", namespace.Name) - delete(namespace.Labels, agentLabel) + delete(namespace.Labels, k8s.AgentLabel) namespaceCopy := namespace if _, err = c.UpdateNamespace(ctx, &namespaceCopy); err != nil { // This is not a hard failure, but we should log it diff --git a/src/pkg/k8s/common.go b/src/pkg/k8s/common.go index 44027f4492..66434f3caa 100644 --- a/src/pkg/k8s/common.go +++ b/src/pkg/k8s/common.go @@ -21,11 +21,15 @@ import ( "k8s.io/client-go/tools/clientcmd" ) -// cannot import config.ZarfManagedByLabel due to import cycle -const zarfManagedByLabel = "app.kubernetes.io/managed-by" +const ( + // ZarfManagedByLabel is used to denote Zarf manages the lifecycle of a resource + ZarfManagedByLabel = "app.kubernetes.io/managed-by" + // AgentLabel is used to give instructions to the Zarf agent + AgentLabel = "zarf.dev/agent" +) // New creates a new K8s client. -func New(logger Log, defaultLabels Labels) (*K8s, error) { +func New(logger Log) (*K8s, error) { klog.SetLogger(funcr.New(func(_, args string) { logger(args) }, funcr.Options{})) @@ -39,7 +43,6 @@ func New(logger Log, defaultLabels Labels) (*K8s, error) { RestConfig: config, Clientset: clientset, Log: logger, - Labels: defaultLabels, }, nil } diff --git a/src/pkg/k8s/namespace.go b/src/pkg/k8s/namespace.go index 3a63b1ac52..2f1d43d019 100644 --- a/src/pkg/k8s/namespace.go +++ b/src/pkg/k8s/namespace.go @@ -67,22 +67,6 @@ func (k *K8s) DeleteNamespace(ctx context.Context, name string) error { } } -// NewZarfManagedNamespace returns a corev1.Namespace with Zarf-managed labels -func (k *K8s) NewZarfManagedNamespace(name string) *corev1.Namespace { - return &corev1.Namespace{ - TypeMeta: metav1.TypeMeta{ - APIVersion: corev1.SchemeGroupVersion.String(), - Kind: "Namespace", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{ - zarfManagedByLabel: "zarf", - }, - }, - } -} - // 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" { diff --git a/src/pkg/k8s/secrets.go b/src/pkg/k8s/secrets.go index d391b97771..82b9ef52de 100644 --- a/src/pkg/k8s/secrets.go +++ b/src/pkg/k8s/secrets.go @@ -36,7 +36,7 @@ func (k *K8s) GenerateSecret(namespace, name string, secretType corev1.SecretTyp Name: name, Namespace: namespace, Labels: map[string]string{ - zarfManagedByLabel: "zarf", + ZarfManagedByLabel: "zarf", }, }, Type: secretType, diff --git a/src/pkg/k8s/types.go b/src/pkg/k8s/types.go index 2326edbc98..d52ea6389b 100644 --- a/src/pkg/k8s/types.go +++ b/src/pkg/k8s/types.go @@ -21,7 +21,6 @@ type K8s struct { Clientset kubernetes.Interface RestConfig *rest.Config Log Log - Labels Labels } // PodLookup is a struct for specifying a pod to target for data injection or lookups. diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index 039a63cb61..a7c516fb43 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -448,7 +448,7 @@ func (p *Packager) setupState(ctx context.Context) (err error) { // Try to create the zarf namespace spinner.Updatef("Creating the Zarf namespace") - zarfNamespace := p.cluster.NewZarfManagedNamespace(cluster.ZarfNamespaceName) + zarfNamespace := cluster.NewZarfManagedNamespace(cluster.ZarfNamespaceName) if _, err := p.cluster.CreateNamespace(ctx, zarfNamespace); err != nil { spinner.Fatalf(err, "Unable to create the zarf namespace") } diff --git a/src/test/e2e/25_helm_test.go b/src/test/e2e/25_helm_test.go index e09d6cf47f..7c643288c0 100644 --- a/src/test/e2e/25_helm_test.go +++ b/src/test/e2e/25_helm_test.go @@ -189,6 +189,13 @@ func testHelmAdoption(t *testing.T) { require.NoError(t, err) require.Contains(t, string(helmOut), "zarf-f53a99d4a4dd9a3575bedf59cd42d48d751ae866") + existingLabel, _, err := e2e.Kubectl("get", "ns", "dos-games", "-o=jsonpath={.metadata.labels.keep-this}") + require.Equal(t, "label", existingLabel) + require.NoError(t, err) + existingAnnotation, _, err := e2e.Kubectl("get", "ns", "dos-games", "-o=jsonpath={.metadata.annotations.keep-this}") + require.Equal(t, "annotation", existingAnnotation) + require.NoError(t, err) + // Remove the package. stdOut, stdErr, err = e2e.Zarf("package", "remove", "dos-games", "--confirm") require.NoError(t, err, stdOut, stdErr) diff --git a/src/test/packages/25-manifest-adoption/deployment.yaml b/src/test/packages/25-manifest-adoption/deployment.yaml index c5779b20f0..9cd8c5b754 100644 --- a/src/test/packages/25-manifest-adoption/deployment.yaml +++ b/src/test/packages/25-manifest-adoption/deployment.yaml @@ -2,6 +2,10 @@ apiVersion: v1 kind: Namespace metadata: name: dos-games + labels: + keep-this: label + annotations: + keep-this: annotation --- # This is a normal deployment manifest for dos-games that should be "adopted" by Helm/Zarf apiVersion: apps/v1