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

refactor: reintroduce output writer #5564

Merged
merged 1 commit into from
Nov 14, 2023
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
10 changes: 4 additions & 6 deletions pkg/cloud/aws/commands/run_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package commands

import (
"bytes"
"context"
"os"
"path/filepath"
Expand Down Expand Up @@ -1135,8 +1136,8 @@ Summary Report for compliance: my-custom-spec
}()
}

output := filepath.Join(t.TempDir(), "output")
test.options.Output = output
output := bytes.NewBuffer(nil)
test.options.SetOutputWriter(output)
test.options.Debug = true
test.options.GlobalOptions.Timeout = time.Minute
if test.options.Format == "" {
Expand Down Expand Up @@ -1178,10 +1179,7 @@ Summary Report for compliance: my-custom-spec
return
}
assert.NoError(t, err)

b, err := os.ReadFile(output)
require.NoError(t, err)
assert.Equal(t, test.want, string(b))
assert.Equal(t, test.want, output.String())
})
}
}
6 changes: 3 additions & 3 deletions pkg/cloud/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ func (r *Report) Failed() bool {

// Write writes the results in the give format
func Write(rep *Report, opt flag.Options, fromCache bool) error {
output, err := opt.OutputWriter()
output, cleanup, err := opt.OutputWriter()
if err != nil {
return xerrors.Errorf("failed to create output file: %w", err)
}
defer output.Close()
defer cleanup()

if opt.Compliance.Spec.ID != "" {
return writeCompliance(rep, opt, output)
Expand Down Expand Up @@ -104,7 +104,7 @@ func Write(rep *Report, opt flag.Options, fromCache bool) error {

// ensure color/formatting is disabled for pipes/non-pty
var useANSI bool
if opt.Output == "" {
if output == os.Stdout {
if o, err := os.Stdout.Stat(); err == nil {
useANSI = (o.Mode() & os.ModeCharDevice) == os.ModeCharDevice
}
Expand Down
12 changes: 4 additions & 8 deletions pkg/cloud/report/resource_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package report

import (
"os"
"path/filepath"
"bytes"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -110,18 +109,15 @@ No problems detected.
tt.options.AWSOptions.Services,
)

output := filepath.Join(t.TempDir(), "output")
tt.options.Output = output
output := bytes.NewBuffer(nil)
tt.options.SetOutputWriter(output)
require.NoError(t, Write(report, tt.options, tt.fromCache))

assert.Equal(t, "AWS", report.Provider)
assert.Equal(t, tt.options.AWSOptions.Account, report.AccountID)
assert.Equal(t, tt.options.AWSOptions.Region, report.Region)
assert.ElementsMatch(t, tt.options.AWSOptions.Services, report.ServicesInScope)

b, err := os.ReadFile(output)
require.NoError(t, err)
assert.Equal(t, tt.expected, string(b))
assert.Equal(t, tt.expected, output.String())
})
}
}
12 changes: 4 additions & 8 deletions pkg/cloud/report/result_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package report

import (
"os"
"path/filepath"
"bytes"
"strings"
"testing"

Expand Down Expand Up @@ -69,18 +68,15 @@ See https://avd.aquasec.com/misconfig/avd-aws-9999
tt.options.AWSOptions.Services,
)

output := filepath.Join(t.TempDir(), "output")
tt.options.Output = output
output := bytes.NewBuffer(nil)
tt.options.SetOutputWriter(output)
require.NoError(t, Write(report, tt.options, tt.fromCache))

b, err := os.ReadFile(output)
require.NoError(t, err)

assert.Equal(t, "AWS", report.Provider)
assert.Equal(t, tt.options.AWSOptions.Account, report.AccountID)
assert.Equal(t, tt.options.AWSOptions.Region, report.Region)
assert.ElementsMatch(t, tt.options.AWSOptions.Services, report.ServicesInScope)
assert.Equal(t, tt.expected, strings.ReplaceAll(string(b), "\r\n", "\n"))
assert.Equal(t, tt.expected, strings.ReplaceAll(output.String(), "\r\n", "\n"))
})
}
}
13 changes: 5 additions & 8 deletions pkg/cloud/report/service_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package report

import (
"os"
"path/filepath"
"bytes"
"testing"

"github.com/aws/aws-sdk-go-v2/aws/arn"
Expand Down Expand Up @@ -317,22 +316,20 @@ Scan Overview for AWS Account
tt.options.AWSOptions.Services,
)

output := filepath.Join(t.TempDir(), "output")
tt.options.Output = output
output := bytes.NewBuffer(nil)
tt.options.SetOutputWriter(output)
require.NoError(t, Write(report, tt.options, tt.fromCache))

