diff --git a/cli/k8s_client/k8s_client.go b/cli/k8s_client/k8s_client.go index 0ad8dfdcf..34b6f296f 100644 --- a/cli/k8s_client/k8s_client.go +++ b/cli/k8s_client/k8s_client.go @@ -1849,6 +1849,8 @@ func (k *KubeClient) PatchCSIDriverByLabel(label string, patchBytes []byte, patc return nil } + +// CheckNamespaceExists checks if the installation namespace already exists func (k *KubeClient) CheckNamespaceExists(namespace string) (bool, error) { if _, err := k.GetNamespace(namespace); err != nil { if statusErr, ok := err.(*apierrors.StatusError); ok && statusErr.Status().Reason == metav1.StatusReasonNotFound { @@ -1859,6 +1861,22 @@ func (k *KubeClient) CheckNamespaceExists(namespace string) (bool, error) { return true, nil } +// CheckNamespaceLabels checks if the installation namespace already has the required labels +func (k *KubeClient) CheckNamespaceLabels(namespace string, labels map[string]string) bool { + ns, err := k.GetNamespace(namespace) + if err != nil { + return false + } + + for key, value := range labels { + if ns.Labels[key] != value { + return false + } + } + return true +} + + // PatchNamespaceLabels patches the namespace with provided labels func (k *KubeClient) PatchNamespaceLabels(namespace string, labels map[string]string) error { patch, err := json.Marshal(map[string]interface{}{ diff --git a/cli/k8s_client/k8s_client_test.go b/cli/k8s_client/k8s_client_test.go index f2204d5bc..bf3fa8a29 100644 --- a/cli/k8s_client/k8s_client_test.go +++ b/cli/k8s_client/k8s_client_test.go @@ -971,6 +971,10 @@ func TestNamespaceOperations(t *testing.T) { assert.False(t, exists) assert.NoError(t, err) // Existence checks should not error + // Test CheckNamespaceLabels + hasLabel := suite.kubeClient.CheckNamespaceLabels("nonexistent", map[string]string{"kubernetes.io/metadata.name": "test-namespace"}) + assert.False(t, hasLabel) + // Test GetNamespace namespace, err := suite.kubeClient.GetNamespace("test-namespace") // Fake client may behave differently - accept either outcome diff --git a/cli/k8s_client/types.go b/cli/k8s_client/types.go index 3071a6c5a..9e6bb2942 100644 --- a/cli/k8s_client/types.go +++ b/cli/k8s_client/types.go @@ -100,6 +100,7 @@ type KubernetesClient interface { DeleteCSIDriver(name string) error PatchCSIDriverByLabel(label string, patchBytes []byte, patchType types.PatchType) error CheckNamespaceExists(namespace string) (bool, error) + CheckNamespaceLabels(namespace string, labels map[string]string) error PatchNamespaceLabels(namespace string, labels map[string]string) error PatchNamespace(namespace string, patchBytes []byte, patchType types.PatchType) error GetNamespace(namespace string) (*v1.Namespace, error) diff --git a/operator/controllers/orchestrator/installer/installer.go b/operator/controllers/orchestrator/installer/installer.go index d0c385ae3..45712bb72 100644 --- a/operator/controllers/orchestrator/installer/installer.go +++ b/operator/controllers/orchestrator/installer/installer.go @@ -1283,6 +1283,18 @@ func (i *Installer) createOrPatchTridentInstallationNamespace() error { } Log().WithField("namespace", i.namespace).Info("Created Trident installation namespace.") } else { + // Check if namespace is already labeled + hasLabels := i.client.CheckNamespaceLabels(i.namespace, map[string]string{ + commonconfig.PodSecurityStandardsEnforceLabel: commonconfig.PodSecurityStandardsEnforceProfile, + }) + + if hasLabels { + Log().WithField("namespace", i.namespace).Info("Trident installation namespace already has the correct labels.") + return nil + } else { + Log().WithField("namespace", i.namespace).Info("Namespace missing labels. Attempting to patch now.") + } + // Patch namespace err := i.client.PatchNamespaceLabels(i.namespace, map[string]string{ commonconfig.PodSecurityStandardsEnforceLabel: commonconfig.PodSecurityStandardsEnforceProfile, diff --git a/operator/controllers/orchestrator/installer/installer_test.go b/operator/controllers/orchestrator/installer/installer_test.go index 4bdd77fc8..92a6e8d83 100644 --- a/operator/controllers/orchestrator/installer/installer_test.go +++ b/operator/controllers/orchestrator/installer/installer_test.go @@ -1857,6 +1857,19 @@ func TestInstallOrPatchTrident(t *testing.T) { assert.Empty(t, acpVersion) }) + t.Run("namespace exists but missing labels", func(t *testing.T) { + // Setup: namespace exists BUT labels check fails + mockK8sClient.EXPECT().CheckNamespaceExists(installer.namespace).Return(true, nil) + mockK8sClient.EXPECT().CheckNamespaceLabels(installer.namespace, mock.Anything).Return(false) + + // Maybe we expect it to patch the labels? + mockK8sClient.EXPECT().PatchNamespaceLabels(installer.namespace, mock.Anything).Return(nil) + + // When we call the function, it should handle the missing labels + err := installer.createOrPatchTridentInstallationNamespace() + assert.NoError(t, err) + }) + t.Run("failure creating RBAC objects", func(t *testing.T) { // Mock setInstallationParams to succeed mockK8sClient.EXPECT().ServerVersion().Return(&version.Version{}).AnyTimes() @@ -3204,6 +3217,32 @@ func TestCreateOrPatchTridentInstallationNamespace(t *testing.T) { assert.Contains(t, err.Error(), "failed to create Trident installation namespace") }) + t.Run("failure checking if the namespace has labels", func(t *testing.T) { + // Mock CheckNamespaceExists to return true (namespace exists) + mockK8sClient.EXPECT().CheckNamespaceExists(installer.namespace).Return(true, nil) + + // Mock GetNamespace to return namespace WITHOUT the required labels + mockK8sClient.EXPECT().GetNamespace(installer.namespace).Return( + &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: installer.namespace, + Labels: map[string]string{}, // Empty labels + }, + }, + nil, + ) + + // Mock CheckNamespaceLabels to return false (labels missing) + mockK8sClient.EXPECT().CheckNamespaceLabels( + installer.namespace, + map[string]string{"kubernetes.io/metadata.name": installer.namespace}, + ).Return(false) + + err := installer.createOrPatchTridentInstallationNamespace() + assert.Error(t, err, "expected error when checking namespace labels fails") + assert.Contains(t, err.Error(), "Trident installation namespace does not have expected labels") + }) + t.Run("failure patching namespace labels", func(t *testing.T) { // Mock CheckNamespaceExists to return true (namespace exists) mockK8sClient.EXPECT().CheckNamespaceExists(installer.namespace).Return(true, nil)