From 15362ee5d5067f15b537a8f9e38db1dd9ed882d6 Mon Sep 17 00:00:00 2001 From: Dominik Richter Date: Thu, 18 Apr 2024 00:59:25 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=8C=88=20add=20additional=20output=20opti?= =?UTF-8?q?ons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This introduces a series of new optios for the default CLI output printer. These give far greater control over which sections are printed (or rather not printed). As a follow-up, we should enabled these for the other output formats as well. For all options, run: ``` > cnspec scan -o help Available output formats: compact, csv, full, json, json-v1, json-v2, junit, report, summary, yaml, yaml-v1, yaml-v2. Available options: [no]checks, [no]controls, [no]data, [no]vulns. Combine with commas, example: compact,nodata,nocontrols ``` Example usage (from this help): ```bash > cnspec scan -o compact,nodata,nocontrols ``` Signed-off-by: Dominik Richter --- apps/cnspec/cmd/scan.go | 4 +- apps/cnspec/cmd/vuln.go | 9 ++- cli/reporter/cli_reporter.go | 60 ++++----------- cli/reporter/cli_reporter_test.go | 87 +++++++++++---------- cli/reporter/file_handler.go | 6 +- cli/reporter/json_test.go | 8 +- cli/reporter/output_handler.go | 17 ++--- cli/reporter/print.go | 121 ++++++++++++++++++++++++++++++ cli/reporter/print_compact.go | 101 ++++++++++++++----------- cli/reporter/print_test.go | 72 ++++++++++++++++++ 10 files changed, 334 insertions(+), 151 deletions(-) create mode 100644 cli/reporter/print_test.go diff --git a/apps/cnspec/cmd/scan.go b/apps/cnspec/cmd/scan.go index 32b4946f..930353b1 100644 --- a/apps/cnspec/cmd/scan.go +++ b/apps/cnspec/cmd/scan.go @@ -96,7 +96,7 @@ To manually configure a policy, use this: // don't have a target connection or provider. output, _ := cmd.Flags().GetString("output") if output == "help" { - fmt.Println("Available output formats: " + reporter.AllFormats()) + fmt.Println(reporter.AllAvailableOptions()) os.Exit(0) } @@ -284,7 +284,7 @@ func getCobraScanConfig(cmd *cobra.Command, runtime *providers.Runtime, cliRes * // print them before executing the scan output, _ := cmd.Flags().GetString("output") if output == "help" { - fmt.Println("Available output formats: " + reporter.AllFormats()) + fmt.Println(reporter.AllAvailableOptions()) os.Exit(0) } diff --git a/apps/cnspec/cmd/vuln.go b/apps/cnspec/cmd/vuln.go index 1bbae6e9..a783f256 100644 --- a/apps/cnspec/cmd/vuln.go +++ b/apps/cnspec/cmd/vuln.go @@ -4,8 +4,6 @@ package cmd import ( - "strings" - "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -59,6 +57,11 @@ var vulnCmdRun = func(cmd *cobra.Command, runtime *providers.Runtime, cliRes *pl conf.Bundle = policy.FromQueryPackBundle(pb) conf.IsIncognito = true + printConf, err := reporter.ParseConfig(conf.OutputFormat) + if err != nil { + log.Fatal().Err(err).Msg("failed to parse config for reporter") + } + report, err := RunScan(conf) if err != nil { log.Fatal().Err(err).Msg("failed to run scan") @@ -131,7 +134,7 @@ var vulnCmdRun = func(cmd *cobra.Command, runtime *providers.Runtime, cliRes *pl } // print the output using the specified output format - r := reporter.NewReporter(reporter.Formats[strings.ToLower(conf.OutputFormat)], false) + r := reporter.NewReporter(printConf, false) logger.DebugDumpJSON("vulnReport", report) if err := r.PrintVulns(vulnReport, bom.Asset.Name); err != nil { log.Fatal().Err(err).Msg("failed to print") diff --git a/cli/reporter/cli_reporter.go b/cli/reporter/cli_reporter.go index 9cabe227..c6f59a89 100644 --- a/cli/reporter/cli_reporter.go +++ b/cli/reporter/cli_reporter.go @@ -84,16 +84,16 @@ func defaultChecksum(code mqlCode, schema resources.ResourcesSchema) (string, er // note: implements the OutputHandler interface type Reporter struct { - Format Format + Conf *PrintConfig Printer *printer.Printer Colors *colors.Theme IsIncognito bool out io.Writer } -func NewReporter(format Format, incognito bool) *Reporter { +func NewReporter(conf *PrintConfig, incognito bool) *Reporter { return &Reporter{ - Format: format, + Conf: conf, Printer: &printer.DefaultPrinter, Colors: &colors.DefaultColorTheme, IsIncognito: incognito, @@ -109,30 +109,10 @@ func (r *Reporter) WithOutput(out io.Writer) *Reporter { func (r *Reporter) WriteReport(ctx context.Context, data *policy.ReportCollection) error { features := cnquery.GetFeatures(ctx) - switch r.Format { - case FormatCompact: + switch r.Conf.format { + case FormatCompact, FormatSummary, FormatFull: rr := &defaultReporter{ Reporter: r, - isCompact: true, - output: r.out, - data: data, - isStoreResourcesEnabled: features.IsActive(cnquery.StoreResourcesData), - } - return rr.print() - case FormatSummary: - rr := &defaultReporter{ - Reporter: r, - isCompact: true, - isSummary: true, - output: r.out, - data: data, - isStoreResourcesEnabled: features.IsActive(cnquery.StoreResourcesData), - } - return rr.print() - case FormatFull: - rr := &defaultReporter{ - Reporter: r, - isCompact: false, output: r.out, data: data, isStoreResourcesEnabled: features.IsActive(cnquery.StoreResourcesData), @@ -186,30 +166,16 @@ func (r *Reporter) WriteReport(ctx context.Context, data *policy.ReportCollectio } func (r *Reporter) PrintVulns(data *mvd.VulnReport, target string) error { - switch r.Format { - case FormatCompact: - rr := &defaultVulnReporter{ - Reporter: r, - isCompact: true, - out: r.out, - data: data, - target: target, - } - return rr.print() - case FormatSummary: - rr := &defaultVulnReporter{ - Reporter: r, - isCompact: true, - isSummary: true, - out: r.out, - data: data, - target: target, - } - return rr.print() - case FormatFull: + if !r.Conf.printVulnerabilities { + return nil + } + + switch r.Conf.format { + case FormatCompact, FormatSummary, FormatFull: rr := &defaultVulnReporter{ Reporter: r, - isCompact: false, + isCompact: r.Conf.isCompact, + isSummary: !r.Conf.printContents(), out: r.out, data: data, target: target, diff --git a/cli/reporter/cli_reporter_test.go b/cli/reporter/cli_reporter_test.go index 285a5f2e..0c636713 100644 --- a/cli/reporter/cli_reporter_test.go +++ b/cli/reporter/cli_reporter_test.go @@ -30,15 +30,14 @@ func TestCompactReporter(t *testing.T) { writer := shared.IOWriter{Writer: &buf} r := &Reporter{ - Format: FormatCompact, + Conf: defaultPrintConfig(), Printer: &printer.DefaultPrinter, Colors: &colors.DefaultColorTheme, } rr := &defaultReporter{ - Reporter: r, - isCompact: true, - output: &writer, - data: yr, + Reporter: r, + output: &writer, + data: yr, } rr.print() @@ -59,42 +58,48 @@ func TestVulnReporter(t *testing.T) { buf := bytes.Buffer{} writer := shared.IOWriter{Writer: &buf} - - r := NewReporter(FormatSummary, false) - r.out = &writer - require.NoError(t, err) - target := "index.docker.io/library/ubuntu@669e010b58ba" - err = r.PrintVulns(report, target) - require.NoError(t, err) - - r = NewReporter(FormatCompact, false) - r.out = &writer - err = r.PrintVulns(report, target) - require.NoError(t, err) - - assert.Contains(t, buf.String(), "5.5 libblkid1 2.34-0.1ubuntu9.1") - assert.NotContains(t, buf.String(), "USN-5279-1") - - r = NewReporter(FormatFull, false) - r.out = &writer - require.NoError(t, err) - - err = r.PrintVulns(report, target) - require.NoError(t, err) - - assert.Contains(t, buf.String(), "5.5 libblkid1 2.34-0.1ubuntu9.1") - assert.Contains(t, buf.String(), "USN-5279-1") - - r = NewReporter(FormatYAMLv1, false) - r.out = &writer - require.NoError(t, err) - - err = r.PrintVulns(report, target) - require.NoError(t, err) - assert.Contains(t, buf.String(), "score: 5.5") - assert.Contains(t, buf.String(), "package: libblkid1") - assert.Contains(t, buf.String(), "installed: 2.34-0.1ubuntu9.1") - assert.Contains(t, buf.String(), "advisory: USN-5279-1") + t.Run("format=summary", func(t *testing.T) { + conf := defaultPrintConfig().setFormat(FormatSummary) + r := NewReporter(conf, false) + r.out = &writer + require.NoError(t, err) + err = r.PrintVulns(report, target) + require.NoError(t, err) + }) + + t.Run("format=compact", func(t *testing.T) { + conf := defaultPrintConfig().setFormat(FormatCompact) + r := NewReporter(conf, false) + r.out = &writer + err = r.PrintVulns(report, target) + require.NoError(t, err) + assert.Contains(t, buf.String(), "5.5 libblkid1 2.34-0.1ubuntu9.1") + assert.NotContains(t, buf.String(), "USN-5279-1") + }) + + t.Run("format=full", func(t *testing.T) { + conf := defaultPrintConfig().setFormat(FormatFull) + r := NewReporter(conf, false) + r.out = &writer + require.NoError(t, err) + err = r.PrintVulns(report, target) + require.NoError(t, err) + assert.Contains(t, buf.String(), "5.5 libblkid1 2.34-0.1ubuntu9.1") + assert.Contains(t, buf.String(), "USN-5279-1") + }) + + t.Run("format=yaml", func(t *testing.T) { + conf := defaultPrintConfig().setFormat(FormatYAMLv1) + r := NewReporter(conf, false) + r.out = &writer + require.NoError(t, err) + err = r.PrintVulns(report, target) + require.NoError(t, err) + assert.Contains(t, buf.String(), "score: 5.5") + assert.Contains(t, buf.String(), "package: libblkid1") + assert.Contains(t, buf.String(), "installed: 2.34-0.1ubuntu9.1") + assert.Contains(t, buf.String(), "advisory: USN-5279-1") + }) } diff --git a/cli/reporter/file_handler.go b/cli/reporter/file_handler.go index e6ed7fb9..9caacaba 100644 --- a/cli/reporter/file_handler.go +++ b/cli/reporter/file_handler.go @@ -13,8 +13,8 @@ import ( ) type localFileHandler struct { - file string - format Format + file string + conf *PrintConfig } // we reuse the already implemented Reporter's WriteReport method by simply pointing the writer @@ -26,7 +26,7 @@ func (h *localFileHandler) WriteReport(ctx context.Context, report *policy.Repor return err } defer f.Close() //nolint: errcheck - reporter := NewReporter(h.format, false) + reporter := NewReporter(h.conf, false) reporter.out = f err = reporter.WriteReport(ctx, report) if err != nil { diff --git a/cli/reporter/json_test.go b/cli/reporter/json_test.go index 3204acd9..964ff928 100644 --- a/cli/reporter/json_test.go +++ b/cli/reporter/json_test.go @@ -29,8 +29,10 @@ func TestJsonOutput(t *testing.T) { buf := bytes.Buffer{} writer := shared.IOWriter{Writer: &buf} + conf := defaultPrintConfig() + conf.format = FormatJSONv1 r := &Reporter{ - Format: FormatJSONv1, + Conf: conf, Printer: &printer.DefaultPrinter, Colors: &colors.DefaultColorTheme, out: &writer, @@ -56,8 +58,10 @@ func TestJsonOutputOnlyErrors(t *testing.T) { buf := bytes.Buffer{} writer := shared.IOWriter{Writer: &buf} + conf := defaultPrintConfig() + conf.format = FormatJSONv1 r := &Reporter{ - Format: FormatJSONv1, + Conf: conf, Printer: &printer.DefaultPrinter, Colors: &colors.DefaultColorTheme, out: &writer, diff --git a/cli/reporter/output_handler.go b/cli/reporter/output_handler.go index 640413c6..e92f30fe 100644 --- a/cli/reporter/output_handler.go +++ b/cli/reporter/output_handler.go @@ -6,8 +6,6 @@ package reporter import ( "bytes" "context" - "errors" - "strings" "go.mondoo.com/cnquery/v11/shared" "go.mondoo.com/cnspec/v11/policy" @@ -36,22 +34,23 @@ type OutputHandler interface { } func NewOutputHandler(config HandlerConfig) (OutputHandler, error) { - format, ok := Formats[strings.ToLower(config.Format)] - if !ok { - return nil, errors.New("unknown output format '" + config.Format + "'. Available: " + AllFormats()) + conf, err := ParseConfig(config.Format) + if err != nil { + return nil, err } + typ := determineOutputType(config.OutputTarget) switch typ { case LOCAL_FILE: - return &localFileHandler{file: config.OutputTarget, format: format}, nil + return &localFileHandler{file: config.OutputTarget, conf: conf}, nil case AWS_SQS: - return &awsSqsHandler{sqsQueueUrl: config.OutputTarget, format: format}, nil + return &awsSqsHandler{sqsQueueUrl: config.OutputTarget, format: conf.format}, nil case AZURE_SBUS: - return &azureSbusHandler{url: config.OutputTarget, format: format}, nil + return &azureSbusHandler{url: config.OutputTarget, format: conf.format}, nil case CLI: fallthrough default: - return NewReporter(format, config.Incognito), nil + return NewReporter(conf, config.Incognito), nil } } diff --git a/cli/reporter/print.go b/cli/reporter/print.go index ff4a64ad..a1b4a44d 100644 --- a/cli/reporter/print.go +++ b/cli/reporter/print.go @@ -4,6 +4,7 @@ package reporter import ( + "errors" "sort" "strings" @@ -14,6 +15,118 @@ import ( type Format byte +type PrintConfig struct { + format Format + isCompact bool + printControls bool + printChecks bool + printData bool + printRisks bool + printVulnerabilities bool +} + +func defaultPrintConfig() *PrintConfig { + return &PrintConfig{ + format: FormatCompact, + isCompact: true, + printControls: true, + printChecks: true, + printData: true, + printRisks: true, + printVulnerabilities: true, + } +} + +const ( + OptionPrintChecks = "checks" + OptionPrintControls = "controls" + OptionPrintData = "data" + OptionPrintRisks = "risks" + OptionPrintVulns = "vulns" +) + +func ParseConfig[T string | Format](raw T) (*PrintConfig, error) { + res := defaultPrintConfig() + if string(raw) == "" { + return res, nil + } + + parts := strings.Split(string(raw), ",") + var unknown []string + for _, cmd := range parts { + cur := strings.ToLower(cmd) + + format, ok := Formats[cur] + if ok { + res.setFormat(format) + continue + } + + switch cur { + case OptionPrintControls: + res.printControls = true + case OptionPrintChecks: + res.printChecks = true + case OptionPrintData: + res.printData = true + case OptionPrintRisks, "risk": + res.printRisks = true + case OptionPrintVulns, "vuln": + res.printVulnerabilities = true + case "no" + OptionPrintControls: + res.printControls = false + case "no" + OptionPrintChecks: + res.printChecks = false + case "no" + OptionPrintData: + res.printData = false + case "no" + OptionPrintRisks, "norisk": + res.printRisks = false + case "no" + OptionPrintVulns, "novuln": + res.printVulnerabilities = false + default: + unknown = append(unknown, cur) + } + } + + if len(unknown) != 0 { + return res, errors.New("unknown terms entered: " + strings.Join(unknown, ", ") + ". " + AllAvailableOptions()) + } + return res, nil +} + +func AllAvailableOptions() string { + return "Available output formats: " + AllFormats() + ".\n" + + "Available options: " + AllOptions() + ".\n" + + "Combine with commas, example: compact,nodata,nocontrols" +} + +func (p *PrintConfig) setFormat(f Format) *PrintConfig { + p.format = f + switch f { + case FormatCompact: + p.isCompact = true + case FormatSummary: + p.isCompact = true + p.printChecks = false + p.printControls = false + p.printData = false + p.printRisks = false + p.printVulnerabilities = false + case FormatFull: + p.isCompact = false + p.printChecks = true + p.printControls = true + p.printData = true + p.printRisks = true + p.printVulnerabilities = true + } + return p +} + +func (p *PrintConfig) printContents() bool { + return p.printControls || p.printChecks || p.printData || p.printVulnerabilities || p.printRisks +} + const ( FormatCompact Format = iota + 1 FormatSummary @@ -57,6 +170,14 @@ func AllFormats() string { return strings.Join(res, ", ") } +func AllOptions() string { + return "[no]" + OptionPrintChecks + ", " + + "[no]" + OptionPrintControls + ", " + + "[no]" + OptionPrintData + ", " + + "[no]" + OptionPrintRisks + ", " + + "[no]" + OptionPrintVulns +} + func (r *Reporter) scoreColored(rating policy.ScoreRating, s string) string { switch rating { case policy.ScoreRating_aPlus, policy.ScoreRating_a, policy.ScoreRating_aMinus: diff --git a/cli/reporter/print_compact.go b/cli/reporter/print_compact.go index 29d55439..d67af88d 100644 --- a/cli/reporter/print_compact.go +++ b/cli/reporter/print_compact.go @@ -32,10 +32,8 @@ type assetMrnName struct { type defaultReporter struct { *Reporter - isCompact bool - isSummary bool - output io.Writer - data *policy.ReportCollection + output io.Writer + data *policy.ReportCollection // indicates if the StoreResourcesData cnquery feature is enabled isStoreResourcesEnabled bool @@ -74,7 +72,7 @@ func (r *defaultReporter) print() error { return orderedAssets[i].Name < orderedAssets[j].Name }) - if !r.isSummary { + if r.Conf.printContents() { r.printAssetSections(orderedAssets) } @@ -171,7 +169,7 @@ func (r *defaultReporter) printSummary(orderedAssets []assetMrnName) { } } - if r.isCompact { + if r.Conf.isCompact { r.out(NewLineCharacter) if !r.IsIncognito && assetUrl != "" { url := "" @@ -363,12 +361,23 @@ func (r *defaultReporter) printAssetSections(orderedAssets []assetMrnName) { continue } - r.printAssetControls(resolved, report, controls, assetMrn, asset) - r.printAssetQueries(resolved, report, queries, assetMrn, asset) - r.printAssetRisks(resolved, report, assetMrn, asset) + if r.Conf.printControls { + r.printAssetControls(resolved, report, controls, assetMrn, asset) + } + + if r.Conf.printData || r.Conf.printChecks { + r.printAssetQueries(resolved, report, queries, assetMrn, asset) + } + + if r.Conf.printRisks { + r.printAssetRisks(resolved, report, assetMrn, asset) + } r.out(NewLineCharacter) - // TODO: we should re-use the report results - r.printVulns(report, assetMrn) + + if r.Conf.printVulnerabilities { + // TODO: we should re-use the report results + r.printVulns(report, assetMrn) + } } r.out(NewLineCharacter) @@ -429,7 +438,7 @@ func (r *defaultReporter) printControl(score *policy.Score, control *policy.Cont r.out(termenv.String("! Error: ").Foreground(r.Colors.Error).String()) r.out(title) r.out(NewLineCharacter) - if !r.isCompact { + if !r.Conf.isCompact { errorMessage := strings.ReplaceAll(score.Message, "\n", NewLineCharacter) r.out(termenv.String(" Message: " + errorMessage).Foreground(r.Colors.Error).String()) r.out(NewLineCharacter) @@ -462,43 +471,47 @@ func (r *defaultReporter) printControl(score *policy.Score, control *policy.Cont func (r *defaultReporter) printAssetQueries(resolved *policy.ResolvedPolicy, report *policy.Report, queries map[string]*explorer.Mquery, assetMrn string, asset *inventory.Asset) { results := report.RawResults() - dataQueriesOutput := "" - resolved.WithDataQueries(func(id string, query *policy.ExecutionQuery) { - data := query.Code.FilterResults(results) - result := r.Reporter.Printer.Results(query.Code, data) - if result == "" { - return - } - if r.isCompact { - result = stringx.MaxLines(10, result) - } - dataQueriesOutput += result + NewLineCharacter - }) - - if len(dataQueriesOutput) > 0 { - r.out("Data queries:" + NewLineCharacter) - r.out(dataQueriesOutput) - r.out(NewLineCharacter) - } + if r.Conf.printData { + dataQueriesOutput := "" + resolved.WithDataQueries(func(id string, query *policy.ExecutionQuery) { + data := query.Code.FilterResults(results) + result := r.Reporter.Printer.Results(query.Code, data) + if result == "" { + return + } + if r.Conf.isCompact { + result = stringx.MaxLines(10, result) + } + dataQueriesOutput += result + NewLineCharacter + }) - foundChecks := map[string]*policy.Score{} - for id, score := range report.Scores { - _, ok := resolved.CollectorJob.ReportingQueries[id] - if !ok { - continue + if len(dataQueriesOutput) > 0 { + r.out("Data queries:" + NewLineCharacter) + r.out(dataQueriesOutput) + r.out(NewLineCharacter) } - foundChecks[id] = score } - if len(foundChecks) > 0 { - r.out("Checks:" + NewLineCharacter) - for id, score := range foundChecks { - query, ok := queries[id] + + if r.Conf.printChecks { + foundChecks := map[string]*policy.Score{} + for id, score := range report.Scores { + _, ok := resolved.CollectorJob.ReportingQueries[id] if !ok { - r.out("Couldn't find any queries for incoming value for " + id) continue } + foundChecks[id] = score + } + if len(foundChecks) > 0 { + r.out("Checks:" + NewLineCharacter) + for id, score := range foundChecks { + query, ok := queries[id] + if !ok { + r.out("Couldn't find any queries for incoming value for " + id) + continue + } - r.printCheck(score, query, resolved, report, results) + r.printCheck(score, query, resolved, report, results) + } } } } @@ -587,7 +600,7 @@ func (r *defaultReporter) printCheck(score *policy.Score, query *explorer.Mquery r.out(termenv.String("! Error: ").Foreground(r.Colors.Error).String()) r.out(title) r.out(NewLineCharacter) - if !r.isCompact { + if !r.Conf.isCompact { errorMessage := strings.ReplaceAll(score.Message, "\n", NewLineCharacter) r.out(termenv.String(" Message: " + errorMessage).Foreground(r.Colors.Error).String()) r.out(NewLineCharacter) @@ -606,7 +619,7 @@ func (r *defaultReporter) printCheck(score *policy.Score, query *explorer.Mquery r.out(r.printScore(title, score, query)) // additional information about the failed query - if !r.isCompact && score.Value != 100 { + if !r.Conf.isCompact && score.Value != 100 { queryString := strings.ReplaceAll(stringx.Indent(4, query.Query), "\n", NewLineCharacter) r.out(" Query:" + NewLineCharacter + queryString) r.out(NewLineCharacter) diff --git a/cli/reporter/print_test.go b/cli/reporter/print_test.go new file mode 100644 index 00000000..d7764f1b --- /dev/null +++ b/cli/reporter/print_test.go @@ -0,0 +1,72 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package reporter + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseConfig(t *testing.T) { + tests := []struct { + conf string + f func(t *testing.T, conf *PrintConfig) + }{ + {"", func(t *testing.T, conf *PrintConfig) { + assert.Equal(t, defaultPrintConfig(), conf) + }}, + {"compact", func(t *testing.T, conf *PrintConfig) { + assert.Equal(t, defaultPrintConfig(), conf) + }}, + {"summary", func(t *testing.T, conf *PrintConfig) { + expect := defaultPrintConfig() + expect.format = FormatSummary + expect.printData = false + expect.printVulnerabilities = false + expect.printControls = false + expect.printChecks = false + expect.printRisks = false + assert.Equal(t, expect, conf) + }}, + {"summary,checks,DATA,vulns", func(t *testing.T, conf *PrintConfig) { + expect := defaultPrintConfig() + expect.format = FormatSummary + expect.printControls = false + expect.printRisks = false + assert.Equal(t, expect, conf) + }}, + {"full", func(t *testing.T, conf *PrintConfig) { + expect := defaultPrintConfig() + expect.format = FormatFull + expect.isCompact = false + assert.Equal(t, expect, conf) + }}, + {"nodata,noVuln,noRiSks", func(t *testing.T, conf *PrintConfig) { + expect := defaultPrintConfig() + expect.printData = false + expect.printVulnerabilities = false + expect.printRisks = false + assert.Equal(t, expect, conf) + }}, + } + + for i := range tests { + cur := tests[i] + t.Run(cur.conf, func(t *testing.T) { + res, err := ParseConfig(cur.conf) + require.NoError(t, err) + cur.f(t, res) + }) + } + + t.Run("unknown options", func(t *testing.T) { + _, err := ParseConfig("notknown") + require.Error(t, err) + assert.Contains(t, err.Error(), "unknown terms entered: notknown") + assert.Contains(t, err.Error(), "Available output formats: compact, csv, ") + assert.Contains(t, err.Error(), "Available options: [no]checks, [no]controls, ") + }) +}