-
Notifications
You must be signed in to change notification settings - Fork 6.5k
feat: Add tests for - argocd admin import command
#22780
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
base: master
Are you sure you want to change the base?
Changes from 12 commits
ae66626
dff31c2
2dd590d
aee2a95
742e641
cb489df
f112e48
c12b6ae
68ee314
a32f4b5
46b135a
d80c9ba
658fc0b
799e3b6
9b68f47
a286f0f
75c334f
ff3c670
e4d77bd
914694e
c05a0cf
4203365
368beab
13bc592
8bd4329
61698be
9d6d613
e641697
c4524b9
5161f1d
d94e6d0
bb53198
f9d69a7
7348130
0c46781
33d4053
0977796
86c8c94
58b2464
12a7f81
36ed1ed
ce0301c
b0c04e5
b3cbc04
344eed7
22dd2bd
d50565b
e38a879
ca641da
1097d04
da55e66
de3dd86
d96b0b2
94541df
cb52555
f70fa87
1b7cd1f
e85cf5f
ee78b19
255dcc4
10e4ff2
238f6b9
9620755
07045cf
a078e2e
79eabc4
07c615c
3fbd9a2
7a77ee2
4708dcd
9491524
b6a6d8d
9ccfee1
c20a146
18df33b
db16587
59912ef
86e649d
331ca92
5e985e1
fdd1c73
6fcc30a
1be90e4
770fcf3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,16 +2,25 @@ | |
|
|
||
| import ( | ||
| "bytes" | ||
| "context" | ||
| "slices" | ||
| "testing" | ||
|
|
||
| "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" | ||
| "github.com/argoproj/argo-cd/v3/util/security" | ||
| "github.com/stretchr/testify/require" | ||
| "sigs.k8s.io/yaml" | ||
|
|
||
| secutil "github.com/argoproj/argo-cd/v3/util/security" | ||
| "github.com/argoproj/gitops-engine/pkg/utils/kube" | ||
| "github.com/stretchr/testify/assert" | ||
| corev1 "k8s.io/api/core/v1" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
| "k8s.io/apimachinery/pkg/runtime" | ||
| "k8s.io/apimachinery/pkg/runtime/schema" | ||
| "k8s.io/client-go/dynamic" | ||
| dynamicfake "k8s.io/client-go/dynamic/fake" | ||
|
|
||
| "github.com/argoproj/argo-cd/v3/common" | ||
| ) | ||
|
|
@@ -362,6 +371,340 @@ | |
| } | ||
| } | ||
|
|
||
| func Test_importResources(t *testing.T) { | ||
| type args struct { | ||
| bak string | ||
| live string | ||
| } | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| args args | ||
| applicationNamespaces []string | ||
| applicationsetNamespaces []string | ||
| prune bool | ||
| skipResourcesWithLabel string | ||
| }{ | ||
| { | ||
| name: "It should update the live object according to the backup object if the backup object doesn't have the skip label", | ||
| args: args{ | ||
| bak: `apiVersion: v1 | ||
| kind: ConfigMap | ||
| metadata: | ||
| name: my-configmap | ||
| namespace: namespace | ||
| labels: | ||
| env: dev | ||
| annotations: | ||
| argocd.argoproj.io/instance: test-instance | ||
| finalizers: | ||
| - test.finalizer.io | ||
| data: | ||
| foo: bar | ||
| `, | ||
| live: `apiVersion: v1 | ||
| kind: ConfigMap | ||
| metadata: | ||
| name: my-configmap | ||
| namespace: namespace | ||
| labels: | ||
| env: prod | ||
| annotations: | ||
| argocd.argoproj.io/instance: old-instance | ||
| finalizers: [] | ||
| data: | ||
| foo: old | ||
| `, | ||
| }, | ||
| applicationNamespaces: []string{"argocd", "dev"}, | ||
| applicationsetNamespaces: []string{"argocd", "prod"}, | ||
| skipResourcesWithLabel: "env=dev", | ||
| }, | ||
| { | ||
| name: "It should update the data of the live object according to the backup object", | ||
| args: args{ | ||
| bak: `apiVersion: v1 | ||
| kind: ConfigMap | ||
| metadata: | ||
| name: my-configmap | ||
| namespace: namespace | ||
| data: | ||
| foo: bar | ||
| `, | ||
| live: `apiVersion: v1 | ||
| kind: ConfigMap | ||
| metadata: | ||
| name: my-configmap | ||
| namespace: namespace | ||
| data: | ||
| foo: old | ||
| `, | ||
| }, | ||
| applicationNamespaces: []string{}, | ||
| applicationsetNamespaces: []string{}, | ||
| prune: true, | ||
| }, | ||
| { | ||
| name: "Spec should be updated correctly in live object according to the backup object", | ||
| args: args{ | ||
| bak: `apiVersion: v1 | ||
| kind: Application | ||
| metadata: | ||
| name: app | ||
| namespace: argocd | ||
| spec: | ||
| source: | ||
| repoURL: https://github.com/example/updated.git | ||
| destination: | ||
| namespace: default | ||
| server: https://kubernetes.default.svc | ||
| `, | ||
| live: `apiVersion: v1 | ||
| kind: Application | ||
| metadata: | ||
| name: app | ||
| namespace: argocd | ||
| spec: | ||
| source: | ||
| repoURL: https://github.com/example/old.git | ||
| destination: | ||
| namespace: default | ||
| server: https://kubernetes.default.svc | ||
| `, | ||
| }, | ||
| applicationNamespaces: []string{"argocd", "dev"}, | ||
| applicationsetNamespaces: []string{"prod"}, | ||
| }, | ||
| { | ||
| name: "It should update live object's spec according to the backup object", | ||
| args: args{ | ||
| bak: `apiVersion: v1 | ||
| kind: ApplicationSet | ||
| metadata: | ||
| name: my-appset | ||
| namespace: argocd | ||
| spec: | ||
| generators: | ||
| - list: | ||
| elements: | ||
| - clusters: dev | ||
| template: | ||
| metadata: | ||
| name: '{{appName}}' | ||
| spec: {} | ||
| `, | ||
| live: `apiVersion: v1 | ||
| kind: ApplicationSet | ||
| metadata: | ||
| name: my-appset | ||
| namespace: argocd | ||
| spec: | ||
| generators: | ||
| - list: | ||
| elements: | ||
| - clusters: prod | ||
| template: | ||
| metadata: | ||
| name: '{{appName}}' | ||
| spec: {} | ||
| `, | ||
| }, | ||
| applicationNamespaces: []string{}, | ||
| applicationsetNamespaces: []string{"dev"}, | ||
| }, | ||
| { | ||
| name: "It shouldn't update the live object if it's same as the backup object", | ||
| args: args{ | ||
| bak: `apiVersion: v1 | ||
| kind: ConfigMap | ||
| metadata: | ||
| name: my-cm | ||
| namespace: argo-cd | ||
| labels: | ||
| env: dev | ||
| data: | ||
| foo: bar | ||
| `, | ||
| live: `apiVersion: v1 | ||
| kind: ConfigMap | ||
| metadata: | ||
| name: my-cm | ||
| namespace: argo-cd | ||
| labels: | ||
| env: dev | ||
| data: | ||
| foo: bar | ||
| `, | ||
| }, | ||
| applicationNamespaces: []string{"argo-*"}, | ||
| applicationsetNamespaces: []string{"argo-*"}, | ||
| }, | ||
| { | ||
| name: "Resources should be created when they're missing from live", | ||
| args: args{ | ||
| bak: `apiVersion: v1 | ||
| kind: ConfigMap | ||
| metadata: | ||
| name: my-cm | ||
| namespace: argocd | ||
| labels: | ||
| env: dev | ||
| data: | ||
| foo: bar | ||
| `, | ||
| live: `apiVersion: v1 | ||
| kind: ConfigMap | ||
| metadata: | ||
| name: my-cm | ||
| namespaces: argocd | ||
| labels: | ||
| env: dev | ||
| `, | ||
| }, | ||
| applicationNamespaces: []string{"argocd", "dev"}, | ||
| applicationsetNamespaces: []string{"argocd", "prod"}, | ||
| }, | ||
| { | ||
| name: "Live resources should be pruned if --prune flag is set", | ||
| args: args{ | ||
| bak: `apiVersion: v1 | ||
| kind: ConfigMap | ||
| metadata: | ||
| name: configmap-to-keep | ||
| namespace: default | ||
| apiVersion: v1 | ||
| kind: ConfigMap | ||
| metadata: | ||
| name: configmap-to-delete | ||
| namespace: default | ||
| `, | ||
| live: `apiVersion: v1 | ||
| kind: ConfigMap | ||
| metadata: | ||
| name: configmap-to-keep | ||
| namespace: default | ||
| `, | ||
| }, | ||
| prune: true, | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| bakObj := decodeYAMLToUnstructured(t, tt.args.bak) | ||
| liveObj := decodeYAMLToUnstructured(t, tt.args.live) | ||
| pruneObjects := make(map[kube.ResourceKey]unstructured.Unstructured) | ||
|
|
||
| configMap := &unstructured.Unstructured{} | ||
| configMap.SetUnstructuredContent(map[string]interface{}{ | ||
| "apiVersion": "v1", | ||
| "kind": "ConfigMap", | ||
| "metadata": map[string]interface{}{ | ||
| "name": "argocd-cmd-params-cm", | ||
| "namespace": "default", | ||
| }, | ||
| "data": map[string]interface{}{ | ||
| "application.namespaces": "argocd,dev", | ||
| "applicationset.namespaces": "argocd,stage", | ||
| }, | ||
| }) | ||
|
|
||
| ctx := context.Background() | ||
| gvr := schema.GroupVersionResource{ | ||
| Group: "", | ||
| Version: "v1", | ||
| Resource: "configmaps", | ||
| } | ||
|
|
||
| dynamicClient := dynamicfake.NewSimpleDynamicClient(runtime.NewScheme(), configMap) | ||
| configMapResource := dynamicClient.Resource(gvr).Namespace("default") | ||
|
|
||
| if len(tt.applicationNamespaces) == 0 || len(tt.applicationsetNamespaces) == 0 { | ||
| defaultNs := getAdditionalNamespaces(ctx, configMapResource) | ||
|
|
||
| if len(tt.applicationNamespaces) == 0 { | ||
| tt.applicationNamespaces = defaultNs.applicationNamespaces | ||
| } | ||
|
|
||
| if len(tt.applicationsetNamespaces) == 0 { | ||
| tt.applicationsetNamespaces = defaultNs.applicationsetNamespaces | ||
| } | ||
| } | ||
|
|
||
| // check if the object is a configMap or not | ||
| if isArgoCDConfigMap(bakObj.GetName()) { | ||
| pruneObjects[kube.ResourceKey{Group: "", Kind: "ConfigMap", Name: bakObj.GetName(), Namespace: bakObj.GetNamespace()}] = *bakObj | ||
| } | ||
| // check if the object is a secret or not | ||
| if isArgoCDSecret(*bakObj) { | ||
| pruneObjects[kube.ResourceKey{Group: "", Kind: "Secret", Name: bakObj.GetName(), Namespace: bakObj.GetNamespace()}] = *bakObj | ||
| } | ||
| // check if the object is an application or not | ||
| if bakObj.GetKind() == "Application" { | ||
| if secutil.IsNamespaceEnabled(bakObj.GetNamespace(), "argocd", tt.applicationNamespaces) { | ||
| pruneObjects[kube.ResourceKey{Group: "argoproj.io", Kind: "Application", Name: bakObj.GetName(), Namespace: bakObj.GetNamespace()}] = *bakObj | ||
| } | ||
| } | ||
| // check if the object is a project or not | ||
| if bakObj.GetKind() == "AppProject" { | ||
| pruneObjects[kube.ResourceKey{Group: "argoproj.io", Kind: "AppProject", Name: bakObj.GetName(), Namespace: bakObj.GetNamespace()}] = *bakObj | ||
| } | ||
| // check if the object is an applicationSet or not | ||
| if bakObj.GetKind() == "ApplicationSet" { | ||
| if secutil.IsNamespaceEnabled(bakObj.GetNamespace(), "argocd", tt.applicationsetNamespaces) { | ||
| pruneObjects[kube.ResourceKey{Group: "argoproj.io", Kind: "ApplicationSet", Name: bakObj.GetName(), Namespace: bakObj.GetNamespace()}] = *bakObj | ||
| } | ||
| } | ||
| gvk := bakObj.GroupVersionKind() | ||
| if bakObj.GetNamespace() == "" { | ||
| bakObj.SetNamespace("argocd") | ||
| } | ||
| key := kube.ResourceKey{Group: gvk.Group, Kind: gvk.Kind, Name: bakObj.GetName(), Namespace: bakObj.GetNamespace()} | ||
| delete(pruneObjects, key) | ||
|
|
||
| var updatedLive *unstructured.Unstructured | ||
| if slices.Contains(tt.applicationNamespaces, bakObj.GetNamespace()) || slices.Contains(tt.applicationsetNamespaces, bakObj.GetNamespace()) { | ||
|
||
| if !isSkipLabelMatches(bakObj, tt.skipResourcesWithLabel) { | ||
| if tt.prune { | ||
| var dynClient dynamic.ResourceInterface | ||
| switch key.Kind { | ||
| case "Secret": | ||
| dynClient = dynamicClient.Resource(secretResource).Namespace(liveObj.GetNamespace()) | ||
| case "AppProjectKind": | ||
| dynClient = dynamicClient.Resource(appprojectsResource).Namespace(liveObj.GetNamespace()) | ||
| case "ApplicationSetKind": | ||
| dynClient = dynamicClient.Resource(appplicationSetResource).Namespace(liveObj.GetNamespace()) | ||
| case "ApplicationKind": | ||
| dynClient = dynamicClient.Resource(applicationsResource).Namespace(liveObj.GetNamespace()) | ||
| } | ||
|
|
||
| err := dynClient.Delete(ctx, key.Name, metav1.DeleteOptions{}) | ||
| assert.NoError(t, err) | ||
| } else { | ||
| updatedLive = updateLive(bakObj, liveObj, false) | ||
|
|
||
| assert.Equal(t, bakObj.GetLabels(), updatedLive.GetLabels()) | ||
| assert.Equal(t, bakObj.GetAnnotations(), updatedLive.GetAnnotations()) | ||
| assert.Equal(t, bakObj.GetFinalizers(), updatedLive.GetFinalizers()) | ||
| assert.Equal(t, bakObj.Object["data"], updatedLive.Object["data"]) | ||
| } | ||
| } | ||
| } else { | ||
| assert.Nil(t, updatedLive) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func decodeYAMLToUnstructured(t *testing.T, yamlStr string) *unstructured.Unstructured { | ||
yaten2302 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| t.Helper() | ||
|
|
||
| var m map[string]any | ||
| err := yaml.Unmarshal([]byte(yamlStr), &m) | ||
| require.NoError(t, err) | ||
| return &unstructured.Unstructured{Object: m} | ||
| } | ||
|
|
||
| func Test_updateTracking(t *testing.T) { | ||
| type args struct { | ||
| bak *unstructured.Unstructured | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extract all the test setup code to a function, the method should be generic enough so that it accepts a liveObj and returns only the client. You can then provide the faeClient to the "import" method (needs refactoring) and validate the result by doing a Get on the liveObj after the import.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@agaudreault , I didn't get this completely, could you kindly elaborate a bit more on this please?
Like, currently, I've created the
decodeYAMLToUnstructured()func, which accepts the YAML of backup and the live object and decodes them to Unstructured format.Now, as you said that, it should accept the
liveObjand return only the client. I wasn't able to get this exactly?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @agaudreault , wanted to confirm that if I've understood this correctly, then I should extract this import test setup code to a new func let's say -
setupImportTest(). And then I should call this func to theTest_importResources()?Have I understood this correctly?