Skip to content

Commit 7503e6a

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

9 files changed

Lines changed: 241 additions & 91 deletions

File tree

cmd/kubectl-nfd/subcmd/dryrun.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,34 +26,34 @@ 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 == "" {
33+
return fmt.Errorf("--nodefeaturerule-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 {
38+
fmt.Printf("Evaluating %q against NodeFeature %q\n", nodefeaturerule, nodefeature)
39+
errs := kubectlnfd.DryRun(nodefeaturerule, nodefeature)
40+
if len(errs) > 0 {
41+
fmt.Printf("%q is not valid for NodeFeature %q\n", nodefeaturerule, nodefeature)
42+
for _, e := range errs {
3743
cmd.PrintErrln(e)
3844
}
39-
// Return non-zero exit code to indicate failure
4045
os.Exit(1)
4146
}
42-
fmt.Printf("NodeFeatureRule %q is valid for NodeFeature %q\n", nodefeaturerule, nodefeature)
47+
fmt.Printf("%q is valid for NodeFeature %q\n", nodefeaturerule, nodefeature)
4348
},
4449
}
4550

4651
func init() {
4752
RootCmd.AddCommand(dryrunCmd)
4853

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 {
54+
dryrunCmd.Flags().StringVarP(&nodefeaturerule, "nodefeaturerule-file", "f", "", "Path to the NodeFeatureRule or NodeFeatureGroup file to dry run")
55+
dryrunCmd.Flags().StringVarP(&nodefeature, "nodefeature-file", "n", "", "Path to the NodeFeature file to dry run against")
56+
if err := dryrunCmd.MarkFlagRequired("nodefeature-file"); err != nil {
5757
panic(err)
5858
}
5959
}

cmd/kubectl-nfd/subcmd/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
)
2525

