Skip to content

Commit 0c0c0b1

Browse files
committed
kubectl-nfd: Add NodeFeatureGroup dryrun, validation and test
Signed-off-by: Oleg Zhurakivskyy <oleg.zhurakivskyy@intel.com>
1 parent a84f0e8 commit 0c0c0b1

9 files changed

Lines changed: 304 additions & 51 deletions

File tree

cmd/kubectl-nfd/subcmd/dryrun.go

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,34 +26,50 @@ import (
2626

2727
var dryrunCmd = &cobra.Command{
2828
Use: "dryrun",
29-
Short: "Process a NodeFeatureRule file against a NodeFeature file",
30-
Long: `Process a NodeFeatureRule file against a local NodeFeature file to dry run the rule against a node before applying it to a cluster`,
29+
Short: "Process a NodeFeatureRule or NodeFeatureGroup file against a NodeFeature file",
30+
Long: `Process a NodeFeatureRule or NodeFeatureGroup file against a local NodeFeature file to dry run the rule against a node before applying it to a cluster`,
31+
PreRunE: func(cmd *cobra.Command, args []string) error {
32+
if nodefeaturerule == "" && nodefeaturegroup == "" {
33+
return fmt.Errorf("at least one of --nodefeaturerule-file or --nodefeaturegroup-file must be specified")
34+
}
35+
return nil
36+
},
3137
Run: func(cmd *cobra.Command, args []string) {
32-
fmt.Printf("Evaluating NodeFeatureRule %q against NodeFeature %q\n", nodefeaturerule, nodefeature)
33-
err := kubectlnfd.DryRun(nodefeaturerule, nodefeature)
34-
if len(err) > 0 {
35-
fmt.Printf("NodeFeatureRule %q is not valid for NodeFeature %q\n", nodefeaturerule, nodefeature)
36-
for _, e := range err {
37-
cmd.PrintErrln(e)
38+
if nodefeaturerule != "" {
39+
fmt.Printf("Evaluating NodeFeatureRule %q against NodeFeature %q\n", nodefeaturerule, nodefeature)
40+
errs := kubectlnfd.DryRun(nodefeaturerule, nodefeature)
41+
if len(errs) > 0 {
42+
fmt.Printf("NodeFeatureRule %q is not valid for NodeFeature %q\n", nodefeaturerule, nodefeature)
43+
for _, e := range errs {
44+
cmd.PrintErrln(e)
45+
}
46+
os.Exit(1)
3847
}
39-
// Return non-zero exit code to indicate failure
40-
os.Exit(1)
48+
fmt.Printf("NodeFeatureRule %q is valid for NodeFeature %q\n", nodefeaturerule, nodefeature)
49+
}
50+
51+
if nodefeaturegroup != "" {
52+
fmt.Printf("Evaluating NodeFeatureGroup %q against NodeFeature %q\n", nodefeaturegroup, nodefeature)
53+
errs := kubectlnfd.DryRunNFG(nodefeaturegroup, nodefeature)
54+
if len(errs) > 0 {
55+
fmt.Printf("NodeFeatureGroup %q is not valid for NodeFeature %q\n", nodefeaturegroup, nodefeature)
56+
for _, e := range errs {
57+
cmd.PrintErrln(e)
58+
}
59+
os.Exit(1)
60+
}
61+
fmt.Printf("NodeFeatureGroup %q is valid for NodeFeature %q\n", nodefeaturegroup, nodefeature)
4162
}
42-
fmt.Printf("NodeFeatureRule %q is valid for NodeFeature %q\n", nodefeaturerule, nodefeature)
4363
},
4464
}
4565

4666
func init() {
4767
RootCmd.AddCommand(dryrunCmd)
4868

49-
dryrunCmd.Flags().StringVarP(&nodefeaturerule, "nodefeaturerule-file", "f", "", "Path to the NodeFeatureRule file to validate")
50-
dryrunCmd.Flags().StringVarP(&nodefeature, "nodefeature-file", "n", "", "Path to the NodeFeature file to validate against")
51-
err := dryrunCmd.MarkFlagRequired("nodefeaturerule-file")
52-
if err != nil {
53-
panic(err)
54-
}
55-
err = dryrunCmd.MarkFlagRequired("nodefeature-file")
56-
if err != nil {
69+
dryrunCmd.Flags().StringVarP(&nodefeaturerule, "nodefeaturerule-file", "f", "", "Path to the NodeFeatureRule file to dry run")
70+
dryrunCmd.Flags().StringVarP(&nodefeaturegroup, "nodefeaturegroup-file", "g", "", "Path to the NodeFeatureGroup file to dry run")
71+
dryrunCmd.Flags().StringVarP(&nodefeature, "nodefeature-file", "n", "", "Path to the NodeFeature file to dry run against")
72+
if err := dryrunCmd.MarkFlagRequired("nodefeature-file"); err != nil {
5773
panic(err)
5874
}
5975
}

cmd/kubectl-nfd/subcmd/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
var (
2727
// Path to the NodeFeatureRule file to validate
2828
nodefeaturerule string
29+
// Path to the NodeFeatureGroup file to validate
30+
nodefeaturegroup string
2931
// Path to the NodeFeature file to run against the NodeFeatureRule
3032
nodefeature string
3133
// Node to validate against

cmd/kubectl-nfd/subcmd/test.go

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,32 +26,50 @@ import (
2626

2727
var testCmd = &cobra.Command{
2828
Use: "test",
29-
Short: "Test a NodeFeatureRule file against a Node",
30-
Long: `Test a NodeFeatureRule file against a Node to ensure it is valid before applying it to a cluster`,
29+
Short: "Test a NodeFeatureRule or NodeFeatureGroup file against a Node",
30+
Long: `Test a NodeFeatureRule or NodeFeatureGroup file against a Node to ensure it is valid before applying it to a cluster`,
31+
PreRunE: func(cmd *cobra.Command, args []string) error {
32+
if nodefeaturerule == "" && nodefeaturegroup == "" {
33+
return fmt.Errorf("at least one of --nodefeaturerule-file or --nodefeaturegroup-file must be specified")
34+
}
35+
return nil
36+
},
3137
Run: func(cmd *cobra.Command, args []string) {
32-
fmt.Printf("Evaluating NodeFeatureRule against Node %s\n", node)
33-
err := kubectlnfd.Test(nodefeaturerule, node, kubeconfig)
34-
if len(err) > 0 {
35-
fmt.Printf("NodeFeatureRule is not valid for Node %s\n", node)
36-
for _, e := range err {
37-
cmd.PrintErrln(e)
38+
if nodefeaturerule != "" {
39+
fmt.Printf("Evaluating NodeFeatureRule against Node %s\n", node)
40+
errs := kubectlnfd.Test(nodefeaturerule, node, kubeconfig)
41+
if len(errs) > 0 {
42+
fmt.Printf("NodeFeatureRule is not valid for Node %s\n", node)
43+
for _, e := range errs {
44+
cmd.PrintErrln(e)
45+
}
46+
os.Exit(1)
3847
}
39-
// Return non-zero exit code to indicate failure
40-
os.Exit(1)
48+
fmt.Printf("NodeFeatureRule is valid for Node %s\n", node)
49+
}
50+
51+
if nodefeaturegroup != "" {
52+
fmt.Printf("Evaluating NodeFeatureGroup against Node %s\n", node)
53+
errs := kubectlnfd.TestNFG(nodefeaturegroup, node, kubeconfig)
54+
if len(errs) > 0 {
55+
fmt.Printf("NodeFeatureGroup is not valid for Node %s\n", node)
56+
for _, e := range errs {
57+
cmd.PrintErrln(e)
58+
}
59+
os.Exit(1)
60+
}
61+
fmt.Printf("NodeFeatureGroup is valid for Node %s\n", node)
4162
}
42-
fmt.Printf("NodeFeatureRule is valid for Node %s\n", node)
4363
},
4464
}
4565

4666
func init() {
4767
RootCmd.AddCommand(testCmd)
4868

49-
testCmd.Flags().StringVarP(&nodefeaturerule, "nodefeaturerule-file", "f", "", "Path to the NodeFeatureRule file to validate")
50-
testCmd.Flags().StringVarP(&node, "nodename", "n", "", "Node to validate against")
69+
testCmd.Flags().StringVarP(&nodefeaturerule, "nodefeaturerule-file", "f", "", "Path to the NodeFeatureRule file to test")
70+
testCmd.Flags().StringVarP(&nodefeaturegroup, "nodefeaturegroup-file", "g", "", "Path to the NodeFeatureGroup file to test")
71+
testCmd.Flags().StringVarP(&node, "nodename", "n", "", "Node to test against")
5172
testCmd.Flags().StringVarP(&kubeconfig, "kubeconfig", "k", "", "kubeconfig file to use")
52-
if err := testCmd.MarkFlagRequired("nodefeaturerule-file"); err != nil {
53-
panic(err)
54-
}
5573
if err := testCmd.MarkFlagRequired("nodename"); err != nil {
5674
panic(err)
5775
}

cmd/kubectl-nfd/subcmd/validate.go

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,46 @@ import (
2626

2727
var validateCmd = &cobra.Command{
2828
Use: "validate",
29-
Short: "Validate a NodeFeatureRule file",
30-
Long: `Validate a NodeFeatureRule file to ensure it is valid before applying it to a cluster`,
29+
Short: "Validate a NodeFeatureRule or NodeFeatureGroup file",
30+
Long: `Validate a NodeFeatureRule or NodeFeatureGroup file to ensure it is valid before applying it to a cluster`,
31+
PreRunE: func(cmd *cobra.Command, args []string) error {
32+
if nodefeaturerule == "" && nodefeaturegroup == "" {
33+
return fmt.Errorf("at least one of --nodefeaturerule-file or --nodefeaturegroup-file must be specified")
34+
}
35+
return nil
36+
},
3137
Run: func(cmd *cobra.Command, args []string) {
32-
fmt.Printf("Validating NodeFeatureRule %s\n", nodefeaturerule)
33-
err := kubectlnfd.ValidateNFR(nodefeaturerule)
34-
if len(err) > 0 {
35-
fmt.Printf("NodeFeatureRule %s is not valid\n", nodefeaturerule)
36-
for _, e := range err {
37-
cmd.PrintErrln(e)
38+
if nodefeaturerule != "" {
39+
fmt.Printf("Validating NodeFeatureRule %s\n", nodefeaturerule)
40+
errs := kubectlnfd.ValidateNFR(nodefeaturerule)
41+
if len(errs) > 0 {
42+
fmt.Printf("NodeFeatureRule %s is not valid\n", nodefeaturerule)
43+
for _, e := range errs {
44+
cmd.PrintErrln(e)
45+
}
46+
os.Exit(1)
47+
}
48+
fmt.Printf("NodeFeatureRule %s is valid\n", nodefeaturerule)
49+
}
50+
51+
if nodefeaturegroup != "" {
52+
fmt.Printf("Validating NodeFeatureGroup %s\n", nodefeaturegroup)
53+
errs := kubectlnfd.ValidateNFG(nodefeaturegroup)
54+
if len(errs) > 0 {
55+
fmt.Printf("NodeFeatureGroup %s is not valid\n", nodefeaturegroup)
56+
for _, e := range errs {
57+
cmd.PrintErrln(e)
58+
}
59+
os.Exit(1)
3860
}
39-
// Return non-zero exit code to indicate failure
40-
os.Exit(1)
61+
fmt.Printf("NodeFeatureGroup %s is valid\n", nodefeaturegroup)
4162
}
42-
fmt.Printf("NodeFeatureRule %s is valid\n", nodefeaturerule)
4363
},
4464
}
4565

4666
func init() {
4767
RootCmd.AddCommand(validateCmd)
4868

4969
validateCmd.Flags().StringVarP(&nodefeaturerule, "nodefeaturerule-file", "f", "", "Path to the NodeFeatureRule file to validate")
50-
err := validateCmd.MarkFlagRequired("nodefeaturerule-file")
51-
if err != nil {
52-
panic(err)
53-
}
70+
validateCmd.Flags().StringVarP(&nodefeaturegroup, "nodefeaturegroup-file", "g", "", "Path to the NodeFeatureGroup file to validate")
5471
}

docs/usage/kubectl-plugin.md

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ sort: 10
2222
## Overview
2323

2424
The `kubectl` plugin `kubectl nfd` can be used to validate/dryrun and test
25-
NodeFeatureRule objects. It can be installed with the following command:
25+
NodeFeatureRule and NodeFeatureGroup objects. It can be installed with the
26+
following command:
2627

2728
```bash
2829
git clone https://github.com/kubernetes-sigs/node-feature-discovery
@@ -40,6 +41,29 @@ The plugin can be used to validate a NodeFeatureRule object:
4041
kubectl nfd validate -f <nodefeaturerule.yaml>
4142
```
4243

44+
The plugin can also be used to validate a NodeFeatureGroup object:
45+
46+
```bash
47+
kubectl nfd validate -g <nodefeaturegroup.yaml>
48+
```
49+
50+
Both flags can be combined to validate a NodeFeatureRule and a
51+
NodeFeatureGroup in a single invocation:
52+
53+
```bash
54+
kubectl nfd validate -f <nodefeaturerule.yaml> -g <nodefeaturegroup.yaml>
55+
```
56+
57+
You can use the example files to try it out:
58+
59+
```bash
60+
$ kubectl nfd validate -g examples/nodefeaturegroup.yaml
61+
Validating NodeFeatureGroup examples/nodefeaturegroup.yaml
62+
Validating rule: kernel version
63+
Validating rule: veth kernel module
64+
NodeFeatureGroup examples/nodefeaturegroup.yaml is valid
65+
```
66+
4367
### Test
4468

4569
The plugin can be used to test a NodeFeatureRule object against a node:
@@ -48,6 +72,19 @@ The plugin can be used to test a NodeFeatureRule object against a node:
4872
kubectl nfd test -f <nodefeaturerule.yaml> -n <node-name>
4973
```
5074

75+
The plugin can also be used to test a NodeFeatureGroup object against a node:
76+
77+
```bash
78+
kubectl nfd test -g <nodefeaturegroup.yaml> -n <node-name>
79+
```
80+
81+
Both flags can be combined to test a NodeFeatureRule and a NodeFeatureGroup
82+
against the same node in a single invocation:
83+
84+
```bash
85+
kubectl nfd test -f <nodefeaturerule.yaml> -g <nodefeaturegroup.yaml> -n <node-name>
86+
```
87+
5188
### DryRun
5289

5390
The plugin can be used to DryRun a NodeFeatureRule object against a NodeFeature
@@ -58,6 +95,21 @@ kubectl get -n node-feature-discovery nodefeature <nodename> -o yaml > <nodefeat
5895
kubectl nfd dryrun -f <nodefeaturerule.yaml> -n <nodefeature.yaml>
5996
```
6097

98+
The plugin can also be used to DryRun a NodeFeatureGroup object against a
99+
NodeFeature file:
100+
101+
```bash
102+
kubectl get -n node-feature-discovery nodefeature <nodename> -o yaml > <nodefeature.yaml>
103+
kubectl nfd dryrun -g <nodefeaturegroup.yaml> -n <nodefeature.yaml>
104+
```
105+
106+
Both flags can be combined to dry run a NodeFeatureRule and a NodeFeatureGroup
107+
against the same NodeFeature file in a single invocation:
108+
109+
```bash
110+
kubectl nfd dryrun -f <nodefeaturerule.yaml> -g <nodefeaturegroup.yaml> -n <nodefeature.yaml>
111+
```
112+
61113
Or you can use the example NodeFeature file(it is a minimal NodeFeature file):
62114

63115
```bash
@@ -68,3 +120,13 @@ Processing rule: my sample rule
68120
vendor.io/my-sample-feature=true
69121
NodeFeatureRule "examples/nodefeaturerule.yaml" is valid for NodeFeature "examples/nodefeature.yaml"
70122
```
123+
124+
```bash
125+
$ kubectl nfd dryrun -g examples/nodefeaturegroup.yaml -n examples/nodefeature.yaml
126+
Evaluating NodeFeatureGroup "examples/nodefeaturegroup.yaml" against NodeFeature "examples/nodefeature.yaml"
127+
Processing rule: kernel version
128+
Rule "kernel version" did not match
129+
Processing rule: veth kernel module
130+
Rule "veth kernel module" did not match
131+
NodeFeatureGroup "examples/nodefeaturegroup.yaml" is valid for NodeFeature "examples/nodefeature.yaml"
132+
```

examples/nodefeaturegroup.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ spec:
99
- feature: kernel.version
1010
matchExpressions:
1111
major: {op: In, value: ["6"]}
12+
- name: "veth kernel module"
13+
matchFeatures:
14+
- feature: kernel.loadedmodule
15+
matchExpressions:
16+
veth: {op: Exists}

pkg/kubectl-nfd/dryrun.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,55 @@ import (
3131
"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/validate"
3232
)
3333

34+
func DryRunNFG(nodefeaturegrouppath, nodefeaturepath string) []error {
35+
nfg := nfdv1alpha1.NodeFeatureGroup{}
36+
nf := nfdv1alpha1.NodeFeature{}
37+
38+
nfgFile, err := os.ReadFile(nodefeaturegrouppath)
39+
if err != nil {
40+
return []error{fmt.Errorf("error reading NodeFeatureGroup file: %w", err)}
41+
}
42+
if err = yaml.Unmarshal(nfgFile, &nfg); err != nil {
43+
return []error{fmt.Errorf("error parsing NodeFeatureGroup: %w", err)}
44+
}
45+
46+
nfFile, err := os.ReadFile(nodefeaturepath)
47+
if err != nil {
48+
return []error{fmt.Errorf("error reading NodeFeature file: %w", err)}
49+
}
50+
if err = yaml.Unmarshal(nfFile, &nf); err != nil {
51+
return []error{fmt.Errorf("error parsing NodeFeature: %w", err)}
52+
}
53+
54+
return processNodeFeatureGroup(nfg, nf.Spec)
55+
}
56+
57+
func processNodeFeatureGroup(nodeFeatureGroup nfdv1alpha1.NodeFeatureGroup, nodeFeature nfdv1alpha1.NodeFeatureSpec) []error {
58+
var errs []error
59+
60+
for _, rule := range nodeFeatureGroup.Spec.Rules {
61+
fmt.Println("Processing rule: ", rule.Name)
62+
ruleOut, err := nodefeaturerule.ExecuteGroupRule(&rule, &nodeFeature.Features, true)
63+
if err != nil {
64+
errs = append(errs, fmt.Errorf("failed to process rule %q: %w", rule.Name, err))
65+
continue
66+
}
67+
if ruleOut.MatchStatus == nil || !ruleOut.MatchStatus.IsMatch {
68+
fmt.Printf("Rule %q did not match\n", rule.Name)
69+
continue
70+
}
71+
fmt.Printf("Rule %q matched\n", rule.Name)
72+
if len(ruleOut.Vars) > 0 {
73+
fmt.Println("***\tVars\t***")
74+
for k, v := range ruleOut.Vars {
75+
fmt.Printf("%s=%s\n", k, v)
76+
}
77+
}
78+
}
79+
80+
return errs
81+
}
82+
3483
func DryRun(nodefeaturerulepath, nodefeaturepath string) []error {
3584
var errs []error
3685
nfr := nfdv1alpha1.NodeFeatureRule{}

0 commit comments

Comments
 (0)