mirror of
https://github.com/aquasecurity/trivy.git
synced 2026-01-31 13:53:14 +08:00
refactor(app): use internal and separate configurations (#291)
* refactor(cmd): move app to internal * refactor(config): inject logger * test(config): add tests
This commit is contained in:
committed by
Simarpreet Singh
parent
6cbbb22ab4
commit
3a53a88139
@@ -4,7 +4,8 @@ import (
|
||||
l "log"
|
||||
"os"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg"
|
||||
"github.com/aquasecurity/trivy/internal/standalone"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
)
|
||||
|
||||
@@ -13,7 +14,7 @@ var (
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := pkg.NewApp(version)
|
||||
app := standalone.NewApp(version)
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
if log.Logger != nil {
|
||||
|
||||
4
go.mod
4
go.mod
@@ -18,12 +18,14 @@ require (
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/urfave/cli v1.20.0
|
||||
go.etcd.io/bbolt v1.3.3 // indirect
|
||||
go.uber.org/atomic v1.5.1 // indirect
|
||||
go.uber.org/multierr v1.4.0 // indirect
|
||||
go.uber.org/zap v1.9.1
|
||||
go.uber.org/zap v1.13.0
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529
|
||||
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421
|
||||
golang.org/x/sys v0.0.0-20191020152052-9984515f0562 // indirect
|
||||
golang.org/x/tools v0.0.0-20191121040551-947d4aa89328 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28
|
||||
gopkg.in/yaml.v2 v2.2.4 // indirect
|
||||
|
||||
7
go.sum
7
go.sum
@@ -281,14 +281,19 @@ go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.5.1 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM=
|
||||
go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E=
|
||||
go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -364,6 +369,8 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191121040551-947d4aa89328 h1:t3X42h9e6xdbrCD/gPyWqAXr2BEpdJqRd1brThaaxII=
|
||||
golang.org/x/tools v0.0.0-20191121040551-947d4aa89328/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373 h1:PPwnA7z1Pjf7XYaBP9GL1VAMZmcIWyFz7QCMSIIa3Bg=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
|
||||
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/aquasecurity/trivy/internal/standalone"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var update = flag.Bool("update", false, "update golden files")
|
||||
@@ -340,7 +340,7 @@ func TestRun_WithTar(t *testing.T) {
|
||||
defer os.RemoveAll(cacheDir)
|
||||
|
||||
// Setup CLI App
|
||||
app := pkg.NewApp(c.testArgs.Version)
|
||||
app := standalone.NewApp(c.testArgs.Version)
|
||||
app.Writer = ioutil.Discard
|
||||
|
||||
osArgs := []string{"trivy", "--cache-dir", cacheDir, "--format", c.testArgs.Format}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package pkg
|
||||
package standalone
|
||||
|
||||
import (
|
||||
"strings"
|
||||
164
internal/standalone/config/config.go
Normal file
164
internal/standalone/config/config.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/genuinetools/reg/registry"
|
||||
"github.com/urfave/cli"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
context *cli.Context
|
||||
logger *zap.SugaredLogger
|
||||
|
||||
Quiet bool
|
||||
NoProgress bool
|
||||
Debug bool
|
||||
|
||||
CacheDir string
|
||||
Reset bool
|
||||
DownloadDBOnly bool
|
||||
SkipUpdate bool
|
||||
ClearCache bool
|
||||
|
||||
Input string
|
||||
output string
|
||||
Format string
|
||||
Template string
|
||||
|
||||
Timeout time.Duration
|
||||
vulnType string
|
||||
Light bool
|
||||
severities string
|
||||
IgnoreFile string
|
||||
IgnoreUnfixed bool
|
||||
ExitCode int
|
||||
|
||||
// these variables are generated by Init()
|
||||
ImageName string
|
||||
VulnType []string
|
||||
Output *os.File
|
||||
Severities []dbTypes.Severity
|
||||
AppVersion string
|
||||
|
||||
// deprecated
|
||||
onlyUpdate string
|
||||
// deprecated
|
||||
refresh bool
|
||||
// deprecated
|
||||
autoRefresh bool
|
||||
}
|
||||
|
||||
func New(c *cli.Context) (Config, error) {
|
||||
debug := c.Bool("debug")
|
||||
quiet := c.Bool("quiet")
|
||||
logger, err := log.NewLogger(debug, quiet)
|
||||
if err != nil {
|
||||
return Config{}, xerrors.New("failed to create a logger")
|
||||
}
|
||||
return Config{
|
||||
context: c,
|
||||
logger: logger,
|
||||
|
||||
Quiet: quiet,
|
||||
NoProgress: c.Bool("no-progress"),
|
||||
Debug: debug,
|
||||
|
||||
CacheDir: c.String("cache-dir"),
|
||||
Reset: c.Bool("reset"),
|
||||
DownloadDBOnly: c.Bool("download-db-only"),
|
||||
SkipUpdate: c.Bool("skip-update"),
|
||||
ClearCache: c.Bool("clear-cache"),
|
||||
|
||||
Input: c.String("input"),
|
||||
output: c.String("output"),
|
||||
Format: c.String("format"),
|
||||
Template: c.String("template"),
|
||||
|
||||
Timeout: c.Duration("timeout"),
|
||||
vulnType: c.String("vuln-type"),
|
||||
Light: c.Bool("light"),
|
||||
severities: c.String("severity"),
|
||||
IgnoreFile: c.String("ignorefile"),
|
||||
IgnoreUnfixed: c.Bool("ignore-unfixed"),
|
||||
ExitCode: c.Int("exit-code"),
|
||||
|
||||
onlyUpdate: c.String("only-update"),
|
||||
refresh: c.Bool("refresh"),
|
||||
autoRefresh: c.Bool("auto-refresh"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Config) Init() (err error) {
|
||||
if c.onlyUpdate != "" || c.refresh || c.autoRefresh {
|
||||
c.logger.Warn("--only-update, --refresh and --auto-refresh are unnecessary and ignored now. These commands will be removed in the next version.")
|
||||
}
|
||||
if c.SkipUpdate && c.DownloadDBOnly {
|
||||
return xerrors.New("The --skip-update and --download-db-only option can not be specified both")
|
||||
}
|
||||
|
||||
c.Severities = c.splitSeverity(c.severities)
|
||||
c.VulnType = strings.Split(c.vulnType, ",")
|
||||
c.AppVersion = c.context.App.Version
|
||||
|
||||
if c.Quiet || c.NoProgress {
|
||||
utils.Quiet = true
|
||||
}
|
||||
|
||||
// --clear-cache and --reset don't conduct the scan
|
||||
if c.ClearCache || c.Reset {
|
||||
return nil
|
||||
}
|
||||
|
||||
args := c.context.Args()
|
||||
if c.Input == "" && len(args) == 0 {
|
||||
c.logger.Error(`trivy requires at least 1 argument or --input option`)
|
||||
cli.ShowAppHelp(c.context)
|
||||
return xerrors.New("arguments error")
|
||||
}
|
||||
|
||||
c.Output = os.Stdout
|
||||
if c.output != "" {
|
||||
if c.Output, err = os.Create(c.output); err != nil {
|
||||
return xerrors.Errorf("failed to create an output file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Input == "" {
|
||||
c.ImageName = args[0]
|
||||
}
|
||||
|
||||
// Check whether 'latest' tag is used
|
||||
if c.ImageName != "" {
|
||||
image, err := registry.ParseImage(c.ImageName)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("invalid image: %w", err)
|
||||
}
|
||||
if image.Tag == "latest" {
|
||||
c.logger.Warn("You should avoid using the :latest tag as it is cached. You need to specify '--clear-cache' option when :latest image is changed")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) splitSeverity(severity string) []dbTypes.Severity {
|
||||
c.logger.Debugf("Severities: %s", severity)
|
||||
var severities []dbTypes.Severity
|
||||
for _, s := range strings.Split(severity, ",") {
|
||||
severity, err := dbTypes.NewSeverity(s)
|
||||
if err != nil {
|
||||
c.logger.Warnf("unknown severity option: %s", err)
|
||||
}
|
||||
severities = append(severities, severity)
|
||||
}
|
||||
return severities
|
||||
}
|
||||
288
internal/standalone/config/config_test.go
Normal file
288
internal/standalone/config/config_test.go
Normal file
@@ -0,0 +1,288 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zaptest/observer"
|
||||
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want Config
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
args: []string{"-quiet", "--no-progress", "--reset", "--skip-update"},
|
||||
want: Config{
|
||||
Quiet: true,
|
||||
NoProgress: true,
|
||||
Reset: true,
|
||||
SkipUpdate: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
app := &cli.App{}
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
set.Bool("quiet", false, "")
|
||||
set.Bool("no-progress", false, "")
|
||||
set.Bool("reset", false, "")
|
||||
set.Bool("skip-update", false, "")
|
||||
|
||||
c := cli.NewContext(app, set, nil)
|
||||
_ = set.Parse(tt.args)
|
||||
|
||||
tt.want.context = c
|
||||
|
||||
_, err := New(c)
|
||||
assert.NoError(t, err, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_Init(t *testing.T) {
|
||||
type fields struct {
|
||||
context *cli.Context
|
||||
Quiet bool
|
||||
NoProgress bool
|
||||
Debug bool
|
||||
CacheDir string
|
||||
Reset bool
|
||||
DownloadDBOnly bool
|
||||
SkipUpdate bool
|
||||
ClearCache bool
|
||||
Input string
|
||||
output string
|
||||
Format string
|
||||
Template string
|
||||
Timeout time.Duration
|
||||
vulnType string
|
||||
Light bool
|
||||
severities string
|
||||
IgnoreFile string
|
||||
IgnoreUnfixed bool
|
||||
ExitCode int
|
||||
ImageName string
|
||||
VulnType []string
|
||||
Output *os.File
|
||||
Severities []dbTypes.Severity
|
||||
AppVersion string
|
||||
onlyUpdate string
|
||||
refresh bool
|
||||
autoRefresh bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args []string
|
||||
logs []string
|
||||
want Config
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
fields: fields{
|
||||
severities: "CRITICAL",
|
||||
vulnType: "os",
|
||||
Quiet: true,
|
||||
},
|
||||
args: []string{"alpine:3.10"},
|
||||
want: Config{
|
||||
AppVersion: "0.0.0",
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||
severities: "CRITICAL",
|
||||
ImageName: "alpine:3.10",
|
||||
VulnType: []string{"os"},
|
||||
vulnType: "os",
|
||||
Output: os.Stdout,
|
||||
Quiet: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path: reset",
|
||||
fields: fields{
|
||||
severities: "CRITICAL",
|
||||
vulnType: "os",
|
||||
Reset: true,
|
||||
},
|
||||
args: []string{"alpine:3.10"},
|
||||
want: Config{
|
||||
AppVersion: "0.0.0",
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
|
||||
severities: "CRITICAL",
|
||||
VulnType: []string{"os"},
|
||||
vulnType: "os",
|
||||
Reset: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path with an unknown severity",
|
||||
fields: fields{
|
||||
severities: "CRITICAL,INVALID",
|
||||
vulnType: "os,library",
|
||||
},
|
||||
args: []string{"centos:7"},
|
||||
logs: []string{
|
||||
"unknown severity option: unknown severity: INVALID",
|
||||
},
|
||||
want: Config{
|
||||
AppVersion: "0.0.0",
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityUnknown},
|
||||
severities: "CRITICAL,INVALID",
|
||||
ImageName: "centos:7",
|
||||
VulnType: []string{"os", "library"},
|
||||
vulnType: "os,library",
|
||||
Output: os.Stdout,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "deprecated options",
|
||||
fields: fields{
|
||||
onlyUpdate: "alpine",
|
||||
severities: "LOW",
|
||||
vulnType: "os,library",
|
||||
},
|
||||
args: []string{"debian:buster"},
|
||||
logs: []string{
|
||||
"--only-update, --refresh and --auto-refresh are unnecessary and ignored now. These commands will be removed in the next version.",
|
||||
},
|
||||
want: Config{
|
||||
AppVersion: "0.0.0",
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
||||
severities: "LOW",
|
||||
ImageName: "debian:buster",
|
||||
VulnType: []string{"os", "library"},
|
||||
vulnType: "os,library",
|
||||
Output: os.Stdout,
|
||||
onlyUpdate: "alpine",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with latest tag",
|
||||
fields: fields{
|
||||
onlyUpdate: "alpine",
|
||||
severities: "LOW",
|
||||
vulnType: "os,library",
|
||||
},
|
||||
args: []string{"gcr.io/distroless/base"},
|
||||
logs: []string{
|
||||
"--only-update, --refresh and --auto-refresh are unnecessary and ignored now. These commands will be removed in the next version.",
|
||||
"You should avoid using the :latest tag as it is cached. You need to specify '--clear-cache' option when :latest image is changed",
|
||||
},
|
||||
want: Config{
|
||||
AppVersion: "0.0.0",
|
||||
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
|
||||
severities: "LOW",
|
||||
ImageName: "gcr.io/distroless/base",
|
||||
VulnType: []string{"os", "library"},
|
||||
vulnType: "os,library",
|
||||
Output: os.Stdout,
|
||||
onlyUpdate: "alpine",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sad: skip and download db",
|
||||
fields: fields{
|
||||
refresh: true,
|
||||
SkipUpdate: true,
|
||||
DownloadDBOnly: true,
|
||||
},
|
||||
args: []string{"alpine:3.10"},
|
||||
logs: []string{
|
||||
"--only-update, --refresh and --auto-refresh are unnecessary and ignored now. These commands will be removed in the next version.",
|
||||
},
|
||||
wantErr: "The --skip-update and --download-db-only option can not be specified both",
|
||||
},
|
||||
{
|
||||
name: "sad: no image name",
|
||||
fields: fields{
|
||||
severities: "MEDIUM",
|
||||
},
|
||||
logs: []string{
|
||||
"trivy requires at least 1 argument or --input option",
|
||||
},
|
||||
wantErr: "arguments error",
|
||||
},
|
||||
{
|
||||
name: "sad: invalid image name",
|
||||
fields: fields{
|
||||
severities: "HIGH",
|
||||
},
|
||||
args: []string{`!"#$%&'()`},
|
||||
wantErr: "invalid image: parsing image",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
core, obs := observer.New(zap.InfoLevel)
|
||||
logger := zap.New(core)
|
||||
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", 0)
|
||||
ctx := cli.NewContext(app, set, nil)
|
||||
_ = set.Parse(tt.args)
|
||||
|
||||
c := &Config{
|
||||
context: ctx,
|
||||
logger: logger.Sugar(),
|
||||
Quiet: tt.fields.Quiet,
|
||||
NoProgress: tt.fields.NoProgress,
|
||||
Debug: tt.fields.Debug,
|
||||
CacheDir: tt.fields.CacheDir,
|
||||
Reset: tt.fields.Reset,
|
||||
DownloadDBOnly: tt.fields.DownloadDBOnly,
|
||||
SkipUpdate: tt.fields.SkipUpdate,
|
||||
ClearCache: tt.fields.ClearCache,
|
||||
Input: tt.fields.Input,
|
||||
output: tt.fields.output,
|
||||
Format: tt.fields.Format,
|
||||
Template: tt.fields.Template,
|
||||
Timeout: tt.fields.Timeout,
|
||||
vulnType: tt.fields.vulnType,
|
||||
Light: tt.fields.Light,
|
||||
severities: tt.fields.severities,
|
||||
IgnoreFile: tt.fields.IgnoreFile,
|
||||
IgnoreUnfixed: tt.fields.IgnoreUnfixed,
|
||||
ExitCode: tt.fields.ExitCode,
|
||||
ImageName: tt.fields.ImageName,
|
||||
Output: tt.fields.Output,
|
||||
onlyUpdate: tt.fields.onlyUpdate,
|
||||
refresh: tt.fields.refresh,
|
||||
autoRefresh: tt.fields.autoRefresh,
|
||||
}
|
||||
|
||||
err := c.Init()
|
||||
|
||||
// tests log messages
|
||||
var gotMessages []string
|
||||
for _, entry := range obs.AllUntimed() {
|
||||
gotMessages = append(gotMessages, entry.Message)
|
||||
}
|
||||
assert.Equal(t, tt.logs, gotMessages, tt.name)
|
||||
|
||||
// test the error
|
||||
switch {
|
||||
case tt.wantErr != "":
|
||||
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
|
||||
return
|
||||
default:
|
||||
assert.NoError(t, err, tt.name)
|
||||
}
|
||||
|
||||
tt.want.context = ctx
|
||||
tt.want.logger = logger.Sugar()
|
||||
assert.Equal(t, &tt.want, c, tt.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
136
internal/standalone/run.go
Normal file
136
internal/standalone/run.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package standalone
|
||||
|
||||
import (
|
||||
"context"
|
||||
l "log"
|
||||
"os"
|
||||
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
"github.com/aquasecurity/trivy/internal/standalone/config"
|
||||
dbFile "github.com/aquasecurity/trivy/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func Run(cliCtx *cli.Context) error {
|
||||
c, err := config.New(cliCtx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return run(c)
|
||||
}
|
||||
|
||||
func run(c config.Config) (err error) {
|
||||
if err = log.InitLogger(c.Debug, c.Quiet); err != nil {
|
||||
l.Fatal(err)
|
||||
}
|
||||
|
||||
// initialize config
|
||||
if err = c.Init(); err != nil {
|
||||
return xerrors.Errorf("failed to initialize options: %w", err)
|
||||
}
|
||||
|
||||
// configure cache dir
|
||||
utils.SetCacheDir(c.CacheDir)
|
||||
log.Logger.Debugf("cache dir: %s", utils.CacheDir())
|
||||
|
||||
if c.Reset {
|
||||
return reset()
|
||||
}
|
||||
|
||||
if c.ClearCache {
|
||||
return clearCache()
|
||||
}
|
||||
|
||||
if err = db.Init(c.CacheDir); err != nil {
|
||||
return xerrors.Errorf("error in vulnerability DB initialize: %w", err)
|
||||
}
|
||||
|
||||
// download the database file
|
||||
if err = downloadDB(c.AppVersion, c.CacheDir, c.Light, c.SkipUpdate); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.DownloadDBOnly {
|
||||
return nil
|
||||
}
|
||||
|
||||
scanOptions := types.ScanOptions{
|
||||
VulnType: c.VulnType,
|
||||
Timeout: c.Timeout,
|
||||
}
|
||||
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)
|
||||
|
||||
results, err := scanner.ScanImage(c.ImageName, c.Input, scanOptions)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error in image scan: %w", err)
|
||||
}
|
||||
|
||||
vulnClient := vulnerability.NewClient()
|
||||
for i := range results {
|
||||
results[i].Vulnerabilities = vulnClient.FillAndFilter(results[i].Vulnerabilities,
|
||||
c.Severities, c.IgnoreUnfixed, c.IgnoreFile, c.Light)
|
||||
}
|
||||
|
||||
if err = report.WriteResults(c.Format, c.Output, results, c.Template, c.Light); err != nil {
|
||||
return xerrors.Errorf("unable to write results: %w", err)
|
||||
}
|
||||
|
||||
if c.ExitCode != 0 {
|
||||
for _, result := range results {
|
||||
if len(result.Vulnerabilities) > 0 {
|
||||
os.Exit(c.ExitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func reset() (err error) {
|
||||
log.Logger.Info("Resetting...")
|
||||
if err = cache.Clear(); err != nil {
|
||||
return xerrors.New("failed to remove image layer cache")
|
||||
}
|
||||
if err = os.RemoveAll(utils.CacheDir()); err != nil {
|
||||
return xerrors.New("failed to remove cache")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func clearCache() error {
|
||||
log.Logger.Info("Removing image caches...")
|
||||
if err := cache.Clear(); err != nil {
|
||||
return xerrors.New("failed to remove image layer cache")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadDB(appVersion, cacheDir string, light, skipUpdate bool) error {
|
||||
client := dbFile.NewClient()
|
||||
ctx := context.Background()
|
||||
if err := client.Download(ctx, appVersion, cacheDir, light, skipUpdate); err != nil {
|
||||
return xerrors.Errorf("failed to download vulnerability DB: %w", err)
|
||||
}
|
||||
// for debug
|
||||
if err := showDBInfo(); err != nil {
|
||||
return xerrors.Errorf("failed to show database info")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func showDBInfo() error {
|
||||
metadata, err := db.Config{}.GetMetadata()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("something wrong with DB: %w", err)
|
||||
}
|
||||
log.Logger.Debugf("DB Schema: %d, Type: %d, UpdatedAt: %s, NextUpdate: %s",
|
||||
metadata.Version, metadata.Type, metadata.UpdatedAt, metadata.NextUpdate)
|
||||
return nil
|
||||
}
|
||||
@@ -15,7 +15,7 @@ var (
|
||||
|
||||
func InitLogger(debug, disable bool) (err error) {
|
||||
debugOption = debug
|
||||
Logger, err = newLogger(debug, disable)
|
||||
Logger, err = NewLogger(debug, disable)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error in new logger: %w", err)
|
||||
}
|
||||
@@ -23,7 +23,7 @@ func InitLogger(debug, disable bool) (err error) {
|
||||
|
||||
}
|
||||
|
||||
func newLogger(debug, disable bool) (*zap.SugaredLogger, error) {
|
||||
func NewLogger(debug, disable bool) (*zap.SugaredLogger, error) {
|
||||
level := zap.NewAtomicLevel()
|
||||
if debug {
|
||||
level.SetLevel(zapcore.DebugLevel)
|
||||
@@ -56,6 +56,7 @@ func newLogger(debug, disable bool) (*zap.SugaredLogger, error) {
|
||||
myConfig.OutputPaths = []string{os.DevNull}
|
||||
myConfig.ErrorOutputPaths = []string{os.DevNull}
|
||||
}
|
||||
|
||||
logger, err := myConfig.Build()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to build zap config: %w", err)
|
||||
|
||||
173
pkg/run.go
173
pkg/run.go
@@ -1,173 +0,0 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"context"
|
||||
l "log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/aquasecurity/fanal/cache"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
dbFile "github.com/aquasecurity/trivy/pkg/db"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/report"
|
||||
"github.com/aquasecurity/trivy/pkg/scanner"
|
||||
"github.com/aquasecurity/trivy/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
"github.com/aquasecurity/trivy/pkg/vulnerability"
|
||||
"github.com/genuinetools/reg/registry"
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
func Run(c *cli.Context) (err error) {
|
||||
if c.Bool("quiet") || c.Bool("no-progress") {
|
||||
utils.Quiet = true
|
||||
}
|
||||
debug := c.Bool("debug")
|
||||
if err = log.InitLogger(debug, c.Bool("quiet")); err != nil {
|
||||
l.Fatal(err)
|
||||
}
|
||||
|
||||
if c.String("only-update") != "" || c.Bool("refresh") || c.Bool("auto-refresh") {
|
||||
log.Logger.Warn("--only-update, --refresh and --auto-refresh are unnecessary and ignored now. These commands will be removed in the next version.")
|
||||
}
|
||||
|
||||
cacheDir := c.String("cache-dir")
|
||||
utils.SetCacheDir(c.String("cache-dir"))
|
||||
log.Logger.Debugf("cache dir: %s", utils.CacheDir())
|
||||
|
||||
reset := c.Bool("reset")
|
||||
if reset {
|
||||
log.Logger.Info("Resetting...")
|
||||
if err = cache.Clear(); err != nil {
|
||||
return xerrors.New("failed to remove image layer cache")
|
||||
}
|
||||
if err = os.RemoveAll(utils.CacheDir()); err != nil {
|
||||
return xerrors.New("failed to remove cache")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = db.Init(cacheDir); err != nil {
|
||||
return xerrors.Errorf("error in vulnerability DB initialize: %w", err)
|
||||
}
|
||||
|
||||
downloadDBOnly := c.Bool("download-db-only")
|
||||
skipUpdate := c.Bool("skip-update")
|
||||
if skipUpdate && downloadDBOnly {
|
||||
return xerrors.New("The --skip-update and --download-db-only option can not be specified both")
|
||||
}
|
||||
|
||||
light := c.Bool("light")
|
||||
client := dbFile.NewClient()
|
||||
ctx := context.Background()
|
||||
if err = client.Download(ctx, c.App.Version, cacheDir, light, skipUpdate); err != nil {
|
||||
return xerrors.Errorf("failed to download vulnerability DB: %w", err)
|
||||
}
|
||||
|
||||
// for debug
|
||||
metadata, err := db.Config{}.GetMetadata()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("something wrong with DB: %w", err)
|
||||
}
|
||||
log.Logger.Debugf("DB Schema: %d, Type: %d, UpdatedAt: %s, NextUpdate: %s",
|
||||
metadata.Version, metadata.Type, metadata.UpdatedAt, metadata.NextUpdate)
|
||||
|
||||
if downloadDBOnly {
|
||||
return nil
|
||||
}
|
||||
|
||||
clearCache := c.Bool("clear-cache")
|
||||
if clearCache {
|
||||
log.Logger.Info("Removing image caches...")
|
||||
if err = cache.Clear(); err != nil {
|
||||
return xerrors.New("failed to remove image layer cache")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
args := c.Args()
|
||||
filePath := c.String("input")
|
||||
if filePath == "" && len(args) == 0 {
|
||||
log.Logger.Info(`trivy requires at least 1 argument or --input option.`)
|
||||
cli.ShowAppHelpAndExit(c, 1)
|
||||
}
|
||||
|
||||
o := c.String("output")
|
||||
output := os.Stdout
|
||||
if o != "" {
|
||||
if output, err = os.Create(o); err != nil {
|
||||
return xerrors.Errorf("failed to create an output file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var imageName string
|
||||
if filePath == "" {
|
||||
imageName = args[0]
|
||||
}
|
||||
|
||||
// Check whether 'latest' tag is used
|
||||
if imageName != "" {
|
||||
image, err := registry.ParseImage(imageName)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("invalid image: %w", err)
|
||||
}
|
||||
if image.Tag == "latest" && !clearCache {
|
||||
log.Logger.Warn("You should avoid using the :latest tag as it is cached. You need to specify '--clear-cache' option when :latest image is changed")
|
||||
}
|
||||
}
|
||||
|
||||
timeout := c.Duration("timeout")
|
||||
scanOptions := types.ScanOptions{
|
||||
VulnType: strings.Split(c.String("vuln-type"), ","),
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)
|
||||
|
||||
results, err := scanner.ScanImage(imageName, filePath, scanOptions)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("error in image scan: %w", err)
|
||||
}
|
||||
|
||||
severities := splitSeverity(c.String("severity"))
|
||||
ignoreFile := c.String("ignorefile")
|
||||
ignoreUnfixed := c.Bool("ignore-unfixed")
|
||||
vulnClient := vulnerability.NewClient()
|
||||
for i := range results {
|
||||
results[i].Vulnerabilities = vulnClient.FillAndFilter(results[i].Vulnerabilities,
|
||||
severities, ignoreUnfixed, ignoreFile, light)
|
||||
}
|
||||
|
||||
format := c.String("format")
|
||||
template := c.String("template")
|
||||
if err = report.WriteResults(format, output, results, template, light); err != nil {
|
||||
return xerrors.Errorf("unable to write results: %w", err)
|
||||
}
|
||||
|
||||
exitCode := c.Int("exit-code")
|
||||
if exitCode != 0 {
|
||||
for _, result := range results {
|
||||
if len(result.Vulnerabilities) > 0 {
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func splitSeverity(severity string) []dbTypes.Severity {
|
||||
log.Logger.Debugf("Severities: %s", severity)
|
||||
var severities []dbTypes.Severity
|
||||
for _, s := range strings.Split(severity, ",") {
|
||||
severity, err := dbTypes.NewSeverity(s)
|
||||
if err != nil {
|
||||
log.Logger.Warnf("unknown severity option: %s", err)
|
||||
}
|
||||
severities = append(severities, severity)
|
||||
}
|
||||
return severities
|
||||
}
|
||||
Reference in New Issue
Block a user