1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2024-12-14 11:57:51 +00:00

Merge pull request #550 from marquiz/devel/custom-templating

Templating of custom label names
This commit is contained in:
Kubernetes Prow Robot 2021-11-23 12:02:51 -08:00 committed by GitHub
commit da484b7bd3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 516 additions and 37 deletions

View file

@ -87,3 +87,27 @@ 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}

View file

@ -48,6 +48,12 @@ 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.

View file

@ -202,3 +202,27 @@
# 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}

View file

@ -48,6 +48,12 @@ 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.

View file

@ -291,6 +291,30 @@ 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}
### <NFD-WORKER-CONF-END-DO-NOT-REMOVE>
podSecurityContext: {}

View file

@ -314,44 +314,113 @@ 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 false, err
return nil, err
}
if !match {
return false, nil
return nil, nil
}
ret = append(ret, MatchedKey{Name: n})
}
return true, nil
// Sort for reproducible output
sort.Slice(ret, func(i, j int) bool { return ret[i].Name < ret[j].Name })
return ret, 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 false, err
return nil, err
}
if !match {
return false, nil
return nil, nil
}
ret = append(ret, MatchedValue{Name: n, Value: values[n]})
}
return true, nil
// Sort for reproducible output
sort.Slice(ret, func(i, j int) bool { return ret[i].Name < ret[j].Name })
return ret, 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 false, err
return nil, err
} else if match {
return true, nil
ret = append(ret, i.Attributes)
}
}
return false, nil
return ret, nil
}
// UnmarshalJSON implements the Unmarshaler interface of "encoding/json".

View file

