Skip to content

Commit c644265

Browse files
authored
Merge pull request #83 from gianlucam76/stop-matching
Introduce StopMatchingBehavior
2 parents 26f041c + db0962c commit c644265

16 files changed

+515
-57
lines changed

Makefile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ kind-test: test create-cluster fv ## Build docker image; start kind cluster; loa
181181

182182
.PHONY: fv
183183
fv: $(GINKGO) ## Run Sveltos Controller tests using existing cluster
184-
cd test/fv; ../../$(GINKGO) -nodes $(NUM_NODES) --label-filter='FV1' --v --trace --randomize-all
184+
cd test/fv; ../../$(GINKGO) -nodes $(NUM_NODES) --label-filter='FV' --v --trace --randomize-all
185185

186186
.PHONY: create-cluster
187187
create-cluster: $(KIND) $(CLUSTERCTL) $(KUBECTL) $(ENVSUBST) ## Create a new kind cluster designed for development
@@ -224,10 +224,13 @@ delete-cluster: $(KIND) ## Deletes the kind cluster $(CONTROL_CLUSTER_NAME)
224224
#
225225
# add this target. It needs to be run only when changing cluster-api version. create-cluster target uses the output of this command which is stored within repo
226226
# It requires control cluster to exist. So first "make create-control-cluster" then run this target.
227+
# Once generated, remove
228+
# enforce: "{{ .podSecurityStandard.enforce }}"
229+
# enforce-version: "latest"
227230
create-clusterapi-kind-cluster-yaml: $(CLUSTERCTL)
228-
KUBERNETES_VERSION=$(K8S_VERSION) SERVICE_CIDR=["10.225.0.0/16"] POD_CIDR=["10.220.0.0/16"] $(CLUSTERCTL) generate cluster $(WORKLOAD_CLUSTER_NAME) --flavor development \
231+
ENABLE_POD_SECURITY_STANDARD="true" KUBERNETES_VERSION=$(K8S_VERSION) SERVICE_CIDR=["10.225.0.0/16"] POD_CIDR=["10.220.0.0/16"] $(CLUSTERCTL) generate cluster $(WORKLOAD_CLUSTER_NAME) --flavor development \
229232
--control-plane-machine-count=1 \
230-
--worker-machine-count=1 > $(KIND_CLUSTER_YAML)
233+
--worker-machine-count=2 > $(KIND_CLUSTER_YAML)
231234

232235
create-control-cluster:
233236
sed -e "s/K8S_VERSION/$(K8S_VERSION)/g" test/$(KIND_CONFIG) > test/$(KIND_CONFIG).tmp

api/v1alpha1/clusterprofile_types.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,18 @@ type HelmChart struct {
115115
HelmChartAction HelmChartAction `json:"helmChartAction,omitempty"`
116116
}
117117

