From d2a6c8cdce8baad53802b14962909ed2a08fbb64 Mon Sep 17 00:00:00 2001 From: zhujian Date: Wed, 15 Oct 2025 16:49:35 +0800 Subject: [PATCH] Allow empty string for AgentInstallNamespace to support template namespaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change updates the AgentInstallNamespace field validation pattern to allow empty strings, enabling template-type addons to use the namespace defined in their addonTemplate. Key changes: - Updated validation pattern from ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ to ^([a-z0-9]([-a-z0-9]*[a-z0-9])?)?$ to allow empty strings - Added documentation clarifying behavior for template vs non-template addons - Regenerated CRD manifests - Added comprehensive integration tests for validation This allows users to explicitly set agentInstallNamespace to "" to use their template's namespace, while maintaining backward compatibility with the default "open-cluster-management-agent-addon" namespace. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Signed-off-by: zhujian --- ...agement.io_addondeploymentconfigs.crd.yaml | 8 +- addon/v1alpha1/types_addondeploymentconfig.go | 4 +- .../api/addondeploymentconfig_test.go | 95 +++++++++++++++++++ 3 files changed, 103 insertions(+), 4 deletions(-) diff --git a/addon/v1alpha1/0000_02_addon.open-cluster-management.io_addondeploymentconfigs.crd.yaml b/addon/v1alpha1/0000_02_addon.open-cluster-management.io_addondeploymentconfigs.crd.yaml index 1a39c3d3..e0c899b7 100644 --- a/addon/v1alpha1/0000_02_addon.open-cluster-management.io_addondeploymentconfigs.crd.yaml +++ b/addon/v1alpha1/0000_02_addon.open-cluster-management.io_addondeploymentconfigs.crd.yaml @@ -41,10 +41,12 @@ spec: properties: agentInstallNamespace: default: open-cluster-management-agent-addon - description: AgentInstallNamespace is the namespace where the add-on - agent should be installed on the managed cluster. + description: |- + AgentInstallNamespace is the namespace where the add-on agent should be installed on the managed cluster. + For template-type addons: set to empty string "" to use the namespace defined in the addonTemplate. + For non-template addons: defaults to "open-cluster-management-agent-addon" if not specified. maxLength: 63 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?)?$ type: string customizedVariables: description: |- diff --git a/addon/v1alpha1/types_addondeploymentconfig.go b/addon/v1alpha1/types_addondeploymentconfig.go index 1b39f89d..3fd3d013 100644 --- a/addon/v1alpha1/types_addondeploymentconfig.go +++ b/addon/v1alpha1/types_addondeploymentconfig.go @@ -54,10 +54,12 @@ type AddOnDeploymentConfigSpec struct { ProxyConfig ProxyConfig `json:"proxyConfig,omitempty"` // AgentInstallNamespace is the namespace where the add-on agent should be installed on the managed cluster. + // For template-type addons: set to empty string "" to use the namespace defined in the addonTemplate. + // For non-template addons: defaults to "open-cluster-management-agent-addon" if not specified. // +optional // +kubebuilder:default=open-cluster-management-agent-addon // +kubebuilder:validation:MaxLength=63 - // +kubebuilder:validation:Pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + // +kubebuilder:validation:Pattern=^([a-z0-9]([-a-z0-9]*[a-z0-9])?)?$ AgentInstallNamespace string `json:"agentInstallNamespace,omitempty"` // ResourceRequirements specify the resources required by add-on agents. diff --git a/test/integration/api/addondeploymentconfig_test.go b/test/integration/api/addondeploymentconfig_test.go index b22a0233..850230fa 100644 --- a/test/integration/api/addondeploymentconfig_test.go +++ b/test/integration/api/addondeploymentconfig_test.go @@ -159,4 +159,99 @@ var _ = ginkgo.Describe("AddOnDeploymentConfig API test", func() { ) gomega.Expect(errors.IsInvalid(err)).To(gomega.BeTrue()) }) + + ginkgo.It("Should create a AddOnDeploymentConfig with empty agentInstallNamespace", func() { + addOnDeploymentConfig := &addonv1alpha1.AddOnDeploymentConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: addOnDeploymentConfigName, + Namespace: testNamespace, + }, + Spec: addonv1alpha1.AddOnDeploymentConfigSpec{ + AgentInstallNamespace: "", + }, + } + + _, err := hubAddonClient.AddonV1alpha1().AddOnDeploymentConfigs(testNamespace).Create( + context.TODO(), + addOnDeploymentConfig, + metav1.CreateOptions{}, + ) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + }) + + ginkgo.It("Should create a AddOnDeploymentConfig with valid agentInstallNamespace", func() { + addOnDeploymentConfig := &addonv1alpha1.AddOnDeploymentConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: addOnDeploymentConfigName, + Namespace: testNamespace, + }, + Spec: addonv1alpha1.AddOnDeploymentConfigSpec{ + AgentInstallNamespace: "my-custom-namespace", + }, + } + + _, err := hubAddonClient.AddonV1alpha1().AddOnDeploymentConfigs(testNamespace).Create( + context.TODO(), + addOnDeploymentConfig, + metav1.CreateOptions{}, + ) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + }) + + ginkgo.It("Should not create a AddOnDeploymentConfig with invalid agentInstallNamespace (starts with hyphen)", func() { + addOnDeploymentConfig := &addonv1alpha1.AddOnDeploymentConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: addOnDeploymentConfigName, + Namespace: testNamespace, + }, + Spec: addonv1alpha1.AddOnDeploymentConfigSpec{ + AgentInstallNamespace: "-invalid", + }, + } + + _, err := hubAddonClient.AddonV1alpha1().AddOnDeploymentConfigs(testNamespace).Create( + context.TODO(), + addOnDeploymentConfig, + metav1.CreateOptions{}, + ) + gomega.Expect(errors.IsInvalid(err)).To(gomega.BeTrue()) + }) + + ginkgo.It("Should not create a AddOnDeploymentConfig with invalid agentInstallNamespace (contains uppercase)", func() { + addOnDeploymentConfig := &addonv1alpha1.AddOnDeploymentConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: addOnDeploymentConfigName, + Namespace: testNamespace, + }, + Spec: addonv1alpha1.AddOnDeploymentConfigSpec{ + AgentInstallNamespace: "Invalid-Namespace", + }, + } + + _, err := hubAddonClient.AddonV1alpha1().AddOnDeploymentConfigs(testNamespace).Create( + context.TODO(), + addOnDeploymentConfig, + metav1.CreateOptions{}, + ) + gomega.Expect(errors.IsInvalid(err)).To(gomega.BeTrue()) + }) + + ginkgo.It("Should not create a AddOnDeploymentConfig with agentInstallNamespace exceeding max length", func() { + addOnDeploymentConfig := &addonv1alpha1.AddOnDeploymentConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: addOnDeploymentConfigName, + Namespace: testNamespace, + }, + Spec: addonv1alpha1.AddOnDeploymentConfigSpec{ + AgentInstallNamespace: rand.String(64), // max is 63 + }, + } + + _, err := hubAddonClient.AddonV1alpha1().AddOnDeploymentConfigs(testNamespace).Create( + context.TODO(), + addOnDeploymentConfig, + metav1.CreateOptions{}, + ) + gomega.Expect(errors.IsInvalid(err)).To(gomega.BeTrue()) + }) })