1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2024-12-14 11:57:51 +00:00

Introduce nfd client tool with subset of image compatibility commands.

Signed-off-by: Marcin Franczyk <marcin0franczyk@gmail.com>
This commit is contained in:
Marcin Franczyk 2024-11-14 18:29:40 +01:00
parent b4996ad399
commit b9749cffe1
15 changed files with 1320 additions and 1 deletions

View file

@ -90,7 +90,7 @@ IMAGE_BUILD_ARGS_MINIMAL = --target minimal \
all: image
BUILD_BINARIES := nfd-master nfd-worker nfd-topology-updater nfd-gc kubectl-nfd
BUILD_BINARIES := nfd-master nfd-worker nfd-topology-updater nfd-gc kubectl-nfd nfd
build-%:
$(GO_CMD) build -v -o bin/ $(BUILD_FLAGS) ./cmd/$*

View file

@ -0,0 +1,48 @@
/*
Copyright 2024 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 v1alpha1
import (
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
)
// ArtifactType is a type of OCI artifact that contains image compatibility metadata.
const (
ArtifactType = "application/vnd.nfd.image-compatibility.v1alpha1"
Version = "v1alpha1"
)
// Spec represents image compatibility metadata.
type Spec struct {
// Version of the spec.
Version string `json:"version"`
// Compatibilities contains list of compatibility sets.
Compatibilties []Compatibility `json:"compatibilities"`
}
// Compatibility represents image compatibility metadata
// that describe the image requirements for the host and OS.
type Compatibility struct {
// Rules represents a list of Node Feature Rules.
Rules []nfdv1alpha1.Rule `json:"rules"`
// Weight indicates the priority of the compatibility set.
Weight int `json:"weight,omitempty"`
// Tag enables grouping or distinguishing between compatibility sets.
Tag string `json:"tag,omitempty"`
// Description of the compatibility set.
Description string `json:"description,omitempty"`
}

27
cmd/nfd/main.go Normal file
View file

@ -0,0 +1,27 @@
/*
Copyright 2024 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 main
import (
"sigs.k8s.io/node-feature-discovery/cmd/nfd/subcmd"
)
const ProgramName = "nfd"
func main() {
subcmd.Execute()
}

View file

@ -0,0 +1,36 @@
/*
Copyright 2024 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 compat
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var CompatCmd = &cobra.Command{
Use: "compat",
Short: "Image compatibility commands",
}
func Execute() {
if err := CompatCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View file

@ -0,0 +1,70 @@
/*
Copyright 2024 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 options
import (
"fmt"
"runtime"
"strings"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra"
)
// PlatformOption represents
type PlatformOption struct {
// PlatformStr contains the raw platform argument provided by the user.
PlatformStr string
// Platform represents the OCI platform specification, built from PlatformStr.
Platform *ocispec.Platform
}
// Parse takes the PlatformStr argument provided by the user
// to build OCI platform specification.
func (opt *PlatformOption) Parse(*cobra.Command) error {
var pStr string
if opt.PlatformStr == "" {
return nil
}
platform := &ocispec.Platform{}
pStr, platform.OSVersion, _ = strings.Cut(opt.PlatformStr, ":")
parts := strings.Split(pStr, "/")
switch len(parts) {
case 3:
platform.Variant = parts[2]
fallthrough
case 2:
platform.Architecture = parts[1]
case 1:
platform.Architecture = runtime.GOARCH
default:
return fmt.Errorf("failed to parse platform %q: expected format os[/arch[/variant]]", opt.PlatformStr)
}
platform.OS = parts[0]
if platform.OS == "" {
return fmt.Errorf("invalid platform: OS cannot be empty")
}
if platform.Architecture == "" {
return fmt.Errorf("invalid platform: Architecture cannot be empty")
}
opt.Platform = platform
return nil
}

View file

@ -0,0 +1,227 @@
/*
Copyright 2024 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 compat
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"time"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/spf13/cobra"
"oras.land/oras-go/v2/registry"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/cmd/nfd/subcmd/compat/options"
artifactcli "sigs.k8s.io/node-feature-discovery/pkg/client-nfd/compat/artifact-client"
nodevalidator "sigs.k8s.io/node-feature-discovery/pkg/client-nfd/compat/node-validator"
"sigs.k8s.io/node-feature-discovery/source"
)
var (
image string
tags []string
platform options.PlatformOption
plainHTTP bool
outputJSON bool
// secrets
readPassword bool
readAccessToken bool
username string
password string
accessToken string
)
var validateNodeCmd = &cobra.Command{
Use: "validate-node",
Short: "Perform node validation based on its associated image compatibility artifact",
PreRunE: func(cmd *cobra.Command, args []string) error {
var err error
if err = platform.Parse(cmd); err != nil {
return err
}
if readAccessToken && readPassword {
return fmt.Errorf("cannot use --read-access-token and --read-password at the same time")
} else if readAccessToken {
accessToken, err = readStdin()
if err != nil {
return err
}
} else if readPassword {
password, err = readStdin()
if err != nil {
return err
}
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
ref, err := registry.ParseReference(image)
if err != nil {
return err
}
sources := map[string]source.FeatureSource{}
for k, v := range source.GetAllFeatureSources() {
if ts, ok := v.(source.SupplementalSource); ok && ts.DisableByDefault() {
continue
}
sources[k] = v
}
authOpt := artifactcli.WithAuthDefault()
if username != "" && password != "" {
authOpt = artifactcli.WithAuthPassword(username, password)
} else if accessToken != "" {
authOpt = artifactcli.WithAuthToken(accessToken)
}
ac := artifactcli.New(
&ref,
artifactcli.WithArgs(artifactcli.Args{PlainHttp: plainHTTP}),
artifactcli.WithPlatform(platform.Platform),
authOpt,
)
nv := nodevalidator.New(
nodevalidator.WithArgs(&nodevalidator.Args{Tags: tags}),
nodevalidator.WithArtifactClient(ac),
nodevalidator.WithSources(sources),
)
out, err := nv.Execute(ctx)
if err != nil {
return err
}
if outputJSON {
b, err := json.Marshal(out)
if err != nil {
return err
}
fmt.Printf("%s", b)
} else {
pprintResult(out)
}
return nil
},
}
func readStdin() (string, error) {
secretRaw, err := io.ReadAll(os.Stdin)
if err != nil {
return "", err
}
secret := strings.TrimSuffix(string(secretRaw), "\n")
secret = strings.TrimSuffix(secret, "\r")
return secret, nil
}
func pprintResult(css []*nodevalidator.CompatibilityStatus) {
for i, cs := range css {
fmt.Print(text.Colors{text.FgCyan, text.Bold}.Sprintf("COMPATIBILITY SET #%d ", i+1))
fmt.Print(text.FgCyan.Sprintf("Weight: %d", cs.Weight))
if cs.Tag != "" {
fmt.Print(text.FgCyan.Sprintf("; Tag: %s", cs.Tag))
}
fmt.Println()
fmt.Println(text.FgWhite.Sprintf("Description: %s", cs.Description))
for _, r := range cs.Rules {
printTable(r)
}
fmt.Println()
}
}
func printTable(rs nodevalidator.RuleStatus) {
t := table.NewWriter()
t.SetStyle(table.StyleLight)
t.SetOutputMirror(os.Stdout)
t.Style().Format.Header = text.FormatDefault
t.SetAutoIndex(true)
validTxt := text.BgRed.Sprint(" FAIL ")
if rs.IsMatch {
validTxt = text.BgGreen.Sprint(" OK ")
}
ruleTxt := strings.ToUpper(fmt.Sprintf("rule: %s", rs.Rule.Name))
t.SetTitle(text.Bold.Sprintf("%s - %s", ruleTxt, validTxt))
t.AppendHeader(table.Row{"Feature", "Expression", "Status"})
if mf := rs.Rule.MatchFeatures; len(mf) > 0 {
renderMatchFeatures(t, mf)
}
if ma := rs.Rule.MatchAny; len(ma) > 0 {
for _, mae := range ma {
t.AppendSeparator()
renderMatchFeatures(t, mae.MatchFeatures)
}
}
t.Render()
}
func renderMatchFeatures(t table.Writer, matchFeatures nfdv1alpha1.FeatureMatcher) {
for _, fm := range matchFeatures {
if fm.MatchExpressions != nil {
for key, exp := range *fm.MatchExpressions {
addTableRows(t, fmt.Sprintf("%s.%s", fm.Feature, key), exp.String(), exp.IsMatch)
}
}
if exp := fm.MatchName; exp != nil {
addTableRows(t, fm.Feature, exp.String(), exp.IsMatch)
}
}
}
func addTableRows(t table.Writer, fullFeatureDomain string, expression string, isMatch bool) {
status := text.FgHiRed.Sprint("FAIL")
if isMatch {
status = text.FgHiGreen.Sprint("OK")
}
t.AppendRow(table.Row{fullFeatureDomain, expression, status})
}
func init() {
CompatCmd.AddCommand(validateNodeCmd)
validateNodeCmd.Flags().StringVar(&image, "image", "", "the URL of the image containing compatibility metadata")
validateNodeCmd.Flags().StringSliceVar(&tags, "tags", []string{}, "a list of tags that must match the tags set on the compatibility objects")
validateNodeCmd.Flags().StringVar(&platform.PlatformStr, "platform", "", "the artifact platform in the format os[/arch][/variant][:os_version]")
validateNodeCmd.Flags().BoolVar(&plainHTTP, "plain-http", false, "use of HTTP protocol for all registry communications")
validateNodeCmd.Flags().BoolVar(&outputJSON, "output-json", false, "print a JSON object")
validateNodeCmd.Flags().StringVar(&username, "reg-username", "", "registry username")
validateNodeCmd.Flags().BoolVar(&readPassword, "reg-password-stdin", false, "read registry password from stdin")
validateNodeCmd.Flags().BoolVar(&readAccessToken, "reg-token-stdin", false, "read registry access token from stdin")
if err := validateNodeCmd.MarkFlagRequired("image"); err != nil {
panic(err)
}
}

45
cmd/nfd/subcmd/root.go Normal file
View file

@ -0,0 +1,45 @@
/*
Copyright 2024 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 subcmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"sigs.k8s.io/node-feature-discovery/cmd/nfd/subcmd/compat"
)
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "nfd",
Short: "Node Feature Discovery client",
}
func init() {
RootCmd.AddCommand(compat.CompatCmd)
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := RootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View file

@ -0,0 +1,27 @@
version: v1alpha1
compatibilities:
- description: "my image requirements"
rules:
- name: "kernel and cpu"
matchFeatures:
- feature: kernel.loadedmodule
matchExpressions:
vfio-pci: {op: Exists}
ip_tables: {op: Exists}
- feature: cpu.model
matchExpressions:
vendor_id: {op: In, value: ["Intel", "AMD"]}
- feature: cpu.cpuid
matchName: {op: InRegexp, value: ["^AVX"]}
- name: "one of available nics"
matchAny:
- matchFeatures:
- feature: pci.device
matchExpressions:
vendor: {op: In, value: ["0eee"]}
class: {op: In, value: ["0200"]}
- matchFeatures:
- feature: pci.device
matchExpressions:
vendor: {op: In, value: ["0fff"]}
class: {op: In, value: ["0200"]}

5
go.mod
View file

@ -7,11 +7,13 @@ require (
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.6.0
github.com/jaypipes/ghw v0.13.0
github.com/jedib0t/go-pretty/v6 v6.6.1
github.com/k8stopologyawareschedwg/noderesourcetopology-api v0.1.2
github.com/k8stopologyawareschedwg/podfingerprint v0.2.2
github.com/klauspost/cpuid/v2 v2.2.9
github.com/onsi/ginkgo/v2 v2.22.0
github.com/onsi/gomega v1.36.0
github.com/opencontainers/image-spec v1.1.0
github.com/opencontainers/runc v1.2.2
github.com/prometheus/client_golang v1.19.1
github.com/smartystreets/goconvey v1.8.1
@ -34,6 +36,7 @@ require (
k8s.io/kubernetes v1.31.3
k8s.io/pod-security-admission v0.31.3
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
oras.land/oras-go/v2 v2.5.0
sigs.k8s.io/node-feature-discovery/api/nfd v0.0.0-00010101000000-000000000000
sigs.k8s.io/yaml v1.4.0
)
@ -83,6 +86,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/jtolds/gls v4.20.0+incompatible // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/spdystream v0.4.0 // indirect
github.com/moby/sys/mountinfo v0.7.1 // indirect
@ -98,6 +102,7 @@ require (
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/smarty/assertions v1.15.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect

10
go.sum
View file

@ -124,6 +124,8 @@ github.com/jaypipes/ghw v0.13.0 h1:log8MXuB8hzTNnSktqpXMHc0c/2k/WgjOMSUtnI1RV4=
github.com/jaypipes/ghw v0.13.0/go.mod h1:In8SsaDqlb1oTyrbmTC14uy+fbBMvp+xdqX51MidlD8=
github.com/jaypipes/pcidb v1.0.1 h1:WB2zh27T3nwg8AE8ei81sNRb9yWBii3JGNJtT7K9Oic=
github.com/jaypipes/pcidb v1.0.1/go.mod h1:6xYUz/yYEyOkIkUt2t2J2folIuZ4Yg6uByCGFXMCeE4=
github.com/jedib0t/go-pretty/v6 v6.6.1 h1:iJ65Xjb680rHcikRj6DSIbzCex2huitmc7bDtxYVWyc=
github.com/jedib0t/go-pretty/v6 v6.6.1/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
@ -152,6 +154,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible h1:aKW/4cBs+yK6gpqU3K/oIwk9Q/XICqd3zOX/UFuvqmk=
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@ -181,6 +185,8 @@ github.com/onsi/gomega v1.36.0 h1:Pb12RlruUtj4XUuPUqeEWc6j5DkVVVA49Uf6YLfC95Y=
github.com/onsi/gomega v1.36.0/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/opencontainers/runc v1.2.2 h1:jTg3Vw2A5f0N9PoxFTEwUhvpANGaNPT3689Yfd/zaX0=
github.com/opencontainers/runc v1.2.2/go.mod h1:/PXzF0h531HTMsYQnmxXkBD7YaGShm/2zcRB79dksUc=
github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk=
@ -200,6 +206,8 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@ -417,6 +425,8 @@ k8s.io/pod-security-admission v0.31.3 h1:8NzEV0HtdStX367AuSKfRMIZHn0hT4xuz8xNEf7
k8s.io/pod-security-admission v0.31.3/go.mod h1:YMIcTe/7f9R9d+3ErCMMM3Wtbj9ejKo7Z9S0OxZQrRg=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c=
oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=

View file

@ -0,0 +1,178 @@
/*
Copyright 2024 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 compat
//go:generate mockery --name=ArtifactClient --inpackage
import (
"context"
"encoding/json"
"fmt"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
oras "oras.land/oras-go/v2"
"oras.land/oras-go/v2/registry"
"oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry/remote/auth"
"oras.land/oras-go/v2/registry/remote/retry"
"sigs.k8s.io/yaml"
compatv1alpha1 "sigs.k8s.io/node-feature-discovery/api/image-compatibility/v1alpha1"
)
// ArtifactClient interface contain set of functions to manipulate compatibility artfact.
type ArtifactClient interface {
// FetchCompatibilitySpec downloads the compatibility specifcation associated with the image.
FetchCompatibilitySpec(ctx context.Context) (*compatv1alpha1.Spec, error)
}
// Args holds command line arguments.
type Args struct {
PlainHttp bool
}
// Client represents a client that is reposnible for all artifact operations.
type Client struct {
Args Args
RegReference *registry.Reference
Platform *ocispec.Platform
orasClient *auth.Client
}
// New returns a new compatibility spec object.
func New(regReference *registry.Reference, opts ...ArtifactClientOpts) *Client {
c := &Client{
RegReference: regReference,
}
for _, opt := range opts {
opt.apply(c)
}
return c
}
// FetchCompatibilitySpec pulls the image compatibility specification associated with the image.
func (c *Client) FetchCompatibilitySpec(ctx context.Context) (*compatv1alpha1.Spec, error) {
repo, err := remote.NewRepository(c.RegReference.String())
if err != nil {
return nil, err
}
repo.Client = c.orasClient
repo.PlainHTTP = c.Args.PlainHttp
opts := oras.DefaultResolveOptions
if c.Platform != nil {
opts.TargetPlatform = c.Platform
}
targetDesc, err := oras.Resolve(ctx, repo, c.RegReference.Reference, opts)
if err != nil {
return nil, err
}
descs, err := registry.Referrers(ctx, repo, targetDesc, compatv1alpha1.ArtifactType)
if err != nil {
return nil, nil
} else if len(descs) < 1 {
return nil, fmt.Errorf("compatibility artifact not found")
}
artifactDesc := descs[len(descs)-1]
_, content, err := oras.FetchBytes(ctx, repo.Manifests(), artifactDesc.Digest.String(), oras.DefaultFetchBytesOptions)
if err != nil {
return nil, err
}
manifest := ocispec.Manifest{}
if err := json.Unmarshal(content, &manifest); err != nil {
return nil, err
}
// TODO: now it's a lazy check, verify in the future the media types and number of layers
if len(manifest.Layers) < 1 {
return nil, fmt.Errorf("compatibility layer not found")
}
specDesc := manifest.Layers[0]
_, compatSpecRaw, err := oras.FetchBytes(ctx, repo.Blobs(), specDesc.Digest.String(), oras.DefaultFetchBytesOptions)
if err != nil {
return nil, err
}
compatSpec := compatv1alpha1.Spec{}
err = yaml.Unmarshal(compatSpecRaw, &compatSpec)
if err != nil {
return nil, err
}
return &compatSpec, nil
}
// NodeValidatorOpts applies certain options to the node validator.
type ArtifactClientOpts interface {
apply(*Client)
}
type artifactClientOpt struct {
f func(*Client)
}
func (o *artifactClientOpt) apply(nv *Client) {
o.f(nv)
}
// WithArgs applies arguments to the artifact client.
func WithArgs(args Args) ArtifactClientOpts {
return &artifactClientOpt{f: func(c *Client) { c.Args = args }}
}
// WithPlatform applies OCI platform spec to the artifact client.
func WithPlatform(platform *ocispec.Platform) ArtifactClientOpts {
return &artifactClientOpt{f: func(c *Client) { c.Platform = platform }}
}
// WithAuthPassword initializes oras client with user and password.
func WithAuthPassword(username, password string) ArtifactClientOpts {
return &artifactClientOpt{f: func(c *Client) {
c.orasClient = &auth.Client{
Client: retry.DefaultClient,
Cache: auth.NewCache(),
Credential: auth.StaticCredential(c.RegReference.Registry, auth.Credential{
Username: username,
Password: password,
}),
}
}}
}
// WithAuthToken initializes oras client with auth token.
func WithAuthToken(token string) ArtifactClientOpts {
return &artifactClientOpt{f: func(c *Client) {
c.orasClient = &auth.Client{
Client: retry.DefaultClient,
Cache: auth.NewCache(),
Credential: auth.StaticCredential(c.RegReference.Registry, auth.Credential{
AccessToken: token,
}),
}
}}
}
// WithAuthDefault initializes the default oras client that does not authenticate.
func WithAuthDefault() ArtifactClientOpts {
return &artifactClientOpt{f: func(c *Client) { c.orasClient = auth.DefaultClient }}
}

View file

@ -0,0 +1,59 @@
// Code generated by mockery v2.42.0. DO NOT EDIT.
package compat
import (
context "context"
mock "github.com/stretchr/testify/mock"
v1alpha1 "sigs.k8s.io/node-feature-discovery/api/image-compatibility/v1alpha1"
)
// MockArtifactClient is an autogenerated mock type for the ArtifactClient type
type MockArtifactClient struct {
mock.Mock
}
// FetchCompatibilitySpec provides a mock function with given fields: ctx
func (_m *MockArtifactClient) FetchCompatibilitySpec(ctx context.Context) (*v1alpha1.Spec, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for FetchCompatibilitySpec")
}
var r0 *v1alpha1.Spec
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) (*v1alpha1.Spec, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) *v1alpha1.Spec); ok {
r0 = rf(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.Spec)
}
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// NewMockArtifactClient creates a new instance of MockArtifactClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockArtifactClient(t interface {
mock.TestingT
Cleanup(func())
}) *MockArtifactClient {
mock := &MockArtifactClient{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View file

@ -0,0 +1,127 @@
/*
Copyright 2024 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 nodevalidator
import (
"context"
"slices"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/nodefeaturerule"
artifactcli "sigs.k8s.io/node-feature-discovery/pkg/client-nfd/compat/artifact-client"
"sigs.k8s.io/node-feature-discovery/source"
// register sources
_ "sigs.k8s.io/node-feature-discovery/source/cpu"
_ "sigs.k8s.io/node-feature-discovery/source/kernel"
_ "sigs.k8s.io/node-feature-discovery/source/memory"
_ "sigs.k8s.io/node-feature-discovery/source/network"
_ "sigs.k8s.io/node-feature-discovery/source/pci"
_ "sigs.k8s.io/node-feature-discovery/source/storage"
_ "sigs.k8s.io/node-feature-discovery/source/system"
_ "sigs.k8s.io/node-feature-discovery/source/usb"
)
// Args holds command line arguments.
type Args struct {
Tags []string
}
type nodeValidator struct {
args Args
artifactClient artifactcli.ArtifactClient
sources map[string]source.FeatureSource
}
// New builds a node validator with specified options.
func New(opts ...NodeValidatorOpts) nodeValidator {
n := nodeValidator{}
for _, opt := range opts {
opt.apply(&n)
}
return n
}
// Execute pulls the compatibility artifact to compare described features with the ones discovered on the node.
func (nv *nodeValidator) Execute(ctx context.Context) ([]*CompatibilityStatus, error) {
spec, err := nv.artifactClient.FetchCompatibilitySpec(ctx)
if err != nil {
return nil, err
}
for _, s := range nv.sources {
if err := s.Discover(); err != nil {
return nil, err
}
}
features := source.GetAllFeatures()
compats := []*CompatibilityStatus{}
for _, c := range spec.Compatibilties {
if len(nv.args.Tags) > 0 && !slices.Contains(nv.args.Tags, c.Tag) {
continue
}
compat := newCompatibilityStatus(&c)
for _, r := range c.Rules {
ruleOut, err := nodefeaturerule.Execute(&r, features, nodefeaturerule.RunAllStrategy{})
if err != nil {
return nil, err
}
compat.Rules = append(compat.Rules, RuleStatus{
Rule: &r,
IsMatch: ruleOut.IsMatch,
})
// Add the 'rule.matched' feature for backreference functionality
features.InsertAttributeFeatures(nfdv1alpha1.RuleBackrefDomain, nfdv1alpha1.RuleBackrefFeature, ruleOut.Labels)
features.InsertAttributeFeatures(nfdv1alpha1.RuleBackrefDomain, nfdv1alpha1.RuleBackrefFeature, ruleOut.Vars)
}
compats = append(compats, &compat)
}
return compats, nil
}
// NodeValidatorOpts applies certain options to the node validator.
type NodeValidatorOpts interface {
apply(*nodeValidator)
}
type nodeValidatorOpt struct {
f func(*nodeValidator)
}
func (o *nodeValidatorOpt) apply(nv *nodeValidator) {
o.f(nv)
}
// WithArgs applies command line arguments to the node validator object.
func WithArgs(args *Args) NodeValidatorOpts {
return &nodeValidatorOpt{f: func(nv *nodeValidator) { nv.args = *args }}
}
// WithArtifactClient applies the client for all artifact operations.
func WithArtifactClient(cli artifactcli.ArtifactClient) NodeValidatorOpts {
return &nodeValidatorOpt{f: func(nv *nodeValidator) { nv.artifactClient = cli }}
}
// WithSources applies the list of enabled feature sources.
func WithSources(sources map[string]source.FeatureSource) NodeValidatorOpts {
return &nodeValidatorOpt{f: func(nv *nodeValidator) { nv.sources = sources }}
}

View file

@ -0,0 +1,407 @@
/*
Copyright 2024 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 nodevalidator
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
compatv1alpha1 "sigs.k8s.io/node-feature-discovery/api/image-compatibility/v1alpha1"
"sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
artifactcli "sigs.k8s.io/node-feature-discovery/pkg/client-nfd/compat/artifact-client"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/fake"
)
func init() {
fs := source.GetConfigurableSource(fake.Name)
fs.SetConfig(fs.NewConfig())
}
func TestNodeValidator(t *testing.T) {
ctx := context.Background()
Convey("With a single compatibility set that contains flags, attributes and instances", t, func() {
spec := &compatv1alpha1.Spec{
Version: compatv1alpha1.Version,
Compatibilties: []compatv1alpha1.Compatibility{
{
Description: "Fake compatibility",
Rules: []v1alpha1.Rule{
{
Name: "fake_1",
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.flag",
MatchName: &v1alpha1.MatchExpression{Op: v1alpha1.MatchInRegexp, Value: v1alpha1.MatchValue{"^flag"}},
},
},
},
{
Name: "fake_2",
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.flag",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"flag_unkown": &v1alpha1.MatchExpression{Op: v1alpha1.MatchExists},
},
},
{
Feature: "fake.attribute",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"attr_1": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"true"}},
},
},
},
},
{
Name: "fake_3",
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.instance",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"name": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"instance_1"}},
"attr_1": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"true"}},
},
},
{
Feature: "fake.instance",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"name": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"instance_2"}},
"attr_1": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"false"}},
},
},
},
},
{
Name: "fake_4",
MatchAny: []v1alpha1.MatchAnyElem{
{
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.instance",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"name": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"instance_1"}},
},
},
},
},
{
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.instance",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"name": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"instance_unknown"}},
},
},
},
},
},
},
},
},
},
}
expectedOutput := []*CompatibilityStatus{
{
Description: "Fake compatibility",
Rules: []RuleStatus{
{
Rule: &v1alpha1.Rule{
Name: "fake_1",
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.flag",
MatchName: &v1alpha1.MatchExpression{Op: v1alpha1.MatchInRegexp, Value: v1alpha1.MatchValue{"^flag"}, IsMatch: true},
},
},
},
IsMatch: true,
},
{
Rule: &v1alpha1.Rule{
Name: "fake_2",
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.flag",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"flag_unkown": &v1alpha1.MatchExpression{Op: v1alpha1.MatchExists, IsMatch: false},
},
},
{
Feature: "fake.attribute",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"attr_1": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"true"}, IsMatch: true},
},
},
},
},
IsMatch: false,
},
{
Rule: &v1alpha1.Rule{
Name: "fake_3",
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.instance",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"name": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"instance_1"}, IsMatch: true},
"attr_1": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"true"}, IsMatch: true},
},
},
{
Feature: "fake.instance",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"name": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"instance_2"}, IsMatch: true},
"attr_1": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"false"}, IsMatch: false},
},
},
},
},
IsMatch: false,
},
{
Rule: &v1alpha1.Rule{
Name: "fake_4",
MatchAny: []v1alpha1.MatchAnyElem{
{
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.instance",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"name": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"instance_1"}, IsMatch: true},
},
},
},
},
{
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.instance",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"name": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"instance_unknown"}, IsMatch: false},
},
},
},
},
},
},
IsMatch: true,
},
},
},
}
validator := New(
WithArgs(&Args{}),
WithArtifactClient(newMock(ctx, spec)),
WithSources(map[string]source.FeatureSource{fake.Name: source.GetFeatureSource(fake.Name)}),
)
output, err := validator.Execute(ctx)
So(err, ShouldBeNil)
So(output, ShouldEqual, expectedOutput)
})
Convey("With multiple compatibility sets", t, func() {
spec := &compatv1alpha1.Spec{
Version: compatv1alpha1.Version,
Compatibilties: []compatv1alpha1.Compatibility{
{
Tag: "prefered",
Weight: 90,
Description: "Fake compatibility 1",
Rules: []v1alpha1.Rule{
{
Name: "fake_1",
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.attribute",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"attr_1": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"false"}},
},
},
},
},
},
},
{
Tag: "fallback",
Weight: 40,
Description: "Fake compatibility 2",
Rules: []v1alpha1.Rule{
{
Name: "fake_1",
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.attribute",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"attr_2": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"false"}},
},
},
},
},
},
},
},
}
expectedOutput := []*CompatibilityStatus{
{
Tag: "prefered",
Weight: 90,
Description: "Fake compatibility 1",
Rules: []RuleStatus{
{
Rule: &v1alpha1.Rule{
Name: "fake_1",
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.attribute",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"attr_1": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"false"}, IsMatch: false},
},
},
},
},
IsMatch: false,
},
},
},
{
Tag: "fallback",
Weight: 40,
Description: "Fake compatibility 2",
Rules: []RuleStatus{
{
Rule: &v1alpha1.Rule{
Name: "fake_1",
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.attribute",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"attr_2": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"false"}, IsMatch: true},
},
},
},
},
IsMatch: true,
},
},
},
}
validator := New(
WithArgs(&Args{}),
WithArtifactClient(newMock(ctx, spec)),
WithSources(map[string]source.FeatureSource{fake.Name: source.GetFeatureSource(fake.Name)}),
)
output, err := validator.Execute(ctx)
So(err, ShouldBeNil)
So(output, ShouldEqual, expectedOutput)
})
Convey("With compatibility sets filtered out by tags", t, func() {
spec := &compatv1alpha1.Spec{
Version: compatv1alpha1.Version,
Compatibilties: []compatv1alpha1.Compatibility{
{
Tag: "prefered",
Weight: 90,
Description: "Fake compatibility 1",
Rules: []v1alpha1.Rule{
{
Name: "fake_1",
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.attribute",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"attr_1": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"false"}},
},
},
},
},
},
},
{
Tag: "fallback",
Weight: 40,
Description: "Fake compatibility 2",
Rules: []v1alpha1.Rule{
{
Name: "fake_1",
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.attribute",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"attr_2": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"false"}},
},
},
},
},
},
},
},
}
expectedOutput := []*CompatibilityStatus{
{
Tag: "prefered",
Weight: 90,
Description: "Fake compatibility 1",
Rules: []RuleStatus{
{
Rule: &v1alpha1.Rule{
Name: "fake_1",
MatchFeatures: v1alpha1.FeatureMatcher{
{
Feature: "fake.attribute",
MatchExpressions: &v1alpha1.MatchExpressionSet{
"attr_1": &v1alpha1.MatchExpression{Op: v1alpha1.MatchIn, Value: v1alpha1.MatchValue{"false"}, IsMatch: false},
},
},
},
},
IsMatch: false,
},
},
},
}
validator := New(
WithArgs(&Args{
Tags: []string{"prefered"},
}),
WithArtifactClient(newMock(ctx, spec)),
WithSources(map[string]source.FeatureSource{fake.Name: source.GetFeatureSource(fake.Name)}),
)
output, err := validator.Execute(ctx)
So(err, ShouldBeNil)
So(output, ShouldEqual, expectedOutput)
})
}
func newMock(ctx context.Context, result *compatv1alpha1.Spec) *artifactcli.MockArtifactClient {
artifactClient := &artifactcli.MockArtifactClient{}
artifactClient.On("FetchCompatibilitySpec", ctx).Return(result, nil)
return artifactClient
}

View file

@ -0,0 +1,53 @@
/*
Copyright 2024 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 nodevalidator
import (
compatv1alpha1 "sigs.k8s.io/node-feature-discovery/api/image-compatibility/v1alpha1"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
)
// CompatibilityStatus represents the state of
// feature matching between the image and the host.
type CompatibilityStatus struct {
// Rules contain information about the matching status
// of all Node Feature Rules.
Rules []RuleStatus `json:"rules"`
// Description of the compatibility set.
Description string `json:"description,omitempty"`
// Weight provides information about the priority of the compatibility set.
Weight int `json:"weight,omitempty"`
// Tag provides information about the tag assigned to the compatibility set.
Tag string `json:"tag,omitempty"`
}
func newCompatibilityStatus(c *compatv1alpha1.Compatibility) CompatibilityStatus {
cs := CompatibilityStatus{
Description: c.Description,
Weight: c.Weight,
Tag: c.Tag,
}
return cs
}
// RuleStatus contains information about features matching.
type RuleStatus struct {
*nfdv1alpha1.Rule
// IsMatch provides information if the rule matches with the host.
IsMatch bool `json:"isMatch"`
}