Repository: argoproj-labs/applicationset
Pull request #290 (2021-07-12 03:13:17)

feat: allow any key/value pair in list generator
Allow any key/value pair in list generator supporting backward compatibility for `values` field

Fixes: #162 

Signed-off : [ Ishita Sequeira](@ishitasequeira)

ishitasequeira: @jgwest PTAL the PR is ready for review

Patch:
diff --git a/api/v1alpha1/applicationset_types.go b/api/v1alpha1/applicationset_types.go
index aff0161..86a93a2 100644
--- a/api/v1alpha1/applicationset_types.go
+++ b/api/v1alpha1/applicationset_types.go
@@ -18,6 +18,7 @@ package v1alpha1
 
 import (
 	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -91,7 +92,7 @@ type ApplicationSetBaseGenerator struct {
 
 // ListGenerator include items info
 type ListGenerator struct {
-	Elements []ListGeneratorElement `json:"elements"`
+	Elements []apiextensionsv1.JSON `json:"elements"`
 	Template ApplicationSetTemplate `json:"template,omitempty"`
 }
 
@@ -101,14 +102,6 @@ type MatrixGenerator struct {
 	Template   ApplicationSetTemplate        `json:"template,omitempty"`
 }
 
-// ListGeneratorElement include cluster and url info
-type ListGeneratorElement struct {
-	Cluster string `json:"cluster"`
-	Url     string `json:"url"`
-	// Values contains key/value pairs which are passed directly as parameters to the template
-	Values map[string]string `json:"values,omitempty"`
-}
-
 // ClusterGenerator defines a generator to match against clusters registered with ArgoCD.
 type ClusterGenerator struct {
 	// Selector defines a label selector to match against all clusters registered with ArgoCD.
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 5eae33e..86d3484 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -21,6 +21,7 @@ limitations under the License.
 package v1alpha1
 
 import (
+	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	runtime "k8s.io/apimachinery/pkg/runtime"
 )
 
@@ -396,7 +397,7 @@ func (in *ListGenerator) DeepCopyInto(out *ListGenerator) {
 	*out = *in
 	if in.Elements != nil {
 		in, out := &in.Elements, &out.Elements
-		*out = make([]ListGeneratorElement, len(*in))
+		*out = make([]v1.JSON, len(*in))
 		for i := range *in {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
@@ -414,28 +415,6 @@ func (in *ListGenerator) DeepCopy() *ListGenerator {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ListGeneratorElement) DeepCopyInto(out *ListGeneratorElement) {
-	*out = *in
-	if in.Values != nil {
-		in, out := &in.Values, &out.Values
-		*out = make(map[string]string, len(*in))
-		for key, val := range *in {
-			(*out)[key] = val
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListGeneratorElement.
-func (in *ListGeneratorElement) DeepCopy() *ListGeneratorElement {
-	if in == nil {
-		return nil
-	}
-	out := new(ListGeneratorElement)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *MatrixGenerator) DeepCopyInto(out *MatrixGenerator) {
 	*out = *in
diff --git a/docs/Generators-List.md b/docs/Generators-List.md
index 6bb08d0..0490274 100644
--- a/docs/Generators-List.md
+++ b/docs/Generators-List.md
@@ -1,6 +1,6 @@
 # List Generator
 
-The List generator generates parameters based on a fixed list of cluster name/URL values. In this example, we're targeting a local cluster named `engineering-dev`:
+The List generator generates parameters based on any list of key/value pairs as long as the values are string values. In this example, we're targeting a local cluster named `engineering-dev`:
 ```yaml
 apiVersion: argoproj.io/v1alpha1
 kind: ApplicationSet
@@ -14,6 +14,7 @@ spec:
        url: https://kubernetes.default.svc
 #    - cluster: engineering-prod
 #      url: https://kubernetes.default.svc
+#      foo: bar
  template:
    metadata:
      name: '{{cluster}}-guestbook'
@@ -29,7 +30,7 @@ spec:
 ```
 (*The full example can be found [here](https://github.com/argoproj-labs/applicationset/tree/master/examples/list-generator).*)
 
-The List generator passes the `url` and `cluster` fields as parameters into the template. In this example, if one wanted to add a second cluster, we could uncomment the second cluster element and the ApplicationSet controller would automatically target it with the defined application.
+The List generator passes the `url` and `cluster` fields as parameters into the template. In this example, if one wanted to add a second element, we could uncomment the second element and the ApplicationSet controller would automatically target it with the defined application.
 
 !!! note "Clusters must be predefined in Argo CD"
     These clusters *must* already be defined within Argo CD, in order to generate applications for these values. The ApplicationSet controller does not create clusters within Argo CD (for instance, it does not have the credentials to do so).
diff --git a/go.mod b/go.mod
index fa73e9e..d5c92a6 100644
--- a/go.mod
+++ b/go.mod
@@ -20,6 +20,7 @@ require (
 	golang.org/x/oauth2 v0.0.0-20210413134643-5e61552d6c78
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
 	k8s.io/api v0.21.1
+	k8s.io/apiextensions-apiserver v0.21.1
 	k8s.io/apimachinery v0.21.1
 	k8s.io/client-go v11.0.1-0.20190816222228-6d55c1b1f1ca+incompatible
 	k8s.io/kube-aggregator v0.21.0 // indirect
diff --git a/manifests/crds/argoproj.io_applicationsets.yaml b/manifests/crds/argoproj.io_applicationsets.yaml
index 46a748d..2e480d7 100644
--- a/manifests/crds/argoproj.io_applicationsets.yaml
+++ b/manifests/crds/argoproj.io_applicationsets.yaml
@@ -1548,23 +1548,7 @@ spec:
                       properties:
                         elements:
                           items:
-                            description: ListGeneratorElement include cluster and
-                              url info
-                            properties:
-                              cluster:
-                                type: string
-                              url:
-                                type: string
-                              values:
-                                additionalProperties:
-                                  type: string
-                                description: Values contains key/value pairs which
-                                  are passed directly as parameters to the template
-                                type: object
-                            required:
-                            - cluster
-                            - url
-                            type: object
+                            x-kubernetes-preserve-unknown-fields: true
                           type: array
                         template:
                           description: ApplicationSetTemplate represents argocd ApplicationSpec
@@ -3689,24 +3673,7 @@ spec:
                                 properties:
                                   elements:
                                     items:
-                                      description: ListGeneratorElement include cluster
-                                        and url info
-                                      properties:
-                                        cluster:
-                                          type: string
-                                        url:
-                                          type: string
-                                        values:
-                                          additionalProperties:
-                                            type: string
-                                          description: Values contains key/value pairs
-                                            which are passed directly as parameters
-                                            to the template
-                                          type: object
-                                      required:
-                                      - cluster
-                                      - url
-                                      type: object
+                                      x-kubernetes-preserve-unknown-fields: true
                                     type: array
                                   template:
                                     description: ApplicationSetTemplate represents
diff --git a/manifests/install-with-argo-cd.yaml b/manifests/install-with-argo-cd.yaml
index 415ea1c..3d2816a 100644
--- a/manifests/install-with-argo-cd.yaml
+++ b/manifests/install-with-argo-cd.yaml
@@ -2906,21 +2906,7 @@ spec:
                       properties:
                         elements:
                           items:
-                            description: ListGeneratorElement include cluster and url info
-                            properties:
-                              cluster:
-                                type: string
-                              url:
-                                type: string
-                              values:
-                                additionalProperties:
-                                  type: string
-                                description: Values contains key/value pairs which are passed directly as parameters to the template
-                                type: object
-                            required:
-                            - cluster
-                            - url
-                            type: object
+                            x-kubernetes-preserve-unknown-fields: true
                           type: array
                         template:
                           description: ApplicationSetTemplate represents argocd ApplicationSpec
@@ -4366,21 +4352,7 @@ spec:
                                 properties:
                                   elements:
                                     items:
-                                      description: ListGeneratorElement include cluster and url info
-                                      properties:
-                                        cluster:
-                                          type: string
-                                        url:
-                                          type: string
-                                        values:
-                                          additionalProperties:
-                                            type: string
-                                          description: Values contains key/value pairs which are passed directly as parameters to the template
-                                          type: object
-                                      required:
-                                      - cluster
-                                      - url
-                                      type: object
+                                      x-kubernetes-preserve-unknown-fields: true
                                     type: array
                                   template:
                                     description: ApplicationSetTemplate represents argocd ApplicationSpec
diff --git a/manifests/install.yaml b/manifests/install.yaml
index f346501..c2c2c94 100644
--- a/manifests/install.yaml
+++ b/manifests/install.yaml
@@ -1145,21 +1145,7 @@ spec:
                       properties:
                         elements:
                           items:
-                            description: ListGeneratorElement include cluster and url info
-                            properties:
-                              cluster:
-                                type: string
-                              url:
-                                type: string
-                              values:
-                                additionalProperties:
-                                  type: string
-                                description: Values contains key/value pairs which are passed directly as parameters to the template
-                                type: object
-                            required:
-                            - cluster
-                            - url
-                            type: object
+                            x-kubernetes-preserve-unknown-fields: true
                           type: array
                         template:
                           description: ApplicationSetTemplate represents argocd ApplicationSpec
@@ -2605,21 +2591,7 @@ spec:
                                 properties:
                                   elements:
                                     items:
-                                      description: ListGeneratorElement include cluster and url info
-                                      properties:
-                                        cluster:
-                                          type: string
-                                        url:
-                                          type: string
-                                        values:
-                                          additionalProperties:
-                                            type: string
-                                          description: Values contains key/value pairs which are passed directly as parameters to the template
-                                          type: object
-                                      required:
-                                      - cluster
-                                      - url
-                                      type: object
+                                      x-kubernetes-preserve-unknown-fields: true
                                     type: array
                                   template:
                                     description: ApplicationSetTemplate represents argocd ApplicationSpec
diff --git a/pkg/controllers/applicationset_controller.go b/pkg/controllers/applicationset_controller.go
index 7cf5505..044d94b 100644
--- a/pkg/controllers/applicationset_controller.go
+++ b/pkg/controllers/applicationset_controller.go
@@ -384,10 +384,15 @@ func (r *ApplicationSetReconciler) getCurrentApplications(_ context.Context, app
 	return current.Items, nil
 }
 
-// deleteInCluster will delete application that are current in the cluster but not in appList.
+// deleteInCluster will delete Applications that are currently on the cluster, but not in appList.
 // The function must be called after all generators had been called and generated applications
 func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, applicationSet argoprojiov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {
 
+	clusterList, err := utils.ListClusters(ctx, r.KubeClientset, applicationSet.Namespace)
+	if err != nil {
+		return err
+	}
+
 	// Save current applications to be able to delete the ones that are not in appList
 	current, err := r.getCurrentApplications(ctx, applicationSet)
 	if err != nil {
@@ -409,7 +414,7 @@ func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, applicat
 		if !exists {
 
 			// Removes the Argo CD resources finalizer if the application contains an invalid target (eg missing cluster)
-			err := r.removeFinalizerOnInvalidDestination(ctx, applicationSet, &app, appLog)
+			err := r.removeFinalizerOnInvalidDestination(ctx, applicationSet, &app, clusterList, appLog)
 			if err != nil {
 				appLog.WithError(err).Error("failed to update Application")
 				if firstError != nil {
@@ -434,16 +439,49 @@ func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, applicat
 }
 
 // removeFinalizerOnInvalidDestination removes the Argo CD resources finalizer if the application contains an invalid target (eg missing cluster)
-func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx context.Context, applicationSet argoprojiov1alpha1.ApplicationSet, app *argov1alpha1.Application, appLog *log.Entry) error {
+func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx context.Context, applicationSet argoprojiov1alpha1.ApplicationSet, app *argov1alpha1.Application, clusterList *argov1alpha1.ClusterList, appLog *log.Entry) error {
 
 	// Only check if the finalizers need to be removed IF there are finalizers to remove
 	if len(app.Finalizers) == 0 {
 		return nil
 	}
 
+	var validDestination bool
+
+	// Detect if the destination is invalid (name doesn't correspond to a matching cluster)
+	if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, applicationSet.Namespace); err != nil {
+		appLog.Warnf("The destination cluster for %s couldn't be found: %v", app.Name, err)
+		validDestination = false
+	} else {
+
+		// Detect if the destination's server field does not match an existing cluster
+
+		matchingCluster := false
+		for _, cluster := range clusterList.Items {
+
+			// Server fields must match. Note that ValidateDestination ensures that the server field is set, if applicable.
+			if app.Spec.Destination.Server != cluster.Server {
+				continue
+			}
+
+			// The name must match, if it is not empty
+			if app.Spec.Destination.Name != "" && cluster.Name != app.Spec.Destination.Name {
+				continue
+			}
+
+			matchingCluster = true
+			break
+		}
+
+		if !matchingCluster {
+			appLog.Warnf("A match for the destination cluster for %s, by server url, couldn't be found.", app.Name)
+		}
+
+		validDestination = matchingCluster
+	}
 	// If the destination is invalid (for example the cluster is no longer defined), then remove
 	// the application finalizers to avoid triggering Argo CD bug #5817
-	if err := utils.ValidateDestination(ctx, &app.Spec.Destination, r.KubeClientset, applicationSet.Namespace); err != nil {
+	if !validDestination {
 
 		// Filter out the Argo CD finalizer from the finalizer list
 		var newFinalizers []string
diff --git a/pkg/generators/list.go b/pkg/generators/list.go
index 9cd85bd..a9a18c1 100644
--- a/pkg/generators/list.go
+++ b/pkg/generators/list.go
@@ -1,11 +1,11 @@
 package generators
 
 import (
+	"encoding/json"
 	"fmt"
 	"time"
 
 	argoprojiov1alpha1 "github.com/argoproj-labs/applicationset/api/v1alpha1"
-	"github.com/argoproj-labs/applicationset/pkg/utils"
 )
 
 var _ Generator = (*ListGenerator)(nil)
@@ -38,12 +38,35 @@ func (g *ListGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Appli
 	res := make([]map[string]string, len(appSetGenerator.List.Elements))
 
 	for i, tmpItem := range appSetGenerator.List.Elements {
-		params := make(map[string]string, 2+len(tmpItem.Values))
-		params[utils.ClusterListGeneratorKeyName] = tmpItem.Cluster
-		params[utils.UrlGeneratorKeyName] = tmpItem.Url
-		for key, value := range tmpItem.Values {
-			params[fmt.Sprintf("values.%s", key)] = value
+		params := map[string]string{}
+		var element map[string]interface{}
+		err := json.Unmarshal(tmpItem.Raw, &element)
+		if err != nil {
+			return nil, fmt.Errorf("error unmarshling list element %v", err)
 		}
+
+		for key, value := range element {
+			if key == "values" {
+				values, ok := (value).(map[string]interface{})
+				if !ok {
+					return nil, fmt.Errorf("error parsing values map")
+				}
+				for k, v := range values {
+					value, ok := v.(string)
+					if !ok {
+						return nil, fmt.Errorf("error parsing value as string %v", err)
+					}
+					params[fmt.Sprintf("values.%s", k)] = value
+				}
+			} else {
+				v, ok := value.(string)
+				if !ok {
+					return nil, fmt.Errorf("error parsing value as string %v", err)
+				}
+				params[key] = v
+			}
+		}
+
 		res[i] = params
 	}
 
diff --git a/pkg/utils/clusterUtils.go b/pkg/utils/clusterUtils.go
index 9cb2713..e48abb2 100644
--- a/pkg/utils/clusterUtils.go
+++ b/pkg/utils/clusterUtils.go
@@ -47,7 +47,8 @@ const (
 
 func ListClusters(ctx context.Context, clientset kubernetes.Interface, namespace string) (*appv1.ClusterList, error) {
 
-	clusterSecretsList, err := clientset.CoreV1().Secrets(namespace).List(ctx, metav1.ListOptions{LabelSelector: common.LabelKeySecretType + "=" + common.LabelValueSecretTypeCluster})
+	clusterSecretsList, err := clientset.CoreV1().Secrets(namespace).List(ctx,
+		metav1.ListOptions{LabelSelector: common.LabelKeySecretType + "=" + common.LabelValueSecretTypeCluster})
 	if err != nil {
 		return nil, err
 	}


Test patch:
diff --git a/pkg/controllers/applicationset_controller_test.go b/pkg/controllers/applicationset_controller_test.go
index 44d9de6..ace35bc 100644
--- a/pkg/controllers/applicationset_controller_test.go
+++ b/pkg/controllers/applicationset_controller_test.go
@@ -10,6 +10,7 @@ import (
 	"time"
 
 	"github.com/argoproj-labs/applicationset/pkg/generators"
+	"github.com/argoproj-labs/applicationset/pkg/utils"
 	"github.com/argoproj/argo-cd/v2/common"
 	argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
 	log "github.com/sirupsen/logrus"
@@ -840,7 +841,7 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
 	}
 }
 
-func TestRemoveFinalizerOnInvalidDestination(t *testing.T) {
+func TestRemoveFinalizerOnInvalidDestination_FinalizerTypes(t *testing.T) {
 
 	scheme := runtime.NewScheme()
 	err := argoprojiov1alpha1.AddToScheme(scheme)
@@ -935,11 +936,14 @@ func TestRemoveFinalizerOnInvalidDestination(t *testing.T) {
 				KubeClientset: kubeclientset,
 			}
 
+			clusterList, err := utils.ListClusters(context.Background(), kubeclientset, "namespace")
+			assert.NoError(t, err, "Unexpected error")
+
 			appLog := log.WithFields(log.Fields{"app": app.Name, "appSet": ""})
 
 			appInputParam := app.DeepCopy()
 
-			err = r.removeFinalizerOnInvalidDestination(context.Background(), appSet, appInputParam, appLog)
+			err = r.removeFinalizerOnInvalidDestination(context.Background(), appSet, appInputParam, clusterList, appLog)
 			assert.NoError(t, err, "Unexpected error")
 
 			retrievedApp := argov1alpha1.Application{}
@@ -959,6 +963,163 @@ func TestRemoveFinalizerOnInvalidDestination(t *testing.T) {
 	}
 }
 
+func TestRemoveFinalizerOnInvalidDestination_DestinationTypes(t *testing.T) {
+
+	scheme := runtime.NewScheme()
+	err := argoprojiov1alpha1.AddToScheme(scheme)
+	assert.Nil(t, err)
+
+	err = argov1alpha1.AddToScheme(scheme)
+	assert.Nil(t, err)
+
+	for _, c := range []struct {
+		// name is human-readable test name
+		name                   string
+		destinationField       argov1alpha1.ApplicationDestination
+		expectFinalizerRemoved bool
+	}{
+		{
+			name: "invalid cluster: empty destination",
+			destinationField: argov1alpha1.ApplicationDestination{
+				Namespace: "namespace",
+			},
+			expectFinalizerRemoved: true,
+		},
+		{
+			name: "invalid cluster: invalid server url",
+			destinationField: argov1alpha1.ApplicationDestination{
+				Namespace: "namespace",
+				Server:    "https://1.2.3.4",
+			},
+			expectFinalizerRemoved: true,
+		},
+		{
+			name: "invalid cluster: invalid cluster name",
+			destinationField: argov1alpha1.ApplicationDestination{
+				Namespace: "namespace",
+				Name:      "invalid-cluster",
+			},
+			expectFinalizerRemoved: true,
+		},
+		{
+			name: "invalid cluster by both valid",
+			destinationField: argov1alpha1.ApplicationDestination{
+				Namespace: "namespace",
+				Name:      "mycluster2",
+				Server:    "https://kubernetes.default.svc",
+			},
+			expectFinalizerRemoved: true,
+		},
+		{
+			name: "invalid cluster by both invalid",
+			destinationField: argov1alpha1.ApplicationDestination{
+				Namespace: "namespace",
+				Name:      "mycluster3",
+				Server:    "https://4.5.6.7",
+			},
+			expectFinalizerRemoved: true,
+		},
+		{
+			name: "valid cluster by name",
+			destinationField: argov1alpha1.ApplicationDestination{
+				Namespace: "namespace",
+				Name:      "mycluster2",
+			},
+			expectFinalizerRemoved: false,
+		},
+		{
+			name: "valid cluster by server",
+			destinationField: argov1alpha1.ApplicationDestination{
+				Namespace: "namespace",
+				Server:    "https://kubernetes.default.svc",
+			},
+			expectFinalizerRemoved: false,
+		},
+	} {
+
+		t.Run(c.name, func(t *testing.T) {
+
+			appSet := argoprojiov1alpha1.ApplicationSet{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "name",
+					Namespace: "namespace",
+				},
+				Spec: argoprojiov1alpha1.ApplicationSetSpec{
+					Template: argoprojiov1alpha1.ApplicationSetTemplate{
+						Spec: argov1alpha1.ApplicationSpec{
+							Project: "project",
+						},
+					},
+				},
+			}
+
+			app := argov1alpha1.Application{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:       "app1",
+					Finalizers: []string{common.ResourcesFinalizerName},
+				},
+				Spec: argov1alpha1.ApplicationSpec{
+					Project:     "project",
+					Source:      argov1alpha1.ApplicationSource{Path: "path", TargetRevision: "revision", RepoURL: "repoURL"},
+					Destination: c.destinationField,
+				},
+			}
+
+			initObjs := []crtclient.Object{&app, &appSet}
+
+			client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).Build()
+			secret := &corev1.Secret{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "my-secret",
+					Namespace: "namespace",
+					Labels: map[string]string{
+						generators.ArgoCDSecretTypeLabel: generators.ArgoCDSecretTypeCluster,
+					},
+				},
+				Data: map[string][]byte{
+					// Since this test requires the cluster to be an invalid destination, we
+					// always return a cluster named 'my-cluster2' (different from app 'my-cluster', above)
+					"name":   []byte("mycluster2"),
+					"server": []byte("https://kubernetes.default.svc"),
+					"config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"),
+				},
+			}
+
+			objects := append([]runtime.Object{}, secret)
+			kubeclientset := kubefake.NewSimpleClientset(objects...)
+
+			r := ApplicationSetReconciler{
+				Client:        client,
+				Scheme:        scheme,
+				Recorder:      record.NewFakeRecorder(10),
+				KubeClientset: kubeclientset,
+			}
+
+			clusterList, err := utils.ListClusters(context.Background(), kubeclientset, "namespace")
+			assert.NoError(t, err, "Unexpected error")
+
+			appLog := log.WithFields(log.Fields{"app": app.Name, "appSet": ""})
+
+			appInputParam := app.DeepCopy()
+
+			err = r.removeFinalizerOnInvalidDestination(context.Background(), appSet, appInputParam, clusterList, appLog)
+			assert.NoError(t, err, "Unexpected error")
+
+			retrievedApp := argov1alpha1.Application{}
+			err = client.Get(context.Background(), crtclient.ObjectKeyFromObject(&app), &retrievedApp)
+			assert.NoError(t, err, "Unexpected error")
+
+			finalizerRemoved := len(retrievedApp.Finalizers) == 0
+
+			assert.True(t, c.expectFinalizerRemoved == finalizerRemoved)
+
+			bytes, _ := json.MarshalIndent(retrievedApp, "", "  ")
+			t.Log("Contents of app after call:", string(bytes))
+
+		})
+	}
+}
+
 func TestCreateApplications(t *testing.T) {
 
 	scheme := runtime.NewScheme()
@@ -1271,9 +1432,10 @@ func TestDeleteInCluster(t *testing.T) {
 		client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(initObjs...).Build()
 
 		r := ApplicationSetReconciler{
-			Client:   client,
-			Scheme:   scheme,
-			Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)),
+			Client:        client,
+			Scheme:        scheme,
+			Recorder:      record.NewFakeRecorder(len(initObjs) + len(c.expected)),
+			KubeClientset: kubefake.NewSimpleClientset(),
 		}
 
 		err = r.deleteInCluster(context.TODO(), c.appSet, c.desiredApps)
diff --git a/pkg/generators/list_test.go b/pkg/generators/list_test.go
index 472d821..4b50bee 100644
--- a/pkg/generators/list_test.go
+++ b/pkg/generators/list_test.go
@@ -4,24 +4,21 @@ import (
 	"testing"
 
 	argoprojiov1alpha1 "github.com/argoproj-labs/applicationset/api/v1alpha1"
-
 	"github.com/stretchr/testify/assert"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 )
 
 func TestGenerateListParams(t *testing.T) {
 	testCases := []struct {
-		elements []argoprojiov1alpha1.ListGeneratorElement
+		elements []apiextensionsv1.JSON
 		expected []map[string]string
 	}{
 		{
-			elements: []argoprojiov1alpha1.ListGeneratorElement{{Cluster: "cluster", Url: "url", Values: map[string]string{}}}, expected: []map[string]string{{
-				"cluster": "cluster", "url": "url"},
-			},
-		},
-		{
-			elements: []argoprojiov1alpha1.ListGeneratorElement{{Cluster: "cluster", Url: "url", Values: map[string]string{"foo": "bar"}}}, expected: []map[string]string{{
-				"cluster": "cluster", "url": "url", "values.foo": "bar",
-			}},
+			elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url"}`)}},
+			expected: []map[string]string{{"cluster": "cluster", "url": "url"}},
+		}, {
+			elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "cluster","url": "url","values":{"foo":"bar"}}`)}},
+			expected: []map[string]string{{"cluster": "cluster", "url": "url", "values.foo": "bar"}},
 		},
 	}
 
diff --git a/pkg/generators/matrix_test.go b/pkg/generators/matrix_test.go
index 67fc10d..8b20059 100644
--- a/pkg/generators/matrix_test.go
+++ b/pkg/generators/matrix_test.go
@@ -7,6 +7,7 @@ import (
 	argoprojiov1alpha1 "github.com/argoproj-labs/applicationset/api/v1alpha1"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 )
 
 func TestMatrixGenerate(t *testing.T) {
@@ -18,12 +19,7 @@ func TestMatrixGenerate(t *testing.T) {
 	}
 
 	listGenerator := &argoprojiov1alpha1.ListGenerator{
-		Elements: []argoprojiov1alpha1.ListGeneratorElement{
-			{
-				Cluster: "Cluster",
-				Url:     "Url",
-			},
-		},
+		Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url"}`)}},
 	}
 
 	testCases := []struct {
@@ -162,12 +158,7 @@ func TestMatrixGetRequeueAfter(t *testing.T) {
 	}
 
 	listGenerator := &argoprojiov1alpha1.ListGenerator{
-		Elements: []argoprojiov1alpha1.ListGeneratorElement{
-			{
-				Cluster: "Cluster",
-				Url:     "Url",
-			},
-		},
+		Elements: []apiextensionsv1.JSON{{Raw: []byte(`{"cluster": "Cluster","url": "Url"}`)}},
 	}
 
 	testCases := []struct {
diff --git a/test/e2e/applicationset/applicationset_test.go b/test/e2e/applicationset/applicationset_test.go
index d9fefdd..31a556d 100644
--- a/test/e2e/applicationset/applicationset_test.go
+++ b/test/e2e/applicationset/applicationset_test.go
@@ -10,6 +10,7 @@ import (
 	"github.com/argoproj-labs/applicationset/test/e2e/fixture/applicationsets/utils"
 	argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
 	corev1 "k8s.io/api/core/v1"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -65,9 +66,9 @@ func TestSimpleListGenerator(t *testing.T) {
 			Generators: []v1alpha1.ApplicationSetGenerator{
 				{
 					List: &v1alpha1.ListGenerator{
-						Elements: []v1alpha1.ListGeneratorElement{
-							{Cluster: "my-cluster", Url: "https://kubernetes.default.svc"},
-						},
+						Elements: []apiextensionsv1.JSON{{
+							Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
+						}},
 					},
 				},
 			},
@@ -494,9 +495,9 @@ func TestCustomApplicationFinalizers(t *testing.T) {
 			Generators: []v1alpha1.ApplicationSetGenerator{
 				{
 					List: &v1alpha1.ListGenerator{
-						Elements: []v1alpha1.ListGeneratorElement{
-							{Cluster: "my-cluster", Url: "https://kubernetes.default.svc"},
-						},
+						Elements: []apiextensionsv1.JSON{{
+							Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
+						}},
 					},
 				},
 			},
diff --git a/test/e2e/applicationset/cluster_e2e_test.go b/test/e2e/applicationset/cluster_e2e_test.go
index 8e2ccfc..56c20b3 100644
--- a/test/e2e/applicationset/cluster_e2e_test.go
+++ b/test/e2e/applicationset/cluster_e2e_test.go
@@ -303,7 +303,6 @@ func TestSimpleClusterGeneratorAddingCluster(t *testing.T) {
 		Delete().Then().Expect(ApplicationsDoNotExist([]argov1alpha1.Application{expectedAppCluster1, expectedAppCluster2}))
 }
 
-// There is a bug in ArgoCD that prevents this test from passing: https://github.com/argoproj/argo-cd/issues/5817
 func TestSimpleClusterGeneratorDeletingCluster(t *testing.T) {
 
 	expectedAppTemplate := argov1alpha1.Application{
diff --git a/test/e2e/applicationset/matrix_e2e_test.go b/test/e2e/applicationset/matrix_e2e_test.go
index 5965987..f4be8e3 100644
--- a/test/e2e/applicationset/matrix_e2e_test.go
+++ b/test/e2e/applicationset/matrix_e2e_test.go
@@ -8,6 +8,7 @@ import (
 	. "github.com/argoproj-labs/applicationset/test/e2e/fixture/applicationsets"
 	"github.com/argoproj-labs/applicationset/test/e2e/fixture/applicationsets/utils"
 	argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -79,9 +80,9 @@ func TestListMatrixGenerator(t *testing.T) {
 							Generators: []v1alpha1.ApplicationSetBaseGenerator{
 								{
 									List: &v1alpha1.ListGenerator{
-										Elements: []v1alpha1.ListGeneratorElement{
-											{Cluster: "my-cluster", Url: "https://kubernetes.default.svc", Values: map[string]string{"name": "cluster1"}},
-											{Cluster: "my-cluster", Url: "https://kubernetes.default.svc", Values: map[string]string{"name": "cluster2"}},
+										Elements: []apiextensionsv1.JSON{
+											{Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc", "values": {"name": "cluster1"}}`)},
+											{Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc", "values": {"name": "cluster2"}}`)},
 										},
 									},
 								},