@ -291,23 +291,29 @@ 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{
{result: assert.Truef, err: assert.Nilf},
{output: O{}, result: assert.Truef, err: assert.Nilf},
{input: I{"foo": {}}, 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},
{mes: `
foo: { op: DoesNotExist }
bar: { op: Exists }
`,
input: I{"bar": {}, "baz": {}},
input: I{"bar": {}, "baz": {}, "buzz": {}},
output: O{MK{Name: "bar"}, MK{Name: "foo"}},
result: assert.Truef, err: assert.Nilf},
{mes: `
@ -315,6 +321,7 @@ foo: { op: DoesNotExist }
bar: { op: Exists }
`,
input: I{"foo": {}, "bar": {}, "baz": {}},
output: nil,
result: assert.Falsef, err: assert.Nilf},
{mes: `
@ -322,6 +329,7 @@ foo: { op: In, value: ["bar"] }
bar: { op: Exists }
`,
input: I{"bar": {}, "baz": {}},
output: nil,
result: assert.Falsef, err: assert.NotNilf},
}
@ -331,6 +339,10 @@ 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)
@ -339,17 +351,22 @@ 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{
{result: assert.Truef, err: assert.Nilf},
{output: O{}, result: assert.Truef, err: assert.Nilf},
{input: I{"foo": "bar"}, result: assert.Truef, err: assert.Nilf},
{input: I{}, output: O{}, result: assert.Truef, err: assert.Nilf},
{input: I{"foo": "bar"}, output: O{MV{Name: "foo", Value: "bar"}}, result: assert.Truef, err: assert.Nilf},
{mes: `
foo: { op: Exists }
@ -364,7 +381,8 @@ foo: { op: Exists }
bar: { op: In, value: ["val", "wal"] }
baz: { op: Gt, value: ["10"] }
`,
input: I{"foo": "1", "bar": "val", "baz": "123"},
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"}},
result: assert.Truef, err: assert.Nilf},
{mes: `
@ -382,6 +400,10 @@ 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)
@ -390,26 +412,30 @@ 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{
{result: assert.Falsef, err: assert.Nilf}, // nil instances -> false
{output: O{}, result: assert.Falsef, err: assert.Nilf}, // nil instances -> false
{input: []I{}, result: assert.Falsef, err: assert.Nilf}, // zero instances -> false
{input: []I{}, output: O{}, result: assert.Falsef, err: assert.Nilf}, // zero instances -> false
{input: []I{I{Attributes: A{}}}, result: assert.Truef, err: assert.Nilf}, // one "empty" instance
{input: []I{I{Attributes: A{}}}, output: O{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: `
@ -417,6 +443,7 @@ 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: `
@ -432,6 +459,10 @@ 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)

View file

@ -18,23 +18,38 @@ package v1alpha1
import (
"strings"
"text/template"
"bytes"
"fmt"
"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
"sigs.k8s.io/node-feature-discovery/pkg/utils"
)
// Execute the rule against a set of input features.
func (r *Rule) Execute(features map[string]*feature.DomainFeatures) (map[string]string, error) {
ret := make(map[string]string)
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 nil, err
} else if m {
} else if m != nil {
matched = true
break
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, ret); err != nil {
return nil, err
}
}
}
if !matched {
@ -45,29 +60,62 @@ func (r *Rule) Execute(features map[string]*feature.DomainFeatures) (map[string]
if len(r.MatchFeatures) > 0 {
if m, err := r.MatchFeatures.match(features); err != nil {
return nil, err
} else if !m {
} else if m == nil {
return nil, nil
} else {
utils.KlogDump(4, "matches for matchFeatures "+r.Name, " ", m)
if err := r.executeLabelsTemplate(m, ret); err != nil {
return nil, err
}
}
}
labels := make(map[string]string, len(r.Labels))
for k, v := range r.Labels {
labels[k] = v
ret[k] = v
}
return labels, nil
return ret, nil
}
func (e *MatchAnyElem) match(features map[string]*feature.DomainFeatures) (bool, error) {
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 err
}
r.labelsTemplate = t
}
labels, err := r.labelsTemplate.expandMap(in)
if err != nil {
return err
}
for k, v := range labels {
out[k] = v
}
return nil
}
type matchedFeatures map[string]domainMatchedFeatures
type domainMatchedFeatures map[string]interface{}
func (e *MatchAnyElem) match(features map[string]*feature.DomainFeatures) (matchedFeatures, error) {
return e.MatchFeatures.match(features)
}
func (m *FeatureMatcher) match(features map[string]*feature.DomainFeatures) (bool, error) {
func (m *FeatureMatcher) match(features map[string]*feature.DomainFeatures) (matchedFeatures, error) {
ret := make(matchedFeatures, len(*m))
// Logical AND over the terms
for _, term := range *m {
split := strings.SplitN(term.Feature, ".", 2)
if len(split) != 2 {
return false, fmt.Errorf("invalid selector %q: must be <domain>.<feature>", term.Feature)
return nil, fmt.Errorf("invalid feature %q: must be <domain>.<feature>", term.Feature)
}
domain := split[0]
// Ignore case
@ -75,26 +123,100 @@ func (m *FeatureMatcher) match(features map[string]*feature.DomainFeatures) (boo
domainFeatures, ok := features[domain]
if !ok {
return false, fmt.Errorf("unknown feature source/domain %q", domain)
return nil, fmt.Errorf("unknown feature source/domain %q", domain)
}
if _, ok := ret[domain]; !ok {
ret[domain] = make(domainMatchedFeatures)
}
var m bool
var err error
var e error
if f, ok := domainFeatures.Keys[featureName]; ok {
m, err = term.MatchExpressions.MatchKeys(f.Elements)
v, err := term.MatchExpressions.MatchGetKeys(f.Elements)
m = len(v) > 0
e = err
ret[domain][featureName] = v
} else if f, ok := domainFeatures.Values[featureName]; ok {
m, err = term.MatchExpressions.MatchValues(f.Elements)
v, err := term.MatchExpressions.MatchGetValues(f.Elements)
m = len(v) > 0
e = err
ret[domain][featureName] = v
} else if f, ok := domainFeatures.Instances[featureName]; ok {
m, err = term.MatchExpressions.MatchInstances(f.Elements)
v, err := term.MatchExpressions.MatchGetInstances(f.Elements)
m = len(v) > 0
e = err
ret[domain][featureName] = v
} else {
return false, fmt.Errorf("%q feature of source/domain %q not available", featureName, domain)
return nil, fmt.Errorf("%q feature of source/domain %q not available", featureName, domain)
}
if err != nil {
return false, err
if e != nil {
return nil, e
} else if !m {
return false, nil
return nil, nil
}
}
return true, 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 {
out[split[0]] = ""
} else {
out[split[0]] = split[1]
}
}
}
return out, nil
}

View file

@ -192,3 +192,107 @@ func TestRule(t *testing.T) {
assert.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r5.Labels, m, "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: `
{{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}}`,
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",
// 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",
}
m, err := r1.Execute(f)
assert.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, expectedLabels, m, "instances should have matched")
}

View file

@ -59,6 +59,12 @@ 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"`
// MatchFeatures specifies a set of matcher terms all of which must match.
// +optional
MatchFeatures FeatureMatcher `json:"matchFeatures"`
@ -66,6 +72,10 @@ 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.

View file

@ -167,6 +167,57 @@ 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
@ -271,6 +322,14 @@ 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.