Skip to content

Commit fa2eb19

Browse files
committed
wip: label export
Signed-off-by: vsoch <[email protected]>
1 parent 6cb8333 commit fa2eb19

File tree

5 files changed

+132
-33
lines changed

5 files changed

+132
-33
lines changed

cmd/nfd/subcmd/export/features.go

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 The Kubernetes Authors.
2+
Copyright 2025 The Kubernetes Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -50,30 +50,8 @@ func NewExportCmd() *cobra.Command {
5050
}
5151
}
5252

53-
// Make into flat format.
5453
features := source.GetAllFeatures()
55-
featureListing := map[string]string{}
56-
57-
// A flag's presence == true
58-
for flagName := range features.Flags {
59-
featureListing[flagName] = "true"
60-
}
61-
62-
// Attributes are sets of elements (key value pairs)
63-
for featureName, featureSet := range features.Attributes {
64-
for elementName, elementValue := range featureSet.Elements {
65-
featureListing[fmt.Sprintf("%s.%s", featureName, elementName)] = elementValue
66-
}
67-
}
68-
69-
for featureName, featureSet := range features.Instances {
70-
for i, element := range featureSet.Elements {
71-
for attrName, attrValue := range element.Attributes {
72-
featureListing[fmt.Sprintf("%s.%d.%s", featureName, i, attrName)] = attrValue
73-
}
74-
}
75-
}
76-
exportedLabels, err := json.MarshalIndent(featureListing, "", " ")
54+
exportedLabels, err := json.MarshalIndent(features, "", " ")
7755
if err != nil {
7856
return err
7957
}

cmd/nfd/subcmd/export/labels.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package export
18+
19+
import (
20+
"encoding/json"
21+
"fmt"
22+
"maps"
23+
"os"
24+
"regexp"
25+
"slices"
26+
"sort"
27+
28+
"github.com/spf13/cobra"
29+
"k8s.io/klog/v2"
30+
31+
worker "sigs.k8s.io/node-feature-discovery/pkg/nfd-worker"
32+
"sigs.k8s.io/node-feature-discovery/source"
33+
)
34+
35+
func NewLabelsCmd() *cobra.Command {
36+
cmd := &cobra.Command{
37+
Use: "labels",
38+
Short: "Export feature labels for given node",
39+
RunE: func(cmd *cobra.Command, args []string) error {
40+
41+
// Determine enabled feature sources
42+
featureSources := make(map[string]source.FeatureSource)
43+
for n, s := range source.GetAllFeatureSources() {
44+
if ts, ok := s.(source.SupplementalSource); !ok || !ts.DisableByDefault() {
45+
featureSources[n] = s
46+
}
47+
}
48+
featureSourceList := slices.Collect(maps.Values(featureSources))
49+
sort.Slice(featureSourceList, func(i, j int) bool { return featureSourceList[i].Name() < featureSourceList[j].Name() })
50+
51+
// Determine enabled label sources
52+
labelSources := make(map[string]source.LabelSource)
53+
for n, s := range source.GetAllLabelSources() {
54+
if ts, ok := s.(source.SupplementalSource); !ok || !ts.DisableByDefault() {
55+
labelSources[n] = s
56+
}
57+
}
58+
labelSourcesList := slices.Collect(maps.Values(labelSources))
59+
sort.Slice(labelSourcesList, func(i, j int) bool {
60+
iP, jP := labelSourcesList[i].Priority(), labelSourcesList[j].Priority()
61+
if iP != jP {
62+
return iP < jP
63+
}
64+
return labelSourcesList[i].Name() < labelSourcesList[j].Name()
65+
})
66+
67+
labels := worker.Labels{}
68+
labelWhiteList := *regexp.MustCompile("")
69+
70+
// Get labels from all enabled label sources
71+
for _, source := range labelSourcesList {
72+
labelsFromSource, err := worker.GetFeatureLabels(source, labelWhiteList)
73+
if err != nil {
74+
klog.ErrorS(err, "discovery failed", "source", source.Name())
75+
continue
76+
}
77+
maps.Copy(labels, labelsFromSource)
78+
}
79+
80+
exportedLabels, err := json.MarshalIndent(labels, "", " ")
81+
if err != nil {
82+
return err
83+
}
84+
85+
if outputPath != "" {
86+
fd, err := os.Create(outputPath)
87+
if err != nil {
88+
return err
89+
}
90+
defer fd.Close()
91+
_, err = fmt.Fprint(fd, string(exportedLabels))
92+
return err
93+
} else {
94+
fmt.Println(string(exportedLabels))
95+
}
96+
return nil
97+
},
98+
}
99+
cmd.Flags().StringVar(&outputPath, "path", "", "export to this JSON path")
100+
return cmd
101+
}
102+
103+
func init() {
104+
ExportCmd.AddCommand(NewLabelsCmd())
105+
}

docs/usage/nfd-export.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: "Feature Export"
2+
title: "Export"
33
layout: default
44
sort: 12
55
---
@@ -15,13 +15,15 @@ sort: 12
1515

1616
---
1717

18-
## Feature Export
18+
## Export
19+
20+
If you are interested in exporting features or labels in a generic context, the nfd client supports an export mode, where both can be derived on the command line.
21+
22+
### Feature Export
1923

2024
**Feature export is in the experimental `v1alpha1` version.**
2125

22-
If you are interested in exporting features in a generic context, the nfd client supports an export mode, where features can be derived without requiring a Kubernetes context.
23-
This addresses use cases such as high performance computing (HPC) and other environments with compute nodes that warrant assessment, but may not have Kubernetes running, or may not be able to or want to run a central daemon service for data.
24-
To use export, you can use `nfd export features`:
26+
This addresses use cases such as high performance computing (HPC) and other environments with compute nodes that warrant assessment, but may not have Kubernetes running, or may not be able to or want to run a central daemon service for data. To export features, you can use `nfd export features`:
2527

2628
```bash
2729
nfd export features
@@ -32,4 +34,18 @@ To save to a file path:
3234

3335
```bash
3436
nfd export features --path features.json
37+
```
38+
39+
### Label Export
40+
41+
To export equivalent labels outside of a Kubernetes context, you can use `nfd export labels`.
42+
43+
```bash
44+
nfd export labels
45+
```
46+
47+
Or export to an output file:
48+
49+
```bash
50+
nfd export labels --path labels.json
3551
```

pkg/nfd-worker/nfd-worker-internal_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func TestGetLabelsWithMockSources(t *testing.T) {
5454
mockLabelSource.On("Name").Return(fakeLabelSourceName)
5555
mockLabelSource.On("GetLabels").Return(fakeFeatures, nil)
5656

57-
returnedLabels, err := getFeatureLabels(fakeLabelSource, labelWhiteList.Regexp)
57+
returnedLabels, err := GetFeatureLabels(fakeLabelSource, labelWhiteList.Regexp)
5858
Convey("Proper label is returned", func() {
5959
So(returnedLabels, ShouldResemble, fakeFeatureLabels)
6060
})
@@ -67,7 +67,7 @@ func TestGetLabelsWithMockSources(t *testing.T) {
6767
expectedError := errors.New("fake error")
6868
mockLabelSource.On("GetLabels").Return(nil, expectedError)
6969

70-
returnedLabels, err := getFeatureLabels(fakeLabelSource, labelWhiteList.Regexp)
70+
returnedLabels, err := GetFeatureLabels(fakeLabelSource, labelWhiteList.Regexp)
7171
Convey("No label is returned", func() {
7272
So(returnedLabels, ShouldBeNil)
7373
})

pkg/nfd-worker/nfd-worker.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,7 @@ func createFeatureLabels(sources []source.LabelSource, labelWhiteList regexp.Reg
537537
// Get labels from all enabled label sources
538538
klog.InfoS("starting feature discovery...")
539539
for _, source := range sources {
540-
labelsFromSource, err := getFeatureLabels(source, labelWhiteList)
540+
labelsFromSource, err := GetFeatureLabels(source, labelWhiteList)
541541
if err != nil {
542542
klog.ErrorS(err, "discovery failed", "source", source.Name())
543543
continue
@@ -555,7 +555,7 @@ func createFeatureLabels(sources []source.LabelSource, labelWhiteList regexp.Reg
555555

556556
// getFeatureLabels returns node labels for features discovered by the
557557
// supplied source.
558-
func getFeatureLabels(source source.LabelSource, labelWhiteList regexp.Regexp) (labels Labels, err error) {
558+
func GetFeatureLabels(source source.LabelSource, labelWhiteList regexp.Regexp) (labels Labels, err error) {
559559
labels = Labels{}
560560
features, err := source.GetLabels()
561561
if err != nil {

0 commit comments

Comments
 (0)