Skip to content

Commit 8007df5

Browse files
authored
fix(sync): create namespace before dry-run (#731)
1 parent f8f1b61 commit 8007df5

File tree

2 files changed

+87
-28
lines changed

2 files changed

+87
-28
lines changed

pkg/sync/sync_context.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -430,10 +430,25 @@ func (sc *syncContext) Sync() {
430430
// to perform the sync. we only wish to do this once per operation, performing additional dry-runs
431431
// is harmless, but redundant. The indicator we use to detect if we have already performed
432432
// the dry-run for this operation, is if the resource or hook list is empty.
433-
434433
dryRunTasks := tasks
434+
435+
// Before doing any validation, we have to create the application namespace if it does not exist.
436+
// The validation is expected to fail in multiple scenarios if a namespace does not exist.
437+
if nsCreateTask := sc.getNamespaceCreationTask(dryRunTasks); nsCreateTask != nil {
438+
nsSyncTasks := syncTasks{nsCreateTask}
439+
// No need to perform a dry-run on the namespace creation, because if it fails we stop anyway
440+
sc.log.WithValues("task", nsCreateTask).Info("Creating namespace")
441+
if sc.runTasks(nsSyncTasks, false) == failed {
442+
sc.setOperationFailed(syncTasks{}, nsSyncTasks, "the namespace failed to apply")
443+
return
444+
}
445+
446+
// The namespace was created, we can remove this task from the dry-run
447+
dryRunTasks = tasks.Filter(func(t *syncTask) bool { return t != nsCreateTask })
448+
}
449+
435450
if sc.applyOutOfSyncOnly {
436-
dryRunTasks = sc.filterOutOfSyncTasks(tasks)
451+
dryRunTasks = sc.filterOutOfSyncTasks(dryRunTasks)
437452
}
438453

439454
sc.log.WithValues("tasks", dryRunTasks).Info("Tasks (dry-run)")
@@ -608,6 +623,18 @@ func (sc *syncContext) filterOutOfSyncTasks(tasks syncTasks) syncTasks {
608623
})
609624
}
610625