assert.Equal(t, "AWS", report.Provider)
assert.Equal(t, tt.options.AWSOptions.Account, report.AccountID)
assert.Equal(t, tt.options.AWSOptions.Region, report.Region)
assert.ElementsMatch(t, tt.options.AWSOptions.Services, report.ServicesInScope)

b, err := os.ReadFile(output)
require.NoError(t, err)
if tt.options.Format == "json" {
// json output can be formatted/ordered differently - we just care that the data matches
assert.JSONEq(t, tt.expected, string(b))
assert.JSONEq(t, tt.expected, output.String())
} else {
assert.Equal(t, tt.expected, string(b))
assert.Equal(t, tt.expected, output.String())
}
})
}
Expand Down
22 changes: 17 additions & 5 deletions pkg/flag/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"github.com/aquasecurity/trivy/pkg/result"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy/pkg/version"
xio "github.com/aquasecurity/trivy/pkg/x/io"
xstrings "github.com/aquasecurity/trivy/pkg/x/strings"
)

Expand Down Expand Up @@ -114,6 +113,10 @@ type Options struct {

// We don't want to allow disabled analyzers to be passed by users, but it is necessary for internal use.
DisabledAnalyzers []analyzer.Type

// outputWriter is not initialized via the CLI.
// It is mainly used for testing purposes or by tools that use Trivy as a library.
outputWriter io.Writer
}

// Align takes consistency of options
Expand Down Expand Up @@ -159,17 +162,26 @@ func (o *Options) FilterOpts() result.FilterOption {
}
}

// SetOutputWriter sets an output writer.
func (o *Options) SetOutputWriter(w io.Writer) {
o.outputWriter = w
}

// OutputWriter returns an output writer.
// If the output file is not specified, it returns os.Stdout.
func (o *Options) OutputWriter() (io.WriteCloser, error) {
func (o *Options) OutputWriter() (io.Writer, func(), error) {
if o.outputWriter != nil {
return o.outputWriter, func() {}, nil
}

if o.Output != "" {
f, err := os.Create(o.Output)
if err != nil {
return nil, xerrors.Errorf("failed to create output file: %w", err)
return nil, nil, xerrors.Errorf("failed to create output file: %w", err)
}
return f, nil
return f, func() { _ = f.Close() }, nil
}
return xio.NopCloser(os.Stdout), nil
return os.Stdout, func() {}, nil
}

func addFlag(cmd *cobra.Command, flag *Flag) {
Expand Down
4 changes: 2 additions & 2 deletions pkg/k8s/commands/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,11 @@ func (r *runner) run(ctx context.Context, artifacts []*k8sArtifacts.Artifact) er
return xerrors.Errorf("k8s scan error: %w", err)
}

output, err := r.flagOpts.OutputWriter()
output, cleanup, err := r.flagOpts.OutputWriter()
if err != nil {
return xerrors.Errorf("failed to create output file: %w", err)
}
defer output.Close()
defer cleanup()

if r.flagOpts.Compliance.Spec.ID != "" {
var scanResults []types.Results
Expand Down
3 changes: 1 addition & 2 deletions pkg/report/table/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"github.com/aquasecurity/tml"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/types"
xio "github.com/aquasecurity/trivy/pkg/x/io"
)

var (
Expand Down Expand Up @@ -137,7 +136,7 @@ func IsOutputToTerminal(output io.Writer) bool {
return false
}

if output != xio.NopCloser(os.Stdout) {
if output != os.Stdout {
return false
}
o, err := os.Stdout.Stat()
Expand Down
4 changes: 2 additions & 2 deletions pkg/report/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ const (

// Write writes the result to output, format as passed in argument
func Write(report types.Report, option flag.Options) error {
output, err := option.OutputWriter()
output, cleanup, err := option.OutputWriter()
if err != nil {
return xerrors.Errorf("failed to create a file: %w", err)
}
defer output.Close()
defer cleanup()

// Compliance report
if option.Compliance.Spec.ID != "" {
Expand Down
12 changes: 0 additions & 12 deletions pkg/x/io/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,6 @@ import (
dio "github.com/aquasecurity/go-dep-parser/pkg/io"
)

// NopCloser returns a WriteCloser with a no-op Close method wrapping
// the provided Writer w.
func NopCloser(w io.Writer) io.WriteCloser {
return nopCloser{w}
}

type nopCloser struct {
io.Writer
}

func (nopCloser) Close() error { return nil }

func NewReadSeekerAt(r io.Reader) (dio.ReadSeekerAt, error) {
if rr, ok := r.(dio.ReadSeekerAt); ok {
return rr, nil
Expand Down