Repository: kubernetes-sigs/node-feature-discovery
Pull request #667 (2021-11-23 19:08:02)

Add deploy-prune makefile rule to ease devel processes
Signed-off-by: Carlos Eduardo Arango Gutierrez <carangog@redhat.com>

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/node-feature-discovery/pull/667#" title="Author self-approved">ArangoGutierrez</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **marquiz** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @marquiz` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fnode-feature-discovery).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/node-feature-discovery/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["marquiz"]} -->
ArangoGutierrez: /assign @marquiz
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/node-feature-discovery/pull/667#" title="Author self-approved">ArangoGutierrez</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please ask for approval from **marquiz** after the PR has been reviewed.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fnode-feature-discovery).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/node-feature-discovery/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["marquiz"]} -->
ArangoGutierrez: Changed to `prune-deployment` as discussed 

Patch:
diff --git a/Makefile b/Makefile
index 5b9b0e34e..e373520fc 100644
--- a/Makefile
+++ b/Makefile
@@ -86,6 +86,14 @@ image: yamls
 	    $(foreach tag,$(IMAGE_EXTRA_TAGS),-t $(tag)-minimal) \
 	    $(IMAGE_BUILD_EXTRA_OPTS) ./
 
+# clean NFD labels on all nodes
+# devel only
+deploy-prune:
+	kubectl apply -k deployment/overlays/prune/
+	kubectl wait --for=condition=complete job -l app=nfd -n node-feature-discovery
+	kubectl delete -k deployment/overlays/prune/
+
+
 yamls:
 	@./scripts/kustomize.sh $(K8S_NAMESPACE) $(IMAGE_REPO) $(IMAGE_TAG_NAME)
 
diff --git a/deployment/base/nfd-crds/cr-sample.yaml b/deployment/base/nfd-crds/cr-sample.yaml
index dad9405d0..ac33c8068 100644
--- a/deployment/base/nfd-crds/cr-sample.yaml
+++ b/deployment/base/nfd-crds/cr-sample.yaml
@@ -87,53 +87,3 @@ spec:
             matchExpressions:
               vendor: {op: In, value: ["8086"]}
               class: {op: In, value: ["02"]}
-
-    # The following features demonstreate label templating capabilities
-    - name: "my system template feature"
-      labelsTemplate: |
-        {{ range .system.osrelease }}my-system-feature.{{ .Name }}={{ .Value }}
-        {{ end }}
-      matchFeatures:
-        - feature: system.osrelease
-          matchExpressions:
-            ID: {op: InRegexp, value: ["^open.*"]}
-            VERSION_ID.major: {op: In, value: ["13", "15"]}
-
-    - name: "my pci template feature"
-      labelsTemplate: |
-        {{ range .pci.device }}my-pci-device.{{ .class }}-{{ .device }}=with-cpuid
-        {{ end }}
-      matchFeatures:
-        - feature: pci.device
-          matchExpressions:
-            class: {op: InRegexp, value: ["^06"]}
-            vendor: {op: In, value: ["8086"]}
-        - feature: cpu.cpuid
-          matchExpressions:
-            AVX: {op: Exists}
-
-    # The following examples demonstrate vars field and back-referencing
-    # previous labels and vars
-    - name: "my dummy kernel rule"
-      labels:
-        "my.kernel.feature": "true"
-      matchFeatures:
-        - feature: kernel.version
-          matchExpressions:
-            major: {op: Gt, value: ["2"]}
-
-    - name: "my dummy rule with no labels"
-      vars:
-        "my.dummy.var": "1"
-      matchFeatures:
-        - feature: cpu.cpuid
-          matchExpressions: {}
-
-    - name: "my rule using backrefs"
-      labels:
-        "my.backref.feature": "true"
-      matchFeatures:
-        - feature: rule.matched
-          matchExpressions:
-            my.kernel.feature: {op: IsTrue}
-            my.dummy.var: {op: Gt, value: ["0"]}
diff --git a/deployment/base/nfd-crds/nodefeaturerule-crd.yaml b/deployment/base/nfd-crds/nodefeaturerule-crd.yaml
index cd9b48bf4..1f273e6c7 100644
--- a/deployment/base/nfd-crds/nodefeaturerule-crd.yaml
+++ b/deployment/base/nfd-crds/nodefeaturerule-crd.yaml
@@ -48,12 +48,6 @@ spec:
                         type: string
                       description: Labels to create if the rule matches.
                       type: object
-                    labelsTemplate:
-                      description: LabelsTemplate specifies a template to expand for
-                        dynamically generating multiple labels. Data (after template
-                        expansion) must be keys with an optional value (<key>[=<value>])
-                        separated by newlines.
-                      type: string
                     matchAny:
                       description: MatchAny specifies a list of matchers one of which
                         must match.
@@ -188,21 +182,6 @@ spec:
                     name:
                       description: Name of the rule.
                       type: string
-                    vars:
-                      additionalProperties:
-                        type: string
-                      description: Vars is the variables to store if the rule matches.
-                        Variables do not directly inflict any changes in the node
-                        object. However, they can be referenced from other rules enabling
-                        more complex rule hierarchies, without exposing intermediary
-                        output values as labels.
-                      type: object
-                    varsTemplate:
-                      description: VarsTemplate specifies a template to expand for
-                        dynamically generating multiple variables. Data (after template
-                        expansion) must be keys with an optional value (<key>[=<value>])
-                        separated by newlines.
-                      type: string
                   required:
                   - name
                   type: object
diff --git a/deployment/components/worker-config/nfd-worker.conf.example b/deployment/components/worker-config/nfd-worker.conf.example
index 2a5d2961b..11d43450d 100644
--- a/deployment/components/worker-config/nfd-worker.conf.example
+++ b/deployment/components/worker-config/nfd-worker.conf.example
@@ -202,53 +202,3 @@
 #              matchExpressions:
 #                vendor: {op: In, value: ["8086"]}
 #                class: {op: In, value: ["02"]}
-#
-#    # The following features demonstreate label templating capabilities
-#    - name: "my-template-test"
-#      labelsTemplate: |
-#        {{ range .system.osrelease }}my-system-feature.{{ .Name }}={{ .Value }}
-#        {{ end }}
-#      matchFeatures:
-#        - feature: system.osrelease
-#          matchExpressions:
-#            ID: {op: InRegexp, value: ["^open.*"]}
-#            VERSION_ID.major: {op: In, value: ["13", "15"]}
-#
-#    - name: "my-template-test-2"
-#      labelsTemplate: |
-#        {{ range .pci.device }}my-pci-device.{{ .class }}-{{ .device }}=with-cpuid
-#        {{ end }}
-#      matchFeatures:
-#        - feature: pci.device
-#          matchExpressions:
-#            class: {op: InRegexp, value: ["^06"]}
-#            vendor: ["8086"]
-#        - feature: cpu.cpuid
-#          matchExpressions:
-#            AVX: {op: Exists}
-#
-#    # The following examples demonstrate vars field and back-referencing
-#    # previous labels and vars
-#    - name: "my dummy kernel rule"
-#      labels:
-#        "my.kernel.feature": "true"
-#      matchFeatures:
-#        - feature: kernel.version
-#          matchExpressions:
-#            major: {op: Gt, value: ["2"]}
-#
-#    - name: "my dummy rule with no labels"
-#      vars:
-#        "my.dummy.var": "1"
-#      matchFeatures:
-#        - feature: cpu.cpuid
-#          matchExpressions: {}
-#
-#    - name: "my rule using backrefs"
-#      labels:
-#        "my.backref.feature": "true"
-#      matchFeatures:
-#        - feature: rule.matched
-#          matchExpressions:
-#            my.kernel.feature: {op: IsTrue}
-#            my.dummy.var: {op: Gt, value: ["0"]}
diff --git a/deployment/helm/node-feature-discovery/manifests/nodefeaturerule-crd.yaml b/deployment/helm/node-feature-discovery/manifests/nodefeaturerule-crd.yaml
index cd9b48bf4..1f273e6c7 100644
--- a/deployment/helm/node-feature-discovery/manifests/nodefeaturerule-crd.yaml
+++ b/deployment/helm/node-feature-discovery/manifests/nodefeaturerule-crd.yaml
@@ -48,12 +48,6 @@ spec:
                         type: string
                       description: Labels to create if the rule matches.
                       type: object
