diff --git a/cmd/kubeconform/main.go b/cmd/kubeconform/validate.go similarity index 80% rename from cmd/kubeconform/main.go rename to cmd/kubeconform/validate.go index e8a93e4e..c7ff7d2e 100644 --- a/cmd/kubeconform/main.go +++ b/cmd/kubeconform/validate.go @@ -1,4 +1,4 @@ -package main +package kubeconform import ( "context" @@ -15,8 +15,6 @@ import ( "github.com/yannh/kubeconform/pkg/validator" ) -var version = "development" - func processResults(cancel context.CancelFunc, o output.Output, validationResults <-chan validator.Result, exitOnError bool) <-chan bool { success := true result := make(chan bool) @@ -28,7 +26,7 @@ func processResults(cancel context.CancelFunc, o output.Output, validationResult } if o != nil { if err := o.Write(res); err != nil { - fmt.Fprint(os.Stderr, "failed writing log\n") + log.Fatal("failed writing log: ", err) } } if !success && exitOnError { @@ -46,27 +44,19 @@ func processResults(cancel context.CancelFunc, o output.Output, validationResult return result } -func realMain() int { - cfg, out, err := config.FromFlags(os.Args[0], os.Args[1:]) +func Validate(cfg config.Config, out string) error { + if out != "" { - o := os.Stderr - errCode := 1 if cfg.Help { - o = os.Stdout - errCode = 0 + fmt.Fprintln(cfg.Stream.Output, out) + return nil } - fmt.Fprintln(o, out) - return errCode + return fmt.Errorf("config out is not empty") } if cfg.Version { - fmt.Println(version) - return 0 - } - - if err != nil { - fmt.Fprintf(os.Stderr, "failed parsing command line: %s\n", err.Error()) - return 1 + fmt.Fprintln(cfg.Stream.Output, out) + return nil } cpuProfileFile := os.Getenv("KUBECONFORM_CPUPROFILE_FILE") @@ -93,13 +83,11 @@ func realMain() int { useStdin = true } - var o output.Output - if o, err = output.New(cfg.OutputFormat, cfg.Summary, useStdin, cfg.Verbose); err != nil { - fmt.Fprintln(os.Stderr, err) - return 1 + o, err := output.New(cfg.Stream.Output, cfg.OutputFormat, cfg.Summary, useStdin, cfg.Verbose) + if err != nil { + return fmt.Errorf("failed to get output: %s", err.Error()) } - var v validator.Validator - v, err = validator.New(cfg.SchemaLocations, validator.Opts{ + v, err := validator.New(cfg.SchemaLocations, validator.Opts{ Cache: cfg.Cache, Debug: cfg.Debug, SkipTLS: cfg.SkipTLS, @@ -110,8 +98,7 @@ func realMain() int { IgnoreMissingSchemas: cfg.IgnoreMissingSchemas, }) if err != nil { - fmt.Fprintln(os.Stderr, err) - return 1 + return fmt.Errorf("failed to validate: %s", err.Error()) } validationResults := make(chan validator.Result) @@ -121,7 +108,7 @@ func realMain() int { var resourcesChan <-chan resource.Resource var errors <-chan error if useStdin { - resourcesChan, errors = resource.FromStream(ctx, "stdin", os.Stdin) + resourcesChan, errors = resource.FromStream(ctx, "stdin", cfg.Stream.Input) } else { resourcesChan, errors = resource.FromFiles(ctx, cfg.Files, cfg.IgnoreFilenamePatterns) } @@ -171,12 +158,8 @@ func realMain() int { o.Flush() if !success { - return 1 + return fmt.Errorf("failed to process results") } - return 0 -} - -func main() { - os.Exit(realMain()) + return nil } diff --git a/main.go b/main.go new file mode 100644 index 00000000..29e6a229 --- /dev/null +++ b/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "os" + + "github.com/yannh/kubeconform/cmd/kubeconform" + "github.com/yannh/kubeconform/pkg/config" +) + +var version = "development" + +func main() { + cfg, out, err := config.FromFlags(os.Args[0], os.Args[1:]) + if err != nil { + fmt.Fprintf(os.Stderr, "failed parsing command line: %s\n", err.Error()) + os.Exit(1) + } + + if err = kubeconform.Validate(cfg, out); err != nil { + fmt.Fprintf(os.Stderr, "failed validating resources: %s - %s\n", err.Error(), out) + os.Exit(1) + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go index b64a3c12..d6c4ac0c 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -4,28 +4,58 @@ import ( "bytes" "flag" "fmt" + "io" + "os" "strings" ) +type Stream struct { + Input io.Reader + Output io.Writer + Error io.Writer +} + type Config struct { - Cache string - Debug bool - ExitOnError bool - Files []string - SchemaLocations []string - SkipTLS bool - SkipKinds map[string]struct{} + Cache string `yaml:"cache" json:"cache"` + Debug bool `yaml:"debug" json:"debug"` + ExitOnError bool `yaml:"exitOnError" json:"exitOnError"` + Files []string `yaml:"files" json:"files"` + Help bool `yaml:"help" json:"help"` + IgnoreFilenamePatterns []string `yaml:"ignoreFilenamePatterns" json:"ignoreFilenamePatterns"` + IgnoreMissingSchemas bool `yaml:"ignoreMissingSchemas" json:"ignoreMissingSchemas"` + KubernetesVersion string `yaml:"kubernetesVersion" json:"kubernetesVersion"` + NumberOfWorkers int `yaml:"numberOfWorkers" json:"numberOfWorkers"` + OutputFormat string `yaml:"output" json:"output"` RejectKinds map[string]struct{} - OutputFormat string - KubernetesVersion string - NumberOfWorkers int - Summary bool - Strict bool - Verbose bool - IgnoreMissingSchemas bool - IgnoreFilenamePatterns []string - Help bool - Version bool + RejectKindsNG []string `yaml:"reject" json:"reject"` + SchemaLocations []string `yaml:"schemaLocations" json:"schemaLocations"` + SkipKinds map[string]struct{} + SkipKindsNG []string `yaml:"skip" json:"skip"` + SkipTLS bool `yaml:"insecureSkipTLSVerify" json:"insecureSkipTLSVerify"` + Strict bool `yaml:"strict" json:"strict"` + Summary bool `yaml:"summary" json:"summary"` + Verbose bool `yaml:"verbose" json:"verbose"` + Version bool `yaml:"version" json:"version"` + Stream *Stream +} + +// LoadNGConfig allows to introduce new config format/structure while keeping backward compatibility. +func (c *Config) LoadNGConfig() error { + // SkipKindsNG. + loadKinds(c.SkipKinds, c.SkipKindsNG) + + // RejectKindsNG. + loadKinds(c.RejectKinds, c.RejectKindsNG) + + return nil +} + +func loadKinds(dest map[string]struct{}, src []string) { + for _, kind := range src { + if len(kind) > 0 { + dest[kind] = struct{}{} + } + } } type arrayParam []string @@ -40,15 +70,8 @@ func (ap *arrayParam) Set(value string) error { } func splitCSV(csvStr string) map[string]struct{} { - splitValues := strings.Split(csvStr, ",") valuesMap := map[string]struct{}{} - - for _, kind := range splitValues { - if len(kind) > 0 { - valuesMap[kind] = struct{}{} - } - } - + loadKinds(valuesMap, strings.Split(csvStr, ",")) return valuesMap } @@ -62,7 +85,9 @@ func FromFlags(progName string, args []string) (Config, string, error) { c := Config{} c.Files = []string{} + c.Stream = &Stream{os.Stdin, os.Stdout, os.Stderr} + flags.SetOutput(c.Stream.Output) flags.StringVar(&c.KubernetesVersion, "kubernetes-version", "master", "version of Kubernetes to validate against, e.g.: 1.18.0") flags.Var(&schemaLocationsParam, "schema-location", "override schemas location search path (can be specified multiple times)") flags.StringVar(&skipKindsCSV, "skip", "", "comma-separated list of kinds or GVKs to ignore") diff --git a/pkg/output/output.go b/pkg/output/output.go index 76128730..488aadd3 100644 --- a/pkg/output/output.go +++ b/pkg/output/output.go @@ -2,7 +2,7 @@ package output import ( "fmt" - "os" + "io" "github.com/yannh/kubeconform/pkg/validator" ) @@ -12,9 +12,7 @@ type Output interface { Flush() error } -func New(outputFormat string, printSummary, isStdin, verbose bool) (Output, error) { - w := os.Stdout - +func New(w io.Writer, outputFormat string, printSummary, isStdin, verbose bool) (Output, error) { switch { case outputFormat == "json": return jsonOutput(w, printSummary, isStdin, verbose), nil