github.com/argoproj/argo-cd/v3@v3.2.1/cmd/argocd/commands/admin/backup_test.go (about) 1 package admin 2 3 import ( 4 "bytes" 5 "testing" 6 7 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 8 "github.com/argoproj/argo-cd/v3/util/security" 9 10 "github.com/argoproj/gitops-engine/pkg/utils/kube" 11 "github.com/stretchr/testify/assert" 12 corev1 "k8s.io/api/core/v1" 13 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 15 16 "github.com/argoproj/argo-cd/v3/common" 17 ) 18 19 func newBackupObject(trackingValue string, trackingLabel bool, trackingAnnotation bool) *unstructured.Unstructured { 20 cm := corev1.ConfigMap{ 21 ObjectMeta: metav1.ObjectMeta{ 22 Name: "my-configmap", 23 Namespace: "namespace", 24 }, 25 Data: map[string]string{ 26 "foo": "bar", 27 }, 28 } 29 if trackingLabel { 30 cm.SetLabels(map[string]string{ 31 common.LabelKeyAppInstance: trackingValue, 32 }) 33 } 34 if trackingAnnotation { 35 cm.SetAnnotations(map[string]string{ 36 common.AnnotationKeyAppInstance: trackingValue, 37 }) 38 } 39 return kube.MustToUnstructured(&cm) 40 } 41 42 func newConfigmapObject() *unstructured.Unstructured { 43 cm := corev1.ConfigMap{ 44 ObjectMeta: metav1.ObjectMeta{ 45 Name: common.ArgoCDConfigMapName, 46 Namespace: "argocd", 47 Labels: map[string]string{ 48 "app.kubernetes.io/part-of": "argocd", 49 }, 50 }, 51 } 52 53 return kube.MustToUnstructured(&cm) 54 } 55 56 func newSecretsObject() *unstructured.Unstructured { 57 secret := corev1.Secret{ 58 ObjectMeta: metav1.ObjectMeta{ 59 Name: common.ArgoCDSecretName, 60 Namespace: "default", 61 Labels: map[string]string{ 62 "app.kubernetes.io/part-of": "argocd", 63 }, 64 }, 65 Data: map[string][]byte{ 66 "admin.password": nil, 67 "server.secretkey": nil, 68 }, 69 } 70 71 return kube.MustToUnstructured(&secret) 72 } 73 74 func newAppProject() *unstructured.Unstructured { 75 appProject := v1alpha1.AppProject{ 76 ObjectMeta: metav1.ObjectMeta{ 77 Name: "default", 78 Namespace: "argocd", 79 }, 80 Spec: v1alpha1.AppProjectSpec{ 81 Destinations: []v1alpha1.ApplicationDestination{ 82 { 83 Namespace: "*", 84 Server: "*", 85 }, 86 }, 87 ClusterResourceWhitelist: []metav1.GroupKind{ 88 { 89 Group: "*", 90 Kind: "*", 91 }, 92 }, 93 SourceRepos: []string{"*"}, 94 }, 95 } 96 97 return kube.MustToUnstructured(&appProject) 98 } 99 100 func newApplication(namespace string) *unstructured.Unstructured { 101 app := v1alpha1.Application{ 102 TypeMeta: metav1.TypeMeta{ 103 Kind: "Application", 104 }, 105 ObjectMeta: metav1.ObjectMeta{ 106 Name: "test", 107 Namespace: namespace, 108 }, 109 Spec: v1alpha1.ApplicationSpec{ 110 Source: &v1alpha1.ApplicationSource{}, 111 Project: "default", 112 Destination: v1alpha1.ApplicationDestination{ 113 Server: v1alpha1.KubernetesInternalAPIServerAddr, 114 Namespace: "default", 115 }, 116 }, 117 } 118 119 return kube.MustToUnstructured(&app) 120 } 121 122 func newApplicationSet(namespace string) *unstructured.Unstructured { 123 appSet := v1alpha1.ApplicationSet{ 124 TypeMeta: metav1.TypeMeta{ 125 Kind: "ApplicationSet", 126 }, 127 ObjectMeta: metav1.ObjectMeta{ 128 Name: "test-appset", 129 Namespace: namespace, 130 }, 131 Spec: v1alpha1.ApplicationSetSpec{ 132 Generators: []v1alpha1.ApplicationSetGenerator{ 133 { 134 Git: &v1alpha1.GitGenerator{ 135 RepoURL: "https://github.com/org/repo", 136 }, 137 }, 138 }, 139 }, 140 } 141 142 return kube.MustToUnstructured(&appSet) 143 } 144 145 // Test_exportResources tests for the resources exported when using the `argocd admin export` command 146 func Test_exportResources(t *testing.T) { 147 tests := []struct { 148 name string 149 object *unstructured.Unstructured 150 namespace string 151 enabledNamespaces []string 152 expectedFileContent string 153 expectExport bool 154 }{ 155 { 156 name: "ConfigMap should be in the exported manifest", 157 object: newConfigmapObject(), 158 expectExport: true, 159 expectedFileContent: `apiVersion: "" 160 kind: "" 161 metadata: 162 labels: 163 app.kubernetes.io/part-of: argocd 164 name: argocd-cm 165 --- 166 `, 167 }, 168 { 169 name: "Secret should be in the exported manifest", 170 object: newSecretsObject(), 171 expectExport: true, 172 expectedFileContent: `apiVersion: "" 173 data: 174 admin.password: null 175 server.secretkey: null 176 kind: "" 177 metadata: 178 labels: 179 app.kubernetes.io/part-of: argocd 180 name: argocd-secret 181 namespace: default 182 --- 183 `, 184 }, 185 { 186 name: "App Project should be in the exported manifest", 187 object: newAppProject(), 188 expectExport: true, 189 expectedFileContent: `apiVersion: "" 190 kind: "" 191 metadata: 192 name: default 193 spec: 194 clusterResourceWhitelist: 195 - group: '*' 196 kind: '*' 197 destinations: 198 - namespace: '*' 199 server: '*' 200 sourceRepos: 201 - '*' 202 status: {} 203 --- 204 `, 205 }, 206 { 207 name: "Application should be in the exported manifest when created in the default 'argocd' namespace", 208 object: newApplication("argocd"), 209 namespace: "argocd", 210 expectExport: true, 211 expectedFileContent: `apiVersion: "" 212 kind: Application 213 metadata: 214 name: test 215 spec: 216 destination: 217 namespace: default 218 server: https://kubernetes.default.svc 219 project: default 220 source: 221 repoURL: "" 222 status: 223 health: {} 224 sourceHydrator: {} 225 summary: {} 226 sync: 227 comparedTo: 228 destination: {} 229 source: 230 repoURL: "" 231 status: "" 232 --- 233 `, 234 }, 235 { 236 name: "Application should be in the exported manifest when created in the enabled namespaces", 237 object: newApplication("dev"), 238 namespace: "dev", 239 enabledNamespaces: []string{"dev", "prod"}, 240 expectExport: true, 241 expectedFileContent: `apiVersion: "" 242 kind: Application 243 metadata: 244 name: test 245 namespace: dev 246 spec: 247 destination: 248 namespace: default 249 server: https://kubernetes.default.svc 250 project: default 251 source: 252 repoURL: "" 253 status: 254 health: {} 255 sourceHydrator: {} 256 summary: {} 257 sync: 258 comparedTo: 259 destination: {} 260 source: 261 repoURL: "" 262 status: "" 263 --- 264 `, 265 }, 266 { 267 name: "Application should not be in the exported manifest when it's neither created in the default argod namespace nor in enabled namespace", 268 object: newApplication("staging"), 269 namespace: "staging", 270 enabledNamespaces: []string{"dev", "prod"}, 271 expectExport: false, 272 expectedFileContent: ``, 273 }, 274 { 275 name: "ApplicationSet should be in the exported manifest when created in the default 'argocd' namespace", 276 object: newApplicationSet("argocd"), 277 namespace: "argocd", 278 expectExport: true, 279 expectedFileContent: `apiVersion: "" 280 kind: ApplicationSet 281 metadata: 282 name: test-appset 283 spec: 284 generators: 285 - git: 286 repoURL: https://github.com/org/repo 287 revision: "" 288 template: 289 metadata: {} 290 spec: 291 destination: {} 292 project: "" 293 template: 294 metadata: {} 295 spec: 296 destination: {} 297 project: "" 298 status: {} 299 --- 300 `, 301 }, 302 { 303 name: "ApplicationSet should be in the exported manifest when created in the enabled namespaces", 304 object: newApplicationSet("dev"), 305 namespace: "dev", 306 enabledNamespaces: []string{"dev", "prod"}, 307 expectExport: true, 308 expectedFileContent: `apiVersion: "" 309 kind: ApplicationSet 310 metadata: 311 name: test-appset 312 namespace: dev 313 spec: 314 generators: 315 - git: 316 repoURL: https://github.com/org/repo 317 revision: "" 318 template: 319 metadata: {} 320 spec: 321 destination: {} 322 project: "" 323 template: 324 metadata: {} 325 spec: 326 destination: {} 327 project: "" 328 status: {} 329 --- 330 `, 331 }, 332 { 333 name: "ApplicationSet should not be in the exported manifest when neither created in the default 'argocd' namespace nor in enabled namespaces", 334 object: newApplicationSet("staging"), 335 namespace: "staging", 336 enabledNamespaces: []string{"dev", "prod"}, 337 expectExport: false, 338 expectedFileContent: ``, 339 }, 340 } 341 342 for _, tt := range tests { 343 t.Run(tt.name, func(t *testing.T) { 344 var buf bytes.Buffer 345 346 kind := tt.object.GetKind() 347 if kind == "Application" || kind == "ApplicationSet" { 348 if security.IsNamespaceEnabled(tt.namespace, "argocd", tt.enabledNamespaces) { 349 export(&buf, *tt.object, ArgoCDNamespace) 350 } 351 } else { 352 export(&buf, *tt.object, ArgoCDNamespace) 353 } 354 355 content := buf.String() 356 if tt.expectExport { 357 assert.Equal(t, tt.expectedFileContent, content) 358 } else { 359 assert.Empty(t, content) 360 } 361 }) 362 } 363 } 364 365 func Test_updateTracking(t *testing.T) { 366 type args struct { 367 bak *unstructured.Unstructured 368 live *unstructured.Unstructured 369 } 370 tests := []struct { 371 name string 372 args args 373 expected *unstructured.Unstructured 374 }{ 375 { 376 name: "update annotation when present in live", 377 args: args{ 378 bak: newBackupObject("bak", false, true), 379 live: newBackupObject("live", false, true), 380 }, 381 expected: newBackupObject("live", false, true), 382 }, 383 { 384 name: "update default label when present in live", 385 args: args{ 386 bak: newBackupObject("bak", true, true), 387 live: newBackupObject("live", true, true), 388 }, 389 expected: newBackupObject("live", true, true), 390 }, 391 { 392 name: "do not update if live object does not have tracking", 393 args: args{ 394 bak: newBackupObject("bak", true, true), 395 live: newBackupObject("live", false, false), 396 }, 397 expected: newBackupObject("bak", true, true), 398 }, 399 { 400 name: "do not update if bak object does not have tracking", 401 args: args{ 402 bak: newBackupObject("bak", false, false), 403 live: newBackupObject("live", true, true), 404 }, 405 expected: newBackupObject("bak", false, false), 406 }, 407 } 408 for _, tt := range tests { 409 t.Run(tt.name, func(t *testing.T) { 410 updateTracking(tt.args.bak, tt.args.live) 411 assert.Equal(t, tt.expected, tt.args.bak) 412 }) 413 } 414 } 415 416 func TestIsSkipLabelMatches(t *testing.T) { 417 tests := []struct { 418 name string 419 obj *unstructured.Unstructured 420 skipLabels string 421 expected bool 422 }{ 423 { 424 name: "Label matches", 425 obj: &unstructured.Unstructured{ 426 Object: map[string]any{ 427 "metadata": map[string]any{ 428 "labels": map[string]any{ 429 "test-label": "value", 430 }, 431 }, 432 }, 433 }, 434 skipLabels: "test-label=value", 435 expected: true, 436 }, 437 { 438 name: "Label does not match", 439 obj: &unstructured.Unstructured{ 440 Object: map[string]any{ 441 "metadata": map[string]any{ 442 "labels": map[string]any{ 443 "different-label": "value", 444 }, 445 }, 446 }, 447 }, 448 skipLabels: "test-label=value", 449 expected: false, 450 }, 451 { 452 name: "Empty skip labels", 453 obj: &unstructured.Unstructured{ 454 Object: map[string]any{ 455 "metadata": map[string]any{ 456 "labels": map[string]any{ 457 "test-label": "value", 458 }, 459 }, 460 }, 461 }, 462 skipLabels: "", 463 expected: false, 464 }, 465 { 466 name: "No labels value", 467 obj: &unstructured.Unstructured{ 468 Object: map[string]any{ 469 "metadata": map[string]any{ 470 "labels": map[string]any{ 471 "test-label": "value", 472 "another-label": "value2", 473 }, 474 }, 475 }, 476 }, 477 skipLabels: "test-label", 478 expected: false, 479 }, 480 } 481 for _, tt := range tests { 482 t.Run(tt.name, func(t *testing.T) { 483 result := isSkipLabelMatches(tt.obj, tt.skipLabels) 484 assert.Equal(t, tt.expected, result) 485 }) 486 } 487 }