diff --git a/cmd/nfd-master/main.go b/cmd/nfd-master/main.go index 9f66a37c3..6611d5ccd 100644 --- a/cmd/nfd-master/main.go +++ b/cmd/nfd-master/main.go @@ -1,5 +1,5 @@ /* -Copyright 2019 The Kubernetes Authors. +Copyright 2019-2021 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. @@ -17,14 +17,14 @@ limitations under the License. package main import ( + "flag" "fmt" "log" + "os" "regexp" - "strconv" - "strings" - "github.com/docopt/docopt-go" master "sigs.k8s.io/node-feature-discovery/pkg/nfd-master" + "sigs.k8s.io/node-feature-discovery/pkg/utils" "sigs.k8s.io/node-feature-discovery/pkg/version" ) @@ -34,17 +34,29 @@ const ( ) func main() { + flags := flag.NewFlagSet(ProgramName, flag.ExitOnError) + + printVersion := flags.Bool("version", false, "Print version and exit.") + + args := initFlags(flags) + + _ = flags.Parse(os.Args[1:]) + if len(flags.Args()) > 0 { + fmt.Printf("unknown command line argument: %s\n", flags.Args()[0]) + flags.Usage() + os.Exit(2) + } + + if *printVersion { + fmt.Println(ProgramName, version.Get()) + os.Exit(0) + } + // Assert that the version is known if version.Undefined() { log.Print("WARNING: version not set! Set -ldflags \"-X sigs.k8s.io/node-feature-discovery/pkg/version.version=`git describe --tags --dirty --always`\" during build or run.") } - // Parse command-line arguments. - args, err := argsParse(nil) - if err != nil { - log.Fatalf("failed to parse command line: %v", err) - } - // Get new NfdMaster instance instance, err := master.NewNfdMaster(args) if err != nil { @@ -56,88 +68,37 @@ func main() { } } -// argsParse parses the command line arguments passed to the program. -// The argument argv is passed only for testing purposes. -func argsParse(argv []string) (master.Args, error) { - args := master.Args{} - usage := fmt.Sprintf(`%s. - - Usage: - %s [--prune] [--no-publish] [--label-whitelist=] [--port=] - [--ca-file=] [--cert-file=] [--key-file=] - [--verify-node-name] [--extra-label-ns=] [--resource-labels=] - [--kubeconfig=] [--instance=] - %s -h | --help - %s --version - - Options: - -h --help Show this screen. - --version Output version and exit. - --prune Prune all NFD related attributes from all nodes - of the cluster and exit. - --instance= Instance name. Used to separate annotation - namespaces for multiple parallel deployments. - [Default: ] - --kubeconfig= Kubeconfig to use [Default: ] - of the cluster and exit. - --port= Port on which to listen for connections. - [Default: 8080] - --ca-file= Root certificate for verifying connections - [Default: ] - --cert-file= Certificate used for authenticating connections - [Default: ] - --key-file= Private key matching --cert-file - [Default: ] - --verify-node-name Verify worker node name against CN from the TLS - certificate. Only has effect when TLS authentication - has been enabled. - --no-publish Do not publish feature labels - --label-whitelist= Regular expression to filter label names to - publish to the Kubernetes API server. - NB: the label namespace is omitted i.e. the filter - is only applied to the name part after '/'. - [Default: ] - --extra-label-ns= Comma separated list of allowed extra label namespaces - [Default: ] - --resource-labels= Comma separated list of labels to be exposed as extended resources. - [Default: ]`, - ProgramName, - ProgramName, - ProgramName, - ProgramName, - ) - - arguments, _ := docopt.ParseArgs(usage, argv, - fmt.Sprintf("%s %s", ProgramName, version.Get())) - - // Parse argument values as usable types. - var err error - args.Instance = arguments["--instance"].(string) - if ok, _ := regexp.MatchString(`^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$`, args.Instance); args.Instance != "" && !ok { - return args, fmt.Errorf("invalid --instance %q: instance name "+ - "must start and end with an alphanumeric character and may only contain "+ - "alphanumerics, `-`, `_` or `.`", args.Instance) +func initFlags(flagset *flag.FlagSet) *master.Args { + args := &master.Args{ + LabelWhiteList: utils.RegexpVal{Regexp: *regexp.MustCompile("")}, } - args.CaFile = arguments["--ca-file"].(string) - args.CertFile = arguments["--cert-file"].(string) - args.KeyFile = arguments["--key-file"].(string) - args.NoPublish = arguments["--no-publish"].(bool) - args.Port, err = strconv.Atoi(arguments["--port"].(string)) - if err != nil { - return args, fmt.Errorf("invalid --port defined: %s", err) - } - args.LabelWhiteList, err = regexp.Compile(arguments["--label-whitelist"].(string)) - if err != nil { - return args, fmt.Errorf("error parsing whitelist regex (%s): %s", arguments["--label-whitelist"], err) - } - args.VerifyNodeName = arguments["--verify-node-name"].(bool) - args.ExtraLabelNs = map[string]struct{}{} - for _, n := range strings.Split(arguments["--extra-label-ns"].(string), ",") { - args.ExtraLabelNs[n] = struct{}{} - } - args.ResourceLabels = strings.Split(arguments["--resource-labels"].(string), ",") - args.Prune = arguments["--prune"].(bool) - args.Kubeconfig = arguments["--kubeconfig"].(string) - return args, nil + flagset.StringVar(&args.CaFile, "ca-file", "", + "Root certificate for verifying connections") + flagset.StringVar(&args.CertFile, "cert-file", "", + "Certificate used for authenticating connections") + flagset.Var(&args.ExtraLabelNs, "extra-label-ns", + "Comma separated list of allowed extra label namespaces") + flagset.StringVar(&args.Instance, "instance", "", + "Instance name. Used to separate annotation namespaces for multiple parallel deployments.") + flagset.StringVar(&args.KeyFile, "key-file", "", + "Private key matching -cert-file") + flagset.StringVar(&args.Kubeconfig, "kubeconfig", "", + "Kubeconfig to use") + flagset.Var(&args.LabelWhiteList, "label-whitelist", + "Regular expression to filter label names to publish to the Kubernetes API server. "+ + "NB: the label namespace is omitted i.e. the filter is only applied to the name part after '/'.") + flagset.BoolVar(&args.NoPublish, "no-publish", false, + "Do not publish feature labels") + flagset.IntVar(&args.Port, "port", 8080, + "Port on which to listen for connections.") + flagset.BoolVar(&args.Prune, "prune", false, + "Prune all NFD related attributes from all nodes of the cluaster and exit.") + flagset.Var(&args.ResourceLabels, "resource-labels", + "Comma separated list of labels to be exposed as extended resources.") + flagset.BoolVar(&args.VerifyNodeName, "verify-node-name", false, + "Verify worker node name against CN from the TLS certificate. "+ + "Only takes effect when TLS authentication has been enabled.") + + return args } diff --git a/cmd/nfd-master/main_test.go b/cmd/nfd-master/main_test.go deleted file mode 100644 index 57ab6bf4f..000000000 --- a/cmd/nfd-master/main_test.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright 2019 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 ( - "testing" - - . "github.com/smartystreets/goconvey/convey" -) - -func TestArgsParse(t *testing.T) { - Convey("When parsing command line arguments", t, func() { - Convey("When --no-publish and --oneshot flags are passed", func() { - args, err := argsParse([]string{"--no-publish"}) - Convey("noPublish is set and args.sources is set to the default value", func() { - So(args.NoPublish, ShouldBeTrue) - So(len(args.LabelWhiteList.String()), ShouldEqual, 0) - So(err, ShouldBeNil) - }) - }) - - Convey("When valid args are specified", func() { - args, err := argsParse([]string{"--label-whitelist=.*rdt.*", "--port=1234", "--cert-file=crt", "--key-file=key", "--ca-file=ca"}) - Convey("Argument parsing should succeed and args set to correct values", func() { - So(args.NoPublish, ShouldBeFalse) - So(args.Port, ShouldEqual, 1234) - So(args.CertFile, ShouldEqual, "crt") - So(args.KeyFile, ShouldEqual, "key") - So(args.CaFile, ShouldEqual, "ca") - So(args.LabelWhiteList.String(), ShouldResemble, ".*rdt.*") - So(err, ShouldBeNil) - }) - }) - Convey("When invalid --port is defined", func() { - _, err := argsParse([]string{"--port=123a"}) - Convey("argsParse should fail", func() { - So(err, ShouldNotBeNil) - }) - }) - }) -} diff --git a/cmd/nfd-worker/main.go b/cmd/nfd-worker/main.go index 01f93c4f9..3182a6a53 100644 --- a/cmd/nfd-worker/main.go +++ b/cmd/nfd-worker/main.go @@ -1,5 +1,5 @@ /* -Copyright 2019 The Kubernetes Authors. +Copyright 2019-2021 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. @@ -17,14 +17,13 @@ limitations under the License. package main import ( + "flag" "fmt" "log" - "regexp" - "strings" - "time" + "os" - "github.com/docopt/docopt-go" worker "sigs.k8s.io/node-feature-discovery/pkg/nfd-worker" + "sigs.k8s.io/node-feature-discovery/pkg/utils" "sigs.k8s.io/node-feature-discovery/pkg/version" ) @@ -34,17 +33,22 @@ const ( ) func main() { + flags := flag.NewFlagSet(ProgramName, flag.ExitOnError) + + printVersion := flags.Bool("version", false, "Print version and exit.") + + args := parseArgs(flags, os.Args[1:]...) + + if *printVersion { + fmt.Println(ProgramName, version.Get()) + os.Exit(0) + } + // Assert that the version is known if version.Undefined() { log.Printf("WARNING: version not set! Set -ldflags \"-X sigs.k8s.io/node-feature-discovery/pkg/version.version=`git describe --tags --dirty --always`\" during build or run.") } - // Parse command-line arguments. - args, err := argsParse(nil) - if err != nil { - log.Fatalf("failed to parse command line: %v", err) - } - // Get new NfdWorker instance instance, err := worker.NewNfdWorker(args) if err != nil { @@ -56,104 +60,74 @@ func main() { } } -// argsParse parses the command line arguments passed to the program. -// The argument argv is passed only for testing purposes. -func argsParse(argv []string) (worker.Args, error) { - args := worker.Args{} - usage := fmt.Sprintf(`%s. +func parseArgs(flags *flag.FlagSet, osArgs ...string) *worker.Args { + args, overrides := initFlags(flags) - Usage: - %s [--no-publish] [--sources=] [--label-whitelist=] - [--oneshot | --sleep-interval=] [--config=] - [--options=] [--server=] [--server-name-override=] - [--ca-file=] [--cert-file=] [--key-file=] - %s -h | --help - %s --version + _ = flags.Parse(osArgs) + if len(flags.Args()) > 0 { + fmt.Printf("unknown command line argument: %s\n", flags.Args()[0]) + flags.Usage() + os.Exit(2) + } - Options: - -h --help Show this screen. - --version Output version and exit. - --config= Config file to use. - [Default: /etc/kubernetes/node-feature-discovery/nfd-worker.conf] - --options= Specify config options from command line. Config - options are specified in the same format as in the - config file (i.e. json or yaml). These options - will override settings read from the config file. - [Default: ] - --ca-file= Root certificate for verifying connections - [Default: ] - --cert-file= Certificate used for authenticating connections - [Default: ] - --key-file= Private key matching --cert-file - [Default: ] - --server= NFD server address to connecto to. - [Default: localhost:8080] - --server-name-override= Name (CN) expect from server certificate, useful - in testing - [Default: ] - --sources= Comma separated list of feature sources. Special - value 'all' enables all feature sources. - (DEPRECATED: This parameter should be set via the - config file) - --no-publish Do not publish discovered features to the - cluster-local Kubernetes API server. - --label-whitelist= Regular expression to filter label names to - publish to the Kubernetes API server. - NB: the label namespace is omitted i.e. the filter - is only applied to the name part after '/'. - (DEPRECATED: This parameter should be set via the - config file) - --oneshot Label once and exit. - --sleep-interval= Time to sleep between re-labeling. Non-positive - value implies no re-labeling (i.e. infinite - sleep). - (DEPRECATED: This parameter should be set via the - config file)`, - ProgramName, - ProgramName, - ProgramName, - ProgramName, - ) - - arguments, _ := docopt.ParseArgs(usage, argv, - fmt.Sprintf("%s %s", ProgramName, version.Get())) - - // Parse argument values as usable types. - args.CaFile = arguments["--ca-file"].(string) - args.CertFile = arguments["--cert-file"].(string) - args.ConfigFile = arguments["--config"].(string) - args.KeyFile = arguments["--key-file"].(string) - args.Options = arguments["--options"].(string) - args.Server = arguments["--server"].(string) - args.ServerNameOverride = arguments["--server-name-override"].(string) - args.Oneshot = arguments["--oneshot"].(bool) - - // Parse deprecated/override args - if v := arguments["--label-whitelist"]; v != nil { - s := v.(string) - // Compile labelWhiteList regex - if r, err := regexp.Compile(s); err != nil { - return args, fmt.Errorf("error parsing --label-whitelist regex (%s): %v", s, err) - } else { - args.LabelWhiteList = r + // Handle overrides + flags.Visit(func(f *flag.Flag) { + switch f.Name { + case "no-publish": + args.Overrides.NoPublish = overrides.NoPublish + case "label-whitelist": + log.Printf("WARNING: --label-whitelist is deprecated, use 'core.labelWhiteList' option in the config file, instead") + args.Overrides.LabelWhiteList = overrides.LabelWhiteList + case "sleep-interval": + log.Printf("WARNING: --sleep-interval is deprecated, use 'core.sleepInterval' option in the config file, instead") + args.Overrides.SleepInterval = overrides.SleepInterval + case "sources": + log.Printf("WARNING: --sources is deprecated, use 'core.sources' option in the config file, instead") + args.Overrides.Sources = overrides.Sources } - } - if arguments["--no-publish"].(bool) { - b := true - args.NoPublish = &b - } - if v := arguments["--sleep-interval"]; v != nil { - log.Printf("WARNING: --sleep-interval is deprecated, use 'core.sleepInterval' option in the config file, instead") - if s, err := time.ParseDuration(v.(string)); err != nil { - return args, fmt.Errorf("invalid --sleep-interval specified: %s", err.Error()) - } else { - args.SleepInterval = &s - } - } - if v := arguments["--sources"]; v != nil { - fmt.Println(v) - s := strings.Split(v.(string), ",") - args.Sources = &s - } - return args, nil + }) + + return args +} + +func initFlags(flagset *flag.FlagSet) (*worker.Args, *worker.ConfigOverrideArgs) { + args := &worker.Args{} + + flagset.StringVar(&args.CaFile, "ca-file", "", + "Root certificate for verifying connections") + flagset.StringVar(&args.CertFile, "cert-file", "", + "Certificate used for authenticating connections") + flagset.StringVar(&args.ConfigFile, "config", "/etc/kubernetes/node-feature-discovery/nfd-worker.conf", + "Config file to use.") + flagset.StringVar(&args.KeyFile, "key-file", "", + "Private key matching -cert-file") + flagset.BoolVar(&args.Oneshot, "oneshot", false, + "Do not publish feature labels") + flagset.StringVar(&args.Options, "options", "", + "Specify config options from command line. Config options are specified "+ + "in the same format as in the config file (i.e. json or yaml). These options") + flagset.StringVar(&args.Server, "server", "localhost:8080", + "NFD server address to connecto to.") + flagset.StringVar(&args.ServerNameOverride, "server-name-override", "", + "Hostname expected from server certificate, useful in testing") + + // Flags overlapping with config file options + overrides := &worker.ConfigOverrideArgs{ + LabelWhiteList: &utils.RegexpVal{}, + Sources: &utils.StringSliceVal{}, + } + overrides.NoPublish = flagset.Bool("no-publish", false, + "Do not publish discovered features, disable connection to nfd-master.") + flagset.Var(overrides.LabelWhiteList, "label-whitelist", + "Regular expression to filter label names to publish to the Kubernetes API server. "+ + "NB: the label namespace is omitted i.e. the filter is only applied to the name part after '/'. "+ + "DEPRECATED: This parameter should be set via the config file.") + overrides.SleepInterval = flagset.Duration("sleep-interval", 0, + "Time to sleep between re-labeling. Non-positive value implies no re-labeling (i.e. infinite sleep). "+ + "DEPRECATED: This parameter should be set via the config file") + flagset.Var(overrides.Sources, "sources", + "Comma separated list of feature sources. Special value 'all' enables all feature sources. "+ + "DEPRECATED: This parameter should be set via the config file") + + return args, overrides } diff --git a/cmd/nfd-worker/main_test.go b/cmd/nfd-worker/main_test.go index d6b222bc5..9481f8116 100644 --- a/cmd/nfd-worker/main_test.go +++ b/cmd/nfd-worker/main_test.go @@ -1,5 +1,5 @@ /* -Copyright 2019 The Kubernetes Authors. +Copyright 2019-2021 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. @@ -17,62 +17,44 @@ limitations under the License. package main import ( + "flag" "testing" "time" . "github.com/smartystreets/goconvey/convey" + + "sigs.k8s.io/node-feature-discovery/pkg/utils" ) -func TestArgsParse(t *testing.T) { +func TestParseArgs(t *testing.T) { Convey("When parsing command line arguments", t, func() { - Convey("When --no-publish and --oneshot flags are passed", func() { - args, err := argsParse([]string{"--no-publish", "--oneshot"}) + flags := flag.NewFlagSet(ProgramName, flag.ExitOnError) - Convey("noPublish is set and args.sources is set to the default value", func() { - So(args.SleepInterval, ShouldEqual, nil) - So(*args.NoPublish, ShouldBeTrue) + Convey("When no override args are specified", func() { + args := parseArgs(flags, "--oneshot") + + Convey("overrides should be nil", func() { So(args.Oneshot, ShouldBeTrue) - So(args.Sources, ShouldBeNil) - So(args.LabelWhiteList, ShouldBeNil) - So(err, ShouldBeNil) + So(args.Overrides.NoPublish, ShouldBeNil) + So(args.Overrides.LabelWhiteList, ShouldBeNil) + So(args.Overrides.SleepInterval, ShouldBeNil) + So(args.Overrides.Sources, ShouldBeNil) }) }) - Convey("When --sources flag is passed and set to some values, --sleep-inteval is specified", func() { - args, err := argsParse([]string{"--sources=fake1,fake2,fake3", "--sleep-interval=30s"}) + Convey("When all override args are specified", func() { + args := parseArgs(flags, + "--no-publish", + "-label-whitelist=.*rdt.*", + "-sources=fake1,fake2,fake3", + "-sleep-interval=30s") Convey("args.sources is set to appropriate values", func() { - So(*args.SleepInterval, ShouldEqual, 30*time.Second) - So(args.NoPublish, ShouldBeNil) So(args.Oneshot, ShouldBeFalse) - So(*args.Sources, ShouldResemble, []string{"fake1", "fake2", "fake3"}) - So(args.LabelWhiteList, ShouldBeNil) - So(err, ShouldBeNil) - }) - }) - - Convey("When --label-whitelist flag is passed and set to some value", func() { - args, err := argsParse([]string{"--label-whitelist=.*rdt.*"}) - - Convey("args.labelWhiteList is set to appropriate value and args.sources is set to default value", func() { - So(args.NoPublish, ShouldBeNil) - So(args.Sources, ShouldBeNil) - So(args.LabelWhiteList.String(), ShouldResemble, ".*rdt.*") - So(err, ShouldBeNil) - }) - }) - - Convey("When valid args are specified", func() { - args, err := argsParse([]string{"--no-publish", "--sources=fake1,fake2,fake3", "--ca-file=ca", "--cert-file=crt", "--key-file=key"}) - - Convey("--no-publish is set and args.sources is set to appropriate values", func() { - So(*args.NoPublish, ShouldBeTrue) - So(args.CaFile, ShouldEqual, "ca") - So(args.CertFile, ShouldEqual, "crt") - So(args.KeyFile, ShouldEqual, "key") - So(*args.Sources, ShouldResemble, []string{"fake1", "fake2", "fake3"}) - So(args.LabelWhiteList, ShouldBeNil) - So(err, ShouldBeNil) + So(*args.Overrides.NoPublish, ShouldBeTrue) + So(*args.Overrides.SleepInterval, ShouldEqual, 30*time.Second) + So(*args.Overrides.Sources, ShouldResemble, utils.StringSliceVal{"fake1", "fake2", "fake3"}) + So(args.Overrides.LabelWhiteList.Regexp.String(), ShouldResemble, ".*rdt.*") }) }) }) diff --git a/docs/advanced/developer-guide.md b/docs/advanced/developer-guide.md index 80268bcf1..ce472f50a 100644 --- a/docs/advanced/developer-guide.md +++ b/docs/advanced/developer-guide.md @@ -164,43 +164,34 @@ $ docker run --rm --name=nfd-test ${NFD_CONTAINER_IMAGE} nfd-master --no-publish Command line flags of nfd-master: ```bash -$ docker run --rm ${NFD_CONTAINER_IMAGE} nfd-master --help -... -Usage: - nfd-master [--prune] [--no-publish] [--label-whitelist=] [--port=] - [--ca-file=] [--cert-file=] [--key-file=] - [--verify-node-name] [--extra-label-ns=] [--resource-labels=] - [--kubeconfig=] - nfd-master -h | --help - nfd-master --version - - Options: - -h --help Show this screen. - --version Output version and exit. - --prune Prune all NFD related attributes from all nodes - of the cluster and exit. - --kubeconfig= Kubeconfig to use [Default: ] - --port= Port on which to listen for connections. - [Default: 8080] - --ca-file= Root certificate for verifying connections - [Default: ] - --cert-file= Certificate used for authenticating connections - [Default: ] - --key-file= Private key matching --cert-file - [Default: ] - --verify-node-name Verify worker node name against CN from the TLS - certificate. Only has effect when TLS authentication - has been enabled. - --no-publish Do not publish feature labels - --label-whitelist= Regular expression to filter label names to - publish to the Kubernetes API server. - NB: the label namespace is omitted i.e. the filter - is only applied to the name part after '/'. - [Default: ] - --extra-label-ns= Comma separated list of allowed extra label namespaces - [Default: ] - --resource-labels= Comma separated list of labels to be exposed as extended resources. - [Default: ] +$ docker run --rm ${NFD_CONTAINER_IMAGE} nfd-master -help +Usage of nfd-master: + -ca-file string + Root certificate for verifying connections + -cert-file string + Certificate used for authenticating connections + -extra-label-ns value + Comma separated list of allowed extra label namespaces + -instance string + Instance name. Used to separate annotation namespaces for multiple parallel deployments. + -key-file string + Private key matching -cert-file + -kubeconfig string + Kubeconfig to use + -label-whitelist value + Regular expression to filter label names to publish to the Kubernetes API server. NB: the label namespace is omitted i.e. the filter is only applied to the name part after '/'. + -no-publish + Do not publish feature labels + -port int + Port on which to listen for connections. (default 8080) + -prune + Prune all NFD related attributes from all nodes of the cluaster and exit. + -resource-labels value + Comma separated list of labels to be exposed as extended resources. + -verify-node-name + Verify worker node name against CN from the TLS certificate. Only takes effect when TLS authentication has been enabled. + -version + Print version and exit. ``` ### NFD-Worker @@ -220,52 +211,34 @@ pass the `--no-publish` flag to nfd-worker. Command line flags of nfd-worker: ```bash -$ docker run --rm ${NFD_CONTAINER_IMAGE} nfd-worker --help -... - Usage: - nfd-worker [--no-publish] [--sources=] [--label-whitelist=] - [--oneshot | --sleep-interval=] [--config=] - [--options=] [--server=] [--server-name-override=] - [--ca-file=] [--cert-file=] [--key-file=] - nfd-worker -h | --help - nfd-worker --version - - Options: - -h --help Show this screen. - --version Output version and exit. - --config= Config file to use. - [Default: /etc/kubernetes/node-feature-discovery/nfd-worker.conf] - --options= Specify config options from command line. Config - options are specified in the same format as in the - config file (i.e. json or yaml). These options - will override settings read from the config file. - [Default: ] - --ca-file= Root certificate for verifying connections - [Default: ] - --cert-file= Certificate used for authenticating connections - [Default: ] - --key-file= Private key matching --cert-file - [Default: ] - --server= NFD server address to connecto to. - [Default: localhost:8080] - --server-name-override= Name (CN) expect from server certificate, useful - in testing - [Default: ] - --sources= Comma separated list of feature sources. Special - value 'all' enables all feature sources. - [Default: all] - --no-publish Do not publish discovered features to the - cluster-local Kubernetes API server. - --label-whitelist= Regular expression to filter label names to - publish to the Kubernetes API server. - NB: the label namespace is omitted i.e. the filter - is only applied to the name part after '/'. - [Default: ] - --oneshot Label once and exit. - --sleep-interval= Time to sleep between re-labeling. Non-positive - value implies no re-labeling (i.e. infinite - sleep). [Default: 60s] - +$ docker run --rm ${NFD_CONTAINER_IMAGE} nfd-worker -help +Usage of nfd-worker: + -ca-file string + Root certificate for verifying connections + -cert-file string + Certificate used for authenticating connections + -config string + Config file to use. (default "/etc/kubernetes/node-feature-discovery/nfd-worker.conf") + -key-file string + Private key matching -cert-file + -label-whitelist value + Regular expression to filter label names to publish to the Kubernetes API server. NB: the label namespace is omitted i.e. the filter is only applied to the name part after '/'. DEPRECATED: This parameter should be set via the config file. + -no-publish + Do not publish discovered features, disable connection to nfd-master. + -oneshot + Do not publish feature labels + -options string + Specify config options from command line. Config options are specified in the same format as in the config file (i.e. json or yaml). These options + -server string + NFD server address to connecto to. (default "localhost:8080") + -server-name-override string + Hostname expected from server certificate, useful in testing + -sleep-interval duration + Time to sleep between re-labeling. Non-positive value implies no re-labeling (i.e. infinite sleep). DEPRECATED: This parameter should be set via the config file + -sources value + Comma separated list of feature sources. Special value 'all' enables all feature sources. DEPRECATED: This parameter should be set via the config file + -version + Print version and exit. ``` **NOTE** Some feature sources need certain directories and/or files from the diff --git a/go.mod b/go.mod index 5ef0f147c..3895db012 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module sigs.k8s.io/node-feature-discovery go 1.15 require ( - github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 github.com/fsnotify/fsnotify v1.4.9 github.com/golang/protobuf v1.4.3 github.com/klauspost/cpuid/v2 v2.0.2 diff --git a/pkg/nfd-master/nfd-master-internal_test.go b/pkg/nfd-master/nfd-master-internal_test.go index 45a1d239e..3accdaaa6 100644 --- a/pkg/nfd-master/nfd-master-internal_test.go +++ b/pkg/nfd-master/nfd-master-internal_test.go @@ -1,5 +1,5 @@ /* -Copyright 2019 The Kubernetes Authors. +Copyright 2019-2021 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. @@ -34,6 +34,7 @@ import ( k8sclient "k8s.io/client-go/kubernetes" "sigs.k8s.io/node-feature-discovery/pkg/apihelper" "sigs.k8s.io/node-feature-discovery/pkg/labeler" + "sigs.k8s.io/node-feature-discovery/pkg/utils" "sigs.k8s.io/node-feature-discovery/pkg/version" "sigs.k8s.io/yaml" ) @@ -55,7 +56,7 @@ func newMockMaster(apihelper apihelper.APIHelpers) *nfdMaster { return &nfdMaster{ nodeName: mockNodeName, annotationNs: AnnotationNsBase, - args: Args{LabelWhiteList: regexp.MustCompile("")}, + args: Args{LabelWhiteList: utils.RegexpVal{Regexp: *regexp.MustCompile("")}}, apihelper: apihelper, } } @@ -339,7 +340,7 @@ func TestSetLabels(t *testing.T) { apihelper.NewJsonPatch("add", "/metadata/labels", LabelNs+"/feature-2", mockLabels["feature-2"]), } - mockMaster.args.LabelWhiteList = regexp.MustCompile("^f.*2$") + mockMaster.args.LabelWhiteList.Regexp = *regexp.MustCompile("^f.*2$") mockHelper.On("GetClient").Return(mockClient, nil) mockHelper.On("GetNode", mockClient, workerName).Return(mockNode, nil) mockHelper.On("PatchNode", mockClient, mockNodeName, mock.MatchedBy(jsonPatchMatcher(expectedPatches))).Return(nil) @@ -390,7 +391,7 @@ func TestSetLabels(t *testing.T) { apihelper.NewJsonPatch("add", "/status/capacity", LabelNs+"/feature-3", mockLabels["feature-3"]), } - mockMaster.args.ResourceLabels = []string{"feature-3", "feature-1"} + mockMaster.args.ResourceLabels = map[string]struct{}{"feature-3": struct{}{}, "feature-1": struct{}{}} mockHelper.On("GetClient").Return(mockClient, nil) mockHelper.On("GetNode", mockClient, workerName).Return(mockNode, nil) mockHelper.On("PatchNode", mockClient, mockNodeName, mock.MatchedBy(jsonPatchMatcher(expectedPatches))).Return(nil) diff --git a/pkg/nfd-master/nfd-master.go b/pkg/nfd-master/nfd-master.go index 6a3ac39c5..f1e9ed378 100644 --- a/pkg/nfd-master/nfd-master.go +++ b/pkg/nfd-master/nfd-master.go @@ -1,5 +1,5 @@ /* -Copyright 2019 The Kubernetes Authors. +Copyright 2019-2021 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. @@ -38,6 +38,7 @@ import ( api "k8s.io/api/core/v1" "sigs.k8s.io/node-feature-discovery/pkg/apihelper" pb "sigs.k8s.io/node-feature-discovery/pkg/labeler" + "sigs.k8s.io/node-feature-discovery/pkg/utils" "sigs.k8s.io/node-feature-discovery/pkg/version" ) @@ -74,16 +75,16 @@ type Annotations map[string]string type Args struct { CaFile string CertFile string - ExtraLabelNs map[string]struct{} + ExtraLabelNs utils.StringSetVal Instance string KeyFile string Kubeconfig string - LabelWhiteList *regexp.Regexp + LabelWhiteList utils.RegexpVal NoPublish bool Port int Prune bool VerifyNodeName bool - ResourceLabels []string + ResourceLabels utils.StringSetVal } type NfdMaster interface { @@ -102,8 +103,8 @@ type nfdMaster struct { } // Create new NfdMaster server instance. -func NewNfdMaster(args Args) (NfdMaster, error) { - nfd := &nfdMaster{args: args, +func NewNfdMaster(args *Args) (NfdMaster, error) { + nfd := &nfdMaster{args: *args, nodeName: os.Getenv("NODE_NAME"), ready: make(chan bool, 1), } @@ -111,6 +112,11 @@ func NewNfdMaster(args Args) (NfdMaster, error) { if args.Instance == "" { nfd.annotationNs = AnnotationNsBase } else { + if ok, _ := regexp.MatchString(`^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$`, args.Instance); !ok { + return nfd, fmt.Errorf("invalid --instance %q: instance name "+ + "must start and end with an alphanumeric character and may only contain "+ + "alphanumerics, `-`, `_` or `.`", args.Instance) + } nfd.annotationNs = args.Instance + "." + AnnotationNsBase } @@ -283,7 +289,7 @@ func (m *nfdMaster) updateMasterNode() error { // into extended resources. This function also handles proper namespacing of // labels and ERs, i.e. adds the possibly missing default namespace for labels // arriving through the gRPC API. -func filterFeatureLabels(labels Labels, extraLabelNs map[string]struct{}, labelWhiteList *regexp.Regexp, extendedResourceNames []string) (Labels, ExtendedResources) { +func filterFeatureLabels(labels Labels, extraLabelNs map[string]struct{}, labelWhiteList regexp.Regexp, extendedResourceNames map[string]struct{}) (Labels, ExtendedResources) { outLabels := Labels{} for label, value := range labels { @@ -310,7 +316,7 @@ func filterFeatureLabels(labels Labels, extraLabelNs map[string]struct{}, labelW // Remove labels which are intended to be extended resources extendedResources := ExtendedResources{} - for _, extendedResourceName := range extendedResourceNames { + for extendedResourceName := range extendedResourceNames { // Add possibly missing default ns extendedResourceName = addNs(extendedResourceName, LabelNs) if value, ok := outLabels[extendedResourceName]; ok { @@ -354,7 +360,7 @@ func (m *nfdMaster) SetLabels(c context.Context, r *pb.SetLabelsRequest) (*pb.Se } stdoutLogger.Printf("REQUEST Node: %s NFD-version: %s Labels: %s", r.NodeName, r.NfdVersion, r.Labels) - labels, extendedResources := filterFeatureLabels(r.Labels, m.args.ExtraLabelNs, m.args.LabelWhiteList, m.args.ResourceLabels) + labels, extendedResources := filterFeatureLabels(r.Labels, m.args.ExtraLabelNs, m.args.LabelWhiteList.Regexp, m.args.ResourceLabels) if !m.args.NoPublish { // Advertise NFD worker version as an annotation diff --git a/pkg/nfd-master/nfd-master_test.go b/pkg/nfd-master/nfd-master_test.go index d3d5e085c..43b0b60a6 100644 --- a/pkg/nfd-master/nfd-master_test.go +++ b/pkg/nfd-master/nfd-master_test.go @@ -1,5 +1,5 @@ /* -Copyright 2019 The Kubernetes Authors. +Copyright 2019-2021 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. @@ -26,9 +26,9 @@ import ( func TestNewNfdMaster(t *testing.T) { Convey("When initializing new NfdMaster instance", t, func() { Convey("When one of --cert-file, --key-file or --ca-file is missing", func() { - _, err := m.NewNfdMaster(m.Args{CertFile: "crt", KeyFile: "key"}) - _, err2 := m.NewNfdMaster(m.Args{KeyFile: "key", CaFile: "ca"}) - _, err3 := m.NewNfdMaster(m.Args{CertFile: "crt", CaFile: "ca"}) + _, err := m.NewNfdMaster(&m.Args{CertFile: "crt", KeyFile: "key"}) + _, err2 := m.NewNfdMaster(&m.Args{KeyFile: "key", CaFile: "ca"}) + _, err3 := m.NewNfdMaster(&m.Args{CertFile: "crt", CaFile: "ca"}) Convey("An error should be returned", func() { So(err, ShouldNotBeNil) So(err2, ShouldNotBeNil) diff --git a/pkg/nfd-worker/nfd-worker-internal_test.go b/pkg/nfd-worker/nfd-worker-internal_test.go index eaeffa122..8b2943ad5 100644 --- a/pkg/nfd-worker/nfd-worker-internal_test.go +++ b/pkg/nfd-worker/nfd-worker-internal_test.go @@ -1,5 +1,5 @@ /* -Copyright 2019 The Kubernetes Authors. +Copyright 2019-2021 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. @@ -26,12 +26,12 @@ import ( "testing" "time" - //"github.com/onsi/gomega" . "github.com/smartystreets/goconvey/convey" "github.com/stretchr/testify/mock" "github.com/vektra/errors" "sigs.k8s.io/node-feature-discovery/pkg/labeler" + "sigs.k8s.io/node-feature-discovery/pkg/utils" "sigs.k8s.io/node-feature-discovery/source" "sigs.k8s.io/node-feature-discovery/source/cpu" "sigs.k8s.io/node-feature-discovery/source/fake" @@ -53,13 +53,13 @@ func TestDiscoveryWithMockSources(t *testing.T) { fakeFeatureSource := source.FeatureSource(mockFeatureSource) - labelWhiteList := regex{*regexp.MustCompile("^test")} + labelWhiteList := utils.RegexpVal{Regexp: *regexp.MustCompile("^test")} Convey("When I successfully get the labels from the mock source", func() { mockFeatureSource.On("Name").Return(fakeFeatureSourceName) mockFeatureSource.On("Discover").Return(fakeFeatures, nil) - returnedLabels, err := getFeatureLabels(fakeFeatureSource, labelWhiteList) + returnedLabels, err := getFeatureLabels(fakeFeatureSource, labelWhiteList.Regexp) Convey("Proper label is returned", func() { So(returnedLabels, ShouldResemble, fakeFeatureLabels) }) @@ -72,7 +72,7 @@ func TestDiscoveryWithMockSources(t *testing.T) { expectedError := errors.New("fake error") mockFeatureSource.On("Discover").Return(nil, expectedError) - returnedLabels, err := getFeatureLabels(fakeFeatureSource, labelWhiteList) + returnedLabels, err := getFeatureLabels(fakeFeatureSource, labelWhiteList.Regexp) Convey("No label is returned", func() { So(returnedLabels, ShouldBeNil) }) @@ -109,7 +109,7 @@ func (w *nfdWorker) getSource(name string) source.FeatureSource { func TestConfigParse(t *testing.T) { Convey("When parsing configuration", t, func() { - w, err := NewNfdWorker(Args{}) + w, err := NewNfdWorker(&Args{}) So(err, ShouldBeNil) worker := w.(*nfdWorker) overrides := `{"core": {"sources": ["fake"],"noPublish": true},"sources": {"cpu": {"cpuid": {"attributeBlacklist": ["foo","bar"]}}}}` @@ -123,7 +123,7 @@ func TestConfigParse(t *testing.T) { }) }) Convey("and a non-accessible file, but core cmdline flags and some overrides are specified", func() { - worker.args = Args{Sources: &[]string{"cpu", "kernel", "pci"}} + worker.args = Args{Overrides: ConfigOverrideArgs{Sources: &utils.StringSliceVal{"cpu", "kernel", "pci"}}} So(worker.configure("non-existing-file", overrides), ShouldBeNil) Convey("core cmdline flags should be in effect instead overrides", func() { @@ -157,7 +157,7 @@ sources: So(err, ShouldBeNil) Convey("and a proper config file is specified", func() { - worker.args = Args{Sources: &[]string{"cpu", "kernel", "pci"}} + worker.args = Args{Overrides: ConfigOverrideArgs{Sources: &utils.StringSliceVal{"cpu", "kernel", "pci"}}} So(worker.configure(f.Name(), ""), ShouldBeNil) Convey("specified configuration should take effect", func() { @@ -178,7 +178,7 @@ sources: Convey("and a proper config file and overrides are given", func() { sleepIntervalArg := 15 * time.Second - worker.args = Args{SleepInterval: &sleepIntervalArg} + worker.args = Args{Overrides: ConfigOverrideArgs{SleepInterval: &sleepIntervalArg}} overrides := `{"core": {"sources": ["fake"],"noPublish": true},"sources": {"pci": {"deviceClassWhitelist": ["03"]}}}` So(worker.configure(f.Name(), overrides), ShouldBeNil) @@ -229,11 +229,11 @@ core: `) noPublish := true - w, err := NewNfdWorker(Args{ - ConfigFile: configFile, - Sources: &[]string{"fake"}, - NoPublish: &noPublish, - SleepInterval: new(time.Duration), + w, err := NewNfdWorker(&Args{ + ConfigFile: configFile, + Overrides: ConfigOverrideArgs{ + Sources: &utils.StringSliceVal{"fake"}, + NoPublish: &noPublish}, }) So(err, ShouldBeNil) worker := w.(*nfdWorker) @@ -309,10 +309,10 @@ func withTimeout(actual interface{}, expected ...interface{}) string { func TestNewNfdWorker(t *testing.T) { Convey("When creating new NfdWorker instance", t, func() { - emptyRegexp := regex{*regexp.MustCompile("")} + emptyRegexp := utils.RegexpVal{Regexp: *regexp.MustCompile("")} Convey("without any args specified", func() { - args := Args{} + args := &Args{} w, err := NewNfdWorker(args) Convey("no error should be returned", func() { So(err, ShouldBeNil) @@ -326,7 +326,7 @@ func TestNewNfdWorker(t *testing.T) { }) Convey("with non-empty Sources arg specified", func() { - args := Args{Sources: &[]string{"fake"}} + args := &Args{Overrides: ConfigOverrideArgs{Sources: &utils.StringSliceVal{"fake"}}} w, err := NewNfdWorker(args) Convey("no error should be returned", func() { So(err, ShouldBeNil) @@ -341,14 +341,14 @@ func TestNewNfdWorker(t *testing.T) { }) Convey("with valid LabelWhiteListStr arg specified", func() { - args := Args{LabelWhiteList: regexp.MustCompile(".*rdt.*")} + args := &Args{Overrides: ConfigOverrideArgs{LabelWhiteList: &utils.RegexpVal{Regexp: *regexp.MustCompile(".*rdt.*")}}} w, err := NewNfdWorker(args) Convey("no error should be returned", func() { So(err, ShouldBeNil) }) worker := w.(*nfdWorker) So(worker.configure("", ""), ShouldBeNil) - expectRegexp := regex{*regexp.MustCompile(".*rdt.*")} + expectRegexp := utils.RegexpVal{Regexp: *regexp.MustCompile(".*rdt.*")} Convey("proper labelWhiteList regexp should be produced", func() { So(worker.config.Core.LabelWhiteList, ShouldResemble, expectRegexp) }) @@ -359,12 +359,12 @@ func TestNewNfdWorker(t *testing.T) { func TestCreateFeatureLabels(t *testing.T) { Convey("When creating feature labels from the configured sources", t, func() { Convey("When fake feature source is configured", func() { - emptyLabelWL := regex{*regexp.MustCompile("")} + emptyLabelWL := regexp.MustCompile("") fakeFeatureSource := source.FeatureSource(new(fake.Source)) fakeFeatureSource.SetConfig(fakeFeatureSource.NewConfig()) sources := []source.FeatureSource{} sources = append(sources, fakeFeatureSource) - labels := createFeatureLabels(sources, emptyLabelWL) + labels := createFeatureLabels(sources, *emptyLabelWL) Convey("Proper fake labels are returned", func() { So(len(labels), ShouldEqual, 3) @@ -374,11 +374,10 @@ func TestCreateFeatureLabels(t *testing.T) { }) }) Convey("When fake feature source is configured with a whitelist that doesn't match", func() { - labelWL := regex{*regexp.MustCompile(".*rdt.*")} fakeFeatureSource := source.FeatureSource(new(fake.Source)) sources := []source.FeatureSource{} sources = append(sources, fakeFeatureSource) - labels := createFeatureLabels(sources, labelWL) + labels := createFeatureLabels(sources, *regexp.MustCompile(".*rdt.*")) Convey("fake labels are not returned", func() { So(len(labels), ShouldEqual, 0) @@ -394,7 +393,7 @@ func TestGetFeatureLabels(t *testing.T) { Convey("When I get feature labels and panic occurs during discovery of a feature source", t, func() { fakePanicFeatureSource := source.FeatureSource(new(panicfake.Source)) - returnedLabels, err := getFeatureLabels(fakePanicFeatureSource, regex{*regexp.MustCompile("")}) + returnedLabels, err := getFeatureLabels(fakePanicFeatureSource, *regexp.MustCompile("")) Convey("No label is returned", func() { So(len(returnedLabels), ShouldEqual, 0) }) diff --git a/pkg/nfd-worker/nfd-worker.go b/pkg/nfd-worker/nfd-worker.go index 454eede3d..36beb6f8d 100644 --- a/pkg/nfd-worker/nfd-worker.go +++ b/pkg/nfd-worker/nfd-worker.go @@ -1,5 +1,5 @@ /* -Copyright 2019 The Kubernetes Authors. +Copyright 2019-2021 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. @@ -34,7 +34,10 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials" "k8s.io/apimachinery/pkg/util/validation" + "sigs.k8s.io/yaml" + pb "sigs.k8s.io/node-feature-discovery/pkg/labeler" + "sigs.k8s.io/node-feature-discovery/pkg/utils" "sigs.k8s.io/node-feature-discovery/pkg/version" "sigs.k8s.io/node-feature-discovery/source" "sigs.k8s.io/node-feature-discovery/source/cpu" @@ -50,7 +53,6 @@ import ( "sigs.k8s.io/node-feature-discovery/source/storage" "sigs.k8s.io/node-feature-discovery/source/system" "sigs.k8s.io/node-feature-discovery/source/usb" - "sigs.k8s.io/yaml" ) var ( @@ -66,7 +68,7 @@ type NFDConfig struct { } type coreConfig struct { - LabelWhiteList regex + LabelWhiteList utils.RegexpVal NoPublish bool Sources []string SleepInterval duration @@ -87,11 +89,18 @@ type Args struct { Oneshot bool Server string ServerNameOverride string - // Deprecated options that should be set via the config file - LabelWhiteList *regexp.Regexp - NoPublish *bool + + Overrides ConfigOverrideArgs +} + +// ConfigOverrideArgs are args that override config file options +type ConfigOverrideArgs struct { + NoPublish *bool + + // Deprecated + LabelWhiteList *utils.RegexpVal SleepInterval *time.Duration - Sources *[]string + Sources *utils.StringSliceVal } type NfdWorker interface { @@ -111,18 +120,14 @@ type nfdWorker struct { enabledSources []source.FeatureSource } -type regex struct { - regexp.Regexp -} - type duration struct { time.Duration } // Create new NfdWorker instance. -func NewNfdWorker(args Args) (NfdWorker, error) { +func NewNfdWorker(args *Args) (NfdWorker, error) { nfd := &nfdWorker{ - args: args, + args: *args, config: &NFDConfig{}, realSources: []source.FeatureSource{ &cpu.Source{}, @@ -203,7 +208,7 @@ func addConfigWatch(path string) (*fsnotify.Watcher, map[string]struct{}, error) func newDefaultConfig() *NFDConfig { return &NFDConfig{ Core: coreConfig{ - LabelWhiteList: regex{*regexp.MustCompile("")}, + LabelWhiteList: utils.RegexpVal{Regexp: *regexp.MustCompile("")}, SleepInterval: duration{60 * time.Second}, Sources: []string{"all"}, }, @@ -238,7 +243,7 @@ func (w *nfdWorker) Run() error { select { case <-labelTrigger: // Get the set of feature labels. - labels := createFeatureLabels(w.enabledSources, w.config.Core.LabelWhiteList) + labels := createFeatureLabels(w.enabledSources, w.config.Core.LabelWhiteList.Regexp) // Update the node with the feature labels. if w.client != nil { @@ -448,17 +453,17 @@ func (w *nfdWorker) configure(filepath string, overrides string) error { return fmt.Errorf("Failed to parse --options: %s", err) } - if w.args.LabelWhiteList != nil { - c.Core.LabelWhiteList = regex{*w.args.LabelWhiteList} + if w.args.Overrides.LabelWhiteList != nil { + c.Core.LabelWhiteList = *w.args.Overrides.LabelWhiteList } - if w.args.NoPublish != nil { - c.Core.NoPublish = *w.args.NoPublish + if w.args.Overrides.NoPublish != nil { + c.Core.NoPublish = *w.args.Overrides.NoPublish } - if w.args.SleepInterval != nil { - c.Core.SleepInterval = duration{*w.args.SleepInterval} + if w.args.Overrides.SleepInterval != nil { + c.Core.SleepInterval = duration{*w.args.Overrides.SleepInterval} } - if w.args.Sources != nil { - c.Core.Sources = *w.args.Sources + if w.args.Overrides.Sources != nil { + c.Core.Sources = *w.args.Overrides.Sources } c.Core.sanitize() @@ -477,7 +482,7 @@ func (w *nfdWorker) configure(filepath string, overrides string) error { // createFeatureLabels returns the set of feature labels from the enabled // sources and the whitelist argument. -func createFeatureLabels(sources []source.FeatureSource, labelWhiteList regex) (labels Labels) { +func createFeatureLabels(sources []source.FeatureSource, labelWhiteList regexp.Regexp) (labels Labels) { labels = Labels{} // Do feature discovery from all configured sources. @@ -500,7 +505,7 @@ func createFeatureLabels(sources []source.FeatureSource, labelWhiteList regex) ( // getFeatureLabels returns node labels for features discovered by the // supplied source. -func getFeatureLabels(source source.FeatureSource, labelWhiteList regex) (labels Labels, err error) { +func getFeatureLabels(source source.FeatureSource, labelWhiteList regexp.Regexp) (labels Labels, err error) { defer func() { if r := recover(); r != nil { stderrLogger.Printf("panic occurred during discovery of source [%s]: %v", source.Name(), r) @@ -584,25 +589,6 @@ func advertiseFeatureLabels(client pb.LabelerClient, labels Labels) error { return nil } -// UnmarshalJSON implements the Unmarshaler interface from "encoding/json" -func (r *regex) UnmarshalJSON(data []byte) error { - var v interface{} - if err := json.Unmarshal(data, &v); err != nil { - return err - } - switch val := v.(type) { - case string: - if rr, err := regexp.Compile(string(val)); err != nil { - return err - } else { - *r = regex{*rr} - } - default: - return fmt.Errorf("invalid regexp %s", data) - } - return nil -} - // UnmarshalJSON implements the Unmarshaler interface from "encoding/json" func (d *duration) UnmarshalJSON(data []byte) error { var v interface{} diff --git a/pkg/nfd-worker/nfd-worker_test.go b/pkg/nfd-worker/nfd-worker_test.go index 2707a98bb..2f7b4ea9b 100644 --- a/pkg/nfd-worker/nfd-worker_test.go +++ b/pkg/nfd-worker/nfd-worker_test.go @@ -1,5 +1,5 @@ /* -Copyright 2019 The Kubernetes Authors. +Copyright 2019-2021 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. @@ -24,22 +24,24 @@ import ( "time" . "github.com/smartystreets/goconvey/convey" - nfdmaster "sigs.k8s.io/node-feature-discovery/pkg/nfd-master" - w "sigs.k8s.io/node-feature-discovery/pkg/nfd-worker" + + master "sigs.k8s.io/node-feature-discovery/pkg/nfd-master" + worker "sigs.k8s.io/node-feature-discovery/pkg/nfd-worker" + "sigs.k8s.io/node-feature-discovery/pkg/utils" "sigs.k8s.io/node-feature-discovery/test/data" ) type testContext struct { - master nfdmaster.NfdMaster + master master.NfdMaster errs chan error } -func setupTest(args nfdmaster.Args) testContext { +func setupTest(args *master.Args) testContext { // Fixed port and no-publish, for convenience args.NoPublish = true args.Port = 8192 - args.LabelWhiteList = regexp.MustCompile("") - m, err := nfdmaster.NewNfdMaster(args) + args.LabelWhiteList.Regexp = *regexp.MustCompile("") + m, err := master.NewNfdMaster(args) if err != nil { fmt.Printf("Test setup failed: %v\n", err) os.Exit(1) @@ -73,9 +75,9 @@ func teardownTest(ctx testContext) { func TestNewNfdWorker(t *testing.T) { Convey("When initializing new NfdWorker instance", t, func() { Convey("When one of --cert-file, --key-file or --ca-file is missing", func() { - _, err := w.NewNfdWorker(w.Args{CertFile: "crt", KeyFile: "key"}) - _, err2 := w.NewNfdWorker(w.Args{KeyFile: "key", CaFile: "ca"}) - _, err3 := w.NewNfdWorker(w.Args{CertFile: "crt", CaFile: "ca"}) + _, err := worker.NewNfdWorker(&worker.Args{CertFile: "crt", KeyFile: "key"}) + _, err2 := worker.NewNfdWorker(&worker.Args{KeyFile: "key", CaFile: "ca"}) + _, err3 := worker.NewNfdWorker(&worker.Args{CertFile: "crt", CaFile: "ca"}) Convey("An error should be returned", func() { So(err, ShouldNotBeNil) So(err2, ShouldNotBeNil) @@ -86,12 +88,17 @@ func TestNewNfdWorker(t *testing.T) { } func TestRun(t *testing.T) { - ctx := setupTest(nfdmaster.Args{}) + ctx := setupTest(&master.Args{}) defer teardownTest(ctx) Convey("When running nfd-worker against nfd-master", t, func() { Convey("When publishing features from fake source", func() { - worker, _ := w.NewNfdWorker(w.Args{Oneshot: true, Sources: &[]string{"fake"}, Server: "localhost:8192"}) - err := worker.Run() + args := &worker.Args{ + Oneshot: true, + Server: "localhost:8192", + Overrides: worker.ConfigOverrideArgs{Sources: &utils.StringSliceVal{"fake"}}, + } + fooasdf, _ := worker.NewNfdWorker(args) + err := fooasdf.Run() Convey("No error should be returned", func() { So(err, ShouldBeNil) }) @@ -100,7 +107,7 @@ func TestRun(t *testing.T) { } func TestRunTls(t *testing.T) { - masterArgs := nfdmaster.Args{ + masterArgs := &master.Args{ CaFile: data.FilePath("ca.crt"), CertFile: data.FilePath("nfd-test-master.crt"), KeyFile: data.FilePath("nfd-test-master.key"), @@ -110,16 +117,17 @@ func TestRunTls(t *testing.T) { defer teardownTest(ctx) Convey("When running nfd-worker against nfd-master with mutual TLS auth enabled", t, func() { Convey("When publishing features from fake source", func() { - workerArgs := w.Args{ + workerArgs := worker.Args{ CaFile: data.FilePath("ca.crt"), CertFile: data.FilePath("nfd-test-worker.crt"), KeyFile: data.FilePath("nfd-test-worker.key"), Oneshot: true, - Sources: &[]string{"fake"}, Server: "localhost:8192", - ServerNameOverride: "nfd-test-master"} - worker, _ := w.NewNfdWorker(workerArgs) - err := worker.Run() + ServerNameOverride: "nfd-test-master", + Overrides: worker.ConfigOverrideArgs{Sources: &utils.StringSliceVal{"fake"}}, + } + w, _ := worker.NewNfdWorker(&workerArgs) + err := w.Run() Convey("No error should be returned", func() { So(err, ShouldBeNil) }) diff --git a/pkg/utils/flags.go b/pkg/utils/flags.go new file mode 100644 index 000000000..b91c70c7f --- /dev/null +++ b/pkg/utils/flags.go @@ -0,0 +1,100 @@ +/* +Copyright 2021 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 utils + +import ( + "encoding/json" + "fmt" + "regexp" + "sort" + "strings" +) + +// RegexpVal is a wrapper for regexp command line flags +type RegexpVal struct { + regexp.Regexp +} + +// Set implements the flag.Value interface +func (a *RegexpVal) Set(val string) error { + r, err := regexp.Compile(val) + a.Regexp = *r + return err +} + +// UnmarshalJSON implements the Unmarshaler interface from "encoding/json" +func (a *RegexpVal) UnmarshalJSON(data []byte) error { + var v interface{} + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch val := v.(type) { + case string: + if r, err := regexp.Compile(string(val)); err != nil { + return err + } else { + *a = RegexpVal{*r} + } + default: + return fmt.Errorf("invalid regexp %s", data) + } + return nil +} + +// StringSetVal is a Value encapsulating a set of comma-separated strings +type StringSetVal map[string]struct{} + +// Set implements the flag.Value interface +func (a *StringSetVal) Set(val string) error { + m := map[string]struct{}{} + for _, n := range strings.Split(val, ",") { + m[n] = struct{}{} + } + *a = m + return nil +} + +// String implements the flag.Value interface +func (a *StringSetVal) String() string { + if *a == nil { + return "" + } + + vals := make([]string, len(*a), 0) + for val := range *a { + vals = append(vals, val) + } + sort.Strings(vals) + return strings.Join(vals, ",") +} + +// StringSliceVal is a Value encapsulating a slice of comma-separated strings +type StringSliceVal []string + +// Set implements the regexp.Value interface +func (a *StringSliceVal) Set(val string) error { + *a = strings.Split(val, ",") + return nil +} + +// String implements the regexp.Value interface +func (a *StringSliceVal) String() string { + if *a == nil { + return "" + } + return strings.Join(*a, ",") +}