Skip to content

Commit

Permalink
ref: Add OutputFormat type to enforce flag validation at parse time
Browse files Browse the repository at this point in the history
  • Loading branch information
harry97uk committed Nov 14, 2024
1 parent 1afe0af commit d1d5534
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 24 deletions.
2 changes: 1 addition & 1 deletion cmd/kubent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func main() {
log.Fatal().Err(err).Msgf("Failed to create output file")
}

err = outputResults(results, config.Output, options)
err = outputResults(results, config.Output.String(), options)
if err != nil {
log.Fatal().Err(err).Msgf("Failed to output results")
}
Expand Down
26 changes: 3 additions & 23 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,6 @@ import (
flag "github.com/spf13/pflag"
)

const (
JSON = "json"
TEXT = "text"
CSV = "csv"
)

type Config struct {
AdditionalKinds []string
AdditionalAnnotations []string
Expand All @@ -32,7 +26,7 @@ type Config struct {
Helm3 bool
Kubeconfig string
LogLevel ZeroLogLevel
Output string
Output OutputFormat
OutputFile string
TargetVersion *judge.Version
KubentVersion bool
Expand All @@ -43,6 +37,7 @@ func NewFromFlags() (*Config, error) {
config := Config{
LogLevel: ZeroLogLevel(zerolog.InfoLevel),
TargetVersion: &judge.Version{},
Output: TEXT,
}

flag.StringSliceVarP(&config.AdditionalKinds, "additional-kind", "a", []string{}, "additional kinds of resources to report in Kind.version.group.com format")
Expand All @@ -54,17 +49,13 @@ func NewFromFlags() (*Config, error) {
flag.BoolVar(&config.Helm3, "helm3", true, "enable Helm v3 collector")
flag.StringSliceVarP(&config.Filenames, "filename", "f", []string{}, "manifests to check, use - for stdin")
flag.StringVarP(&config.Kubeconfig, "kubeconfig", "k", os.Getenv(clientcmd.RecommendedConfigPathEnvVar), "path to the kubeconfig file")
flag.StringVarP(&config.Output, "output", "o", TEXT, "output format - [text|json|csv]")
flag.VarP(&config.Output, "output", "o", "output format - [text|json|csv]")
flag.StringVarP(&config.OutputFile, "output-file", "O", "-", "output file, use - for stdout")
flag.VarP(&config.LogLevel, "log-level", "l", "set log level (trace, debug, info, warn, error, fatal, panic, disabled)")
flag.VarP(config.TargetVersion, "target-version", "t", "target K8s version in SemVer format (autodetected by default)")
flag.BoolVar(&config.ShowLabels, "labels", false, "print resource labels")
flag.Parse()

if !isValidOutputFormat(config.Output) {
return nil, fmt.Errorf("failed to validate argument output: %s", config.Output)
}

if err := validateOutputFile(config.OutputFile); err != nil {
return nil, fmt.Errorf("failed to validate argument output-file: %w", err)
}
Expand All @@ -83,17 +74,6 @@ func NewFromFlags() (*Config, error) {
return &config, nil
}

// Previously this was handled by a printer.go ParsePrinter function
// but we need to avoid cycle imports in order to inject the additional flags
func isValidOutputFormat(format string) bool {
switch format {
case JSON, TEXT, CSV:
return true
default:
return false
}
}

// validateAdditionalResources check that all resources are provided in full form
// resource.version.group.com. E.g. managedcertificate.v1beta1.networking.gke.io
func validateAdditionalResources(resources []string) error {
Expand Down
37 changes: 37 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ func TestNewFromFlags(t *testing.T) {
if !config.Cluster && config.Output != "text" {
t.Errorf("Config not parsed correctly")
}

err = pflag.CommandLine.Parse([]string{"--output", "json"})
if err != nil {
t.Errorf("Flag parsing failed: %v", err)
}
if config.Output != "json" {
t.Errorf("Expected output format to be 'json', got '%s'", config.Output)
}
}

func TestValidateAdditionalResources(t *testing.T) {
Expand Down Expand Up @@ -183,3 +191,32 @@ func Test_validateOutputFile(t *testing.T) {
})
}
}

func TestValidOutputFormatValues(t *testing.T) {
validFormats := []string{"json", "text", "csv"}
for _, format := range validFormats {
t.Run("Valid "+format, func(t *testing.T) {
var output OutputFormat
err := output.Set(format)
if err != nil {
t.Errorf("Expected no error for valid format '%s', got %v", format, err)
}
if output.String() != format {
t.Errorf("Expected output format to be '%s', got '%s'", format, output.String())
}
})
}
}

func TestInvalidOutputFormatValues(t *testing.T) {
invalidFormats := []string{"xml", "yaml", "pdf"}
for _, format := range invalidFormats {
t.Run("Invalid "+format, func(t *testing.T) {
var output OutputFormat
err := output.Set(format)
if err == nil {
t.Errorf("Expected an error for invalid format '%s', but got none", format)
}
})
}
}
30 changes: 30 additions & 0 deletions pkg/config/output_format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package config

import "fmt"

const (
JSON = "json"
TEXT = "text"
CSV = "csv"
)

// implements pflag.Value to enforce strict string matching for the output format during flag parsing
type OutputFormat string

func (o *OutputFormat) String() string {
return string(*o)
}

func (o *OutputFormat) Set(value string) error {
switch value {
case JSON, TEXT, CSV:
*o = OutputFormat(value)
return nil
default:
return fmt.Errorf("invalid output format: %s (must be one of: json, text, csv)", value)
}
}

func (o *OutputFormat) Type() string {
return "OutputFormat"
}

0 comments on commit d1d5534

Please sign in to comment.