diff --git a/e2e/assets/single-cluster/namespaceLabels_targetCustomization.yaml b/e2e/assets/single-cluster/namespaceLabels_targetCustomization.yaml new file mode 100644 index 0000000000..f53ed41b38 --- /dev/null +++ b/e2e/assets/single-cluster/namespaceLabels_targetCustomization.yaml @@ -0,0 +1,10 @@ +kind: GitRepo +apiVersion: fleet.cattle.io/v1alpha1 +metadata: + name: test + namespace: fleet-local +spec: + repo: https://github.com/Tommy12789/fleet-examples-tommy + branch: test + paths: + - namespaceLabels_targetCustomization diff --git a/e2e/single-cluster/targetCustomization_test.go b/e2e/single-cluster/targetCustomization_test.go new file mode 100644 index 0000000000..80c69e2144 --- /dev/null +++ b/e2e/single-cluster/targetCustomization_test.go @@ -0,0 +1,78 @@ +package singlecluster_test + +import ( + "strings" + + "github.com/rancher/fleet/e2e/testenv" + "github.com/rancher/fleet/e2e/testenv/kubectl" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Helm deploy options", func() { + var ( + asset string + k kubectl.Command + ) + BeforeEach(func() { + k = env.Kubectl.Namespace(env.Namespace) + }) + + JustBeforeEach(func() { + out, err := k.Apply("-f", testenv.AssetPath(asset)) + Expect(err).ToNot(HaveOccurred(), out) + }) + + AfterEach(func() { + out, err := k.Delete("-f", testenv.AssetPath(asset)) + Expect(err).ToNot(HaveOccurred(), out) + }) + + Describe("namespaceLabels TargetCustomization", func() { + BeforeEach(func() { + asset = "single-cluster/namespaceLabels_targetCustomization.yaml" + }) + When("namespaceLabels set as targetCustomization ", func() { + It("deploy the bundledeployment with the merged namespaceLabels", func() { + Eventually(func() string { + bundleDeploymentNames, _ := k.Namespace("cluster-fleet-local-local-1a3d67d0a899").Get("bundledeployments", "-o", "jsonpath={.items[*].metadata.name}") + + var bundleDeploymentName string + for _, podName := range strings.Split(bundleDeploymentNames, " ") { + if strings.HasPrefix(podName, "test-namespacelabels-targetcustomization") { + bundleDeploymentName = podName + break + } + } + if bundleDeploymentName == "" { + return "nil" + } + + bundleDeploymentNamespacesLabels, _ := k.Namespace("cluster-fleet-local-local-1a3d67d0a899").Get("bundledeployments", bundleDeploymentName, "-o", "jsonpath={.spec.options.namespaceLabels}") + return bundleDeploymentNamespacesLabels + }).Should(Equal(`{"foo":"bar","this.is/a":"test"}`)) + + Eventually(func() string { + bundleDeploymentNames, _ := k.Namespace("cluster-fleet-local-local-1a3d67d0a899").Get("bundledeployments", "-o", "jsonpath={.items[*].metadata.name}") + + var bundleDeploymentName string + for _, podName := range strings.Split(bundleDeploymentNames, " ") { + if strings.HasPrefix(podName, "test-namespacelabels-targetcustomization") { + bundleDeploymentName = podName + break + } + } + if bundleDeploymentName == "" { + return "nil" + } + + bundleDeploymentNamespacesLabels, _ := k.Namespace("cluster-fleet-local-local-1a3d67d0a899").Get("bundledeployments", bundleDeploymentName, "-o", "jsonpath={.spec.options.namespaceAnnotations}") + return bundleDeploymentNamespacesLabels + }).Should(Equal(`{"foo":"bar","this.is/a":"test"}`)) + + }) + }) + }) + +}) diff --git a/internal/cmd/controller/target/target.go b/internal/cmd/controller/target/target.go index 9025f2c1da..bc320086dc 100644 --- a/internal/cmd/controller/target/target.go +++ b/internal/cmd/controller/target/target.go @@ -58,6 +58,22 @@ func (t *Target) BundleDeployment() *fleet.BundleDeployment { Spec: t.Deployment.Spec, } bd.Spec.Paused = t.IsPaused() + for _, bundleTarget := range t.Bundle.Spec.Targets { + if bundleTarget.NamespaceLabels != nil { + for key, value := range *bundleTarget.NamespaceLabels { + (*bd.Spec.Options.NamespaceLabels)[key] = value + (*bd.Spec.StagedOptions.NamespaceLabels)[key] = value + } + } + } + for _, bundleTargets := range t.Bundle.Spec.Targets { + if bundleTargets.NamespaceAnnotations != nil { + for key, value := range *bundleTargets.NamespaceAnnotations { + (*bd.Spec.Options.NamespaceAnnotations)[key] = value + (*bd.Spec.StagedOptions.NamespaceAnnotations)[key] = value + } + } + } bd.Spec.DependsOn = t.Bundle.Spec.DependsOn bd.Spec.CorrectDrift = t.Options.CorrectDrift return bd diff --git a/pkg/apis/fleet.cattle.io/v1alpha1/bundle_types.go b/pkg/apis/fleet.cattle.io/v1alpha1/bundle_types.go index 65e2d9da09..a3431fbd37 100644 --- a/pkg/apis/fleet.cattle.io/v1alpha1/bundle_types.go +++ b/pkg/apis/fleet.cattle.io/v1alpha1/bundle_types.go @@ -227,6 +227,12 @@ type BundleTarget struct { ClusterGroupSelector *metav1.LabelSelector `json:"clusterGroupSelector,omitempty"` // DoNotDeploy if set to true, will not deploy to this target. DoNotDeploy bool `json:"doNotDeploy,omitempty"` + // NamespaceLabels are labels that will be appended to the namespace created by Fleet. + // +nullable + NamespaceLabels *map[string]string `json:"namespaceLabels,omitempty"` + // NamespaceAnnotations are annotations that will be appended to the namespace created by Fleet. + // +nullable + NamespaceAnnotations *map[string]string `json:"namespaceAnnotations,omitempty"` } // BundleSummary contains the number of bundle deployments in each state and a diff --git a/pkg/apis/fleet.cattle.io/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/fleet.cattle.io/v1alpha1/zz_generated.deepcopy.go index fe60d80726..8ae8fbfb5a 100644 --- a/pkg/apis/fleet.cattle.io/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/fleet.cattle.io/v1alpha1/zz_generated.deepcopy.go @@ -579,6 +579,28 @@ func (in *BundleTarget) DeepCopyInto(out *BundleTarget) { *out = new(v1.LabelSelector) (*in).DeepCopyInto(*out) } + if in.NamespaceLabels != nil { + in, out := &in.NamespaceLabels, &out.NamespaceLabels + *out = new(map[string]string) + if **in != nil { + in, out := *in, *out + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + } + if in.NamespaceAnnotations != nil { + in, out := &in.NamespaceAnnotations, &out.NamespaceAnnotations + *out = new(map[string]string) + if **in != nil { + in, out := *in, *out + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BundleTarget.