Skip to content

Commit

Permalink
🌈 add additional output options
Browse files Browse the repository at this point in the history
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 <dominik.richter@gmail.com>
  • Loading branch information
arlimus committed Apr 18, 2024
1 parent 2eb73f5 commit 15362ee
Show file tree
Hide file tree
Showing 10 changed files with 334 additions and 151 deletions.
4 changes: 2 additions & 2 deletions apps/cnspec/cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}

Expand Down
9 changes: 6 additions & 3 deletions apps/cnspec/cmd/vuln.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
package cmd

import (
"strings"

"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down
60 changes: 13 additions & 47 deletions cli/reporter/cli_reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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),
Expand Down Expand Up @@ -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,
Expand Down
87 changes: 46 additions & 41 deletions cli/reporter/cli_reporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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")
})
}
6 changes: 3 additions & 3 deletions cli/reporter/file_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
8 changes: 6 additions & 2 deletions cli/reporter/json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
17 changes: 8 additions & 9 deletions cli/reporter/output_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ package reporter
import (
"bytes"
"context"
"errors"
"strings"

"go.mondoo.com/cnquery/v11/shared"
"go.mondoo.com/cnspec/v11/policy"
Expand Down Expand Up @@ -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
}
}

Expand Down
Loading

0 comments on commit 15362ee

Please sign in to comment.