From 727f86f9e887298cd171916dbaf3b29d373b7d28 Mon Sep 17 00:00:00 2001 From: Markus Lehtonen Date: Thu, 17 Mar 2022 18:30:32 +0200 Subject: [PATCH] apis/nfd: add matchName field in feature matcher terms Extend the format of feature matcher terms (the elements specified under under matchFeatures) with new matchName field. This field contains an expression that is evaluated against the names of features (i.e. keys in the set of feature elements) instead of their values (which is accomplished with the matchExpressions field). The matchName field is useful e.g. in template rules for creating per-feature-element labels based on feature names (instead of values) and in "normal" (non-template) rules for checking if (at least) one of certain feature element names are present. If both matchExpressions and matchName for certain feature matcher term is specified, they both must match in order to get an overall match. Also, in this case the list of matched features (used in templating) is the union of the results from matchExpressions and matchName. An example of creating an "avx512" label if any AVX512* CPUID feature is present: - name: "avx wildcard rule" labels: avx512: "true" matchFeatures: - feature: cpu.cpuid matchName: {op: InRegexp, value: ["^AVX512"]} An example of a template rule creating a dynamic set of labels based on the existence of certain kconfig options. - name: "kconfig template rule" labelsTemplate: | {{ range .kernel.config }}kconfig-{{ .Name }}={{ .Value }} {{ end }} matchFeatures: - feature: kernel.config matchName: {op: In, value: ["SWAP", "X86", "ARM"]} NOTE: this patch changes the corner case of nil/null match expressions with instance features (i.e. "matchExpressions: null"). Previously, we returned all instances for templating but now a nil match expression is not evaluated and no instances for templating are returned. --- deployment/base/nfd-crds/cr-sample.yaml | 15 ++++ .../base/nfd-crds/nodefeaturerule-crd.yaml | 83 ++++++++++++++++-- .../worker-config/nfd-worker.conf.example | 14 +++ .../manifests/nodefeaturerule-crd.yaml | 83 ++++++++++++++++-- .../helm/node-feature-discovery/values.yaml | 14 +++ pkg/api/feature/generated.pb.go | 2 +- pkg/api/feature/generated.proto | 2 +- pkg/apis/nfd/v1alpha1/expression.go | 87 +++++++++++++++++++ pkg/apis/nfd/v1alpha1/rule.go | 47 +++++++--- pkg/apis/nfd/v1alpha1/rule_test.go | 50 ++++++++--- pkg/apis/nfd/v1alpha1/types.go | 16 +++- .../nfd/v1alpha1/zz_generated.deepcopy.go | 11 ++- .../clientset/versioned/clientset.go | 2 +- pkg/generated/clientset/versioned/doc.go | 2 +- .../versioned/fake/clientset_generated.go | 2 +- pkg/generated/clientset/versioned/fake/doc.go | 2 +- .../clientset/versioned/fake/register.go | 2 +- .../clientset/versioned/scheme/doc.go | 2 +- .../clientset/versioned/scheme/register.go | 2 +- .../versioned/typed/nfd/v1alpha1/doc.go | 2 +- .../versioned/typed/nfd/v1alpha1/fake/doc.go | 2 +- .../nfd/v1alpha1/fake/fake_nfd_client.go | 2 +- .../nfd/v1alpha1/fake/fake_nodefeaturerule.go | 2 +- .../typed/nfd/v1alpha1/generated_expansion.go | 2 +- .../typed/nfd/v1alpha1/nfd_client.go | 2 +- .../typed/nfd/v1alpha1/nodefeaturerule.go | 2 +- .../informers/externalversions/factory.go | 2 +- .../informers/externalversions/generic.go | 2 +- .../internalinterfaces/factory_interfaces.go | 2 +- .../externalversions/nfd/interface.go | 2 +- .../nfd/v1alpha1/interface.go | 2 +- .../nfd/v1alpha1/nodefeaturerule.go | 2 +- .../nfd/v1alpha1/expansion_generated.go | 2 +- .../listers/nfd/v1alpha1/nodefeaturerule.go | 2 +- 34 files changed, 405 insertions(+), 63 deletions(-) diff --git a/deployment/base/nfd-crds/cr-sample.yaml b/deployment/base/nfd-crds/cr-sample.yaml index dad9405d08..51192cf515 100644 --- a/deployment/base/nfd-crds/cr-sample.yaml +++ b/deployment/base/nfd-crds/cr-sample.yaml @@ -88,6 +88,13 @@ spec: vendor: {op: In, value: ["8086"]} class: {op: In, value: ["02"]} + - name: "avx wildcard rule" + labels: + "my-avx-feature": "true" + matchFeatures: + - feature: cpu.cpuid + matchName: {op: InRegexp, value: ["^AVX512"]} + # The following features demonstreate label templating capabilities - name: "my system template feature" labelsTemplate: | @@ -137,3 +144,11 @@ spec: matchExpressions: my.kernel.feature: {op: IsTrue} my.dummy.var: {op: Gt, value: ["0"]} + + - name: "kconfig template rule" + labelsTemplate: | + {{ range .kernel.config }}kconfig-{{ .Name }}={{ .Value }} + {{ end }} + matchFeatures: + - feature: kernel.config + matchName: {op: In, value: ["SWAP", "X86", "ARM"]} diff --git a/deployment/base/nfd-crds/nodefeaturerule-crd.yaml b/deployment/base/nfd-crds/nodefeaturerule-crd.yaml index cd9b48bf46..705bae85cc 100644 --- a/deployment/base/nfd-crds/nodefeaturerule-crd.yaml +++ b/deployment/base/nfd-crds/nodefeaturerule-crd.yaml @@ -70,6 +70,8 @@ spec: in the feature set. properties: feature: + description: Feature is the name of the feature + set to match against. type: string matchExpressions: additionalProperties: @@ -114,13 +116,46 @@ spec: required: - op type: object - description: MatchExpressionSet contains a set of - MatchExpressions, each of which is evaluated against - a set of input values. + description: MatchExpressions is the set of per-element + expressions evaluated. These match against the + value of the specified elements. + type: object + matchName: + description: MatchName in an expression that is + matched against the name of each element in the + feature set. + properties: + op: + description: Op is the operator to be applied. + enum: + - In + - NotIn + - InRegexp + - Exists + - DoesNotExist + - Gt + - Lt + - GtLt + - IsTrue + - IsFalse + type: string + value: + description: Value is the list of values that + the operand evaluates the input against. Value + should be empty if the operator is Exists, + DoesNotExist, IsTrue or IsFalse. Value should + contain exactly one element if the operator + is Gt or Lt and exactly two elements if the + operator is GtLt. In other cases Value should + contain at least one element. + items: + type: string + type: array + required: + - op type: object required: - feature - - matchExpressions type: object type: array required: @@ -136,6 +171,8 @@ spec: are evaluated against each element in the feature set. properties: feature: + description: Feature is the name of the feature set to + match against. type: string matchExpressions: additionalProperties: @@ -177,12 +214,44 @@ spec: required: - op type: object - description: MatchExpressionSet contains a set of MatchExpressions, - each of which is evaluated against a set of input values. + description: MatchExpressions is the set of per-element + expressions evaluated. These match against the value + of the specified elements. + type: object + matchName: + description: MatchName in an expression that is matched + against the name of each element in the feature set. + properties: + op: + description: Op is the operator to be applied. + enum: + - In + - NotIn + - InRegexp + - Exists + - DoesNotExist + - Gt + - Lt + - GtLt + - IsTrue + - IsFalse + type: string + value: + description: Value is the list of values that the + operand evaluates the input against. Value should + be empty if the operator is Exists, DoesNotExist, + IsTrue or IsFalse. Value should contain exactly + one element if the operator is Gt or Lt and exactly + two elements if the operator is GtLt. In other cases + Value should contain at least one element. + items: + type: string + type: array + required: + - op type: object required: - feature - - matchExpressions type: object type: array name: diff --git a/deployment/components/worker-config/nfd-worker.conf.example b/deployment/components/worker-config/nfd-worker.conf.example index eb5030bd5e..0cfb8aa057 100644 --- a/deployment/components/worker-config/nfd-worker.conf.example +++ b/deployment/components/worker-config/nfd-worker.conf.example @@ -171,6 +171,13 @@ # vendor: {op: In, value: ["8086"]} # class: {op: In, value: ["02"]} # +# - name: "avx wildcard rule" +# labels: +# "my-avx-feature": "true" +# matchFeatures: +# - feature: cpu.cpuid +# matchName: {op: InRegexp, value: ["^AVX512"]} +# # # The following features demonstreate label templating capabilities # - name: "my template rule" # labelsTemplate: | @@ -221,3 +228,10 @@ # my.kernel.feature: {op: IsTrue} # my.dummy.var: {op: Gt, value: ["0"]} # +# - name: "kconfig template rule" +# labelsTemplate: | +# {{ range .kernel.config }}kconfig-{{ .Name }}={{ .Value }} +# {{ end }} +# matchFeatures: +# - feature: kernel.config +# matchName: {op: In, value: ["SWAP", "X86", "ARM"]} diff --git a/deployment/helm/node-feature-discovery/manifests/nodefeaturerule-crd.yaml b/deployment/helm/node-feature-discovery/manifests/nodefeaturerule-crd.yaml index cd9b48bf46..705bae85cc 100644 --- a/deployment/helm/node-feature-discovery/manifests/nodefeaturerule-crd.yaml +++ b/deployment/helm/node-feature-discovery/manifests/nodefeaturerule-crd.yaml @@ -70,6 +70,8 @@ spec: in the feature set. properties: feature: + description: Feature is the name of the feature + set to match against. type: string matchExpressions: additionalProperties: @@ -114,13 +116,46 @@ spec: required: - op type: object - description: MatchExpressionSet contains a set of - MatchExpressions, each of which is evaluated against - a set of input values. + description: MatchExpressions is the set of per-element + expressions evaluated. These match against the + value of the specified elements. + type: object + matchName: + description: MatchName in an expression that is + matched against the name of each element in the + feature set. + properties: + op: + description: Op is the operator to be applied. + enum: + - In + - NotIn + - InRegexp + - Exists + - DoesNotExist + - Gt + - Lt + - GtLt + - IsTrue + - IsFalse + type: string + value: + description: Value is the list of values that + the operand evaluates the input against. Value + should be empty if the operator is Exists, + DoesNotExist, IsTrue or IsFalse. Value should + contain exactly one element if the operator + is Gt or Lt and exactly two elements if the + operator is GtLt. In other cases Value should + contain at least one element. + items: + type: string + type: array + required: + - op type: object required: - feature - - matchExpressions type: object type: array required: @@ -136,6 +171,8 @@ spec: are evaluated against each element in the feature set. properties: feature: + description: Feature is the name of the feature set to + match against. type: string matchExpressions: additionalProperties: @@ -177,12 +214,44 @@ spec: required: - op type: object - description: MatchExpressionSet contains a set of MatchExpressions, - each of which is evaluated against a set of input values. + description: MatchExpressions is the set of per-element + expressions evaluated. These match against the value + of the specified elements. + type: object + matchName: + description: MatchName in an expression that is matched + against the name of each element in the feature set. + properties: + op: + description: Op is the operator to be applied. + enum: + - In + - NotIn + - InRegexp + - Exists + - DoesNotExist + - Gt + - Lt + - GtLt + - IsTrue + - IsFalse + type: string + value: + description: Value is the list of values that the + operand evaluates the input against. Value should + be empty if the operator is Exists, DoesNotExist, + IsTrue or IsFalse. Value should contain exactly + one element if the operator is Gt or Lt and exactly + two elements if the operator is GtLt. In other cases + Value should contain at least one element. + items: + type: string + type: array + required: + - op type: object required: - feature - - matchExpressions type: object type: array name: diff --git a/deployment/helm/node-feature-discovery/values.yaml b/deployment/helm/node-feature-discovery/values.yaml index 466c75dbbc..0fec891eed 100644 --- a/deployment/helm/node-feature-discovery/values.yaml +++ b/deployment/helm/node-feature-discovery/values.yaml @@ -264,6 +264,13 @@ worker: # vendor: {op: In, value: ["8086"]} # class: {op: In, value: ["02"]} # + # - name: "avx wildcard rule" + # labels: + # "my-avx-feature": "true" + # matchFeatures: + # - feature: cpu.cpuid + # matchName: {op: InRegexp, value: ["^AVX512"]} + # # # The following features demonstreate label templating capabilities # - name: "my template rule" # labelsTemplate: | @@ -314,6 +321,13 @@ worker: # my.kernel.feature: {op: IsTrue} # my.dummy.var: {op: Gt, value: ["0"]} # + # - name: "kconfig template rule" + # labelsTemplate: | + # {{ range .kernel.config }}kconfig-{{ .Name }}={{ .Value }} + # {{ end }} + # matchFeatures: + # - feature: kernel.config + # matchName: {op: In, value: ["SWAP", "X86", "ARM"]} ### daemonsetAnnotations: {} diff --git a/pkg/api/feature/generated.pb.go b/pkg/api/feature/generated.pb.go index 33df143ccd..cfe7c772bd 100644 --- a/pkg/api/feature/generated.pb.go +++ b/pkg/api/feature/generated.pb.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/api/feature/generated.proto b/pkg/api/feature/generated.proto index c3ae18b332..9ab6b7ad4b 100644 --- a/pkg/api/feature/generated.proto +++ b/pkg/api/feature/generated.proto @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/apis/nfd/v1alpha1/expression.go b/pkg/apis/nfd/v1alpha1/expression.go index 41a032b405..330eb2bcea 100644 --- a/pkg/apis/nfd/v1alpha1/expression.go +++ b/pkg/apis/nfd/v1alpha1/expression.go @@ -270,6 +270,93 @@ func (m *MatchExpression) MatchValues(name string, values map[string]string) (bo return matched, nil } +// MatchKeyNames evaluates the MatchExpression against names of a set of key features. +func (m *MatchExpression) MatchKeyNames(keys map[string]feature.Nil) (bool, []MatchedKey, error) { + ret := []MatchedKey{} + + for k := range keys { + if match, err := m.Match(true, k); err != nil { + return false, nil, err + } else if match { + ret = append(ret, MatchedKey{Name: k}) + } + } + // Sort for reproducible output + sort.Slice(ret, func(i, j int) bool { return ret[i].Name < ret[j].Name }) + + if klog.V(3).Enabled() { + mk := make([]string, len(ret)) + for i, v := range ret { + mk[i] = v.Name + } + + k := make([]string, 0, len(keys)) + for n := range keys { + k = append(k, n) + } + sort.Strings(k) + + if len(keys) < 10 || klog.V(4).Enabled() { + klog.Infof("matched names %s when matching %q %q against %s", strings.Join(mk, ", "), m.Op, m.Value, strings.Join(k, " ")) + } else { + klog.Infof("matched names %s when matching %q %q against %s... (list truncated)", strings.Join(mk, ", "), m.Op, m.Value, strings.Join(k[0:10], ", ")) + } + } + + return true, ret, nil +} + +// MatchValueNames evaluates the MatchExpression against names of a set of value features. +func (m *MatchExpression) MatchValueNames(values map[string]string) (bool, []MatchedValue, error) { + ret := []MatchedValue{} + + for k, v := range values { + if match, err := m.Match(true, k); err != nil { + return false, nil, err + } else if match { + ret = append(ret, MatchedValue{Name: k, Value: v}) + } + } + // Sort for reproducible output + sort.Slice(ret, func(i, j int) bool { return ret[i].Name < ret[j].Name }) + + if klog.V(3).Enabled() { + mk := make([]string, len(ret)) + for i, v := range ret { + mk[i] = v.Name + } + + k := make([]string, 0, len(values)) + for n := range values { + k = append(k, n) + } + sort.Strings(k) + + if len(values) < 10 || klog.V(4).Enabled() { + klog.Infof("matched names %s when matching %q %q against %s", strings.Join(mk, ", "), m.Op, m.Value, strings.Join(k, " ")) + } else { + klog.Infof("matched names %s when matching %q %q against %s... (list truncated)", strings.Join(mk, ", "), m.Op, m.Value, strings.Join(k[0:10], ", ")) + } + } + + return true, ret, nil +} + +// MatchInstanceAttributeNames evaluates the MatchExpression against a set of +// instance features, matching against the names of their attributes. +func (m *MatchExpression) MatchInstanceAttributeNames(instances []feature.InstanceFeature) ([]MatchedInstance, error) { + ret := []MatchedInstance{} + + for _, i := range instances { + if match, _, err := m.MatchValueNames(i.Attributes); err != nil { + return nil, err + } else if match { + ret = append(ret, i.Attributes) + } + } + return ret, nil +} + // matchExpression is a helper type for unmarshalling MatchExpression type matchExpression MatchExpression diff --git a/pkg/apis/nfd/v1alpha1/rule.go b/pkg/apis/nfd/v1alpha1/rule.go index 267680326f..7c908793eb 100644 --- a/pkg/apis/nfd/v1alpha1/rule.go +++ b/pkg/apis/nfd/v1alpha1/rule.go @@ -174,22 +174,49 @@ func (m *FeatureMatcher) match(features map[string]*feature.DomainFeatures) (boo matches[domain] = make(domainMatchedFeatures) } - var isMatch bool + var isMatch = true var err error if f, ok := domainFeatures.Keys[featureName]; ok { - m, v, e := term.MatchExpressions.MatchGetKeys(f.Elements) - isMatch = m - err = e + var v []MatchedKey + if term.MatchExpressions != nil { + isMatch, v, err = term.MatchExpressions.MatchGetKeys(f.Elements) + } + + if err == nil && isMatch && term.MatchName != nil { + mTmp, vTmp, e := term.MatchName.MatchKeyNames(f.Elements) + isMatch = mTmp + err = e + v = append(v, vTmp...) + } + matches[domain][featureName] = v } else if f, ok := domainFeatures.Values[featureName]; ok { - m, v, e := term.MatchExpressions.MatchGetValues(f.Elements) - isMatch = m - err = e + var v []MatchedValue + if term.MatchExpressions != nil { + isMatch, v, err = term.MatchExpressions.MatchGetValues(f.Elements) + } + + if err == nil && isMatch && term.MatchName != nil { + mTmp, vTmp, e := term.MatchName.MatchValueNames(f.Elements) + isMatch = mTmp + err = e + v = append(v, vTmp...) + } + matches[domain][featureName] = v } else if f, ok := domainFeatures.Instances[featureName]; ok { - v, e := term.MatchExpressions.MatchGetInstances(f.Elements) - isMatch = len(v) > 0 - err = e + var v []MatchedInstance + if term.MatchExpressions != nil { + v, err = term.MatchExpressions.MatchGetInstances(f.Elements) + isMatch = len(v) > 0 + } + + if err == nil && isMatch && term.MatchName != nil { + vTmp, e := term.MatchName.MatchInstanceAttributeNames(f.Elements) + isMatch = len(vTmp) > 0 + err = e + v = append(v, vTmp...) + } matches[domain][featureName] = v } else { return false, nil, fmt.Errorf("%q feature of source/domain %q not available", featureName, domain) diff --git a/pkg/apis/nfd/v1alpha1/rule_test.go b/pkg/apis/nfd/v1alpha1/rule_test.go index 87d9c7b3b0..e534eda6af 100644 --- a/pkg/apis/nfd/v1alpha1/rule_test.go +++ b/pkg/apis/nfd/v1alpha1/rule_test.go @@ -32,7 +32,7 @@ func TestRule(t *testing.T) { MatchFeatures: FeatureMatcher{ FeatureMatcherTerm{ Feature: "domain-1.kf-1", - MatchExpressions: MatchExpressionSet{ + MatchExpressions: &MatchExpressionSet{ Expressions: Expressions{"key-1": MustCreateMatchExpression(MatchExists)}, }, }, @@ -86,7 +86,7 @@ func TestRule(t *testing.T) { r1.MatchFeatures = FeatureMatcher{ FeatureMatcherTerm{ Feature: "domain-1.kf-1", - MatchExpressions: MatchExpressionSet{}, + MatchExpressions: &MatchExpressionSet{}, }, } m, err = r1.Execute(f) @@ -110,7 +110,7 @@ func TestRule(t *testing.T) { MatchFeatures: FeatureMatcher{ FeatureMatcherTerm{ Feature: "domain-1.vf-1", - MatchExpressions: MatchExpressionSet{ + MatchExpressions: &MatchExpressionSet{ Expressions: Expressions{"key-1": MustCreateMatchExpression(MatchIn, "val-1")}, }, }, @@ -131,7 +131,7 @@ func TestRule(t *testing.T) { MatchFeatures: FeatureMatcher{ FeatureMatcherTerm{ Feature: "domain-1.if-1", - MatchExpressions: MatchExpressionSet{ + MatchExpressions: &MatchExpressionSet{ Expressions: Expressions{"attr-1": MustCreateMatchExpression(MatchIn, "val-1")}, }, }, @@ -152,13 +152,13 @@ func TestRule(t *testing.T) { MatchFeatures: FeatureMatcher{ FeatureMatcherTerm{ Feature: "domain-1.vf-1", - MatchExpressions: MatchExpressionSet{ + MatchExpressions: &MatchExpressionSet{ Expressions: Expressions{"key-1": MustCreateMatchExpression(MatchIn, "val-x")}, }, }, FeatureMatcherTerm{ Feature: "domain-1.if-1", - MatchExpressions: MatchExpressionSet{ + MatchExpressions: &MatchExpressionSet{ Expressions: Expressions{"attr-1": MustCreateMatchExpression(MatchIn, "val-1")}, }, }, @@ -179,7 +179,7 @@ func TestRule(t *testing.T) { MatchFeatures: FeatureMatcher{ FeatureMatcherTerm{ Feature: "domain-1.kf-1", - MatchExpressions: MatchExpressionSet{ + MatchExpressions: &MatchExpressionSet{ Expressions: Expressions{"key-na": MustCreateMatchExpression(MatchExists)}, }, }, @@ -195,7 +195,7 @@ func TestRule(t *testing.T) { MatchFeatures: FeatureMatcher{ FeatureMatcherTerm{ Feature: "domain-1.kf-1", - MatchExpressions: MatchExpressionSet{ + MatchExpressions: &MatchExpressionSet{ Expressions: Expressions{"key-1": MustCreateMatchExpression(MatchExists)}, }, }, @@ -225,6 +225,7 @@ func TestTemplating(t *testing.T) { "key-1": "val-1", "keu-2": "val-2", "key-3": "val-3", + "key-4": "val-4", }, }, }, @@ -275,7 +276,7 @@ var-2= MatchFeatures: FeatureMatcher{ FeatureMatcherTerm{ Feature: "domain_1.kf_1", - MatchExpressions: MatchExpressionSet{Expressions: Expressions{ + MatchExpressions: &MatchExpressionSet{Expressions: Expressions{ "key-a": MustCreateMatchExpression(MatchExists), "key-c": MustCreateMatchExpression(MatchExists), "foo": MustCreateMatchExpression(MatchDoesNotExist), @@ -284,7 +285,7 @@ var-2= }, FeatureMatcherTerm{ Feature: "domain_1.vf_1", - MatchExpressions: MatchExpressionSet{Expressions: Expressions{ + MatchExpressions: &MatchExpressionSet{Expressions: Expressions{ "key-1": MustCreateMatchExpression(MatchIn, "val-1", "val-2"), "bar": MustCreateMatchExpression(MatchDoesNotExist), }, @@ -292,7 +293,7 @@ var-2= }, FeatureMatcherTerm{ Feature: "domain_1.if_1", - MatchExpressions: MatchExpressionSet{Expressions: Expressions{ + MatchExpressions: &MatchExpressionSet{Expressions: Expressions{ "attr-1": MustCreateMatchExpression(MatchLt, "100"), }, }, @@ -337,7 +338,7 @@ var-2= // Use a simple empty matchexpression set to match anything. FeatureMatcherTerm{ Feature: "domain_1.kf_1", - MatchExpressions: MatchExpressionSet{Expressions: Expressions{ + MatchExpressions: &MatchExpressionSet{Expressions: Expressions{ "key-a": MustCreateMatchExpression(MatchExists), }, }, @@ -379,4 +380,29 @@ var-2= _, err = r2.Execute(f) assert.Error(t, err) + // + // Test matchName + // + r3 := Rule{ + LabelsTemplate: "{{range .domain_1.vf_1}}{{.Name}}={{.Value}}\n{{end}}", + MatchFeatures: FeatureMatcher{ + FeatureMatcherTerm{ + Feature: "domain_1.vf_1", + MatchExpressions: &MatchExpressionSet{Expressions: Expressions{ + "key-5": MustCreateMatchExpression(MatchDoesNotExist), + }, + }, + MatchName: MustCreateMatchExpression(MatchIn, "key-1", "key-4"), + }, + }, + } + expectedLabels = map[string]string{ + "key-1": "val-1", + "key-4": "val-4", + "key-5": "", + } + + m, err = r3.Execute(f) + assert.Nilf(t, err, "unexpected error: %v", err) + assert.Equal(t, expectedLabels, m.Labels, "instances should have matched") } diff --git a/pkg/apis/nfd/v1alpha1/types.go b/pkg/apis/nfd/v1alpha1/types.go index b8982b49e7..6dd4f3c2ea 100644 --- a/pkg/apis/nfd/v1alpha1/types.go +++ b/pkg/apis/nfd/v1alpha1/types.go @@ -105,8 +105,16 @@ type FeatureMatcher []FeatureMatcherTerm // requirements (specified as MatchExpressions) are evaluated against each // element in the feature set. type FeatureMatcherTerm struct { - Feature string `json:"feature"` - MatchExpressions MatchExpressionSet `json:"matchExpressions"` + // Feature is the name of the feature set to match against. + Feature string `json:"feature"` + // MatchExpressions is the set of per-element expressions evaluated. These + // match against the value of the specified elements. + // +optional + MatchExpressions *MatchExpressionSet `json:"matchExpressions"` + // MatchName in an expression that is matched against the name of each + // element in the feature set. + // +optional + MatchName *MatchExpression `json:"matchName"` } // MatchExpressionSet contains a set of MatchExpressions, each of which is @@ -199,3 +207,7 @@ const ( // output of preceding rules. RuleBackrefFeature = "matched" ) + +// MatchAllNames is a special key in MatchExpressionSet to use field names +// (keys from the input) instead of values when matching. +const MatchAllNames = "*" diff --git a/pkg/apis/nfd/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/nfd/v1alpha1/zz_generated.deepcopy.go index ece20769ab..7ee365e35e 100644 --- a/pkg/apis/nfd/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/nfd/v1alpha1/zz_generated.deepcopy.go @@ -62,7 +62,16 @@ func (in FeatureMatcher) DeepCopy() FeatureMatcher { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FeatureMatcherTerm) DeepCopyInto(out *FeatureMatcherTerm) { *out = *in - in.MatchExpressions.DeepCopyInto(&out.MatchExpressions) + if in.MatchExpressions != nil { + in, out := &in.MatchExpressions, &out.MatchExpressions + *out = new(MatchExpressionSet) + (*in).DeepCopyInto(*out) + } + if in.MatchName != nil { + in, out := &in.MatchName, &out.MatchName + *out = new(MatchExpression) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeatureMatcherTerm. diff --git a/pkg/generated/clientset/versioned/clientset.go b/pkg/generated/clientset/versioned/clientset.go index 58f0e461cc..75330a9d95 100644 --- a/pkg/generated/clientset/versioned/clientset.go +++ b/pkg/generated/clientset/versioned/clientset.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/clientset/versioned/doc.go b/pkg/generated/clientset/versioned/doc.go index 92576525fa..ab7539cb9f 100644 --- a/pkg/generated/clientset/versioned/doc.go +++ b/pkg/generated/clientset/versioned/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/clientset/versioned/fake/clientset_generated.go b/pkg/generated/clientset/versioned/fake/clientset_generated.go index dd33f2a04d..9ba9e83904 100644 --- a/pkg/generated/clientset/versioned/fake/clientset_generated.go +++ b/pkg/generated/clientset/versioned/fake/clientset_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/clientset/versioned/fake/doc.go b/pkg/generated/clientset/versioned/fake/doc.go index 4b91eb6a64..7d98eabcc8 100644 --- a/pkg/generated/clientset/versioned/fake/doc.go +++ b/pkg/generated/clientset/versioned/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/clientset/versioned/fake/register.go b/pkg/generated/clientset/versioned/fake/register.go index a7846e07af..cdc746791a 100644 --- a/pkg/generated/clientset/versioned/fake/register.go +++ b/pkg/generated/clientset/versioned/fake/register.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/clientset/versioned/scheme/doc.go b/pkg/generated/clientset/versioned/scheme/doc.go index 6dc5e6530d..288d3794dc 100644 --- a/pkg/generated/clientset/versioned/scheme/doc.go +++ b/pkg/generated/clientset/versioned/scheme/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/clientset/versioned/scheme/register.go b/pkg/generated/clientset/versioned/scheme/register.go index cfac0d743c..b455617b6f 100644 --- a/pkg/generated/clientset/versioned/scheme/register.go +++ b/pkg/generated/clientset/versioned/scheme/register.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/doc.go b/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/doc.go index be4e7e92b5..5362dda2ff 100644 --- a/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/doc.go +++ b/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/fake/doc.go b/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/fake/doc.go index 43eec078a4..dd9e9e4c8f 100644 --- a/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/fake/doc.go +++ b/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/fake/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nfd_client.go b/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nfd_client.go index 8bf3fc196a..7133268330 100644 --- a/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nfd_client.go +++ b/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nfd_client.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nodefeaturerule.go b/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nodefeaturerule.go index 7710634ef1..b4afd87f99 100644 --- a/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nodefeaturerule.go +++ b/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nodefeaturerule.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/generated_expansion.go b/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/generated_expansion.go index 965bc1eff4..8807cd64dd 100644 --- a/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/generated_expansion.go +++ b/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/generated_expansion.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/nfd_client.go b/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/nfd_client.go index 1858451d17..653f75df21 100644 --- a/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/nfd_client.go +++ b/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/nfd_client.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/nodefeaturerule.go b/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/nodefeaturerule.go index 176e7410b3..d7d4428adf 100644 --- a/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/nodefeaturerule.go +++ b/pkg/generated/clientset/versioned/typed/nfd/v1alpha1/nodefeaturerule.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/informers/externalversions/factory.go b/pkg/generated/informers/externalversions/factory.go index 5a65015deb..e64a77d356 100644 --- a/pkg/generated/informers/externalversions/factory.go +++ b/pkg/generated/informers/externalversions/factory.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/informers/externalversions/generic.go b/pkg/generated/informers/externalversions/generic.go index 18dd6de9bc..453de7f151 100644 --- a/pkg/generated/informers/externalversions/generic.go +++ b/pkg/generated/informers/externalversions/generic.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go index f63a11b79c..0b862ce98d 100644 --- a/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/pkg/generated/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/informers/externalversions/nfd/interface.go b/pkg/generated/informers/externalversions/nfd/interface.go index a7d35dca09..8130adf7d8 100644 --- a/pkg/generated/informers/externalversions/nfd/interface.go +++ b/pkg/generated/informers/externalversions/nfd/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/informers/externalversions/nfd/v1alpha1/interface.go b/pkg/generated/informers/externalversions/nfd/v1alpha1/interface.go index 6c8c39d324..5f476d8873 100644 --- a/pkg/generated/informers/externalversions/nfd/v1alpha1/interface.go +++ b/pkg/generated/informers/externalversions/nfd/v1alpha1/interface.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/informers/externalversions/nfd/v1alpha1/nodefeaturerule.go b/pkg/generated/informers/externalversions/nfd/v1alpha1/nodefeaturerule.go index bb3b23c9e9..6879a711d9 100644 --- a/pkg/generated/informers/externalversions/nfd/v1alpha1/nodefeaturerule.go +++ b/pkg/generated/informers/externalversions/nfd/v1alpha1/nodefeaturerule.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/listers/nfd/v1alpha1/expansion_generated.go b/pkg/generated/listers/nfd/v1alpha1/expansion_generated.go index 64eee10f91..a072a4f4ae 100644 --- a/pkg/generated/listers/nfd/v1alpha1/expansion_generated.go +++ b/pkg/generated/listers/nfd/v1alpha1/expansion_generated.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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. diff --git a/pkg/generated/listers/nfd/v1alpha1/nodefeaturerule.go b/pkg/generated/listers/nfd/v1alpha1/nodefeaturerule.go index 68e37951d8..e214509134 100644 --- a/pkg/generated/listers/nfd/v1alpha1/nodefeaturerule.go +++ b/pkg/generated/listers/nfd/v1alpha1/nodefeaturerule.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The Kubernetes Authors. +Copyright 2022 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.