github.com/argoproj/argo-cd/v2@v2.10.5/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/v2/pkg/apis/application/v1alpha1"
    12  	argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    13  	. "github.com/argoproj/argo-cd/v2/test/e2e/fixture/applicationsets"
    14  	"github.com/argoproj/argo-cd/v2/test/e2e/fixture/applicationsets/utils"
    15  
    16  	"github.com/argoproj/argo-cd/v2/pkg/apis/application"
    17  )
    18  
    19  func TestListMergeGenerator(t *testing.T) {
    20  	generateExpectedApp := func(name, nameSuffix string) argov1alpha1.Application {
    21  		return argov1alpha1.Application{
    22  			TypeMeta: metav1.TypeMeta{
    23  				Kind:       application.ApplicationKind,
    24  				APIVersion: "argoproj.io/v1alpha1",
    25  			},
    26  			ObjectMeta: metav1.ObjectMeta{
    27  				Name:       fmt.Sprintf("%s-%s", name, nameSuffix),
    28  				Namespace:  utils.TestNamespace(),
    29  				Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
    30  			},
    31  			Spec: argov1alpha1.ApplicationSpec{
    32  				Project: "default",
    33  				Source: &argov1alpha1.ApplicationSource{
    34  					RepoURL:        "https://github.com/argoproj/argocd-example-apps.git",
    35  					TargetRevision: "HEAD",
    36  					Path:           name,
    37  				},
    38  				Destination: argov1alpha1.ApplicationDestination{
    39  					Server:    "https://kubernetes.default.svc",
    40  					Namespace: name,
    41  				},
    42  			},
    43  		}
    44  	}
    45  
    46  	expectedApps := []argov1alpha1.Application{
    47  		generateExpectedApp("kustomize-guestbook", "1"),
    48  		generateExpectedApp("helm-guestbook", "2"),
    49  	}
    50  
    51  	var expectedAppsNewNamespace []argov1alpha1.Application
    52  	var expectedAppsNewMetadata []argov1alpha1.Application
    53  
    54  	Given(t).
    55  		// Create a ClusterGenerator-based ApplicationSet
    56  		When().
    57  		Create(v1alpha1.ApplicationSet{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: argov1alpha1.ApplicationSpec{
    64  						Project: "default",
    65  						Source: &argov1alpha1.ApplicationSource{
    66  							RepoURL:        "https://github.com/argoproj/argocd-example-apps.git",
    67  							TargetRevision: "HEAD",
    68  							Path:           "{{path}}",
    69  						},
    70  						Destination: argov1alpha1.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) argov1alpha1.Application {
   141  		return argov1alpha1.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{"resources-finalizer.argocd.argoproj.io"},
   150  			},
   151  			Spec: argov1alpha1.ApplicationSpec{
   152  				Project: "default",
   153  				Source: &argov1alpha1.ApplicationSource{
   154  					RepoURL:        "https://github.com/argoproj/argocd-example-apps.git",
   155  					TargetRevision: "HEAD",
   156  					Path:           name,
   157  				},
   158  				Destination: argov1alpha1.ApplicationDestination{
   159  					Name:      cluster,
   160  					Namespace: name,
   161  				},
   162  			},
   163  		}
   164  	}
   165  
   166  	expectedApps := []argov1alpha1.Application{
   167  		generateExpectedApp("cluster1", "kustomize-guestbook", "1"),
   168  		generateExpectedApp("cluster1", "helm-guestbook", "0"),
   169  		generateExpectedApp("cluster1", "ksonnet-guestbook", "0"),
   170  
   171  		generateExpectedApp("cluster2", "kustomize-guestbook", "0"),
   172  		generateExpectedApp("cluster2", "helm-guestbook", "2"),
   173  		generateExpectedApp("cluster2", "ksonnet-guestbook", "0"),
   174  	}
   175  
   176  	var expectedAppsNewNamespace []argov1alpha1.Application
   177  	var expectedAppsNewMetadata []argov1alpha1.Application
   178  
   179  	Given(t).
   180  		// Create a ClusterGenerator-based ApplicationSet
   181  		When().
   182  		CreateClusterSecret("my-secret", "cluster1", "https://kubernetes.default.svc").
   183  		CreateClusterSecret("my-secret2", "cluster2", "https://kubernetes.default.svc").
   184  		Create(v1alpha1.ApplicationSet{ObjectMeta: metav1.ObjectMeta{
   185  			Name: "merge-generator",
   186  		},
   187  			Spec: v1alpha1.ApplicationSetSpec{
   188  				Template: v1alpha1.ApplicationSetTemplate{
   189  					ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{name}}-{{path.basename}}-{{values.name-suffix}}"},
   190  					Spec: argov1alpha1.ApplicationSpec{
   191  						Project: "default",
   192  						Source: &argov1alpha1.ApplicationSource{
   193  							RepoURL:        "https://github.com/argoproj/argocd-example-apps.git",
   194  							TargetRevision: "HEAD",
   195  							Path:           "{{path}}",
   196  						},
   197  						Destination: argov1alpha1.ApplicationDestination{
   198  							Name:      "{{name}}",
   199  							Namespace: "{{path.basename}}",
   200  						},
   201  					},
   202  				},
   203  				Generators: []v1alpha1.ApplicationSetGenerator{
   204  					{
   205  						Merge: &v1alpha1.MergeGenerator{
   206  							MergeKeys: []string{"name", "path.basename"},
   207  							Generators: []v1alpha1.ApplicationSetNestedGenerator{
   208  								{
   209  									Matrix: toAPIExtensionsJSON(t, &v1alpha1.NestedMatrixGenerator{
   210  										Generators: []v1alpha1.ApplicationSetTerminalGenerator{
   211  											{
   212  												Clusters: &v1alpha1.ClusterGenerator{
   213  													Selector: metav1.LabelSelector{
   214  														MatchLabels: map[string]string{
   215  															"argocd.argoproj.io/secret-type": "cluster",
   216  														},
   217  													},
   218  													Values: map[string]string{
   219  														"name-suffix": "0",
   220  													},
   221  												},
   222  											},
   223  											{
   224  												Git: &v1alpha1.GitGenerator{
   225  													RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
   226  													Directories: []v1alpha1.GitDirectoryGeneratorItem{
   227  														{
   228  															Path: "*guestbook*",
   229  														},
   230  													},
   231  												},
   232  											},
   233  										},
   234  									}),
   235  								},
   236  								{
   237  									List: &v1alpha1.ListGenerator{
   238  										Elements: []apiextensionsv1.JSON{
   239  											{Raw: []byte(`{"name": "cluster1", "path.basename": "kustomize-guestbook", "values": {"name-suffix": "1"}}`)},
   240  											{Raw: []byte(`{"name": "cluster2", "path.basename": "helm-guestbook", "values": {"name-suffix": "2"}}`)},
   241  										},
   242  									},
   243  								},
   244  							},
   245  						},
   246  					},
   247  				},
   248  			},
   249  		}).Then().Expect(ApplicationsExist(expectedApps)).
   250  
   251  		// Update the ApplicationSet template namespace, and verify it updates the Applications
   252  		When().
   253  		And(func() {
   254  			for _, expectedApp := range expectedApps {
   255  				newExpectedApp := expectedApp.DeepCopy()
   256  				newExpectedApp.Spec.Destination.Namespace = "guestbook2"
   257  				expectedAppsNewNamespace = append(expectedAppsNewNamespace, *newExpectedApp)
   258  			}
   259  		}).
   260  		Update(func(appset *v1alpha1.ApplicationSet) {
   261  			appset.Spec.Template.Spec.Destination.Namespace = "guestbook2"
   262  		}).Then().Expect(ApplicationsExist(expectedAppsNewNamespace)).
   263  
   264  		// Update the metadata fields in the appset template, and make sure it propagates to the apps
   265  		When().
   266  		And(func() {
   267  			for _, expectedApp := range expectedAppsNewNamespace {
   268  				expectedAppNewMetadata := expectedApp.DeepCopy()
   269  				expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
   270  				expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"}
   271  				expectedAppsNewMetadata = append(expectedAppsNewMetadata, *expectedAppNewMetadata)
   272  			}
   273  		}).
   274  		Update(func(appset *v1alpha1.ApplicationSet) {
   275  			appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
   276  			appset.Spec.Template.Labels = map[string]string{"label-key": "label-value"}
   277  		}).Then().Expect(ApplicationsExist(expectedAppsNewMetadata)).
   278  
   279  		// Delete the ApplicationSet, and verify it deletes the Applications
   280  		When().
   281  		Delete().Then().Expect(ApplicationsDoNotExist(expectedAppsNewNamespace))
   282  }
   283  
   284  func TestMergeTerminalMergeGeneratorSelector(t *testing.T) {
   285  	generateExpectedApp := func(name, nameSuffix string) argov1alpha1.Application {
   286  		return argov1alpha1.Application{
   287  			TypeMeta: metav1.TypeMeta{
   288  				Kind:       application.ApplicationKind,
   289  				APIVersion: "argoproj.io/v1alpha1",
   290  			},
   291  			ObjectMeta: metav1.ObjectMeta{
   292  				Name:       fmt.Sprintf("%s-%s", name, nameSuffix),
   293  				Namespace:  utils.TestNamespace(),
   294  				Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
   295  			},
   296  			Spec: argov1alpha1.ApplicationSpec{
   297  				Project: "default",
   298  				Source: &argov1alpha1.ApplicationSource{
   299  					RepoURL:        "https://github.com/argoproj/argocd-example-apps.git",
   300  					TargetRevision: "HEAD",
   301  					Path:           name,
   302  				},
   303  				Destination: argov1alpha1.ApplicationDestination{
   304  					Server:    "https://kubernetes.default.svc",
   305  					Namespace: name,
   306  				},
   307  			},
   308  		}
   309  	}
   310  
   311  	expectedApps1 := []argov1alpha1.Application{
   312  		generateExpectedApp("kustomize-guestbook", "1"),
   313  	}
   314  	expectedApps2 := []argov1alpha1.Application{
   315  		generateExpectedApp("helm-guestbook", "2"),
   316  	}
   317  
   318  	Given(t).
   319  		// Create ApplicationSet with LabelSelector on an ApplicationSetTerminalGenerator
   320  		When().
   321  		Create(v1alpha1.ApplicationSet{ObjectMeta: metav1.ObjectMeta{
   322  			Name: "merge-generator-nested-merge",
   323  		},
   324  			Spec: v1alpha1.ApplicationSetSpec{
   325  				ApplyNestedSelectors: true,
   326  				Template: v1alpha1.ApplicationSetTemplate{
   327  					ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{path.basename}}-{{name-suffix}}"},
   328  					Spec: argov1alpha1.ApplicationSpec{
   329  						Project: "default",
   330  						Source: &argov1alpha1.ApplicationSource{
   331  							RepoURL:        "https://github.com/argoproj/argocd-example-apps.git",
   332  							TargetRevision: "HEAD",
   333  							Path:           "{{path}}",
   334  						},
   335  						Destination: argov1alpha1.ApplicationDestination{
   336  							Server:    "https://kubernetes.default.svc",
   337  							Namespace: "{{path.basename}}",
   338  						},
   339  					},
   340  				},
   341  				Generators: []v1alpha1.ApplicationSetGenerator{
   342  					{
   343  						Merge: &v1alpha1.MergeGenerator{
   344  							MergeKeys: []string{"path.basename"},
   345  							Generators: []v1alpha1.ApplicationSetNestedGenerator{
   346  								{
   347  									Merge: toAPIExtensionsJSON(t, &v1alpha1.NestedMergeGenerator{
   348  										MergeKeys: []string{"path.basename"},
   349  										Generators: []v1alpha1.ApplicationSetTerminalGenerator{
   350  											{
   351  												Git: &v1alpha1.GitGenerator{
   352  													RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
   353  													Directories: []v1alpha1.GitDirectoryGeneratorItem{
   354  														{
   355  															Path: "*guestbook*",
   356  														},
   357  													},
   358  												},
   359  												Selector: &metav1.LabelSelector{
   360  													MatchLabels: map[string]string{
   361  														"path.basename": "kustomize-guestbook",
   362  													},
   363  												},
   364  											},
   365  											{
   366  												List: &v1alpha1.ListGenerator{
   367  													Elements: []apiextensionsv1.JSON{
   368  														{Raw: []byte(`{"path.basename": "kustomize-guestbook", "name-suffix": "1"}`)},
   369  														{Raw: []byte(`{"path.basename": "helm-guestbook", "name-suffix": "2"}`)},
   370  													},
   371  												},
   372  											},
   373  										},
   374  									}),
   375  								},
   376  								{
   377  									List: &v1alpha1.ListGenerator{
   378  										Elements: []apiextensionsv1.JSON{
   379  											{Raw: []byte(`{}`)},
   380  										},
   381  									},
   382  								},
   383  							},
   384  						},
   385  					},
   386  				},
   387  			},
   388  		}).Then().Expect(ApplicationsExist(expectedApps1)).Expect(ApplicationsDoNotExist(expectedApps2)).
   389  
   390  		// Update the ApplicationSetTerminalGenerator LabelSelector, and verify the Applications are deleted and created
   391  		When().
   392  		Update(func(appset *v1alpha1.ApplicationSet) {
   393  
   394  			appset.Spec.Generators[0].Merge.Generators[0].Merge = toAPIExtensionsJSON(t, &v1alpha1.NestedMergeGenerator{
   395  				MergeKeys: []string{"path.basename"},
   396  				Generators: []v1alpha1.ApplicationSetTerminalGenerator{
   397  					{
   398  						Git: &v1alpha1.GitGenerator{
   399  							RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
   400  							Directories: []v1alpha1.GitDirectoryGeneratorItem{
   401  								{
   402  									Path: "*guestbook*",
   403  								},
   404  							},
   405  						},
   406  						Selector: &metav1.LabelSelector{
   407  							MatchLabels: map[string]string{
   408  								"path.basename": "helm-guestbook",
   409  							},
   410  						},
   411  					},
   412  					{
   413  						List: &v1alpha1.ListGenerator{
   414  							Elements: []apiextensionsv1.JSON{
   415  								{Raw: []byte(`{"path.basename": "kustomize-guestbook", "name-suffix": "1"}`)},
   416  								{Raw: []byte(`{"path.basename": "helm-guestbook", "name-suffix": "2"}`)},
   417  							},
   418  						},
   419  					},
   420  				},
   421  			})
   422  		}).Then().Expect(ApplicationsExist(expectedApps2)).Expect(ApplicationsDoNotExist(expectedApps1)).
   423  
   424  		// Set ApplyNestedSelector to false and verify all Applications are created
   425  		When().
   426  		Update(func(appset *v1alpha1.ApplicationSet) {
   427  			appset.Spec.ApplyNestedSelectors = false
   428  		}).Then().Expect(ApplicationsExist(expectedApps1)).Expect(ApplicationsExist(expectedApps2)).
   429  
   430  		// Delete the ApplicationSet, and verify it deletes the Applications
   431  		When().
   432  		Delete().Then().Expect(ApplicationsDoNotExist(expectedApps1)).Expect(ApplicationsDoNotExist(expectedApps2))
   433  }
   434  
   435  func toAPIExtensionsJSON(t *testing.T, g interface{}) *apiextensionsv1.JSON {
   436  
   437  	resVal, err := json.Marshal(g)
   438  	if err != nil {
   439  		t.Error("unable to unmarshal json", g)
   440  		return nil
   441  	}
   442  
   443  	res := &apiextensionsv1.JSON{Raw: resVal}
   444  
   445  	return res
   446  }