From f72aae0aeb69cb9efb06c6139502563f03c62d94 Mon Sep 17 00:00:00 2001 From: Felipe Amaral Date: Wed, 9 Oct 2024 20:55:06 +0100 Subject: [PATCH] feat: webapp with secret --- README.md | 2 +- cmd/kubent/main.go | 11 ++- cmd/kubent/main_test.go | 9 +- fixtures/expected-json-output.json | 5 +- pkg/collector/file_test.go | 2 +- pkg/config/config.go | 37 ++++++-- pkg/config/config_test.go | 28 +++--- pkg/context/context-keys.go | 5 ++ pkg/judge/judge.go | 1 + pkg/judge/rego.go | 7 ++ pkg/judge/rego_test.go | 2 +- pkg/printer/csv.go | 28 ++++-- pkg/printer/csv_test.go | 11 ++- pkg/printer/fixtures_test.go | 19 +++++ pkg/printer/json.go | 3 +- pkg/printer/json_test.go | 8 +- pkg/printer/printer.go | 3 +- pkg/printer/printer_helper.go | 27 ++++++ pkg/printer/printer_helper_test.go | 118 ++++++++++++++++++++++++++ pkg/printer/text.go | 21 ++++- pkg/printer/text_test.go | 10 ++- pkg/rules/rego/deprecated-1-16.rego | 1 + pkg/rules/rego/deprecated-1-22.rego | 1 + pkg/rules/rego/deprecated-1-25.rego | 1 + pkg/rules/rego/deprecated-1-26.rego | 1 + pkg/rules/rego/deprecated-1-27.rego | 1 + pkg/rules/rego/deprecated-1-29.rego | 1 + pkg/rules/rego/deprecated-1-32.rego | 1 + pkg/rules/rego/deprecated-future.rego | 1 + pkg/rules/rules_test.go | 2 +- test/rules_custom_test.go | 2 +- 31 files changed, 317 insertions(+), 52 deletions(-) create mode 100644 pkg/context/context-keys.go create mode 100644 pkg/printer/fixtures_test.go create mode 100644 pkg/printer/printer_helper.go create mode 100644 pkg/printer/printer_helper_test.go diff --git a/README.md b/README.md index d3a721a3..b0a78f7d 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,7 @@ Otherwise there's `Makefile` ```sh $ make make -all Cean, build and pack +all Clean, build and pack help Prints list of tasks build Build binary generate Go generate diff --git a/cmd/kubent/main.go b/cmd/kubent/main.go index fc54bc0c..0ea68824 100644 --- a/cmd/kubent/main.go +++ b/cmd/kubent/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "fmt" "io" @@ -96,11 +97,13 @@ func getServerVersion(cv *judge.Version, collectors []collector.Collector) (*jud } func main() { + ctx := context.Background() exitCode := EXIT_CODE_FAIL_GENERIC configureGlobalLogging() - config, err := config.NewFromFlags() + config, ctx, err := config.NewFromFlags(ctx) + if err != nil { log.Fatal().Err(err).Msg("failed to parse config flags") } @@ -156,7 +159,7 @@ func main() { log.Fatal().Err(err).Str("name", "Rego").Msg("Failed to filter results") } - err = outputResults(results, config.Output, config.OutputFile) + err = outputResults(results, config.Output, config.OutputFile, ctx) if err != nil { log.Fatal().Err(err).Msgf("Failed to output results") } @@ -180,14 +183,14 @@ func configureGlobalLogging() { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) } -func outputResults(results []judge.Result, outputType string, outputFile string) error { +func outputResults(results []judge.Result, outputType string, outputFile string, ctx context.Context) error { printer, err := printer.NewPrinter(outputType, outputFile) if err != nil { return fmt.Errorf("failed to create printer: %v", err) } defer printer.Close() - err = printer.Print(results) + err = printer.Print(results, ctx) if err != nil { return fmt.Errorf("failed to print results: %v", err) } diff --git a/cmd/kubent/main_test.go b/cmd/kubent/main_test.go index fe81a8de..9b59ee60 100644 --- a/cmd/kubent/main_test.go +++ b/cmd/kubent/main_test.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "context" "encoding/base64" "encoding/json" "errors" @@ -13,6 +14,7 @@ import ( "github.com/doitintl/kube-no-trouble/pkg/collector" "github.com/doitintl/kube-no-trouble/pkg/config" + ctxKey "github.com/doitintl/kube-no-trouble/pkg/context" "github.com/doitintl/kube-no-trouble/pkg/judge" "github.com/rs/zerolog" @@ -120,7 +122,7 @@ func TestMainExitCodes(t *testing.T) { }{ {"success", []string{clusterFlagDisabled, helm3FlagDisabled}, 0, "", "", false}, {"errorBadFlag", []string{"-c=not-boolean"}, 2, "", "", false}, - {"successFound", []string{"-o=json", clusterFlagDisabled, helm3FlagDisabled, "-f=" + filepath.Join(FIXTURES_DIR, "deployment-v1beta1.yaml")}, 0, string(expectedJsonOutput), "", false}, + {"successFound", []string{"--labels=true", "-o=json", clusterFlagDisabled, helm3FlagDisabled, "-f=" + filepath.Join(FIXTURES_DIR, "deployment-v1beta1.yaml")}, 0, string(expectedJsonOutput), "", false}, {"exitErrorFlagNone", []string{clusterFlagDisabled, helm3FlagDisabled, "-e"}, 0, "", "", false}, {"exitErrorFlagFound", []string{clusterFlagDisabled, helm3FlagDisabled, "-e", "-f=" + filepath.Join(FIXTURES_DIR, "deployment-v1beta1.yaml")}, 200, "", "", false}, {"version short flag set", []string{"-v"}, 0, "", "", false}, @@ -286,9 +288,12 @@ func Test_outputResults(t *testing.T) { {"bad-new-printer-file", args{testResults, "text", "/unlikely/to/exist/dir"}, true}, } + labelsFlag := false + ctx := context.WithValue(context.Background(), ctxKey.LABELS_CTX_KEY, &labelsFlag) + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := outputResults(tt.args.results, tt.args.outputType, tt.args.outputFile); (err != nil) != tt.wantErr { + if err := outputResults(tt.args.results, tt.args.outputType, tt.args.outputFile, ctx); (err != nil) != tt.wantErr { t.Errorf("unexpected error - got: %v, wantErr: %v", err, tt.wantErr) } }) diff --git a/fixtures/expected-json-output.json b/fixtures/expected-json-output.json index ffbfbae7..48e5f386 100644 --- a/fixtures/expected-json-output.json +++ b/fixtures/expected-json-output.json @@ -6,6 +6,9 @@ "ApiVersion": "apps/v1beta1", "RuleSet": "Deprecated APIs removed in 1.16", "ReplaceWith": "apps/v1", - "Since": "1.9.0" + "Since": "1.9.0", + "Labels": { + "app": "nginx" + } } ] diff --git a/pkg/collector/file_test.go b/pkg/collector/file_test.go index bec0d265..310b218f 100644 --- a/pkg/collector/file_test.go +++ b/pkg/collector/file_test.go @@ -52,7 +52,7 @@ func TestFileCollectorGet(t *testing.T) { t.Errorf("Expected to get %d, got %d", len(tc.expected), len(manifests)) } - for i, _ := range manifests { + for i := range manifests { if manifests[i]["kind"] != tc.expected[i] { t.Errorf("Expected to get %s, instead got: %s", tc.expected[i], manifests[i]["kind"]) } diff --git a/pkg/config/config.go b/pkg/config/config.go index 1608e96c..b1cff056 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,6 +1,7 @@ package config import ( + "context" "errors" "fmt" "io/fs" @@ -9,14 +10,20 @@ import ( "strings" "unicode" + ctxKey "github.com/doitintl/kube-no-trouble/pkg/context" "github.com/doitintl/kube-no-trouble/pkg/judge" - "github.com/doitintl/kube-no-trouble/pkg/printer" "k8s.io/client-go/tools/clientcmd" "github.com/rs/zerolog" flag "github.com/spf13/pflag" ) +const ( + JSON = "json" + TEXT = "text" + CSV = "csv" +) + type Config struct { AdditionalKinds []string AdditionalAnnotations []string @@ -33,12 +40,14 @@ type Config struct { KubentVersion bool } -func NewFromFlags() (*Config, error) { +func NewFromFlags(ctx context.Context) (*Config, context.Context, error) { config := Config{ LogLevel: ZeroLogLevel(zerolog.InfoLevel), TargetVersion: &judge.Version{}, } + var labels bool + flag.StringSliceVarP(&config.AdditionalKinds, "additional-kind", "a", []string{}, "additional kinds of resources to report in Kind.version.group.com format") flag.StringSliceVarP(&config.AdditionalAnnotations, "additional-annotation", "A", []string{}, "additional annotations that should be checked to determine the last applied config") flag.BoolVarP(&config.Cluster, "cluster", "c", true, "enable Cluster collector") @@ -52,19 +61,22 @@ func NewFromFlags() (*Config, error) { 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(&labels, "labels", false, "print resource labels") flag.Parse() - if _, err := printer.ParsePrinter(config.Output); err != nil { - return nil, fmt.Errorf("failed to validate argument output: %w", err) + newContext := context.WithValue(ctx, ctxKey.LABELS_CTX_KEY, &labels) + + if !isValidOutputFormat(config.Output) { + return nil, 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) + return nil, nil, fmt.Errorf("failed to validate argument output-file: %w", err) } if err := validateAdditionalResources(config.AdditionalKinds); err != nil { - return nil, fmt.Errorf("failed to validate arguments: %w", err) + return nil, nil, fmt.Errorf("failed to validate arguments: %w", err) } // This is a little ugly, but I think preferred to implementing @@ -74,7 +86,18 @@ func NewFromFlags() (*Config, error) { config.TargetVersion = nil } - return &config, nil + return &config, newContext, nil +} + +// Previuosly 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 diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 654bac03..2a4b1cba 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -1,16 +1,19 @@ package config import ( - goversion "github.com/hashicorp/go-version" + "context" "os" "testing" + goversion "github.com/hashicorp/go-version" + "github.com/spf13/pflag" ) func TestValidLogLevelFromFlags(t *testing.T) { oldArgs := os.Args[1] defer func() { os.Args[1] = oldArgs }() + ctx := context.Background() var validLevels = []string{"trace", "debug", "info", "warn", "error", "fatal", "panic", "", "disabled"} for i, level := range validLevels { @@ -18,7 +21,8 @@ func TestValidLogLevelFromFlags(t *testing.T) { pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) os.Args[1] = "--log-level=" + level - config, err := NewFromFlags() + + config, _, err := NewFromFlags(ctx) if err != nil { t.Errorf("Flags parsing failed %s", err) @@ -44,8 +48,9 @@ func TestInvalidLogLevelFromFlags(t *testing.T) { func TestNewFromFlags(t *testing.T) { // reset for testing pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) + ctx := context.Background() - config, err := NewFromFlags() + config, _, err := NewFromFlags(ctx) if err != nil { t.Errorf("Flags parsing failed %s", err) @@ -72,9 +77,9 @@ func TestValidateAdditionalResources(t *testing.T) { func TestValidateAdditionalResourcesFail(t *testing.T) { testCases := [][]string{ - []string{"abcdef"}, - []string{""}, - []string{"test.v1.com"}, + {"abcdef"}, + {""}, + {"test.v1.com"}, } for _, tc := range testCases { @@ -90,6 +95,7 @@ func TestTargetVersion(t *testing.T) { validVersions := []string{ "1.16", "1.16.3", "1.2.3", } + ctx := context.Background() oldArgs := os.Args[1] defer func() { os.Args[1] = oldArgs }() @@ -99,7 +105,7 @@ func TestTargetVersion(t *testing.T) { pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) os.Args[1] = "--target-version=" + v - config, err := NewFromFlags() + config, _, err := NewFromFlags(ctx) if err != nil { t.Errorf("Flags parsing failed %s", err) @@ -120,7 +126,7 @@ func TestTargetVersionInvalid(t *testing.T) { invalidVersions := []string{ "1.blah", "nope", } - + ctx := context.Background() oldArgs := os.Args[1] defer func() { os.Args[1] = oldArgs }() @@ -129,7 +135,7 @@ func TestTargetVersionInvalid(t *testing.T) { pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ContinueOnError) os.Args[1] = "--target-version=" + v - config, _ := NewFromFlags() + config, _, _ := NewFromFlags(ctx) if config.TargetVersion != nil { t.Errorf("expected --target-version flag parsing to fail for: %s", v) @@ -141,7 +147,7 @@ func TestContext(t *testing.T) { validContexts := []string{ "my-context", } - + ctx := context.Background() oldArgs := os.Args[1] defer func() { os.Args[1] = oldArgs }() @@ -150,7 +156,7 @@ func TestContext(t *testing.T) { pflag.CommandLine = pflag.NewFlagSet(os.Args[0], pflag.ExitOnError) os.Args[1] = "--context=" + context - config, err := NewFromFlags() + config, _, err := NewFromFlags(ctx) if err != nil { t.Errorf("Flags parsing failed %s", err) diff --git a/pkg/context/context-keys.go b/pkg/context/context-keys.go new file mode 100644 index 00000000..b6eef230 --- /dev/null +++ b/pkg/context/context-keys.go @@ -0,0 +1,5 @@ +package context + +type ctxKey string + +const LABELS_CTX_KEY ctxKey = "labels" diff --git a/pkg/judge/judge.go b/pkg/judge/judge.go index 6cdbe035..8f018c47 100644 --- a/pkg/judge/judge.go +++ b/pkg/judge/judge.go @@ -8,6 +8,7 @@ type Result struct { RuleSet string ReplaceWith string Since *Version + Labels map[string]interface{} } type Judge interface { diff --git a/pkg/judge/rego.go b/pkg/judge/rego.go index 7baa52eb..0edb2d92 100644 --- a/pkg/judge/rego.go +++ b/pkg/judge/rego.go @@ -63,6 +63,12 @@ func (j *RegoJudge) Eval(input []map[string]interface{}) ([]Result, error) { m["Namespace"] = "" } + var labels map[string]interface{} + if v, ok := m["Labels"].(map[string]interface{}); ok { + labels = v + } else { + labels = make(map[string]interface{}) + } results = append(results, Result{ Name: m["Name"].(string), Namespace: m["Namespace"].(string), @@ -71,6 +77,7 @@ func (j *RegoJudge) Eval(input []map[string]interface{}) ([]Result, error) { ReplaceWith: m["ReplaceWith"].(string), RuleSet: m["RuleSet"].(string), Since: since, + Labels: labels, }) } } diff --git a/pkg/judge/rego_test.go b/pkg/judge/rego_test.go index bc29b70d..0dd58982 100644 --- a/pkg/judge/rego_test.go +++ b/pkg/judge/rego_test.go @@ -90,7 +90,7 @@ func TestEvalRules(t *testing.T) { t.Errorf("expected %d findings, instead got: %d", len(tc.expected), len(results)) } - for i, _ := range results { + for i := range results { if results[i].Kind != tc.expected[i] { t.Errorf("expected to get %s finding, instead got: %s", tc.expected[i], results[i].Kind) } diff --git a/pkg/printer/csv.go b/pkg/printer/csv.go index 3ea7d334..45f811eb 100644 --- a/pkg/printer/csv.go +++ b/pkg/printer/csv.go @@ -1,6 +1,7 @@ package printer import ( + "context" "encoding/csv" "fmt" "sort" @@ -29,7 +30,7 @@ func (c *csvPrinter) Close() error { } // Print will print results in CSV format -func (c *csvPrinter) Print(results []judge.Result) error { +func (c *csvPrinter) Print(results []judge.Result, ctx context.Context) error { sort.Slice(results, func(i, j int) bool { return results[i].Name < results[j].Name @@ -46,7 +47,7 @@ func (c *csvPrinter) Print(results []judge.Result) error { w := csv.NewWriter(c.commonPrinter.outputFile) - w.Write([]string{ + fields := []string{ "api_version", "kind", "namespace", @@ -54,10 +55,21 @@ func (c *csvPrinter) Print(results []judge.Result) error { "replace_with", "since", "rule_set", - }) + } + + labels, err := getLabels(ctx) + if err != nil { + return fmt.Errorf("failed to get labels: %w", err) + } + + if labels != nil && *labels { + fields = append(fields, "labels") + } + + w.Write(fields) for _, r := range results { - w.Write([]string{ + row := []string{ r.ApiVersion, r.Kind, r.Namespace, @@ -65,7 +77,13 @@ func (c *csvPrinter) Print(results []judge.Result) error { r.ReplaceWith, r.Since.String(), r.RuleSet, - }) + } + + if labels != nil && *labels { + row = append(row, mapToCommaSeparatedString(r.Labels)) + } + + w.Write(row) } w.Flush() diff --git a/pkg/printer/csv_test.go b/pkg/printer/csv_test.go index c0552bc4..38f3921f 100644 --- a/pkg/printer/csv_test.go +++ b/pkg/printer/csv_test.go @@ -1,11 +1,12 @@ package printer import ( + "context" "io/ioutil" "os" "testing" - "github.com/doitintl/kube-no-trouble/pkg/judge" + ctxKey "github.com/doitintl/kube-no-trouble/pkg/context" ) func TestNewCSVPrinter(t *testing.T) { @@ -50,10 +51,12 @@ func TestCSVPrinterPrint(t *testing.T) { commonPrinter: &commonPrinter{tmpFile}, } - version, _ := judge.NewVersion("1.2.3") - results := []judge.Result{{"Name", "Namespace", "Kind", "1.2.3", "Test", "4.5.6", version}} + labelsFlag := false + ctx := context.WithValue(context.Background(), ctxKey.LABELS_CTX_KEY, &labelsFlag) - if err := tp.Print(results); err != nil { + results := getTestResult(map[string]interface{}{"key2": "value2"}) + + if err := tp.Print(results, ctx); err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/pkg/printer/fixtures_test.go b/pkg/printer/fixtures_test.go new file mode 100644 index 00000000..00fc1622 --- /dev/null +++ b/pkg/printer/fixtures_test.go @@ -0,0 +1,19 @@ +package printer + +import "github.com/doitintl/kube-no-trouble/pkg/judge" + +func getTestResult(labels map[string]interface{}) []judge.Result { + version, _ := judge.NewVersion("1.2.3") + + res := []judge.Result{{ + Name: "Name", + Namespace: "Namespace", + Kind: "Kind", + ApiVersion: "1.2.3", + RuleSet: "Test", + ReplaceWith: "4.5.6", + Since: version, + Labels: labels, + }} + return res +} diff --git a/pkg/printer/json.go b/pkg/printer/json.go index e51756d7..653ad15a 100644 --- a/pkg/printer/json.go +++ b/pkg/printer/json.go @@ -2,6 +2,7 @@ package printer import ( "bufio" + "context" "encoding/json" "fmt" @@ -29,7 +30,7 @@ func (c *jsonPrinter) Close() error { } // Print will print results in text format -func (c *jsonPrinter) Print(results []judge.Result) error { +func (c *jsonPrinter) Print(results []judge.Result, _ context.Context) error { writer := bufio.NewWriter(c.commonPrinter.outputFile) defer writer.Flush() diff --git a/pkg/printer/json_test.go b/pkg/printer/json_test.go index 9a2f1a58..733abe76 100644 --- a/pkg/printer/json_test.go +++ b/pkg/printer/json_test.go @@ -1,6 +1,7 @@ package printer import ( + "context" "encoding/json" "io/ioutil" "os" @@ -40,7 +41,6 @@ func Test_newJSONPrinter(t *testing.T) { }) } } - func Test_jsonPrinter_Print(t *testing.T) { tmpFile, err := ioutil.TempFile(os.TempDir(), tempFilePrefix) if err != nil { @@ -52,10 +52,10 @@ func Test_jsonPrinter_Print(t *testing.T) { commonPrinter: &commonPrinter{tmpFile}, } - version, _ := judge.NewVersion("1.2.3") - results := []judge.Result{{"Name", "Namespace", "Kind", "1.2.3", "Test", "4.5.6", version}} + results := getTestResult(map[string]interface{}{"key2": "value2"}) - if err := c.Print(results); err != nil { + ctx := context.Background() + if err := c.Print(results, ctx); err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go index 31ab2e58..880f464a 100644 --- a/pkg/printer/printer.go +++ b/pkg/printer/printer.go @@ -1,6 +1,7 @@ package printer import ( + "context" "fmt" "os" @@ -14,7 +15,7 @@ var printers = map[string]func(string) (Printer, error){ } type Printer interface { - Print([]judge.Result) error + Print([]judge.Result, context.Context) error Close() error } diff --git a/pkg/printer/printer_helper.go b/pkg/printer/printer_helper.go new file mode 100644 index 00000000..3751430e --- /dev/null +++ b/pkg/printer/printer_helper.go @@ -0,0 +1,27 @@ +package printer + +import ( + "context" + "fmt" + "strings" + + ctxKey "github.com/doitintl/kube-no-trouble/pkg/context" +) + +func mapToCommaSeparatedString(m map[string]interface{}) string { + var sb strings.Builder + for k, v := range m { + if sb.Len() > 0 { + sb.WriteString(", ") + } + sb.WriteString(fmt.Sprintf("%s:%v", k, v)) + } + return sb.String() +} + +func getLabels(ctx context.Context) (*bool, error) { + if v := ctx.Value(ctxKey.LABELS_CTX_KEY); v != nil { + return ctx.Value(ctxKey.LABELS_CTX_KEY).(*bool), nil + } + return nil, fmt.Errorf("labels flag not present in the context") +} diff --git a/pkg/printer/printer_helper_test.go b/pkg/printer/printer_helper_test.go new file mode 100644 index 00000000..2ba211a1 --- /dev/null +++ b/pkg/printer/printer_helper_test.go @@ -0,0 +1,118 @@ +package printer + +import ( + "context" + "io/ioutil" + "os" + "strings" + "testing" + + ctxKey "github.com/doitintl/kube-no-trouble/pkg/context" +) + +func TestTypePrinterPrint(t *testing.T) { + tests := []struct { + name string + labels map[string]interface{} + withLabels bool + }{ + { + name: "WithLabels", + labels: map[string]interface{}{"app": "version1"}, + withLabels: true, + }, + { + name: "NoLabels", + labels: map[string]interface{}{}, + withLabels: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpFile, err := ioutil.TempFile(os.TempDir(), tempFilePrefix) + if err != nil { + t.Fatalf(tempFileCreateFailureMessage, err) + } + defer os.Remove(tmpFile.Name()) + + tp := &csvPrinter{ + commonPrinter: &commonPrinter{tmpFile}, + } + + results := getTestResult(tt.labels) + + ctx := context.WithValue(context.Background(), ctxKey.LABELS_CTX_KEY, &tt.withLabels) + if err := tp.Print(results, ctx); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + fi, _ := tmpFile.Stat() + if fi.Size() == 0 { + t.Fatalf("expected non-zero size output file: %v", err) + } + }) + } +} + +func TestMapToCommaSeparatedString(t *testing.T) { + tests := []struct { + input map[string]interface{} + expected string + }{ + { + input: map[string]interface{}{}, + expected: "", + }, + { + input: map[string]interface{}{"key1": "value1"}, + expected: "key1:value1", + }, + { + input: map[string]interface{}{"key1": "value1", "key2": "value2"}, + expected: "key1:value1, key2:value2", + }, + { + input: map[string]interface{}{"key1": 123, "key2": true, "key3": 45.67}, + expected: "key1:123, key2:true, key3:45.67", + }, + } + + for _, test := range tests { + result := mapToCommaSeparatedString(test.input) + parsedResult := parseCSVString(result) + parsedExpected := parseCSVString(test.expected) + + // Compare the parsed maps + if !compareMaps(parsedResult, parsedExpected) { + t.Errorf("For input %v, expected %s but got %s", test.input, test.expected, result) + } + } +} + +func parseCSVString(s string) map[string]string { + result := make(map[string]string) + if s == "" { + return result + } + + pairs := strings.Split(s, ", ") + for _, pair := range pairs { + kv := strings.SplitN(pair, ":", 2) + if len(kv) == 2 { + result[kv[0]] = kv[1] + } + } + return result +} + +func compareMaps(a, b map[string]string) bool { + if len(a) != len(b) { + return false + } + for k, v := range a { + if bv, ok := b[k]; !ok || v != bv { + return false + } + } + return true +} diff --git a/pkg/printer/text.go b/pkg/printer/text.go index f613cc7b..45cb5af2 100644 --- a/pkg/printer/text.go +++ b/pkg/printer/text.go @@ -1,6 +1,7 @@ package printer import ( + "context" "fmt" "sort" "strings" @@ -30,7 +31,7 @@ func (c *textPrinter) Close() error { return c.commonPrinter.Close() } -func (c *textPrinter) Print(results []judge.Result) error { +func (c *textPrinter) Print(results []judge.Result, ctx context.Context) error { sort.Slice(results, func(i, j int) bool { return results[i].Name < results[j].Name @@ -48,15 +49,29 @@ func (c *textPrinter) Print(results []judge.Result) error { ruleSet := "" w := tabwriter.NewWriter(c.commonPrinter.outputFile, 10, 0, 3, ' ', 0) + labels, err := getLabels(ctx) + if err != nil { + return fmt.Errorf("failed to get labels: %w", err) + } + for _, r := range results { if ruleSet != r.RuleSet { ruleSet = r.RuleSet fmt.Fprintf(w, "%s\n", strings.Repeat("_", 90)) fmt.Fprintf(w, ">>> %s <<<\n", ruleSet) fmt.Fprintf(w, "%s\n", strings.Repeat("-", 90)) - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s (%s)\n", "KIND", "NAMESPACE", "NAME", "API_VERSION", "REPLACE_WITH", "SINCE") + if labels != nil && *labels { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s \t(%s) \t%s\n", "KIND", "NAMESPACE", "NAME", "API_VERSION", "REPLACE_WITH", "SINCE", "LABELS") + } else { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s \t(%s)\n", "KIND", "NAMESPACE", "NAME", "API_VERSION", "REPLACE_WITH", "SINCE") + } + + } + if labels != nil && *labels { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s \t(%s) \t%s\n", r.Kind, r.Namespace, r.Name, r.ApiVersion, r.ReplaceWith, r.Since, mapToCommaSeparatedString(r.Labels)) + } else { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s \t(%s) \n", r.Kind, r.Namespace, r.Name, r.ApiVersion, r.ReplaceWith, r.Since) } - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s (%s)\n", r.Kind, r.Namespace, r.Name, r.ApiVersion, r.ReplaceWith, r.Since) } w.Flush() return nil diff --git a/pkg/printer/text_test.go b/pkg/printer/text_test.go index 6888e88d..7e6123ad 100644 --- a/pkg/printer/text_test.go +++ b/pkg/printer/text_test.go @@ -1,11 +1,12 @@ package printer import ( + "context" "io/ioutil" "os" "testing" - "github.com/doitintl/kube-no-trouble/pkg/judge" + ctxKey "github.com/doitintl/kube-no-trouble/pkg/context" ) func Test_newTextPrinter(t *testing.T) { @@ -50,10 +51,11 @@ func Test_textPrinter_Print(t *testing.T) { commonPrinter: &commonPrinter{tmpFile}, } - version, _ := judge.NewVersion("1.2.3") - results := []judge.Result{{"Name", "Namespace", "Kind", "1.2.3", "Test", "4.5.6", version}} + results := getTestResult(map[string]interface{}{"key2": "value2"}) + labelsFlag := false + ctx := context.WithValue(context.Background(), ctxKey.LABELS_CTX_KEY, &labelsFlag) - if err := tp.Print(results); err != nil { + if err := tp.Print(results, ctx); err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/pkg/rules/rego/deprecated-1-16.rego b/pkg/rules/rego/deprecated-1-16.rego index 6927cbd7..1065c121 100644 --- a/pkg/rules/rego/deprecated-1-16.rego +++ b/pkg/rules/rego/deprecated-1-16.rego @@ -12,6 +12,7 @@ main[return] { "ReplaceWith": api.new, "RuleSet": "Deprecated APIs removed in 1.16", "Since": api.since, + "Labels": get_default(resource.metadata, "labels", ""), } } diff --git a/pkg/rules/rego/deprecated-1-22.rego b/pkg/rules/rego/deprecated-1-22.rego index 09a786da..c5c8544d 100644 --- a/pkg/rules/rego/deprecated-1-22.rego +++ b/pkg/rules/rego/deprecated-1-22.rego @@ -12,6 +12,7 @@ main[return] { "ReplaceWith": api.new, "RuleSet": "Deprecated APIs removed in 1.22", "Since": api.since, + "Labels": get_default(resource.metadata, "labels", ""), } } diff --git a/pkg/rules/rego/deprecated-1-25.rego b/pkg/rules/rego/deprecated-1-25.rego index 47bfc8d2..eb35b78e 100644 --- a/pkg/rules/rego/deprecated-1-25.rego +++ b/pkg/rules/rego/deprecated-1-25.rego @@ -12,6 +12,7 @@ main[return] { "ReplaceWith": api.new, "RuleSet": "Deprecated APIs removed in 1.25", "Since": api.since, + "Labels": get_default(resource.metadata, "labels", ""), } } diff --git a/pkg/rules/rego/deprecated-1-26.rego b/pkg/rules/rego/deprecated-1-26.rego index e2d0e09f..7ae9e2bf 100644 --- a/pkg/rules/rego/deprecated-1-26.rego +++ b/pkg/rules/rego/deprecated-1-26.rego @@ -12,6 +12,7 @@ main[return] { "ReplaceWith": api.new, "RuleSet": "Deprecated APIs removed in 1.26", "Since": api.since, + "Labels": get_default(resource.metadata, "labels", ""), } } diff --git a/pkg/rules/rego/deprecated-1-27.rego b/pkg/rules/rego/deprecated-1-27.rego index 7595dca6..084a848a 100644 --- a/pkg/rules/rego/deprecated-1-27.rego +++ b/pkg/rules/rego/deprecated-1-27.rego @@ -12,6 +12,7 @@ main[return] { "ReplaceWith": api.new, "RuleSet": "Deprecated APIs removed in 1.27", "Since": api.since, + "Labels": get_default(resource.metadata, "labels", ""), } } diff --git a/pkg/rules/rego/deprecated-1-29.rego b/pkg/rules/rego/deprecated-1-29.rego index 94b0c40d..22444a26 100644 --- a/pkg/rules/rego/deprecated-1-29.rego +++ b/pkg/rules/rego/deprecated-1-29.rego @@ -12,6 +12,7 @@ main[return] { "ReplaceWith": api.new, "RuleSet": "Deprecated APIs removed in 1.29", "Since": api.since, + "Labels": get_default(resource.metadata, "labels", ""), } } diff --git a/pkg/rules/rego/deprecated-1-32.rego b/pkg/rules/rego/deprecated-1-32.rego index 11a16edf..1db8c526 100644 --- a/pkg/rules/rego/deprecated-1-32.rego +++ b/pkg/rules/rego/deprecated-1-32.rego @@ -12,6 +12,7 @@ main[return] { "ReplaceWith": api.new, "RuleSet": "Deprecated APIs removed in 1.32", "Since": api.since, + "Labels": get_default(resource.metadata, "labels", ""), } } diff --git a/pkg/rules/rego/deprecated-future.rego b/pkg/rules/rego/deprecated-future.rego index f0d4f589..c02ad6f5 100644 --- a/pkg/rules/rego/deprecated-future.rego +++ b/pkg/rules/rego/deprecated-future.rego @@ -12,6 +12,7 @@ main[return] { "ReplaceWith": api.new, "RuleSet": "Deprecated APIs to be removed in future", "Since": api.since, + "Labels": get_default(resource.metadata, "labels", ""), } } diff --git a/pkg/rules/rules_test.go b/pkg/rules/rules_test.go index cc617708..e8156347 100644 --- a/pkg/rules/rules_test.go +++ b/pkg/rules/rules_test.go @@ -75,7 +75,7 @@ func TestRenderRuleRego(t *testing.T) { func TestRenderRuleTmpl(t *testing.T) { additionalResources := []schema.GroupVersionKind{ - schema.GroupVersionKind{ + { Group: "example.com", Version: "v2", Kind: "Test", diff --git a/test/rules_custom_test.go b/test/rules_custom_test.go index 0ac387fc..e6c273ae 100644 --- a/test/rules_custom_test.go +++ b/test/rules_custom_test.go @@ -14,7 +14,7 @@ func TestRegoCustom(t *testing.T) { manifestName := "../fixtures/issuer-v1alpha2.yaml" expectedKind := "Issuer" additionalKinds := []schema.GroupVersionKind{ - schema.GroupVersionKind{ + { Group: "cert-manager.io", Version: "v1alpha2", Kind: "Issuer",