Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref: Add OutputFormat type to enforce flag validation at parse time #659

Merged
merged 2 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
}
Loading