Skip to content

Commit e7b9e17

Browse files
committed
feat: add support for Prune/Delete=false sync option
For consistency with Prune/Delete=confirm that are now present as application level sync option add Prune/Delete=false as sync option too. Signed-off-by: Arthur Outhenin-Chalandre <[email protected]>
1 parent c983320 commit e7b9e17

File tree

5 files changed

+93
-0
lines changed

5 files changed

+93
-0
lines changed

controller/appcontroller.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,6 +1162,7 @@ func (ctrl *ApplicationController) removeProjectFinalizer(proj *appv1.AppProject
11621162
func (ctrl *ApplicationController) shouldBeDeleted(app *appv1.Application, obj *unstructured.Unstructured) bool {
11631163
return !kube.IsCRD(obj) && !isSelfReferencedApp(app, kube.GetObjectRef(obj)) &&
11641164
!resourceutil.HasAnnotationOption(obj, synccommon.AnnotationSyncOptions, synccommon.SyncOptionDisableDeletion) &&
1165+
(app.Spec.SyncPolicy == nil || !app.Spec.SyncPolicy.SyncOptions.HasOption(synccommon.SyncOptionDisableDeletion)) &&
11651166
!resourceutil.HasAnnotationOption(obj, helm.ResourcePolicyAnnotation, helm.ResourcePolicyKeep)
11661167
}
11671168

controller/appcontroller_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2360,6 +2360,16 @@ func Test_syncDeleteOption(t *testing.T) {
23602360
})
23612361
}
23622362

2363+
func Test_syncDeleteOptionGlobal(t *testing.T) {
2364+
app := newFakeApp()
2365+
app.Spec.SyncPolicy.SyncOptions = []string{"Delete=false"}
2366+
ctrl := newFakeController(&fakeData{apps: []runtime.Object{app}}, nil)
2367+
2368+
cm := newFakeCM()
2369+
cmObj := kube.MustToUnstructured(&cm)
2370+
assert.False(t, ctrl.shouldBeDeleted(app, cmObj))
2371+
}
2372+
23632373
func TestAddControllerNamespace(t *testing.T) {
23642374
t.Run("set controllerNamespace when the app is in the controller namespace", func(t *testing.T) {
23652375
app := newFakeApp()

controller/sync.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
388388
),
389389
sync.WithPruneConfirmed(app.IsDeletionConfirmed(state.StartedAt.Time)),
390390
sync.WithRequiresPruneConfirmation(syncOp.SyncOptions.HasOption(common.SyncOptionPruneRequireConfirm)),
391+
sync.WithPruneDisabled(syncOp.SyncOptions.HasOption(common.SyncOptionDisablePrune)),
391392
sync.WithSkipDryRunOnMissingResource(syncOp.SyncOptions.HasOption(common.SyncOptionSkipDryRunOnMissingResource)),
392393
}
393394

docs/user-guide/sync-options.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ metadata:
1414
argocd.argoproj.io/sync-options: Prune=false
1515
```
1616
17+
If you want to prevent any object from being pruned in the Application,
18+
it also can be enabled at the application level like in the example below:
19+
20+
```yaml
21+
apiVersion: argoproj.io/v1alpha1
22+
kind: Application
23+
spec:
24+
syncPolicy:
25+
syncOptions:
26+
- Prune=false
27+
```
28+
29+
Note that setting a Prune sync option on the application level will not override
30+
the same sync option set on a specific resource, both will still be applied.
31+
1732
The sync-status panel shows that pruning was skipped, and why:
1833
1934
![sync option no prune](../assets/sync-option-no-prune-sync-status.png)
@@ -101,6 +116,21 @@ metadata:
101116
argocd.argoproj.io/sync-options: Delete=false
102117
```
103118

119+
If you want to prevent any object from being deleted in the Application,
120+
it also can be enabled at the application level like in the example below:
121+
122+
```yaml
123+
apiVersion: argoproj.io/v1alpha1
124+
kind: Application
125+
spec:
126+
syncPolicy:
127+
syncOptions:
128+
- Delete=false
129+
```
130+
131+
Note that setting a Delete sync option on the application level will not override
132+
the same sync option set on a specific resource, both will still be applied.
133+
104134
## Resource Deletion With Confirmation
105135

106136
Resources such as Namespaces are critical and should not be deleted without confirmation. You can set the `Delete=confirm`

test/e2e/app_management_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1747,6 +1747,57 @@ func TestSyncOptionPruneFalse(t *testing.T) {
17471747
Expect(ResourceSyncStatusIs("Pod", "pod-1", SyncStatusCodeOutOfSync))
17481748
}
17491749

1750+
// make sure that if we deleted a resource from the app, it is not pruned if the app has the sync option Prune=false
1751+
func TestSyncOptionSyncOptionPruneFalse(t *testing.T) {
1752+
Given(t).
1753+
Path("two-nice-pods").
1754+
When().
1755+
CreateApp().
1756+
PatchApp(`[{
1757+
"op": "add",
1758+
"path": "/spec/syncPolicy",
1759+
"value": { "syncOptions": ["Prune=false"] }
1760+
}]`).
1761+
Sync().
1762+
Then().
1763+
Expect(OperationPhaseIs(OperationSucceeded)).
1764+
Expect(SyncStatusIs(SyncStatusCodeSynced)).
1765+
When().
1766+
DeleteFile("pod-1.yaml").
1767+
Refresh(RefreshTypeHard).
1768+
IgnoreErrors().
1769+
Sync().
1770+
Then().
1771+
Expect(OperationPhaseIs(OperationSucceeded)).
1772+
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
1773+
Expect(ResourceSyncStatusIs("Pod", "pod-1", SyncStatusCodeOutOfSync))
1774+
1775+
// Also check that another setting on a ressource level doesn't override that
1776+
Given(t).
1777+
Path("two-nice-pods").
1778+
When().
1779+
CreateApp().
1780+
PatchApp(`[{
1781+
"op": "add",
1782+
"path": "/spec/syncPolicy",
1783+
"value": { "syncOptions": ["Prune=false"] }
1784+
}]`).
1785+
PatchFile("pod-1.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "Prune=true"}}]`).
1786+
Sync().
1787+
Then().
1788+
Expect(OperationPhaseIs(OperationSucceeded)).
1789+
Expect(SyncStatusIs(SyncStatusCodeSynced)).
1790+
When().
1791+
DeleteFile("pod-1.yaml").
1792+
Refresh(RefreshTypeHard).
1793+
IgnoreErrors().
1794+
Sync().
1795+
Then().
1796+
Expect(OperationPhaseIs(OperationSucceeded)).
1797+
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
1798+
Expect(ResourceSyncStatusIs("Pod", "pod-1", SyncStatusCodeOutOfSync))
1799+
}
1800+
17501801
// make sure that if we have an invalid manifest, we can add it if we disable validation, we get a server error rather than a client error
17511802
func TestSyncOptionValidateFalse(t *testing.T) {
17521803
Given(t).

0 commit comments

Comments
 (0)