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  }