Repository: argoproj-labs/applicationset
Pull request #272 (2021-06-18 20:00:17)

Initial commit of labelSelector support for ClusterDecisionResource
Signed-off-by: Joshua Packer <jpacker@redhat.com>

* This adds labelSelector support to the ClusterDecisionResource generator
* It also fixed an issue in the functional tests showing false positives
* I also fixed cluster_test.go which had the same false positive check
* Includes a documentation update as well to show how a `labelSelector` would be used. (pulls in standard Kubernetes labelSelector)

jnpacker: Issue Ref: https://github.com/argoproj-labs/applicationset/issues/271
jnpacker: Good to merge.

Patch:
diff --git a/api/v1alpha1/applicationset_types.go b/api/v1alpha1/applicationset_types.go
index db52517..4e21491 100644
--- a/api/v1alpha1/applicationset_types.go
+++ b/api/v1alpha1/applicationset_types.go
@@ -128,9 +128,10 @@ type DuckTypeGenerator struct {
 	//              this includes apiVersion(group/version), kind, matchKey and validation settings
 	// Name is the resource name of the kind, group and version, defined in the ConfigMapRef
 	// RequeueAfterSeconds is how long before the duckType will be rechecked for a change
-	ConfigMapRef        string `json:"configMapRef"`
-	Name                string `json:"name"`
-	RequeueAfterSeconds *int64 `json:"requeueAfterSeconds,omitempty"`
+	ConfigMapRef        string               `json:"configMapRef"`
+	Name                string               `json:"name,omitempty"`
+	RequeueAfterSeconds *int64               `json:"requeueAfterSeconds,omitempty"`
+	LabelSelector       metav1.LabelSelector `json:"labelSelector,omitempty"`
 
 	Template ApplicationSetTemplate `json:"template,omitempty"`
 	// Values contains key/value pairs which are passed directly as parameters to the template
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index a9b78eb..6860ead 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -304,6 +304,7 @@ func (in *DuckTypeGenerator) DeepCopyInto(out *DuckTypeGenerator) {
 		*out = new(int64)
 		**out = **in
 	}
+	in.LabelSelector.DeepCopyInto(&out.LabelSelector)
 	in.Template.DeepCopyInto(&out.Template)
 	if in.Values != nil {
 		in, out := &in.Values, &out.Values
diff --git a/docs/Generators.md b/docs/Generators.md
index 987e21d..b9caeb9 100644
--- a/docs/Generators.md
+++ b/docs/Generators.md
@@ -610,7 +610,16 @@ spec:
  generators:
  - clusterDecisionResource:
     configMapRef: my-configmap  # ConfigMap with GVK information for the duck type resource
-    name: quak                  # The name of the resource
+    name: quak                  # Choose either "name" of the resource or "labelSelector"
+    labelSelector:
+      matchLabels:              # OPTIONAL
+        duck: spotted
+      matchExpression:          # OPTIONAL
+      - key: duck
+        operator: In
+        values:
+        - "spotted"
+        - "canvasback"                         
     requeueAfterSeconds: 60     # OPTIONAL: Checks for changes every 60sec (default 3min)
  template:
    metadata:
diff --git a/examples/clusterDecisionResource/README.md b/examples/clusterDecisionResource/README.md
index 85ffe57..045cf82 100644
--- a/examples/clusterDecisionResource/README.md
+++ b/examples/clusterDecisionResource/README.md
@@ -37,14 +37,14 @@ data:
 
 # Applying the example
 1. Connect to a cluster with the ApplicationSet controller running
-2. Edit the Role for the ApplicationSet service account, and grant it permission to `get` the `placementdecisions` resources, from apiGroups `cluster.open-cluster-management.io/v1alpha1`
+2. Edit the Role for the ApplicationSet service account, and grant it permission to `list` the `placementdecisions` resources, from apiGroups `cluster.open-cluster-management.io/v1alpha1`
 ```yaml
 - apiGroups:
   - "cluster.open-cluster-management.io/v1alpha1"
   resources:
   - placementdecisions
   verbs:
-  - get
+  - list
 ```
 3. Apply the following controller and associated ManagedCluster CRD's:
 https://github.com/open-cluster-management/placement
diff --git a/manifests/crds/argoproj.io_applicationsets.yaml b/manifests/crds/argoproj.io_applicationsets.yaml
index 6870ece..f6c95eb 100644
--- a/manifests/crds/argoproj.io_applicationsets.yaml
+++ b/manifests/crds/argoproj.io_applicationsets.yaml
@@ -57,6 +57,54 @@ spec:
                             is how long before the duckType will be rechecked for
                             a change
                           type: string
+                        labelSelector:
+                          description: A label selector is a label query over a set
+                            of resources. The result of matchLabels and matchExpressions
+                            are ANDed. An empty label selector matches all objects.
+                            A null label selector matches no objects.
+                          properties:
+                            matchExpressions:
+                              description: matchExpressions is a list of label selector
+                                requirements. The requirements are ANDed.
+                              items:
+                                description: A label selector requirement is a selector
+                                  that contains values, a key, and an operator that
+                                  relates the key and values.
+                                properties:
+                                  key:
+                                    description: key is the label key that the selector
+                                      applies to.
+                                    type: string
+                                  operator:
+                                    description: operator represents a key's relationship
+                                      to a set of values. Valid operators are In,
+                                      NotIn, Exists and DoesNotExist.
+                                    type: string
+                                  values:
+                                    description: values is an array of string values.
+                                      If the operator is In or NotIn, the values array
+                                      must be non-empty. If the operator is Exists
+                                      or DoesNotExist, the values array must be empty.
+                                      This array is replaced during a strategic merge
+                                      patch.
+                                    items:
+                                      type: string
+                                    type: array
+                                required:
+                                - key
+                                - operator
+                                type: object
+                              type: array
+                            matchLabels:
+                              additionalProperties:
+                                type: string
+                              description: matchLabels is a map of {key,value} pairs.
+                                A single {key,value} in the matchLabels map is equivalent
+                                to an element of matchExpressions, whose key field
+                                is "key", the operator is "In", and the values array
+                                contains only "value". The requirements are ANDed.
+                              type: object
+                          type: object
                         name:
                           type: string
                         requeueAfterSeconds:
@@ -464,7 +512,6 @@ spec:
                           type: object
                       required:
                       - configMapRef
-                      - name
                       type: object
                     clusters:
                       description: ClusterGenerator defines a generator to match against
@@ -1789,6 +1836,60 @@ spec:
                                       is how long before the duckType will be rechecked
                                       for a change
                                     type: string
+                                  labelSelector:
+                                    description: A label selector is a label query
+                                      over a set of resources. The result of matchLabels
+                                      and matchExpressions are ANDed. An empty label
+                                      selector matches all objects. A null label selector
+                                      matches no objects.
+                                    properties:
+                                      matchExpressions:
+                                        description: matchExpressions is a list of
+                                          label selector requirements. The requirements
+                                          are ANDed.
+                                        items:
+                                          description: A label selector requirement
+                                            is a selector that contains values, a
+                                            key, and an operator that relates the
+                                            key and values.
+                                          properties:
+                                            key:
+                                              description: key is the label key that
+                                                the selector applies to.
+                                              type: string
+                                            operator:
+                                              description: operator represents a key's
+                                                relationship to a set of values. Valid
+                                                operators are In, NotIn, Exists and
+                                                DoesNotExist.
+                                              type: string
+                                            values:
+                                              description: values is an array of string
+                                                values. If the operator is In or NotIn,
+                                                the values array must be non-empty.
+                                                If the operator is Exists or DoesNotExist,
+                                                the values array must be empty. This
+                                                array is replaced during a strategic
+                                                merge patch.
+                                              items:
+                                                type: string
+                                              type: array
+                                          required:
+                                          - key
+                                          - operator
+                                          type: object
+                                        type: array
+                                      matchLabels:
+                                        additionalProperties:
+                                          type: string
+                                        description: matchLabels is a map of {key,value}
+                                          pairs. A single {key,value} in the matchLabels
+                                          map is equivalent to an element of matchExpressions,
+                                          whose key field is "key", the operator is
+                                          "In", and the values array contains only
+                                          "value". The requirements are ANDed.
+                                        type: object
+                                    type: object
                                   name:
                                     type: string
                                   requeueAfterSeconds:
@@ -2229,7 +2330,6 @@ spec:
                                     type: object
                                 required:
                                 - configMapRef
-                                - name
                                 type: object
                               clusters:
                                 description: ClusterGenerator defines a generator
diff --git a/manifests/install-with-argo-cd.yaml b/manifests/install-with-argo-cd.yaml
index 0cbdb45..301d010 100644
--- a/manifests/install-with-argo-cd.yaml
+++ b/manifests/install-with-argo-cd.yaml
@@ -1805,6 +1805,36 @@ spec:
                         configMapRef:
                           description: ConfigMapRef is a ConfigMap with the duck type definitions needed to retreive the data              this includes apiVersion(group/version), kind, matchKey and validation settings Name is the resource name of the kind, group and version, defined in the ConfigMapRef RequeueAfterSeconds is how long before the duckType will be rechecked for a change
                           type: string
+                        labelSelector:
+                          description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.
+                          properties:
+                            matchExpressions:
+                              description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
+                              items:
+                                description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+                                properties:
+                                  key:
+                                    description: key is the label key that the selector applies to.
+                                    type: string
+                                  operator:
+                                    description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+                                    type: string
+                                  values:
+                                    description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+                                    items:
+                                      type: string
+                                    type: array
+                                required:
+                                - key
+                                - operator
+                                type: object
+                              type: array
+                            matchLabels:
+                              additionalProperties:
+                                type: string
+                              description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+                              type: object
+                          type: object
                         name:
                           type: string
                         requeueAfterSeconds:
@@ -2132,7 +2162,6 @@ spec:
                           type: object
                       required:
                       - configMapRef
-                      - name
                       type: object
                     clusters:
                       description: ClusterGenerator defines a generator to match against clusters registered with ArgoCD.
@@ -3188,6 +3217,36 @@ spec:
                                   configMapRef:
                                     description: ConfigMapRef is a ConfigMap with the duck type definitions needed to retreive the data              this includes apiVersion(group/version), kind, matchKey and validation settings Name is the resource name of the kind, group and version, defined in the ConfigMapRef RequeueAfterSeconds is how long before the duckType will be rechecked for a change
                                     type: string
+                                  labelSelector:
+                                    description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.
+                                    properties:
+                                      matchExpressions:
+                                        description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
+                                        items:
+                                          description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+                                          properties:
+                                            key:
+                                              description: key is the label key that the selector applies to.
+                                              type: string
+                                            operator:
+                                              description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+                                              type: string
+                                            values:
+                                              description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+                                              items:
+                                                type: string
+                                              type: array
+                                          required:
+                                          - key
+                                          - operator
+                                          type: object
+                                        type: array
+                                      matchLabels:
+                                        additionalProperties:
+                                          type: string
+                                        description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+                                        type: object
+                                    type: object
                                   name:
                                     type: string
                                   requeueAfterSeconds:
@@ -3515,7 +3574,6 @@ spec:
                                     type: object
                                 required:
                                 - configMapRef
-                                - name
                                 type: object
                               clusters:
                                 description: ClusterGenerator defines a generator to match against clusters registered with ArgoCD.
diff --git a/manifests/install.yaml b/manifests/install.yaml
index a5a2ad8..1e13939 100644
--- a/manifests/install.yaml
+++ b/manifests/install.yaml
@@ -44,6 +44,36 @@ spec:
                         configMapRef:
                           description: ConfigMapRef is a ConfigMap with the duck type definitions needed to retreive the data              this includes apiVersion(group/version), kind, matchKey and validation settings Name is the resource name of the kind, group and version, defined in the ConfigMapRef RequeueAfterSeconds is how long before the duckType will be rechecked for a change
                           type: string
+                        labelSelector:
+                          description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.
+                          properties:
+                            matchExpressions:
+                              description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
+                              items:
+                                description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+                                properties:
+                                  key:
+                                    description: key is the label key that the selector applies to.
+                                    type: string
+                                  operator:
+                                    description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+                                    type: string
+                                  values:
+                                    description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+                                    items:
+                                      type: string
+                                    type: array
+                                required:
+                                - key
+                                - operator
+                                type: object
+                              type: array
+                            matchLabels:
+                              additionalProperties:
+                                type: string
+                              description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+                              type: object
+                          type: object
                         name:
                           type: string
                         requeueAfterSeconds:
@@ -371,7 +401,6 @@ spec:
                           type: object
                       required:
                       - configMapRef
-                      - name
                       type: object
                     clusters:
                       description: ClusterGenerator defines a generator to match against clusters registered with ArgoCD.
@@ -1427,6 +1456,36 @@ spec:
                                   configMapRef:
                                     description: ConfigMapRef is a ConfigMap with the duck type definitions needed to retreive the data              this includes apiVersion(group/version), kind, matchKey and validation settings Name is the resource name of the kind, group and version, defined in the ConfigMapRef RequeueAfterSeconds is how long before the duckType will be rechecked for a change
                                     type: string
+                                  labelSelector:
+                                    description: A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.
+                                    properties:
+                                      matchExpressions:
+                                        description: matchExpressions is a list of label selector requirements. The requirements are ANDed.
+                                        items:
+                                          description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.
+                                          properties:
+                                            key:
+                                              description: key is the label key that the selector applies to.
+                                              type: string
+                                            operator:
+                                              description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.
+                                              type: string
+                                            values:
+                                              description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.
+                                              items:
+                                                type: string
+                                              type: array
+                                          required:
+                                          - key
+                                          - operator
+                                          type: object
+                                        type: array
+                                      matchLabels:
+                                        additionalProperties:
+                                          type: string
+                                        description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed.
+                                        type: object
+                                    type: object
                                   name:
                                     type: string
                                   requeueAfterSeconds:
@@ -1754,7 +1813,6 @@ spec:
                                     type: object
                                 required:
                                 - configMapRef
-                                - name
                                 type: object
                               clusters:
                                 description: ClusterGenerator defines a generator to match against clusters registered with ArgoCD.
diff --git a/pkg/generators/duck_type.go b/pkg/generators/duck_type.go
index 8d9f654..243879e 100644
--- a/pkg/generators/duck_type.go
+++ b/pkg/generators/duck_type.go
@@ -13,6 +13,7 @@ import (
 	argoprojiov1alpha1 "github.com/argoproj-labs/applicationset/api/v1alpha1"
 	"github.com/argoproj-labs/applicationset/pkg/utils"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/fields"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/client-go/dynamic"
 	"k8s.io/client-go/kubernetes"
@@ -90,35 +91,52 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
 	versionIdx := strings.Index(cm.Data["apiVersion"], "/")
 	kind := cm.Data["kind"]
 	resourceName := appSetGenerator.ClusterDecisionResource.Name
+	labelSelector := appSetGenerator.ClusterDecisionResource.LabelSelector
 
-	log.WithField("resourcename.kind.apiVersion", resourceName+"."+kind+"."+
-		cm.Data["apiVersion"]).Info("ResourceName.Kind.Group/Version Reference")
+	log.WithField("kind.apiVersion", kind+"."+cm.Data["apiVersion"]).Info("Kind.Group/Version Reference")
 
-	if kind == "" || resourceName == "" || versionIdx < 1 {
+	// Validate the fields
+	if kind == "" || versionIdx < 1 {
 		log.Warningf("kind=%v, resourceName=%v, versionIdx=%v", kind, resourceName, versionIdx)
 		return nil, errors.New("There is a problem with the apiVersion, kind or resourceName provided")
 	}
 
+	if (resourceName == "" && labelSelector.MatchLabels == nil && labelSelector.MatchExpressions == nil) ||
+		(resourceName != "" && (labelSelector.MatchExpressions != nil || labelSelector.MatchLabels != nil)) {
+
+		log.Warningf("You must choose either resourceName=%v, labelSelector.matchLabels=%v or labelSelect.matchExpressions=%v", resourceName, labelSelector.MatchLabels, labelSelector.MatchExpressions)
+		return nil, errors.New("There is a problem with the definition of the ClusterDecisionResource generator")
+	}
+
 	// Split up the apiVersion
 	group := cm.Data["apiVersion"][0:versionIdx]
 	version := cm.Data["apiVersion"][versionIdx+1:]
 	log.WithField("kind.group.version", kind+"."+group+"/"+version).Debug("decoded Ref")
 
 	duckGVR := schema.GroupVersionResource{Group: group, Version: version, Resource: kind}
-	duckResource, err := g.dynClient.Resource(duckGVR).Namespace(g.namespace).Get(g.ctx, resourceName, metav1.GetOptions{})
+
+	listOptions := metav1.ListOptions{}
+	if resourceName == "" {
+		listOptions.LabelSelector = metav1.FormatLabelSelector(&labelSelector)
+		log.WithField("listOptions.LabelSelector", listOptions.LabelSelector).Info("selection type")
+	} else {
+		listOptions.FieldSelector = fields.OneTermEqualSelector("metadata.name", resourceName).String()
+		//metav1.Convert_fields_Selector_To_string(fields.).Sprintf("metadata.name=%s", resourceName)
+		log.WithField("listOptions.FieldSelector", listOptions.FieldSelector).Info("selection type")
+	}
+
+	duckResources, err := g.dynClient.Resource(duckGVR).Namespace(g.namespace).List(g.ctx, listOptions)
 
 	if err != nil {
-		log.WithField("GVK", duckGVR).Warningf("resource was not found with name=%v", resourceName)
+		log.WithField("GVK", duckGVR).Warning("resources were not found")
 		return nil, err
 	}
 
-	if duckResource == nil || duckResource.Object["status"] == nil {
-		log.Warning("duck type status missing")
-		return nil, nil
+	if len(duckResources.Items) == 0 {
+		log.Warning("no resource found, make sure you clusterDecisionResource is defined correctly")
+		return nil, errors.New("no clusterDecisionResources found")
 	}
 
-	log.WithField("duckResourceName", duckResource.GetName()).Debug("found duck status on resource")
-
 	// Override the duck type in the status of the resource
 	statusListKey := "clusters"
 
@@ -134,15 +152,34 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
 	}
 
 	res := []map[string]string{}
+	clusterDecisions := []interface{}{}
 
-	clusterDecisions := duckResource.Object["status"].(map[string]interface{})[statusListKey]
+	// Build the decision slice
+	for _, duckResource := range duckResources.Items {
+		log.WithField("duckResourceName", duckResource.GetName()).Debug("found resource")
 
-	if clusterDecisions != nil {
-		for _, cluster := range clusterDecisions.([]interface{}) {
+		if duckResource.Object["status"] == nil || len(duckResource.Object["status"].(map[string]interface{})) == 0 {
+			log.Warningf("clusterDecisionResource: %s, has no status", duckResource.GetName())
+			continue
+		}
+
+		log.WithField("duckResourceStatus", duckResource.Object["status"]).Debug("found resource")
+
+		clusterDecisions = append(clusterDecisions, duckResource.Object["status"].(map[string]interface{})[statusListKey].([]interface{})...)
+
+	}
+	log.Infof("Number of decisions found: %v", len(clusterDecisions))
+
+	// Read this outside the loop to improve performance
+	argoClusters := clustersFromArgoCD.Items
+
+	if len(clusterDecisions) > 0 {
+		for _, cluster := range clusterDecisions {
 
 			// generated instance of cluster params
 			params := map[string]string{}
 
+			log.Infof("cluster: %v", cluster)
 			matchValue := cluster.(map[string]interface{})[matchKey]
 			if matchValue == nil || matchValue.(string) == "" {
 				log.Warningf("matchKey=%v not found in \"%v\" list: %v\n", matchKey, statusListKey, cluster.(map[string]interface{}))
@@ -154,7 +191,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
 
 			found := false
 
-			for _, argoCluster := range clustersFromArgoCD.Items {
+			for _, argoCluster := range argoClusters {
 				if argoCluster.Name == strMatchValue {
 
 					log.WithField(matchKey, argoCluster.Name).Info("matched cluster in ArgoCD")
@@ -162,6 +199,7 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
 					params["server"] = argoCluster.Server
 
 					found = true
+					break // Stop looking
 				}
 
 			}
@@ -182,7 +220,8 @@ func (g *DuckTypeGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.A
 			res = append(res, params)
 		}
 	} else {
-		return nil, errors.New("duck type status." + statusListKey + " missing")
+		log.Warningf("clusterDecisionResource status." + statusListKey + " missing")
+		return nil, nil
 	}
 
 	return res, nil


Test patch:
diff --git a/pkg/generators/cluster_test.go b/pkg/generators/cluster_test.go
index ca441d3..4cc66d5 100644
--- a/pkg/generators/cluster_test.go
+++ b/pkg/generators/cluster_test.go
@@ -232,7 +232,7 @@ func TestGenerateParams(t *testing.T) {
 			}, nil)
 
 			if testCase.expectedError != nil {
-				assert.Error(t, testCase.expectedError, err)
+				assert.EqualError(t, err, testCase.expectedError.Error())
 			} else {
 				assert.NoError(t, err)
 				assert.ElementsMatch(t, testCase.expected, got)
diff --git a/pkg/generators/duck_type_test.go b/pkg/generators/duck_type_test.go
index bd5a544..2be9feb 100644
--- a/pkg/generators/duck_type_test.go
+++ b/pkg/generators/duck_type_test.go
@@ -80,6 +80,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
 			"metadata": map[string]interface{}{
 				"name":      resourceName,
 				"namespace": "namespace",
+				"labels":    map[string]interface{}{"duck": "all-species"},
 			},
 			"status": map[string]interface{}{
 				"decisions": []interface{}{
@@ -101,6 +102,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
 			"metadata": map[string]interface{}{
 				"name":      resourceName,
 				"namespace": "namespace",
+				"labels":    map[string]interface{}{"duck": "spotted"},
 			},
 			"status": map[string]interface{}{
 				"decisions": []interface{}{
@@ -119,6 +121,7 @@ func TestGenerateParamsForDuckType(t *testing.T) {
 			"metadata": map[string]interface{}{
 				"name":      resourceName,
 				"namespace": "namespace",
+				"labels":    map[string]interface{}{"duck": "canvasback"},
 			},
 			"status": map[string]interface{}{},
 		},
@@ -138,14 +141,13 @@ func TestGenerateParamsForDuckType(t *testing.T) {
 	}
 
 	testCases := []struct {
-		name         string
-		configMapRef string
-		resourceName string
-		resource     *unstructured.Unstructured
-		values       map[string]string
-		expected     []map[string]string
-		// clientError is true if a k8s client error should be simulated
-		clientError   bool
+		name          string
+		configMapRef  string
+		resourceName  string
+		labelSelector metav1.LabelSelector
+		resource      *unstructured.Unstructured
+		values        map[string]string
+		expected      []map[string]string
 		expectedError error
 	}{
 		{
@@ -154,20 +156,20 @@ func TestGenerateParamsForDuckType(t *testing.T) {
 			resource:      duckType,
 			values:        nil,
 			expected:      []map[string]string{},
-			clientError:   false,
-			expectedError: errors.New("Invalid resource reference"),
+			expectedError: errors.New("There is a problem with the definition of the ClusterDecisionResource generator"),
 		},
+		/*** This does not work with the FAKE runtime client, fieldSelectors are broken.
 		{
-			name:          "invalid params for duck resource",
-			resourceName:  resourceName,
+			name:          "invalid name for duck resource",
+			resourceName:  resourceName + "-different",
 			resource:      duckType,
 			values:        nil,
 			expected:      []map[string]string{},
-			clientError:   false,
 			expectedError: errors.New("duck.mallard.io \"quak\" not found"),
 		},
+		***/
 		{
-			name:         "duck type generator",
+			name:         "duck type generator resourceName",
 			resourceName: resourceName,
 			resource:     duckType,
 			values:       nil,
@@ -176,7 +178,6 @@ func TestGenerateParamsForDuckType(t *testing.T) {
 
 				{"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"},
 			},
-			clientError:   false,
 			expectedError: nil,
 		},
 		{
@@ -189,7 +190,6 @@ func TestGenerateParamsForDuckType(t *testing.T) {
 			expected: []map[string]string{
 				{"clusterName": "production-01", "values.foo": "bar", "name": "production-01", "server": "https://production-01.example.com"},
 			},
-			clientError:   false,
 			expectedError: nil,
 		},
 		{
@@ -198,17 +198,76 @@ func TestGenerateParamsForDuckType(t *testing.T) {
 			resource:      duckTypeEmpty,
 			values:        nil,
 			expected:      nil,
-			clientError:   false,
-			expectedError: errors.New("duck type status.decisions missing"),
+			expectedError: nil,
 		},
 		{
-			name:          "simulate client error",
-			resourceName:  resourceName,
+			name:          "duck type empty status labelSelector.matchLabels",
+			resourceName:  "",
+			labelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"duck": "canvasback"}},
+			resource:      duckTypeEmpty,
+			values:        nil,
+			expected:      nil,
+			expectedError: nil,
+		},
+		{
+			name:          "duck type generator labelSelector.matchLabels",
+			resourceName:  "",
+			labelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"duck": "all-species"}},
+			resource:      duckType,
+			values:        nil,
+			expected: []map[string]string{
+				{"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"},
+
+				{"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"},
+			},
+			expectedError: nil,
+		},
+		{
+			name:          "production-only labelSelector.matchLabels",
+			resourceName:  "",
+			resource:      duckTypeProdOnly,
+			labelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"duck": "spotted"}},
+			values: map[string]string{
+				"foo": "bar",
+			},
+			expected: []map[string]string{
+				{"clusterName": "production-01", "values.foo": "bar", "name": "production-01", "server": "https://production-01.example.com"},
+			},
+			expectedError: nil,
+		},
+		{
+			name:         "duck type generator labelSelector.matchExpression",
+			resourceName: "",
+			labelSelector: metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{
+				metav1.LabelSelectorRequirement{
+					Key:      "duck",
+					Operator: "In",
+					Values:   []string{"all-species", "marbled"},
+				},
+			}},
+			resource: duckType,
+			values:   nil,
+			expected: []map[string]string{
+				{"clusterName": "production-01", "name": "production-01", "server": "https://production-01.example.com"},
+
+				{"clusterName": "staging-01", "name": "staging-01", "server": "https://staging-01.example.com"},
+			},
+			expectedError: nil,
+		},
+		{
+			name:         "duck type generator resourceName and labelSelector.matchExpression",
+			resourceName: resourceName,
+			labelSelector: metav1.LabelSelector{MatchExpressions: []metav1.LabelSelectorRequirement{
+				metav1.LabelSelectorRequirement{
+					Key:      "duck",
+					Operator: "In",
+					Values:   []string{"all-species", "marbled"},
+				},
+			}},
 			resource:      duckType,
 			values:        nil,
 			expected:      nil,
-			clientError:   true,
-			expectedError: errors.New("could not list Secrets"),
+			expectedError: errors.New("There is a problem with the definition of the ClusterDecisionResource generator"),
 		},
 	}
 
@@ -230,19 +289,19 @@ func TestGenerateParamsForDuckType(t *testing.T) {
 
 			got, err := duckTypeGenerator.GenerateParams(&argoprojiov1alpha1.ApplicationSetGenerator{
 				ClusterDecisionResource: &argoprojiov1alpha1.DuckTypeGenerator{
-					ConfigMapRef: "my-configmap",
-					Name:         testCase.resourceName,
-					Values:       testCase.values,
+					ConfigMapRef:  "my-configmap",
+					Name:          testCase.resourceName,
+					LabelSelector: testCase.labelSelector,
+					Values:        testCase.values,
 				},
 			}, nil)
 
 			if testCase.expectedError != nil {
-				assert.Error(t, testCase.expectedError, err)
+				assert.EqualError(t, err, testCase.expectedError.Error())
 			} else {
 				assert.NoError(t, err)
 				assert.ElementsMatch(t, testCase.expected, got)
 			}
-
 		})
 	}
 }