From 8714476d484f744027008cb5134153dae4dfa7c5 Mon Sep 17 00:00:00 2001 From: Gianluca Mardente Date: Wed, 1 Apr 2026 16:46:09 +0200 Subject: [PATCH] (feat) Make namespace creation optional When Sveltos deploys resources defined in PolicyRefs or KustomizationRefs, it automatically attempts to "Get" the target namespace and "Create" it if it is missing. While convenient, this behavior forces Sveltos to require cluster-wide get and create permissions for Namespaces. For many managed production environments, Sveltos might be granted limited RBAC. If a user is operating in a multi-tenant cluster where namespaces are pre-provisioned and Sveltos lacks cluster-level permissions, the deployment currently fails. Changes: - Introduced SkipNamespaceCreation to the PolicyRef and KustomizationRef structs. - When enabled, Sveltos bypasses the namespace check/creation logic and attempts to deploy resources directly. Default behavior remains unchanged (false) to ensure backward compatibility. --- api/v1beta1/spec.go | 20 ++++ api/v1beta1/zz_generated.deepcopy.go | 5 +- ...fig.projectsveltos.io_clusterprofiles.yaml | 20 ++++ ...g.projectsveltos.io_clusterpromotions.yaml | 40 +++++++ ...ig.projectsveltos.io_clustersummaries.yaml | 20 ++++ .../config.projectsveltos.io_profiles.yaml | 20 ++++ controllers/export_test.go | 4 + controllers/handlers_kustomize.go | 6 +- controllers/handlers_utils.go | 50 +++++---- controllers/handlers_utils_test.go | 53 +++++++--- manifest/manifest.yaml | 100 ++++++++++++++++++ 11 files changed, 297 insertions(+), 41 deletions(-) diff --git a/api/v1beta1/spec.go b/api/v1beta1/spec.go index 17ac56a0..faebd88c 100644 --- a/api/v1beta1/spec.go +++ b/api/v1beta1/spec.go @@ -549,6 +549,16 @@ type KustomizationRef struct { // +kubebuilder:validation:Minimum=1 // +optional Tier int32 `json:"tier,omitempty"` + + // SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + // for namespaced resources defined in this KustomizationRef. + // This field is ignored for cluster-scoped resources. + // By default, Sveltos attempts to get or create the target namespace if it does not exist. + // Setting this to true avoids those calls, which is necessary when Sveltos lacks + // permissions to manage namespaces at the cluster level. + // +kubebuilder:default:=false + // +optional + SkipNamespaceCreation bool `json:"skipNamespaceCreation,omitempty"` } // StopMatchingBehavior indicates what will happen when Cluster stops matching @@ -641,6 +651,16 @@ type PolicyRef struct { // +kubebuilder:validation:Minimum=1 // +optional Tier int32 `json:"tier,omitempty"` + + // SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + // for namespaced resources defined in this PolicyRef. + // This field is ignored for cluster-scoped resources. + // By default, Sveltos attempts to get or create the target namespace if it does not exist. + // Setting this to true avoids those calls, which is necessary when Sveltos lacks + // permissions to manage namespaces at the cluster level. + // +kubebuilder:default:=false + // +optional + SkipNamespaceCreation bool `json:"skipNamespaceCreation,omitempty"` } type Clusters struct { diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 17bbd59d..3504036e 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -21,11 +21,12 @@ limitations under the License. package v1beta1 import ( - apiv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" + + apiv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml b/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml index ad586f21..6dfa1a44 100644 --- a/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml +++ b/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml @@ -707,6 +707,16 @@ spec: When expressed as templates, the values are filled in using information from resources within the management cluster before deployment (Cluster) type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this KustomizationRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean targetNamespace: description: |- TargetNamespace sets or overrides the namespace in the @@ -991,6 +1001,16 @@ spec: Defaults to 'None', which translates to the root path of the SourceRef. Used only for GitRepository;OCIRepository;Bucket type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this PolicyRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean tier: default: 100 description: |- diff --git a/config/crd/bases/config.projectsveltos.io_clusterpromotions.yaml b/config/crd/bases/config.projectsveltos.io_clusterpromotions.yaml index 11c93394..825f6fa5 100644 --- a/config/crd/bases/config.projectsveltos.io_clusterpromotions.yaml +++ b/config/crd/bases/config.projectsveltos.io_clusterpromotions.yaml @@ -608,6 +608,16 @@ spec: When expressed as templates, the values are filled in using information from resources within the management cluster before deployment (Cluster) type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this KustomizationRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean targetNamespace: description: |- TargetNamespace sets or overrides the namespace in the @@ -892,6 +902,16 @@ spec: Defaults to 'None', which translates to the root path of the SourceRef. Used only for GitRepository;OCIRepository;Bucket type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this PolicyRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean tier: default: 100 description: |- @@ -1613,6 +1633,16 @@ spec: Defaults to 'None', which translates to the root path of the SourceRef. Used only for GitRepository;OCIRepository;Bucket type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this PolicyRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean tier: default: 100 description: |- @@ -1844,6 +1874,16 @@ spec: Defaults to 'None', which translates to the root path of the SourceRef. Used only for GitRepository;OCIRepository;Bucket type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this PolicyRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean tier: default: 100 description: |- diff --git a/config/crd/bases/config.projectsveltos.io_clustersummaries.yaml b/config/crd/bases/config.projectsveltos.io_clustersummaries.yaml index 3afc53b9..e1d2db8b 100644 --- a/config/crd/bases/config.projectsveltos.io_clustersummaries.yaml +++ b/config/crd/bases/config.projectsveltos.io_clustersummaries.yaml @@ -745,6 +745,16 @@ spec: When expressed as templates, the values are filled in using information from resources within the management cluster before deployment (Cluster) type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this KustomizationRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean targetNamespace: description: |- TargetNamespace sets or overrides the namespace in the @@ -1029,6 +1039,16 @@ spec: Defaults to 'None', which translates to the root path of the SourceRef. Used only for GitRepository;OCIRepository;Bucket type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this PolicyRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean tier: default: 100 description: |- diff --git a/config/crd/bases/config.projectsveltos.io_profiles.yaml b/config/crd/bases/config.projectsveltos.io_profiles.yaml index 21a578d7..ba30e726 100644 --- a/config/crd/bases/config.projectsveltos.io_profiles.yaml +++ b/config/crd/bases/config.projectsveltos.io_profiles.yaml @@ -707,6 +707,16 @@ spec: When expressed as templates, the values are filled in using information from resources within the management cluster before deployment (Cluster) type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this KustomizationRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean targetNamespace: description: |- TargetNamespace sets or overrides the namespace in the @@ -991,6 +1001,16 @@ spec: Defaults to 'None', which translates to the root path of the SourceRef. Used only for GitRepository;OCIRepository;Bucket type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this PolicyRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean tier: default: 100 description: |- diff --git a/controllers/export_test.go b/controllers/export_test.go index c81f85c3..0bec8fed 100644 --- a/controllers/export_test.go +++ b/controllers/export_test.go @@ -210,3 +210,7 @@ var ( var ( GetSortedKeys = getSortedKeys ) + +type ( + ReferencedObject = referencedObject +) diff --git a/controllers/handlers_kustomize.go b/controllers/handlers_kustomize.go index 6272ffa2..60a88182 100644 --- a/controllers/handlers_kustomize.go +++ b/controllers/handlers_kustomize.go @@ -995,7 +995,8 @@ func deployKustomizeResources(ctx context.Context, c client.Client, remoteRestCo Name: kustomizationRef.Name, } localReports, err = deployUnstructured(ctx, true, localConfig, c, objectsToDeployLocally, - ref, kustomizationRef.Tier, libsveltosv1beta1.FeatureKustomize, clusterSummary, []string{}, logger) + ref, kustomizationRef.Tier, kustomizationRef.SkipNamespaceCreation, libsveltosv1beta1.FeatureKustomize, + clusterSummary, []string{}, logger) if err != nil { logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to deploy to management cluster %v", err)) return localReports, nil, err @@ -1020,7 +1021,8 @@ func deployKustomizeResources(ctx context.Context, c client.Client, remoteRestCo } remoteReports, err = deployUnstructured(ctx, false, remoteRestConfig, remoteClient, objectsToDeployRemotely, - ref, kustomizationRef.Tier, libsveltosv1beta1.FeatureKustomize, clusterSummary, []string{}, logger) + ref, kustomizationRef.Tier, kustomizationRef.SkipNamespaceCreation, libsveltosv1beta1.FeatureKustomize, + clusterSummary, []string{}, logger) if err != nil { logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to deploy to remote cluster %v", err)) return localReports, remoteReports, err diff --git a/controllers/handlers_utils.go b/controllers/handlers_utils.go index 97f78464..5d1d449d 100644 --- a/controllers/handlers_utils.go +++ b/controllers/handlers_utils.go @@ -70,9 +70,10 @@ const ( type referencedObject struct { corev1.ObjectReference - Tier int32 - Optional bool - Path string + Tier int32 + SkipNamespaceCreation bool + Optional bool + Path string } func getClusterSummaryAnnotationValue(clusterSummary *configv1beta1.ClusterSummary) string { @@ -86,12 +87,12 @@ func getClusterSummaryAnnotationValue(clusterSummary *configv1beta1.ClusterSumma // the policies deployed in the form of kind.group:namespace:name for namespaced policies // and kind.group::name for cluster wide policies. func deployContentOfConfigMap(ctx context.Context, deployingToMgmtCluster bool, destConfig *rest.Config, - destClient client.Client, configMap *corev1.ConfigMap, referenceTier int32, clusterSummary *configv1beta1.ClusterSummary, + destClient client.Client, configMap *corev1.ConfigMap, reference *referencedObject, clusterSummary *configv1beta1.ClusterSummary, mgmtResources map[string]*unstructured.Unstructured, logger logr.Logger, ) ([]libsveltosv1beta1.ResourceReport, error) { resourceReports, err := deployContent(ctx, deployingToMgmtCluster, destConfig, destClient, configMap, configMap.Data, - referenceTier, clusterSummary, mgmtResources, logger) + reference.Tier, reference.SkipNamespaceCreation, clusterSummary, mgmtResources, logger) if err != nil { return resourceReports, fmt.Errorf("processing ConfigMap %s/%s: %w", configMap.Namespace, configMap.Name, err) } @@ -104,7 +105,7 @@ func deployContentOfConfigMap(ctx context.Context, deployingToMgmtCluster bool, // the policies deployed in the form of kind.group:namespace:name for namespaced policies // and kind.group::name for cluster wide policies. func deployContentOfSecret(ctx context.Context, deployingToMgmtCluster bool, destConfig *rest.Config, - destClient client.Client, secret *corev1.Secret, referenceTier int32, clusterSummary *configv1beta1.ClusterSummary, + destClient client.Client, secret *corev1.Secret, reference *referencedObject, clusterSummary *configv1beta1.ClusterSummary, mgmtResources map[string]*unstructured.Unstructured, logger logr.Logger, ) ([]libsveltosv1beta1.ResourceReport, error) { @@ -114,7 +115,7 @@ func deployContentOfSecret(ctx context.Context, deployingToMgmtCluster bool, des } resourceReports, err := deployContent(ctx, deployingToMgmtCluster, destConfig, destClient, secret, data, - referenceTier, clusterSummary, mgmtResources, logger) + reference.Tier, reference.SkipNamespaceCreation, clusterSummary, mgmtResources, logger) if err != nil { return resourceReports, fmt.Errorf("processing Secret %s/%s: %w", secret.Namespace, secret.Name, err) } @@ -123,7 +124,7 @@ func deployContentOfSecret(ctx context.Context, deployingToMgmtCluster bool, des } func deployContentOfSource(ctx context.Context, deployingToMgmtCluster bool, destConfig *rest.Config, - destClient client.Client, source client.Object, referenceTier int32, path string, + destClient client.Client, source client.Object, reference *referencedObject, path string, clusterSummary *configv1beta1.ClusterSummary, mgmtResources map[string]*unstructured.Unstructured, logger logr.Logger) ([]libsveltosv1beta1.ResourceReport, error) { @@ -171,7 +172,7 @@ func deployContentOfSource(ctx context.Context, deployingToMgmtCluster bool, des } return deployContent(ctx, deployingToMgmtCluster, destConfig, destClient, source, content, - referenceTier, clusterSummary, mgmtResources, logger) + reference.Tier, reference.SkipNamespaceCreation, clusterSummary, mgmtResources, logger) } func readFiles(dir string) (map[string]string, error) { @@ -281,8 +282,8 @@ func prepareReports(resources []*unstructured.Unstructured) []libsveltosv1beta1. // the policies deployed in the form of kind.group:namespace:name for namespaced policies // and kind.group::name for cluster wide policies. func deployContent(ctx context.Context, deployingToMgmtCluster bool, destConfig *rest.Config, destClient client.Client, - referencedObject client.Object, data map[string]string, referenceTier int32, clusterSummary *configv1beta1.ClusterSummary, - mgmtResources map[string]*unstructured.Unstructured, logger logr.Logger, + referencedObject client.Object, data map[string]string, referenceTier int32, skipNamespaceCreation bool, + clusterSummary *configv1beta1.ClusterSummary, mgmtResources map[string]*unstructured.Unstructured, logger logr.Logger, ) (reports []libsveltosv1beta1.ResourceReport, err error) { subresources := getSubresources(referencedObject) @@ -324,7 +325,7 @@ func deployContent(ctx context.Context, deployingToMgmtCluster bool, destConfig } return deployUnstructured(ctx, deployingToMgmtCluster, destConfig, destClient, resources, ref, referenceTier, - libsveltosv1beta1.FeatureResources, clusterSummary, subresources, logger) + skipNamespaceCreation, libsveltosv1beta1.FeatureResources, clusterSummary, subresources, logger) } // adjustNamespace fixes namespace. @@ -396,7 +397,7 @@ func applyPatches(ctx context.Context, clusterSummary *configv1beta1.ClusterSumm //nolint:funlen // requires a lot of arguments because kustomize and plain resources are using this function func deployUnstructured(ctx context.Context, deployingToMgmtCluster bool, destConfig *rest.Config, destClient client.Client, referencedUnstructured []*unstructured.Unstructured, referencedObject *corev1.ObjectReference, - referenceTier int32, featureID libsveltosv1beta1.FeatureID, clusterSummary *configv1beta1.ClusterSummary, + referenceTier int32, skipNamespaceCreation bool, featureID libsveltosv1beta1.FeatureID, clusterSummary *configv1beta1.ClusterSummary, subresources []string, logger logr.Logger) (reports []libsveltosv1beta1.ResourceReport, err error) { profile, profileTier, err := configv1beta1.GetProfileOwnerAndTier(ctx, getManagementClusterClient(), clusterSummary) @@ -433,11 +434,13 @@ func deployUnstructured(ctx context.Context, deployingToMgmtCluster bool, destCo resource, policyHash := deployer.GetResource(policy, deployer.HasIgnoreConfigurationDriftAnnotation(policy), referencedObject, profile, profileTier, referenceTier, string(featureID), logger) - // If policy is namespaced, create namespace if not already existing - err = deployer.CreateNamespace(ctx, destClient, clusterSummary.Spec.ClusterProfileSpec.SyncMode == configv1beta1.SyncModeDryRun, - policy.GetNamespace()) - if err != nil { - return nil, fmt.Errorf("%s: %w", errorPrefix, err) + if !skipNamespaceCreation { + // If policy is namespaced, create namespace if not already existing + err = deployer.CreateNamespace(ctx, destClient, clusterSummary.Spec.ClusterProfileSpec.SyncMode == configv1beta1.SyncModeDryRun, + policy.GetNamespace()) + if err != nil { + return nil, fmt.Errorf("%s: %w", errorPrefix, err) + } } dr, err := k8s_utils.GetDynamicResourceInterface(destConfig, policy.GroupVersionKind(), policy.GetNamespace()) @@ -752,6 +755,7 @@ func collectReferencedObjects(references []configv1beta1.PolicyRef) (local, remo } object.Tier = reference.Tier object.Optional = reference.Optional + object.SkipNamespaceCreation = reference.SkipNamespaceCreation case string(libsveltosv1beta1.SecretReferencedResourceKind): object.ObjectReference = corev1.ObjectReference{ APIVersion: "v1", @@ -761,6 +765,7 @@ func collectReferencedObjects(references []configv1beta1.PolicyRef) (local, remo } object.Tier = reference.Tier object.Optional = reference.Optional + object.SkipNamespaceCreation = reference.SkipNamespaceCreation case sourcev1.GitRepositoryKind: object.ObjectReference = corev1.ObjectReference{ APIVersion: sourcev1.GroupVersion.String(), @@ -770,6 +775,7 @@ func collectReferencedObjects(references []configv1beta1.PolicyRef) (local, remo } object.Tier = reference.Tier object.Optional = reference.Optional + object.SkipNamespaceCreation = reference.SkipNamespaceCreation object.Path = reference.Path case sourcev1.OCIRepositoryKind: object.ObjectReference = corev1.ObjectReference{ @@ -780,6 +786,7 @@ func collectReferencedObjects(references []configv1beta1.PolicyRef) (local, remo } object.Tier = reference.Tier object.Optional = reference.Optional + object.SkipNamespaceCreation = reference.SkipNamespaceCreation object.Path = reference.Path case sourcev1.BucketKind: object.ObjectReference = corev1.ObjectReference{ @@ -790,6 +797,7 @@ func collectReferencedObjects(references []configv1beta1.PolicyRef) (local, remo } object.Tier = reference.Tier object.Optional = reference.Optional + object.SkipNamespaceCreation = reference.SkipNamespaceCreation object.Path = reference.Path } @@ -923,14 +931,14 @@ func deployObjects(ctx context.Context, deployingToMgmtCluster bool, destClient l := logger.WithValues("configMapNamespace", configMap.Namespace, "configMapName", configMap.Name) l.V(logs.LogDebug).Info("deploying ConfigMap content") tmpResourceReports, err = - deployContentOfConfigMap(ctx, deployingToMgmtCluster, destConfig, destClient, configMap, referencedObjects[i].Tier, + deployContentOfConfigMap(ctx, deployingToMgmtCluster, destConfig, destClient, configMap, &referencedObjects[i], clusterSummary, mgmtResources, l) } else if referencedObjects[i].GetObjectKind().GroupVersionKind().Kind == string(libsveltosv1beta1.SecretReferencedResourceKind) { secret := referencedObject.(*corev1.Secret) l := logger.WithValues("secretNamespace", secret.Namespace, "secretName", secret.Name) l.V(logs.LogDebug).Info("deploying Secret content") tmpResourceReports, err = - deployContentOfSecret(ctx, deployingToMgmtCluster, destConfig, destClient, secret, referencedObjects[i].Tier, + deployContentOfSecret(ctx, deployingToMgmtCluster, destConfig, destClient, secret, &referencedObjects[i], clusterSummary, mgmtResources, l) } else { source := referencedObject @@ -938,7 +946,7 @@ func deployObjects(ctx context.Context, deployingToMgmtCluster bool, destClient annotations := source.GetAnnotations() path := annotations[pathAnnotation] tmpResourceReports, err = - deployContentOfSource(ctx, deployingToMgmtCluster, destConfig, destClient, source, referencedObjects[i].Tier, path, + deployContentOfSource(ctx, deployingToMgmtCluster, destConfig, destClient, source, &referencedObjects[i], path, clusterSummary, mgmtResources, logger) } diff --git a/controllers/handlers_utils_test.go b/controllers/handlers_utils_test.go index 5a0d0ee5..4fefde97 100644 --- a/controllers/handlers_utils_test.go +++ b/controllers/handlers_utils_test.go @@ -556,7 +556,7 @@ var _ = Describe("HandlersUtils", func() { // created) resourceReports, err := controllers.DeployContent(context.TODO(), false, testEnv.Config, testEnv.Client, secret, map[string]string{"service": services}, - defaultTier, clusterSummary, nil, textlogger.NewLogger(textlogger.NewConfig())) + defaultTier, false, clusterSummary, nil, textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) By("Validating action for all resourceReports is Create") validateResourceReports(resourceReports, 2, 0, 0, 0) @@ -587,7 +587,7 @@ var _ = Describe("HandlersUtils", func() { // ( if the ClusterProfile were to be changed from DryRun, nothing would happen). resourceReports, err = controllers.DeployContent(context.TODO(), false, testEnv.Config, testEnv.Client, secret, map[string]string{"service": services}, - defaultTier, clusterSummary, nil, textlogger.NewLogger(textlogger.NewConfig())) + defaultTier, false, clusterSummary, nil, textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) By("Validating action for all resourceReports is NoAction") validateResourceReports(resourceReports, 0, 0, 2, 0) @@ -624,7 +624,7 @@ var _ = Describe("HandlersUtils", func() { // (if the ClusterProfile were to be changed from DryRun, both service would be updated). resourceReports, err = controllers.DeployContent(context.TODO(), false, testEnv.Config, testEnv.Client, secret, map[string]string{"service": newContent}, - defaultTier, clusterSummary, nil, textlogger.NewLogger(textlogger.NewConfig())) + defaultTier, false, clusterSummary, nil, textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) By("Validating action for all resourceReports is Update") validateResourceReports(resourceReports, 0, 2, 0, 0) @@ -634,7 +634,7 @@ var _ = Describe("HandlersUtils", func() { tmpSecret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Namespace: randomString(), Name: randomString()}} resourceReports, err = controllers.DeployContent(context.TODO(), false, testEnv.Config, testEnv.Client, tmpSecret, map[string]string{"service": services}, - defaultTier, clusterSummary, nil, textlogger.NewLogger(textlogger.NewConfig())) + defaultTier, false, clusterSummary, nil, textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) By("Validating action for all resourceReports is Conflict") validateResourceReports(resourceReports, 0, 0, 0, 2) @@ -660,27 +660,37 @@ var _ = Describe("HandlersUtils", func() { Expect(addTypeInformationToObject(testEnv.Scheme(), clusterSummary)).To(Succeed()) + reference := &controllers.ReferencedObject{Tier: defaultTier, SkipNamespaceCreation: false} resourceReports, err := controllers.DeployContentOfSecret(context.TODO(), false, - testEnv.Config, testEnv.Client, secret, defaultTier, clusterSummary, nil, + testEnv.Config, testEnv.Client, secret, reference, clusterSummary, nil, textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) Expect(len(resourceReports)).To(Equal(3)) }) - It("deployContentOfConfigMap deploys all policies contained in a Secret", func() { - services := fmt.Sprintf(serviceTemplate, namespace, namespace) - depl := fmt.Sprintf(deplTemplate, namespace) + It("deployContentOfConfigMap deploys all policies contained in a ConfigMap", func() { + ns := randomString() + services := fmt.Sprintf(serviceTemplate, ns, namespace) + depl := fmt.Sprintf(deplTemplate, ns) configMap := createConfigMapWithPolicy(namespace, randomString(), depl, services) - Expect(testEnv.Create(context.TODO(), configMap)).To(Succeed()) Expect(waitForObject(ctx, testEnv.Client, configMap)).To(Succeed()) Expect(addTypeInformationToObject(testEnv.Scheme(), clusterSummary)).To(Succeed()) + reference := &controllers.ReferencedObject{Tier: defaultTier, SkipNamespaceCreation: true} + _, err := controllers.DeployContentOfConfigMap(context.TODO(), false, + testEnv.Config, testEnv.Client, configMap, reference, clusterSummary, nil, + textlogger.NewLogger(textlogger.NewConfig())) + // SkipNamespaceCreation is set to true. Since Service namespace is missing this will fail + Expect(err).ToNot(BeNil()) + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("namespaces %q not found", ns))) + + reference.SkipNamespaceCreation = false resourceReports, err := controllers.DeployContentOfConfigMap(context.TODO(), false, - testEnv.Config, testEnv.Client, configMap, defaultTier, clusterSummary, nil, + testEnv.Config, testEnv.Client, configMap, reference, clusterSummary, nil, textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) Expect(len(resourceReports)).To(Equal(3)) @@ -1174,6 +1184,7 @@ stringData: }) It("patchRessource with subresources correctly update instance", func() { + namespace := randomString() serviceName := randomString() key := randomString() value := randomString() @@ -1181,7 +1192,7 @@ stringData: kind: Service metadata: name: %s - namespace: default + namespace: %s labels: %s: %s spec: @@ -1192,10 +1203,18 @@ status: ingress: - ip: 1.1.1.1` + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + Expect(testEnv.Create(context.TODO(), ns)).To(Succeed()) + Expect(waitForObject(ctx, testEnv.Client, ns)).To(Succeed()) + service := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: serviceName, - Namespace: "default", + Namespace: namespace, }, Spec: corev1.ServiceSpec{ Type: corev1.ServiceTypeLoadBalancer, @@ -1217,25 +1236,27 @@ status: Expect(addTypeInformationToObject(testEnv.Scheme(), clusterSummary)).To(Succeed()) configMap := createConfigMapWithPolicy(namespace, randomString(), fmt.Sprintf(servicePatch, - serviceName, key, value, key, value)) + serviceName, namespace, key, value, key, value)) configMap.Annotations = map[string]string{ "projectsveltos.io/subresources": "status"} + + reference := &controllers.ReferencedObject{Tier: defaultTier, SkipNamespaceCreation: false} _, err := controllers.DeployContentOfConfigMap(context.TODO(), false, testEnv.Config, testEnv.Client, - configMap, defaultTier, clusterSummary, nil, textlogger.NewLogger(textlogger.NewConfig())) + configMap, reference, clusterSummary, nil, textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) serviceOut := corev1.Service{} // wait for cache to sync Eventually(func() bool { err := testEnv.Get(context.TODO(), - types.NamespacedName{Namespace: "default", Name: serviceName}, + types.NamespacedName{Namespace: namespace, Name: serviceName}, &serviceOut) return err == nil && serviceOut.Status.LoadBalancer.Ingress != nil }, timeout, pollingInterval).Should(BeTrue()) Expect(testEnv.Get(context.TODO(), - types.NamespacedName{Namespace: "default", Name: serviceName}, &serviceOut)).To(Succeed()) + types.NamespacedName{Namespace: namespace, Name: serviceName}, &serviceOut)).To(Succeed()) // verify status has been updated Expect(serviceOut.Status.LoadBalancer.Ingress).ToNot(BeNil()) diff --git a/manifest/manifest.yaml b/manifest/manifest.yaml index eac10584..0db0b06e 100644 --- a/manifest/manifest.yaml +++ b/manifest/manifest.yaml @@ -1016,6 +1016,16 @@ spec: When expressed as templates, the values are filled in using information from resources within the management cluster before deployment (Cluster) type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this KustomizationRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean targetNamespace: description: |- TargetNamespace sets or overrides the namespace in the @@ -1300,6 +1310,16 @@ spec: Defaults to 'None', which translates to the root path of the SourceRef. Used only for GitRepository;OCIRepository;Bucket type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this PolicyRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean tier: default: 100 description: |- @@ -2660,6 +2680,16 @@ spec: When expressed as templates, the values are filled in using information from resources within the management cluster before deployment (Cluster) type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this KustomizationRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean targetNamespace: description: |- TargetNamespace sets or overrides the namespace in the @@ -2944,6 +2974,16 @@ spec: Defaults to 'None', which translates to the root path of the SourceRef. Used only for GitRepository;OCIRepository;Bucket type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this PolicyRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean tier: default: 100 description: |- @@ -3665,6 +3705,16 @@ spec: Defaults to 'None', which translates to the root path of the SourceRef. Used only for GitRepository;OCIRepository;Bucket type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this PolicyRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean tier: default: 100 description: |- @@ -3896,6 +3946,16 @@ spec: Defaults to 'None', which translates to the root path of the SourceRef. Used only for GitRepository;OCIRepository;Bucket type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this PolicyRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean tier: default: 100 description: |- @@ -5065,6 +5125,16 @@ spec: When expressed as templates, the values are filled in using information from resources within the management cluster before deployment (Cluster) type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this KustomizationRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean targetNamespace: description: |- TargetNamespace sets or overrides the namespace in the @@ -5349,6 +5419,16 @@ spec: Defaults to 'None', which translates to the root path of the SourceRef. Used only for GitRepository;OCIRepository;Bucket type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this PolicyRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean tier: default: 100 description: |- @@ -6735,6 +6815,16 @@ spec: When expressed as templates, the values are filled in using information from resources within the management cluster before deployment (Cluster) type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this KustomizationRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean targetNamespace: description: |- TargetNamespace sets or overrides the namespace in the @@ -7019,6 +7109,16 @@ spec: Defaults to 'None', which translates to the root path of the SourceRef. Used only for GitRepository;OCIRepository;Bucket type: string + skipNamespaceCreation: + default: false + description: |- + SkipNamespaceCreation indicates whether Sveltos should skip creating the namespace + for namespaced resources defined in this PolicyRef. + This field is ignored for cluster-scoped resources. + By default, Sveltos attempts to get or create the target namespace if it does not exist. + Setting this to true avoids those calls, which is necessary when Sveltos lacks + permissions to manage namespaces at the cluster level. + type: boolean tier: default: 100 description: |-