Skip to content

Commit

Permalink
fix: adopt namespace metadata (#2494)
Browse files Browse the repository at this point in the history
## Description

Changes behavior to keep existing metadata on namespaces rather than
steamrolling them

## Related Issue

Fixes #2489 

## Checklist before merging

- [ ] Test, docs, adr added or updated as needed
- [ ] [Contributor Guide
Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow)
followed

---------

Co-authored-by: Lucas Rodriguez <lucas.rodriguez@defenseunicorns.com>
Co-authored-by: Philip Laine <philip.laine@gmail.com>
  • Loading branch information
3 people committed May 20, 2024
1 parent 5bf7e01 commit 41c0ad7
Show file tree
Hide file tree
Showing 16 changed files with 91 additions and 64 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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-"
Expand Down
47 changes: 27 additions & 20 deletions src/internal/packager/helm/post-render.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ 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"
"helm.sh/helm/v3/pkg/releaseutil"
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"
)
Expand All @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
10 changes: 2 additions & 8 deletions src/pkg/cluster/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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")
Expand All @@ -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
}
Expand All @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion src/pkg/cluster/injector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 28 additions & 0 deletions src/pkg/cluster/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
}
9 changes: 5 additions & 4 deletions src/pkg/cluster/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions src/pkg/cluster/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
}
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 4 additions & 3 deletions src/pkg/cluster/zarf.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
11 changes: 7 additions & 4 deletions src/pkg/k8s/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}))
Expand All @@ -39,7 +43,6 @@ func New(logger Log, defaultLabels Labels) (*K8s, error) {
RestConfig: config,
Clientset: clientset,
Log: logger,
Labels: defaultLabels,
}, nil
}

Expand Down
16 changes: 0 additions & 16 deletions src/pkg/k8s/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down
2 changes: 1 addition & 1 deletion src/pkg/k8s/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 0 additions & 1 deletion src/pkg/k8s/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion src/pkg/packager/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
7 changes: 7 additions & 0 deletions src/test/e2e/25_helm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions src/test/packages/25-manifest-adoption/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 41c0ad7

Please sign in to comment.