github.com/argoproj/argo-cd/v3@v3.2.1/test/e2e/merge_e2e_test.go (about) 1 package e2e 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "testing" 7 8 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 9 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 11 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" 12 . "github.com/argoproj/argo-cd/v3/test/e2e/fixture/applicationsets" 13 "github.com/argoproj/argo-cd/v3/test/e2e/fixture/applicationsets/utils" 14 15 "github.com/argoproj/argo-cd/v3/pkg/apis/application" 16 ) 17 18 func TestListMergeGenerator(t *testing.T) { 19 generateExpectedApp := func(name, nameSuffix string) v1alpha1.Application { 20 return v1alpha1.Application{ 21 TypeMeta: metav1.TypeMeta{ 22 Kind: application.ApplicationKind, 23 APIVersion: "argoproj.io/v1alpha1", 24 }, 25 ObjectMeta: metav1.ObjectMeta{ 26 Name: fmt.Sprintf("%s-%s", name, nameSuffix), 27 Namespace: utils.TestNamespace(), 28 Finalizers: []string{v1alpha1.ResourcesFinalizerName}, 29 }, 30 Spec: v1alpha1.ApplicationSpec{ 31 Project: "default", 32 Source: &v1alpha1.ApplicationSource{ 33 RepoURL: "https://github.com/argoproj/argocd-example-apps.git", 34 TargetRevision: "HEAD", 35 Path: name, 36 }, 37 Destination: v1alpha1.ApplicationDestination{ 38 Server: "https://kubernetes.default.svc", 39 Namespace: name, 40 }, 41 }, 42 } 43 } 44 45 expectedApps := []v1alpha1.Application{ 46 generateExpectedApp("kustomize-guestbook", "1"), 47 generateExpectedApp("helm-guestbook", "2"), 48 } 49 50 var expectedAppsNewNamespace []v1alpha1.Application 51 var expectedAppsNewMetadata []v1alpha1.Application 52 53 Given(t). 54 // Create a ClusterGenerator-based ApplicationSet 55 When(). 56 Create(v1alpha1.ApplicationSet{ 57 ObjectMeta: metav1.ObjectMeta{ 58 Name: "merge-generator", 59 }, 60 Spec: v1alpha1.ApplicationSetSpec{ 61 Template: v1alpha1.ApplicationSetTemplate{ 62 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{path.basename}}-{{name-suffix}}"}, 63 Spec: v1alpha1.ApplicationSpec{ 64 Project: "default", 65 Source: &v1alpha1.ApplicationSource{ 66 RepoURL: "https://github.com/argoproj/argocd-example-apps.git", 67 TargetRevision: "HEAD", 68 Path: "{{path}}", 69 }, 70 Destination: v1alpha1.ApplicationDestination{ 71 Server: "https://kubernetes.default.svc", 72 Namespace: "{{path.basename}}", 73 }, 74 }, 75 }, 76 Generators: []v1alpha1.ApplicationSetGenerator{ 77 { 78 Merge: &v1alpha1.MergeGenerator{ 79 MergeKeys: []string{"path.basename"}, 80 Generators: []v1alpha1.ApplicationSetNestedGenerator{ 81 { 82 Git: &v1alpha1.GitGenerator{ 83 RepoURL: "https://github.com/argoproj/argocd-example-apps.git", 84 Directories: []v1alpha1.GitDirectoryGeneratorItem{ 85 { 86 Path: "*guestbook*", 87 }, 88 }, 89 }, 90 }, 91 { 92 List: &v1alpha1.ListGenerator{ 93 Elements: []apiextensionsv1.JSON{ 94 {Raw: []byte(`{"path.basename": "kustomize-guestbook", "name-suffix": "1"}`)}, 95 {Raw: []byte(`{"path.basename": "helm-guestbook", "name-suffix": "2"}`)}, 96 }, 97 }, 98 }, 99 }, 100 }, 101 }, 102 }, 103 }, 104 }).Then().Expect(ApplicationsExist(expectedApps)). 105 106 // Update the ApplicationSet template namespace, and verify it updates the Applications 107 When(). 108 And(func() { 109 for _, expectedApp := range expectedApps { 110 newExpectedApp := expectedApp.DeepCopy() 111 newExpectedApp.Spec.Destination.Namespace = "guestbook2" 112 expectedAppsNewNamespace = append(expectedAppsNewNamespace, *newExpectedApp) 113 } 114 }). 115 Update(func(appset *v1alpha1.ApplicationSet) { 116 appset.Spec.Template.Spec.Destination.Namespace = "guestbook2" 117 }).Then().Expect(ApplicationsExist(expectedAppsNewNamespace)). 118 119 // Update the metadata fields in the appset template, and make sure it propagates to the apps 120 When(). 121 And(func() { 122 for _, expectedApp := range expectedAppsNewNamespace { 123 expectedAppNewMetadata := expectedApp.DeepCopy() 124 expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"} 125 expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"} 126 expectedAppsNewMetadata = append(expectedAppsNewMetadata, *expectedAppNewMetadata) 127 } 128 }). 129 Update(func(appset *v1alpha1.ApplicationSet) { 130 appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"} 131 appset.Spec.Template.Labels = map[string]string{"label-key": "label-value"} 132 }).Then().Expect(ApplicationsExist(expectedAppsNewMetadata)). 133 134 // Delete the ApplicationSet, and verify it deletes the Applications 135 When(). 136 Delete().Then().Expect(ApplicationsDoNotExist(expectedAppsNewNamespace)) 137 } 138 139 func TestClusterMergeGenerator(t *testing.T) { 140 generateExpectedApp := func(cluster, name, nameSuffix string) v1alpha1.Application { 141 return v1alpha1.Application{ 142 TypeMeta: metav1.TypeMeta{ 143 Kind: application.ApplicationKind, 144 APIVersion: "argoproj.io/v1alpha1", 145 }, 146 ObjectMeta: metav1.ObjectMeta{ 147 Name: fmt.Sprintf("%s-%s-%s", cluster, name, nameSuffix), 148 Namespace: utils.TestNamespace(), 149 Finalizers: []string{v1alpha1.ResourcesFinalizerName}, 150 }, 151 Spec: v1alpha1.ApplicationSpec{ 152 Project: "default", 153 Source: &v1alpha1.ApplicationSource{ 154 RepoURL: "https://github.com/argoproj/argocd-example-apps.git", 155 TargetRevision: "HEAD", 156 Path: name, 157 }, 158 Destination: v1alpha1.ApplicationDestination{ 159 Name: cluster, 160 Namespace: name, 161 }, 162 }, 163 } 164 } 165 166 expectedApps := []v1alpha1.Application{ 167 generateExpectedApp("cluster1", "kustomize-guestbook", "1"), 168 generateExpectedApp("cluster1", "helm-guestbook", "0"), 169 170 generateExpectedApp("cluster2", "kustomize-guestbook", "0"), 171 generateExpectedApp("cluster2", "helm-guestbook", "2"), 172 } 173 174 var expectedAppsNewNamespace []v1alpha1.Application 175 var expectedAppsNewMetadata []v1alpha1.Application 176 177 Given(t). 178 // Create a ClusterGenerator-based ApplicationSet 179 When(). 180 CreateClusterSecret("my-secret", "cluster1", "https://kubernetes.default.svc"). 181 CreateClusterSecret("my-secret2", "cluster2", "https://kubernetes.default.svc"). 182 Create(v1alpha1.ApplicationSet{ 183 ObjectMeta: metav1.ObjectMeta{ 184 Name: "merge-generator", 185 }, 186 Spec: v1alpha1.ApplicationSetSpec{ 187 Template: v1alpha1.ApplicationSetTemplate{ 188 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-{{path.basename}}-{{values.name-suffix}}"}, 189 Spec: v1alpha1.ApplicationSpec{ 190 Project: "default", 191 Source: &v1alpha1.ApplicationSource{ 192 RepoURL: "https://github.com/argoproj/argocd-example-apps.git", 193 TargetRevision: "HEAD", 194 Path: "{{path}}", 195 }, 196 Destination: v1alpha1.ApplicationDestination{ 197 Name: "{{name}}", 198 Namespace: "{{path.basename}}", 199 }, 200 }, 201 }, 202 Generators: []v1alpha1.ApplicationSetGenerator{ 203 { 204 Merge: &v1alpha1.MergeGenerator{ 205 MergeKeys: []string{"name", "path.basename"}, 206 Generators: []v1alpha1.ApplicationSetNestedGenerator{ 207 { 208 Matrix: toAPIExtensionsJSON(t, &v1alpha1.NestedMatrixGenerator{ 209 Generators: []v1alpha1.ApplicationSetTerminalGenerator{ 210 { 211 Clusters: &v1alpha1.ClusterGenerator{ 212 Selector: metav1.LabelSelector{ 213 MatchLabels: map[string]string{ 214 "argocd.argoproj.io/secret-type": "cluster", 215 }, 216 }, 217 Values: map[string]string{ 218 "name-suffix": "0", 219 }, 220 }, 221 }, 222 { 223 Git: &v1alpha1.GitGenerator{ 224 RepoURL: "https://github.com/argoproj/argocd-example-apps.git", 225 Directories: []v1alpha1.GitDirectoryGeneratorItem{ 226 { 227 Path: "*guestbook*", 228 }, 229 }, 230 }, 231 }, 232 }, 233 }), 234 }, 235 { 236 List: &v1alpha1.ListGenerator{ 237 Elements: []apiextensionsv1.JSON{ 238 {Raw: []byte(`{"name": "cluster1", "path.basename": "kustomize-guestbook", "values": {"name-suffix": "1"}}`)}, 239 {Raw: []byte(`{"name": "cluster2", "path.basename": "helm-guestbook", "values": {"name-suffix": "2"}}`)}, 240 }, 241 }, 242 }, 243 }, 244 }, 245 }, 246 }, 247 }, 248 }).Then().Expect(ApplicationsExist(expectedApps)). 249 250 // Update the ApplicationSet template namespace, and verify it updates the Applications 251 When(). 252 And(func() { 253 for _, expectedApp := range expectedApps { 254 newExpectedApp := expectedApp.DeepCopy() 255 newExpectedApp.Spec.Destination.Namespace = "guestbook2" 256 expectedAppsNewNamespace = append(expectedAppsNewNamespace, *newExpectedApp) 257 } 258 }). 259 Update(func(appset *v1alpha1.ApplicationSet) { 260 appset.Spec.Template.Spec.Destination.Namespace = "guestbook2" 261 }).Then().Expect(ApplicationsExist(expectedAppsNewNamespace)). 262 263 // Update the metadata fields in the appset template, and make sure it propagates to the apps 264 When(). 265 And(func() { 266 for _, expectedApp := range expectedAppsNewNamespace { 267 expectedAppNewMetadata := expectedApp.DeepCopy() 268 expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"} 269 expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"} 270 expectedAppsNewMetadata = append(expectedAppsNewMetadata, *expectedAppNewMetadata) 271 } 272 }). 273 Update(func(appset *v1alpha1.ApplicationSet) { 274 appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"} 275 appset.Spec.Template.Labels = map[string]string{"label-key": "label-value"} 276 }).Then().Expect(ApplicationsExist(expectedAppsNewMetadata)). 277 278 // Delete the ApplicationSet, and verify it deletes the Applications 279 When(). 280 Delete().Then().Expect(ApplicationsDoNotExist(expectedAppsNewNamespace)) 281 } 282 283 func TestMergeTerminalMergeGeneratorSelector(t *testing.T) { 284 generateExpectedApp := func(name, nameSuffix string) v1alpha1.Application { 285 return v1alpha1.Application{ 286 TypeMeta: metav1.TypeMeta{ 287 Kind: application.ApplicationKind, 288 APIVersion: "argoproj.io/v1alpha1", 289 }, 290 ObjectMeta: metav1.ObjectMeta{ 291 Name: fmt.Sprintf("%s-%s", name, nameSuffix), 292 Namespace: utils.TestNamespace(), 293 Finalizers: []string{v1alpha1.ResourcesFinalizerName}, 294 }, 295 Spec: v1alpha1.ApplicationSpec{ 296 Project: "default", 297 Source: &v1alpha1.ApplicationSource{ 298 RepoURL: "https://github.com/argoproj/argocd-example-apps.git", 299 TargetRevision: "HEAD", 300 Path: name, 301 }, 302 Destination: v1alpha1.ApplicationDestination{ 303 Server: "https://kubernetes.default.svc", 304 Namespace: name, 305 }, 306 }, 307 } 308 } 309 310 excludedApps := []v1alpha1.Application{ 311 generateExpectedApp("kustomize-guestbook", "1"), 312 } 313 expectedApps := []v1alpha1.Application{ 314 generateExpectedApp("helm-guestbook", "2"), 315 } 316 317 Given(t). 318 // Create ApplicationSet with LabelSelector on an ApplicationSetTerminalGenerator 319 When(). 320 Create(v1alpha1.ApplicationSet{ 321 ObjectMeta: metav1.ObjectMeta{ 322 Name: "merge-generator-nested-merge", 323 }, 324 Spec: v1alpha1.ApplicationSetSpec{ 325 Template: v1alpha1.ApplicationSetTemplate{ 326 ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{path.basename}}-{{name-suffix}}"}, 327 Spec: v1alpha1.ApplicationSpec{ 328 Project: "default", 329 Source: &v1alpha1.ApplicationSource{ 330 RepoURL: "https://github.com/argoproj/argocd-example-apps.git", 331 TargetRevision: "HEAD", 332 Path: "{{path}}", 333 }, 334 Destination: v1alpha1.ApplicationDestination{ 335 Server: "https://kubernetes.default.svc", 336 Namespace: "{{path.basename}}", 337 }, 338 }, 339 }, 340 Generators: []v1alpha1.ApplicationSetGenerator{ 341 { 342 Merge: &v1alpha1.MergeGenerator{ 343 MergeKeys: []string{"path.basename"}, 344 Generators: []v1alpha1.ApplicationSetNestedGenerator{ 345 { 346 Merge: toAPIExtensionsJSON(t, &v1alpha1.NestedMergeGenerator{ 347 MergeKeys: []string{"path.basename"}, 348 Generators: []v1alpha1.ApplicationSetTerminalGenerator{ 349 { 350 Git: &v1alpha1.GitGenerator{ 351 RepoURL: "https://github.com/argoproj/argocd-example-apps.git", 352 Directories: []v1alpha1.GitDirectoryGeneratorItem{ 353 { 354 Path: "*guestbook*", 355 }, 356 }, 357 }, 358 Selector: &metav1.LabelSelector{ 359 MatchLabels: map[string]string{ 360 "path.basename": "kustomize-guestbook", 361 }, 362 }, 363 }, 364 { 365 List: &v1alpha1.ListGenerator{ 366 Elements: []apiextensionsv1.JSON{ 367 {Raw: []byte(`{"path.basename": "kustomize-guestbook", "name-suffix": "1"}`)}, 368 {Raw: []byte(`{"path.basename": "helm-guestbook", "name-suffix": "2"}`)}, 369 }, 370 }, 371 }, 372 }, 373 }), 374 }, 375 { 376 List: &v1alpha1.ListGenerator{ 377 Elements: []apiextensionsv1.JSON{ 378 {Raw: []byte(`{}`)}, 379 }, 380 }, 381 }, 382 }, 383 }, 384 }, 385 }, 386 }, 387 }).Then().Expect(ApplicationsExist(excludedApps)).Expect(ApplicationsDoNotExist(expectedApps)). 388 389 // Update the ApplicationSetTerminalGenerator LabelSelector, and verify the Applications are deleted and created 390 When(). 391 Update(func(appset *v1alpha1.ApplicationSet) { 392 appset.Spec.Generators[0].Merge.Generators[0].Merge = toAPIExtensionsJSON(t, &v1alpha1.NestedMergeGenerator{ 393 MergeKeys: []string{"path.basename"}, 394 Generators: []v1alpha1.ApplicationSetTerminalGenerator{ 395 { 396 Git: &v1alpha1.GitGenerator{ 397 RepoURL: "https://github.com/argoproj/argocd-example-apps.git", 398 Directories: []v1alpha1.GitDirectoryGeneratorItem{ 399 { 400 Path: "*guestbook*", 401 }, 402 }, 403 }, 404 Selector: &metav1.LabelSelector{ 405 MatchLabels: map[string]string{ 406 "path.basename": "helm-guestbook", 407 }, 408 }, 409 }, 410 { 411 List: &v1alpha1.ListGenerator{ 412 Elements: []apiextensionsv1.JSON{ 413 {Raw: []byte(`{"path.basename": "kustomize-guestbook", "name-suffix": "1"}`)}, 414 {Raw: []byte(`{"path.basename": "helm-guestbook", "name-suffix": "2"}`)}, 415 }, 416 }, 417 }, 418 }, 419 }) 420 }).Then().Expect(ApplicationsExist(expectedApps)).Expect(ApplicationsDoNotExist(excludedApps)). 421 When(). 422 Delete().Then().Expect(ApplicationsDoNotExist(excludedApps)).Expect(ApplicationsDoNotExist(expectedApps)) 423 } 424 425 func toAPIExtensionsJSON(t *testing.T, g any) *apiextensionsv1.JSON { 426 t.Helper() 427 resVal, err := json.Marshal(g) 428 if err != nil { 429 t.Error("unable to unmarshal json", g) 430 return nil 431 } 432 433 res := &apiextensionsv1.JSON{Raw: resVal} 434 435 return res 436 }