2626
var (
27-
// Path to the NodeFeatureRule file to validate
27+
// Path to the NodeFeatureRule or NodeFeatureGroup file
2828
nodefeaturerule string
2929
// Path to the NodeFeature file to run against the NodeFeatureRule
3030
nodefeature string

cmd/kubectl-nfd/subcmd/test.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,32 +26,34 @@ 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 == "" {
33+
return fmt.Errorf("--nodefeaturerule-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 {
38+
fmt.Printf("Evaluating %s against Node %s\n", nodefeaturerule, node)
39+
errs := kubectlnfd.Test(nodefeaturerule, node, kubeconfig)
40+
if len(errs) > 0 {
41+
fmt.Printf("%s is not valid for Node %s\n", nodefeaturerule, node)
42+
for _, e := range errs {
3743
cmd.PrintErrln(e)
3844
}
39-
// Return non-zero exit code to indicate failure
4045
os.Exit(1)
4146
}
42-
fmt.Printf("NodeFeatureRule is valid for Node %s\n", node)
47+
fmt.Printf("%s is valid for Node %s\n", nodefeaturerule, node)
4348
},
4449
}
4550

4651
func init() {
4752
RootCmd.AddCommand(testCmd)
4853

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")
54+
testCmd.Flags().StringVarP(&nodefeaturerule, "nodefeaturerule-file", "f", "", "Path to the NodeFeatureRule or NodeFeatureGroup file to test")
55+
testCmd.Flags().StringVarP(&node, "nodename", "n", "", "Node to test against")
5156
testCmd.Flags().StringVarP(&kubeconfig, "kubeconfig", "k", "", "kubeconfig file to use")
52-
if err := testCmd.MarkFlagRequired("nodefeaturerule-file"); err != nil {
53-
panic(err)
54-
}
5557
if err := testCmd.MarkFlagRequired("nodename"); err != nil {
5658
panic(err)
5759
}

cmd/kubectl-nfd/subcmd/validate.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,30 @@ 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 == "" {
33+
return fmt.Errorf("--nodefeaturerule-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 {
38+
fmt.Printf("Validating %s\n", nodefeaturerule)
39+
errs := kubectlnfd.Validate(nodefeaturerule)
40+
if len(errs) > 0 {
41+
fmt.Printf("%s is not valid\n", nodefeaturerule)
42+
for _, e := range errs {
3743
cmd.PrintErrln(e)
3844
}
39-
// Return non-zero exit code to indicate failure
4045
os.Exit(1)
4146
}
42-
fmt.Printf("NodeFeatureRule %s is valid\n", nodefeaturerule)
47+
fmt.Printf("%s is valid\n", nodefeaturerule)
4348
},
4449
}
4550

4651
func init() {
4752
RootCmd.AddCommand(validateCmd)
4853

49-
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-
}
54+
validateCmd.Flags().StringVarP(&nodefeaturerule, "nodefeaturerule-file", "f", "", "Path to the NodeFeatureRule or NodeFeatureGroup file to validate")
5455
}

docs/usage/kubectl-plugin.md

Lines changed: 35 additions & 11 deletions
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
@@ -34,37 +35,60 @@ mv ./bin/kubectl-nfd ${KUBECTL_PATH}
3435

3536
### Validate
3637

37-
The plugin can be used to validate a NodeFeatureRule object:
38+
The plugin can be used to validate a NodeFeatureRule or NodeFeatureGroup object.
39+
The kind is detected automatically from the file content:
3840

3941
```bash
40-
kubectl nfd validate -f <nodefeaturerule.yaml>
42+
kubectl nfd validate -f <nodefeaturerule-or-nodefeaturegroup.yaml>
43+
```
44+
45+
You can use the example files to try it out:
46+
47+
```bash
48+
$ kubectl nfd validate -f examples/nodefeaturegroup.yaml
49+
Validating examples/nodefeaturegroup.yaml
50+
Validating rule: kernel version
51+
Validating rule: veth kernel module
52+
examples/nodefeaturegroup.yaml is valid
4153
```
4254

4355
### Test
4456

45-
The plugin can be used to test a NodeFeatureRule object against a node:
57+
The plugin can be used to test a NodeFeatureRule or NodeFeatureGroup object
58+
against a node. The kind is detected automatically from the file content:
4659

4760
```bash
48-
kubectl nfd test -f <nodefeaturerule.yaml> -n <node-name>
61+
kubectl nfd test -f <nodefeaturerule-or-nodefeaturegroup.yaml> -n <node-name>
4962
```
5063

5164
### DryRun
5265

53-
The plugin can be used to DryRun a NodeFeatureRule object against a NodeFeature
54-
file:
66+
The plugin can be used to dry run a NodeFeatureRule or NodeFeatureGroup object
67+
against a NodeFeature file. The kind is detected automatically from the file
68+
content:
5569

5670
```bash
5771
kubectl get -n node-feature-discovery nodefeature <nodename> -o yaml > <nodefeature.yaml>
58-
kubectl nfd dryrun -f <nodefeaturerule.yaml> -n <nodefeature.yaml>
72+
kubectl nfd dryrun -f <nodefeaturerule-or-nodefeaturegroup.yaml> -n <nodefeature.yaml>
5973
```
6074

61-
Or you can use the example NodeFeature file(it is a minimal NodeFeature file):
75+
For example, using the example files:
6276

6377
```bash
6478
$ kubectl nfd dryrun -f examples/nodefeaturerule.yaml -n examples/nodefeature.yaml
65-
Evaluating NodeFeatureRule "examples/nodefeaturerule.yaml" against NodeFeature "examples/nodefeature.yaml"
79+
Evaluating "examples/nodefeaturerule.yaml" against NodeFeature "examples/nodefeature.yaml"
6680
Processing rule: my sample rule
6781
*** Labels ***
6882
vendor.io/my-sample-feature=true
69-
NodeFeatureRule "examples/nodefeaturerule.yaml" is valid for NodeFeature "examples/nodefeature.yaml"
83+
"examples/nodefeaturerule.yaml" is valid for NodeFeature "examples/nodefeature.yaml"
84+
```
85+
86+
```bash
87+
$ kubectl nfd dryrun -f examples/nodefeaturegroup.yaml -n examples/nodefeature.yaml
88+
Evaluating "examples/nodefeaturegroup.yaml" against NodeFeature "examples/nodefeature.yaml"
89+
Processing rule: kernel version
90+
Rule "kernel version" did not match
91+
Processing rule: veth kernel module
92+
Rule "veth kernel module" did not match
93+
"examples/nodefeaturegroup.yaml" is valid for NodeFeature "examples/nodefeature.yaml"
7094
```

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: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,34 +31,71 @@ import (
3131
"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/validate"
3232
)
3333

34-
func DryRun(nodefeaturerulepath, nodefeaturepath string) []error {
34+
func processNodeFeatureGroup(nodeFeatureGroup nfdv1alpha1.NodeFeatureGroup, nodeFeature nfdv1alpha1.NodeFeatureSpec) []error {
3535
var errs []error
36-
nfr := nfdv1alpha1.NodeFeatureRule{}
37-
nf := nfdv1alpha1.NodeFeature{}
3836

39-
nfrFile, err := os.ReadFile(nodefeaturerulepath)
40-
if err != nil {
41-
return []error{fmt.Errorf("error reading NodeFeatureRule file: %w", err)}
37+
for _, rule := range nodeFeatureGroup.Spec.Rules {
38+
fmt.Println("Processing rule: ", rule.Name)
39+
ruleOut, err := nodefeaturerule.ExecuteGroupRule(&rule, &nodeFeature.Features, true)
40+
if err != nil {
41+
errs = append(errs, fmt.Errorf("failed to process rule %q: %w", rule.Name, err))
42+
continue
43+
}
44+
if ruleOut.MatchStatus == nil || !ruleOut.MatchStatus.IsMatch {
45+
fmt.Printf("Rule %q did not match\n", rule.Name)
46+
continue
47+
}
48+
fmt.Printf("Rule %q matched\n", rule.Name)
49+
if len(ruleOut.Vars) > 0 {
50+
fmt.Println("***\tVars\t***")
51+
for k, v := range ruleOut.Vars {
52+
fmt.Printf("%s=%s\n", k, v)
53+
}
54+
}
4255
}
4356

44-
err = yaml.Unmarshal(nfrFile, &nfr)
57+
return errs
58+
}
59+
60+
func DryRun(resourcepath, nodefeaturepath string) []error {
61+
resourceFile, err := os.ReadFile(resourcepath)
4562
if err != nil {
46-
return []error{fmt.Errorf("error parsing NodeFeatureRule: %w", err)}
63+
return []error{fmt.Errorf("error reading resource file: %w", err)}
4764
}
4865

49-
nfFile, err := os.ReadFile(nodefeaturepath)
50-
if err != nil {
51-
return []error{fmt.Errorf("error reading NodeFeatureRule file: %w", err)}
66+
// Detect the kind of the resource to determine how to process it.
67+
var typeMeta struct {
68+
Kind string `json:"kind"`
69+
}
70+
if err = yaml.Unmarshal(resourceFile, &typeMeta); err != nil {
71+
return []error{fmt.Errorf("error reading resource kind: %w", err)}
5272
}
5373

54-
err = yaml.Unmarshal(nfFile, &nf)
74+
nfFile, err := os.ReadFile(nodefeaturepath)
5575
if err != nil {
56-
return []error{fmt.Errorf("error parsing NodeFeatureRule: %w", err)}
76+
return []error{fmt.Errorf("error reading NodeFeature file: %w", err)}
77+
}
78+
nf := nfdv1alpha1.NodeFeature{}
79+
if err = yaml.Unmarshal(nfFile, &nf); err != nil {
80+
return []error{fmt.Errorf("error parsing NodeFeature: %w", err)}
5781
}
5882

59-
errs = append(errs, processNodeFeatureRule(nfr, nf.Spec)...)
60-
61-
return errs
83+
switch typeMeta.Kind {
84+
case "NodeFeatureRule":
85+
nfr := nfdv1alpha1.NodeFeatureRule{}
86+
if err = yaml.Unmarshal(resourceFile, &nfr); err != nil {
87+
return []error{fmt.Errorf("error parsing NodeFeatureRule: %w", err)}
88+
}
89+
return processNodeFeatureRule(nfr, nf.Spec)
90+
case "NodeFeatureGroup":
91+
nfg := nfdv1alpha1.NodeFeatureGroup{}
92+
if err = yaml.Unmarshal(resourceFile, &nfg); err != nil {
93+
return []error{fmt.Errorf("error parsing NodeFeatureGroup: %w", err)}
94+
}
95+
return processNodeFeatureGroup(nfg, nf.Spec)
96+
default:
97+
return []error{fmt.Errorf("unsupported resource kind %q: must be NodeFeatureRule or NodeFeatureGroup", typeMeta.Kind)}
98+
}
6299
}
63100

64101
func processNodeFeatureRule(nodeFeatureRule nfdv1alpha1.NodeFeatureRule, nodeFeature nfdv1alpha1.NodeFeatureSpec) []error {

0 commit comments

Comments
 (0)