From 5bd09a483ece534b794029af70e9614c977fe398 Mon Sep 17 00:00:00 2001 From: Yann Hamon Date: Sat, 31 Oct 2020 15:47:34 +0100 Subject: [PATCH] move flag handling to pkg --- cmd/kubeconform/main.go | 53 +++--------- pkg/config/config.go | 83 +++++++++++++++++++ .../main_test.go => pkg/config/config_test.go | 4 +- 3 files changed, 97 insertions(+), 43 deletions(-) create mode 100644 pkg/config/config.go rename cmd/kubeconform/main_test.go => pkg/config/config_test.go (92%) diff --git a/cmd/kubeconform/main.go b/cmd/kubeconform/main.go index d527cc2..db6f669 100644 --- a/cmd/kubeconform/main.go +++ b/cmd/kubeconform/main.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "github.com/xeipuuv/gojsonschema" + "github.com/yannh/kubeconform/pkg/config" "io" "io/ioutil" "os" @@ -201,31 +202,11 @@ func getFiles(files []string, fileBatches chan []string, validationResults chan } func realMain() int { - var schemaLocationsParam arrayParam - var skipKindsCSV, k8sVersion, outputFormat string - var summary, strict, verbose, ignoreMissingSchemas, help bool - var nWorkers int var err error - var files []string - flag.StringVar(&k8sVersion, "kubernetes-version", "1.18.0", "version of Kubernetes to validate against") - flag.Var(&schemaLocationsParam, "schema-location", "override schemas location search path (can be specified multiple times)") - flag.BoolVar(&ignoreMissingSchemas, "ignore-missing-schemas", false, "skip files with missing schemas instead of failing") - flag.BoolVar(&summary, "summary", false, "print a summary at the end") - flag.IntVar(&nWorkers, "n", 4, "number of routines to run in parallel") - flag.StringVar(&skipKindsCSV, "skip", "", "comma-separated list of kinds to ignore") - flag.BoolVar(&strict, "strict", false, "disallow additional properties not in schema") - flag.StringVar(&outputFormat, "output", "text", "output format - text, json") - flag.BoolVar(&verbose, "verbose", false, "print results for all resources") - flag.BoolVar(&help, "h", false, "show help information") - flag.Usage = func() { - fmt.Fprintf(os.Stderr, "Usage: %s [OPTION]... [FILE OR FOLDER]...\n", os.Args[0]) - flag.PrintDefaults() - } + cfg := config.FromFlags() - flag.Parse() - - if help { + if cfg.Help { flag.Usage() return 1 } @@ -234,35 +215,25 @@ func realMain() int { stat, _ := os.Stdin.Stat() isStdin := (stat.Mode() & os.ModeCharDevice) == 0 - skipKinds := skipKindsMap(skipKindsCSV) - if len(flag.Args()) == 1 && flag.Args()[0] == "-" { isStdin = true - } else { - for _, file := range flag.Args() { - files = append(files, file) - } } filter := func(signature resource.Signature) bool { - isSkipKind, ok := skipKinds[signature.Kind] + isSkipKind, ok := cfg.SkipKinds[signature.Kind] return ok && isSkipKind } - if len(schemaLocationsParam) == 0 { - schemaLocationsParam = append(schemaLocationsParam, "https://kubernetesjsonschema.dev") // if not specified, default behaviour is to use kubernetesjson-schema.dev as registry - } - registries := []registry.Registry{} - for _, schemaLocation := range schemaLocationsParam { + for _, schemaLocation := range cfg.SchemaLocations { if !strings.HasSuffix(schemaLocation, "json") { // If we dont specify a full templated path, we assume the paths of kubernetesjsonschema.dev schemaLocation += "/{{ .NormalizedVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json" } if strings.HasPrefix(schemaLocation, "http") { - registries = append(registries, registry.NewHTTPRegistry(schemaLocation, strict)) + registries = append(registries, registry.NewHTTPRegistry(schemaLocation, cfg.Strict)) } else { - registries = append(registries, registry.NewLocalRegistry(schemaLocation, strict)) + registries = append(registries, registry.NewLocalRegistry(schemaLocation, cfg.Strict)) } } @@ -271,12 +242,12 @@ func realMain() int { fileBatches := make(chan []string) go func() { - getFiles(files, fileBatches, validationResults) + getFiles(cfg.Files, fileBatches, validationResults) close(fileBatches) }() var o output.Output - if o, err = output.New(outputFormat, summary, isStdin, verbose); err != nil { + if o, err = output.New(cfg.OutputFormat, cfg.Summary, isStdin, cfg.Verbose); err != nil { fmt.Fprintln(os.Stderr, err) return 1 } @@ -285,14 +256,14 @@ func realMain() int { go processResults(o, validationResults, res) if isStdin { - res := ValidateStream(os.Stdin, registries, k8sVersion, c, filter, ignoreMissingSchemas) + res := ValidateStream(os.Stdin, registries, cfg.KubernetesVersion, c, filter, cfg.IgnoreMissingSchemas) for i := range res { res[i].filename = "stdin" } validationResults <- res } else { var wg sync.WaitGroup - for i := 0; i < nWorkers; i++ { + for i := 0; i < cfg.NumberOfWorkers; i++ { wg.Add(1) go func() { defer wg.Done() @@ -309,7 +280,7 @@ func realMain() int { continue } - res := ValidateStream(f, registries, k8sVersion, c, filter, ignoreMissingSchemas) + res := ValidateStream(f, registries, cfg.KubernetesVersion, c, filter, cfg.IgnoreMissingSchemas) f.Close() for i := range res { diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..8820d7c --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,83 @@ +package config + +import ( + "flag" + "fmt" + "os" + "strings" +) + +type Config struct { + Files []string + SchemaLocations []string + SkipKinds map[string]bool + OutputFormat string + KubernetesVersion string + NumberOfWorkers int + Summary bool + Strict bool + Verbose bool + IgnoreMissingSchemas bool + Help bool +} + +type arrayParam []string + +func (ap *arrayParam) String() string { + return strings.Join(*ap, " - ") +} + +func (ap *arrayParam) Set(value string) error { + *ap = append(*ap, value) + return nil +} + +func skipKinds(skipKindsCSV string) map[string]bool { + splitKinds := strings.Split(skipKindsCSV, ",") + skipKinds := map[string]bool{} + + for _, kind := range splitKinds { + if len(kind) > 0 { + skipKinds[kind] = true + } + } + + return skipKinds +} + +func FromFlags() Config { + var schemaLocationsParam arrayParam + var skipKindsCSV string + + c := Config{} + c.Files = []string{} + + flag.StringVar(&c.KubernetesVersion, "kubernetes-version", "1.18.0", "version of Kubernetes to validate against") + flag.Var(&schemaLocationsParam, "schema-location", "override schemas location search path (can be specified multiple times)") + flag.StringVar(&skipKindsCSV, "skip", "", "comma-separated list of kinds to ignore") + flag.BoolVar(&c.IgnoreMissingSchemas, "ignore-missing-schemas", false, "skip files with missing schemas instead of failing") + flag.BoolVar(&c.Summary, "summary", false, "print a summary at the end") + flag.IntVar(&c.NumberOfWorkers, "n", 4, "number of goroutines to run concurrently") + flag.BoolVar(&c.Strict, "strict", false, "disallow additional properties not in schema") + flag.StringVar(&c.OutputFormat, "output", "text", "output format - text, json") + flag.BoolVar(&c.Verbose, "verbose", false, "print results for all resources") + flag.BoolVar(&c.Help, "h", false, "show help information") + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage: %s [OPTION]... [FILE OR FOLDER]...\n", os.Args[0]) + flag.PrintDefaults() + } + + flag.Parse() + + c.SkipKinds = skipKinds(skipKindsCSV) + c.SchemaLocations = schemaLocationsParam + if len(c.SchemaLocations) == 0 { + c.SchemaLocations = append(c.SchemaLocations, "https://kubernetesjsonschema.dev") // if not specified, default behaviour is to use kubernetesjson-schema.dev as registry + } + + for _, file := range flag.Args() { + c.Files = append(c.Files, file) + } + + return c +} diff --git a/cmd/kubeconform/main_test.go b/pkg/config/config_test.go similarity index 92% rename from cmd/kubeconform/main_test.go rename to pkg/config/config_test.go index 91c0ee5..e8aef9f 100644 --- a/cmd/kubeconform/main_test.go +++ b/pkg/config/config_test.go @@ -1,4 +1,4 @@ -package main +package config import ( "reflect" @@ -33,7 +33,7 @@ func TestSkipKindMaps(t *testing.T) { }, }, } { - got := skipKindsMap(testCase.csvSkipKinds) + got := skipKinds(testCase.csvSkipKinds) if !reflect.DeepEqual(got, testCase.expect) { t.Errorf("%s - got %+v, expected %+v", testCase.name, got, testCase.expect) }