626+
// getNamespaceCreationTask returns a task that will create the current namespace
627+
// or nil if the syncTasks does not contain one
628+
func (sc *syncContext) getNamespaceCreationTask(tasks syncTasks) *syncTask {
629+
creationTasks := tasks.Filter(func(task *syncTask) bool {
630+
return task.liveObj == nil && isNamespaceWithName(task.targetObj, sc.namespace)
631+
})
632+
if len(creationTasks) > 0 {
633+
return creationTasks[0]
634+
}
635+
return nil
636+
}
637+
611638
func (sc *syncContext) removeHookFinalizer(task *syncTask) error {
612639
if task.liveObj == nil {
613640
return nil
@@ -961,12 +988,11 @@ func (sc *syncContext) autoCreateNamespace(tasks syncTasks) syncTasks {
961988
case err == nil:
962989
nsTask := &syncTask{phase: common.SyncPhasePreSync, targetObj: managedNs, liveObj: liveObj}
963990
_, ok := sc.syncRes[nsTask.resultKey()]
964-
if ok {
965-
tasks = sc.appendNsTask(tasks, nsTask, managedNs, liveObj)
966-
} else if liveObj != nil {
991+
if !ok && liveObj != nil {
967992
sc.log.WithValues("namespace", sc.namespace).Info("Namespace already exists")
968-
tasks = sc.appendNsTask(tasks, &syncTask{phase: common.SyncPhasePreSync, targetObj: managedNs, liveObj: liveObj}, managedNs, liveObj)
969993
}
994+
tasks = sc.appendNsTask(tasks, nsTask, managedNs, liveObj)
995+
970996
case apierrors.IsNotFound(err):
971997
tasks = sc.appendNsTask(tasks, &syncTask{phase: common.SyncPhasePreSync, targetObj: managedNs, liveObj: nil}, managedNs, nil)
972998
default:

pkg/sync/sync_context_test.go

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,52 @@ func TestSyncNotPermittedNamespace(t *testing.T) {
121121
assert.Contains(t, resources[0].Message, "not permitted in project")
122122
}
123123

124+
func TestSyncNamespaceCreatedBeforeDryRunWithoutFailure(t *testing.T) {
125+
pod := testingutils.NewPod()
126+
syncCtx := newTestSyncCtx(nil, WithNamespaceModifier(func(_, _ *unstructured.Unstructured) (bool, error) {
127+
return true, nil
128+
}))
129+
syncCtx.resources = groupResources(ReconciliationResult{
130+
Live: []*unstructured.Unstructured{nil, nil},
131+
Target: []*unstructured.Unstructured{pod},
132+
})
133+
syncCtx.Sync()
134+
phase, msg, resources := syncCtx.GetState()
135+
assert.Equal(t, synccommon.OperationRunning, phase)
136+
assert.Equal(t, "waiting for healthy state of /Namespace/fake-argocd-ns", msg)
137+
require.Len(t, resources, 1)
138+
assert.Equal(t, "Namespace", resources[0].ResourceKey.Kind)
139+
assert.Equal(t, synccommon.ResultCodeSynced, resources[0].Status)
140+
}
141+
142+
func TestSyncNamespaceCreatedBeforeDryRunWithFailure(t *testing.T) {
143+
pod := testingutils.NewPod()
144+
syncCtx := newTestSyncCtx(nil, WithNamespaceModifier(func(_, _ *unstructured.Unstructured) (bool, error) {
145+
return true, nil
146+
}), func(ctx *syncContext) {
147+
resourceOps := ctx.resourceOps.(*kubetest.MockResourceOps)
148+
resourceOps.Commands = map[string]kubetest.KubectlOutput{}
149+
resourceOps.Commands[pod.GetName()] = kubetest.KubectlOutput{
150+
Output: "should not be returned",
151+
Err: errors.New("invalid object failing dry-run"),
152+
}
153+
})
154+
syncCtx.resources = groupResources(ReconciliationResult{
155+
Live: []*unstructured.Unstructured{nil, nil},
156+
Target: []*unstructured.Unstructured{pod},
157+
})
158+
syncCtx.Sync()
159+
phase, msg, resources := syncCtx.GetState()
160+
assert.Equal(t, synccommon.OperationFailed, phase)
161+
assert.Equal(t, "one or more objects failed to apply (dry run)", msg)
162+
require.Len(t, resources, 2)
163+
assert.Equal(t, "Namespace", resources[0].ResourceKey.Kind)
164+
assert.Equal(t, synccommon.ResultCodeSynced, resources[0].Status)
165+
assert.Equal(t, "Pod", resources[1].ResourceKey.Kind)
166+
assert.Equal(t, synccommon.ResultCodeSyncFailed, resources[1].Status)
167+
assert.Equal(t, "invalid object failing dry-run", resources[1].Message)
168+
}
169+
124170
func TestSyncCreateInSortedOrder(t *testing.T) {
125171
syncCtx := newTestSyncCtx(nil)
126172
syncCtx.resources = groupResources(ReconciliationResult{
@@ -1045,7 +1091,7 @@ func TestNamespaceAutoCreation(t *testing.T) {
10451091

10461092
// Namespace auto creation pre-sync task should not be there
10471093
// since there is namespace resource in syncCtx.resources
1048-
t.Run("no pre-sync task 1", func(t *testing.T) {
1094+
t.Run("no pre-sync task if resource is managed", func(t *testing.T) {
10491095
syncCtx.resources = groupResources(ReconciliationResult{
10501096
Live: []*unstructured.Unstructured{nil},
10511097
Target: []*unstructured.Unstructured{namespace},
@@ -1057,23 +1103,21 @@ func TestNamespaceAutoCreation(t *testing.T) {
10571103
assert.NotContains(t, tasks, task)
10581104
})
10591105

1060-
// Namespace auto creation pre-sync task should not be there
1061-
// since there is no existing sync result
1062-
t.Run("no pre-sync task 2", func(t *testing.T) {
1106+
// Namespace auto creation pre-sync task should be there when it is not managed
1107+
t.Run("pre-sync task when resource is not managed", func(t *testing.T) {
10631108
syncCtx.resources = groupResources(ReconciliationResult{
10641109
Live: []*unstructured.Unstructured{nil},
10651110
Target: []*unstructured.Unstructured{pod},
10661111
})
10671112
tasks, successful := syncCtx.getSyncTasks()
10681113

10691114
assert.True(t, successful)
1070-
assert.Len(t, tasks, 1)
1071-
assert.NotContains(t, tasks, task)
1115+
assert.Len(t, tasks, 2)
1116+
assert.Contains(t, tasks, task)
10721117
})
10731118

1074-
// Namespace auto creation pre-sync task should be there
1075-
// since there is existing sync result which means that task created this namespace
1076-
t.Run("pre-sync task created", func(t *testing.T) {
1119+
// Namespace auto creation pre-sync task should be there after sync
1120+
t.Run("pre-sync task when resource is not managed with existing sync", func(t *testing.T) {
10771121
syncCtx.resources = groupResources(ReconciliationResult{
10781122
Live: []*unstructured.Unstructured{nil},
10791123
Target: []*unstructured.Unstructured{pod},
@@ -1100,24 +1144,13 @@ func TestNamespaceAutoCreation(t *testing.T) {
11001144

11011145
// Namespace auto creation pre-sync task not should be there
11021146
// since there is no namespace modifier present
1103-
t.Run("no pre-sync task created", func(t *testing.T) {
1147+
t.Run("no pre-sync task created if no modifier", func(t *testing.T) {
11041148
syncCtx.resources = groupResources(ReconciliationResult{
11051149
Live: []*unstructured.Unstructured{nil},
11061150
Target: []*unstructured.Unstructured{pod},
11071151
})
1108-
syncCtx.syncNamespace = nil
11091152

1110-
res := synccommon.ResourceSyncResult{
1111-
ResourceKey: kube.GetResourceKey(task.obj()),
1112-
Version: task.version(),
1113-
Status: task.syncStatus,
1114-
Message: task.message,
1115-
HookType: task.hookType(),
1116-
HookPhase: task.operationState,
1117-
SyncPhase: task.phase,
1118-
}
1119-
syncCtx.syncRes = map[string]synccommon.ResourceSyncResult{}
1120-
syncCtx.syncRes[task.resultKey()] = res
1153+
syncCtx.syncNamespace = nil
11211154

11221155
tasks, successful := syncCtx.getSyncTasks()
11231156

0 commit comments

Comments
 (0)