Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Prune=false and IgnoreExtraneous options #1680

Merged
merged 34 commits into from
Jun 7, 2019
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6c6f0d4
Adds the option to ignore resources when calculating sync status
alexec Jun 3, 2019
8aa745d
lint
alexec Jun 3, 2019
7ba87a9
""github.com/argoproj/argo-cd/util/resources"..." to state.go
alexec Jun 4, 2019
f4c7908
"// AnnotationSyncStatusOptions is a comma-separate..." to common.go
alexec Jun 4, 2019
eed7261
"testCompareAppStateIgnoreAnnotation(t, common.Anno..." to state_test.go
alexec Jun 4, 2019
c72752a
"if resource.HasAnnotationOption(obj, common.Annota..." to state.go
alexec Jun 4, 2019
b59f516
"Kustomize has a feature that allows you to generat..." to sync_statu…
alexec Jun 4, 2019
46eb193
"return &Context{t: t, destServer: KubernetesIntern..." to context.go
alexec Jun 4, 2019
57dccdd
Merge branch 'master' into sync-option-ignore
alexec Jun 5, 2019
2b30c71
Merge branch 'master' into sync-option-ignore
alexec Jun 5, 2019
3f037d2
"func (c *Context) Prune(prune bool) *Context {..." to context.go
alexec Jun 5, 2019
0381c01
Adds NoPrune option
alexec Jun 5, 2019
f7b3b52
"AnnotationSyncStatusOptions = "argocd.argoproj.io/..." to common.go
alexec Jun 5, 2019
6c9dc19
"// AnnotationSyncStatusOptions is a comma-separate..." to common.go
alexec Jun 5, 2019
d3ae6db
"func TestCompareAppStateSyncOptionIgnoreNeedsPruni..." to state_test.go
alexec Jun 5, 2019
c1b0382
"assert.Len(t, compRes.managedResources,0)..." to state_test.go
alexec Jun 5, 2019
8b639cd
"argocd.argoproj.io/compare-options: Ignore..." to kustomization.yaml
alexec Jun 5, 2019
4ed7b27
"repoUrl = fmt.Sprintf("file:///%s", repoDirectory(..." to fixture.go
alexec Jun 5, 2019
84e0260
"func TestConfigMap(t *testing.T) {" to app_management_test.go
alexec Jun 5, 2019
a2a714b
"Expect(OperationPhaseIs(OperationSucceeded))...." to app_management_…
alexec Jun 5, 2019
3a69e0c
"// Expect(OperationPhaseIs(OperationSucceeded))...." to app_manageme…
alexec Jun 5, 2019
4133fa4
"Then()." to kustomize_test.go
alexec Jun 5, 2019
3010587
"testEdgeCasesApplicationResources(t, "deprecated-e..." to app_manage…
alexec Jun 5, 2019
d827645
"![compare option needs pruning](../assets/compare-..." to compare-op…
alexec Jun 5, 2019
28e38a7
"})" to app_management_test.go
alexec Jun 5, 2019
1039c7e
"})" to app_management_test.go
alexec Jun 5, 2019
45bde7d
Update compare-options.md
alexec Jun 5, 2019
00d1243
Update compare-options.md
alexec Jun 5, 2019
543ae6f
Update kustomize.md
alexec Jun 5, 2019
e41bd84
"func (c *Context) Prune(prune bool) *Context {..." to context.go
alexec Jun 5, 2019
fe0ec63
"if !(needsPruning && resource.HasAnnotationOption(..." to state.go
alexec Jun 5, 2019
131fb57
Update state.go
alexec Jun 5, 2019
1744612
Merge branch 'master' into sync-option-ignore
alexec Jun 6, 2019
9927e86
Merge branch 'master' into sync-option-ignore
alexec Jun 6, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ const (
// LabelValueSecretTypeCluster indicates a secret type of cluster
LabelValueSecretTypeCluster = "cluster"

// AnnotationCompareOptions is a comma-separated list of options for comparison
AnnotationCompareOptions = "argocd.argoproj.io/compare-options"
// AnnotationSyncOptions is a comma-separated list of options for syncing
AnnotationSyncOptions = "argocd.argoproj.io/sync-options"
// AnnotationSyncWave indicates which wave of the sync the resource or hook should be in
AnnotationSyncWave = "argocd.argoproj.io/sync-wave"
// AnnotationKeyHook contains the hook type of a resource
Expand Down
19 changes: 12 additions & 7 deletions controller/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,10 +297,11 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
syncCode := v1alpha1.SyncStatusCodeSynced
managedResources := make([]managedResource, len(targetObjs))
resourceSummaries := make([]v1alpha1.ResourceStatus, len(targetObjs))
for i := 0; i < len(targetObjs); i++ {
obj := managedLiveObj[i]
for i, targetObj := range targetObjs {
liveObj := managedLiveObj[i]
obj := liveObj
if obj == nil {
obj = targetObjs[i]
obj = targetObj
}
if obj == nil {
continue
Expand All @@ -319,13 +320,17 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
diffResult := diffResults.Diffs[i]
if resState.Hook || resource.Ignore(obj) {
// For resource hooks, don't store sync status, and do not affect overall sync status
} else if diffResult.Modified || targetObjs[i] == nil || managedLiveObj[i] == nil {
} else if diffResult.Modified || targetObj == nil || liveObj == nil {
// Set resource state to OutOfSync since one of the following is true:
// * target and live resource are different
// * target resource not defined and live resource is extra
// * target resource present but live resource is missing
resState.Status = v1alpha1.SyncStatusCodeOutOfSync
syncCode = v1alpha1.SyncStatusCodeOutOfSync
// we don't apply to the application
alexec marked this conversation as resolved.
Show resolved Hide resolved
needsPruning := targetObj == nil && liveObj != nil
if !(needsPruning && resource.HasAnnotationOption(obj, common.AnnotationCompareOptions, "IgnoreNeedsPruning")) {
syncCode = v1alpha1.SyncStatusCodeOutOfSync
}
} else {
resState.Status = v1alpha1.SyncStatusCodeSynced
}
Expand All @@ -335,8 +340,8 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, revision st
Group: resState.Group,
Kind: resState.Kind,
Version: resState.Version,
Live: managedLiveObj[i],
Target: targetObjs[i],
Live: liveObj,
Target: targetObj,
Diff: diffResult,
Hook: resState.Hook,
}
Expand Down
27 changes: 27 additions & 0 deletions controller/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,33 @@ func TestCompareAppStateHook(t *testing.T) {
assert.Equal(t, 0, len(compRes.conditions))
}

// checks that ignore resources are detected, but excluded from status
func TestCompareAppStateCompareOptionIgnoreNeedsPruning(t *testing.T) {
pod := test.NewPod()
pod.SetAnnotations(map[string]string{common.AnnotationCompareOptions: "IgnoreNeedsPruning"})
app := newFakeApp()
data := fakeData{
apps: []runtime.Object{app},
manifestResponse: &repository.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
}
ctrl := newFakeController(&data)

compRes, err := ctrl.appStateManager.CompareAppState(app, "", app.Spec.Source, false)

assert.NoError(t, err)
assert.NotNil(t, compRes)
assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
alexec marked this conversation as resolved.
Show resolved Hide resolved
assert.Len(t, compRes.resources, 0)
assert.Len(t, compRes.managedResources, 0)
assert.Len(t, compRes.conditions, 0)
}

// TestCompareAppStateExtraHook tests when there is an extra _hook_ object in live but not defined in git
func TestCompareAppStateExtraHook(t *testing.T) {
pod := test.NewPod()
Expand Down
10 changes: 7 additions & 3 deletions controller/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ import (
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"

"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/controller/metrics"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
listersv1alpha1 "github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
"github.com/argoproj/argo-cd/util/argo"
"github.com/argoproj/argo-cd/util/health"
"github.com/argoproj/argo-cd/util/hook"
"github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/resource"
)

type syncContext struct {
Expand Down Expand Up @@ -473,7 +475,11 @@ func (sc *syncContext) applyObject(targetObj *unstructured.Unstructured, dryRun

// pruneObject deletes the object if both prune is true and dryRun is false. Otherwise appropriate message
func (sc *syncContext) pruneObject(liveObj *unstructured.Unstructured, prune, dryRun bool) (v1alpha1.ResultCode, string) {
if prune {
if !prune {
return v1alpha1.ResultCodePruneSkipped, "ignored (requires pruning)"
} else if resource.HasAnnotationOption(liveObj, common.AnnotationSyncOptions, "NoPrune") {
return v1alpha1.ResultCodePruneSkipped, "ignored (no prune)"
} else {
if dryRun {
return v1alpha1.ResultCodePruned, "pruned (dry run)"
} else {
Expand All @@ -487,8 +493,6 @@ func (sc *syncContext) pruneObject(liveObj *unstructured.Unstructured, prune, dr
}
return v1alpha1.ResultCodePruned, "pruned"
}
} else {
return v1alpha1.ResultCodePruneSkipped, "ignored (requires pruning)"
}
}

Expand Down
20 changes: 20 additions & 0 deletions controller/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,26 @@ func TestDontSyncOrPruneHooks(t *testing.T) {
assert.Equal(t, v1alpha1.OperationSucceeded, syncCtx.opState.Phase)
}

// make sure that we do not prune resources with NoPrune
func TestDontPruneNoPrune(t *testing.T) {
syncCtx := newTestSyncCtx()
pod := test.NewPod()
pod.SetAnnotations(map[string]string{common.AnnotationSyncOptions: "NoPrune"})
pod.SetNamespace(test.FakeArgoCDNamespace)
syncCtx.compareResult = &comparisonResult{managedResources: []managedResource{{Live: pod}}}

syncCtx.sync()

assert.Equal(t, v1alpha1.OperationRunning, syncCtx.opState.Phase)
assert.Len(t, syncCtx.syncRes.Resources, 1)
assert.Equal(t, v1alpha1.ResultCodePruneSkipped, syncCtx.syncRes.Resources[0].Status)
assert.Equal(t, "ignored (no prune)", syncCtx.syncRes.Resources[0].Message)

syncCtx.sync()

assert.Equal(t, v1alpha1.OperationSucceeded, syncCtx.opState.Phase)
}

func TestSelectiveSyncOnly(t *testing.T) {
syncCtx := newTestSyncCtx()
pod1 := test.NewPod()
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/sync-option-no-prune.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions docs/user-guide/compare-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Compare Options

## Ignoring Resources That Needs Pruning

You may wish to exclude resources for the app's overall sync status under certain circumstances, for example if they are generated by the tool. This can be done by adding an annotation:

```yaml
metadata:
annotations:
argocd.argoproj.io/compare-options: IgnoreNeedsPruning
```

![compare option needs pruning](../assets/compare-option-ignore-needs-pruning.png)

!!! note
This only affect the sync status. If the resource's health is degraded, then the app will also be degraded.

Kustomize has a feature that allows you to generate config maps ([read more ⧉](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/configGeneration.md)). You can set `generatorOptions` to add this annotation so that your app remains in sync:

```yaml
configMapGenerator:
- name: my-map
literals:
- foo=bar
generatorOptions:
annotations:
argocd.argoproj.io/compare-options: IgnoreNeedsPruning
kind: Kustomization
```

!!! note
`generatorOptions` adds annotations to both config maps and secrets ([read more ⧉](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/generatorOptions.md)).
5 changes: 4 additions & 1 deletion docs/user-guide/kustomize.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ You have three configuration options for Kustomize:
* `imageTags` is a list of Kustomize 1.0 image tag overrides
* `images` is a list of Kustomize 2.0 image overrides

To use Kustomize with an overlay, point your path to the overlay.
To use Kustomize with an overlay, point your path to the overlay.

!!! tip
If you're generating resources, you should read up how to ignore those generated resources using [sync status options](sync_status_options.md).
24 changes: 24 additions & 0 deletions docs/user-guide/sync-options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Sync Options

## No Prune Resources

You may wish to prevent an object from being pruned:

```yaml
metadata:
annotations:
argocd.argoproj.io/sync-options: NoPrune
```

In the UI, the pod will simply appear as out-of-sync:

![sync option no prune](../assets/sync-option-no-prune.png)


The sync-status panel shows that pruning was skipped, and why:

![sync option no prune](../assets/sync-option-no-prune-sync-status.png)

!!! note
The app will be out of sync if Argo CD expects a resource to be pruned. You may wish to use this along with [compare options](compare-options.md).

2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ nav:
- user-guide/private-repositories.md
- user-guide/auto_sync.md
- user-guide/diffing.md
- user-guide/compare-options.md
- user-guide/sync-options.md
- user-guide/parameters.md
- user-guide/tracking_strategies.md
- user-guide/resource_hooks.md
Expand Down
64 changes: 59 additions & 5 deletions test/e2e/app_management_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,15 +306,19 @@ func TestResourceDiffing(t *testing.T) {
}

func TestDeprecatedExtensions(t *testing.T) {
testEdgeCasesApplicationResources(t, "deprecated-extensions")
testEdgeCasesApplicationResources(t, "deprecated-extensions", OperationRunning, HealthStatusProgressing)
}

func TestCRDs(t *testing.T) {
testEdgeCasesApplicationResources(t, "crd-creation")
testEdgeCasesApplicationResources(t, "crd-creation", OperationSucceeded, HealthStatusHealthy)
}

func TestDuplicatedResources(t *testing.T) {
testEdgeCasesApplicationResources(t, "duplicated-resources")
testEdgeCasesApplicationResources(t, "duplicated-resources", OperationSucceeded, HealthStatusHealthy)
}

func TestConfigMap(t *testing.T) {
testEdgeCasesApplicationResources(t, "config-map", OperationSucceeded, HealthStatusHealthy)
}

func TestFailedConversion(t *testing.T) {
Expand All @@ -323,17 +327,19 @@ func TestFailedConversion(t *testing.T) {
errors.FailOnErr(fixture.Run("", "kubectl", "delete", "apiservice", "v1beta1.metrics.k8s.io"))
}()

testEdgeCasesApplicationResources(t, "failed-conversion")
testEdgeCasesApplicationResources(t, "failed-conversion", OperationSucceeded, HealthStatusHealthy)
}

func testEdgeCasesApplicationResources(t *testing.T, appPath string) {
func testEdgeCasesApplicationResources(t *testing.T, appPath string, phase OperationPhase, statusCode HealthStatusCode) {
Given(t).
Path(appPath).
When().
Create().
Sync().
Then().
Expect(OperationPhaseIs(phase)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(statusCode)).
And(func(app *Application) {
diffOutput, err := fixture.RunCli("app", "diff", app.Name, "--local", path.Join("testdata", appPath))
assert.Empty(t, diffOutput)
Expand Down Expand Up @@ -504,3 +510,51 @@ func TestPermissions(t *testing.T) {
assert.True(t, destinationErrorExist)
assert.True(t, sourceErrorExist)
}

// make sure that if we deleted a resource from the app, it is not pruned if annotated with NoPrune
func TestSyncOptionNoPrune(t *testing.T) {
Given(t).
Path("two-nice-pods").
When().
PatchFile("pod-1.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "NoPrune"}}]`).
Create().
Sync().
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
When().
DeleteFile("pod-1.yaml").
Refresh(RefreshTypeHard).
Sync().
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
Expect(ResourceSyncStatusIs("Pod", "pod-1", SyncStatusCodeOutOfSync))
}

// make sure that, if we have a resource that needs pruning, but we're ignoring it, the app is in-sync
func TestCompareOptionIgnoreNeedsPruning(t *testing.T) {
Given(t).
Path("two-nice-pods").
When().
PatchFile("pod-1.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/compare-options": "IgnoreNeedsPruning"}}]`).
Create().
Sync().
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
When().
DeleteFile("pod-1.yaml").
Refresh(RefreshTypeHard).
Then().
Expect(SyncStatusIs(SyncStatusCodeSynced)).
And(func(app *Application) {
assert.Len(t, app.Status.Resources, 2)
assert.Equal(t, SyncStatusCodeOutOfSync, app.Status.Resources[1].Status)
}).
When().
Sync().
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced))
}
10 changes: 5 additions & 5 deletions test/e2e/fixture/app/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ func (c *Context) NamePrefix(namePrefix string) *Context {
return c
}

func (c *Context) Prune(prune bool) *Context {
c.prune = prune
return c
}

func (c *Context) And(block func()) *Context {
block()
return c
Expand All @@ -69,8 +74,3 @@ func (c *Context) And(block func()) *Context {
func (c *Context) When() *Actions {
return &Actions{context: c}
}

func (c *Context) Prune(prune bool) *Context {
c.prune = prune
return c
}
2 changes: 1 addition & 1 deletion test/e2e/fixture/fixture.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func EnsureCleanState() {

// new random ID
id = strings.ToLower(rand.RandString(5))
repoUrl = fmt.Sprintf("file:///%s", repoDirectory())
repoUrl = fmt.Sprintf("file://%s", repoDirectory())

// create tmp dir
FailOnErr(Run("", "mkdir", "-p", tmpDir))
Expand Down
Loading