2021-03-04 07:35:17 +02:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2021-08-31 11:07:44 +03:00
|
|
|
package v1alpha1
|
2021-03-04 07:35:17 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
2021-05-04 10:27:41 +03:00
|
|
|
"sort"
|
2021-03-04 07:35:17 +02:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2023-12-13 11:38:06 +02:00
|
|
|
"golang.org/x/exp/maps"
|
2021-03-04 07:35:17 +02:00
|
|
|
"k8s.io/klog/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
var matchOps = map[MatchOp]struct{}{
|
2022-10-14 15:43:18 +03:00
|
|
|
MatchAny: {},
|
|
|
|
MatchIn: {},
|
|
|
|
MatchNotIn: {},
|
|
|
|
MatchInRegexp: {},
|
|
|
|
MatchExists: {},
|
|
|
|
MatchDoesNotExist: {},
|
|
|
|
MatchGt: {},
|
|
|
|
MatchLt: {},
|
|
|
|
MatchGtLt: {},
|
|
|
|
MatchIsTrue: {},
|
|
|
|
MatchIsFalse: {},
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// newMatchExpression returns a new MatchExpression instance.
|
|
|
|
func newMatchExpression(op MatchOp, values ...string) *MatchExpression {
|
|
|
|
return &MatchExpression{
|
|
|
|
Op: op,
|
|
|
|
Value: values,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate validates the expression.
|
|
|
|
func (m *MatchExpression) Validate() error {
|
|
|
|
if _, ok := matchOps[m.Op]; !ok {
|
|
|
|
return fmt.Errorf("invalid Op %q", m.Op)
|
|
|
|
}
|
|
|
|
switch m.Op {
|
|
|
|
case MatchExists, MatchDoesNotExist, MatchIsTrue, MatchIsFalse, MatchAny:
|
|
|
|
if len(m.Value) != 0 {
|
2022-10-14 15:43:18 +03:00
|
|
|
return fmt.Errorf("value must be empty for Op %q (have %v)", m.Op, m.Value)
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
|
|
|
case MatchGt, MatchLt:
|
|
|
|
if len(m.Value) != 1 {
|
2022-10-14 15:43:18 +03:00
|
|
|
return fmt.Errorf("value must contain exactly one element for Op %q (have %v)", m.Op, m.Value)
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
|
|
|
if _, err := strconv.Atoi(m.Value[0]); err != nil {
|
2022-10-14 15:43:18 +03:00
|
|
|
return fmt.Errorf("value must be an integer for Op %q (have %v)", m.Op, m.Value[0])
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
2021-08-26 15:01:52 +03:00
|
|
|
case MatchGtLt:
|
|
|
|
if len(m.Value) != 2 {
|
2022-10-14 15:43:18 +03:00
|
|
|
return fmt.Errorf("value must contain exactly two elements for Op %q (have %v)", m.Op, m.Value)
|
2021-08-26 15:01:52 +03:00
|
|
|
}
|
|
|
|
var err error
|
|
|
|
v := make([]int, 2)
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
if v[i], err = strconv.Atoi(m.Value[i]); err != nil {
|
2022-10-14 15:43:18 +03:00
|
|
|
return fmt.Errorf("value must contain integers for Op %q (have %v)", m.Op, m.Value)
|
2021-08-26 15:01:52 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if v[0] >= v[1] {
|
2022-10-14 15:43:18 +03:00
|
|
|
return fmt.Errorf("value[0] must be less than Value[1] for Op %q (have %v)", m.Op, m.Value)
|
2021-08-26 15:01:52 +03:00
|
|
|
}
|
2021-03-04 07:35:17 +02:00
|
|
|
case MatchInRegexp:
|
|
|
|
if len(m.Value) == 0 {
|
2022-10-14 15:43:18 +03:00
|
|
|
return fmt.Errorf("value must be non-empty for Op %q", m.Op)
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
2023-12-01 15:18:42 +02:00
|
|
|
for _, v := range m.Value {
|
|
|
|
_, err := regexp.Compile(v)
|
2021-03-04 07:35:17 +02:00
|
|
|
if err != nil {
|
2022-10-14 15:43:18 +03:00
|
|
|
return fmt.Errorf("value must only contain valid regexps for Op %q (have %v)", m.Op, m.Value)
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
if len(m.Value) == 0 {
|
2022-10-14 15:43:18 +03:00
|
|
|
return fmt.Errorf("value must be non-empty for Op %q", m.Op)
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Match evaluates the MatchExpression against a single input value.
|
|
|
|
func (m *MatchExpression) Match(valid bool, value interface{}) (bool, error) {
|
2023-12-01 09:09:59 +02:00
|
|
|
if _, ok := matchOps[m.Op]; !ok {
|
|
|
|
return false, fmt.Errorf("invalid Op %q", m.Op)
|
|
|
|
}
|
|
|
|
|
2021-03-04 07:35:17 +02:00
|
|
|
switch m.Op {
|
|
|
|
case MatchAny:
|
2023-12-01 09:09:59 +02:00
|
|
|
if len(m.Value) != 0 {
|
|
|
|
return false, fmt.Errorf("invalid expression, 'value' field must be empty for Op %q (have %v)", m.Op, m.Value)
|
|
|
|
}
|
2021-03-04 07:35:17 +02:00
|
|
|
return true, nil
|
|
|
|
case MatchExists:
|
2023-12-01 09:09:59 +02:00
|
|
|
if len(m.Value) != 0 {
|
|
|
|
return false, fmt.Errorf("invalid expression, 'value' field must be empty for Op %q (have %v)", m.Op, m.Value)
|
|
|
|
}
|
2021-03-04 07:35:17 +02:00
|
|
|
return valid, nil
|
|
|
|
case MatchDoesNotExist:
|
2023-12-01 09:09:59 +02:00
|
|
|
if len(m.Value) != 0 {
|
|
|
|
return false, fmt.Errorf("invalid expression, 'value' field must be empty for Op %q (have %v)", m.Op, m.Value)
|
|
|
|
}
|
2021-03-04 07:35:17 +02:00
|
|
|
return !valid, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if valid {
|
|
|
|
value := fmt.Sprintf("%v", value)
|
|
|
|
switch m.Op {
|
|
|
|
case MatchIn:
|
2023-12-01 09:09:59 +02:00
|
|
|
if len(m.Value) == 0 {
|
|
|
|
return false, fmt.Errorf("invalid expression, 'value' field must be non-empty for Op %q", m.Op)
|
|
|
|
}
|
2021-03-04 07:35:17 +02:00
|
|
|
for _, v := range m.Value {
|
|
|
|
if value == v {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case MatchNotIn:
|
2023-12-01 09:09:59 +02:00
|
|
|
if len(m.Value) == 0 {
|
|
|
|
return false, fmt.Errorf("invalid expression, 'value' field must be non-empty for Op %q", m.Op)
|
|
|
|
}
|
2021-03-04 07:35:17 +02:00
|
|
|
for _, v := range m.Value {
|
|
|
|
if value == v {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
case MatchInRegexp:
|
2023-12-01 09:09:59 +02:00
|
|
|
if len(m.Value) == 0 {
|
|
|
|
return false, fmt.Errorf("invalid expression, 'value' field must be non-empty for Op %q", m.Op)
|
|
|
|
}
|
2023-12-01 15:18:42 +02:00
|
|
|
valueRe := make([]*regexp.Regexp, len(m.Value))
|
|
|
|
for i, v := range m.Value {
|
|
|
|
re, err := regexp.Compile(v)
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("invalid expressiom, 'value' field must only contain valid regexps for Op %q (have %v)", m.Op, m.Value)
|
2023-12-01 09:09:59 +02:00
|
|
|
}
|
2023-12-01 15:18:42 +02:00
|
|
|
valueRe[i] = re
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
2023-12-01 15:18:42 +02:00
|
|
|
for _, re := range valueRe {
|
2021-03-04 07:35:17 +02:00
|
|
|
if re.MatchString(value) {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case MatchGt, MatchLt:
|
2023-12-01 09:09:59 +02:00
|
|
|
if len(m.Value) != 1 {
|
|
|
|
return false, fmt.Errorf("invalid expression, 'value' field must contain exactly one element for Op %q (have %v)", m.Op, m.Value)
|
|
|
|
}
|
|
|
|
|
2021-03-04 07:35:17 +02:00
|
|
|
l, err := strconv.Atoi(value)
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("not a number %q", value)
|
|
|
|
}
|
|
|
|
r, err := strconv.Atoi(m.Value[0])
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("not a number %q in %v", m.Value[0], m)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (l < r && m.Op == MatchLt) || (l > r && m.Op == MatchGt) {
|
|
|
|
return true, nil
|
|
|
|
}
|
2021-08-26 15:01:52 +03:00
|
|
|
case MatchGtLt:
|
2023-12-01 09:09:59 +02:00
|
|
|
if len(m.Value) != 2 {
|
|
|
|
return false, fmt.Errorf("invalid expression, value' field must contain exactly two elements for Op %q (have %v)", m.Op, m.Value)
|
|
|
|
}
|
2021-08-26 15:01:52 +03:00
|
|
|
v, err := strconv.Atoi(value)
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("not a number %q", value)
|
|
|
|
}
|
|
|
|
lr := make([]int, 2)
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
lr[i], err = strconv.Atoi(m.Value[i])
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("not a number %q in %v", m.Value[i], m)
|
|
|
|
}
|
|
|
|
}
|
2023-12-01 09:09:59 +02:00
|
|
|
if lr[0] >= lr[1] {
|
|
|
|
return false, fmt.Errorf("invalid expression, value[0] must be less than Value[1] for Op %q (have %v)", m.Op, m.Value)
|
|
|
|
}
|
2021-08-26 15:01:52 +03:00
|
|
|
return v > lr[0] && v < lr[1], nil
|
2021-03-04 07:35:17 +02:00
|
|
|
case MatchIsTrue:
|
2023-12-01 09:09:59 +02:00
|
|
|
if len(m.Value) != 0 {
|
|
|
|
return false, fmt.Errorf("invalid expression, 'value' field must be empty for Op %q (have %v)", m.Op, m.Value)
|
|
|
|
}
|
2021-03-04 07:35:17 +02:00
|
|
|
return value == "true", nil
|
|
|
|
case MatchIsFalse:
|
2023-12-01 09:09:59 +02:00
|
|
|
if len(m.Value) != 0 {
|
|
|
|
return false, fmt.Errorf("invalid expression, 'value' field must be empty for Op %q (have %v)", m.Op, m.Value)
|
|
|
|
}
|
2021-03-04 07:35:17 +02:00
|
|
|
return value == "false", nil
|
|
|
|
default:
|
|
|
|
return false, fmt.Errorf("unsupported Op %q", m.Op)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// MatchKeys evaluates the MatchExpression against a set of keys.
|
2022-07-04 10:56:35 +03:00
|
|
|
func (m *MatchExpression) MatchKeys(name string, keys map[string]Nil) (bool, error) {
|
2021-05-04 10:27:41 +03:00
|
|
|
matched := false
|
2021-03-04 07:35:17 +02:00
|
|
|
|
|
|
|
_, ok := keys[name]
|
|
|
|
switch m.Op {
|
|
|
|
case MatchAny:
|
2021-05-04 10:27:41 +03:00
|
|
|
matched = true
|
2021-03-04 07:35:17 +02:00
|
|
|
case MatchExists:
|
2021-05-04 10:27:41 +03:00
|
|
|
matched = ok
|
2021-03-04 07:35:17 +02:00
|
|
|
case MatchDoesNotExist:
|
2021-05-04 10:27:41 +03:00
|
|
|
matched = !ok
|
2021-03-04 07:35:17 +02:00
|
|
|
default:
|
|
|
|
return false, fmt.Errorf("invalid Op %q when matching keys", m.Op)
|
|
|
|
}
|
2021-05-04 10:27:41 +03:00
|
|
|
|
2023-05-03 15:59:39 +03:00
|
|
|
if klogV := klog.V(3); klogV.Enabled() {
|
2023-11-10 09:40:54 +02:00
|
|
|
klogV.InfoS("matched keys", "matchResult", matched, "matchKey", name, "matchOp", m.Op)
|
2023-05-03 15:59:39 +03:00
|
|
|
} else if klogV := klog.V(4); klogV.Enabled() {
|
2023-12-13 11:38:06 +02:00
|
|
|
k := maps.Keys(keys)
|
2021-05-04 10:27:41 +03:00
|
|
|
sort.Strings(k)
|
2023-11-10 09:40:54 +02:00
|
|
|
klogV.InfoS("matched keys", "matchResult", matched, "matchKey", name, "matchOp", m.Op, "inputKeys", k)
|
2021-05-04 10:27:41 +03:00
|
|
|
}
|
|
|
|
return matched, nil
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// MatchValues evaluates the MatchExpression against a set of key-value pairs.
|
|
|
|
func (m *MatchExpression) MatchValues(name string, values map[string]string) (bool, error) {
|
|
|
|
v, ok := values[name]
|
2021-05-04 10:27:41 +03:00
|
|
|
matched, err := m.Match(ok, v)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
2023-05-03 15:59:39 +03:00
|
|
|
if klogV := klog.V(3); klogV.Enabled() {
|
2023-11-10 09:40:54 +02:00
|
|
|
klogV.InfoS("matched values", "matchResult", matched, "matchKey", name, "matchOp", m.Op, "matchValue", m.Value)
|
2023-05-03 15:59:39 +03:00
|
|
|
} else if klogV := klog.V(4); klogV.Enabled() {
|
2023-11-10 09:40:54 +02:00
|
|
|
klogV.InfoS("matched values", "matchResult", matched, "matchKey", name, "matchOp", m.Op, "matchValue", m.Value, "inputValues", values)
|
2021-05-04 10:27:41 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return matched, nil
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
|
|
|
|
2022-03-17 18:30:32 +02:00
|
|
|
// MatchKeyNames evaluates the MatchExpression against names of a set of key features.
|
|
|
|
func (m *MatchExpression) MatchKeyNames(keys map[string]Nil) (bool, []MatchedElement, error) {
|
|
|
|
ret := []MatchedElement{}
|
|
|
|
|
|
|
|
for k := range keys {
|
|
|
|
if match, err := m.Match(true, k); err != nil {
|
|
|
|
return false, nil, err
|
|
|
|
} else if match {
|
|
|
|
ret = append(ret, MatchedElement{"Name": k})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Sort for reproducible output
|
|
|
|
sort.Slice(ret, func(i, j int) bool { return ret[i]["Name"] < ret[j]["Name"] })
|
|
|
|
|
|
|
|
if klogV3 := klog.V(3); klogV3.Enabled() {
|
|
|
|
mk := make([]string, len(ret))
|
|
|
|
for i, v := range ret {
|
|
|
|
mk[i] = v["Name"]
|
|
|
|
}
|
|
|
|
mkMsg := strings.Join(mk, ", ")
|
|
|
|
|
|
|
|
if klogV4 := klog.V(4); klogV4.Enabled() {
|
|
|
|
k := make([]string, 0, len(keys))
|
|
|
|
for n := range keys {
|
|
|
|
k = append(k, n)
|
|
|
|
}
|
|
|
|
sort.Strings(k)
|
|
|
|
klogV3.InfoS("matched (key) names", "matchResult", mkMsg, "matchOp", m.Op, "matchValue", m.Value, "inputKeys", k)
|
|
|
|
} else {
|
|
|
|
klogV3.InfoS("matched (key) names", "matchResult", mkMsg, "matchOp", m.Op, "matchValue", m.Value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return len(ret) > 0, ret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// MatchValueNames evaluates the MatchExpression against names of a set of value features.
|
|
|
|
func (m *MatchExpression) MatchValueNames(values map[string]string) (bool, []MatchedElement, error) {
|
|
|
|
ret := []MatchedElement{}
|
|
|
|
|
|
|
|
for k, v := range values {
|
|
|
|
if match, err := m.Match(true, k); err != nil {
|
|
|
|
return false, nil, err
|
|
|
|
} else if match {
|
|
|
|
ret = append(ret, MatchedElement{"Name": k, "Value": v})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Sort for reproducible output
|
|
|
|
sort.Slice(ret, func(i, j int) bool { return ret[i]["Name"] < ret[j]["Name"] })
|
|
|
|
|
|
|
|
if klogV3 := klog.V(3); klogV3.Enabled() {
|
|
|
|
mk := make([]string, len(ret))
|
|
|
|
for i, v := range ret {
|
|
|
|
mk[i] = v["Name"]
|
|
|
|
}
|
|
|
|
mkMsg := strings.Join(mk, ", ")
|
|
|
|
|
|
|
|
if klogV4 := klog.V(4); klogV4.Enabled() {
|
|
|
|
klogV3.InfoS("matched (value) names", "matchResult", mkMsg, "matchOp", m.Op, "matchValue", m.Value, "inputValues", values)
|
|
|
|
} else {
|
|
|
|
klogV3.InfoS("matched (value) names", "matchResult", mkMsg, "matchOp", m.Op, "matchValue", m.Value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return len(ret) > 0, ret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// MatchInstanceAttributeNames evaluates the MatchExpression against a set of
|
|
|
|
// instance features, matching against the names of their attributes.
|
|
|
|
func (m *MatchExpression) MatchInstanceAttributeNames(instances []InstanceFeature) ([]MatchedElement, error) {
|
|
|
|
ret := []MatchedElement{}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-03-04 07:35:17 +02:00
|
|
|
// matchExpression is a helper type for unmarshalling MatchExpression
|
|
|
|
type matchExpression MatchExpression
|
|
|
|
|
|
|
|
// UnmarshalJSON implements the Unmarshaler interface of "encoding/json"
|
|
|
|
func (m *MatchExpression) UnmarshalJSON(data []byte) error {
|
|
|
|
raw := new(interface{})
|
|
|
|
|
|
|
|
err := json.Unmarshal(data, raw)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch v := (*raw).(type) {
|
|
|
|
case string:
|
|
|
|
*m = *newMatchExpression(MatchIn, v)
|
|
|
|
case bool:
|
|
|
|
*m = *newMatchExpression(MatchIn, strconv.FormatBool(v))
|
|
|
|
case float64:
|
|
|
|
*m = *newMatchExpression(MatchIn, strconv.FormatFloat(v, 'f', -1, 64))
|
|
|
|
case []interface{}:
|
|
|
|
values := make([]string, len(v))
|
|
|
|
for i, value := range v {
|
|
|
|
str, ok := value.(string)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("invalid value %v in %v", value, v)
|
|
|
|
}
|
|
|
|
values[i] = str
|
|
|
|
}
|
|
|
|
*m = *newMatchExpression(MatchIn, values...)
|
|
|
|
case map[string]interface{}:
|
|
|
|
helper := &matchExpression{}
|
|
|
|
if err := json.Unmarshal(data, &helper); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
*m = *newMatchExpression(helper.Op, helper.Value...)
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("invalid rule '%v' (%T)", v, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
return m.Validate()
|
|
|
|
}
|
|
|
|
|
|
|
|
// MatchKeys evaluates the MatchExpressionSet against a set of keys.
|
2022-07-04 10:56:35 +03:00
|
|
|
func (m *MatchExpressionSet) MatchKeys(keys map[string]Nil) (bool, error) {
|
2022-03-17 13:11:23 +02:00
|
|
|
matched, _, err := m.MatchGetKeys(keys)
|
|
|
|
return matched, err
|
pkg/apis/nfd: support label name templating
Support templating of label names in feature rules. It is available both
in NodeFeatureRule CRs and in custom rule configuration of nfd-worker.
This patch adds a new 'labelsTemplate' field to the rule spec, making it
possible to dynamically generate multiple labels per rule based on the
matched features. The feature relies on the golang "text/template"
package. When expanded, the template must contain labels in a raw
<key>[=<value>] format (where 'value' defaults to "true"), separated by
newlines i.e.:
- name: <rule-name>
labelsTemplate: |
<label-1>[=<value-1>]
<label-2>[=<value-2>]
...
All the matched features of 'matchFeatures' directives are available for
templating engine in a nested data structure that can be described in
yaml as:
.
<domain-1>:
<key-feature-1>:
- Name: <matched-key>
- ...
<value-feature-1:
- Name: <matched-key>
Value: <matched-value>
- ...
<instance-feature-1>:
- <attribute-1-name>: <attribute-1-value>
<attribute-2-name>: <attribute-2-value>
...
- ...
<domain-2>:
...
That is, the per-feature data available for matching depends on the type
of feature that was matched:
- "key features": only 'Name' is available
- "value features": 'Name' and 'Value' can be used
- "instance features": all attributes of the matched instance are
available
NOTE: In case of matchAny is specified, the template is executed
separately against each individual matchFeatures matcher and the
eventual set of labels is a superset of all these expansions. Consider
the following:
- name: <name>
labelsTemplate: <template>
matchAny:
- matchFeatures: <matcher#1>
- matchFeatures: <matcher#2>
matchFeatures: <matcher#3>
In the example above (assuming the overall result is a match) the
template would be executed on matcher#1 and/or matcher#2 (depending on
whether both or only one of them match), and finally on matcher#3, and
all the labels from these separate expansions would be created (i.e. the
end result would be a union of all the individual expansions).
NOTE 2: The 'labels' field has priority over 'labelsTemplate', i.e.
labels specified in the 'labels' field will override any labels
originating from the 'labelsTemplate' field.
A special case of an empty match expression set matches everything (i.e.
matches/returns all existing keys/values). This makes it simpler to
write templates that run over all values. Also, makes it possible to
later implement support for templates that run over all _keys_ of a
feature.
Some example configurations:
- name: "my-pci-template-features"
labelsTemplate: |
{{ range .pci.device }}intel-{{ .class }}-{{ .device }}=present
{{ end }}
matchFeatures:
- feature: pci.device
matchExpressions:
class: {op: InRegexp, value: ["^06"]}
vendor: ["8086"]
- name: "my-system-template-features"
labelsTemplate: |
{{ range .system.osrelease }}system-{{ .Name }}={{ .Value }}
{{ end }}
matchFeatures:
- feature: system.osRelease
matchExpressions:
ID: {op: Exists}
VERSION_ID.major: {op: Exists}
Imaginative template pipelines are possible, of course, but care must be
taken in order to produce understandable and maintainable rule sets.
2021-05-04 16:30:06 +03:00
|
|
|
}
|
|
|
|
|
2023-11-22 09:45:48 +02:00
|
|
|
// MatchedElement holds one matched Instance.
|
|
|
|
// +k8s:deepcopy-gen=false
|
|
|
|
type MatchedElement map[string]string
|
pkg/apis/nfd: support label name templating
Support templating of label names in feature rules. It is available both
in NodeFeatureRule CRs and in custom rule configuration of nfd-worker.
This patch adds a new 'labelsTemplate' field to the rule spec, making it
possible to dynamically generate multiple labels per rule based on the
matched features. The feature relies on the golang "text/template"
package. When expanded, the template must contain labels in a raw
<key>[=<value>] format (where 'value' defaults to "true"), separated by
newlines i.e.:
- name: <rule-name>
labelsTemplate: |
<label-1>[=<value-1>]
<label-2>[=<value-2>]
...
All the matched features of 'matchFeatures' directives are available for
templating engine in a nested data structure that can be described in
yaml as:
.
<domain-1>:
<key-feature-1>:
- Name: <matched-key>
- ...
<value-feature-1:
- Name: <matched-key>
Value: <matched-value>
- ...
<instance-feature-1>:
- <attribute-1-name>: <attribute-1-value>
<attribute-2-name>: <attribute-2-value>
...
- ...
<domain-2>:
...
That is, the per-feature data available for matching depends on the type
of feature that was matched:
- "key features": only 'Name' is available
- "value features": 'Name' and 'Value' can be used
- "instance features": all attributes of the matched instance are
available
NOTE: In case of matchAny is specified, the template is executed
separately against each individual matchFeatures matcher and the
eventual set of labels is a superset of all these expansions. Consider
the following:
- name: <name>
labelsTemplate: <template>
matchAny:
- matchFeatures: <matcher#1>
- matchFeatures: <matcher#2>
matchFeatures: <matcher#3>
In the example above (assuming the overall result is a match) the
template would be executed on matcher#1 and/or matcher#2 (depending on
whether both or only one of them match), and finally on matcher#3, and
all the labels from these separate expansions would be created (i.e. the
end result would be a union of all the individual expansions).
NOTE 2: The 'labels' field has priority over 'labelsTemplate', i.e.
labels specified in the 'labels' field will override any labels
originating from the 'labelsTemplate' field.
A special case of an empty match expression set matches everything (i.e.
matches/returns all existing keys/values). This makes it simpler to
write templates that run over all values. Also, makes it possible to
later implement support for templates that run over all _keys_ of a
feature.
Some example configurations:
- name: "my-pci-template-features"
labelsTemplate: |
{{ range .pci.device }}intel-{{ .class }}-{{ .device }}=present
{{ end }}
matchFeatures:
- feature: pci.device
matchExpressions:
class: {op: InRegexp, value: ["^06"]}
vendor: ["8086"]
- name: "my-system-template-features"
labelsTemplate: |
{{ range .system.osrelease }}system-{{ .Name }}={{ .Value }}
{{ end }}
matchFeatures:
- feature: system.osRelease
matchExpressions:
ID: {op: Exists}
VERSION_ID.major: {op: Exists}
Imaginative template pipelines are possible, of course, but care must be
taken in order to produce understandable and maintainable rule sets.
2021-05-04 16:30:06 +03:00
|
|
|
|
|
|
|
// MatchGetKeys evaluates the MatchExpressionSet against a set of keys and
|
2023-11-20 10:11:35 +02:00
|
|
|
// returns all matched keys or nil if no match was found. Note that an empty
|
|
|
|
// MatchExpressionSet returns a match with an empty slice of matched features.
|
2023-11-22 09:45:48 +02:00
|
|
|
func (m *MatchExpressionSet) MatchGetKeys(keys map[string]Nil) (bool, []MatchedElement, error) {
|
|
|
|
ret := make([]MatchedElement, 0, len(*m))
|
pkg/apis/nfd: support label name templating
Support templating of label names in feature rules. It is available both
in NodeFeatureRule CRs and in custom rule configuration of nfd-worker.
This patch adds a new 'labelsTemplate' field to the rule spec, making it
possible to dynamically generate multiple labels per rule based on the
matched features. The feature relies on the golang "text/template"
package. When expanded, the template must contain labels in a raw
<key>[=<value>] format (where 'value' defaults to "true"), separated by
newlines i.e.:
- name: <rule-name>
labelsTemplate: |
<label-1>[=<value-1>]
<label-2>[=<value-2>]
...
All the matched features of 'matchFeatures' directives are available for
templating engine in a nested data structure that can be described in
yaml as:
.
<domain-1>:
<key-feature-1>:
- Name: <matched-key>
- ...
<value-feature-1:
- Name: <matched-key>
Value: <matched-value>
- ...
<instance-feature-1>:
- <attribute-1-name>: <attribute-1-value>
<attribute-2-name>: <attribute-2-value>
...
- ...
<domain-2>:
...
That is, the per-feature data available for matching depends on the type
of feature that was matched:
- "key features": only 'Name' is available
- "value features": 'Name' and 'Value' can be used
- "instance features": all attributes of the matched instance are
available
NOTE: In case of matchAny is specified, the template is executed
separately against each individual matchFeatures matcher and the
eventual set of labels is a superset of all these expansions. Consider
the following:
- name: <name>
labelsTemplate: <template>
matchAny:
- matchFeatures: <matcher#1>
- matchFeatures: <matcher#2>
matchFeatures: <matcher#3>
In the example above (assuming the overall result is a match) the
template would be executed on matcher#1 and/or matcher#2 (depending on
whether both or only one of them match), and finally on matcher#3, and
all the labels from these separate expansions would be created (i.e. the
end result would be a union of all the individual expansions).
NOTE 2: The 'labels' field has priority over 'labelsTemplate', i.e.
labels specified in the 'labels' field will override any labels
originating from the 'labelsTemplate' field.
A special case of an empty match expression set matches everything (i.e.
matches/returns all existing keys/values). This makes it simpler to
write templates that run over all values. Also, makes it possible to
later implement support for templates that run over all _keys_ of a
feature.
Some example configurations:
- name: "my-pci-template-features"
labelsTemplate: |
{{ range .pci.device }}intel-{{ .class }}-{{ .device }}=present
{{ end }}
matchFeatures:
- feature: pci.device
matchExpressions:
class: {op: InRegexp, value: ["^06"]}
vendor: ["8086"]
- name: "my-system-template-features"
labelsTemplate: |
{{ range .system.osrelease }}system-{{ .Name }}={{ .Value }}
{{ end }}
matchFeatures:
- feature: system.osRelease
matchExpressions:
ID: {op: Exists}
VERSION_ID.major: {op: Exists}
Imaginative template pipelines are possible, of course, but care must be
taken in order to produce understandable and maintainable rule sets.
2021-05-04 16:30:06 +03:00
|
|
|
|
2022-07-07 20:57:58 +03:00
|
|
|
for n, e := range *m {
|
2021-03-04 07:35:17 +02:00
|
|
|
match, err := e.MatchKeys(n, keys)
|
|
|
|
if err != nil {
|
2022-03-17 13:11:23 +02:00
|
|
|
return false, nil, err
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
|
|
|
if !match {
|
2022-03-17 13:11:23 +02:00
|
|
|
return false, nil, nil
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
2023-11-22 09:45:48 +02:00
|
|
|
ret = append(ret, MatchedElement{"Name": n})
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
2023-11-24 16:27:38 +02:00
|
|
|
// Sort for reproducible output
|
|
|
|
sort.Slice(ret, func(i, j int) bool { return ret[i]["Name"] < ret[j]["Name"] })
|
2022-03-17 13:11:23 +02:00
|
|
|
return true, ret, nil
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// MatchValues evaluates the MatchExpressionSet against a set of key-value pairs.
|
|
|
|
func (m *MatchExpressionSet) MatchValues(values map[string]string) (bool, error) {
|
2022-03-17 13:11:23 +02:00
|
|
|
matched, _, err := m.MatchGetValues(values)
|
|
|
|
return matched, err
|
pkg/apis/nfd: support label name templating
Support templating of label names in feature rules. It is available both
in NodeFeatureRule CRs and in custom rule configuration of nfd-worker.
This patch adds a new 'labelsTemplate' field to the rule spec, making it
possible to dynamically generate multiple labels per rule based on the
matched features. The feature relies on the golang "text/template"
package. When expanded, the template must contain labels in a raw
<key>[=<value>] format (where 'value' defaults to "true"), separated by
newlines i.e.:
- name: <rule-name>
labelsTemplate: |
<label-1>[=<value-1>]
<label-2>[=<value-2>]
...
All the matched features of 'matchFeatures' directives are available for
templating engine in a nested data structure that can be described in
yaml as:
.
<domain-1>:
<key-feature-1>:
- Name: <matched-key>
- ...
<value-feature-1:
- Name: <matched-key>
Value: <matched-value>
- ...
<instance-feature-1>:
- <attribute-1-name>: <attribute-1-value>
<attribute-2-name>: <attribute-2-value>
...
- ...
<domain-2>:
...
That is, the per-feature data available for matching depends on the type
of feature that was matched:
- "key features": only 'Name' is available
- "value features": 'Name' and 'Value' can be used
- "instance features": all attributes of the matched instance are
available
NOTE: In case of matchAny is specified, the template is executed
separately against each individual matchFeatures matcher and the
eventual set of labels is a superset of all these expansions. Consider
the following:
- name: <name>
labelsTemplate: <template>
matchAny:
- matchFeatures: <matcher#1>
- matchFeatures: <matcher#2>
matchFeatures: <matcher#3>
In the example above (assuming the overall result is a match) the
template would be executed on matcher#1 and/or matcher#2 (depending on
whether both or only one of them match), and finally on matcher#3, and
all the labels from these separate expansions would be created (i.e. the
end result would be a union of all the individual expansions).
NOTE 2: The 'labels' field has priority over 'labelsTemplate', i.e.
labels specified in the 'labels' field will override any labels
originating from the 'labelsTemplate' field.
A special case of an empty match expression set matches everything (i.e.
matches/returns all existing keys/values). This makes it simpler to
write templates that run over all values. Also, makes it possible to
later implement support for templates that run over all _keys_ of a
feature.
Some example configurations:
- name: "my-pci-template-features"
labelsTemplate: |
{{ range .pci.device }}intel-{{ .class }}-{{ .device }}=present
{{ end }}
matchFeatures:
- feature: pci.device
matchExpressions:
class: {op: InRegexp, value: ["^06"]}
vendor: ["8086"]
- name: "my-system-template-features"
labelsTemplate: |
{{ range .system.osrelease }}system-{{ .Name }}={{ .Value }}
{{ end }}
matchFeatures:
- feature: system.osRelease
matchExpressions:
ID: {op: Exists}
VERSION_ID.major: {op: Exists}
Imaginative template pipelines are possible, of course, but care must be
taken in order to produce understandable and maintainable rule sets.
2021-05-04 16:30:06 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// MatchGetValues evaluates the MatchExpressionSet against a set of key-value
|
2023-11-20 10:11:35 +02:00
|
|
|
// pairs and returns all matched key-value pairs. Note that an empty
|
|
|
|
// MatchExpressionSet returns a match with an empty slice of matched features.
|
2023-11-22 09:45:48 +02:00
|
|
|
func (m *MatchExpressionSet) MatchGetValues(values map[string]string) (bool, []MatchedElement, error) {
|
|
|
|
ret := make([]MatchedElement, 0, len(*m))
|
pkg/apis/nfd: support label name templating
Support templating of label names in feature rules. It is available both
in NodeFeatureRule CRs and in custom rule configuration of nfd-worker.
This patch adds a new 'labelsTemplate' field to the rule spec, making it
possible to dynamically generate multiple labels per rule based on the
matched features. The feature relies on the golang "text/template"
package. When expanded, the template must contain labels in a raw
<key>[=<value>] format (where 'value' defaults to "true"), separated by
newlines i.e.:
- name: <rule-name>
labelsTemplate: |
<label-1>[=<value-1>]
<label-2>[=<value-2>]
...
All the matched features of 'matchFeatures' directives are available for
templating engine in a nested data structure that can be described in
yaml as:
.
<domain-1>:
<key-feature-1>:
- Name: <matched-key>
- ...
<value-feature-1:
- Name: <matched-key>
Value: <matched-value>
- ...
<instance-feature-1>:
- <attribute-1-name>: <attribute-1-value>
<attribute-2-name>: <attribute-2-value>
...
- ...
<domain-2>:
...
That is, the per-feature data available for matching depends on the type
of feature that was matched:
- "key features": only 'Name' is available
- "value features": 'Name' and 'Value' can be used
- "instance features": all attributes of the matched instance are
available
NOTE: In case of matchAny is specified, the template is executed
separately against each individual matchFeatures matcher and the
eventual set of labels is a superset of all these expansions. Consider
the following:
- name: <name>
labelsTemplate: <template>
matchAny:
- matchFeatures: <matcher#1>
- matchFeatures: <matcher#2>
matchFeatures: <matcher#3>
In the example above (assuming the overall result is a match) the
template would be executed on matcher#1 and/or matcher#2 (depending on
whether both or only one of them match), and finally on matcher#3, and
all the labels from these separate expansions would be created (i.e. the
end result would be a union of all the individual expansions).
NOTE 2: The 'labels' field has priority over 'labelsTemplate', i.e.
labels specified in the 'labels' field will override any labels
originating from the 'labelsTemplate' field.
A special case of an empty match expression set matches everything (i.e.
matches/returns all existing keys/values). This makes it simpler to
write templates that run over all values. Also, makes it possible to
later implement support for templates that run over all _keys_ of a
feature.
Some example configurations:
- name: "my-pci-template-features"
labelsTemplate: |
{{ range .pci.device }}intel-{{ .class }}-{{ .device }}=present
{{ end }}
matchFeatures:
- feature: pci.device
matchExpressions:
class: {op: InRegexp, value: ["^06"]}
vendor: ["8086"]
- name: "my-system-template-features"
labelsTemplate: |
{{ range .system.osrelease }}system-{{ .Name }}={{ .Value }}
{{ end }}
matchFeatures:
- feature: system.osRelease
matchExpressions:
ID: {op: Exists}
VERSION_ID.major: {op: Exists}
Imaginative template pipelines are possible, of course, but care must be
taken in order to produce understandable and maintainable rule sets.
2021-05-04 16:30:06 +03:00
|
|
|
|
2022-07-07 20:57:58 +03:00
|
|
|
for n, e := range *m {
|
2021-03-04 07:35:17 +02:00
|
|
|
match, err := e.MatchValues(n, values)
|
|
|
|
if err != nil {
|
2022-03-17 13:11:23 +02:00
|
|
|
return false, nil, err
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
|
|
|
if !match {
|
2022-03-17 13:11:23 +02:00
|
|
|
return false, nil, nil
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
2023-11-22 09:45:48 +02:00
|
|
|
ret = append(ret, MatchedElement{"Name": n, "Value": values[n]})
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
2023-11-24 16:27:38 +02:00
|
|
|
// Sort for reproducible output
|
|
|
|
sort.Slice(ret, func(i, j int) bool { return ret[i]["Name"] < ret[j]["Name"] })
|
2022-03-17 13:11:23 +02:00
|
|
|
return true, ret, nil
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// MatchInstances evaluates the MatchExpressionSet against a set of instance
|
|
|
|
// features, each of which is an individual set of key-value pairs
|
|
|
|
// (attributes).
|
2022-07-04 10:56:35 +03:00
|
|
|
func (m *MatchExpressionSet) MatchInstances(instances []InstanceFeature) (bool, error) {
|
pkg/apis/nfd: support label name templating
Support templating of label names in feature rules. It is available both
in NodeFeatureRule CRs and in custom rule configuration of nfd-worker.
This patch adds a new 'labelsTemplate' field to the rule spec, making it
possible to dynamically generate multiple labels per rule based on the
matched features. The feature relies on the golang "text/template"
package. When expanded, the template must contain labels in a raw
<key>[=<value>] format (where 'value' defaults to "true"), separated by
newlines i.e.:
- name: <rule-name>
labelsTemplate: |
<label-1>[=<value-1>]
<label-2>[=<value-2>]
...
All the matched features of 'matchFeatures' directives are available for
templating engine in a nested data structure that can be described in
yaml as:
.
<domain-1>:
<key-feature-1>:
- Name: <matched-key>
- ...
<value-feature-1:
- Name: <matched-key>
Value: <matched-value>
- ...
<instance-feature-1>:
- <attribute-1-name>: <attribute-1-value>
<attribute-2-name>: <attribute-2-value>
...
- ...
<domain-2>:
...
That is, the per-feature data available for matching depends on the type
of feature that was matched:
- "key features": only 'Name' is available
- "value features": 'Name' and 'Value' can be used
- "instance features": all attributes of the matched instance are
available
NOTE: In case of matchAny is specified, the template is executed
separately against each individual matchFeatures matcher and the
eventual set of labels is a superset of all these expansions. Consider
the following:
- name: <name>
labelsTemplate: <template>
matchAny:
- matchFeatures: <matcher#1>
- matchFeatures: <matcher#2>
matchFeatures: <matcher#3>
In the example above (assuming the overall result is a match) the
template would be executed on matcher#1 and/or matcher#2 (depending on
whether both or only one of them match), and finally on matcher#3, and
all the labels from these separate expansions would be created (i.e. the
end result would be a union of all the individual expansions).
NOTE 2: The 'labels' field has priority over 'labelsTemplate', i.e.
labels specified in the 'labels' field will override any labels
originating from the 'labelsTemplate' field.
A special case of an empty match expression set matches everything (i.e.
matches/returns all existing keys/values). This makes it simpler to
write templates that run over all values. Also, makes it possible to
later implement support for templates that run over all _keys_ of a
feature.
Some example configurations:
- name: "my-pci-template-features"
labelsTemplate: |
{{ range .pci.device }}intel-{{ .class }}-{{ .device }}=present
{{ end }}
matchFeatures:
- feature: pci.device
matchExpressions:
class: {op: InRegexp, value: ["^06"]}
vendor: ["8086"]
- name: "my-system-template-features"
labelsTemplate: |
{{ range .system.osrelease }}system-{{ .Name }}={{ .Value }}
{{ end }}
matchFeatures:
- feature: system.osRelease
matchExpressions:
ID: {op: Exists}
VERSION_ID.major: {op: Exists}
Imaginative template pipelines are possible, of course, but care must be
taken in order to produce understandable and maintainable rule sets.
2021-05-04 16:30:06 +03:00
|
|
|
v, err := m.MatchGetInstances(instances)
|
|
|
|
return len(v) > 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2023-11-22 09:45:48 +02:00
|
|
|
func (m *MatchExpressionSet) MatchGetInstances(instances []InstanceFeature) ([]MatchedElement, error) {
|
|
|
|
ret := []MatchedElement{}
|
pkg/apis/nfd: support label name templating
Support templating of label names in feature rules. It is available both
in NodeFeatureRule CRs and in custom rule configuration of nfd-worker.
This patch adds a new 'labelsTemplate' field to the rule spec, making it
possible to dynamically generate multiple labels per rule based on the
matched features. The feature relies on the golang "text/template"
package. When expanded, the template must contain labels in a raw
<key>[=<value>] format (where 'value' defaults to "true"), separated by
newlines i.e.:
- name: <rule-name>
labelsTemplate: |
<label-1>[=<value-1>]
<label-2>[=<value-2>]
...
All the matched features of 'matchFeatures' directives are available for
templating engine in a nested data structure that can be described in
yaml as:
.
<domain-1>:
<key-feature-1>:
- Name: <matched-key>
- ...
<value-feature-1:
- Name: <matched-key>
Value: <matched-value>
- ...
<instance-feature-1>:
- <attribute-1-name>: <attribute-1-value>
<attribute-2-name>: <attribute-2-value>
...
- ...
<domain-2>:
...
That is, the per-feature data available for matching depends on the type
of feature that was matched:
- "key features": only 'Name' is available
- "value features": 'Name' and 'Value' can be used
- "instance features": all attributes of the matched instance are
available
NOTE: In case of matchAny is specified, the template is executed
separately against each individual matchFeatures matcher and the
eventual set of labels is a superset of all these expansions. Consider
the following:
- name: <name>
labelsTemplate: <template>
matchAny:
- matchFeatures: <matcher#1>
- matchFeatures: <matcher#2>
matchFeatures: <matcher#3>
In the example above (assuming the overall result is a match) the
template would be executed on matcher#1 and/or matcher#2 (depending on
whether both or only one of them match), and finally on matcher#3, and
all the labels from these separate expansions would be created (i.e. the
end result would be a union of all the individual expansions).
NOTE 2: The 'labels' field has priority over 'labelsTemplate', i.e.
labels specified in the 'labels' field will override any labels
originating from the 'labelsTemplate' field.
A special case of an empty match expression set matches everything (i.e.
matches/returns all existing keys/values). This makes it simpler to
write templates that run over all values. Also, makes it possible to
later implement support for templates that run over all _keys_ of a
feature.
Some example configurations:
- name: "my-pci-template-features"
labelsTemplate: |
{{ range .pci.device }}intel-{{ .class }}-{{ .device }}=present
{{ end }}
matchFeatures:
- feature: pci.device
matchExpressions:
class: {op: InRegexp, value: ["^06"]}
vendor: ["8086"]
- name: "my-system-template-features"
labelsTemplate: |
{{ range .system.osrelease }}system-{{ .Name }}={{ .Value }}
{{ end }}
matchFeatures:
- feature: system.osRelease
matchExpressions:
ID: {op: Exists}
VERSION_ID.major: {op: Exists}
Imaginative template pipelines are possible, of course, but care must be
taken in order to produce understandable and maintainable rule sets.
2021-05-04 16:30:06 +03:00
|
|
|
|
2021-03-04 07:35:17 +02:00
|
|
|
for _, i := range instances {
|
|
|
|
if match, err := m.MatchValues(i.Attributes); err != nil {
|
pkg/apis/nfd: support label name templating
Support templating of label names in feature rules. It is available both
in NodeFeatureRule CRs and in custom rule configuration of nfd-worker.
This patch adds a new 'labelsTemplate' field to the rule spec, making it
possible to dynamically generate multiple labels per rule based on the
matched features. The feature relies on the golang "text/template"
package. When expanded, the template must contain labels in a raw
<key>[=<value>] format (where 'value' defaults to "true"), separated by
newlines i.e.:
- name: <rule-name>
labelsTemplate: |
<label-1>[=<value-1>]
<label-2>[=<value-2>]
...
All the matched features of 'matchFeatures' directives are available for
templating engine in a nested data structure that can be described in
yaml as:
.
<domain-1>:
<key-feature-1>:
- Name: <matched-key>
- ...
<value-feature-1:
- Name: <matched-key>
Value: <matched-value>
- ...
<instance-feature-1>:
- <attribute-1-name>: <attribute-1-value>
<attribute-2-name>: <attribute-2-value>
...
- ...
<domain-2>:
...
That is, the per-feature data available for matching depends on the type
of feature that was matched:
- "key features": only 'Name' is available
- "value features": 'Name' and 'Value' can be used
- "instance features": all attributes of the matched instance are
available
NOTE: In case of matchAny is specified, the template is executed
separately against each individual matchFeatures matcher and the
eventual set of labels is a superset of all these expansions. Consider
the following:
- name: <name>
labelsTemplate: <template>
matchAny:
- matchFeatures: <matcher#1>
- matchFeatures: <matcher#2>
matchFeatures: <matcher#3>
In the example above (assuming the overall result is a match) the
template would be executed on matcher#1 and/or matcher#2 (depending on
whether both or only one of them match), and finally on matcher#3, and
all the labels from these separate expansions would be created (i.e. the
end result would be a union of all the individual expansions).
NOTE 2: The 'labels' field has priority over 'labelsTemplate', i.e.
labels specified in the 'labels' field will override any labels
originating from the 'labelsTemplate' field.
A special case of an empty match expression set matches everything (i.e.
matches/returns all existing keys/values). This makes it simpler to
write templates that run over all values. Also, makes it possible to
later implement support for templates that run over all _keys_ of a
feature.
Some example configurations:
- name: "my-pci-template-features"
labelsTemplate: |
{{ range .pci.device }}intel-{{ .class }}-{{ .device }}=present
{{ end }}
matchFeatures:
- feature: pci.device
matchExpressions:
class: {op: InRegexp, value: ["^06"]}
vendor: ["8086"]
- name: "my-system-template-features"
labelsTemplate: |
{{ range .system.osrelease }}system-{{ .Name }}={{ .Value }}
{{ end }}
matchFeatures:
- feature: system.osRelease
matchExpressions:
ID: {op: Exists}
VERSION_ID.major: {op: Exists}
Imaginative template pipelines are possible, of course, but care must be
taken in order to produce understandable and maintainable rule sets.
2021-05-04 16:30:06 +03:00
|
|
|
return nil, err
|
2021-03-04 07:35:17 +02:00
|
|
|
} else if match {
|
pkg/apis/nfd: support label name templating
Support templating of label names in feature rules. It is available both
in NodeFeatureRule CRs and in custom rule configuration of nfd-worker.
This patch adds a new 'labelsTemplate' field to the rule spec, making it
possible to dynamically generate multiple labels per rule based on the
matched features. The feature relies on the golang "text/template"
package. When expanded, the template must contain labels in a raw
<key>[=<value>] format (where 'value' defaults to "true"), separated by
newlines i.e.:
- name: <rule-name>
labelsTemplate: |
<label-1>[=<value-1>]
<label-2>[=<value-2>]
...
All the matched features of 'matchFeatures' directives are available for
templating engine in a nested data structure that can be described in
yaml as:
.
<domain-1>:
<key-feature-1>:
- Name: <matched-key>
- ...
<value-feature-1:
- Name: <matched-key>
Value: <matched-value>
- ...
<instance-feature-1>:
- <attribute-1-name>: <attribute-1-value>
<attribute-2-name>: <attribute-2-value>
...
- ...
<domain-2>:
...
That is, the per-feature data available for matching depends on the type
of feature that was matched:
- "key features": only 'Name' is available
- "value features": 'Name' and 'Value' can be used
- "instance features": all attributes of the matched instance are
available
NOTE: In case of matchAny is specified, the template is executed
separately against each individual matchFeatures matcher and the
eventual set of labels is a superset of all these expansions. Consider
the following:
- name: <name>
labelsTemplate: <template>
matchAny:
- matchFeatures: <matcher#1>
- matchFeatures: <matcher#2>
matchFeatures: <matcher#3>
In the example above (assuming the overall result is a match) the
template would be executed on matcher#1 and/or matcher#2 (depending on
whether both or only one of them match), and finally on matcher#3, and
all the labels from these separate expansions would be created (i.e. the
end result would be a union of all the individual expansions).
NOTE 2: The 'labels' field has priority over 'labelsTemplate', i.e.
labels specified in the 'labels' field will override any labels
originating from the 'labelsTemplate' field.
A special case of an empty match expression set matches everything (i.e.
matches/returns all existing keys/values). This makes it simpler to
write templates that run over all values. Also, makes it possible to
later implement support for templates that run over all _keys_ of a
feature.
Some example configurations:
- name: "my-pci-template-features"
labelsTemplate: |
{{ range .pci.device }}intel-{{ .class }}-{{ .device }}=present
{{ end }}
matchFeatures:
- feature: pci.device
matchExpressions:
class: {op: InRegexp, value: ["^06"]}
vendor: ["8086"]
- name: "my-system-template-features"
labelsTemplate: |
{{ range .system.osrelease }}system-{{ .Name }}={{ .Value }}
{{ end }}
matchFeatures:
- feature: system.osRelease
matchExpressions:
ID: {op: Exists}
VERSION_ID.major: {op: Exists}
Imaginative template pipelines are possible, of course, but care must be
taken in order to produce understandable and maintainable rule sets.
2021-05-04 16:30:06 +03:00
|
|
|
ret = append(ret, i.Attributes)
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
|
|
|
}
|
pkg/apis/nfd: support label name templating
Support templating of label names in feature rules. It is available both
in NodeFeatureRule CRs and in custom rule configuration of nfd-worker.
This patch adds a new 'labelsTemplate' field to the rule spec, making it
possible to dynamically generate multiple labels per rule based on the
matched features. The feature relies on the golang "text/template"
package. When expanded, the template must contain labels in a raw
<key>[=<value>] format (where 'value' defaults to "true"), separated by
newlines i.e.:
- name: <rule-name>
labelsTemplate: |
<label-1>[=<value-1>]
<label-2>[=<value-2>]
...
All the matched features of 'matchFeatures' directives are available for
templating engine in a nested data structure that can be described in
yaml as:
.
<domain-1>:
<key-feature-1>:
- Name: <matched-key>
- ...
<value-feature-1:
- Name: <matched-key>
Value: <matched-value>
- ...
<instance-feature-1>:
- <attribute-1-name>: <attribute-1-value>
<attribute-2-name>: <attribute-2-value>
...
- ...
<domain-2>:
...
That is, the per-feature data available for matching depends on the type
of feature that was matched:
- "key features": only 'Name' is available
- "value features": 'Name' and 'Value' can be used
- "instance features": all attributes of the matched instance are
available
NOTE: In case of matchAny is specified, the template is executed
separately against each individual matchFeatures matcher and the
eventual set of labels is a superset of all these expansions. Consider
the following:
- name: <name>
labelsTemplate: <template>
matchAny:
- matchFeatures: <matcher#1>
- matchFeatures: <matcher#2>
matchFeatures: <matcher#3>
In the example above (assuming the overall result is a match) the
template would be executed on matcher#1 and/or matcher#2 (depending on
whether both or only one of them match), and finally on matcher#3, and
all the labels from these separate expansions would be created (i.e. the
end result would be a union of all the individual expansions).
NOTE 2: The 'labels' field has priority over 'labelsTemplate', i.e.
labels specified in the 'labels' field will override any labels
originating from the 'labelsTemplate' field.
A special case of an empty match expression set matches everything (i.e.
matches/returns all existing keys/values). This makes it simpler to
write templates that run over all values. Also, makes it possible to
later implement support for templates that run over all _keys_ of a
feature.
Some example configurations:
- name: "my-pci-template-features"
labelsTemplate: |
{{ range .pci.device }}intel-{{ .class }}-{{ .device }}=present
{{ end }}
matchFeatures:
- feature: pci.device
matchExpressions:
class: {op: InRegexp, value: ["^06"]}
vendor: ["8086"]
- name: "my-system-template-features"
labelsTemplate: |
{{ range .system.osrelease }}system-{{ .Name }}={{ .Value }}
{{ end }}
matchFeatures:
- feature: system.osRelease
matchExpressions:
ID: {op: Exists}
VERSION_ID.major: {op: Exists}
Imaginative template pipelines are possible, of course, but care must be
taken in order to produce understandable and maintainable rule sets.
2021-05-04 16:30:06 +03:00
|
|
|
return ret, nil
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalJSON implements the Unmarshaler interface of "encoding/json".
|
|
|
|
func (m *MatchExpressionSet) UnmarshalJSON(data []byte) error {
|
2022-07-07 20:57:58 +03:00
|
|
|
*m = MatchExpressionSet{}
|
2021-03-04 07:35:17 +02:00
|
|
|
|
|
|
|
names := make([]string, 0)
|
|
|
|
if err := json.Unmarshal(data, &names); err == nil {
|
|
|
|
// Simplified slice form
|
|
|
|
for _, name := range names {
|
|
|
|
split := strings.SplitN(name, "=", 2)
|
|
|
|
if len(split) == 1 {
|
2022-07-07 20:57:58 +03:00
|
|
|
(*m)[split[0]] = newMatchExpression(MatchExists)
|
2021-03-04 07:35:17 +02:00
|
|
|
} else {
|
2022-07-07 20:57:58 +03:00
|
|
|
(*m)[split[0]] = newMatchExpression(MatchIn, split[1])
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Unmarshal the full map form
|
|
|
|
expressions := make(map[string]*MatchExpression)
|
|
|
|
if err := json.Unmarshal(data, &expressions); err != nil {
|
|
|
|
return err
|
2021-12-01 10:54:41 +02:00
|
|
|
}
|
|
|
|
for k, v := range expressions {
|
|
|
|
if v != nil {
|
2022-07-07 20:57:58 +03:00
|
|
|
(*m)[k] = v
|
2021-12-01 10:54:41 +02:00
|
|
|
} else {
|
2022-07-07 20:57:58 +03:00
|
|
|
(*m)[k] = newMatchExpression(MatchExists)
|
2021-03-04 07:35:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalJSON implements the Unmarshaler interface of "encoding/json".
|
|
|
|
func (m *MatchOp) UnmarshalJSON(data []byte) error {
|
|
|
|
var raw string
|
|
|
|
|
|
|
|
if err := json.Unmarshal(data, &raw); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := matchOps[MatchOp(raw)]; !ok {
|
|
|
|
return fmt.Errorf("invalid Op %q", raw)
|
|
|
|
}
|
|
|
|
*m = MatchOp(raw)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalJSON implements the Unmarshaler interface of "encoding/json".
|
|
|
|
func (m *MatchValue) UnmarshalJSON(data []byte) error {
|
|
|
|
var raw interface{}
|
|
|
|
|
|
|
|
if err := json.Unmarshal(data, &raw); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch v := raw.(type) {
|
|
|
|
case string:
|
|
|
|
*m = []string{v}
|
|
|
|
case bool:
|
|
|
|
*m = []string{strconv.FormatBool(v)}
|
|
|
|
case float64:
|
|
|
|
*m = []string{strconv.FormatFloat(v, 'f', -1, 64)}
|
|
|
|
case []interface{}:
|
|
|
|
values := make([]string, len(v))
|
|
|
|
for i, value := range v {
|
|
|
|
str, ok := value.(string)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("invalid value %v in %v", value, v)
|
|
|
|
}
|
|
|
|
values[i] = str
|
|
|
|
}
|
|
|
|
*m = values
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("invalid values '%v' (%T)", v, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|