-                    labelsTemplate:
-                      description: LabelsTemplate specifies a template to expand for
-                        dynamically generating multiple labels. Data (after template
-                        expansion) must be keys with an optional value (<key>[=<value>])
-                        separated by newlines.
-                      type: string
                     matchAny:
                       description: MatchAny specifies a list of matchers one of which
                         must match.
@@ -188,21 +182,6 @@ spec:
                     name:
                       description: Name of the rule.
                       type: string
-                    vars:
-                      additionalProperties:
-                        type: string
-                      description: Vars is the variables to store if the rule matches.
-                        Variables do not directly inflict any changes in the node
-                        object. However, they can be referenced from other rules enabling
-                        more complex rule hierarchies, without exposing intermediary
-                        output values as labels.
-                      type: object
-                    varsTemplate:
-                      description: VarsTemplate specifies a template to expand for
-                        dynamically generating multiple variables. Data (after template
-                        expansion) must be keys with an optional value (<key>[=<value>])
-                        separated by newlines.
-                      type: string
                   required:
                   - name
                   type: object
diff --git a/deployment/helm/node-feature-discovery/values.yaml b/deployment/helm/node-feature-discovery/values.yaml
index b7b50142b..7474ebdd6 100644
--- a/deployment/helm/node-feature-discovery/values.yaml
+++ b/deployment/helm/node-feature-discovery/values.yaml
@@ -291,56 +291,6 @@ worker:
     #              matchExpressions:
     #                vendor: {op: In, value: ["8086"]}
     #                class: {op: In, value: ["02"]}
-    #
-    #    # The following features demonstreate label templating capabilities
-    #    - name: "my-template-test"
-    #      labelsTemplate: |
-    #        {{ range .system.osrelease }}my-system-feature.{{ .Name }}={{ .Value }}
-    #        {{ end }}
-    #      matchFeatures:
-    #        - feature: system.osrelease
-    #          matchExpressions:
-    #            ID: {op: InRegexp, value: ["^open.*"]}
-    #            VERSION_ID.major: {op: In, value: ["13", "15"]}
-    #
-    #    - name: "my-template-test-2"
-    #      labelsTemplate: |
-    #        {{ range .pci.device }}my-pci-device.{{ .class }}-{{ .device }}=with-cpuid
-    #        {{ end }}
-    #      matchFeatures:
-    #        - feature: pci.device
-    #          matchExpressions:
-    #            class: {op: InRegexp, value: ["^06"]}
-    #            vendor: ["8086"]
-    #        - feature: cpu.cpuid
-    #          matchExpressions:
-    #            AVX: {op: Exists}
-    #
-    #    # The following examples demonstrate vars field and back-referencing
-    #    # previous labels and vars
-    #    - name: "my dummy kernel rule"
-    #      labels:
-    #        "my.kernel.feature": "true"
-    #      matchFeatures:
-    #        - feature: kernel.version
-    #          matchExpressions:
-    #            major: {op: Gt, value: ["2"]}
-    #
-    #    - name: "my dummy rule with no labels"
-    #      vars:
-    #        "my.dummy.var": "1"
-    #      matchFeatures:
-    #        - feature: cpu.cpuid
-    #          matchExpressions: {}
-    #
-    #    - name: "my rule using backrefs"
-    #      labels:
-    #        "my.backref.feature": "true"
-    #      matchFeatures:
-    #        - feature: rule.matched
-    #          matchExpressions:
-    #            my.kernel.feature: {op: IsTrue}
-    #            my.dummy.var: {op: Gt, value: ["0"]}
 ### <NFD-WORKER-CONF-END-DO-NOT-REMOVE>
 
   podSecurityContext: {}
diff --git a/docs/get-started/features.md b/docs/get-started/features.md
index f1a3999e4..b74c75994 100644
--- a/docs/get-started/features.md
+++ b/docs/get-started/features.md
@@ -78,7 +78,6 @@ The **cpu** feature source supports the following labels:
 |                         | RDTL3CA            | Intel L3 Cache Allocation Technology
 |                         | RDTL2CA            | Intel L2 Cache Allocation Technology
 |                         | RDTMBA             | Intel Memory Bandwidth Allocation (MBA) Technology
-| sgx                     | enabled            | Set to 'true' if Intel SGX is enabled in BIOS (based a non-zero sum value of SGX EPC section sizes).
 
 The (sub-)set of CPUID attributes to publish is configurable via the
 `attributeBlacklist` and `attributeWhitelist` cpuid options of the cpu source.
diff --git a/pkg/api/feature/feature.go b/pkg/api/feature/feature.go
index 72340ee58..d90339e22 100644
--- a/pkg/api/feature/feature.go
+++ b/pkg/api/feature/feature.go
@@ -50,18 +50,3 @@ func NewInstanceFeature(attrs map[string]string) *InstanceFeature {
 	}
 	return &InstanceFeature{Attributes: attrs}
 }
