From 7884cfbf16fb136a2c5e6f9a490e2c58c70860c3 Mon Sep 17 00:00:00 2001 From: James Strachan Date: Mon, 10 May 2021 12:02:59 +0100 Subject: [PATCH 1/3] fix: use different level for skipped schemas lets not use the same warning level for skipped schemas as we do for actual validation errors so that its easy to tell the output difference between actual schema validation errors versus CRD kinds that are skipped --- kubeval/output.go | 2 +- log/log.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/kubeval/output.go b/kubeval/output.go index f652cb1..12e4934 100644 --- a/kubeval/output.go +++ b/kubeval/output.go @@ -66,7 +66,7 @@ func (s *STDOutputManager) Put(result ValidationResult) error { } else if result.Kind == "" { kLog.Success(result.FileName, "contains an empty YAML document") } else if !result.ValidatedAgainstSchema { - kLog.Warn(result.FileName, "containing a", result.Kind, fmt.Sprintf("(%s)", result.QualifiedName()), "was not validated against a schema") + kLog.Info(result.FileName, "containing a", result.Kind, fmt.Sprintf("(%s)", result.QualifiedName()), "was not validated against a schema") } else { kLog.Success(result.FileName, "contains a valid", result.Kind, fmt.Sprintf("(%s)", result.QualifiedName())) } diff --git a/log/log.go b/log/log.go index 680cda6..291fff8 100644 --- a/log/log.go +++ b/log/log.go @@ -13,6 +13,11 @@ func Success(message ...string) { fmt.Printf("%s - %v\n", green("PASS"), strings.Join(message, " ")) } +func Info(message ...string) { + blue := color.New(color.FgBlue).SprintFunc() + fmt.Printf("%s - %v\n", blue("PASS"), strings.Join(message, " ")) +} + func Warn(message ...string) { yellow := color.New(color.FgYellow).SprintFunc() fmt.Printf("%s - %v\n", yellow("WARN"), strings.Join(message, " ")) From fd90be1e36635a004a39ac52a2a0aecc6ea2033f Mon Sep 17 00:00:00 2001 From: James Strachan Date: Mon, 10 May 2021 17:24:15 +0100 Subject: [PATCH 2/3] fix: add logging levels so we can hide PASS/INFO level logs to just see the warnings only if you are validating lots of files --- kubeval/config.go | 4 ++++ kubeval/output.go | 35 +++++++++++++++++++++++++++-------- main.go | 12 ++++++------ 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/kubeval/config.go b/kubeval/config.go index a9240a8..0f4846a 100644 --- a/kubeval/config.go +++ b/kubeval/config.go @@ -63,6 +63,9 @@ type Config struct { // reporting results to the user. OutputFormat string + // LoggingLevel configures the logging level for whether we should log output when using the human layout + LoggingLevel string + // Quiet indicates whether non-results output should be emitted to the applications // log. Quiet bool @@ -95,6 +98,7 @@ func AddKubevalFlags(cmd *cobra.Command, config *Config) *cobra.Command { cmd.Flags().StringSliceVar(&config.AdditionalSchemaLocations, "additional-schema-locations", []string{}, "Comma-seperated list of secondary base URLs used to download schemas") cmd.Flags().StringVarP(&config.KubernetesVersion, "kubernetes-version", "v", "master", "Version of Kubernetes to validate against") cmd.Flags().StringVarP(&config.OutputFormat, "output", "o", "", fmt.Sprintf("The format of the output of this script. Options are: %v", validOutputs())) + cmd.Flags().StringVarP(&config.LoggingLevel, "log-level", "", "", fmt.Sprintf("The logging level for whether to log all levels or just warnings. Options are: %v", validLevels())) cmd.Flags().BoolVar(&config.Quiet, "quiet", false, "Silences any output aside from the direct results") cmd.Flags().BoolVar(&config.InsecureSkipTLSVerify, "insecure-skip-tls-verify", false, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure") diff --git a/kubeval/output.go b/kubeval/output.go index 12e4934..7277fee 100644 --- a/kubeval/output.go +++ b/kubeval/output.go @@ -26,6 +26,10 @@ const ( outputSTD = "stdout" outputJSON = "json" outputTAP = "tap" + + levelINFO = "info" + levelSUCCESS = "success" + levelWARN = "warn" ) func validOutputs() []string { @@ -36,26 +40,35 @@ func validOutputs() []string { } } -func GetOutputManager(outFmt string) outputManager { +func validLevels() []string { + return []string{ + levelINFO, + levelSUCCESS, + levelWARN, + } +} + +func GetOutputManager(outFmt, loggingLevel string) outputManager { switch outFmt { case outputSTD: - return newSTDOutputManager() + return newSTDOutputManager(loggingLevel) case outputJSON: return newDefaultJSONOutputManager() case outputTAP: return newDefaultTAPOutputManager() default: - return newSTDOutputManager() + return newSTDOutputManager(loggingLevel) } } // STDOutputManager reports `kubeval` results to stdout. type STDOutputManager struct { + loggingLevel string } // newSTDOutputManager instantiates a new instance of STDOutputManager. -func newSTDOutputManager() *STDOutputManager { - return &STDOutputManager{} +func newSTDOutputManager(loggingLevel string) *STDOutputManager { + return &STDOutputManager{loggingLevel: loggingLevel} } func (s *STDOutputManager) Put(result ValidationResult) error { @@ -64,11 +77,17 @@ func (s *STDOutputManager) Put(result ValidationResult) error { kLog.Warn(result.FileName, "contains an invalid", result.Kind, fmt.Sprintf("(%s)", result.QualifiedName()), "-", desc.String()) } } else if result.Kind == "" { - kLog.Success(result.FileName, "contains an empty YAML document") + if s.loggingLevel != levelWARN { + kLog.Success(result.FileName, "contains an empty YAML document") + } } else if !result.ValidatedAgainstSchema { - kLog.Info(result.FileName, "containing a", result.Kind, fmt.Sprintf("(%s)", result.QualifiedName()), "was not validated against a schema") + if s.loggingLevel != levelWARN && s.loggingLevel != levelSUCCESS { + kLog.Info(result.FileName, "containing a", result.Kind, fmt.Sprintf("(%s)", result.QualifiedName()), "was not validated against a schema") + } } else { - kLog.Success(result.FileName, "contains a valid", result.Kind, fmt.Sprintf("(%s)", result.QualifiedName())) + if s.loggingLevel != levelWARN { + kLog.Success(result.FileName, "contains a valid", result.Kind, fmt.Sprintf("(%s)", result.QualifiedName())) + } } return nil diff --git a/main.go b/main.go index cd24086..2a94d6a 100644 --- a/main.go +++ b/main.go @@ -24,10 +24,10 @@ import ( ) var ( - version = "dev" - commit = "none" - date = "unknown" - directories = []string{} + version = "dev" + commit = "none" + date = "unknown" + directories = []string{} ignoredPathPatterns = []string{} // forceColor tells kubeval to use colored output even if @@ -59,7 +59,7 @@ var RootCmd = &cobra.Command{ success := true windowsStdinIssue := false - outputManager := kubeval.GetOutputManager(config.OutputFormat) + outputManager := kubeval.GetOutputManager(config.OutputFormat, config.LoggingLevel) stat, err := os.Stdin.Stat() if err != nil { @@ -242,7 +242,7 @@ func init() { RootCmd.Flags().StringSliceVarP(&directories, "directories", "d", []string{}, "A comma-separated list of directories to recursively search for YAML documents") RootCmd.Flags().StringSliceVarP(&ignoredPathPatterns, "ignored-path-patterns", "i", []string{}, "A comma-separated list of regular expressions specifying paths to ignore") RootCmd.Flags().StringSliceVarP(&ignoredPathPatterns, "ignored-filename-patterns", "", []string{}, "An alias for ignored-path-patterns") - + viper.SetEnvPrefix("KUBEVAL") viper.AutomaticEnv() viper.BindPFlag("schema_location", RootCmd.Flags().Lookup("schema-location")) From f187db012024b4028459901002bc3905655c9de9 Mon Sep 17 00:00:00 2001 From: James Strachan Date: Tue, 11 May 2021 06:25:54 +0100 Subject: [PATCH 3/3] fix: allow helm source comments to be disabled so that the real file name is used in error reporting --- kubeval/config.go | 5 +++++ kubeval/kubeval.go | 22 ++++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/kubeval/config.go b/kubeval/config.go index 0f4846a..b7635dc 100644 --- a/kubeval/config.go +++ b/kubeval/config.go @@ -70,6 +70,10 @@ type Config struct { // log. Quiet bool + // IgnoreHelmSource if enabled we don't replace the file name used in errors reported with + // the contents of the `# Source` comment file name + IgnoreHelmSource bool + // InsecureSkipTLSVerify controls whether to skip TLS certificate validation // when retrieving schema content over HTTPS InsecureSkipTLSVerify bool @@ -100,6 +104,7 @@ func AddKubevalFlags(cmd *cobra.Command, config *Config) *cobra.Command { cmd.Flags().StringVarP(&config.OutputFormat, "output", "o", "", fmt.Sprintf("The format of the output of this script. Options are: %v", validOutputs())) cmd.Flags().StringVarP(&config.LoggingLevel, "log-level", "", "", fmt.Sprintf("The logging level for whether to log all levels or just warnings. Options are: %v", validLevels())) cmd.Flags().BoolVar(&config.Quiet, "quiet", false, "Silences any output aside from the direct results") + cmd.Flags().BoolVar(&config.IgnoreHelmSource, "ignore-helm-source", false, "If enabled we preserve the file name for errors rather than replace it with the contents of the '# Source' comment in the generated helm resource") cmd.Flags().BoolVar(&config.InsecureSkipTLSVerify, "insecure-skip-tls-verify", false, "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure") return cmd diff --git a/kubeval/kubeval.go b/kubeval/kubeval.go index 10e0eef..bcb3f19 100644 --- a/kubeval/kubeval.go +++ b/kubeval/kubeval.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "os" + "path/filepath" "regexp" "strings" @@ -322,10 +323,27 @@ func ValidateWithCache(input []byte, schemaCache map[string]*gojsonschema.Schema seenResourcesSet := make(map[[4]string]bool) // set of [API version, kind, namespace, name] + if config.IgnoreHelmSource { + // lets try use a relative file name to the current dir + dir, err := os.Getwd() + if err != nil { + return nil, err + } + rel, err := filepath.Rel(dir, config.FileName) + if err == nil && rel != "" { + names := strings.Split(rel, string(os.PathSeparator)) + if names[0] != ".." { + config.FileName = rel + } + } + } + for _, element := range bits { if len(element) > 0 { - if found := helmSourcePattern.FindStringSubmatch(string(element)); found != nil { - config.FileName = found[1] + if !config.IgnoreHelmSource { + if found := helmSourcePattern.FindStringSubmatch(string(element)); found != nil { + config.FileName = found[1] + } } result, body, err := validateResource(element, schemaCache, config)