From 248dd44b7026442b14cb5f1ed8a0187a207b6371 Mon Sep 17 00:00:00 2001 From: Gianluca Mardente Date: Wed, 15 Jul 2026 22:27:23 +0200 Subject: [PATCH] feat: expose Force on PolicyRef/KustomizationRef to recover from rejected updates libsveltos' `deployer.UpdateResource` now supports a `forceRecreate` option: when an apply is rejected with an error that only a delete+recreate can fix (eg a Deployment moving to `strategy.type: Recreate` while the API server's previously-defaulted `rollingUpdate` is still set), the object is deleted and recreated instead of surfacing the error. This wires that option up to `PolicyRef.Force` and `KustomizationRef.Force` --- Makefile | 4 +- api/v1beta1/spec.go | 16 + ...fig.projectsveltos.io_clusterprofiles.yaml | 16 + ...g.projectsveltos.io_clusterpromotions.yaml | 32 ++ ...ig.projectsveltos.io_clustersummaries.yaml | 16 + .../config.projectsveltos.io_profiles.yaml | 16 + config/default/manager_auth_proxy_patch.yaml | 2 +- config/default/manager_image_patch.yaml | 4 +- controllers/export_test.go | 22 +- controllers/handlers_helm.go | 4 +- controllers/handlers_kustomize.go | 10 +- controllers/handlers_utils.go | 57 +-- controllers/handlers_utils_test.go | 83 +++- controllers/url_source.go | 2 +- go.mod | 2 +- go.sum | 4 +- manifest/deployment-agentless.yaml | 6 +- manifest/deployment-shard.yaml | 6 +- manifest/manifest.yaml | 86 ++++- ...drift-detection-manager-in-mgmt-cluster.go | 4 +- ...ift-detection-manager-in-mgmt-cluster.yaml | 4 +- .../drift-detection-manager.go | 4 +- .../drift-detection-manager.yaml | 4 +- test/fv/drift_test.go | 355 +++++++----------- test/fv/helm_error_test.go | 2 +- test/fv/paused_profile_test.go | 2 +- test/fv/policy_ref_force_test.go | 207 ++++++++++ test/fv/test_constants_test.go | 20 +- test/fv/tier_test.go | 66 ++-- test/pullmode-sveltosapplier.yaml | 2 +- 30 files changed, 732 insertions(+), 326 deletions(-) create mode 100644 test/fv/policy_ref_force_test.go diff --git a/Makefile b/Makefile index e5c50d2b..5f604d99 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ OS ?= $(shell uname -s) OS := $(shell echo $(OS) | tr '[:upper:]' '[:lower:]') K8S_LATEST_VER ?= $(shell curl -s https://dl.k8s.io/release/stable.txt) export CONTROLLER_IMG ?= $(REGISTRY)/$(IMAGE_NAME) -TAG ?= v1.12.0 +TAG ?= main .PHONY: all all: build @@ -216,7 +216,7 @@ WORKLOAD_CLUSTER_NAME ?= clusterapi-workload TMP_FILE ?= test/pullmode-kubeconfig_data.tmp TIMEOUT ?= 10m WORKLOAD_CLUSTER_YAML ?= test/$(WORKLOAD_CLUSTER_NAME).yaml -NUM_NODES ?= 6 +NUM_NODES ?= 8 .PHONY: quickstart quickstart: ## start kind cluster; install all cluster api components; create a capi cluster; install projectsveltos diff --git a/api/v1beta1/spec.go b/api/v1beta1/spec.go index b3f3c4e9..9e4e3478 100644 --- a/api/v1beta1/spec.go +++ b/api/v1beta1/spec.go @@ -660,6 +660,14 @@ type KustomizationRef struct { // +kubebuilder:default:=false // +optional SkipNamespaceCreation bool `json:"skipNamespaceCreation,omitempty"` + + // Force indicates whether Sveltos should delete and recreate a resource defined in this + // KustomizationRef when an update is rejected with an error that only a delete+recreate + // can resolve (eg an invalid combination of fields, or a field enforced as immutable). + // By default, such errors are surfaced instead of recreating the resource. + // +kubebuilder:default:=false + // +optional + Force bool `json:"force,omitempty"` } // StopMatchingBehavior indicates what will happen when Cluster stops matching @@ -779,6 +787,14 @@ type PolicyRef struct { // +optional SkipNamespaceCreation bool `json:"skipNamespaceCreation,omitempty"` + // Force indicates whether Sveltos should delete and recreate a resource defined in this + // PolicyRef when an update is rejected with an error that only a delete+recreate can + // resolve (eg an invalid combination of fields, or a field enforced as immutable). + // By default, such errors are surfaced instead of recreating the resource. + // +kubebuilder:default:=false + // +optional + Force bool `json:"force,omitempty"` + // RemoteURL configures fetching content from an HTTP/HTTPS endpoint or an OCI registry. // When set, Kind/Name/Namespace must be omitted. // +optional diff --git a/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml b/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml index a2c1a359..f9e056e0 100644 --- a/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml +++ b/config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml @@ -767,6 +767,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + KustomizationRef when an update is rejected with an error that only a delete+recreate + can resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -1064,6 +1072,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + PolicyRef when an update is rejected with an error that only a delete+recreate can + resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: diff --git a/config/crd/bases/config.projectsveltos.io_clusterpromotions.yaml b/config/crd/bases/config.projectsveltos.io_clusterpromotions.yaml index ca604389..5196a03a 100644 --- a/config/crd/bases/config.projectsveltos.io_clusterpromotions.yaml +++ b/config/crd/bases/config.projectsveltos.io_clusterpromotions.yaml @@ -669,6 +669,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + KustomizationRef when an update is rejected with an error that only a delete+recreate + can resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -966,6 +974,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + PolicyRef when an update is rejected with an error that only a delete+recreate can + resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -2183,6 +2199,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + PolicyRef when an update is rejected with an error that only a delete+recreate can + resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -2545,6 +2569,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + PolicyRef when an update is rejected with an error that only a delete+recreate can + resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: diff --git a/config/crd/bases/config.projectsveltos.io_clustersummaries.yaml b/config/crd/bases/config.projectsveltos.io_clustersummaries.yaml index 8cd4dead..3fa1dbc6 100644 --- a/config/crd/bases/config.projectsveltos.io_clustersummaries.yaml +++ b/config/crd/bases/config.projectsveltos.io_clustersummaries.yaml @@ -806,6 +806,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + KustomizationRef when an update is rejected with an error that only a delete+recreate + can resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -1103,6 +1111,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + PolicyRef when an update is rejected with an error that only a delete+recreate can + resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: diff --git a/config/crd/bases/config.projectsveltos.io_profiles.yaml b/config/crd/bases/config.projectsveltos.io_profiles.yaml index 3ef6d24d..84cb4643 100644 --- a/config/crd/bases/config.projectsveltos.io_profiles.yaml +++ b/config/crd/bases/config.projectsveltos.io_profiles.yaml @@ -767,6 +767,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + KustomizationRef when an update is rejected with an error that only a delete+recreate + can resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -1064,6 +1072,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + PolicyRef when an update is rejected with an error that only a delete+recreate can + resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: diff --git a/config/default/manager_auth_proxy_patch.yaml b/config/default/manager_auth_proxy_patch.yaml index 44ed52a1..8f8dea58 100644 --- a/config/default/manager_auth_proxy_patch.yaml +++ b/config/default/manager_auth_proxy_patch.yaml @@ -35,7 +35,7 @@ spec: - --shard-key= - --capi-onboard-annotation= - "--v=5" - - "--version=v1.12.0" + - "--version=main" - "--agent-in-mgmt-cluster=false" env: - name: GOMEMLIMIT diff --git a/config/default/manager_image_patch.yaml b/config/default/manager_image_patch.yaml index 319a1dc9..22ae3140 100644 --- a/config/default/manager_image_patch.yaml +++ b/config/default/manager_image_patch.yaml @@ -7,8 +7,8 @@ spec: template: spec: initContainers: - - image: docker.io/projectsveltos/addon-controller:v1.12.0 + - image: docker.io/projectsveltos/addon-controller:main name: initialization containers: - - image: docker.io/projectsveltos/addon-controller:v1.12.0 + - image: docker.io/projectsveltos/addon-controller:main name: controller diff --git a/controllers/export_test.go b/controllers/export_test.go index af23095b..bfc4a0a2 100644 --- a/controllers/export_test.go +++ b/controllers/export_test.go @@ -107,16 +107,18 @@ var ( GenericDeploy = genericDeploy GenericUndeploy = genericUndeploy - GetEntryKey = getEntryKey - DeployContentOfConfigMap = deployContentOfConfigMap - DeployContentOfSecret = deployContentOfSecret - DeployContent = deployContent - GetClusterSummaryAdmin = getClusterSummaryAdmin - CollectContent = collectContent - UndeployStaleResources = undeployStaleResources - GetDeployedGroupVersionKinds = getDeployedGroupVersionKinds - GetSecret = getSecret - ReadFiles = readFiles + GetEntryKey = getEntryKey + DeployContentOfConfigMap = deployContentOfConfigMap + DeployContentOfSecret = deployContentOfSecret + DeployContent = deployContent + GetClusterSummaryAdmin = getClusterSummaryAdmin + CollectContent = collectContent + CollectReferencedObjects = collectReferencedObjects + PrepareBundleSettersWithResourceInfo = prepareBundleSettersWithResourceInfo + UndeployStaleResources = undeployStaleResources + GetDeployedGroupVersionKinds = getDeployedGroupVersionKinds + GetSecret = getSecret + ReadFiles = readFiles AddExtraLabels = addExtraLabels AddExtraAnnotations = addExtraAnnotations diff --git a/controllers/handlers_helm.go b/controllers/handlers_helm.go index 9b48ae66..7e246c30 100644 --- a/controllers/handlers_helm.go +++ b/controllers/handlers_helm.go @@ -4574,8 +4574,8 @@ func addExtraMetadata(ctx context.Context, requestedChart *configv1beta1.HelmCha isDriftDetection := clusterSummary.Spec.ClusterProfileSpec.SyncMode == configv1beta1.SyncModeContinuousWithDriftDetection isDryRun := clusterSummary.Spec.ClusterProfileSpec.SyncMode == configv1beta1.SyncModeDryRun - _, err = deployer.UpdateResource(ctx, dr, isDriftDetection, isDryRun, clusterSummary.Spec.ClusterProfileSpec.DriftExclusions, - r, []string{}, logger) + _, err = deployer.UpdateResource(ctx, dr, isDriftDetection, isDryRun, getForceValue(requestedChart.Options), + clusterSummary.Spec.ClusterProfileSpec.DriftExclusions, r, []string{}, logger) if err != nil { logger.V(logs.LogInfo).Info(fmt.Sprintf("failed to update resource %s %s/%s: %v", r.GetKind(), r.GetNamespace(), r.GetName(), err)) diff --git a/controllers/handlers_kustomize.go b/controllers/handlers_kustomize.go index 2a363158..82344fa8 100644 --- a/controllers/handlers_kustomize.go +++ b/controllers/handlers_kustomize.go @@ -1011,8 +1011,8 @@ func deployKustomizeResources(ctx context.Context, c client.Client, remoteRestCo Name: kustomizationRef.Name, } localReports, err = deployUnstructured(ctx, true, localConfig, c, objectsToDeployLocally, - ref, kustomizationRef.Tier, kustomizationRef.SkipNamespaceCreation, libsveltosv1beta1.FeatureKustomize, - dCtx.clusterSummary, []string{}, logger) + ref, kustomizationRef.Tier, kustomizationRef.SkipNamespaceCreation, kustomizationRef.Force, + libsveltosv1beta1.FeatureKustomize, dCtx.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 @@ -1025,7 +1025,7 @@ func deployKustomizeResources(ctx context.Context, c client.Client, remoteRestCo bundleResources[key] = convertPointerSliceToValueSlice(objectsToDeployRemotely) setters := prepareBundleSettersWithResourceInfo(ref.Kind, ref.Namespace, ref.Name, kustomizationRef.Tier, - kustomizationRef.SkipNamespaceCreation) + kustomizationRef.SkipNamespaceCreation, kustomizationRef.Force) return localReports, nil, pullmode.StageResourcesForDeployment(ctx, getManagementClusterClient(), dCtx.clusterSummary.Spec.ClusterNamespace, dCtx.clusterSummary.Spec.ClusterName, configv1beta1.ClusterSummaryKind, @@ -1038,8 +1038,8 @@ func deployKustomizeResources(ctx context.Context, c client.Client, remoteRestCo } remoteReports, err = deployUnstructured(ctx, false, remoteRestConfig, remoteClient, objectsToDeployRemotely, - ref, kustomizationRef.Tier, kustomizationRef.SkipNamespaceCreation, libsveltosv1beta1.FeatureKustomize, - dCtx.clusterSummary, []string{}, logger) + ref, kustomizationRef.Tier, kustomizationRef.SkipNamespaceCreation, kustomizationRef.Force, + libsveltosv1beta1.FeatureKustomize, dCtx.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 928542a6..bd4e5d4d 100644 --- a/controllers/handlers_utils.go +++ b/controllers/handlers_utils.go @@ -73,6 +73,7 @@ type referencedObject struct { corev1.ObjectReference Tier int32 SkipNamespaceCreation bool + Force bool Optional bool Path string // URL and related fields are set only for URL-based PolicyRefs (Kind == urlSourceKind). @@ -97,7 +98,7 @@ func deployContentOfConfigMap(ctx context.Context, deployingToMgmtCluster bool, ) ([]libsveltosv1beta1.ResourceReport, error) { resourceReports, err := deployContent(ctx, deployingToMgmtCluster, destConfig, destClient, configMap, configMap.Data, - reference.Tier, reference.SkipNamespaceCreation, dCtx, logger) + reference.Tier, reference.SkipNamespaceCreation, reference.Force, dCtx, logger) if err != nil { return resourceReports, fmt.Errorf("processing ConfigMap %s/%s: %w", configMap.Namespace, configMap.Name, err) } @@ -120,7 +121,7 @@ func deployContentOfSecret(ctx context.Context, deployingToMgmtCluster bool, des } resourceReports, err := deployContent(ctx, deployingToMgmtCluster, destConfig, destClient, secret, data, - reference.Tier, reference.SkipNamespaceCreation, dCtx, logger) + reference.Tier, reference.SkipNamespaceCreation, reference.Force, dCtx, logger) if err != nil { return resourceReports, fmt.Errorf("processing Secret %s/%s: %w", secret.Namespace, secret.Name, err) } @@ -171,7 +172,7 @@ func deployContentOfSource(ctx context.Context, deployingToMgmtCluster bool, des } return deployContent(ctx, deployingToMgmtCluster, destConfig, destClient, source, content, - reference.Tier, reference.SkipNamespaceCreation, dCtx, logger) + reference.Tier, reference.SkipNamespaceCreation, reference.Force, dCtx, logger) } func readFiles(dir string) (map[string]string, error) { @@ -281,7 +282,7 @@ 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, skipNamespaceCreation bool, + referencedObject client.Object, data map[string]string, referenceTier int32, skipNamespaceCreation, force bool, dCtx *deploymentContext, logger logr.Logger) (reports []libsveltosv1beta1.ResourceReport, err error) { subresources := getSubresources(referencedObject) @@ -314,7 +315,8 @@ func deployContent(ctx context.Context, deployingToMgmtCluster bool, destConfig // In pull mode we return reports with action Create. Those will only be used to update deployed GVK. // sveltos-applier will take care of sending proper reports - setters := prepareBundleSettersWithResourceInfo(ref.Kind, ref.Namespace, ref.Name, referenceTier, skipNamespaceCreation) + setters := prepareBundleSettersWithResourceInfo(ref.Kind, ref.Namespace, ref.Name, referenceTier, + skipNamespaceCreation, force) return prepareReports(resources), pullmode.StageResourcesForDeployment(ctx, getManagementClusterClient(), @@ -324,7 +326,7 @@ func deployContent(ctx context.Context, deployingToMgmtCluster bool, destConfig } return deployUnstructured(ctx, deployingToMgmtCluster, destConfig, destClient, resources, ref, referenceTier, - skipNamespaceCreation, libsveltosv1beta1.FeatureResources, dCtx.clusterSummary, subresources, logger) + skipNamespaceCreation, force, libsveltosv1beta1.FeatureResources, dCtx.clusterSummary, subresources, logger) } // adjustNamespace fixes namespace. @@ -396,8 +398,9 @@ 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, skipNamespaceCreation bool, featureID libsveltosv1beta1.FeatureID, clusterSummary *configv1beta1.ClusterSummary, - subresources []string, logger logr.Logger) (reports []libsveltosv1beta1.ResourceReport, err error) { + referenceTier int32, skipNamespaceCreation, forceRecreate 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) if err != nil { @@ -507,6 +510,7 @@ func deployUnstructured(ctx context.Context, deployingToMgmtCluster bool, destCo dr, clusterSummary.Spec.ClusterProfileSpec.SyncMode == configv1beta1.SyncModeContinuousWithDriftDetection, clusterSummary.Spec.ClusterProfileSpec.SyncMode == configv1beta1.SyncModeDryRun, + forceRecreate, clusterSummary.Spec.ClusterProfileSpec.DriftExclusions, policy, subresources, @@ -728,6 +732,15 @@ func appendPathAnnotations(object client.Object, path string) { object.SetAnnotations(annotations) } +// setCommonReferencedObjectFields copies the options shared by every PolicyRef kind +// (Tier, Optional, SkipNamespaceCreation, Force) from reference onto object. +func setCommonReferencedObjectFields(object *referencedObject, reference *configv1beta1.PolicyRef) { + object.Tier = reference.Tier + object.Optional = reference.Optional + object.SkipNamespaceCreation = reference.SkipNamespaceCreation + object.Force = reference.Force +} + // collectReferencedObjects collects all referenced configMaps/secrets in control cluster // local contains all configMaps/Secrets whose content need to be deployed locally (in the management cluster) // remote contains all configMap/Secrets whose content need to be deployed remotely (in the managed cluster) @@ -746,9 +759,7 @@ func collectReferencedObjects(references []configv1beta1.PolicyRef) (local, remo object.URL = reference.RemoteURL.URL object.IsTemplate = reference.RemoteURL.Template object.SecretRef = reference.RemoteURL.SecretRef - object.Tier = reference.Tier - object.Optional = reference.Optional - object.SkipNamespaceCreation = reference.SkipNamespaceCreation + setCommonReferencedObjectFields(&object, reference) if reference.DeploymentType == configv1beta1.DeploymentTypeLocal { local = append(local, object) @@ -766,9 +777,7 @@ func collectReferencedObjects(references []configv1beta1.PolicyRef) (local, remo Namespace: reference.Namespace, Name: reference.Name, } - object.Tier = reference.Tier - object.Optional = reference.Optional - object.SkipNamespaceCreation = reference.SkipNamespaceCreation + setCommonReferencedObjectFields(&object, reference) case string(libsveltosv1beta1.SecretReferencedResourceKind): object.ObjectReference = corev1.ObjectReference{ APIVersion: coreAPIVersion, @@ -776,9 +785,7 @@ func collectReferencedObjects(references []configv1beta1.PolicyRef) (local, remo Namespace: reference.Namespace, Name: reference.Name, } - object.Tier = reference.Tier - object.Optional = reference.Optional - object.SkipNamespaceCreation = reference.SkipNamespaceCreation + setCommonReferencedObjectFields(&object, reference) case sourcev1.GitRepositoryKind: object.ObjectReference = corev1.ObjectReference{ APIVersion: sourcev1.GroupVersion.String(), @@ -786,9 +793,7 @@ func collectReferencedObjects(references []configv1beta1.PolicyRef) (local, remo Namespace: reference.Namespace, Name: reference.Name, } - object.Tier = reference.Tier - object.Optional = reference.Optional - object.SkipNamespaceCreation = reference.SkipNamespaceCreation + setCommonReferencedObjectFields(&object, reference) object.Path = reference.Path case sourcev1.OCIRepositoryKind: object.ObjectReference = corev1.ObjectReference{ @@ -797,9 +802,7 @@ func collectReferencedObjects(references []configv1beta1.PolicyRef) (local, remo Namespace: reference.Namespace, Name: reference.Name, } - object.Tier = reference.Tier - object.Optional = reference.Optional - object.SkipNamespaceCreation = reference.SkipNamespaceCreation + setCommonReferencedObjectFields(&object, reference) object.Path = reference.Path case sourcev1.BucketKind: object.ObjectReference = corev1.ObjectReference{ @@ -808,9 +811,7 @@ func collectReferencedObjects(references []configv1beta1.PolicyRef) (local, remo Namespace: reference.Namespace, Name: reference.Name, } - object.Tier = reference.Tier - object.Optional = reference.Optional - object.SkipNamespaceCreation = reference.SkipNamespaceCreation + setCommonReferencedObjectFields(&object, reference) object.Path = reference.Path } @@ -1753,12 +1754,12 @@ func getReloaderClient(ctx context.Context, clusterNamespace, clusterName string } func prepareBundleSettersWithResourceInfo(referenceKind, referenceNamespace, referenceName string, - tier int32, skipNamespaceCreation bool) []pullmode.BundleOption { + tier int32, skipNamespaceCreation, force bool) []pullmode.BundleOption { setters := make([]pullmode.BundleOption, 0) setters = append(setters, - pullmode.WithResourceInfo(referenceKind, referenceNamespace, referenceName, tier, skipNamespaceCreation)) + pullmode.WithResourceInfo(referenceKind, referenceNamespace, referenceName, tier, skipNamespaceCreation, force)) return setters } diff --git a/controllers/handlers_utils_test.go b/controllers/handlers_utils_test.go index 2bc301c1..55279b28 100644 --- a/controllers/handlers_utils_test.go +++ b/controllers/handlers_utils_test.go @@ -48,6 +48,7 @@ import ( libsveltosv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" "github.com/projectsveltos/libsveltos/lib/deployer" "github.com/projectsveltos/libsveltos/lib/k8s_utils" + "github.com/projectsveltos/libsveltos/lib/pullmode" ) const ( @@ -292,7 +293,7 @@ var _ = Describe("HandlersUtils", func() { isDryRun := clusterSummary.Spec.ClusterProfileSpec.SyncMode == configv1beta1.SyncModeDryRun isDriftDetection := clusterSummary.Spec.ClusterProfileSpec.SyncMode == configv1beta1.SyncModeContinuousWithDriftDetection // following will successfully create deployment - _, err = deployer.UpdateResource(context.TODO(), dr, isDriftDetection, isDryRun, clusterSummary.Spec.ClusterProfileSpec.DriftExclusions, + _, err = deployer.UpdateResource(context.TODO(), dr, isDriftDetection, isDryRun, false, clusterSummary.Spec.ClusterProfileSpec.DriftExclusions, u, nil, textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) @@ -336,7 +337,7 @@ var _ = Describe("HandlersUtils", func() { isDryRun = clusterSummary.Spec.ClusterProfileSpec.SyncMode == configv1beta1.SyncModeDryRun isDriftDetection = clusterSummary.Spec.ClusterProfileSpec.SyncMode == configv1beta1.SyncModeContinuousWithDriftDetection // New deploy will not override replicas - _, err = deployer.UpdateResource(context.TODO(), dr, isDriftDetection, isDryRun, clusterSummary.Spec.ClusterProfileSpec.DriftExclusions, + _, err = deployer.UpdateResource(context.TODO(), dr, isDriftDetection, isDryRun, false, clusterSummary.Spec.ClusterProfileSpec.DriftExclusions, u, nil, textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) @@ -361,7 +362,7 @@ var _ = Describe("HandlersUtils", func() { isDryRun := false isDriftDetection := true - _, err = deployer.UpdateResource(context.TODO(), dr, isDriftDetection, isDryRun, clusterSummary.Spec.ClusterProfileSpec.DriftExclusions, + _, err = deployer.UpdateResource(context.TODO(), dr, isDriftDetection, isDryRun, false, clusterSummary.Spec.ClusterProfileSpec.DriftExclusions, u, nil, textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) @@ -405,7 +406,7 @@ var _ = Describe("HandlersUtils", func() { }, timeout, pollingInterval).Should(BeTrue()) // New deploy will override labels - _, err = deployer.UpdateResource(context.TODO(), dr, isDriftDetection, isDryRun, clusterSummary.Spec.ClusterProfileSpec.DriftExclusions, + _, err = deployer.UpdateResource(context.TODO(), dr, isDriftDetection, isDryRun, false, clusterSummary.Spec.ClusterProfileSpec.DriftExclusions, u, nil, textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) @@ -429,7 +430,7 @@ var _ = Describe("HandlersUtils", func() { isDryRun := clusterSummary.Spec.ClusterProfileSpec.SyncMode == configv1beta1.SyncModeDryRun isDriftDetection := clusterSummary.Spec.ClusterProfileSpec.SyncMode == configv1beta1.SyncModeContinuousWithDriftDetection // following will successfully create deployment - _, err = deployer.UpdateResource(context.TODO(), dr, isDriftDetection, isDryRun, clusterSummary.Spec.ClusterProfileSpec.DriftExclusions, + _, err = deployer.UpdateResource(context.TODO(), dr, isDriftDetection, isDryRun, false, clusterSummary.Spec.ClusterProfileSpec.DriftExclusions, u, nil, textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) @@ -481,7 +482,7 @@ var _ = Describe("HandlersUtils", func() { isDryRun = clusterSummary.Spec.ClusterProfileSpec.SyncMode == configv1beta1.SyncModeDryRun isDriftDetection = clusterSummary.Spec.ClusterProfileSpec.SyncMode == configv1beta1.SyncModeContinuousWithDriftDetection // New deploy will not override replicas - _, err = deployer.UpdateResource(context.TODO(), dr, isDriftDetection, isDryRun, clusterSummary.Spec.ClusterProfileSpec.DriftExclusions, + _, err = deployer.UpdateResource(context.TODO(), dr, isDriftDetection, isDryRun, false, clusterSummary.Spec.ClusterProfileSpec.DriftExclusions, u, []string{testStatusField}, textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) @@ -561,7 +562,7 @@ var _ = Describe("HandlersUtils", func() { // created) resourceReports, err := controllers.DeployContent(context.TODO(), false, testEnv.Config, testEnv.Client, secret, map[string]string{testServiceKey: services}, - defaultTier, false, controllers.NewDeploymentContext(clusterSummary, clusterObjects, nil), + defaultTier, false, false, controllers.NewDeploymentContext(clusterSummary, clusterObjects, nil), textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) By("Validating action for all resourceReports is Create") @@ -593,7 +594,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{testServiceKey: services}, - defaultTier, false, controllers.NewDeploymentContext(clusterSummary, clusterObjects, nil), + defaultTier, false, false, controllers.NewDeploymentContext(clusterSummary, clusterObjects, nil), textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) By("Validating action for all resourceReports is NoAction") @@ -631,7 +632,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{testServiceKey: newContent}, - defaultTier, false, controllers.NewDeploymentContext(clusterSummary, clusterObjects, nil), + defaultTier, false, false, controllers.NewDeploymentContext(clusterSummary, clusterObjects, nil), textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) By("Validating action for all resourceReports is Update") @@ -642,7 +643,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{testServiceKey: services}, - defaultTier, false, controllers.NewDeploymentContext(clusterSummary, clusterObjects, nil), + defaultTier, false, false, controllers.NewDeploymentContext(clusterSummary, clusterObjects, nil), textlogger.NewLogger(textlogger.NewConfig())) Expect(err).To(BeNil()) By("Validating action for all resourceReports is Conflict") @@ -1510,6 +1511,68 @@ status: }) }) +var _ = Describe("collectReferencedObjects", func() { + It("copies Force from PolicyRef onto the referencedObject for every supported Kind", func() { + references := []configv1beta1.PolicyRef{ + { + Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), + Namespace: randomString(), Name: randomString(), + DeploymentType: configv1beta1.DeploymentTypeRemote, + Force: true, + }, + { + Kind: string(libsveltosv1beta1.SecretReferencedResourceKind), + Namespace: randomString(), Name: randomString(), + DeploymentType: configv1beta1.DeploymentTypeRemote, + Force: false, + }, + } + + _, remote := controllers.CollectReferencedObjects(references) + Expect(len(remote)).To(Equal(2)) + Expect(remote[0].Force).To(BeTrue()) + Expect(remote[1].Force).To(BeFalse()) + }) + + It("copies Force from a RemoteURL PolicyRef onto the referencedObject", func() { + references := []configv1beta1.PolicyRef{ + { + RemoteURL: &configv1beta1.RemoteURL{ + URL: "https://example.com/manifest.yaml", + }, + DeploymentType: configv1beta1.DeploymentTypeRemote, + Force: true, + }, + } + + _, remote := controllers.CollectReferencedObjects(references) + Expect(len(remote)).To(Equal(1)) + Expect(remote[0].Force).To(BeTrue()) + }) +}) + +var _ = Describe("prepareBundleSettersWithResourceInfo", func() { + It("threads force onto the ConfigurationBundle options", func() { + kind := randomString() + namespace := randomString() + name := randomString() + var tier int32 = 100 + + setters := controllers.PrepareBundleSettersWithResourceInfo(kind, namespace, name, tier, true, true) + Expect(len(setters)).To(Equal(1)) + + bundleOptions := &pullmode.BundleOptions{} + setters[0](bundleOptions) + + Expect(bundleOptions.ReferencedObjectKind).To(Equal(kind)) + Expect(bundleOptions.ReferencedObjectNamespace).To(Equal(namespace)) + Expect(bundleOptions.ReferencedObjectName).To(Equal(name)) + Expect(bundleOptions.ReferencedTier).To(Equal(tier)) + Expect(bundleOptions.SkipNamespaceCreation).To(BeTrue()) + Expect(bundleOptions.Force).To(BeTrue()) + }) +}) + // validateResourceReports validates that number of resourceResources with certain actions // match the expected number per action func validateResourceReports(resourceReports []libsveltosv1beta1.ResourceReport, diff --git a/controllers/url_source.go b/controllers/url_source.go index 3a5f10b4..82e79775 100644 --- a/controllers/url_source.go +++ b/controllers/url_source.go @@ -332,7 +332,7 @@ func deployContentOfURL(ctx context.Context, deployingToMgmtCluster bool, destCo l.V(logs.LogDebug).Info("deploying URL content") return deployContent(ctx, deployingToMgmtCluster, destConfig, destClient, syntheticSource, - data, ref.Tier, ref.SkipNamespaceCreation, dCtx, l) + data, ref.Tier, ref.SkipNamespaceCreation, ref.Force, dCtx, l) } // minURLInterval returns the shortest polling interval across all URL-based PolicyRefs, diff --git a/go.mod b/go.mod index 98c69998..afa0d624 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/onsi/gomega v1.42.1 github.com/opencontainers/image-spec v1.1.1 github.com/pkg/errors v0.9.1 - github.com/projectsveltos/libsveltos v1.12.0 + github.com/projectsveltos/libsveltos v1.12.1-0.20260715200227-148c08381bed github.com/prometheus/client_golang v1.23.2 github.com/robfig/cron v1.2.0 github.com/sigstore/cosign/v3 v3.1.1 diff --git a/go.sum b/go.sum index c26a53aa..04c0d4ac 100644 --- a/go.sum +++ b/go.sum @@ -644,8 +644,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= -github.com/projectsveltos/libsveltos v1.12.0 h1:xQfo/AEh3vVRbfWVazpsgBoRgBG5vzm/sJmXp5YrUEg= -github.com/projectsveltos/libsveltos v1.12.0/go.mod h1:4/vcbYFCFE8uEGIHmltriAoHxdB8jgS2zI+9gruOfJ4= +github.com/projectsveltos/libsveltos v1.12.1-0.20260715200227-148c08381bed h1:k5t1S+iaj2IqDUbL4za4ywEKoWJx0/gdEaXDTij5ZhQ= +github.com/projectsveltos/libsveltos v1.12.1-0.20260715200227-148c08381bed/go.mod h1:4/vcbYFCFE8uEGIHmltriAoHxdB8jgS2zI+9gruOfJ4= github.com/projectsveltos/lua-utils/glua-json v0.0.0-20251212200258-2b3cdcb7c0f5 h1:khnc+994UszxZYu69J+R5FKiLA/Nk1JQj0EYAkwTWz0= github.com/projectsveltos/lua-utils/glua-json v0.0.0-20251212200258-2b3cdcb7c0f5/go.mod h1:yVL8KQFa9tmcxgwl9nwIMtKgtmIVC1zaFRSCfOwYvPY= github.com/projectsveltos/lua-utils/glua-runes v0.0.0-20251212200258-2b3cdcb7c0f5 h1:YbsebwRwTRhV8QacvEAdFqxcxHdeu7JTVtsBovbkgos= diff --git a/manifest/deployment-agentless.yaml b/manifest/deployment-agentless.yaml index 246459a8..5504187b 100644 --- a/manifest/deployment-agentless.yaml +++ b/manifest/deployment-agentless.yaml @@ -26,7 +26,7 @@ spec: - --shard-key= - --capi-onboard-annotation= - --v=5 - - --version=v1.12.0 + - --version=main - --agent-in-mgmt-cluster=true command: - /manager @@ -43,7 +43,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: docker.io/projectsveltos/addon-controller:v1.12.0 + image: docker.io/projectsveltos/addon-controller:main livenessProbe: failureThreshold: 3 httpGet: @@ -101,7 +101,7 @@ spec: fieldPath: metadata.namespace - name: IS_INITIALIZATION value: "true" - image: docker.io/projectsveltos/addon-controller:v1.12.0 + image: docker.io/projectsveltos/addon-controller:main name: initialization securityContext: allowPrivilegeEscalation: false diff --git a/manifest/deployment-shard.yaml b/manifest/deployment-shard.yaml index ebc1c69c..c17ef18d 100644 --- a/manifest/deployment-shard.yaml +++ b/manifest/deployment-shard.yaml @@ -26,7 +26,7 @@ spec: - --shard-key={{.SHARD}} - --capi-onboard-annotation= - --v=5 - - --version=v1.12.0 + - --version=main - --agent-in-mgmt-cluster=false command: - /manager @@ -43,7 +43,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: docker.io/projectsveltos/addon-controller:v1.12.0 + image: docker.io/projectsveltos/addon-controller:main livenessProbe: failureThreshold: 3 httpGet: @@ -101,7 +101,7 @@ spec: fieldPath: metadata.namespace - name: IS_INITIALIZATION value: "true" - image: docker.io/projectsveltos/addon-controller:v1.12.0 + image: docker.io/projectsveltos/addon-controller:main name: initialization securityContext: allowPrivilegeEscalation: false diff --git a/manifest/manifest.yaml b/manifest/manifest.yaml index e5dccb8b..1d6ecf20 100644 --- a/manifest/manifest.yaml +++ b/manifest/manifest.yaml @@ -1076,6 +1076,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + KustomizationRef when an update is rejected with an error that only a delete+recreate + can resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -1373,6 +1381,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + PolicyRef when an update is rejected with an error that only a delete+recreate can + resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -3271,6 +3287,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + KustomizationRef when an update is rejected with an error that only a delete+recreate + can resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -3568,6 +3592,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + PolicyRef when an update is rejected with an error that only a delete+recreate can + resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -4785,6 +4817,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + PolicyRef when an update is rejected with an error that only a delete+recreate can + resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -5147,6 +5187,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + PolicyRef when an update is rejected with an error that only a delete+recreate can + resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -6484,6 +6532,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + KustomizationRef when an update is rejected with an error that only a delete+recreate + can resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -6781,6 +6837,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + PolicyRef when an update is rejected with an error that only a delete+recreate can + resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -8710,6 +8774,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + KustomizationRef when an update is rejected with an error that only a delete+recreate + can resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -9007,6 +9079,14 @@ spec: - Local - Remote type: string + force: + default: false + description: |- + Force indicates whether Sveltos should delete and recreate a resource defined in this + PolicyRef when an update is rejected with an error that only a delete+recreate can + resolve (eg an invalid combination of fields, or a field enforced as immutable). + By default, such errors are surfaced instead of recreating the resource. + type: boolean kind: description: |- Kind of the resource. Supported kinds are: @@ -10646,7 +10726,7 @@ spec: - --shard-key= - --capi-onboard-annotation= - --v=5 - - --version=v1.12.0 + - --version=main - --agent-in-mgmt-cluster=false command: - /manager @@ -10663,7 +10743,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: docker.io/projectsveltos/addon-controller:v1.12.0 + image: docker.io/projectsveltos/addon-controller:main livenessProbe: failureThreshold: 3 httpGet: @@ -10721,7 +10801,7 @@ spec: fieldPath: metadata.namespace - name: IS_INITIALIZATION value: "true" - image: docker.io/projectsveltos/addon-controller:v1.12.0 + image: docker.io/projectsveltos/addon-controller:main name: initialization securityContext: allowPrivilegeEscalation: false diff --git a/pkg/drift-detection/drift-detection-manager-in-mgmt-cluster.go b/pkg/drift-detection/drift-detection-manager-in-mgmt-cluster.go index 30fbcb9d..f68fbab6 100644 --- a/pkg/drift-detection/drift-detection-manager-in-mgmt-cluster.go +++ b/pkg/drift-detection/drift-detection-manager-in-mgmt-cluster.go @@ -44,7 +44,7 @@ spec: - --cluster-type= - --current-cluster=management-cluster - --run-mode=do-not-send-updates - - --version=v1.12.0 + - --version=main command: - /manager env: @@ -60,7 +60,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: docker.io/projectsveltos/drift-detection-manager@sha256:b86be2bb11c8e417c68446b79866d86955847910df284e626c454ce5a6e60022 + image: docker.io/projectsveltos/drift-detection-manager@sha256:651ef898a5116d07f74fec85f533927f74af56f36422efed926d69ab481ff36d livenessProbe: failureThreshold: 3 httpGet: diff --git a/pkg/drift-detection/drift-detection-manager-in-mgmt-cluster.yaml b/pkg/drift-detection/drift-detection-manager-in-mgmt-cluster.yaml index 494f02a9..7a932eb7 100644 --- a/pkg/drift-detection/drift-detection-manager-in-mgmt-cluster.yaml +++ b/pkg/drift-detection/drift-detection-manager-in-mgmt-cluster.yaml @@ -26,7 +26,7 @@ spec: - --cluster-type= - --current-cluster=management-cluster - --run-mode=do-not-send-updates - - --version=v1.12.0 + - --version=main command: - /manager env: @@ -42,7 +42,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: docker.io/projectsveltos/drift-detection-manager@sha256:b86be2bb11c8e417c68446b79866d86955847910df284e626c454ce5a6e60022 + image: docker.io/projectsveltos/drift-detection-manager@sha256:651ef898a5116d07f74fec85f533927f74af56f36422efed926d69ab481ff36d livenessProbe: failureThreshold: 3 httpGet: diff --git a/pkg/drift-detection/drift-detection-manager.go b/pkg/drift-detection/drift-detection-manager.go index 519fe618..41b78d20 100644 --- a/pkg/drift-detection/drift-detection-manager.go +++ b/pkg/drift-detection/drift-detection-manager.go @@ -146,7 +146,7 @@ spec: - --cluster-type= - --current-cluster=managed-cluster - --run-mode=do-not-send-updates - - --version=v1.12.0 + - --version=main command: - /manager env: @@ -162,7 +162,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: docker.io/projectsveltos/drift-detection-manager@sha256:b86be2bb11c8e417c68446b79866d86955847910df284e626c454ce5a6e60022 + image: docker.io/projectsveltos/drift-detection-manager@sha256:651ef898a5116d07f74fec85f533927f74af56f36422efed926d69ab481ff36d livenessProbe: failureThreshold: 3 httpGet: diff --git a/pkg/drift-detection/drift-detection-manager.yaml b/pkg/drift-detection/drift-detection-manager.yaml index bea48347..27070b94 100644 --- a/pkg/drift-detection/drift-detection-manager.yaml +++ b/pkg/drift-detection/drift-detection-manager.yaml @@ -128,7 +128,7 @@ spec: - --cluster-type= - --current-cluster=managed-cluster - --run-mode=do-not-send-updates - - --version=v1.12.0 + - --version=main command: - /manager env: @@ -144,7 +144,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: docker.io/projectsveltos/drift-detection-manager@sha256:b86be2bb11c8e417c68446b79866d86955847910df284e626c454ce5a6e60022 + image: docker.io/projectsveltos/drift-detection-manager@sha256:651ef898a5116d07f74fec85f533927f74af56f36422efed926d69ab481ff36d livenessProbe: failureThreshold: 3 httpGet: diff --git a/test/fv/drift_test.go b/test/fv/drift_test.go index 79eeee07..cd444b29 100644 --- a/test/fv/drift_test.go +++ b/test/fv/drift_test.go @@ -43,74 +43,50 @@ const ( ) var ( - labelsValues = `customLabels: + // podAnnotationsValuesTemplate is applied (via ValuesFrom) only to podinfoIgnoredRelease. + // It exercises the same "Values templated from Cluster annotation" mechanism the drift + // test relied on, retargeted from a chart-wide customLabels key (kyverno-specific) to + // podinfo's podAnnotations key, which lands on the pod template's annotations. + podAnnotationsValuesTemplate = `podAnnotations: %s: "{{ .Cluster.metadata.annotations.cluster }}"` - cleanupControllerValues = `cleanupController: - livenessProbe: - httpGet: - path: /health/liveness - port: 9443 - scheme: HTTPS - initialDelaySeconds: 16 + // probesValuesTemplate is shared (via ValuesFrom) by both podinfo releases. + probesValuesTemplate = `probes: + liveness: periodSeconds: %d - timeoutSeconds: 5 - failureThreshold: 2 - successThreshold: 1 - - readinessProbe: - httpGet: - path: /health/readiness - port: 9443 - scheme: HTTPS - initialDelaySeconds: 6 - periodSeconds: %d - timeoutSeconds: 5 - failureThreshold: 6 - successThreshold: 1` - - admissionControllerValues = `admissionController: - livenessProbe: - httpGet: - path: /health/liveness - port: 9443 - scheme: HTTPS - initialDelaySeconds: 16 - periodSeconds: %d - timeoutSeconds: 5 - failureThreshold: 2 - successThreshold: 1 - - readinessProbe: - httpGet: - path: /health/readiness - port: 9443 - scheme: HTTPS - initialDelaySeconds: 6 - periodSeconds: %d - timeoutSeconds: 5 - failureThreshold: 6 - successThreshold: 1` + readiness: + periodSeconds: %d` ) const ( - kyvernoNamespace = "kyverno" - admissionControllerDeplName = "kyverno-admission-controller" - cleanupControllerDeplName = "kyverno-cleanup-controller" - cleanupImage = "reg.kyverno.io/kyverno/cleanup-controller:v1.17.1" - admissionImage = "reg.kyverno.io/kyverno/kyverno:v1.17.1" + podinfoDriftNamespace = "podinfo-drift" + podinfoDriftRepoURL = "https://stefanprodan.github.io/podinfo" + podinfoDriftRepoName = "podinfo" + podinfoDriftChartName = "podinfo/podinfo" + podinfoDriftChartVersion = "6.14.0" + podinfoDriftImageRepo = "docker.io/stefanprodan/podinfo" + podinfoBaselineTag = "6.13.0" + + // podinfoIgnoredRelease plays the role the admission-controller Deployment used to: + // fully ignored for configuration drift via a projectsveltos.io/driftDetectionIgnore patch. + podinfoIgnoredRelease = "podinfo-ignored" + podinfoIgnoredDriftTag = "6.7.1" + + // podinfoExcludedRelease plays the role the cleanup-controller Deployment used to: + // only /spec/replicas is excluded from drift detection, everything else is not. + podinfoExcludedRelease = "podinfo-excluded" + podinfoExcludedDriftTag = "6.7.0" ) -var _ = Describe("Helm", Serial, func() { +var _ = Describe("Helm", func() { const ( - namePrefix = "drift-" - kyvernoCleanupImageName = "controller" + namePrefix = "drift-" ) It("React to configuration drift and verifies Values/ValuesFrom", Label("FV", "PULLMODE", "EXTENDED"), func() { tagValue := randomString() - // Annotation is used to instantiate ConfigMap with labelsValues used in ValuesFrom + // Annotation is used to instantiate ConfigMap with podAnnotationsValuesTemplate used in ValuesFrom Byf("Add annotation %s: %s on cluster %s/%s", clusterKey, tagValue, kindWorkloadCluster.GetNamespace(), kindWorkloadCluster.GetName()) setAnnotationOnCluster(clusterKey, tagValue) @@ -149,71 +125,76 @@ var _ = Describe("Helm", Serial, func() { } Expect(k8sClient.Create(context.TODO(), ns)) - Byf("Creating ConfigMap to hold cleanup controller values") - cleanupControllerConfigMap := createConfigMapWithPolicy(configMapNamespace, randomString(), - fmt.Sprintf(cleanupControllerValues, livenessPeriodSecond, readinessPeriodSecond)) - Expect(k8sClient.Create(context.TODO(), cleanupControllerConfigMap)).To(Succeed()) + Byf("Creating ConfigMap to hold probes values (shared by both podinfo releases)") + probesConfigMap := createConfigMapWithPolicy(configMapNamespace, randomString(), + fmt.Sprintf(probesValuesTemplate, livenessPeriodSecond, readinessPeriodSecond)) + Expect(k8sClient.Create(context.TODO(), probesConfigMap)).To(Succeed()) - Byf("Creating ConfigMap to hold admission controller values") - admissionControllerConfigMap := createConfigMapWithPolicy(configMapNamespace, randomString(), - fmt.Sprintf(admissionControllerValues, livenessPeriodSecond, readinessPeriodSecond)) - Expect(k8sClient.Create(context.TODO(), admissionControllerConfigMap)).To(Succeed()) - - Byf("Creating ConfigMap to hold labels controller values (with template annotation)") - labelsConfigMap := createConfigMapWithPolicy(configMapNamespace, randomString(), - fmt.Sprintf(labelsValues, clusterKey)) - labelsConfigMap.Annotations = map[string]string{ + Byf("Creating ConfigMap to hold podAnnotations values (with template annotation)") + podAnnotationsConfigMap := createConfigMapWithPolicy(configMapNamespace, randomString(), + fmt.Sprintf(podAnnotationsValuesTemplate, clusterKey)) + podAnnotationsConfigMap.Annotations = map[string]string{ libsveltosv1beta1.PolicyTemplateAnnotation: annotationOkValue, } - Expect(k8sClient.Create(context.TODO(), labelsConfigMap)).To(Succeed()) + Expect(k8sClient.Create(context.TODO(), podAnnotationsConfigMap)).To(Succeed()) Byf("Update ClusterProfile %s to deploy helm charts", clusterProfile.Name) - By("use driftExclusion to ignore cleanup controller spec/replicas changes") - By("Use patches to add projectsveltos.io/driftDetectionIgnore annotation") + By("use driftExclusion to ignore podinfo-excluded's spec/replicas changes") + By("Use patches to add projectsveltos.io/driftDetectionIgnore annotation to podinfo-ignored") currentClusterProfile := &configv1beta1.ClusterProfile{} + baselineImageValues := fmt.Sprintf(`replicaCount: 1 +image: + repository: %s + tag: %s`, podinfoDriftImageRepo, podinfoBaselineTag) + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: clusterProfile.Name}, currentClusterProfile)).To(Succeed()) currentClusterProfile.Spec.HelmCharts = []configv1beta1.HelmChart{ { - RepositoryURL: kyvernoRepoURL, - RepositoryName: kyvernoNamespace, - ChartName: kyvernoChartName, - ChartVersion: kyvernoVersion372, - ReleaseName: kyvernoLatestRelease, - ReleaseNamespace: kyvernoNamespace, + RepositoryURL: podinfoDriftRepoURL, + RepositoryName: podinfoDriftRepoName, + ChartName: podinfoDriftChartName, + ChartVersion: podinfoDriftChartVersion, + ReleaseName: podinfoIgnoredRelease, + ReleaseNamespace: podinfoDriftNamespace, HelmChartAction: configv1beta1.HelmChartActionInstall, - Values: `admissionController: - replicas: 1 -backgroundController: - replicas: 1 -cleanupController: - replicas: 1 -reportsController: - replicas: 1`, + Values: baselineImageValues, ValuesFrom: []configv1beta1.ValueFrom{ { Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), - Namespace: labelsConfigMap.Namespace, - Name: labelsConfigMap.Name, + Namespace: podAnnotationsConfigMap.Namespace, + Name: podAnnotationsConfigMap.Name, }, { Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), - Namespace: cleanupControllerConfigMap.Namespace, - Name: cleanupControllerConfigMap.Name, + Namespace: probesConfigMap.Namespace, + Name: probesConfigMap.Name, }, + }, + }, + { + RepositoryURL: podinfoDriftRepoURL, + RepositoryName: podinfoDriftRepoName, + ChartName: podinfoDriftChartName, + ChartVersion: podinfoDriftChartVersion, + ReleaseName: podinfoExcludedRelease, + ReleaseNamespace: podinfoDriftNamespace, + HelmChartAction: configv1beta1.HelmChartActionInstall, + Values: baselineImageValues, + ValuesFrom: []configv1beta1.ValueFrom{ { Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), - Namespace: admissionControllerConfigMap.Namespace, - Name: admissionControllerConfigMap.Name, + Namespace: probesConfigMap.Namespace, + Name: probesConfigMap.Name, }, }, }, } - By("use driftExclusion to ignore cleanup controller spec/replicas changes") + By("use driftExclusion to ignore podinfo-excluded's spec/replicas changes") currentClusterProfile.Spec.DriftExclusions = []libsveltosv1beta1.DriftExclusion{ { Paths: []string{"/spec/replicas"}, @@ -221,8 +202,8 @@ reportsController: Kind: kindDeployment, Group: appsGroupName, Version: apiVersionV1, - Namespace: kyvernoNamespace, - Name: cleanupControllerDeplName, + Namespace: podinfoDriftNamespace, + Name: podinfoExcludedRelease, }, }, } @@ -236,8 +217,8 @@ reportsController: Group: appsGroupName, Version: apiVersionV1, Kind: kindDeployment, - Namespace: kyvernoNamespace, - Name: admissionControllerDeplName, + Namespace: podinfoDriftNamespace, + Name: podinfoIgnoredRelease, }, }, } @@ -262,25 +243,25 @@ reportsController: Expect(workloadClient).ToNot(BeNil()) expectedReplicas := int32(1) - Byf("Verifying Kyverno deployment %s/%s is created in the workload cluster (with label %s/%s and replicas %d)", - kyvernoNamespace, admissionControllerDeplName, clusterKey, tagValue, expectedReplicas) + Byf("Verifying podinfo deployment %s/%s is created in the workload cluster (with annotation %s/%s and replicas %d)", + podinfoDriftNamespace, podinfoIgnoredRelease, clusterKey, tagValue, expectedReplicas) Eventually(func() bool { depl := &appsv1.Deployment{} err = workloadClient.Get(context.TODO(), - types.NamespacedName{Namespace: kyvernoNamespace, Name: admissionControllerDeplName}, depl) + types.NamespacedName{Namespace: podinfoDriftNamespace, Name: podinfoIgnoredRelease}, depl) if err != nil { return false } - if !isDeplLabelCorrect(depl.Labels, clusterKey, tagValue) { + if !hasAnnotation(depl.Spec.Template.Annotations, clusterKey, tagValue) { return false } return depl.Spec.Replicas != nil && *depl.Spec.Replicas == expectedReplicas }, timeout, pollingInterval).Should(BeTrue()) Byf("Verifying helm values") - verifyHelmValues(workloadClient, kyvernoNamespace, admissionControllerDeplName, + verifyHelmValues(workloadClient, podinfoDriftNamespace, podinfoIgnoredRelease, livenessPeriodSecond, readinessPeriodSecond) - verifyHelmValues(workloadClient, kyvernoNamespace, cleanupControllerDeplName, + verifyHelmValues(workloadClient, podinfoDriftNamespace, podinfoExcludedRelease, livenessPeriodSecond, readinessPeriodSecond) verifyDriftDetectionManagerDeployment(workloadClient) @@ -289,7 +270,8 @@ reportsController: verifyFeatureStatusIsProvisioned(kindWorkloadCluster.GetNamespace(), clusterSummary.Name, libsveltosv1beta1.FeatureHelm) charts := []configv1beta1.Chart{ - {ReleaseName: kyvernoLatestRelease, ChartVersion: kyvernoVersion372S, Namespace: kyvernoNamespace}, + {ReleaseName: podinfoIgnoredRelease, ChartVersion: podinfoDriftChartVersion, Namespace: podinfoDriftNamespace}, + {ReleaseName: podinfoExcludedRelease, ChartVersion: podinfoDriftChartVersion, Namespace: podinfoDriftNamespace}, } verifyClusterConfiguration(configv1beta1.ClusterProfileKind, clusterProfile.Name, @@ -309,145 +291,88 @@ reportsController: const sleepTime = 30 time.Sleep(sleepTime * time.Second) - // Change Kyverno image + // Change podinfo-excluded's image (this Deployment only has /spec/replicas excluded, + // so Sveltos is expected to revert this) depl := &appsv1.Deployment{} Expect(workloadClient.Get(context.TODO(), - types.NamespacedName{Namespace: kyvernoNamespace, Name: cleanupControllerDeplName}, depl)).To(Succeed()) - imageChanged := false - for i := range depl.Spec.Template.Spec.Containers { - if depl.Spec.Template.Spec.Containers[i].Name == kyvernoCleanupImageName { - imageChanged = true - depl.Spec.Template.Spec.Containers[i].Image = cleanupImage - } - } - Expect(imageChanged).To(BeTrue()) + types.NamespacedName{Namespace: podinfoDriftNamespace, Name: podinfoExcludedRelease}, depl)).To(Succeed()) + Expect(len(depl.Spec.Template.Spec.Containers)).To(Equal(1)) + depl.Spec.Template.Spec.Containers[0].Image = fmt.Sprintf("%s:%s", podinfoDriftImageRepo, podinfoExcludedDriftTag) Expect(workloadClient.Update(context.TODO(), depl)).To(Succeed()) Expect(workloadClient.Get(context.TODO(), - types.NamespacedName{Namespace: kyvernoNamespace, Name: cleanupControllerDeplName}, depl)).To(Succeed()) - for i := range depl.Spec.Template.Spec.Containers { - if depl.Spec.Template.Spec.Containers[i].Name == kyvernoCleanupImageName { - By("Kyverno image is set to v1.17.1") - Expect(depl.Spec.Template.Spec.Containers[i].Image).To(Equal(cleanupImage)) - } - } + types.NamespacedName{Namespace: podinfoDriftNamespace, Name: podinfoExcludedRelease}, depl)).To(Succeed()) + By("podinfo-excluded image is set to the drift tag") + Expect(depl.Spec.Template.Spec.Containers[0].Image).To(Equal(fmt.Sprintf("%s:%s", podinfoDriftImageRepo, podinfoExcludedDriftTag))) Byf("Verifying Sveltos reacts to drift configuration change") Eventually(func() bool { depl := &appsv1.Deployment{} err = workloadClient.Get(context.TODO(), - types.NamespacedName{Namespace: kyvernoNamespace, Name: cleanupControllerDeplName}, depl) + types.NamespacedName{Namespace: podinfoDriftNamespace, Name: podinfoExcludedRelease}, depl) if err != nil { return false } - for i := range depl.Spec.Template.Spec.Containers { - if depl.Spec.Template.Spec.Containers[i].Name == kyvernoCleanupImageName { - return depl.Spec.Template.Spec.Containers[i].Image == "reg.kyverno.io/kyverno/cleanup-controller:v1.17.2" - } - } - return false + return depl.Spec.Template.Spec.Containers[0].Image == fmt.Sprintf("%s:%s", podinfoDriftImageRepo, podinfoBaselineTag) }, timeout, pollingInterval).Should(BeTrue()) - By("Kyverno image is reset to v1.17.2") + By("podinfo-excluded image is reset to the baseline tag") Byf("Verifying ClusterSummary %s status is set to Deployed for Helm feature", clusterSummary.Name) verifyFeatureStatusIsProvisioned(kindWorkloadCluster.GetNamespace(), clusterSummary.Name, libsveltosv1beta1.FeatureHelm) - // Change Kyverno image for admission controller + // Change podinfo-ignored's image (this Deployment has the driftDetectionIgnore + // annotation, so Sveltos is expected to leave this alone) Expect(workloadClient.Get(context.TODO(), - types.NamespacedName{Namespace: kyvernoNamespace, Name: admissionControllerDeplName}, depl)).To(Succeed()) - imageChanged = false - for i := range depl.Spec.Template.Spec.Containers { - if depl.Spec.Template.Spec.Containers[i].Name == kyvernoNamespace { - imageChanged = true - depl.Spec.Template.Spec.Containers[i].Image = admissionImage - } - } - Expect(imageChanged).To(BeTrue()) + types.NamespacedName{Namespace: podinfoDriftNamespace, Name: podinfoIgnoredRelease}, depl)).To(Succeed()) + Expect(len(depl.Spec.Template.Spec.Containers)).To(Equal(1)) + depl.Spec.Template.Spec.Containers[0].Image = fmt.Sprintf("%s:%s", podinfoDriftImageRepo, podinfoIgnoredDriftTag) Expect(workloadClient.Update(context.TODO(), depl)).To(Succeed()) Eventually(func() bool { err = workloadClient.Get(context.TODO(), - types.NamespacedName{Namespace: kyvernoNamespace, Name: admissionControllerDeplName}, depl) + types.NamespacedName{Namespace: podinfoDriftNamespace, Name: podinfoIgnoredRelease}, depl) if err != nil { return false } - for i := range depl.Spec.Template.Spec.Containers { - if depl.Spec.Template.Spec.Containers[i].Name == kyvernoNamespace { - By("Kyverno image is set to v1.17.1") - return depl.Spec.Template.Spec.Containers[i].Image == admissionImage - } - } - return false + By("podinfo-ignored image is set to the drift tag") + return depl.Spec.Template.Spec.Containers[0].Image == fmt.Sprintf("%s:%s", podinfoDriftImageRepo, podinfoIgnoredDriftTag) }, timeout, pollingInterval).Should(BeTrue()) Byf("Verifying ClusterSummary %s status is set to Deployed for Helm feature", clusterSummary.Name) verifyFeatureStatusIsProvisioned(kindWorkloadCluster.GetNamespace(), clusterSummary.Name, libsveltosv1beta1.FeatureHelm) - Byf("Verifying Sveltos does not reacts to drift configuration change as admission controller has ignore annotation") + Byf("Verifying Sveltos does not reacts to drift configuration change as podinfo-ignored has ignore annotation") Consistently(func() bool { depl := &appsv1.Deployment{} err = workloadClient.Get(context.TODO(), - types.NamespacedName{Namespace: kyvernoNamespace, Name: admissionControllerDeplName}, depl) + types.NamespacedName{Namespace: podinfoDriftNamespace, Name: podinfoIgnoredRelease}, depl) if err != nil { return false } - for i := range depl.Spec.Template.Spec.Containers { - if depl.Spec.Template.Spec.Containers[i].Name == kyvernoNamespace { - return depl.Spec.Template.Spec.Containers[i].Image == admissionImage - } - } - return false + return depl.Spec.Template.Spec.Containers[0].Image == fmt.Sprintf("%s:%s", podinfoDriftImageRepo, podinfoIgnoredDriftTag) }, timeout/4, pollingInterval).Should(BeTrue()) - By("Kyverno image is NOT reset to v1.17.2") + By("podinfo-ignored image is NOT reset to the baseline tag") By("Change values section") Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: clusterProfile.Name}, currentClusterProfile)).To(Succeed()) - currentClusterProfile.Spec.HelmCharts = []configv1beta1.HelmChart{ - { - RepositoryURL: kyvernoRepoURL, - RepositoryName: kyvernoNamespace, - ChartName: kyvernoChartName, - ChartVersion: kyvernoVersion371, - ReleaseName: kyvernoLatestRelease, - ReleaseNamespace: kyvernoNamespace, - HelmChartAction: configv1beta1.HelmChartActionInstall, - Values: `admissionController: - replicas: 3 -backgroundController: - replicas: 1 -cleanupController: - replicas: 1 -reportsController: - replicas: 1`, - ValuesFrom: []configv1beta1.ValueFrom{ - { - Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), - Namespace: labelsConfigMap.Namespace, - Name: labelsConfigMap.Name, - }, - { - Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), - Namespace: cleanupControllerConfigMap.Namespace, - Name: cleanupControllerConfigMap.Name, - }, - { - Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), - Namespace: admissionControllerConfigMap.Namespace, - Name: admissionControllerConfigMap.Name, - }, - }, - }, + for i := range currentClusterProfile.Spec.HelmCharts { + if currentClusterProfile.Spec.HelmCharts[i].ReleaseName == podinfoIgnoredRelease { + currentClusterProfile.Spec.HelmCharts[i].Values = fmt.Sprintf(`replicaCount: 3 +image: + repository: %s + tag: %s`, podinfoDriftImageRepo, podinfoBaselineTag) + } } Expect(k8sClient.Update(context.TODO(), currentClusterProfile)).To(Succeed()) - Byf("Verifying Kyverno deployment is updated in the workload cluster") + Byf("Verifying podinfo-ignored deployment is updated in the workload cluster") Eventually(func() bool { expectedReplicas := int32(3) depl := &appsv1.Deployment{} err = workloadClient.Get(context.TODO(), - types.NamespacedName{Namespace: kyvernoNamespace, Name: admissionControllerDeplName}, depl) + types.NamespacedName{Namespace: podinfoDriftNamespace, Name: podinfoIgnoredRelease}, depl) if err != nil { return false } @@ -465,25 +390,31 @@ reportsController: Byf("Verifying ClusterSummary %s status is set to Deployed for Helm feature", clusterSummary.Name) verifyFeatureStatusIsProvisioned(kindWorkloadCluster.GetNamespace(), clusterSummary.Name, libsveltosv1beta1.FeatureHelm) - Byf("Verifying Kyverno deployment %s/%s is update in the workload cluster with label %s:%s", - kyvernoNamespace, admissionControllerDeplName, clusterKey, tagValue) + Byf("Verifying podinfo-ignored deployment %s/%s is updated in the workload cluster with annotation %s:%s", + podinfoDriftNamespace, podinfoIgnoredRelease, clusterKey, tagValue) Eventually(func() bool { depl := &appsv1.Deployment{} err = workloadClient.Get(context.TODO(), - types.NamespacedName{Namespace: kyvernoNamespace, Name: admissionControllerDeplName}, depl) + types.NamespacedName{Namespace: podinfoDriftNamespace, Name: podinfoIgnoredRelease}, depl) if err != nil { return false } - return isDeplLabelCorrect(depl.Labels, clusterKey, tagValue) + return hasAnnotation(depl.Spec.Template.Annotations, clusterKey, tagValue) }, timeout, pollingInterval).Should(BeTrue()) deleteClusterProfile(clusterProfile) - Byf("Verifying Kyverno deployment is removed from workload cluster") + Byf("Verifying podinfo deployments are removed from workload cluster") + Eventually(func() bool { + depl := &appsv1.Deployment{} + err = workloadClient.Get(context.TODO(), + types.NamespacedName{Namespace: podinfoDriftNamespace, Name: podinfoIgnoredRelease}, depl) + return apierrors.IsNotFound(err) + }, timeout, pollingInterval).Should(BeTrue()) Eventually(func() bool { depl := &appsv1.Deployment{} err = workloadClient.Get(context.TODO(), - types.NamespacedName{Namespace: kyvernoNamespace, Name: kyvernoLatestRelease}, depl) + types.NamespacedName{Namespace: podinfoDriftNamespace, Name: podinfoExcludedRelease}, depl) return apierrors.IsNotFound(err) }, timeout, pollingInterval).Should(BeTrue()) @@ -532,11 +463,11 @@ func verifyHelmValues(workloadClient client.Client, deploymentNamespace, deploym Expect(depl.Spec.Template.Spec.Containers[0].LivenessProbe.PeriodSeconds).To(Equal(livenessPeriodSecond)) } -func isDeplLabelCorrect(lbls map[string]string, key, value string) bool { - if lbls == nil { +func hasAnnotation(annotations map[string]string, key, value string) bool { + if annotations == nil { return false } - v := lbls[key] + v := annotations[key] return v == value } @@ -595,18 +526,18 @@ func verifyResourceSummary(c client.Client, clusterSummary *configv1beta1.Cluste Expect(currentResourceSummary).ToNot(BeNil()) - verifyAdmissionControllerDeployment(c, currentResourceSummary, kindDeployment) + verifyIgnoredResourceSummary(c, currentResourceSummary, kindDeployment) verifySpecReplicas(currentResourceSummary, kindDeployment) } -func verifyAdmissionControllerDeployment(c client.Client, +func verifyIgnoredResourceSummary(c client.Client, currentResourceSummary *libsveltosv1beta1.ResourceSummary, deploymentKind string) { - // Patches has been configured to ignore admission controller for configuration + // Patches has been configured to ignore podinfo-ignored for configuration // drift (by adding annotation projectsveltos.io/driftDetectionIgnore) Byf("Verify deployment %s/%s is marked to be ignored for configuration drift", - kyvernoNamespace, admissionControllerDeplName) + podinfoDriftNamespace, podinfoIgnoredRelease) Eventually(func() bool { resourceSummaries := &libsveltosv1beta1.ResourceSummaryList{} err := c.List(context.TODO(), resourceSummaries) @@ -624,8 +555,8 @@ func verifyAdmissionControllerDeployment(c client.Client, for i := range currentResourceSummary.Spec.ChartResources { for j := range currentResourceSummary.Spec.ChartResources[i].Resources { r := ¤tResourceSummary.Spec.ChartResources[i].Resources[j] - if r.Kind == deploymentKind && r.Namespace == kyvernoNamespace && - r.Name == admissionControllerDeplName { + if r.Kind == deploymentKind && r.Namespace == podinfoDriftNamespace && + r.Name == podinfoIgnoredRelease { ignore = r.IgnoreForConfigurationDrift found = true @@ -640,8 +571,8 @@ func verifyAdmissionControllerDeployment(c client.Client, for i := range currentResourceSummary.Spec.ChartResources { for j := range currentResourceSummary.Spec.ChartResources[i].Resources { r := ¤tResourceSummary.Spec.ChartResources[i].Resources[j] - if r.Kind == deploymentKind && r.Namespace == kyvernoNamespace && - r.Name == admissionControllerDeplName { + if r.Kind == deploymentKind && r.Namespace == podinfoDriftNamespace && + r.Name == podinfoIgnoredRelease { continue } else { @@ -655,13 +586,13 @@ func verifySpecReplicas(currentResourceSummary *libsveltosv1beta1.ResourceSummar deploymentKind string) { found := false - // DriftExclusion has been configured to ignore cleanup controller spec/replicas + // DriftExclusion has been configured to ignore podinfo-excluded's spec/replicas Byf("Verify deployment %s/%s spec/replicas is marked to be ignored for configuration drift", - kyvernoNamespace, cleanupControllerDeplName) + podinfoDriftNamespace, podinfoExcludedRelease) for i := range currentResourceSummary.Spec.Patches { p := ¤tResourceSummary.Spec.Patches[i] - if p.Target.Kind == deploymentKind && p.Target.Namespace == kyvernoNamespace && - p.Target.Name == cleanupControllerDeplName { + if p.Target.Kind == deploymentKind && p.Target.Namespace == podinfoDriftNamespace && + p.Target.Name == podinfoExcludedRelease { Expect(p.Patch).To(ContainSubstring("/spec/replicas")) found = true diff --git a/test/fv/helm_error_test.go b/test/fv/helm_error_test.go index 784db8ec..66611a89 100644 --- a/test/fv/helm_error_test.go +++ b/test/fv/helm_error_test.go @@ -59,7 +59,7 @@ data: replicas: 2` ) -var _ = Describe("HelmSourceIntegrity", Serial, func() { +var _ = Describe("HelmSourceIntegrity", func() { const namePrefix = "helm-error-" It("An error in helm values does not remove helm chart deployed with GPG provenance verification", Label("FV", "PULLMODE"), func() { diff --git a/test/fv/paused_profile_test.go b/test/fv/paused_profile_test.go index 3b555779..112ab87f 100644 --- a/test/fv/paused_profile_test.go +++ b/test/fv/paused_profile_test.go @@ -49,7 +49,7 @@ var _ = Describe("Paused Profile", func() { types.NamespacedName{Name: clusterProfile.Name}, currentclusterProfile) return err == nil && len(currentclusterProfile.Status.MatchingClusterRefs) == 0 - }, timeout, pollingInterval).Should(BeTrue()) + }, timeout/2, pollingInterval).Should(BeTrue()) By("Remove the paused annotation from the ClusterProfile") currentclusterProfile := &configv1beta1.ClusterProfile{} diff --git a/test/fv/policy_ref_force_test.go b/test/fv/policy_ref_force_test.go new file mode 100644 index 00000000..0c2a956f --- /dev/null +++ b/test/fv/policy_ref_force_test.go @@ -0,0 +1,207 @@ +/* +Copyright 2026. projectsveltos.io. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fv_test + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" + + configv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" + "github.com/projectsveltos/addon-controller/lib/clusterops" + libsveltosv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" +) + +const ( + depNoStrategy = `apiVersion: apps/v1 +kind: Deployment +metadata: + name: %s + namespace: %s +spec: + replicas: 1 + selector: + matchLabels: + app: %s + template: + metadata: + labels: + app: %s + spec: + containers: + - name: main + image: nginx:latest` + + depRecreateStrategy = `apiVersion: apps/v1 +kind: Deployment +metadata: + name: %s + namespace: %s +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: %s + template: + metadata: + labels: + app: %s + spec: + containers: + - name: main + image: nginx:latest` +) + +// This test only runs in push mode: sveltos-applier does not yet act on +// ConfigurationBundle.Spec.Force in pull mode. +var _ = Describe("Feature", func() { + const ( + namePrefix = "policyref-force-" + ) + + It("Force lets Sveltos recreate a Deployment when a strategy change is rejected by the API server", + Label("NEW-FV", "NEW-FV-PULLMODE", "EXTENDED"), func() { + configMapNs := randomString() + Byf("Create configMap's namespace %s", configMapNs) + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: configMapNs, + }, + } + Expect(k8sClient.Create(context.TODO(), ns)).To(Succeed()) + + deploymentName := randomString() + Byf("Create a configMap with a Deployment (no strategy set)") + configMap := createConfigMapWithPolicy(configMapNs, namePrefix+randomString(), + fmt.Sprintf(depNoStrategy, deploymentName, configMapNs, deploymentName, deploymentName)) + Expect(k8sClient.Create(context.TODO(), configMap)).To(Succeed()) + + Byf("Create a ClusterProfile matching Cluster %s/%s", kindWorkloadCluster.GetNamespace(), kindWorkloadCluster.GetName()) + clusterProfile := getClusterProfile(namePrefix, map[string]string{key: value}) + clusterProfile.Spec.SyncMode = configv1beta1.SyncModeContinuous + Expect(k8sClient.Create(context.TODO(), clusterProfile)).To(Succeed()) + + verifyClusterProfileMatches(clusterProfile) + + verifyClusterSummary(clusterops.ClusterProfileLabelName, clusterProfile.Name, &clusterProfile.Spec, + kindWorkloadCluster.GetNamespace(), kindWorkloadCluster.GetName(), getClusterType()) + + Byf("Update ClusterProfile %s to reference ConfigMap %s/%s", clusterProfile.Name, configMap.Namespace, configMap.Name) + currentClusterProfile := &configv1beta1.ClusterProfile{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: clusterProfile.Name}, currentClusterProfile)).To(Succeed()) + currentClusterProfile.Spec.PolicyRefs = []configv1beta1.PolicyRef{ + { + Kind: string(libsveltosv1beta1.ConfigMapReferencedResourceKind), + Namespace: configMap.Namespace, + Name: configMap.Name, + }, + } + Expect(k8sClient.Update(context.TODO(), currentClusterProfile)).To(Succeed()) + + clusterSummary := verifyClusterSummary(clusterops.ClusterProfileLabelName, + currentClusterProfile.Name, ¤tClusterProfile.Spec, + kindWorkloadCluster.GetNamespace(), kindWorkloadCluster.GetName(), getClusterType()) + + Byf("Verifying ClusterSummary %s status is set to Provisioned for Resources feature", clusterSummary.Name) + verifyFeatureStatusIsProvisioned(clusterSummary.Namespace, clusterSummary.Name, libsveltosv1beta1.FeatureResources) + + Byf("Getting client to access the workload cluster") + workloadClient, err := getKindWorkloadClusterKubeconfig() + Expect(err).To(BeNil()) + Expect(workloadClient).ToNot(BeNil()) + + Byf("Verifying Deployment %s/%s is created in the workload cluster with a defaulted rollingUpdate strategy", + configMapNs, deploymentName) + Eventually(func() bool { + depl := &appsv1.Deployment{} + err = workloadClient.Get(context.TODO(), + types.NamespacedName{Namespace: configMapNs, Name: deploymentName}, depl) + if err != nil { + return false + } + return depl.Spec.Strategy.RollingUpdate != nil + }, timeout, pollingInterval).Should(BeTrue()) + + By("Updating ConfigMap to set the Deployment's strategy to Recreate") + currentConfigMap := &corev1.ConfigMap{} + Expect(k8sClient.Get(context.TODO(), + types.NamespacedName{Namespace: configMap.Namespace, Name: configMap.Name}, currentConfigMap)).To(Succeed()) + currentConfigMap = updateConfigMapWithPolicy(currentConfigMap, + fmt.Sprintf(depRecreateStrategy, deploymentName, configMapNs, deploymentName, deploymentName)) + Expect(k8sClient.Update(context.TODO(), currentConfigMap)).To(Succeed()) + + Byf("Verifying ClusterSummary %s reports a failure for Resources feature (rollingUpdate forbidden with Recreate)", + clusterSummary.Name) + Eventually(func() bool { + currentClusterSummary := &configv1beta1.ClusterSummary{} + err = k8sClient.Get(context.TODO(), + types.NamespacedName{Namespace: clusterSummary.Namespace, Name: clusterSummary.Name}, + currentClusterSummary) + if err != nil { + return false + } + for i := range currentClusterSummary.Status.FeatureSummaries { + fs := ¤tClusterSummary.Status.FeatureSummaries[i] + if fs.FeatureID == libsveltosv1beta1.FeatureResources { + return fs.FailureMessage != nil + } + } + return false + }, timeout, pollingInterval).Should(BeTrue()) + + By("Setting Force on the PolicyRef") + err = retry.RetryOnConflict(retry.DefaultRetry, func() error { + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: clusterProfile.Name}, + currentClusterProfile)).To(Succeed()) + currentClusterProfile.Spec.PolicyRefs[0].Force = true + return k8sClient.Update(context.TODO(), currentClusterProfile) + }) + Expect(err).To(BeNil()) + + Byf("Verifying ClusterSummary %s status is back to Provisioned for Resources feature", clusterSummary.Name) + verifyFeatureStatusIsProvisioned(clusterSummary.Namespace, clusterSummary.Name, libsveltosv1beta1.FeatureResources) + + Byf("Verifying Deployment %s/%s was recreated with strategy Recreate and no rollingUpdate", + configMapNs, deploymentName) + Eventually(func() bool { + depl := &appsv1.Deployment{} + err = workloadClient.Get(context.TODO(), + types.NamespacedName{Namespace: configMapNs, Name: deploymentName}, depl) + if err != nil { + return false + } + return depl.Spec.Strategy.Type == appsv1.RecreateDeploymentStrategyType && + depl.Spec.Strategy.RollingUpdate == nil + }, timeout, pollingInterval).Should(BeTrue()) + + deleteClusterProfile(clusterProfile) + + currentNs := &corev1.Namespace{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: configMapNs}, currentNs)).To(Succeed()) + Expect(k8sClient.Delete(context.TODO(), currentNs)).To(Succeed()) + }) +}) diff --git a/test/fv/test_constants_test.go b/test/fv/test_constants_test.go index b40dbb12..c1a08376 100644 --- a/test/fv/test_constants_test.go +++ b/test/fv/test_constants_test.go @@ -18,13 +18,19 @@ package fv_test const ( // Kyverno chart details - kyvernoRepoURL = "https://kyverno.github.io/kyverno/" - kyvernoChartName = "kyverno/kyverno" - kyvernoVersion372 = "v3.7.2" - kyvernoVersion372S = "3.7.2" - kyvernoVersion371 = "v3.7.1" - kyvernoVersion371S = "3.7.1" - kyvernoLatestRelease = "kyverno-latest" + kyvernoRepoURL = "https://kyverno.github.io/kyverno/" + kyvernoChartName = "kyverno/kyverno" + kyvernoNamespace = "kyverno" + admissionControllerDeplName = "kyverno-admission-controller" + kyvernoVersion382 = "v3.8.2" + kyvernoVersion382S = "3.8.2" + kyvernoVersion381 = "v3.8.1" + kyvernoVersion381S = "3.8.1" + kyvernoVersion372 = "v3.7.2" + kyvernoVersion372S = "3.7.2" + kyvernoVersion371 = "v3.7.1" + kyvernoVersion371S = "3.7.1" + kyvernoLatestRelease = "kyverno-latest" // Prometheus community chart details prometheusCommunityURL = "https://prometheus-community.github.io/helm-charts" diff --git a/test/fv/tier_test.go b/test/fv/tier_test.go index d36ec70b..b920f177 100644 --- a/test/fv/tier_test.go +++ b/test/fv/tier_test.go @@ -36,6 +36,23 @@ import ( var _ = Describe("Helm", Serial, func() { const ( namePrefix = "tier-" + + // kro is used here (instead of kyverno, used by other Helm FV tests) because its chart has + // no hooks: kyverno's post-upgrade hook made this test flaky, timing out whenever a hook's + // API call to the workload cluster hit a transient connection drop, since recovering from + // that costs a full extra reconcile+upgrade cycle on top of the fixed FV timeout. + kroRepoURL = "oci://registry.k8s.io/kro/charts" + kroRepoName = "kro-repo" + kroChartName = "kro" + kroNamespace = "kro-system" + kroReleaseName = "kro" + kroDeploymentName = "kro" + // kro's chart is pulled from OCI using the bare tag ("0.9.2"), but Sveltos reports the + // version recorded in the chart's own Chart.yaml ("v0.9.2") in ClusterConfiguration. + kroVersion092 = "0.9.2" + kroVersion092Reported = "v0.9.2" + kroVersion091 = "0.9.1" + kroVersion091Reported = "v0.9.1" ) It("Use tier to solve conflicts", Label("FV", "PULLMODE", "EXTENDED"), func() { @@ -57,12 +74,12 @@ var _ = Describe("Helm", Serial, func() { types.NamespacedName{Name: clusterProfile.Name}, currentClusterProfile)).To(Succeed()) currentClusterProfile.Spec.HelmCharts = []configv1beta1.HelmChart{ { - RepositoryURL: kyvernoRepoURL, - RepositoryName: kyvernoNamespace, - ChartName: kyvernoChartName, - ChartVersion: kyvernoVersion372, - ReleaseName: kyvernoLatestRelease, - ReleaseNamespace: kyvernoNamespace, + RepositoryURL: kroRepoURL, + RepositoryName: kroRepoName, + ChartName: kroChartName, + ChartVersion: kroVersion092, + ReleaseName: kroReleaseName, + ReleaseNamespace: kroNamespace, HelmChartAction: configv1beta1.HelmChartActionInstall, }, { @@ -103,18 +120,18 @@ var _ = Describe("Helm", Serial, func() { Expect(err).To(BeNil()) Expect(workloadClient).ToNot(BeNil()) - Byf("Verifying kyverno deployment is created in the workload cluster") + Byf("Verifying kro deployment is created in the workload cluster") Eventually(func() error { depl := &appsv1.Deployment{} return workloadClient.Get(context.TODO(), - types.NamespacedName{Namespace: kyvernoNamespace, Name: admissionControllerDeplName}, depl) + types.NamespacedName{Namespace: kroNamespace, Name: kroDeploymentName}, depl) }, timeout, pollingInterval).Should(BeNil()) Byf("Verifying ClusterSummary %s status is set to Deployed for Helm feature", clusterSummary.Name) verifyFeatureStatusIsProvisioned(kindWorkloadCluster.GetNamespace(), clusterSummary.Name, libsveltosv1beta1.FeatureHelm) charts := []configv1beta1.Chart{ - {ReleaseName: kyvernoLatestRelease, ChartVersion: kyvernoVersion372S, Namespace: kyvernoNamespace}, + {ReleaseName: kroReleaseName, ChartVersion: kroVersion092Reported, Namespace: kroNamespace}, {ReleaseName: grafanaRepoName, ChartVersion: grafanaVersion1000, Namespace: grafanaRepoName}, {ReleaseName: prometheusRelease, ChartVersion: prometheusVersion2739, Namespace: prometheusRelease}, } @@ -130,16 +147,16 @@ var _ = Describe("Helm", Serial, func() { verifyClusterProfileMatches(newClusterProfile) - Byf("Configuring the new clusterProfile to deploy Kyverno") + Byf("Configuring the new clusterProfile to deploy kro") Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: newClusterProfile.Name}, currentClusterProfile)).To(Succeed()) currentClusterProfile.Spec.HelmCharts = []configv1beta1.HelmChart{ { - RepositoryURL: kyvernoRepoURL, - RepositoryName: kyvernoNamespace, - ChartName: kyvernoChartName, - ChartVersion: kyvernoVersion371, - ReleaseName: kyvernoLatestRelease, - ReleaseNamespace: kyvernoNamespace, + RepositoryURL: kroRepoURL, + RepositoryName: kroRepoName, + ChartName: kroChartName, + ChartVersion: kroVersion091, + ReleaseName: kroReleaseName, + ReleaseNamespace: kroNamespace, HelmChartAction: configv1beta1.HelmChartActionInstall, }, } @@ -205,6 +222,9 @@ var _ = Describe("Helm", Serial, func() { return currentClusterSummary.Status.HelmReleaseSummaries[0].Status == configv1beta1.HelmChartStatusManaging }, timeout, pollingInterval).Should(BeTrue()) + Byf("Verifying ClusterSummary %s status is set to Deployed for Helm feature", newClusterSummary.Name) + verifyFeatureStatusIsProvisioned(kindWorkloadCluster.GetNamespace(), newClusterSummary.Name, libsveltosv1beta1.FeatureHelm) + charts = []configv1beta1.Chart{ {ReleaseName: grafanaRepoName, ChartVersion: grafanaVersion1000, Namespace: grafanaRepoName}, {ReleaseName: prometheusRelease, ChartVersion: prometheusVersion2739, Namespace: prometheusRelease}, @@ -215,14 +235,14 @@ var _ = Describe("Helm", Serial, func() { nil, charts) charts = []configv1beta1.Chart{ - {ReleaseName: kyvernoLatestRelease, ChartVersion: kyvernoVersion371S, Namespace: kyvernoNamespace}, + {ReleaseName: kroReleaseName, ChartVersion: kroVersion091Reported, Namespace: kroNamespace}, } verifyClusterConfiguration(configv1beta1.ClusterProfileKind, newClusterProfile.Name, newClusterSummary.Spec.ClusterNamespace, newClusterSummary.Spec.ClusterName, libsveltosv1beta1.FeatureHelm, nil, charts) - Byf("Changing ClusterProfile %s tier to 100 (above profile1 tier 90); profile1 should reclaim kyverno", newClusterProfile.Name) + Byf("Changing ClusterProfile %s tier to 100 (above profile1 tier 90); profile1 should reclaim kro", newClusterProfile.Name) Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: newClusterProfile.Name}, currentClusterProfile)).To(Succeed()) currentClusterProfile.Spec.Tier = 100 Expect(k8sClient.Update(context.TODO(), currentClusterProfile)).To(Succeed()) @@ -231,7 +251,7 @@ var _ = Describe("Helm", Serial, func() { currentClusterProfile.Name, ¤tClusterProfile.Spec, kindWorkloadCluster.GetNamespace(), kindWorkloadCluster.GetName(), getClusterType()) - Byf("Verifying ClusterProfile %s ClusterSummary reports conflict for kyverno", newClusterProfile.Name) + Byf("Verifying ClusterProfile %s ClusterSummary reports conflict for kro", newClusterProfile.Name) Eventually(func() bool { currentClusterSummary := &configv1beta1.ClusterSummary{} err = k8sClient.Get(context.TODO(), @@ -249,7 +269,7 @@ var _ = Describe("Helm", Serial, func() { verifyFeatureStatusIsProvisioned(kindWorkloadCluster.GetNamespace(), clusterSummary.Name, libsveltosv1beta1.FeatureHelm) charts = []configv1beta1.Chart{ - {ReleaseName: kyvernoLatestRelease, ChartVersion: kyvernoVersion372S, Namespace: kyvernoNamespace}, + {ReleaseName: kroReleaseName, ChartVersion: kroVersion092Reported, Namespace: kroNamespace}, {ReleaseName: grafanaRepoName, ChartVersion: grafanaVersion1000, Namespace: grafanaRepoName}, {ReleaseName: prometheusRelease, ChartVersion: prometheusVersion2739, Namespace: prometheusRelease}, } @@ -258,14 +278,14 @@ var _ = Describe("Helm", Serial, func() { clusterSummary.Spec.ClusterNamespace, clusterSummary.Spec.ClusterName, libsveltosv1beta1.FeatureHelm, nil, charts) - deleteClusterProfile(clusterProfile) deleteClusterProfile(newClusterProfile) + deleteClusterProfile(clusterProfile) - Byf("Verifying kyverno deployment is removed from workload cluster") + Byf("Verifying kro deployment is removed from workload cluster") Eventually(func() bool { depl := &appsv1.Deployment{} err = workloadClient.Get(context.TODO(), - types.NamespacedName{Namespace: kyvernoNamespace, Name: kyvernoLatestRelease}, depl) + types.NamespacedName{Namespace: kroNamespace, Name: kroDeploymentName}, depl) return apierrors.IsNotFound(err) }, timeout, pollingInterval).Should(BeTrue()) }) diff --git a/test/pullmode-sveltosapplier.yaml b/test/pullmode-sveltosapplier.yaml index 12d92083..f0cf7d10 100644 --- a/test/pullmode-sveltosapplier.yaml +++ b/test/pullmode-sveltosapplier.yaml @@ -99,7 +99,7 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace - image: docker.io/projectsveltos/sveltos-applier@sha256:62900d55e4a3d818150e0bc46cd1657219ba0614960acecc0f3b805baba3c74d + image: docker.io/projectsveltos/sveltos-applier@sha256:c9c08dc2679266aeed4afee16cca95aaa8281c31b7ffcb5122914e99123e8625 livenessProbe: failureThreshold: 3 httpGet: