-
Notifications
You must be signed in to change notification settings - Fork 277
feat: add ability to export features for static node #2183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* | ||
Copyright 2025 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. | ||
*/ | ||
|
||
package export | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/spf13/cobra" | ||
) | ||
|
||
var ExportCmd = &cobra.Command{ | ||
Use: "export", | ||
Short: "Export commands", | ||
} | ||
|
||
func Execute() { | ||
if err := ExportCmd.Execute(); err != nil { | ||
fmt.Println(err) | ||
os.Exit(1) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
/* | ||
Copyright 2025 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. | ||
*/ | ||
|
||
package export | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"sigs.k8s.io/node-feature-discovery/source" | ||
) | ||
|
||
func NewExportCmd() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "features", | ||
Short: "Export features for given node", | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
sources := map[string]source.FeatureSource{} | ||
for k, v := range source.GetAllFeatureSources() { | ||
if ts, ok := v.(source.SupplementalSource); ok && ts.DisableByDefault() { | ||
continue | ||
} | ||
sources[k] = v | ||
} | ||
|
||
// Discover all feature sources | ||
for _, s := range sources { | ||
if err := s.Discover(); err != nil { | ||
return fmt.Errorf("error during discovery of source %s: %w", s.Name(), err) | ||
} | ||
} | ||
|
||
features := source.GetAllFeatures() | ||
exportedLabels, err := json.MarshalIndent(features, "", " ") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if outputPath != "" { | ||
err = writeToFile(outputPath, string(exportedLabels)) | ||
} else { | ||
fmt.Println(string(exportedLabels)) | ||
} | ||
return err | ||
}, | ||
} | ||
cmd.Flags().StringVar(&outputPath, "path", "", "export to this JSON path") | ||
return cmd | ||
} | ||
|
||
func init() { | ||
ExportCmd.AddCommand(NewExportCmd()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/* | ||
Copyright 2025 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. | ||
*/ | ||
|
||
package export | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"maps" | ||
"regexp" | ||
"slices" | ||
"sort" | ||
|
||
"github.com/spf13/cobra" | ||
"k8s.io/klog/v2" | ||
|
||
worker "sigs.k8s.io/node-feature-discovery/pkg/nfd-worker" | ||
"sigs.k8s.io/node-feature-discovery/source" | ||
) | ||
|
||
func NewLabelsCmd() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "labels", | ||
Short: "Export feature labels for given node", | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
|
||
// Determine enabled feature sources | ||
featureSources := make(map[string]source.FeatureSource) | ||
for n, s := range source.GetAllFeatureSources() { | ||
if ts, ok := s.(source.SupplementalSource); !ok || !ts.DisableByDefault() { | ||
err := s.Discover() | ||
if err != nil { | ||
return err | ||
} | ||
featureSources[n] = s | ||
} | ||
} | ||
featureSourceList := slices.Collect(maps.Values(featureSources)) | ||
sort.Slice(featureSourceList, func(i, j int) bool { return featureSourceList[i].Name() < featureSourceList[j].Name() }) | ||
|
||
// Determine enabled label sources | ||
labelSources := make(map[string]source.LabelSource) | ||
for n, s := range source.GetAllLabelSources() { | ||
if ts, ok := s.(source.SupplementalSource); !ok || !ts.DisableByDefault() { | ||
labelSources[n] = s | ||
} | ||
} | ||
labelSourcesList := slices.Collect(maps.Values(labelSources)) | ||
sort.Slice(labelSourcesList, func(i, j int) bool { | ||
iP, jP := labelSourcesList[i].Priority(), labelSourcesList[j].Priority() | ||
if iP != jP { | ||
return iP < jP | ||
} | ||
return labelSourcesList[i].Name() < labelSourcesList[j].Name() | ||
}) | ||
|
||
labels := worker.Labels{} | ||
labelWhiteList := *regexp.MustCompile("") | ||
|
||
// Get labels from all enabled label sources | ||
for _, source := range labelSourcesList { | ||
labelsFromSource, err := worker.GetFeatureLabels(source, labelWhiteList) | ||
if err != nil { | ||
klog.ErrorS(err, "discovery failed", "source", source.Name()) | ||
continue | ||
} | ||
maps.Copy(labels, labelsFromSource) | ||
} | ||
|
||
exportedLabels, err := json.MarshalIndent(labels, "", " ") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if outputPath != "" { | ||
err = writeToFile(outputPath, string(exportedLabels)) | ||
} else { | ||
fmt.Println(string(exportedLabels)) | ||
} | ||
return err | ||
}, | ||
} | ||
cmd.Flags().StringVar(&outputPath, "path", "", "export to this JSON path") | ||
return cmd | ||
} | ||
|
||
func init() { | ||
ExportCmd.AddCommand(NewLabelsCmd()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* | ||
Copyright 2025 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. | ||
*/ | ||
|
||
package export | ||
vsoch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
import ( | ||
"fmt" | ||
"os" | ||
) | ||
|
||
var ( | ||
outputPath string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If Var is global for the pkg There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is global (and private) because it's a shared flag, and I usually see those defined in this way. For the latter, I think it's better practice to not have package functions rely on global variables. |
||
) | ||
|
||
// writeToFile saves string content to a file at the path set by path | ||
func writeToFile(path, content string) error { | ||
fd, err := os.Create(path) | ||
if err != nil { | ||
return err | ||
} | ||
defer fd.Close() | ||
_, err = fmt.Fprint(fd, content) | ||
return err | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
--- | ||
title: "Export" | ||
layout: default | ||
sort: 12 | ||
--- | ||
|
||
# Feature Export | ||
{: .no_toc} | ||
|
||
## Table of contents | ||
{: .no_toc .text-delta} | ||
|
||
1. TOC | ||
{:toc} | ||
|
||
--- | ||
|
||
## Export | ||
|
||
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. | ||
|
||
### Features | ||
|
||
**Feature export is in the experimental version.** | ||
|
||
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`: | ||
|
||
```bash | ||
nfd export features | ||
``` | ||
|
||
By default, JSON structure with parsed key value pairs will appear in the | ||
terminal. To save to a file path: | ||
|
||
```bash | ||
nfd export features --path features.json | ||
``` | ||
|
||
### Labels | ||
|
||
To export equivalent labels outside of a Kubernetes context, | ||
you can use `nfd export labels`. | ||
|
||
```bash | ||
nfd export labels | ||
``` | ||
|
||
Or export to an output file: | ||
|
||
```bash | ||
nfd export labels --path labels.json | ||
``` |
Uh oh!
There was an error while loading. Please reload this page.