argo-cd
1668 строк · 58.5 Кб
1package controller
2
3import (
4"encoding/json"
5"fmt"
6"os"
7"testing"
8"time"
9
10"github.com/argoproj/gitops-engine/pkg/health"
11synccommon "github.com/argoproj/gitops-engine/pkg/sync/common"
12"github.com/argoproj/gitops-engine/pkg/utils/kube"
13. "github.com/argoproj/gitops-engine/pkg/utils/testing"
14"github.com/imdario/mergo"
15"github.com/sirupsen/logrus"
16logrustest "github.com/sirupsen/logrus/hooks/test"
17"github.com/stretchr/testify/assert"
18v1 "k8s.io/api/apps/v1"
19corev1 "k8s.io/api/core/v1"
20networkingv1 "k8s.io/api/networking/v1"
21metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
23"k8s.io/apimachinery/pkg/runtime"
24
25"github.com/argoproj/argo-cd/v2/common"
26"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
27argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
28"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
29"github.com/argoproj/argo-cd/v2/test"
30"github.com/argoproj/argo-cd/v2/util/argo"
31)
32
33// TestCompareAppStateEmpty tests comparison when both git and live have no objects
34func TestCompareAppStateEmpty(t *testing.T) {
35app := newFakeApp()
36data := fakeData{
37manifestResponse: &apiclient.ManifestResponse{
38Manifests: []string{},
39Namespace: test.FakeDestNamespace,
40Server: test.FakeClusterURL,
41Revision: "abc123",
42},
43managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
44}
45ctrl := newFakeController(&data, nil)
46sources := make([]argoappv1.ApplicationSource, 0)
47sources = append(sources, app.Spec.GetSource())
48revisions := make([]string, 0)
49revisions = append(revisions, "")
50compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
51assert.Nil(t, err)
52assert.NotNil(t, compRes)
53assert.NotNil(t, compRes.syncStatus)
54assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
55assert.Len(t, compRes.resources, 0)
56assert.Len(t, compRes.managedResources, 0)
57assert.Len(t, app.Status.Conditions, 0)
58}
59
60// TestCompareAppStateRepoError tests the case when CompareAppState notices a repo error
61func TestCompareAppStateRepoError(t *testing.T) {
62app := newFakeApp()
63ctrl := newFakeController(&fakeData{manifestResponses: make([]*apiclient.ManifestResponse, 3)}, fmt.Errorf("test repo error"))
64sources := make([]argoappv1.ApplicationSource, 0)
65sources = append(sources, app.Spec.GetSource())
66revisions := make([]string, 0)
67revisions = append(revisions, "")
68compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
69assert.Nil(t, compRes)
70assert.EqualError(t, err, CompareStateRepoError.Error())
71
72// expect to still get compare state error to as inside grace period
73compRes, err = ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
74assert.Nil(t, compRes)
75assert.EqualError(t, err, CompareStateRepoError.Error())
76
77time.Sleep(10 * time.Second)
78// expect to not get error as outside of grace period, but status should be unknown
79compRes, err = ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
80assert.NotNil(t, compRes)
81assert.Nil(t, err)
82assert.Equal(t, compRes.syncStatus.Status, argoappv1.SyncStatusCodeUnknown)
83}
84
85// TestCompareAppStateNamespaceMetadataDiffers tests comparison when managed namespace metadata differs
86func TestCompareAppStateNamespaceMetadataDiffers(t *testing.T) {
87app := newFakeApp()
88app.Spec.SyncPolicy.ManagedNamespaceMetadata = &argoappv1.ManagedNamespaceMetadata{
89Labels: map[string]string{
90"foo": "bar",
91},
92Annotations: map[string]string{
93"foo": "bar",
94},
95}
96app.Status.OperationState = &argoappv1.OperationState{
97SyncResult: &argoappv1.SyncOperationResult{},
98}
99
100data := fakeData{
101manifestResponse: &apiclient.ManifestResponse{
102Manifests: []string{},
103Namespace: test.FakeDestNamespace,
104Server: test.FakeClusterURL,
105Revision: "abc123",
106},
107managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
108}
109ctrl := newFakeController(&data, nil)
110sources := make([]argoappv1.ApplicationSource, 0)
111sources = append(sources, app.Spec.GetSource())
112revisions := make([]string, 0)
113revisions = append(revisions, "")
114compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
115assert.Nil(t, err)
116assert.NotNil(t, compRes)
117assert.NotNil(t, compRes.syncStatus)
118assert.Equal(t, argoappv1.SyncStatusCodeOutOfSync, compRes.syncStatus.Status)
119assert.Len(t, compRes.resources, 0)
120assert.Len(t, compRes.managedResources, 0)
121assert.Len(t, app.Status.Conditions, 0)
122}
123
124// TestCompareAppStateNamespaceMetadataDiffers tests comparison when managed namespace metadata differs to live and manifest ns
125func TestCompareAppStateNamespaceMetadataDiffersToManifest(t *testing.T) {
126ns := NewNamespace()
127ns.SetName(test.FakeDestNamespace)
128ns.SetNamespace(test.FakeDestNamespace)
129ns.SetAnnotations(map[string]string{"bar": "bat"})
130
131app := newFakeApp()
132app.Spec.SyncPolicy.ManagedNamespaceMetadata = &argoappv1.ManagedNamespaceMetadata{
133Labels: map[string]string{
134"foo": "bar",
135},
136Annotations: map[string]string{
137"foo": "bar",
138},
139}
140app.Status.OperationState = &argoappv1.OperationState{
141SyncResult: &argoappv1.SyncOperationResult{},
142}
143
144liveNs := ns.DeepCopy()
145liveNs.SetAnnotations(nil)
146
147data := fakeData{
148manifestResponse: &apiclient.ManifestResponse{
149Manifests: []string{toJSON(t, liveNs)},
150Namespace: test.FakeDestNamespace,
151Server: test.FakeClusterURL,
152Revision: "abc123",
153},
154managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
155kube.GetResourceKey(ns): ns,
156},
157}
158ctrl := newFakeController(&data, nil)
159sources := make([]argoappv1.ApplicationSource, 0)
160sources = append(sources, app.Spec.GetSource())
161revisions := make([]string, 0)
162revisions = append(revisions, "")
163compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
164assert.Nil(t, err)
165assert.NotNil(t, compRes)
166assert.NotNil(t, compRes.syncStatus)
167assert.Equal(t, argoappv1.SyncStatusCodeOutOfSync, compRes.syncStatus.Status)
168assert.Len(t, compRes.resources, 1)
169assert.Len(t, compRes.managedResources, 1)
170assert.NotNil(t, compRes.diffResultList)
171assert.Len(t, compRes.diffResultList.Diffs, 1)
172
173result := NewNamespace()
174assert.NoError(t, json.Unmarshal(compRes.diffResultList.Diffs[0].PredictedLive, result))
175
176labels := result.GetLabels()
177delete(labels, "kubernetes.io/metadata.name")
178
179assert.Equal(t, map[string]string{}, labels)
180// Manifests override definitions in managedNamespaceMetadata
181assert.Equal(t, map[string]string{"bar": "bat"}, result.GetAnnotations())
182assert.Len(t, app.Status.Conditions, 0)
183}
184
185// TestCompareAppStateNamespaceMetadata tests comparison when managed namespace metadata differs to live
186func TestCompareAppStateNamespaceMetadata(t *testing.T) {
187ns := NewNamespace()
188ns.SetName(test.FakeDestNamespace)
189ns.SetNamespace(test.FakeDestNamespace)
190ns.SetAnnotations(map[string]string{"bar": "bat"})
191
192app := newFakeApp()
193app.Spec.SyncPolicy.ManagedNamespaceMetadata = &argoappv1.ManagedNamespaceMetadata{
194Labels: map[string]string{
195"foo": "bar",
196},
197Annotations: map[string]string{
198"foo": "bar",
199},
200}
201app.Status.OperationState = &argoappv1.OperationState{
202SyncResult: &argoappv1.SyncOperationResult{},
203}
204
205data := fakeData{
206manifestResponse: &apiclient.ManifestResponse{
207Manifests: []string{},
208Namespace: test.FakeDestNamespace,
209Server: test.FakeClusterURL,
210Revision: "abc123",
211},
212managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
213kube.GetResourceKey(ns): ns,
214},
215}
216ctrl := newFakeController(&data, nil)
217sources := make([]argoappv1.ApplicationSource, 0)
218sources = append(sources, app.Spec.GetSource())
219revisions := make([]string, 0)
220revisions = append(revisions, "")
221compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
222assert.Nil(t, err)
223assert.NotNil(t, compRes)
224assert.NotNil(t, compRes.syncStatus)
225assert.Equal(t, argoappv1.SyncStatusCodeOutOfSync, compRes.syncStatus.Status)
226assert.Len(t, compRes.resources, 1)
227assert.Len(t, compRes.managedResources, 1)
228assert.NotNil(t, compRes.diffResultList)
229assert.Len(t, compRes.diffResultList.Diffs, 1)
230
231result := NewNamespace()
232assert.NoError(t, json.Unmarshal(compRes.diffResultList.Diffs[0].PredictedLive, result))
233
234labels := result.GetLabels()
235delete(labels, "kubernetes.io/metadata.name")
236
237assert.Equal(t, map[string]string{"foo": "bar"}, labels)
238assert.Equal(t, map[string]string{"argocd.argoproj.io/sync-options": "ServerSideApply=true", "bar": "bat", "foo": "bar"}, result.GetAnnotations())
239assert.Len(t, app.Status.Conditions, 0)
240}
241
242// TestCompareAppStateNamespaceMetadataIsTheSame tests comparison when managed namespace metadata is the same
243func TestCompareAppStateNamespaceMetadataIsTheSame(t *testing.T) {
244app := newFakeApp()
245app.Spec.SyncPolicy.ManagedNamespaceMetadata = &argoappv1.ManagedNamespaceMetadata{
246Labels: map[string]string{
247"foo": "bar",
248},
249Annotations: map[string]string{
250"foo": "bar",
251},
252}
253app.Status.OperationState = &argoappv1.OperationState{
254SyncResult: &argoappv1.SyncOperationResult{
255ManagedNamespaceMetadata: &argoappv1.ManagedNamespaceMetadata{
256Labels: map[string]string{
257"foo": "bar",
258},
259Annotations: map[string]string{
260"foo": "bar",
261},
262},
263},
264}
265
266data := fakeData{
267manifestResponse: &apiclient.ManifestResponse{
268Manifests: []string{},
269Namespace: test.FakeDestNamespace,
270Server: test.FakeClusterURL,
271Revision: "abc123",
272},
273managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
274}
275ctrl := newFakeController(&data, nil)
276sources := make([]argoappv1.ApplicationSource, 0)
277sources = append(sources, app.Spec.GetSource())
278revisions := make([]string, 0)
279revisions = append(revisions, "")
280compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
281assert.Nil(t, err)
282assert.NotNil(t, compRes)
283assert.NotNil(t, compRes.syncStatus)
284assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
285assert.Len(t, compRes.resources, 0)
286assert.Len(t, compRes.managedResources, 0)
287assert.Len(t, app.Status.Conditions, 0)
288}
289
290// TestCompareAppStateMissing tests when there is a manifest defined in the repo which doesn't exist in live
291func TestCompareAppStateMissing(t *testing.T) {
292app := newFakeApp()
293data := fakeData{
294apps: []runtime.Object{app},
295manifestResponse: &apiclient.ManifestResponse{
296Manifests: []string{PodManifest},
297Namespace: test.FakeDestNamespace,
298Server: test.FakeClusterURL,
299Revision: "abc123",
300},
301managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
302}
303ctrl := newFakeController(&data, nil)
304sources := make([]argoappv1.ApplicationSource, 0)
305sources = append(sources, app.Spec.GetSource())
306revisions := make([]string, 0)
307revisions = append(revisions, "")
308compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
309assert.Nil(t, err)
310assert.NotNil(t, compRes)
311assert.NotNil(t, compRes.syncStatus)
312assert.Equal(t, argoappv1.SyncStatusCodeOutOfSync, compRes.syncStatus.Status)
313assert.Len(t, compRes.resources, 1)
314assert.Len(t, compRes.managedResources, 1)
315assert.Len(t, app.Status.Conditions, 0)
316}
317
318// TestCompareAppStateExtra tests when there is an extra object in live but not defined in git
319func TestCompareAppStateExtra(t *testing.T) {
320pod := NewPod()
321pod.SetNamespace(test.FakeDestNamespace)
322app := newFakeApp()
323key := kube.ResourceKey{Group: "", Kind: "Pod", Namespace: test.FakeDestNamespace, Name: app.Name}
324data := fakeData{
325manifestResponse: &apiclient.ManifestResponse{
326Manifests: []string{},
327Namespace: test.FakeDestNamespace,
328Server: test.FakeClusterURL,
329Revision: "abc123",
330},
331managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
332key: pod,
333},
334}
335ctrl := newFakeController(&data, nil)
336sources := make([]argoappv1.ApplicationSource, 0)
337sources = append(sources, app.Spec.GetSource())
338revisions := make([]string, 0)
339revisions = append(revisions, "")
340compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
341assert.Nil(t, err)
342assert.NotNil(t, compRes)
343assert.Equal(t, argoappv1.SyncStatusCodeOutOfSync, compRes.syncStatus.Status)
344assert.Equal(t, 1, len(compRes.resources))
345assert.Equal(t, 1, len(compRes.managedResources))
346assert.Equal(t, 0, len(app.Status.Conditions))
347}
348
349// TestCompareAppStateHook checks that hooks are detected during manifest generation, and not
350// considered as part of resources when assessing Synced status
351func TestCompareAppStateHook(t *testing.T) {
352pod := NewPod()
353pod.SetAnnotations(map[string]string{synccommon.AnnotationKeyHook: "PreSync"})
354podBytes, _ := json.Marshal(pod)
355app := newFakeApp()
356data := fakeData{
357apps: []runtime.Object{app},
358manifestResponse: &apiclient.ManifestResponse{
359Manifests: []string{string(podBytes)},
360Namespace: test.FakeDestNamespace,
361Server: test.FakeClusterURL,
362Revision: "abc123",
363},
364managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
365}
366ctrl := newFakeController(&data, nil)
367sources := make([]argoappv1.ApplicationSource, 0)
368sources = append(sources, app.Spec.GetSource())
369revisions := make([]string, 0)
370revisions = append(revisions, "")
371compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
372assert.Nil(t, err)
373assert.NotNil(t, compRes)
374assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
375assert.Equal(t, 0, len(compRes.resources))
376assert.Equal(t, 0, len(compRes.managedResources))
377assert.Equal(t, 1, len(compRes.reconciliationResult.Hooks))
378assert.Equal(t, 0, len(app.Status.Conditions))
379}
380
381// TestCompareAppStateSkipHook checks that skipped resources are detected during manifest generation, and not
382// considered as part of resources when assessing Synced status
383func TestCompareAppStateSkipHook(t *testing.T) {
384pod := NewPod()
385pod.SetAnnotations(map[string]string{synccommon.AnnotationKeyHook: "Skip"})
386podBytes, _ := json.Marshal(pod)
387app := newFakeApp()
388data := fakeData{
389apps: []runtime.Object{app},
390manifestResponse: &apiclient.ManifestResponse{
391Manifests: []string{string(podBytes)},
392Namespace: test.FakeDestNamespace,
393Server: test.FakeClusterURL,
394Revision: "abc123",
395},
396managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
397}
398ctrl := newFakeController(&data, nil)
399sources := make([]argoappv1.ApplicationSource, 0)
400sources = append(sources, app.Spec.GetSource())
401revisions := make([]string, 0)
402revisions = append(revisions, "")
403compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
404assert.Nil(t, err)
405assert.NotNil(t, compRes)
406assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
407assert.Equal(t, 1, len(compRes.resources))
408assert.Equal(t, 1, len(compRes.managedResources))
409assert.Equal(t, 0, len(compRes.reconciliationResult.Hooks))
410assert.Equal(t, 0, len(app.Status.Conditions))
411}
412
413// checks that ignore resources are detected, but excluded from status
414func TestCompareAppStateCompareOptionIgnoreExtraneous(t *testing.T) {
415pod := NewPod()
416pod.SetAnnotations(map[string]string{common.AnnotationCompareOptions: "IgnoreExtraneous"})
417app := newFakeApp()
418data := fakeData{
419apps: []runtime.Object{app},
420manifestResponse: &apiclient.ManifestResponse{
421Manifests: []string{},
422Namespace: test.FakeDestNamespace,
423Server: test.FakeClusterURL,
424Revision: "abc123",
425},
426managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
427}
428ctrl := newFakeController(&data, nil)
429
430sources := make([]argoappv1.ApplicationSource, 0)
431sources = append(sources, app.Spec.GetSource())
432revisions := make([]string, 0)
433revisions = append(revisions, "")
434compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
435assert.Nil(t, err)
436
437assert.NotNil(t, compRes)
438assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
439assert.Len(t, compRes.resources, 0)
440assert.Len(t, compRes.managedResources, 0)
441assert.Len(t, app.Status.Conditions, 0)
442}
443
444// TestCompareAppStateExtraHook tests when there is an extra _hook_ object in live but not defined in git
445func TestCompareAppStateExtraHook(t *testing.T) {
446pod := NewPod()
447pod.SetAnnotations(map[string]string{synccommon.AnnotationKeyHook: "PreSync"})
448pod.SetNamespace(test.FakeDestNamespace)
449app := newFakeApp()
450key := kube.ResourceKey{Group: "", Kind: "Pod", Namespace: test.FakeDestNamespace, Name: app.Name}
451data := fakeData{
452manifestResponse: &apiclient.ManifestResponse{
453Manifests: []string{},
454Namespace: test.FakeDestNamespace,
455Server: test.FakeClusterURL,
456Revision: "abc123",
457},
458managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
459key: pod,
460},
461}
462ctrl := newFakeController(&data, nil)
463sources := make([]argoappv1.ApplicationSource, 0)
464sources = append(sources, app.Spec.GetSource())
465revisions := make([]string, 0)
466revisions = append(revisions, "")
467compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
468assert.Nil(t, err)
469
470assert.NotNil(t, compRes)
471assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
472assert.Equal(t, 1, len(compRes.resources))
473assert.Equal(t, 1, len(compRes.managedResources))
474assert.Equal(t, 0, len(compRes.reconciliationResult.Hooks))
475assert.Equal(t, 0, len(app.Status.Conditions))
476}
477
478// TestAppRevisions tests that revisions are properly propagated for a single source app
479func TestAppRevisionsSingleSource(t *testing.T) {
480obj1 := NewPod()
481obj1.SetNamespace(test.FakeDestNamespace)
482data := fakeData{
483manifestResponse: &apiclient.ManifestResponse{
484Manifests: []string{toJSON(t, obj1)},
485Namespace: test.FakeDestNamespace,
486Server: test.FakeClusterURL,
487Revision: "abc123",
488},
489managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
490}
491ctrl := newFakeController(&data, nil)
492
493app := newFakeApp()
494revisions := make([]string, 0)
495revisions = append(revisions, "")
496compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, app.Spec.GetSources(), false, false, nil, app.Spec.HasMultipleSources())
497assert.Nil(t, err)
498assert.NotNil(t, compRes)
499assert.NotNil(t, compRes.syncStatus)
500assert.NotEmpty(t, compRes.syncStatus.Revision)
501assert.Len(t, compRes.syncStatus.Revisions, 0)
502}
503
504// TestAppRevisions tests that revisions are properly propagated for a multi source app
505func TestAppRevisionsMultiSource(t *testing.T) {
506obj1 := NewPod()
507obj1.SetNamespace(test.FakeDestNamespace)
508data := fakeData{
509manifestResponses: []*apiclient.ManifestResponse{
510{
511Manifests: []string{toJSON(t, obj1)},
512Namespace: test.FakeDestNamespace,
513Server: test.FakeClusterURL,
514Revision: "abc123",
515},
516{
517Manifests: []string{toJSON(t, obj1)},
518Namespace: test.FakeDestNamespace,
519Server: test.FakeClusterURL,
520Revision: "def456",
521},
522{
523Manifests: []string{},
524Namespace: test.FakeDestNamespace,
525Server: test.FakeClusterURL,
526Revision: "ghi789",
527},
528},
529managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
530}
531ctrl := newFakeController(&data, nil)
532
533app := newFakeMultiSourceApp()
534revisions := make([]string, 0)
535revisions = append(revisions, "")
536compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, app.Spec.GetSources(), false, false, nil, app.Spec.HasMultipleSources())
537assert.Nil(t, err)
538assert.NotNil(t, compRes)
539assert.NotNil(t, compRes.syncStatus)
540assert.Empty(t, compRes.syncStatus.Revision)
541assert.Len(t, compRes.syncStatus.Revisions, 3)
542assert.Equal(t, "abc123", compRes.syncStatus.Revisions[0])
543assert.Equal(t, "def456", compRes.syncStatus.Revisions[1])
544assert.Equal(t, "ghi789", compRes.syncStatus.Revisions[2])
545}
546
547func toJSON(t *testing.T, obj *unstructured.Unstructured) string {
548data, err := json.Marshal(obj)
549assert.NoError(t, err)
550return string(data)
551}
552
553func TestCompareAppStateDuplicatedNamespacedResources(t *testing.T) {
554obj1 := NewPod()
555obj1.SetNamespace(test.FakeDestNamespace)
556obj2 := NewPod()
557obj3 := NewPod()
558obj3.SetNamespace("kube-system")
559obj4 := NewPod()
560obj4.SetGenerateName("my-pod")
561obj4.SetName("")
562obj5 := NewPod()
563obj5.SetName("")
564obj5.SetGenerateName("my-pod")
565
566app := newFakeApp()
567data := fakeData{
568manifestResponse: &apiclient.ManifestResponse{
569Manifests: []string{toJSON(t, obj1), toJSON(t, obj2), toJSON(t, obj3), toJSON(t, obj4), toJSON(t, obj5)},
570Namespace: test.FakeDestNamespace,
571Server: test.FakeClusterURL,
572Revision: "abc123",
573},
574managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
575kube.GetResourceKey(obj1): obj1,
576kube.GetResourceKey(obj3): obj3,
577},
578}
579ctrl := newFakeController(&data, nil)
580sources := make([]argoappv1.ApplicationSource, 0)
581sources = append(sources, app.Spec.GetSource())
582revisions := make([]string, 0)
583revisions = append(revisions, "")
584compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
585assert.Nil(t, err)
586
587assert.NotNil(t, compRes)
588assert.Equal(t, 1, len(app.Status.Conditions))
589assert.NotNil(t, app.Status.Conditions[0].LastTransitionTime)
590assert.Equal(t, argoappv1.ApplicationConditionRepeatedResourceWarning, app.Status.Conditions[0].Type)
591assert.Equal(t, "Resource /Pod/fake-dest-ns/my-pod appeared 2 times among application resources.", app.Status.Conditions[0].Message)
592assert.Equal(t, 4, len(compRes.resources))
593}
594
595func TestCompareAppStateManagedNamespaceMetadataWithLiveNsDoesNotGetPruned(t *testing.T) {
596app := newFakeApp()
597app.Spec.SyncPolicy = &argoappv1.SyncPolicy{
598ManagedNamespaceMetadata: &argoappv1.ManagedNamespaceMetadata{
599Labels: nil,
600Annotations: nil,
601},
602}
603
604ns := NewNamespace()
605ns.SetName(test.FakeDestNamespace)
606ns.SetNamespace(test.FakeDestNamespace)
607ns.SetAnnotations(map[string]string{"argocd.argoproj.io/sync-options": "ServerSideApply=true"})
608
609data := fakeData{
610manifestResponse: &apiclient.ManifestResponse{
611Manifests: []string{},
612Namespace: test.FakeDestNamespace,
613Server: test.FakeClusterURL,
614Revision: "abc123",
615},
616managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
617kube.GetResourceKey(ns): ns,
618},
619}
620ctrl := newFakeController(&data, nil)
621compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, []string{}, app.Spec.Sources, false, false, nil, false)
622assert.Nil(t, err)
623
624assert.NotNil(t, compRes)
625assert.Equal(t, 0, len(app.Status.Conditions))
626assert.NotNil(t, compRes)
627assert.NotNil(t, compRes.syncStatus)
628// Ensure that ns does not get pruned
629assert.NotNil(t, compRes.reconciliationResult.Target[0])
630assert.Equal(t, compRes.reconciliationResult.Target[0].GetName(), ns.GetName())
631assert.Equal(t, compRes.reconciliationResult.Target[0].GetAnnotations(), ns.GetAnnotations())
632assert.Equal(t, compRes.reconciliationResult.Target[0].GetLabels(), ns.GetLabels())
633assert.Len(t, compRes.resources, 1)
634assert.Len(t, compRes.managedResources, 1)
635}
636
637var defaultProj = argoappv1.AppProject{
638ObjectMeta: metav1.ObjectMeta{
639Name: "default",
640Namespace: test.FakeArgoCDNamespace,
641},
642Spec: argoappv1.AppProjectSpec{
643SourceRepos: []string{"*"},
644Destinations: []argoappv1.ApplicationDestination{
645{
646Server: "*",
647Namespace: "*",
648},
649},
650},
651}
652
653func TestSetHealth(t *testing.T) {
654app := newFakeApp()
655deployment := kube.MustToUnstructured(&v1.Deployment{
656TypeMeta: metav1.TypeMeta{
657APIVersion: "apps/v1",
658Kind: "Deployment",
659},
660ObjectMeta: metav1.ObjectMeta{
661Name: "demo",
662Namespace: "default",
663},
664})
665ctrl := newFakeController(&fakeData{
666apps: []runtime.Object{app, &defaultProj},
667manifestResponse: &apiclient.ManifestResponse{
668Manifests: []string{},
669Namespace: test.FakeDestNamespace,
670Server: test.FakeClusterURL,
671Revision: "abc123",
672},
673managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
674kube.GetResourceKey(deployment): deployment,
675},
676}, nil)
677
678sources := make([]argoappv1.ApplicationSource, 0)
679sources = append(sources, app.Spec.GetSource())
680revisions := make([]string, 0)
681revisions = append(revisions, "")
682compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
683assert.Nil(t, err)
684
685assert.Equal(t, health.HealthStatusHealthy, compRes.healthStatus.Status)
686}
687
688func TestSetHealthSelfReferencedApp(t *testing.T) {
689app := newFakeApp()
690unstructuredApp := kube.MustToUnstructured(app)
691deployment := kube.MustToUnstructured(&v1.Deployment{
692TypeMeta: metav1.TypeMeta{
693APIVersion: "apps/v1",
694Kind: "Deployment",
695},
696ObjectMeta: metav1.ObjectMeta{
697Name: "demo",
698Namespace: "default",
699},
700})
701ctrl := newFakeController(&fakeData{
702apps: []runtime.Object{app, &defaultProj},
703manifestResponse: &apiclient.ManifestResponse{
704Manifests: []string{},
705Namespace: test.FakeDestNamespace,
706Server: test.FakeClusterURL,
707Revision: "abc123",
708},
709managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
710kube.GetResourceKey(deployment): deployment,
711kube.GetResourceKey(unstructuredApp): unstructuredApp,
712},
713}, nil)
714
715sources := make([]argoappv1.ApplicationSource, 0)
716sources = append(sources, app.Spec.GetSource())
717revisions := make([]string, 0)
718revisions = append(revisions, "")
719compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
720assert.Nil(t, err)
721
722assert.Equal(t, health.HealthStatusHealthy, compRes.healthStatus.Status)
723}
724
725func TestSetManagedResourcesWithOrphanedResources(t *testing.T) {
726proj := defaultProj.DeepCopy()
727proj.Spec.OrphanedResources = &argoappv1.OrphanedResourcesMonitorSettings{}
728
729app := newFakeApp()
730ctrl := newFakeController(&fakeData{
731apps: []runtime.Object{app, proj},
732namespacedResources: map[kube.ResourceKey]namespacedResource{
733kube.NewResourceKey("apps", kube.DeploymentKind, app.Namespace, "guestbook"): {
734ResourceNode: argoappv1.ResourceNode{
735ResourceRef: argoappv1.ResourceRef{Kind: kube.DeploymentKind, Name: "guestbook", Namespace: app.Namespace},
736},
737AppName: "",
738},
739},
740}, nil)
741
742tree, err := ctrl.setAppManagedResources(app, &comparisonResult{managedResources: make([]managedResource, 0)})
743
744assert.NoError(t, err)
745assert.Equal(t, len(tree.OrphanedNodes), 1)
746assert.Equal(t, "guestbook", tree.OrphanedNodes[0].Name)
747assert.Equal(t, app.Namespace, tree.OrphanedNodes[0].Namespace)
748}
749
750func TestSetManagedResourcesWithResourcesOfAnotherApp(t *testing.T) {
751proj := defaultProj.DeepCopy()
752proj.Spec.OrphanedResources = &argoappv1.OrphanedResourcesMonitorSettings{}
753
754app1 := newFakeApp()
755app1.Name = "app1"
756app2 := newFakeApp()
757app2.Name = "app2"
758
759ctrl := newFakeController(&fakeData{
760apps: []runtime.Object{app1, app2, proj},
761namespacedResources: map[kube.ResourceKey]namespacedResource{
762kube.NewResourceKey("apps", kube.DeploymentKind, app2.Namespace, "guestbook"): {
763ResourceNode: argoappv1.ResourceNode{
764ResourceRef: argoappv1.ResourceRef{Kind: kube.DeploymentKind, Name: "guestbook", Namespace: app2.Namespace},
765},
766AppName: "app2",
767},
768},
769}, nil)
770
771tree, err := ctrl.setAppManagedResources(app1, &comparisonResult{managedResources: make([]managedResource, 0)})
772
773assert.NoError(t, err)
774assert.Equal(t, 0, len(tree.OrphanedNodes))
775}
776
777func TestReturnUnknownComparisonStateOnSettingLoadError(t *testing.T) {
778proj := defaultProj.DeepCopy()
779proj.Spec.OrphanedResources = &argoappv1.OrphanedResourcesMonitorSettings{}
780
781app := newFakeApp()
782
783ctrl := newFakeController(&fakeData{
784apps: []runtime.Object{app, proj},
785configMapData: map[string]string{
786"resource.customizations": "invalid setting",
787},
788}, nil)
789
790sources := make([]argoappv1.ApplicationSource, 0)
791sources = append(sources, app.Spec.GetSource())
792revisions := make([]string, 0)
793revisions = append(revisions, "")
794compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
795assert.Nil(t, err)
796
797assert.Equal(t, health.HealthStatusUnknown, compRes.healthStatus.Status)
798assert.Equal(t, argoappv1.SyncStatusCodeUnknown, compRes.syncStatus.Status)
799}
800
801func TestSetManagedResourcesKnownOrphanedResourceExceptions(t *testing.T) {
802proj := defaultProj.DeepCopy()
803proj.Spec.OrphanedResources = &argoappv1.OrphanedResourcesMonitorSettings{}
804proj.Spec.SourceNamespaces = []string{"default"}
805
806app := newFakeApp()
807app.Namespace = "default"
808
809ctrl := newFakeController(&fakeData{
810apps: []runtime.Object{app, proj},
811namespacedResources: map[kube.ResourceKey]namespacedResource{
812kube.NewResourceKey("apps", kube.DeploymentKind, app.Namespace, "guestbook"): {
813ResourceNode: argoappv1.ResourceNode{ResourceRef: argoappv1.ResourceRef{Group: "apps", Kind: kube.DeploymentKind, Name: "guestbook", Namespace: app.Namespace}},
814},
815kube.NewResourceKey("", kube.ServiceAccountKind, app.Namespace, "default"): {
816ResourceNode: argoappv1.ResourceNode{ResourceRef: argoappv1.ResourceRef{Kind: kube.ServiceAccountKind, Name: "default", Namespace: app.Namespace}},
817},
818kube.NewResourceKey("", kube.ServiceKind, app.Namespace, "kubernetes"): {
819ResourceNode: argoappv1.ResourceNode{ResourceRef: argoappv1.ResourceRef{Kind: kube.ServiceAccountKind, Name: "kubernetes", Namespace: app.Namespace}},
820},
821},
822}, nil)
823
824tree, err := ctrl.setAppManagedResources(app, &comparisonResult{managedResources: make([]managedResource, 0)})
825
826assert.NoError(t, err)
827assert.Len(t, tree.OrphanedNodes, 1)
828assert.Equal(t, "guestbook", tree.OrphanedNodes[0].Name)
829}
830
831func Test_appStateManager_persistRevisionHistory(t *testing.T) {
832app := newFakeApp()
833ctrl := newFakeController(&fakeData{
834apps: []runtime.Object{app},
835}, nil)
836manager := ctrl.appStateManager.(*appStateManager)
837setRevisionHistoryLimit := func(value int) {
838i := int64(value)
839app.Spec.RevisionHistoryLimit = &i
840}
841addHistory := func() {
842err := manager.persistRevisionHistory(app, "my-revision", argoappv1.ApplicationSource{}, []string{}, []argoappv1.ApplicationSource{}, false, metav1.Time{}, v1alpha1.OperationInitiator{})
843assert.NoError(t, err)
844}
845addHistory()
846assert.Len(t, app.Status.History, 1)
847addHistory()
848assert.Len(t, app.Status.History, 2)
849addHistory()
850assert.Len(t, app.Status.History, 3)
851addHistory()
852assert.Len(t, app.Status.History, 4)
853addHistory()
854assert.Len(t, app.Status.History, 5)
855addHistory()
856assert.Len(t, app.Status.History, 6)
857addHistory()
858assert.Len(t, app.Status.History, 7)
859addHistory()
860assert.Len(t, app.Status.History, 8)
861addHistory()
862assert.Len(t, app.Status.History, 9)
863addHistory()
864assert.Len(t, app.Status.History, 10)
865// default limit is 10
866addHistory()
867assert.Len(t, app.Status.History, 10)
868// increase limit
869setRevisionHistoryLimit(11)
870addHistory()
871assert.Len(t, app.Status.History, 11)
872// decrease limit
873setRevisionHistoryLimit(9)
874addHistory()
875assert.Len(t, app.Status.History, 9)
876
877metav1NowTime := metav1.NewTime(time.Now())
878err := manager.persistRevisionHistory(app, "my-revision", argoappv1.ApplicationSource{}, []string{}, []argoappv1.ApplicationSource{}, false, metav1NowTime, v1alpha1.OperationInitiator{})
879assert.NoError(t, err)
880assert.Equal(t, app.Status.History.LastRevisionHistory().DeployStartedAt, &metav1NowTime)
881}
882
883// helper function to read contents of a file to string
884// panics on error
885func mustReadFile(path string) string {
886b, err := os.ReadFile(path)
887if err != nil {
888panic(err.Error())
889}
890return string(b)
891}
892
893var signedProj = argoappv1.AppProject{
894ObjectMeta: metav1.ObjectMeta{
895Name: "default",
896Namespace: test.FakeArgoCDNamespace,
897},
898Spec: argoappv1.AppProjectSpec{
899SourceRepos: []string{"*"},
900Destinations: []argoappv1.ApplicationDestination{
901{
902Server: "*",
903Namespace: "*",
904},
905},
906SignatureKeys: []argoappv1.SignatureKey{
907{
908KeyID: "4AEE18F83AFDEB23",
909},
910},
911},
912}
913
914func TestSignedResponseNoSignatureRequired(t *testing.T) {
915t.Setenv("ARGOCD_GPG_ENABLED", "true")
916
917// We have a good signature response, but project does not require signed commits
918{
919app := newFakeApp()
920data := fakeData{
921manifestResponse: &apiclient.ManifestResponse{
922Manifests: []string{},
923Namespace: test.FakeDestNamespace,
924Server: test.FakeClusterURL,
925Revision: "abc123",
926VerifyResult: mustReadFile("../util/gpg/testdata/good_signature.txt"),
927},
928managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
929}
930ctrl := newFakeController(&data, nil)
931sources := make([]argoappv1.ApplicationSource, 0)
932sources = append(sources, app.Spec.GetSource())
933revisions := make([]string, 0)
934revisions = append(revisions, "")
935compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
936assert.Nil(t, err)
937assert.NotNil(t, compRes)
938assert.NotNil(t, compRes.syncStatus)
939assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
940assert.Len(t, compRes.resources, 0)
941assert.Len(t, compRes.managedResources, 0)
942assert.Len(t, app.Status.Conditions, 0)
943}
944// We have a bad signature response, but project does not require signed commits
945{
946app := newFakeApp()
947data := fakeData{
948manifestResponse: &apiclient.ManifestResponse{
949Manifests: []string{},
950Namespace: test.FakeDestNamespace,
951Server: test.FakeClusterURL,
952Revision: "abc123",
953VerifyResult: mustReadFile("../util/gpg/testdata/bad_signature_bad.txt"),
954},
955managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
956}
957ctrl := newFakeController(&data, nil)
958sources := make([]argoappv1.ApplicationSource, 0)
959sources = append(sources, app.Spec.GetSource())
960revisions := make([]string, 0)
961revisions = append(revisions, "")
962compRes, err := ctrl.appStateManager.CompareAppState(app, &defaultProj, revisions, sources, false, false, nil, false)
963assert.Nil(t, err)
964assert.NotNil(t, compRes)
965assert.NotNil(t, compRes.syncStatus)
966assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
967assert.Len(t, compRes.resources, 0)
968assert.Len(t, compRes.managedResources, 0)
969assert.Len(t, app.Status.Conditions, 0)
970}
971}
972
973func TestSignedResponseSignatureRequired(t *testing.T) {
974t.Setenv("ARGOCD_GPG_ENABLED", "true")
975
976// We have a good signature response, valid key, and signing is required - sync!
977{
978app := newFakeApp()
979data := fakeData{
980manifestResponse: &apiclient.ManifestResponse{
981Manifests: []string{},
982Namespace: test.FakeDestNamespace,
983Server: test.FakeClusterURL,
984Revision: "abc123",
985VerifyResult: mustReadFile("../util/gpg/testdata/good_signature.txt"),
986},
987managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
988}
989ctrl := newFakeController(&data, nil)
990sources := make([]argoappv1.ApplicationSource, 0)
991sources = append(sources, app.Spec.GetSource())
992revisions := make([]string, 0)
993revisions = append(revisions, "")
994compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, nil, false)
995assert.Nil(t, err)
996assert.NotNil(t, compRes)
997assert.NotNil(t, compRes.syncStatus)
998assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
999assert.Len(t, compRes.resources, 0)
1000assert.Len(t, compRes.managedResources, 0)
1001assert.Len(t, app.Status.Conditions, 0)
1002}
1003// We have a bad signature response and signing is required - do not sync
1004{
1005app := newFakeApp()
1006data := fakeData{
1007manifestResponse: &apiclient.ManifestResponse{
1008Manifests: []string{},
1009Namespace: test.FakeDestNamespace,
1010Server: test.FakeClusterURL,
1011Revision: "abc123",
1012VerifyResult: mustReadFile("../util/gpg/testdata/bad_signature_bad.txt"),
1013},
1014managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
1015}
1016ctrl := newFakeController(&data, nil)
1017sources := make([]argoappv1.ApplicationSource, 0)
1018sources = append(sources, app.Spec.GetSource())
1019revisions := make([]string, 0)
1020revisions = append(revisions, "abc123")
1021compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, nil, false)
1022assert.Nil(t, err)
1023assert.NotNil(t, compRes)
1024assert.NotNil(t, compRes.syncStatus)
1025assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
1026assert.Len(t, compRes.resources, 0)
1027assert.Len(t, compRes.managedResources, 0)
1028assert.Len(t, app.Status.Conditions, 1)
1029}
1030// We have a malformed signature response and signing is required - do not sync
1031{
1032app := newFakeApp()
1033data := fakeData{
1034manifestResponse: &apiclient.ManifestResponse{
1035Manifests: []string{},
1036Namespace: test.FakeDestNamespace,
1037Server: test.FakeClusterURL,
1038Revision: "abc123",
1039VerifyResult: mustReadFile("../util/gpg/testdata/bad_signature_malformed1.txt"),
1040},
1041managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
1042}
1043ctrl := newFakeController(&data, nil)
1044sources := make([]argoappv1.ApplicationSource, 0)
1045sources = append(sources, app.Spec.GetSource())
1046revisions := make([]string, 0)
1047revisions = append(revisions, "abc123")
1048compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, nil, false)
1049assert.Nil(t, err)
1050assert.NotNil(t, compRes)
1051assert.NotNil(t, compRes.syncStatus)
1052assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
1053assert.Len(t, compRes.resources, 0)
1054assert.Len(t, compRes.managedResources, 0)
1055assert.Len(t, app.Status.Conditions, 1)
1056}
1057// We have no signature response (no signature made) and signing is required - do not sync
1058{
1059app := newFakeApp()
1060data := fakeData{
1061manifestResponse: &apiclient.ManifestResponse{
1062Manifests: []string{},
1063Namespace: test.FakeDestNamespace,
1064Server: test.FakeClusterURL,
1065Revision: "abc123",
1066VerifyResult: "",
1067},
1068managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
1069}
1070ctrl := newFakeController(&data, nil)
1071sources := make([]argoappv1.ApplicationSource, 0)
1072sources = append(sources, app.Spec.GetSource())
1073revisions := make([]string, 0)
1074revisions = append(revisions, "abc123")
1075compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, nil, false)
1076assert.Nil(t, err)
1077assert.NotNil(t, compRes)
1078assert.NotNil(t, compRes.syncStatus)
1079assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
1080assert.Len(t, compRes.resources, 0)
1081assert.Len(t, compRes.managedResources, 0)
1082assert.Len(t, app.Status.Conditions, 1)
1083}
1084
1085// We have a good signature and signing is required, but key is not allowed - do not sync
1086{
1087app := newFakeApp()
1088data := fakeData{
1089manifestResponse: &apiclient.ManifestResponse{
1090Manifests: []string{},
1091Namespace: test.FakeDestNamespace,
1092Server: test.FakeClusterURL,
1093Revision: "abc123",
1094VerifyResult: mustReadFile("../util/gpg/testdata/good_signature.txt"),
1095},
1096managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
1097}
1098ctrl := newFakeController(&data, nil)
1099testProj := signedProj
1100testProj.Spec.SignatureKeys[0].KeyID = "4AEE18F83AFDEB24"
1101sources := make([]argoappv1.ApplicationSource, 0)
1102sources = append(sources, app.Spec.GetSource())
1103revisions := make([]string, 0)
1104revisions = append(revisions, "abc123")
1105compRes, err := ctrl.appStateManager.CompareAppState(app, &testProj, revisions, sources, false, false, nil, false)
1106assert.Nil(t, err)
1107assert.NotNil(t, compRes)
1108assert.NotNil(t, compRes.syncStatus)
1109assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
1110assert.Len(t, compRes.resources, 0)
1111assert.Len(t, compRes.managedResources, 0)
1112assert.Len(t, app.Status.Conditions, 1)
1113assert.Contains(t, app.Status.Conditions[0].Message, "key is not allowed")
1114}
1115// Signature required and local manifests supplied - do not sync
1116{
1117app := newFakeApp()
1118data := fakeData{
1119manifestResponse: &apiclient.ManifestResponse{
1120Manifests: []string{},
1121Namespace: test.FakeDestNamespace,
1122Server: test.FakeClusterURL,
1123Revision: "abc123",
1124VerifyResult: "",
1125},
1126managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
1127}
1128// it doesn't matter for our test whether local manifests are valid
1129localManifests := []string{"foobar"}
1130ctrl := newFakeController(&data, nil)
1131sources := make([]argoappv1.ApplicationSource, 0)
1132sources = append(sources, app.Spec.GetSource())
1133revisions := make([]string, 0)
1134revisions = append(revisions, "abc123")
1135compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, localManifests, false)
1136assert.Nil(t, err)
1137assert.NotNil(t, compRes)
1138assert.NotNil(t, compRes.syncStatus)
1139assert.Equal(t, argoappv1.SyncStatusCodeUnknown, compRes.syncStatus.Status)
1140assert.Len(t, compRes.resources, 0)
1141assert.Len(t, compRes.managedResources, 0)
1142assert.Len(t, app.Status.Conditions, 1)
1143assert.Contains(t, app.Status.Conditions[0].Message, "Cannot use local manifests")
1144}
1145
1146t.Setenv("ARGOCD_GPG_ENABLED", "false")
1147// We have a bad signature response and signing would be required, but GPG subsystem is disabled - sync
1148{
1149app := newFakeApp()
1150data := fakeData{
1151manifestResponse: &apiclient.ManifestResponse{
1152Manifests: []string{},
1153Namespace: test.FakeDestNamespace,
1154Server: test.FakeClusterURL,
1155Revision: "abc123",
1156VerifyResult: mustReadFile("../util/gpg/testdata/bad_signature_bad.txt"),
1157},
1158managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
1159}
1160ctrl := newFakeController(&data, nil)
1161sources := make([]argoappv1.ApplicationSource, 0)
1162sources = append(sources, app.Spec.GetSource())
1163revisions := make([]string, 0)
1164revisions = append(revisions, "abc123")
1165compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, nil, false)
1166assert.Nil(t, err)
1167assert.NotNil(t, compRes)
1168assert.NotNil(t, compRes.syncStatus)
1169assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
1170assert.Len(t, compRes.resources, 0)
1171assert.Len(t, compRes.managedResources, 0)
1172assert.Len(t, app.Status.Conditions, 0)
1173}
1174
1175// Signature required and local manifests supplied and GPG subsystem is disabled - sync
1176{
1177app := newFakeApp()
1178data := fakeData{
1179manifestResponse: &apiclient.ManifestResponse{
1180Manifests: []string{},
1181Namespace: test.FakeDestNamespace,
1182Server: test.FakeClusterURL,
1183Revision: "abc123",
1184VerifyResult: "",
1185},
1186managedLiveObjs: make(map[kube.ResourceKey]*unstructured.Unstructured),
1187}
1188// it doesn't matter for our test whether local manifests are valid
1189localManifests := []string{""}
1190ctrl := newFakeController(&data, nil)
1191sources := make([]argoappv1.ApplicationSource, 0)
1192sources = append(sources, app.Spec.GetSource())
1193revisions := make([]string, 0)
1194revisions = append(revisions, "abc123")
1195compRes, err := ctrl.appStateManager.CompareAppState(app, &signedProj, revisions, sources, false, false, localManifests, false)
1196assert.Nil(t, err)
1197assert.NotNil(t, compRes)
1198assert.NotNil(t, compRes.syncStatus)
1199assert.Equal(t, argoappv1.SyncStatusCodeSynced, compRes.syncStatus.Status)
1200assert.Len(t, compRes.resources, 0)
1201assert.Len(t, compRes.managedResources, 0)
1202assert.Len(t, app.Status.Conditions, 0)
1203}
1204}
1205
1206func TestComparisonResult_GetHealthStatus(t *testing.T) {
1207status := &argoappv1.HealthStatus{Status: health.HealthStatusMissing}
1208res := comparisonResult{
1209healthStatus: status,
1210}
1211
1212assert.Equal(t, status, res.GetHealthStatus())
1213}
1214
1215func TestComparisonResult_GetSyncStatus(t *testing.T) {
1216status := &argoappv1.SyncStatus{Status: argoappv1.SyncStatusCodeOutOfSync}
1217res := comparisonResult{
1218syncStatus: status,
1219}
1220
1221assert.Equal(t, status, res.GetSyncStatus())
1222}
1223
1224func TestIsLiveResourceManaged(t *testing.T) {
1225managedObj := kube.MustToUnstructured(&corev1.ConfigMap{
1226TypeMeta: metav1.TypeMeta{
1227APIVersion: "v1",
1228Kind: "ConfigMap",
1229},
1230ObjectMeta: metav1.ObjectMeta{
1231Name: "configmap1",
1232Namespace: "default",
1233Annotations: map[string]string{
1234common.AnnotationKeyAppInstance: "guestbook:/ConfigMap:default/configmap1",
1235},
1236},
1237})
1238managedObjWithLabel := kube.MustToUnstructured(&corev1.ConfigMap{
1239TypeMeta: metav1.TypeMeta{
1240APIVersion: "v1",
1241Kind: "ConfigMap",
1242},
1243ObjectMeta: metav1.ObjectMeta{
1244Name: "configmap1",
1245Namespace: "default",
1246Labels: map[string]string{
1247common.LabelKeyAppInstance: "guestbook",
1248},
1249},
1250})
1251unmanagedObjWrongName := kube.MustToUnstructured(&corev1.ConfigMap{
1252TypeMeta: metav1.TypeMeta{
1253APIVersion: "v1",
1254Kind: "ConfigMap",
1255},
1256ObjectMeta: metav1.ObjectMeta{
1257Name: "configmap2",
1258Namespace: "default",
1259Annotations: map[string]string{
1260common.AnnotationKeyAppInstance: "guestbook:/ConfigMap:default/configmap1",
1261},
1262},
1263})
1264unmanagedObjWrongKind := kube.MustToUnstructured(&corev1.ConfigMap{
1265TypeMeta: metav1.TypeMeta{
1266APIVersion: "v1",
1267Kind: "ConfigMap",
1268},
1269ObjectMeta: metav1.ObjectMeta{
1270Name: "configmap2",
1271Namespace: "default",
1272Annotations: map[string]string{
1273common.AnnotationKeyAppInstance: "guestbook:/Service:default/configmap2",
1274},
1275},
1276})
1277unmanagedObjWrongGroup := kube.MustToUnstructured(&corev1.ConfigMap{
1278TypeMeta: metav1.TypeMeta{
1279APIVersion: "v1",
1280Kind: "ConfigMap",
1281},
1282ObjectMeta: metav1.ObjectMeta{
1283Name: "configmap2",
1284Namespace: "default",
1285Annotations: map[string]string{
1286common.AnnotationKeyAppInstance: "guestbook:apps/ConfigMap:default/configmap2",
1287},
1288},
1289})
1290unmanagedObjWrongNamespace := kube.MustToUnstructured(&corev1.ConfigMap{
1291TypeMeta: metav1.TypeMeta{
1292APIVersion: "v1",
1293Kind: "ConfigMap",
1294},
1295ObjectMeta: metav1.ObjectMeta{
1296Name: "configmap2",
1297Namespace: "default",
1298Annotations: map[string]string{
1299common.AnnotationKeyAppInstance: "guestbook:/ConfigMap:fakens/configmap2",
1300},
1301},
1302})
1303managedWrongAPIGroup := kube.MustToUnstructured(&networkingv1.Ingress{
1304TypeMeta: metav1.TypeMeta{
1305APIVersion: "networking.k8s.io/v1",
1306Kind: "Ingress",
1307},
1308ObjectMeta: metav1.ObjectMeta{
1309Name: "some-ingress",
1310Namespace: "default",
1311Annotations: map[string]string{
1312common.AnnotationKeyAppInstance: "guestbook:extensions/Ingress:default/some-ingress",
1313},
1314},
1315})
1316ctrl := newFakeController(&fakeData{
1317apps: []runtime.Object{app, &defaultProj},
1318manifestResponse: &apiclient.ManifestResponse{
1319Manifests: []string{},
1320Namespace: test.FakeDestNamespace,
1321Server: test.FakeClusterURL,
1322Revision: "abc123",
1323},
1324managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
1325kube.GetResourceKey(managedObj): managedObj,
1326kube.GetResourceKey(unmanagedObjWrongName): unmanagedObjWrongName,
1327kube.GetResourceKey(unmanagedObjWrongKind): unmanagedObjWrongKind,
1328kube.GetResourceKey(unmanagedObjWrongGroup): unmanagedObjWrongGroup,
1329kube.GetResourceKey(unmanagedObjWrongNamespace): unmanagedObjWrongNamespace,
1330},
1331}, nil)
1332
1333manager := ctrl.appStateManager.(*appStateManager)
1334appName := "guestbook"
1335
1336t.Run("will return true if trackingid matches the resource", func(t *testing.T) {
1337// given
1338t.Parallel()
1339configObj := managedObj.DeepCopy()
1340
1341// then
1342assert.True(t, manager.isSelfReferencedObj(managedObj, configObj, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
1343assert.True(t, manager.isSelfReferencedObj(managedObj, configObj, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
1344})
1345t.Run("will return true if tracked with label", func(t *testing.T) {
1346// given
1347t.Parallel()
1348configObj := managedObjWithLabel.DeepCopy()
1349
1350// then
1351assert.True(t, manager.isSelfReferencedObj(managedObjWithLabel, configObj, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
1352})
1353t.Run("will handle if trackingId has wrong resource name and config is nil", func(t *testing.T) {
1354// given
1355t.Parallel()
1356
1357// then
1358assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongName, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
1359assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongName, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
1360})
1361t.Run("will handle if trackingId has wrong resource group and config is nil", func(t *testing.T) {
1362// given
1363t.Parallel()
1364
1365// then
1366assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongGroup, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
1367assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongGroup, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
1368})
1369t.Run("will handle if trackingId has wrong kind and config is nil", func(t *testing.T) {
1370// given
1371t.Parallel()
1372
1373// then
1374assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongKind, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
1375assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongKind, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
1376})
1377t.Run("will handle if trackingId has wrong namespace and config is nil", func(t *testing.T) {
1378// given
1379t.Parallel()
1380
1381// then
1382assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongNamespace, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
1383assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongNamespace, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotationAndLabel))
1384})
1385t.Run("will return true if live is nil", func(t *testing.T) {
1386t.Parallel()
1387assert.True(t, manager.isSelfReferencedObj(nil, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
1388})
1389
1390t.Run("will handle upgrade in desired state APIGroup", func(t *testing.T) {
1391// given
1392t.Parallel()
1393config := managedWrongAPIGroup.DeepCopy()
1394delete(config.GetAnnotations(), common.AnnotationKeyAppInstance)
1395
1396// then
1397assert.True(t, manager.isSelfReferencedObj(managedWrongAPIGroup, config, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
1398})
1399}
1400
1401func TestUseDiffCache(t *testing.T) {
1402type fixture struct {
1403testName string
1404noCache bool
1405manifestInfos []*apiclient.ManifestResponse
1406sources []argoappv1.ApplicationSource
1407app *argoappv1.Application
1408manifestRevisions []string
1409statusRefreshTimeout time.Duration
1410expectedUseCache bool
1411serverSideDiff bool
1412}
1413
1414manifestInfos := func(revision string) []*apiclient.ManifestResponse {
1415return []*apiclient.ManifestResponse{
1416{
1417Manifests: []string{
1418"{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"labels\":{\"app.kubernetes.io/instance\":\"httpbin\"},\"name\":\"httpbin-svc\",\"namespace\":\"httpbin\"},\"spec\":{\"ports\":[{\"name\":\"http-port\",\"port\":7777,\"targetPort\":80},{\"name\":\"test\",\"port\":333}],\"selector\":{\"app\":\"httpbin\"}}}",
1419"{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"labels\":{\"app.kubernetes.io/instance\":\"httpbin\"},\"name\":\"httpbin-deployment\",\"namespace\":\"httpbin\"},\"spec\":{\"replicas\":2,\"selector\":{\"matchLabels\":{\"app\":\"httpbin\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"httpbin\"}},\"spec\":{\"containers\":[{\"image\":\"kennethreitz/httpbin\",\"imagePullPolicy\":\"Always\",\"name\":\"httpbin\",\"ports\":[{\"containerPort\":80}]}]}}}}",
1420},
1421Namespace: "",
1422Server: "",
1423Revision: revision,
1424SourceType: "Kustomize",
1425VerifyResult: "",
1426},
1427}
1428}
1429sources := func() []argoappv1.ApplicationSource {
1430return []argoappv1.ApplicationSource{
1431{
1432RepoURL: "https://some-repo.com",
1433Path: "argocd/httpbin",
1434TargetRevision: "HEAD",
1435},
1436}
1437}
1438
1439app := func(namespace string, revision string, refresh bool, a *argoappv1.Application) *argoappv1.Application {
1440app := &argoappv1.Application{
1441ObjectMeta: metav1.ObjectMeta{
1442Name: "httpbin",
1443Namespace: namespace,
1444},
1445Spec: argoappv1.ApplicationSpec{
1446Source: &argoappv1.ApplicationSource{
1447RepoURL: "https://some-repo.com",
1448Path: "argocd/httpbin",
1449TargetRevision: "HEAD",
1450},
1451Destination: argoappv1.ApplicationDestination{
1452Server: "https://kubernetes.default.svc",
1453Namespace: "httpbin",
1454},
1455Project: "default",
1456SyncPolicy: &argoappv1.SyncPolicy{
1457SyncOptions: []string{
1458"CreateNamespace=true",
1459"ServerSideApply=true",
1460},
1461},
1462},
1463Status: argoappv1.ApplicationStatus{
1464Resources: []argoappv1.ResourceStatus{},
1465Sync: argoappv1.SyncStatus{
1466Status: argoappv1.SyncStatusCodeSynced,
1467ComparedTo: argoappv1.ComparedTo{
1468Source: argoappv1.ApplicationSource{
1469RepoURL: "https://some-repo.com",
1470Path: "argocd/httpbin",
1471TargetRevision: "HEAD",
1472},
1473Destination: argoappv1.ApplicationDestination{
1474Server: "https://kubernetes.default.svc",
1475Namespace: "httpbin",
1476},
1477},
1478Revision: revision,
1479Revisions: []string{},
1480},
1481ReconciledAt: &metav1.Time{
1482Time: time.Now().Add(-time.Hour),
1483},
1484},
1485}
1486if refresh {
1487annotations := make(map[string]string)
1488annotations[argoappv1.AnnotationKeyRefresh] = string(argoappv1.RefreshTypeNormal)
1489app.SetAnnotations(annotations)
1490}
1491if a != nil {
1492err := mergo.Merge(app, a, mergo.WithOverride, mergo.WithOverwriteWithEmptyValue)
1493if err != nil {
1494t.Fatalf("error merging app: %s", err)
1495}
1496}
1497return app
1498}
1499
1500cases := []fixture{
1501{
1502testName: "will use diff cache",
1503noCache: false,
1504manifestInfos: manifestInfos("rev1"),
1505sources: sources(),
1506app: app("httpbin", "rev1", false, nil),
1507manifestRevisions: []string{"rev1"},
1508statusRefreshTimeout: time.Hour * 24,
1509expectedUseCache: true,
1510serverSideDiff: false,
1511},
1512{
1513testName: "will use diff cache for multisource",
1514noCache: false,
1515manifestInfos: manifestInfos("rev1"),
1516sources: sources(),
1517app: app("httpbin", "", false, &argoappv1.Application{
1518Spec: argoappv1.ApplicationSpec{
1519Source: nil,
1520Sources: argoappv1.ApplicationSources{
1521{
1522RepoURL: "multisource repo1",
1523},
1524{
1525RepoURL: "multisource repo2",
1526},
1527},
1528},
1529Status: argoappv1.ApplicationStatus{
1530Resources: []argoappv1.ResourceStatus{},
1531Sync: argoappv1.SyncStatus{
1532Status: argoappv1.SyncStatusCodeSynced,
1533ComparedTo: argoappv1.ComparedTo{
1534Source: argoappv1.ApplicationSource{},
1535Sources: argoappv1.ApplicationSources{
1536{
1537RepoURL: "multisource repo1",
1538},
1539{
1540RepoURL: "multisource repo2",
1541},
1542},
1543},
1544Revisions: []string{"rev1", "rev2"},
1545},
1546ReconciledAt: &metav1.Time{
1547Time: time.Now().Add(-time.Hour),
1548},
1549},
1550}),
1551manifestRevisions: []string{"rev1", "rev2"},
1552statusRefreshTimeout: time.Hour * 24,
1553expectedUseCache: true,
1554serverSideDiff: false,
1555},
1556{
1557testName: "will return false if nocache is true",
1558noCache: true,
1559manifestInfos: manifestInfos("rev1"),
1560sources: sources(),
1561app: app("httpbin", "rev1", false, nil),
1562manifestRevisions: []string{"rev1"},
1563statusRefreshTimeout: time.Hour * 24,
1564expectedUseCache: false,
1565serverSideDiff: false,
1566},
1567{
1568testName: "will return false if requested refresh",
1569noCache: false,
1570manifestInfos: manifestInfos("rev1"),
1571sources: sources(),
1572app: app("httpbin", "rev1", true, nil),
1573manifestRevisions: []string{"rev1"},
1574statusRefreshTimeout: time.Hour * 24,
1575expectedUseCache: false,
1576serverSideDiff: false,
1577},
1578{
1579testName: "will return false if status expired",
1580noCache: false,
1581manifestInfos: manifestInfos("rev1"),
1582sources: sources(),
1583app: app("httpbin", "rev1", false, nil),
1584manifestRevisions: []string{"rev1"},
1585statusRefreshTimeout: time.Minute,
1586expectedUseCache: false,
1587serverSideDiff: false,
1588},
1589{
1590testName: "will return true if status expired and server-side diff",
1591noCache: false,
1592manifestInfos: manifestInfos("rev1"),
1593sources: sources(),
1594app: app("httpbin", "rev1", false, nil),
1595manifestRevisions: []string{"rev1"},
1596statusRefreshTimeout: time.Minute,
1597expectedUseCache: true,
1598serverSideDiff: true,
1599},
1600{
1601testName: "will return false if there is a new revision",
1602noCache: false,
1603manifestInfos: manifestInfos("rev1"),
1604sources: sources(),
1605app: app("httpbin", "rev1", false, nil),
1606manifestRevisions: []string{"rev2"},
1607statusRefreshTimeout: time.Hour * 24,
1608expectedUseCache: false,
1609serverSideDiff: false,
1610},
1611{
1612testName: "will return false if app spec repo changed",
1613noCache: false,
1614manifestInfos: manifestInfos("rev1"),
1615sources: sources(),
1616app: app("httpbin", "rev1", false, &argoappv1.Application{
1617Spec: argoappv1.ApplicationSpec{
1618Source: &argoappv1.ApplicationSource{
1619RepoURL: "new-repo",
1620},
1621},
1622}),
1623manifestRevisions: []string{"rev1"},
1624statusRefreshTimeout: time.Hour * 24,
1625expectedUseCache: false,
1626serverSideDiff: false,
1627},
1628{
1629testName: "will return false if app spec IgnoreDifferences changed",
1630noCache: false,
1631manifestInfos: manifestInfos("rev1"),
1632sources: sources(),
1633app: app("httpbin", "rev1", false, &argoappv1.Application{
1634Spec: argoappv1.ApplicationSpec{
1635IgnoreDifferences: []argoappv1.ResourceIgnoreDifferences{
1636{
1637Group: "app/v1",
1638Kind: "application",
1639Name: "httpbin",
1640Namespace: "httpbin",
1641JQPathExpressions: []string{"."},
1642},
1643},
1644},
1645}),
1646manifestRevisions: []string{"rev1"},
1647statusRefreshTimeout: time.Hour * 24,
1648expectedUseCache: false,
1649serverSideDiff: false,
1650},
1651}
1652
1653for _, tc := range cases {
1654tc := tc
1655t.Run(tc.testName, func(t *testing.T) {
1656// Given
1657t.Parallel()
1658logger, _ := logrustest.NewNullLogger()
1659log := logrus.NewEntry(logger)
1660
1661// When
1662useDiffCache := useDiffCache(tc.noCache, tc.manifestInfos, tc.sources, tc.app, tc.manifestRevisions, tc.statusRefreshTimeout, tc.serverSideDiff, log)
1663
1664// Then
1665assert.Equal(t, useDiffCache, tc.expectedUseCache)
1666})
1667}
1668}
1669