diff --git a/pkg/util/globalflag/globalflags.go b/pkg/util/globalflag/globalflags.go new file mode 100644 index 000000000..c00ef7b9f --- /dev/null +++ b/pkg/util/globalflag/globalflags.go @@ -0,0 +1,74 @@ +/* +Copyright 2018 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 globalflag + +import ( + "flag" + "fmt" + "os" + "strings" + + "github.com/spf13/pflag" + + "k8s.io/apiserver/pkg/util/logs" +) + +// AddGlobalFlags explicitly registers flags that libraries (klog, verflag, etc.) register +// against the global flagsets from "flag" and "github.com/spf13/pflag". +// We do this in order to prevent unwanted flags from leaking into the component's flagset. +func AddGlobalFlags(fs *pflag.FlagSet, name string) { + addGlogFlags(fs) + logs.AddFlags(fs) + + fs.BoolP("help", "h", false, fmt.Sprintf("help for %s", name)) +} + +// addGlogFlags explicitly registers flags that klog libraries(k8s.io/klog) register. +func addGlogFlags(fs *pflag.FlagSet) { + // lookup flags in global flag set and re-register the values with our flagset + global := flag.CommandLine + local := pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) + + register(global, local, "logtostderr") + register(global, local, "alsologtostderr") + register(global, local, "v") + register(global, local, "skip_headers") + register(global, local, "stderrthreshold") + register(global, local, "vmodule") + register(global, local, "log_backtrace_at") + register(global, local, "log_dir") + register(global, local, "log_file") + + fs.AddFlagSet(local) +} + +// normalize replaces underscores with hyphens +// we should always use hyphens instead of underscores when registering component flags +func normalize(s string) string { + return strings.Replace(s, "_", "-", -1) +} + +// register adds a flag to local that targets the Value associated with the Flag named globalName in global +func register(global *flag.FlagSet, local *pflag.FlagSet, globalName string) { + if f := global.Lookup(globalName); f != nil { + pflagFlag := pflag.PFlagFromGoFlag(f) + pflagFlag.Name = normalize(pflagFlag.Name) + local.AddFlag(pflagFlag) + } else { + panic(fmt.Sprintf("failed to find flag in global flagset (flag): %s", globalName)) + } +} diff --git a/pkg/util/globalflag/globalflags_test.go b/pkg/util/globalflag/globalflags_test.go new file mode 100644 index 000000000..16475154a --- /dev/null +++ b/pkg/util/globalflag/globalflags_test.go @@ -0,0 +1,86 @@ +/* +Copyright 2018 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 globalflag + +import ( + "flag" + "reflect" + "sort" + "strings" + "testing" + + "github.com/spf13/pflag" + + apiserverflag "k8s.io/apiserver/pkg/util/flag" +) + +func TestAddGlobalFlags(t *testing.T) { + namedFlagSets := &apiserverflag.NamedFlagSets{} + nfs := namedFlagSets.FlagSet("global") + AddGlobalFlags(nfs, "test-cmd") + + actualFlag := []string{} + nfs.VisitAll(func(flag *pflag.Flag) { + actualFlag = append(actualFlag, flag.Name) + }) + + // Get all flags from flags.CommandLine, except flag `test.*`. + wantedFlag := []string{"help"} + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.VisitAll(func(flag *pflag.Flag) { + if !strings.Contains(flag.Name, "test.") { + wantedFlag = append(wantedFlag, normalize(flag.Name)) + } + }) + sort.Strings(wantedFlag) + + if !reflect.DeepEqual(wantedFlag, actualFlag) { + t.Errorf("[Default]: expected %+v, got %+v", wantedFlag, actualFlag) + } + + tests := []struct { + expectedFlag []string + matchExpected bool + }{ + { + // Happy case + expectedFlag: []string{"alsologtostderr", "help", "log-backtrace-at", "log-dir", "log-file", "log-flush-frequency", "logtostderr", "skip-headers", "stderrthreshold", "v", "vmodule"}, + matchExpected: false, + }, + { + // Missing flag + expectedFlag: []string{"logtostderr", "log-dir"}, + matchExpected: true, + }, + { + // Empty flag + expectedFlag: []string{}, + matchExpected: true, + }, + { + // Invalid flag + expectedFlag: []string{"foo"}, + matchExpected: true, + }, + } + + for i, test := range tests { + if reflect.DeepEqual(test.expectedFlag, actualFlag) == test.matchExpected { + t.Errorf("[%d]: expected %+v, got %+v", i, test.expectedFlag, actualFlag) + } + } +}