118+
// StopMatchingBehavior indicates what will happen when Cluster stops matching
119+
// a ClusterProfile. By default, withdrawpolicies, deployed Helm charts and Kubernetes
120+
// resources will be removed from Cluster. LeavePolicy instead leaves Helm charts
121+
// and Kubernetes policies in the Cluster.
122+
type StopMatchingBehavior string
123+
124+
// Define the StopMatchingBehavior constants.
125+
const (
126+
WithdrawPolicies StopMatchingBehavior = "WithdrawPolicies"
127+
LeavePolicies StopMatchingBehavior = "LeavePolicies"
128+
)
129+
118130
// ClusterProfileSpec defines the desired state of ClusterProfile
119131
type ClusterProfileSpec struct {
120132
// ClusterSelector identifies ClusterAPI clusters to associate to.
@@ -127,10 +139,21 @@ type ClusterProfileSpec struct {
127139
// - Continuous means first time a workload cluster matches the ClusterProfile,
128140
// features will be deployed in such a cluster. Any subsequent feature configuration
129141
// change will be applied into the matching workload clusters.
142+
// - DryRun means no change will be propagated to any matching cluster. A report
143+
// instead will be generated summarizing what would happen in any matching cluster
144+
// because of the changes made to ClusterProfile while in DryRun mode.
130145
// +kubebuilder:default:=Continuous
131146
// +optional
132147
SyncMode SyncMode `json:"syncMode,omitempty"`
133148

149+
// StopMatchingBehavior indicates what behavior should be when a Cluster stop matching
150+
// the ClusterProfile. By default all deployed Helm charts and Kubernetes resources will
151+
// be withdrawn from Cluster. Setting StopMatchingBehavior to LeavePolicies will instead
152+
// leave ClusterProfile deployed policies in the Cluster.
153+
// +kubebuilder:default:=WithdrawPolicies
154+
// +optional
155+
StopMatchingBehavior StopMatchingBehavior `json:"stopMatchingBehavior,omitempty"`
156+
134157
// PolicyRefs references all the ConfigMaps containing kubernetes resources
135158
// that need to be deployed in the matching CAPI clusters.
136159
// +optional

config/crd/bases/config.projectsveltos.io_clusterprofiles.yaml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,14 @@ spec:
116116
- namespace
117117
type: object
118118
type: array
119+
stopMatchingBehavior:
120+
default: WithdrawPolicies
121+
description: StopMatchingBehavior indicates what behavior should be
122+
when a Cluster stop matching the ClusterProfile. By default all
123+
deployed Helm charts and Kubernetes resources will be withdrawn
124+
from Cluster. Setting StopMatchingBehavior to LeavePolicies will
125+
instead leave ClusterProfile deployed policies in the Cluster.
126+
type: string
119127
syncMode:
120128
default: Continuous
121129
description: SyncMode specifies how features are synced in a matching
@@ -125,7 +133,11 @@ spec:
125133
the matching workload clusters; - Continuous means first time a
126134
workload cluster matches the ClusterProfile, features will be deployed
127135
in such a cluster. Any subsequent feature configuration change will
128-
be applied into the matching workload clusters.
136+
be applied into the matching workload clusters. - DryRun means no
137+
change will be propagated to any matching cluster. A report instead
138+
will be generated summarizing what would happen in any matching
139+
cluster because of the changes made to ClusterProfile while in DryRun
140+
mode.
129141
enum:
130142
- OneTime
131143
- Continuous

config/crd/bases/config.projectsveltos.io_clustersummaries.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,14 @@ spec:
129129
- namespace
130130
type: object
131131
type: array
132+
stopMatchingBehavior:
133+
default: WithdrawPolicies
134+
description: StopMatchingBehavior indicates what behavior should
135+
be when a Cluster stop matching the ClusterProfile. By default
136+
all deployed Helm charts and Kubernetes resources will be withdrawn
137+
from Cluster. Setting StopMatchingBehavior to LeavePolicies
138+
will instead leave ClusterProfile deployed policies in the Cluster.
139+
type: string
132140
syncMode:
133141
default: Continuous
134142
description: SyncMode specifies how features are synced in a matching
@@ -139,6 +147,10 @@ spec:
139147
first time a workload cluster matches the ClusterProfile, features
140148
will be deployed in such a cluster. Any subsequent feature configuration
141149
change will be applied into the matching workload clusters.
150+
- DryRun means no change will be propagated to any matching
151+
cluster. A report instead will be generated summarizing what
152+
would happen in any matching cluster because of the changes
153+
made to ClusterProfile while in DryRun mode.
142154
enum:
143155
- OneTime
144156
- Continuous

config/manager/manager.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@ spec:
5959
port: 8081
6060
initialDelaySeconds: 5
6161
periodSeconds: 10
62-
# TODO(user): Configure the resources accordingly based on the project requirements.
63-
# More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
6462
resources:
6563
limits:
6664
cpu: 500m

controllers/export_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ var (
6969
GetPolicyInfo = getPolicyInfo
7070
UndeployStaleResources = undeployStaleResources
7171
GetDeployedGroupVersionKinds = getDeployedGroupVersionKinds
72+
CanDelete = canDelete
73+
HandleResourceDelete = handleResourceDelete
7274

7375
ResourcesHash = resourcesHash
7476
GetResourceRefs = getResourceRefs

controllers/handlers_helm.go

Lines changed: 68 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -135,47 +135,12 @@ func undeployHelmCharts(ctx context.Context, c client.Client,
135135
}
136136
defer os.Remove(kubeconfig)
137137

138-
chartManager, err := chartmanager.GetChartManagerInstance(ctx, c)
138+
var releaseReports []configv1alpha1.ReleaseReport
139+
releaseReports, err = uninstallHelmCharts(ctx, c, clusterSummary, kubeconfig, logger)
139140
if err != nil {
140141
return err
141142
}
142143

143-
releaseReports := make([]configv1alpha1.ReleaseReport, 0)
144-
for i := range clusterSummary.Spec.ClusterProfileSpec.HelmCharts {
145-
currentChart := &clusterSummary.Spec.ClusterProfileSpec.HelmCharts[i]
146-
if chartManager.CanManageChart(clusterSummary, currentChart) {
147-
logger.V(logs.LogInfo).Info(fmt.Sprintf("Uninstalling chart %s from repo %s %s)",
148-
currentChart.ChartName,
149-
currentChart.RepositoryURL,
150-
currentChart.RepositoryName))
151-
152-
// If another ClusterSummary is queued to manage this chart in this cluster, do not uninstall.
153-
// Let the other ClusterSummary take it over.
154-
if chartManager.GetNumberOfRegisteredClusterSummaries(clusterSummary.Spec.ClusterNamespace,
155-
clusterSummary.Spec.ClusterName, currentChart) > 1 {
156-
// Immediately unregister so next inline ClusterSummary can take this over
157-
chartManager.UnregisterClusterSummaryForChart(clusterSummary, currentChart)
158-
} else {
159-
err = doUninstallRelease(clusterSummary, currentChart, kubeconfig, logger)
160-
if err != nil {
161-
if !errors.Is(err, driver.ErrReleaseNotFound) {
162-
return err
163-
}
164-
}
165-
}
166-
167-
releaseReports = append(releaseReports, configv1alpha1.ReleaseReport{
168-
ReleaseNamespace: currentChart.ReleaseNamespace, ReleaseName: currentChart.ReleaseName,
169-
Action: string(configv1alpha1.UninstallHelmAction),
170-
})
171-
} else {
172-
releaseReports = append(releaseReports, configv1alpha1.ReleaseReport{
173-
ReleaseNamespace: currentChart.ReleaseNamespace, ReleaseName: currentChart.ReleaseName,
174-
Action: string(configv1alpha1.NoHelmAction), Message: "Currently managed by another ClusterProfile",
175-
})
176-
}
177-
}
178-
179144
// First get the helm releases currently managed and uninstall all the ones
180145
// not referenced anymore. Only if this operation succeeds, removes all stale
181146
// helm release registration for this clusterSummary.
@@ -201,6 +166,11 @@ func undeployHelmCharts(ctx context.Context, c client.Client,
201166
return err
202167
}
203168

169+
chartManager, err := chartmanager.GetChartManagerInstance(ctx, c)
170+
if err != nil {
171+
return err
172+
}
173+
204174
// In dry-run mode nothing gets deployed/undeployed. So if this instance used to manage
205175
// an helm release and it is now not referencing anymore, do not unsubscribe.
206176
if clusterSummary.Spec.ClusterProfileSpec.SyncMode != configv1alpha1.SyncModeDryRun {
@@ -211,6 +181,60 @@ func undeployHelmCharts(ctx context.Context, c client.Client,
211181
return &configv1alpha1.DryRunReconciliationError{}
212182
}
213183

184+
func uninstallHelmCharts(ctx context.Context, c client.Client, clusterSummary *configv1alpha1.ClusterSummary,
185+
kubeconfig string, logger logr.Logger) ([]configv1alpha1.ReleaseReport, error) {
186+
187+
chartManager, err := chartmanager.GetChartManagerInstance(ctx, c)
188+
if err != nil {
189+
return nil, err
190+
}
191+
192+
releaseReports := make([]configv1alpha1.ReleaseReport, 0)
193+
for i := range clusterSummary.Spec.ClusterProfileSpec.HelmCharts {
194+
currentChart := &clusterSummary.Spec.ClusterProfileSpec.HelmCharts[i]
195+
if chartManager.CanManageChart(clusterSummary, currentChart) {
196+
logger.V(logs.LogInfo).Info(fmt.Sprintf("Uninstalling chart %s from repo %s %s)",
197+
currentChart.ChartName,
198+
currentChart.RepositoryURL,
199+
currentChart.RepositoryName))
200+
201+
// If another ClusterSummary is queued to manage this chart in this cluster, do not uninstall.
202+
// Let the other ClusterSummary take it over.
203+
if chartManager.GetNumberOfRegisteredClusterSummaries(clusterSummary.Spec.ClusterNamespace,
204+
clusterSummary.Spec.ClusterName, currentChart) > 1 {
205+
// Immediately unregister so next inline ClusterSummary can take this over
206+
chartManager.UnregisterClusterSummaryForChart(clusterSummary, currentChart)
207+
} else {
208+
// If StopMatchingBehavior is LeavePolicies, do not uninstall helm charts
209+
if !clusterSummary.DeletionTimestamp.IsZero() &&
210+
clusterSummary.Spec.ClusterProfileSpec.StopMatchingBehavior == configv1alpha1.LeavePolicies {
211+
212+
logger.V(logs.LogInfo).Info("ClusterProfile StopMatchingBehavior set to LeavePolicies")
213+
} else {
214+
err = doUninstallRelease(clusterSummary, currentChart, kubeconfig, logger)
215+
if err != nil {
216+
if !errors.Is(err, driver.ErrReleaseNotFound) {
217+
return nil, err
218+
}
219+
}
220+
}
221+
}
222+
223+
releaseReports = append(releaseReports, configv1alpha1.ReleaseReport{
224+
ReleaseNamespace: currentChart.ReleaseNamespace, ReleaseName: currentChart.ReleaseName,
225+
Action: string(configv1alpha1.UninstallHelmAction),
226+
})
227+
} else {
228+
releaseReports = append(releaseReports, configv1alpha1.ReleaseReport{
229+
ReleaseNamespace: currentChart.ReleaseNamespace, ReleaseName: currentChart.ReleaseName,
230+
Action: string(configv1alpha1.NoHelmAction), Message: "Currently managed by another ClusterProfile",
231+
})
232+
}
233+
}
234+
235+
return releaseReports, nil
236+
}
237+
214238
func helmHash(ctx context.Context, c client.Client, clusterSummaryScope *scope.ClusterSummaryScope,
215239
logger logr.Logger) ([]byte, error) {
216240

@@ -364,8 +388,13 @@ func handleUpgrade(ctx context.Context, clusterSummary *configv1alpha1.ClusterSu
364388
if err != nil {
365389
return nil, err
366390
}
367-
message := fmt.Sprintf("Current version: %s. Would move to version: %s",
368-
currentRelease.ChartVersion, currentChart.ChartVersion)
391+
var message string
392+
if currentRelease.ChartVersion != currentChart.ChartVersion {
393+
message = fmt.Sprintf("Current version: %s. Would move to version: %s",
394+
currentRelease.ChartVersion, currentChart.ChartVersion)
395+
} else {
396+
message = fmt.Sprintf("No op, already at version: %s", currentRelease.ChartVersion)
397+
}
369398
report = &configv1alpha1.ReleaseReport{
370399
ReleaseNamespace: currentChart.ReleaseNamespace, ReleaseName: currentChart.ReleaseName,
371400
ChartVersion: currentChart.ChartVersion, Action: string(configv1alpha1.UpgradeHelmAction),

controllers/handlers_utils.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,9 @@ func undeployStaleResources(ctx context.Context, remoteConfig *rest.Config, c, r
489489
// If this ClusterSummary is the only OwnerReference and it is not deploying this policy anymore,
490490
// policy would be withdrawn
491491
if clusterSummary.Spec.ClusterProfileSpec.SyncMode == configv1alpha1.SyncModeDryRun {
492-
if canDelete(&r, currentPolicies) && isOnlyhOwnerReference(&r, clusterProfile) {
492+
if canDelete(&r, currentPolicies) && isOnlyhOwnerReference(&r, clusterProfile) &&
493+
!isLeavePolicies(clusterSummary, logger) {
494+
493495
undeployed = append(undeployed, configv1alpha1.ResourceReport{
494496
Resource: configv1alpha1.Resource{
495497
Kind: r.GetObjectKind().GroupVersionKind().Kind, Namespace: r.GetNamespace(), Name: r.GetName(),
@@ -509,7 +511,7 @@ func undeployStaleResources(ctx context.Context, remoteConfig *rest.Config, c, r
509511
}
510512

511513
if canDelete(&r, currentPolicies) {
512-
err = remoteClient.Delete(ctx, &r)
514+
err = handleResourceDelete(ctx, remoteClient, &r, clusterSummary, logger)
513515
if err != nil {
514516
return nil, err
515517
}
@@ -521,6 +523,23 @@ func undeployStaleResources(ctx context.Context, remoteConfig *rest.Config, c, r
521523
return undeployed, nil
522524
}
523525

526+
func handleResourceDelete(ctx context.Context, remoteClient client.Client, policy client.Object,
527+
clusterSummary *configv1alpha1.ClusterSummary, logger logr.Logger) error {
528+
529+
// If mode is set to LeavePolicies, leave policies in the workload cluster.
530+
// Remove all labels added by Sveltos.
531+
if isLeavePolicies(clusterSummary, logger) {
532+
labels := policy.GetLabels()
533+
delete(labels, ReferenceLabelKind)
534+
delete(labels, ReferenceLabelName)
535+
delete(labels, ReferenceLabelNamespace)
536+
policy.SetLabels(labels)
537+
return remoteClient.Update(ctx, policy)
538+
}
539+
540+
return remoteClient.Delete(ctx, policy)
541+
}
542+
524543
// canDelete returns true if a policy can be deleted. For a policy to be deleted:
525544
// - policy is not part of currentReferencedPolicies
526545
func canDelete(policy client.Object, currentReferencedPolicies map[string]configv1alpha1.Resource) bool {
@@ -533,9 +552,23 @@ func canDelete(policy client.Object, currentReferencedPolicies map[string]config
533552
if _, ok := currentReferencedPolicies[name]; ok {
534553
return false
535554
}
555+
536556
return true
537557
}
538558

559+
// isLeavePolicies returns true if:
560+
// - ClusterSummary is marked for deletion
561+
// - StopMatchingBehavior is set to LeavePolicies
562+
func isLeavePolicies(clusterSummary *configv1alpha1.ClusterSummary, logger logr.Logger) bool {
563+
if !clusterSummary.DeletionTimestamp.IsZero() &&
564+
clusterSummary.Spec.ClusterProfileSpec.StopMatchingBehavior == configv1alpha1.LeavePolicies {
565+
566+
logger.V(logs.LogInfo).Info("ClusterProfile StopMatchingBehavior set to LeavePolicies")
567+
return true
568+
}
569+
return false
570+
}
571+
539572
// hasLabel search if key is one of the label.
540573
// If value is empty, returns true if key is present.
541574
// If value is not empty, returns true if key is present and value is a match.

0 commit comments

Comments
 (0)