Skip to content

Commit

Permalink
feat: Introduce sync-option SkipDryRunOnMissingResource=true (#2873) (#…
Browse files Browse the repository at this point in the history
…3247)

* feat: Introduce sync-option SkipDryRunOnMissingResource=true
  • Loading branch information
a-hat authored Apr 8, 2020
1 parent f586385 commit 8d082cc
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 2 deletions.
6 changes: 4 additions & 2 deletions controller/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -484,9 +484,11 @@ func (sc *syncContext) getSyncTasks() (_ syncTasks, successful bool) {
serverRes, err := kube.ServerResourceForGroupVersionKind(sc.disco, task.groupVersionKind())
if err != nil {
// Special case for custom resources: if CRD is not yet known by the K8s API server,
// skip verification during `kubectl apply --dry-run` since we expect the CRD
// and the CRD is part of this sync or the resource is annotated with SkipDryRunOnMissingResource=true,
// then skip verification during `kubectl apply --dry-run` since we expect the CRD
// to be created during app synchronization.
if apierr.IsNotFound(err) && sc.hasCRDOfGroupKind(task.group(), task.kind()) {
skipDryRunOnMissingResource := resource.HasAnnotationOption(task.targetObj, common.AnnotationSyncOptions, "SkipDryRunOnMissingResource=true")
if apierr.IsNotFound(err) && (skipDryRunOnMissingResource || sc.hasCRDOfGroupKind(task.group(), task.kind())) {
sc.log.WithFields(log.Fields{"task": task}).Debug("skip dry-run for custom resource")
task.skipDryRun = true
} else {
Expand Down
95 changes: 95 additions & 0 deletions controller/sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,101 @@ func TestObjectsGetANamespace(t *testing.T) {
assert.Equal(t, "", pod.GetNamespace())
}

func TestSyncCustomResources(t *testing.T) {
type fields struct {
skipDryRunAnnotationPresent bool
crdAlreadyPresent bool
crdInSameSync bool
}

tests := []struct {
name string
fields fields
wantDryRun bool
wantSuccess bool
}{

{"unknown crd", fields{
skipDryRunAnnotationPresent: false, crdAlreadyPresent: false, crdInSameSync: false,
}, true, false},
{"crd present in same sync", fields{
skipDryRunAnnotationPresent: false, crdAlreadyPresent: false, crdInSameSync: true,
}, false, true},
{"crd is already present in cluster", fields{
skipDryRunAnnotationPresent: false, crdAlreadyPresent: true, crdInSameSync: false,
}, true, true},
{"crd is already present in cluster, skip dry run annotated", fields{
skipDryRunAnnotationPresent: true, crdAlreadyPresent: true, crdInSameSync: false,
}, true, true},
{"unknown crd, skip dry run annotated", fields{
skipDryRunAnnotationPresent: true, crdAlreadyPresent: false, crdInSameSync: false,
}, false, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

knownCustomResourceTypes := []v1.APIResource{}
if tt.fields.crdAlreadyPresent {
knownCustomResourceTypes = append(knownCustomResourceTypes, v1.APIResource{Kind: "TestCrd", Group: "argoproj.io", Version: "v1", Namespaced: true})
}

syncCtx := newTestSyncCtx(
&v1.APIResourceList{
GroupVersion: "argoproj.io/v1",
APIResources: knownCustomResourceTypes,
},
&v1.APIResourceList{
GroupVersion: "apiextensions.k8s.io/v1beta1",
APIResources: []v1.APIResource{
{Kind: "CustomResourceDefinition", Group: "apiextensions.k8s.io", Version: "v1beta1", Namespaced: true},
},
},
)

cr := test.Unstructured(`
{
"apiVersion": "argoproj.io/v1",
"kind": "TestCrd",
"metadata": {
"name": "my-resource"
}
}
`)

if tt.fields.skipDryRunAnnotationPresent {
cr.SetAnnotations(map[string]string{common.AnnotationSyncOptions: "SkipDryRunOnMissingResource=true"})
}

resources := []managedResource{{Target: cr}}
if tt.fields.crdInSameSync {
resources = append(resources, managedResource{Target: test.NewCRD()})
}

syncCtx.compareResult = &comparisonResult{managedResources: resources}

tasks, successful := syncCtx.getSyncTasks()

if successful != tt.wantSuccess {
t.Errorf("successful = %v, want: %v", successful, tt.wantSuccess)
return
}

skipDryRun := false
for _, task := range tasks {
if task.targetObj.GetKind() == cr.GetKind() {
skipDryRun = task.skipDryRun
break
}
}

if tt.wantDryRun != !skipDryRun {
t.Errorf("dryRun = %v, want: %v", !skipDryRun, tt.wantDryRun)
}
})
}

}

func TestPersistRevisionHistory(t *testing.T) {
app := newFakeApp()
app.Status.OperationState = nil
Expand Down
19 changes: 19 additions & 0 deletions docs/user-guide/sync-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,22 @@ metadata:

If you want to exclude a whole class of objects globally, consider setting `resource.customizations` in [system level configuation](../user-guide/diffing.md#system-level-configuration).

## Skip Dry Run for new custom resources types

>v1.6

When syncing a custom resource which is not yet known to the cluster, there are generally two options:

1) The CRD manifest is part of the same sync. Then ArgoCD will automatically skip the dry run, the CRD will be applied and the resource can be created.
2) In some cases the CRD is not part of the sync, but it could be created in another way, e.g. by a controller in the cluster. An example is [gatekeeper](https://github.com/open-policy-agent/gatekeeper),
which creates CRDs in response to user defined `ConstraintTemplates`. ArgoCD cannot find the CRD in the sync and will fail with the error `the server could not find the requested resource`.

To skip the dry run for missing resource types, use the following annotation:

```yaml
metadata:
annotations:
argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true
```

The dry run will still be executed if the CRD is already present in the cluster.

0 comments on commit 8d082cc

Please sign in to comment.