-
-// InsertFeatureValues inserts new values into a specific feature.
-func InsertFeatureValues(f Features, domain, feature string, values map[string]string) {
-	if _, ok := f[domain]; !ok {
-		f[domain] = NewDomainFeatures()
-	}
-	if _, ok := f[domain].Values[feature]; !ok {
-		f[domain].Values[feature] = NewValueFeatures(values)
-		return
-	}
-
-	for k, v := range values {
-		f[domain].Values[feature].Elements[k] = v
-	}
-}
diff --git a/pkg/apis/nfd/v1alpha1/expression.go b/pkg/apis/nfd/v1alpha1/expression.go
index 0eaf49c0d..1591b88d2 100644
--- a/pkg/apis/nfd/v1alpha1/expression.go
+++ b/pkg/apis/nfd/v1alpha1/expression.go
@@ -314,113 +314,44 @@ func (m *MatchExpression) UnmarshalJSON(data []byte) error {
 
 // MatchKeys evaluates the MatchExpressionSet against a set of keys.
 func (m *MatchExpressionSet) MatchKeys(keys map[string]feature.Nil) (bool, error) {
-	v, err := m.MatchGetKeys(keys)
-	return v != nil, err
-}
-
-// MatchedKey holds one matched key.
-type MatchedKey struct {
-	Name string
-}
-
-// MatchGetKeys evaluates the MatchExpressionSet against a set of keys and
-// returns all matched keys or nil if no match was found. Special case of an
-// empty MatchExpressionSet returns all existing keys are returned. Note that
-// an empty MatchExpressionSet and an empty set of keys returns an empty slice
-// which is not nil and is treated as a match.
-func (m *MatchExpressionSet) MatchGetKeys(keys map[string]feature.Nil) ([]MatchedKey, error) {
-	ret := make([]MatchedKey, 0, m.Len())
-
-	// An empty rule matches all existing keys
-	if m.Len() == 0 {
-		for n := range keys {
-			ret = append(ret, MatchedKey{Name: n})
-		}
-	}
-
 	for n, e := range (*m).Expressions {
 		match, err := e.MatchKeys(n, keys)
 		if err != nil {
-			return nil, err
+			return false, err
 		}
 		if !match {
-			return nil, nil
+			return false, nil
 		}
-		ret = append(ret, MatchedKey{Name: n})
 	}
-	// Sort for reproducible output
-	sort.Slice(ret, func(i, j int) bool { return ret[i].Name < ret[j].Name })
-	return ret, nil
+	return true, nil
 }
 
 // MatchValues evaluates the MatchExpressionSet against a set of key-value pairs.
 func (m *MatchExpressionSet) MatchValues(values map[string]string) (bool, error) {
-	v, err := m.MatchGetValues(values)
-	return v != nil, err
-}
-
-// MatchedValue holds one matched key-value pair.
-type MatchedValue struct {
-	Name  string
-	Value string
-}
-
-// MatchGetValues evaluates the MatchExpressionSet against a set of key-value
-// pairs and returns all matched key-value pairs. Special case of an empty
-// MatchExpressionSet returns all existing key-value pairs. Note that an empty
-// MatchExpressionSet and an empty set of values returns an empty non-nil map
-// which is treated as a match.
-func (m *MatchExpressionSet) MatchGetValues(values map[string]string) ([]MatchedValue, error) {
-	ret := make([]MatchedValue, 0, m.Len())
-
-	// An empty rule matches all existing values
-	if m.Len() == 0 {
-		for n, v := range values {
-			ret = append(ret, MatchedValue{Name: n, Value: v})
-		}
-	}
-
 	for n, e := range (*m).Expressions {
 		match, err := e.MatchValues(n, values)
 		if err != nil {
-			return nil, err
+			return false, err
 		}
 		if !match {
-			return nil, nil
+			return false, nil
 		}
-		ret = append(ret, MatchedValue{Name: n, Value: values[n]})
 	}
-	// Sort for reproducible output
-	sort.Slice(ret, func(i, j int) bool { return ret[i].Name < ret[j].Name })
-	return ret, nil
+	return true, nil
 }
 
 // MatchInstances evaluates the MatchExpressionSet against a set of instance
 // features, each of which is an individual set of key-value pairs
 // (attributes).
 func (m *MatchExpressionSet) MatchInstances(instances []feature.InstanceFeature) (bool, error) {
-	v, err := m.MatchGetInstances(instances)
-	return len(v) > 0, err
-}
-
-// MatchedInstance holds one matched Instance.
-type MatchedInstance map[string]string
-
-// MatchGetInstances evaluates the MatchExpressionSet against a set of instance
-// features, each of which is an individual set of key-value pairs
-// (attributes). A slice containing all matching instances is returned. An
-// empty (non-nil) slice is returned if no matching instances were found.
-func (m *MatchExpressionSet) MatchGetInstances(instances []feature.InstanceFeature) ([]MatchedInstance, error) {
-	ret := []MatchedInstance{}
-
 	for _, i := range instances {
 		if match, err := m.MatchValues(i.Attributes); err != nil {
-			return nil, err
+			return false, err
 		} else if match {
-			ret = append(ret, i.Attributes)
+			return true, nil
 		}
 	}
-	return ret, nil
+	return false, nil
 }
 
 // UnmarshalJSON implements the Unmarshaler interface of "encoding/json".
diff --git a/pkg/apis/nfd/v1alpha1/rule.go b/pkg/apis/nfd/v1alpha1/rule.go
index 0f6f81cd7..fcb07a13a 100644
--- a/pkg/apis/nfd/v1alpha1/rule.go
+++ b/pkg/apis/nfd/v1alpha1/rule.go
@@ -17,149 +17,57 @@ limitations under the License.
 package v1alpha1
 
 import (
-	"bytes"
-	"fmt"
 	"strings"
-	"text/template"
 
-	"k8s.io/klog/v2"
+	"fmt"
 
 	"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
-	"sigs.k8s.io/node-feature-discovery/pkg/utils"
 )
 
-// RuleOutput contains the output out rule execution.
-// +k8s:deepcopy-gen=false
-type RuleOutput struct {
-	Labels map[string]string
-	Vars   map[string]string
-}
-
 // Execute the rule against a set of input features.
-func (r *Rule) Execute(features feature.Features) (RuleOutput, error) {
-	labels := make(map[string]string)
-	vars := make(map[string]string)
-
+func (r *Rule) Execute(features map[string]*feature.DomainFeatures) (map[string]string, error) {
 	if len(r.MatchAny) > 0 {
 		// Logical OR over the matchAny matchers
 		matched := false
 		for _, matcher := range r.MatchAny {
 			if m, err := matcher.match(features); err != nil {
-				return RuleOutput{}, err
-			} else if m != nil {
+				return nil, err
+			} else if m {
 				matched = true
-				utils.KlogDump(4, "matches for matchAny "+r.Name, "  ", m)
-
-				if r.labelsTemplate == nil {
-					// No templating so we stop here (further matches would just
-					// produce the same labels)
-					break
-				}
-				if err := r.executeLabelsTemplate(m, labels); err != nil {
-					return RuleOutput{}, err
-				}
-				if err := r.executeVarsTemplate(m, vars); err != nil {
-					return RuleOutput{}, err
-				}
+				break
 			}
 		}
 		if !matched {
-			klog.V(2).Infof("rule %q did not match", r.Name)
-			return RuleOutput{}, nil
+			return nil, nil
 		}
 	}
 
 	if len(r.MatchFeatures) > 0 {
 		if m, err := r.MatchFeatures.match(features); err != nil {
-			return RuleOutput{}, err
-		} else if m == nil {
-			klog.V(2).Infof("rule %q did not match", r.Name)
-			return RuleOutput{}, nil
-		} else {
-			utils.KlogDump(4, "matches for matchFeatures "+r.Name, "  ", m)
-			if err := r.executeLabelsTemplate(m, labels); err != nil {
-				return RuleOutput{}, err
-			}
-			if err := r.executeVarsTemplate(m, vars); err != nil {
-				return RuleOutput{}, err
-			}
+			return nil, err
+		} else if !m {
+			return nil, nil
 		}
 	}
 
+	labels := make(map[string]string, len(r.Labels))
 	for k, v := range r.Labels {
 		labels[k] = v
 	}
-	for k, v := range r.Vars {
-		vars[k] = v
-	}
-
-	ret := RuleOutput{Labels: labels, Vars: vars}
-	utils.KlogDump(2, fmt.Sprintf("rule %q matched with: ", r.Name), "  ", ret)
-
-	return ret, nil
-}
-
-func (r *Rule) executeLabelsTemplate(in matchedFeatures, out map[string]string) error {
-	if r.LabelsTemplate == "" {
-		return nil
-	}
-
-	if r.labelsTemplate == nil {
-		t, err := newTemplateHelper(r.LabelsTemplate)
-		if err != nil {
-			return fmt.Errorf("failed to parse LabelsTemplate: %w", err)
-		}
-		r.labelsTemplate = t
-	}
-
-	labels, err := r.labelsTemplate.expandMap(in)
-	if err != nil {
-		return fmt.Errorf("failed to expand LabelsTemplate: %w", err)
-	}
-	for k, v := range labels {
-		out[k] = v
-	}
-	return nil
-}
-
-func (r *Rule) executeVarsTemplate(in matchedFeatures, out map[string]string) error {
-	if r.VarsTemplate == "" {
-		return nil
-	}
-	if r.varsTemplate == nil {
-		t, err := newTemplateHelper(r.VarsTemplate)
-		if err != nil {
-			return err
-		}
-		r.varsTemplate = t
-	}
 
-	vars, err := r.varsTemplate.expandMap(in)
-	if err != nil {
-		return err
-	}
-	for k, v := range vars {
-		out[k] = v
-	}
-	return nil
+	return labels, nil
 }
 
-type matchedFeatures map[string]domainMatchedFeatures
-
-type domainMatchedFeatures map[string]interface{}
-
-func (e *MatchAnyElem) match(features map[string]*feature.DomainFeatures) (matchedFeatures, error) {
+func (e *MatchAnyElem) match(features map[string]*feature.DomainFeatures) (bool, error) {
 	return e.MatchFeatures.match(features)
 }
 
-func (m *FeatureMatcher) match(features map[string]*feature.DomainFeatures) (matchedFeatures, error) {
-	ret := make(matchedFeatures, len(*m))
-
+func (m *FeatureMatcher) match(features map[string]*feature.DomainFeatures) (bool, error) {
 	// Logical AND over the terms
 	for _, term := range *m {
 		split := strings.SplitN(term.Feature, ".", 2)
 		if len(split) != 2 {
-			return nil, fmt.Errorf("invalid feature %q: must be <domain>.<feature>", term.Feature)
+			return false, fmt.Errorf("invalid selector %q: must be <domain>.<feature>", term.Feature)
 		}
 		domain := split[0]
 		// Ignore case
@@ -167,100 +75,26 @@ func (m *FeatureMatcher) match(features map[string]*feature.DomainFeatures) (mat
 
 		domainFeatures, ok := features[domain]
 		if !ok {
-			return nil, fmt.Errorf("unknown feature source/domain %q", domain)
-		}
-
-		if _, ok := ret[domain]; !ok {
-			ret[domain] = make(domainMatchedFeatures)
+			return false, fmt.Errorf("unknown feature source/domain %q", domain)
 		}
 
 		var m bool
-		var e error
+		var err error
 		if f, ok := domainFeatures.Keys[featureName]; ok {
-			v, err := term.MatchExpressions.MatchGetKeys(f.Elements)
-			m = len(v) > 0
-			e = err
-			ret[domain][featureName] = v
+			m, err = term.MatchExpressions.MatchKeys(f.Elements)
 		} else if f, ok := domainFeatures.Values[featureName]; ok {
-			v, err := term.MatchExpressions.MatchGetValues(f.Elements)
-			m = len(v) > 0
-			e = err
-			ret[domain][featureName] = v
+			m, err = term.MatchExpressions.MatchValues(f.Elements)
 		} else if f, ok := domainFeatures.Instances[featureName]; ok {
-			v, err := term.MatchExpressions.MatchGetInstances(f.Elements)
-			m = len(v) > 0
-			e = err
-			ret[domain][featureName] = v
+			m, err = term.MatchExpressions.MatchInstances(f.Elements)
 		} else {
-			return nil, fmt.Errorf("%q feature of source/domain %q not available", featureName, domain)
+			return false, fmt.Errorf("%q feature of source/domain %q not available", featureName, domain)
 		}
 
-		if e != nil {
-			return nil, e
+		if err != nil {
+			return false, err
 		} else if !m {
-			return nil, nil
-		}
-	}
-	return ret, nil
-}
-
-type templateHelper struct {
-	template *template.Template
-}
-
-func newTemplateHelper(name string) (*templateHelper, error) {
-	tmpl, err := template.New("").Option("missingkey=error").Parse(name)
-	if err != nil {
-		return nil, fmt.Errorf("invalid template: %w", err)
-	}
-	return &templateHelper{template: tmpl}, nil
-}
-
-// DeepCopy is a stub to augment the auto-generated code
-func (in *templateHelper) DeepCopy() *templateHelper {
-	if in == nil {
-		return nil
-	}
-	out := new(templateHelper)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is a stub to augment the auto-generated code
-func (in *templateHelper) DeepCopyInto(out *templateHelper) {
-	// HACK: just re-use the template
-	out.template = in.template
-}
-
-func (h *templateHelper) execute(data interface{}) (string, error) {
-	var tmp bytes.Buffer
-	if err := h.template.Execute(&tmp, data); err != nil {
-		return "", err
-	}
-	return tmp.String(), nil
-}
-
-// expandMap is a helper for expanding a template in to a map of strings. Data
-// after executing the template is expexted to be key=value pairs separated by
-// newlines.
-func (h *templateHelper) expandMap(data interface{}) (map[string]string, error) {
-	expanded, err := h.execute(data)
-	if err != nil {
-		return nil, err
-	}
-
-	// Split out individual key-value pairs
-	out := make(map[string]string)
-	for _, item := range strings.Split(expanded, "\n") {
-		// Remove leading/trailing whitespace and skip empty lines
-		if trimmed := strings.TrimSpace(item); trimmed != "" {
-			split := strings.SplitN(trimmed, "=", 2)
-			if len(split) == 1 {
-				return nil, fmt.Errorf("missing value in expanded template line %q, (format must be '<key>=<value>')", trimmed)
-			} else {
-				out[split[0]] = split[1]
-			}
+			return false, nil
 		}
 	}
-	return out, nil
+	return true, nil
 }
diff --git a/pkg/apis/nfd/v1alpha1/types.go b/pkg/apis/nfd/v1alpha1/types.go
index d7bf89e7d..b5f59c90e 100644
--- a/pkg/apis/nfd/v1alpha1/types.go
+++ b/pkg/apis/nfd/v1alpha1/types.go
@@ -59,25 +59,6 @@ type Rule struct {
 	// +optional
 	Labels map[string]string `json:"labels"`
 
-	// LabelsTemplate specifies a template to expand for dynamically generating
-	// multiple labels. Data (after template expansion) must be keys with an
-	// optional value (<key>[=<value>]) separated by newlines.
-	// +optional
-	LabelsTemplate string `json:"labelsTemplate"`
-
-	// Vars is the variables to store if the rule matches. Variables do not
-	// directly inflict any changes in the node object. However, they can be
-	// referenced from other rules enabling more complex rule hierarchies,
-	// without exposing intermediary output values as labels.
-	// +optional
-	Vars map[string]string `json:"vars"`
-
-	// VarsTemplate specifies a template to expand for dynamically generating
-	// multiple variables. Data (after template expansion) must be keys with an
-	// optional value (<key>[=<value>]) separated by newlines.
-	// +optional
-	VarsTemplate string `json:"varsTemplate"`
-
 	// MatchFeatures specifies a set of matcher terms all of which must match.
 	// +optional
 	MatchFeatures FeatureMatcher `json:"matchFeatures"`
@@ -85,10 +66,6 @@ type Rule struct {
 	// MatchAny specifies a list of matchers one of which must match.
 	// +optional
 	MatchAny []MatchAnyElem `json:"matchAny"`
-
-	// private helpers/cache for handling golang templates
-	labelsTemplate *templateHelper `json:"-"`
-	varsTemplate   *templateHelper `json:"-"`
 }
 
 // MatchAnyElem specifies one sub-matcher of MatchAny.
@@ -190,12 +167,3 @@ const (
 	// expression must not have any values.
 	MatchIsFalse MatchOp = "IsFalse"
 )
-
-const (
-	// RuleBackrefDomain is the special feature domain for backreferencing
-	// output of preceding rules.
-	RuleBackrefDomain = "rule"
-	// RuleBackrefFeature is the special feature name for backreferencing
-	// output of preceding rules.
-	RuleBackrefFeature = "matched"
-)
diff --git a/pkg/apis/nfd/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/nfd/v1alpha1/zz_generated.deepcopy.go
index ece20769a..6166a118e 100644
--- a/pkg/apis/nfd/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/nfd/v1alpha1/zz_generated.deepcopy.go
@@ -167,57 +167,6 @@ func (in MatchValue) DeepCopy() MatchValue {
 	return *out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in MatchedInstance) DeepCopyInto(out *MatchedInstance) {
-	{
-		in := &in
-		*out = make(MatchedInstance, len(*in))
-		for key, val := range *in {
-			(*out)[key] = val
-		}
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchedInstance.
-func (in MatchedInstance) DeepCopy() MatchedInstance {
-	if in == nil {
-		return nil
-	}
-	out := new(MatchedInstance)
-	in.DeepCopyInto(out)
-	return *out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *MatchedKey) DeepCopyInto(out *MatchedKey) {
-	*out = *in
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchedKey.
-func (in *MatchedKey) DeepCopy() *MatchedKey {
-	if in == nil {
-		return nil
-	}
-	out := new(MatchedKey)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *MatchedValue) DeepCopyInto(out *MatchedValue) {
-	*out = *in
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchedValue.
-func (in *MatchedValue) DeepCopy() *MatchedValue {
-	if in == nil {
-		return nil
-	}
-	out := new(MatchedValue)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *NodeFeatureRule) DeepCopyInto(out *NodeFeatureRule) {
 	*out = *in
@@ -308,13 +257,6 @@ func (in *Rule) DeepCopyInto(out *Rule) {
 			(*out)[key] = val
 		}
 	}
-	if in.Vars != nil {
-		in, out := &in.Vars, &out.Vars
-		*out = make(map[string]string, len(*in))
-		for key, val := range *in {
-			(*out)[key] = val
-		}
-	}
 	if in.MatchFeatures != nil {
 		in, out := &in.MatchFeatures, &out.MatchFeatures
 		*out = make(FeatureMatcher, len(*in))
@@ -329,14 +271,6 @@ func (in *Rule) DeepCopyInto(out *Rule) {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
-	if in.labelsTemplate != nil {
-		in, out := &in.labelsTemplate, &out.labelsTemplate
-		*out = (*in).DeepCopy()
-	}
-	if in.varsTemplate != nil {
-		in, out := &in.varsTemplate, &out.varsTemplate
-		*out = (*in).DeepCopy()
-	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rule.
diff --git a/pkg/nfd-master/nfd-master.go b/pkg/nfd-master/nfd-master.go
index 519a4518a..bffbcce2e 100644
--- a/pkg/nfd-master/nfd-master.go
+++ b/pkg/nfd-master/nfd-master.go
@@ -43,9 +43,7 @@ import (
 	restclient "k8s.io/client-go/rest"
 	"k8s.io/klog/v2"
 
-	"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
 	"sigs.k8s.io/node-feature-discovery/pkg/apihelper"
-	nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
 	pb "sigs.k8s.io/node-feature-discovery/pkg/labeler"
 	topologypb "sigs.k8s.io/node-feature-discovery/pkg/topologyupdater"
 	"sigs.k8s.io/node-feature-discovery/pkg/utils"
@@ -501,10 +499,6 @@ func (m *nfdMaster) crLabels(r *pb.SetLabelsRequest) map[string]string {
 
 	l := make(map[string]string)
 	ruleSpecs, err := m.nfdController.lister.List(labels.Everything())
-	sort.Slice(ruleSpecs, func(i, j int) bool {
-		return ruleSpecs[i].Name < ruleSpecs[j].Name
-	})
-
 	if err != nil {
 		klog.Errorf("failed to list LabelRule resources: %w", err)
 		return nil
@@ -525,14 +519,10 @@ func (m *nfdMaster) crLabels(r *pb.SetLabelsRequest) map[string]string {
 				klog.Errorf("failed to process Rule %q: %w", rule.Name, err)
 				continue
 			}
-
-			for k, v := range ruleOut.Labels {
+			for k, v := range ruleOut {
 				l[k] = v
 			}
-
-			// Feed back rule output to features map for subsequent rules to match
-			feature.InsertFeatureValues(r.Features, nfdv1alpha1.RuleBackrefDomain, nfdv1alpha1.RuleBackrefFeature, ruleOut.Labels)
-			feature.InsertFeatureValues(r.Features, nfdv1alpha1.RuleBackrefDomain, nfdv1alpha1.RuleBackrefFeature, ruleOut.Vars)
+			utils.KlogDump(1, "", "  ", ruleOut)
 		}
 	}
 
diff --git a/source/cpu/cpu.go b/source/cpu/cpu.go
index 6a7ec2b02..ebd2322e8 100644
--- a/source/cpu/cpu.go
+++ b/source/cpu/cpu.go
@@ -34,7 +34,6 @@ const (
 	CstateFeature   = "cstate"
 	PstateFeature   = "pstate"
 	RdtFeature      = "rdt"
-	SgxFeature      = "sgx"
 	SstFeature      = "sst"
 	TopologyFeature = "topology"
 )
@@ -154,11 +153,6 @@ func (s *cpuSource) GetLabels() (source.FeatureLabels, error) {
 		labels["rdt."+k] = true
 	}
 
-	// SGX
-	for k, v := range features.Values[SgxFeature].Elements {
-		labels["sgx."+k] = v
-	}
-
 	// SST
 	for k, v := range features.Values[SstFeature].Elements {
 		labels["power.sst_"+k] = v
@@ -197,9 +191,6 @@ func (s *cpuSource) Discover() error {
 	// Detect RDT features
 	s.features.Keys[RdtFeature] = feature.NewKeyFeatures(discoverRDT()...)
 
-	// Detect SGX features
-	s.features.Values[SgxFeature] = feature.NewValueFeatures(discoverSGX())
-
 	// Detect SST features
 	s.features.Values[SstFeature] = feature.NewValueFeatures(discoverSST())
 
diff --git a/source/cpu/sgx_amd64.go b/source/cpu/sgx_amd64.go
deleted file mode 100644
index a94d5bdb5..000000000
--- a/source/cpu/sgx_amd64.go
+++ /dev/null
@@ -1,46 +0,0 @@
-//go:build amd64
-// +build amd64
-
-/*
-Copyright 2021 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package cpu
-
-import (
-	"github.com/klauspost/cpuid/v2"
-)
-
-func discoverSGX() map[string]string {
-	var epcSize uint64
-	sgx := make(map[string]string)
-
-	if cpuid.CPU.SGX.Available {
-		for _, s := range cpuid.CPU.SGX.EPCSections {
-			epcSize += s.EPCSize
-		}
-	}
-
-	// Set to 'true' based a non-zero sum value of SGX EPC section sizes. The
-	// kernel checks for IA32_FEATURE_CONTROL.SGX_ENABLE MSR bit but we can't
-	// do that as a normal user. Typically the BIOS, when enabling SGX,
-	// allocates "Processor Reserved Memory" for SGX EPC so we rely on > 0
-	// size here to set "SGX = enabled".
-	if epcSize > 0 {
-		sgx["enabled"] = "true"
-	}
-
-	return sgx
-}
diff --git a/source/cpu/sgx_stub.go b/source/cpu/sgx_stub.go
deleted file mode 100644
index 99e4c046b..000000000
--- a/source/cpu/sgx_stub.go
+++ /dev/null
@@ -1,24 +0,0 @@
-//go:build !amd64
-// +build !amd64
-
-/*
-Copyright 2021 The Kubernetes Authors.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package cpu
-
-func discoverSGX() map[string]string {
-	return nil
-}
diff --git a/source/custom/custom.go b/source/custom/custom.go
index 48c0de50b..cde8d167c 100644
--- a/source/custom/custom.go
+++ b/source/custom/custom.go
@@ -124,34 +124,31 @@ func (s *customSource) GetLabels() (source.FeatureLabels, error) {
 			continue
 		}
 
-		for n, v := range ruleOut.Labels {
+		for n, v := range ruleOut {
 			labels[n] = v
 		}
-		// Feed back rule output to features map for subsequent rules to match
-		feature.InsertFeatureValues(domainFeatures, nfdv1alpha1.RuleBackrefDomain, nfdv1alpha1.RuleBackrefFeature, ruleOut.Labels)
-		feature.InsertFeatureValues(domainFeatures, nfdv1alpha1.RuleBackrefDomain, nfdv1alpha1.RuleBackrefFeature, ruleOut.Vars)
 	}
 	return labels, nil
 }
 
-func (r *CustomRule) execute(features map[string]*feature.DomainFeatures) (nfdv1alpha1.RuleOutput, error) {
+func (r *CustomRule) execute(features map[string]*feature.DomainFeatures) (map[string]string, error) {
 	if r.LegacyRule != nil {
 		ruleOut, err := r.LegacyRule.execute(features)
 		if err != nil {
-			return nfdv1alpha1.RuleOutput{}, fmt.Errorf("failed to execute legacy rule %s: %w", r.LegacyRule.Name, err)
+			return nil, fmt.Errorf("failed to execute legacy rule %s: %w", r.LegacyRule.Name, err)
 		}
-		return nfdv1alpha1.RuleOutput{Labels: ruleOut}, nil
+		return ruleOut, err
 	}
 
 	if r.Rule != nil {
 		ruleOut, err := r.Rule.Execute(features)
 		if err != nil {
-			return ruleOut, fmt.Errorf("failed to execute rule %s: %w", r.Rule.Name, err)
+			return nil, fmt.Errorf("failed to execute rule %s: %w", r.Rule.Name, err)
 		}
-		return ruleOut, nil
+		return ruleOut, err
 	}
 
-	return nfdv1alpha1.RuleOutput{}, fmt.Errorf("BUG: an empty rule, this really should not happen")
+	return nil, fmt.Errorf("BUG: an empty rule, this really should not happen")
 }
 
 func (r *LegacyRule) execute(features map[string]*feature.DomainFeatures) (map[string]string, error) {
diff --git a/source/kernel/kernel.go b/source/kernel/kernel.go
index ad79ed62c..a24fba650 100644
--- a/source/kernel/kernel.go
+++ b/source/kernel/kernel.go
@@ -105,8 +105,8 @@ func (s *kernelSource) GetLabels() (source.FeatureLabels, error) {
 		}
 	}
 
-	if enabled, ok := features.Values[SelinuxFeature].Elements["enabled"]; ok && enabled == "true" {
-		labels["selinux.enabled"] = "true"
+	for k, v := range features.Values[SelinuxFeature].Elements {
+		labels[SelinuxFeature+"."+k] = v
 	}
 
 	return labels, nil
diff --git a/source/memory/memory.go b/source/memory/memory.go
index 27670e830..823ad41fb 100644
--- a/source/memory/memory.go
+++ b/source/memory/memory.go
@@ -60,7 +60,7 @@ func (s *memorySource) GetLabels() (source.FeatureLabels, error) {
 	features := s.GetFeatures()
 
 	// NUMA
-	if isNuma, ok := features.Values[NumaFeature].Elements["is_numa"]; ok && isNuma == "true" {
+	if len(features.Values[NumaFeature].Elements) > 0 {
 		labels["numa"] = true
 	}
 
@@ -118,10 +118,7 @@ func detectNuma() (map[string]string, error) {
 		return nil, fmt.Errorf("failed to list numa nodes: %w", err)
 	}
 
-	return map[string]string{
-		"is_numa":    strconv.FormatBool(len(nodes) > 1),
-		"node_count": strconv.Itoa(len(nodes)),
-	}, nil
+	return map[string]string{"node_count": strconv.Itoa(len(nodes))}, nil
 }
 
 // detectNv detects NVDIMM devices


Test patch:
diff --git a/pkg/apis/nfd/v1alpha1/expression_test.go b/pkg/apis/nfd/v1alpha1/expression_test.go
index c84a5f3c7..33682a3b1 100644
--- a/pkg/apis/nfd/v1alpha1/expression_test.go
+++ b/pkg/apis/nfd/v1alpha1/expression_test.go
@@ -291,29 +291,23 @@ func TestMatchValues(t *testing.T) {
 
 func TestMESMatchKeys(t *testing.T) {
 	type I = map[string]feature.Nil
-	type MK = api.MatchedKey
-	type O = []MK
 	type TC struct {
 		mes    string
 		input  I
-		output O
 		result BoolAssertionFuncf
 		err    ValueAssertionFuncf
 	}
 
 	tcs := []TC{
-		{output: O{}, result: assert.Truef, err: assert.Nilf},
+		{result: assert.Truef, err: assert.Nilf},
 
-		{input: I{}, output: O{}, result: assert.Truef, err: assert.Nilf},
-
-		{input: I{"foo": {}}, output: O{MK{Name: "foo"}}, result: assert.Truef, err: assert.Nilf},
+		{input: I{"foo": {}}, result: assert.Truef, err: assert.Nilf},
 
 		{mes: `
 foo: { op: DoesNotExist }
 bar: { op: Exists }
 `,
-			input:  I{"bar": {}, "baz": {}, "buzz": {}},
-			output: O{MK{Name: "bar"}, MK{Name: "foo"}},
+			input:  I{"bar": {}, "baz": {}},
 			result: assert.Truef, err: assert.Nilf},
 
 		{mes: `
@@ -321,7 +315,6 @@ foo: { op: DoesNotExist }
 bar: { op: Exists }
 `,
 			input:  I{"foo": {}, "bar": {}, "baz": {}},
-			output: nil,
 			result: assert.Falsef, err: assert.Nilf},
 
 		{mes: `
@@ -329,7 +322,6 @@ foo: { op: In, value: ["bar"] }
 bar: { op: Exists }
 `,
 			input:  I{"bar": {}, "baz": {}},
-			output: nil,
 			result: assert.Falsef, err: assert.NotNilf},
 	}
 
@@ -339,10 +331,6 @@ bar: { op: Exists }
 			t.Fatalf("failed to parse data of test case #%d (%v): %v", i, tc, err)
 		}
 
-		out, err := mes.MatchGetKeys(tc.input)
-		assert.Equalf(t, tc.output, out, "test case #%d (%v) failed", i, tc)
-		tc.err(t, err, "test case #%d (%v) failed", i, tc)
-
 		res, err := mes.MatchKeys(tc.input)
 		tc.result(t, res, "test case #%d (%v) failed", i, tc)
 		tc.err(t, err, "test case #%d (%v) failed", i, tc)
@@ -351,22 +339,17 @@ bar: { op: Exists }
 
 func TestMESMatchValues(t *testing.T) {
 	type I = map[string]string
-	type MV = api.MatchedValue
-	type O = []MV
 	type TC struct {
 		mes    string
 		input  I
-		output O
 		result BoolAssertionFuncf
 		err    ValueAssertionFuncf
 	}
 
 	tcs := []TC{
-		{output: O{}, result: assert.Truef, err: assert.Nilf},
-
-		{input: I{}, output: O{}, result: assert.Truef, err: assert.Nilf},
+		{result: assert.Truef, err: assert.Nilf},
 
-		{input: I{"foo": "bar"}, output: O{MV{Name: "foo", Value: "bar"}}, result: assert.Truef, err: assert.Nilf},
+		{input: I{"foo": "bar"}, result: assert.Truef, err: assert.Nilf},
 
 		{mes: `
 foo: { op: Exists }
@@ -381,8 +364,7 @@ foo: { op: Exists }
 bar: { op: In, value: ["val", "wal"] }
 baz: { op: Gt, value: ["10"] }
 `,
-			input:  I{"foo": "1", "bar": "val", "baz": "123", "buzz": "light"},
-			output: O{MV{Name: "bar", Value: "val"}, MV{Name: "baz", Value: "123"}, MV{Name: "foo", Value: "1"}},
+			input:  I{"foo": "1", "bar": "val", "baz": "123"},
 			result: assert.Truef, err: assert.Nilf},
 
 		{mes: `
@@ -400,10 +382,6 @@ baz: { op: Gt, value: ["10"] }
 			t.Fatalf("failed to parse data of test case #%d (%v): %v", i, tc, err)
 		}
 
-		out, err := mes.MatchGetValues(tc.input)
-		assert.Equalf(t, tc.output, out, "test case #%d (%v) failed", i, tc)
-		tc.err(t, err, "test case #%d (%v) failed", i, tc)
-
 		res, err := mes.MatchValues(tc.input)
 		tc.result(t, res, "test case #%d (%v) failed", i, tc)
 		tc.err(t, err, "test case #%d (%v) failed", i, tc)
@@ -412,30 +390,26 @@ baz: { op: Gt, value: ["10"] }
 
 func TestMESMatchInstances(t *testing.T) {
 	type I = feature.InstanceFeature
-	type MI = api.MatchedInstance
-	type O = []MI
 	type A = map[string]string
 	type TC struct {
 		mes    string
 		input  []I
-		output O
 		result BoolAssertionFuncf
 		err    ValueAssertionFuncf
 	}
 
 	tcs := []TC{
-		{output: O{}, result: assert.Falsef, err: assert.Nilf}, // nil instances -> false
+		{result: assert.Falsef, err: assert.Nilf}, // nil instances -> false
 
-		{input: []I{}, output: O{}, result: assert.Falsef, err: assert.Nilf}, // zero instances -> false
+		{input: []I{}, result: assert.Falsef, err: assert.Nilf}, // zero instances -> false
 
-		{input: []I{I{Attributes: A{}}}, output: O{A{}}, result: assert.Truef, err: assert.Nilf}, // one "empty" instance
+		{input: []I{I{Attributes: A{}}}, result: assert.Truef, err: assert.Nilf}, // one "empty" instance
 
 		{mes: `
 foo: { op: Exists }
 bar: { op: Lt, value: ["10"] }
 `,
 			input:  []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"bar": "1"}}},
-			output: O{},
 			result: assert.Falsef, err: assert.Nilf},
 
 		{mes: `
@@ -443,7 +417,6 @@ foo: { op: Exists }
 bar: { op: Lt, value: ["10"] }
 `,
 			input:  []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"foo": "2", "bar": "1"}}},
-			output: O{A{"foo": "2", "bar": "1"}},
 			result: assert.Truef, err: assert.Nilf},
 
 		{mes: `
@@ -459,10 +432,6 @@ bar: { op: Lt, value: ["10"] }
 			t.Fatalf("failed to parse data of test case #%d (%v): %v", i, tc, err)
 		}
 
-		out, err := mes.MatchGetInstances(tc.input)
-		assert.Equalf(t, tc.output, out, "test case #%d (%v) failed", i, tc)
-		tc.err(t, err, "test case #%d (%v) failed", i, tc)
-
 		res, err := mes.MatchInstances(tc.input)
 		tc.result(t, res, "test case #%d (%v) failed", i, tc)
 		tc.err(t, err, "test case #%d (%v) failed", i, tc)
diff --git a/pkg/apis/nfd/v1alpha1/rule_test.go b/pkg/apis/nfd/v1alpha1/rule_test.go
index 7a87c5da1..b4c574203 100644
--- a/pkg/apis/nfd/v1alpha1/rule_test.go
+++ b/pkg/apis/nfd/v1alpha1/rule_test.go
@@ -28,7 +28,6 @@ func TestRule(t *testing.T) {
 	r1 := Rule{Labels: map[string]string{"label-1": "", "label-2": "true"}}
 	r2 := Rule{
 		Labels: map[string]string{"label-1": "label-val-1"},
-		Vars:   map[string]string{"var-1": "var-val-1"},
 		MatchFeatures: FeatureMatcher{
 			FeatureMatcherTerm{
 				Feature: "domain-1.kf-1",
@@ -42,7 +41,7 @@ func TestRule(t *testing.T) {
 	// Test totally empty features
 	m, err := r1.Execute(f)
 	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features")
+	assert.Equal(t, r1.Labels, m, "empty matcher should have matched empty features")
 
 	_, err = r2.Execute(f)
 	assert.Error(t, err, "matching agains a missing domain should have returned an error")
@@ -53,8 +52,7 @@ func TestRule(t *testing.T) {
 
 	m, err = r1.Execute(f)
 	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features")
-	assert.Empty(t, r1.Vars, "vars should be empty")
+	assert.Equal(t, r1.Labels, m, "empty matcher should have matched empty features")
 
 	_, err = r2.Execute(f)
 	assert.Error(t, err, "matching agains a missing feature type should have returned an error")
@@ -66,11 +64,11 @@ func TestRule(t *testing.T) {
 
 	m, err = r1.Execute(f)
 	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features")
+	assert.Equal(t, r1.Labels, m, "empty matcher should have matched empty features")
 
 	m, err = r2.Execute(f)
 	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Nil(t, m.Labels, "unexpected match")
+	assert.Nil(t, m, "unexpected match")
 
 	// Test non-empty feature sets
 	d.Keys["kf-1"].Elements["key-x"] = feature.Nil{}
@@ -80,18 +78,17 @@ func TestRule(t *testing.T) {
 
 	m, err = r1.Execute(f)
 	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features")
+	assert.Equal(t, r1.Labels, m, "empty matcher should have matched empty features")
 
 	// Match "key" features
 	m, err = r2.Execute(f)
 	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Nil(t, m.Labels, "keys should not have matched")
+	assert.Nil(t, m, "keys should not have matched")
 
 	d.Keys["kf-1"].Elements["key-1"] = feature.Nil{}
 	m, err = r2.Execute(f)
 	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Equal(t, r2.Labels, m.Labels, "keys should have matched")
-	assert.Equal(t, r2.Vars, m.Vars, "vars should be present")
+	assert.Equal(t, r2.Labels, m, "keys should have matched")
 
 	// Match "value" features
 	r3 := Rule{
@@ -107,12 +104,12 @@ func TestRule(t *testing.T) {
 	}
 	m, err = r3.Execute(f)
 	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Nil(t, m.Labels, "values should not have matched")
+	assert.Nil(t, m, "values should not have matched")
 
 	d.Values["vf-1"].Elements["key-1"] = "val-1"
 	m, err = r3.Execute(f)
 	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Equal(t, r3.Labels, m.Labels, "values should have matched")
+	assert.Equal(t, r3.Labels, m, "values should have matched")
 
 	// Match "instance" features
 	r4 := Rule{
@@ -128,12 +125,12 @@ func TestRule(t *testing.T) {
 	}
 	m, err = r4.Execute(f)
 	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Nil(t, m.Labels, "instances should not have matched")
+	assert.Nil(t, m, "instances should not have matched")
 
 	d.Instances["if-1"].Elements[0].Attributes["attr-1"] = "val-1"
 	m, err = r4.Execute(f)
 	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Equal(t, r4.Labels, m.Labels, "instances should have matched")
+	assert.Equal(t, r4.Labels, m, "instances should have matched")
 
 	// Test multiple feature matchers
 	r5 := Rule{
@@ -155,12 +152,12 @@ func TestRule(t *testing.T) {
 	}
 	m, err = r5.Execute(f)
 	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Nil(t, m.Labels, "instances should not have matched")
+	assert.Nil(t, m, "instances should not have matched")
 
 	r5.MatchFeatures[0].MatchExpressions.Expressions["key-1"] = MustCreateMatchExpression(MatchIn, "val-1")
 	m, err = r5.Execute(f)
 	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Equal(t, r5.Labels, m.Labels, "instances should have matched")
+	assert.Equal(t, r5.Labels, m, "instances should have matched")
 
 	// Test MatchAny
 	r5.MatchAny = []MatchAnyElem{
@@ -177,7 +174,7 @@ func TestRule(t *testing.T) {
 	}
 	m, err = r5.Execute(f)
 	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Nil(t, m.Labels, "instances should not have matched")
+	assert.Nil(t, m, "instances should not have matched")
 
 	r5.MatchAny = append(r5.MatchAny,
 		MatchAnyElem{
@@ -193,176 +190,5 @@ func TestRule(t *testing.T) {
 	r5.MatchFeatures[0].MatchExpressions.Expressions["key-1"] = MustCreateMatchExpression(MatchIn, "val-1")
 	m, err = r5.Execute(f)
 	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Equal(t, r5.Labels, m.Labels, "instances should have matched")
-}
-
-func TestTemplating(t *testing.T) {
-	f := map[string]*feature.DomainFeatures{
-		"domain_1": &feature.DomainFeatures{
-			Keys: map[string]feature.KeyFeatureSet{
-				"kf_1": feature.KeyFeatureSet{
-					Elements: map[string]feature.Nil{
-						"key-a": feature.Nil{},
-						"key-b": feature.Nil{},
-						"key-c": feature.Nil{},
-					},
-				},
-			},
-			Values: map[string]feature.ValueFeatureSet{
-				"vf_1": feature.ValueFeatureSet{
-					Elements: map[string]string{
-						"key-1": "val-1",
-						"keu-2": "val-2",
-						"key-3": "val-3",
-					},
-				},
-			},
-			Instances: map[string]feature.InstanceFeatureSet{
-				"if_1": feature.InstanceFeatureSet{
-					Elements: []feature.InstanceFeature{
-						feature.InstanceFeature{
-							Attributes: map[string]string{
-								"attr-1": "1",
-								"attr-2": "val-2",
-							},
-						},
-						feature.InstanceFeature{
-							Attributes: map[string]string{
-								"attr-1": "10",
-								"attr-2": "val-20",
-							},
-						},
-						feature.InstanceFeature{
-							Attributes: map[string]string{
-								"attr-1": "100",
-								"attr-2": "val-200",
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-
-	r1 := Rule{
-		Labels: map[string]string{"label-1": "label-val-1"},
-		LabelsTemplate: `
-label-1=will-be-overridden
-label-2=
-{{range .domain_1.kf_1}}kf-{{.Name}}=present
-{{end}}
-{{range .domain_1.vf_1}}vf-{{.Name}}=vf-{{.Value}}
-{{end}}
-{{range .domain_1.if_1}}if-{{index . "attr-1"}}_{{index . "attr-2"}}=present
-{{end}}`,
-		Vars: map[string]string{"var-1": "var-val-1"},
-		VarsTemplate: `
-var-1=value-will-be-overridden-by-vars
-var-2=
-{{range .domain_1.kf_1}}kf-{{.Name}}=true
-{{end}}`,
-		MatchFeatures: FeatureMatcher{
-			FeatureMatcherTerm{
-				Feature: "domain_1.kf_1",
-				MatchExpressions: MatchExpressionSet{Expressions: Expressions{
-					"key-a": MustCreateMatchExpression(MatchExists),
-					"key-c": MustCreateMatchExpression(MatchExists),
-					"foo":   MustCreateMatchExpression(MatchDoesNotExist),
-				},
-				},
-			},
-			FeatureMatcherTerm{
-				Feature: "domain_1.vf_1",
-				MatchExpressions: MatchExpressionSet{Expressions: Expressions{
-					"key-1": MustCreateMatchExpression(MatchIn, "val-1", "val-2"),
-					"bar":   MustCreateMatchExpression(MatchDoesNotExist),
-				},
-				},
-			},
-			FeatureMatcherTerm{
-				Feature: "domain_1.if_1",
-				MatchExpressions: MatchExpressionSet{Expressions: Expressions{
-					"attr-1": MustCreateMatchExpression(MatchLt, "100"),
-				},
-				},
-			},
-		},
-	}
-
-	expectedLabels := map[string]string{
-		"label-1": "label-val-1",
-		"label-2": "",
-		// From kf_1 template
-		"kf-key-a": "present",
-		"kf-key-c": "present",
-		"kf-foo":   "present",
-		// From vf_1 template
-		"vf-key-1": "vf-val-1",
-		"vf-bar":   "vf-",
-		// From if_1 template
-		"if-1_val-2":   "present",
-		"if-10_val-20": "present",
-	}
-	expectedVars := map[string]string{
-		"var-1": "var-val-1",
-		"var-2": "",
-		// From template
-		"kf-key-a": "true",
-		"kf-key-c": "true",
-		"kf-foo":   "true",
-	}
-
-	m, err := r1.Execute(f)
-	assert.Nilf(t, err, "unexpected error: %v", err)
-	assert.Equal(t, expectedLabels, m.Labels, "instances should have matched")
-	assert.Equal(t, expectedVars, m.Vars, "instances should have matched")
-
-	//
-	// Test error cases
-	//
-	r2 := Rule{
-		MatchFeatures: FeatureMatcher{
-			// We need at least one matcher to match to execute the template.
-			// Use a simple empty matchexpression set to match anything.
-			FeatureMatcherTerm{
-				Feature:          "domain_1.kf_1",
-				MatchExpressions: MatchExpressionSet{},
-			},
-		},
-	}
-
-	r2.LabelsTemplate = "foo=bar"
-	m, err = r2.Execute(f)
-	assert.Nil(t, err)
-	assert.Equal(t, map[string]string{"foo": "bar"}, m.Labels, "instances should have matched")
-	assert.Empty(t, m.Vars)
-
-	r2.labelsTemplate = nil
-	r2.LabelsTemplate = "foo"
-	_, err = r2.Execute(f)
-	assert.Error(t, err)
-
-	r2.labelsTemplate = nil
-	r2.LabelsTemplate = "{{"
-	_, err = r2.Execute(f)
-	assert.Error(t, err)
-
-	r2.labelsTemplate = nil
-	r2.LabelsTemplate = ""
-	r2.VarsTemplate = "bar=baz"
-	m, err = r2.Execute(f)
-	assert.Nil(t, err)
-	assert.Empty(t, m.Labels)
-	assert.Equal(t, map[string]string{"bar": "baz"}, m.Vars, "instances should have matched")
-
-	r2.varsTemplate = nil
-	r2.VarsTemplate = "bar"
-	_, err = r2.Execute(f)
-	assert.Error(t, err)
-
-	r2.varsTemplate = nil
-	r2.VarsTemplate = "{{"
-	_, err = r2.Execute(f)
-	assert.Error(t, err)
-
+	assert.Equal(t, r5.Labels, m, "instances should have matched")
 }