refactor: move from urfave/cli to spf13/cobra (#2458)

Co-authored-by: afdesk <work@afdesk.com>
Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com>
This commit is contained in:
Teppei Fukuda
2022-07-09 19:40:31 +03:00
committed by GitHub
parent 7699153c66
commit 5b7e0a858d
73 changed files with 3663 additions and 3533 deletions

View File

@@ -31,7 +31,6 @@ linters:
- ineffassign
- typecheck
- govet
- errcheck
- varcheck
- deadcode
- revive

View File

@@ -1,8 +1,6 @@
package main
import (
"os"
"github.com/aquasecurity/trivy/pkg/commands"
"github.com/aquasecurity/trivy/pkg/log"
)
@@ -13,8 +11,7 @@ var (
func main() {
app := commands.NewApp(version)
err := app.Run(os.Args)
if err != nil {
if err := app.Execute(); err != nil {
log.Fatal(err)
}
}

41
go.mod
View File

@@ -36,30 +36,37 @@ require (
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
github.com/knqyf263/go-rpm-version v0.0.0-20220614171824-631e686d1075
github.com/kylelemons/godebug v1.1.0
github.com/liamg/memoryfs v1.4.2
github.com/liamg/tml v0.6.0
github.com/mailru/easyjson v0.7.7
github.com/masahiro331/go-mvn-version v0.0.0-20210429150710-d3157d602a08
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/open-policy-agent/opa v0.41.0
github.com/owenrumney/go-sarif/v2 v2.1.1
github.com/package-url/packageurl-go v0.1.1-0.20220203205134-d70459300c8a
github.com/samber/lo v1.21.0
github.com/samber/lo v1.24.0
github.com/sosedoff/gitkit v0.3.0
github.com/spf13/cobra v1.4.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.8.1
github.com/stretchr/testify v1.8.0
github.com/testcontainers/testcontainers-go v0.13.0
github.com/tetratelabs/wazero v0.0.0-20220701105919-891761ac1ee2
github.com/twitchtv/twirp v8.1.2+incompatible
github.com/urfave/cli/v2 v2.8.1
github.com/xlab/treeprint v1.1.0
go.etcd.io/bbolt v1.3.6
go.uber.org/zap v1.21.0
golang.org/x/exp v0.0.0-20220407100705-7b9b53b0aca4
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df
google.golang.org/protobuf v1.28.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
)
require (
cloud.google.com/go v0.99.0 // indirect
cloud.google.com/go v0.100.2 // indirect
cloud.google.com/go/compute v1.6.1 // indirect
cloud.google.com/go/iam v0.3.0 // indirect
cloud.google.com/go/storage v1.14.0 // indirect
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
@@ -108,7 +115,6 @@ require (
github.com/containerd/stargz-snapshotter/estargz v0.11.4 // indirect
github.com/containerd/ttrpc v1.1.1-0.20220420014843-944ef4a40df3 // indirect
github.com/containerd/typeurl v1.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
@@ -124,6 +130,7 @@ require (
github.com/emirpasic/gods v1.12.0 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
@@ -146,7 +153,7 @@ require (
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
@@ -157,6 +164,7 @@ require (
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.4.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/hcl/v2 v2.12.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.13 // indirect
@@ -175,11 +183,9 @@ require (
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/liamg/iamgo v0.0.6 // indirect
github.com/liamg/jfather v0.0.7 // indirect
github.com/liamg/memoryfs v1.4.2
github.com/liamg/tml v0.6.0
github.com/lib/pq v1.10.4 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
@@ -188,6 +194,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/buildkit v0.10.3
github.com/moby/locker v1.0.1 // indirect
@@ -208,6 +215,7 @@ require (
github.com/opencontainers/runtime-spec v1.0.3-0.20220311020903-6969a0a09ab1 // indirect
github.com/opencontainers/selinux v1.10.1 // indirect
github.com/owenrumney/squealer v1.0.1-0.20220510063705-c0be93f0edea // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -220,16 +228,16 @@ require (
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rubenv/sql-migrate v1.1.1 // indirect
github.com/russross/blackfriday v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e
github.com/sergi/go-diff v1.1.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spdx/tools-golang v0.3.0
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/cobra v1.4.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/stretchr/objx v0.4.0 // indirect
github.com/subosito/gotenv v1.4.0 // indirect
github.com/ulikunitz/xz v0.5.8 // indirect
github.com/vbatts/tar-split v0.11.2 // indirect
github.com/vektah/gqlparser/v2 v2.4.4 // indirect
@@ -237,12 +245,10 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yashtewari/glob-intersection v0.1.0 // indirect
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect
github.com/zclconf/go-cty v1.10.0 // indirect
github.com/zclconf/go-cty-yaml v1.0.2 // indirect
go.etcd.io/bbolt v1.3.6
go.opencensus.io v0.23.0 // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
go.uber.org/atomic v1.7.0 // indirect
@@ -250,20 +256,21 @@ require (
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717 // indirect
google.golang.org/api v0.62.0 // indirect
google.golang.org/api v0.81.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f // indirect
google.golang.org/grpc v1.47.0 // indirect
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gotest.tools v2.2.0+incompatible

107
go.sum
View File

@@ -5,6 +5,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
@@ -28,18 +29,26 @@ cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aD
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY=
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y=
cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
cloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc=
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc=
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@@ -454,7 +463,6 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -565,6 +573,7 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897 h1:E52jfcE64UG42SwLmrW0QByONfGynWuzBvm86BoB9z8=
github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
@@ -801,11 +810,15 @@ github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk=
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
@@ -866,6 +879,7 @@ github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.12.0 h1:PsYxySWpMD4KPaoJLnsHwtK5Qptvj/4Q6s0t4sUxZf4=
github.com/hashicorp/hcl/v2 v2.12.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
@@ -957,8 +971,8 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@@ -991,8 +1005,9 @@ github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@@ -1074,6 +1089,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
@@ -1209,6 +1226,8 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
@@ -1221,6 +1240,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
@@ -1278,6 +1298,7 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rubenv/sql-migrate v1.1.1 h1:haR5Hn8hbW9/SpAICrXoZqXnywS7Q5WijwkQENPeNWY=
github.com/rubenv/sql-migrate v1.1.1/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ=
@@ -1285,13 +1306,12 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/samber/lo v1.21.0 h1:FSby8pJQtX4KmyddTCCGhc3JvnnIVrDA+NW37rG+7G8=
github.com/samber/lo v1.21.0/go.mod h1:2I7tgIv8Q1SG2xEIkRq0F2i2zgxVpnyPOP0d3Gj2r+A=
github.com/samber/lo v1.24.0 h1:8BtUIUpAK2UfLv4/yI+1+1ux8brGwjhTpSndNWjRsjs=
github.com/samber/lo v1.24.0/go.mod h1:2I7tgIv8Q1SG2xEIkRq0F2i2zgxVpnyPOP0d3Gj2r+A=
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e h1:NO86zOn5ScSKW8wRbMaSIcjDZUFpWdCQQnexRqZ9h9A=
github.com/saracen/walker v0.0.0-20191201085201-324a081bae7e/go.mod h1:G0Z6yVPru183i2MuRJx1DcR4dgIZtLcTdaaE/pC1BJU=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
@@ -1331,10 +1351,12 @@ github.com/spdx/tools-golang v0.3.0/go.mod h1:RO4Y3IFROJnz+43JKm1YOrbtgQNljW4gAP
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
@@ -1344,6 +1366,7 @@ github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t6
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@@ -1355,6 +1378,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
@@ -1375,6 +1399,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs=
github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
@@ -1400,8 +1426,6 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4=
github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY=
github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
github.com/vektah/gqlparser/v2 v2.4.4 h1:rh9hwZ5Jx9cCq88zXz2YHKmuQBuwY1JErHU8GywFdwE=
@@ -1434,8 +1458,6 @@ github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6Ut
github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk=
github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yashtewari/glob-intersection v0.1.0 h1:6gJvMYQlTDOL3dMsPF6J0+26vwX9MB8/1q3uAdhmTrg=
github.com/yashtewari/glob-intersection v0.1.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -1565,6 +1587,7 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -1677,6 +1700,11 @@ golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -1695,8 +1723,11 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1708,6 +1739,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1829,11 +1861,15 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0=
@@ -1943,8 +1979,10 @@ golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@@ -1977,8 +2015,15 @@ google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.62.0 h1:PhGymJMXfGBzc4lBRmrx9+1w4w2wEzURHNGF/sD/xGc=
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
google.golang.org/api v0.81.0 h1:o8WF5AvfidafWbFjsRyupxyEQJNUWxLZJCK5NXrxZZ8=
google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -2054,11 +2099,24 @@ google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEc
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f h1:hJ/Y5SqPXbarffmAsApliUlcvMU+wScNGfyop4bZm8o=
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
@@ -2093,7 +2151,10 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
@@ -2136,6 +2197,8 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=

View File

@@ -1,5 +1,4 @@
//go:build integration
// +build integration
package integration
@@ -7,7 +6,6 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
@@ -19,10 +17,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
testcontainers "github.com/testcontainers/testcontainers-go"
"github.com/urfave/cli/v2"
"github.com/aquasecurity/trivy/pkg/clock"
"github.com/aquasecurity/trivy/pkg/commands"
"github.com/aquasecurity/trivy/pkg/report"
)
@@ -242,14 +238,14 @@ func TestClientServer(t *testing.T) {
},
}
app, addr, cacheDir := setup(t, setupOptions{})
addr, cacheDir := setup(t, setupOptions{})
for _, c := range tests {
t.Run(c.name, func(t *testing.T) {
osArgs, outputFile := setupClient(t, c.args, addr, cacheDir, c.golden)
// Run Trivy client
err := app.Run(osArgs)
//
err := execute(osArgs)
require.NoError(t, err)
compareReports(t, c.golden, outputFile)
@@ -340,7 +336,7 @@ func TestClientServerWithFormat(t *testing.T) {
report.CustomTemplateFuncMap = map[string]interface{}{}
})
app, addr, cacheDir := setup(t, setupOptions{})
addr, cacheDir := setup(t, setupOptions{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -349,7 +345,7 @@ func TestClientServerWithFormat(t *testing.T) {
osArgs, outputFile := setupClient(t, tt.args, addr, cacheDir, tt.golden)
// Run Trivy client
err := app.Run(osArgs)
err := execute(osArgs)
require.NoError(t, err)
want, err := os.ReadFile(tt.golden)
@@ -386,13 +382,13 @@ func TestClientServerWithCycloneDX(t *testing.T) {
},
}
app, addr, cacheDir := setup(t, setupOptions{})
addr, cacheDir := setup(t, setupOptions{})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
osArgs, outputFile := setupClient(t, tt.args, addr, cacheDir, "")
// Run Trivy client
err := app.Run(osArgs)
err := execute(osArgs)
require.NoError(t, err)
f, err := os.Open(outputFile)
@@ -450,7 +446,7 @@ func TestClientServerWithToken(t *testing.T) {
serverToken := "token"
serverTokenHeader := "Trivy-Token"
app, addr, cacheDir := setup(t, setupOptions{
addr, cacheDir := setup(t, setupOptions{
token: serverToken,
tokenHeader: serverTokenHeader,
})
@@ -460,16 +456,14 @@ func TestClientServerWithToken(t *testing.T) {
osArgs, outputFile := setupClient(t, c.args, addr, cacheDir, c.golden)
// Run Trivy client
err := app.Run(osArgs)
err := execute(osArgs)
if c.wantErr != "" {
require.NotNil(t, err, c.name)
require.Error(t, err, c.name)
assert.Contains(t, err.Error(), c.wantErr, c.name)
return
} else {
assert.NoError(t, err, c.name)
}
require.NoError(t, err, c.name)
compareReports(t, c.golden, outputFile)
})
}
@@ -481,7 +475,7 @@ func TestClientServerWithRedis(t *testing.T) {
redisC, addr := setupRedis(t, ctx)
// Set up Trivy server
app, addr, cacheDir := setup(t, setupOptions{cacheBackend: addr})
addr, cacheDir := setup(t, setupOptions{cacheBackend: addr})
t.Cleanup(func() { os.RemoveAll(cacheDir) })
// Test parameters
@@ -494,7 +488,7 @@ func TestClientServerWithRedis(t *testing.T) {
osArgs, outputFile := setupClient(t, testArgs, addr, cacheDir, golden)
// Run Trivy client
err := app.Run(osArgs)
err := execute(osArgs)
require.NoError(t, err)
compareReports(t, golden, outputFile)
@@ -507,8 +501,8 @@ func TestClientServerWithRedis(t *testing.T) {
osArgs, _ := setupClient(t, testArgs, addr, cacheDir, golden)
// Run Trivy client
err := app.Run(osArgs)
require.NotNil(t, err)
err := execute(osArgs)
require.Error(t, err)
assert.Contains(t, err.Error(), "connect: connection refused")
})
}
@@ -519,9 +513,8 @@ type setupOptions struct {
cacheBackend string
}
func setup(t *testing.T, options setupOptions) (*cli.App, string, string) {
func setup(t *testing.T, options setupOptions) (string, string) {
t.Helper()
version := "dev"
// Set up testing DB
cacheDir := initDB(t)
@@ -534,28 +527,21 @@ func setup(t *testing.T, options setupOptions) (*cli.App, string, string) {
addr := fmt.Sprintf("localhost:%d", port)
go func() {
// Setup CLI App
app := commands.NewApp(version)
app.Writer = io.Discard
osArgs := setupServer(addr, options.token, options.tokenHeader, cacheDir, options.cacheBackend)
// Run Trivy server
app.Run(osArgs)
require.NoError(t, execute(osArgs))
}()
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
err = waitPort(ctx, addr)
assert.NoError(t, err)
// Setup CLI App
app := commands.NewApp(version)
app.Writer = io.Discard
return app, addr, cacheDir
return addr, cacheDir
}
func setupServer(addr, token, tokenHeader, cacheDir, cacheBackend string) []string {
osArgs := []string{"trivy", "--cache-dir", cacheDir, "server", "--skip-update", "--listen", addr}
osArgs := []string{"--cache-dir", cacheDir, "server", "--skip-update", "--listen", addr}
if token != "" {
osArgs = append(osArgs, []string{"--token", token, "--token-header", tokenHeader}...)
}
@@ -573,7 +559,7 @@ func setupClient(t *testing.T, c csArgs, addr string, cacheDir string, golden st
c.RemoteAddrOption = "--server"
}
t.Helper()
osArgs := []string{"trivy", "--cache-dir", cacheDir, c.Command, c.RemoteAddrOption, "http://" + addr}
osArgs := []string{"--cache-dir", cacheDir, c.Command, c.RemoteAddrOption, "http://" + addr}
if c.Format != "" {
osArgs = append(osArgs, "--format", c.Format)

View File

@@ -15,8 +15,6 @@ import (
"github.com/docker/docker/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/commands"
)
func TestDockerEngine(t *testing.T) {
@@ -233,16 +231,14 @@ func TestDockerEngine(t *testing.T) {
tmpDir := t.TempDir()
output := filepath.Join(tmpDir, "result.json")
// run trivy
app := commands.NewApp("dev")
trivyArgs := []string{"trivy", "--cache-dir", cacheDir, "image",
osArgs := []string{"--cache-dir", cacheDir, "image",
"--skip-update", "--format=json", "--output", output}
if tt.ignoreUnfixed {
trivyArgs = append(trivyArgs, "--ignore-unfixed")
osArgs = append(osArgs, "--ignore-unfixed")
}
if len(tt.severity) != 0 {
trivyArgs = append(trivyArgs,
osArgs = append(osArgs,
[]string{"--severity", strings.Join(tt.severity, ",")}...,
)
}
@@ -252,11 +248,12 @@ func TestDockerEngine(t *testing.T) {
assert.NoError(t, err, "failed to write .trivyignore")
defer os.Remove(trivyIgnore)
}
trivyArgs = append(trivyArgs, tt.input)
osArgs = append(osArgs, tt.input)
err = app.Run(trivyArgs)
// Run Trivy
err = execute(osArgs)
if tt.wantErr != "" {
require.NotNil(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
return
}

View File

@@ -4,15 +4,13 @@
package integration
import (
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/aquasecurity/trivy/pkg/commands"
"github.com/stretchr/testify/require"
)
func TestFilesystem(t *testing.T) {
@@ -145,7 +143,7 @@ func TestFilesystem(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
osArgs := []string{
"trivy", "--cache-dir", cacheDir, "fs", "--skip-db-update", "--skip-policy-update",
"-q", "--cache-dir", cacheDir, "fs", "--skip-db-update", "--skip-policy-update",
"--format", "json", "--offline-scan", "--security-checks", tt.args.securityChecks,
}
@@ -189,12 +187,9 @@ func TestFilesystem(t *testing.T) {
osArgs = append(osArgs, "--output", outputFile)
osArgs = append(osArgs, tt.args.input)
// Setup CLI App
app := commands.NewApp("dev")
app.Writer = io.Discard
// Run "trivy fs"
assert.Nil(t, app.Run(osArgs))
err := execute(osArgs)
require.NoError(t, err)
// Compare want and got
compareReports(t, tt.golden, outputFile)

View File

@@ -6,6 +6,7 @@ import (
"context"
"encoding/json"
"flag"
"io"
"net"
"os"
"path/filepath"
@@ -18,6 +19,7 @@ import (
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy-db/pkg/metadata"
"github.com/aquasecurity/trivy/pkg/commands"
"github.com/aquasecurity/trivy/pkg/dbtest"
"github.com/aquasecurity/trivy/pkg/types"
)
@@ -120,6 +122,16 @@ func readReport(t *testing.T, filePath string) types.Report {
return report
}
func execute(osArgs []string) error {
// Setup CLI App
app := commands.NewApp("dev")
app.SetOut(io.Discard)
// Run Trivy
app.SetArgs(osArgs)
return app.Execute()
}
func compareReports(t *testing.T, wantFile, gotFile string) {
want := readReport(t, wantFile)
got := readReport(t, gotFile)

View File

@@ -3,7 +3,6 @@
package integration
import (
"io"
"os"
"path/filepath"
"testing"
@@ -11,7 +10,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/commands"
"github.com/aquasecurity/trivy/pkg/module"
"github.com/aquasecurity/trivy/pkg/utils"
)
@@ -48,13 +46,9 @@ func TestModule(t *testing.T) {
filepath.Join(moduleDir, "spring4shell.wasm"))
require.NoError(t, err)
// Setup CLI App
app := commands.NewApp("dev")
app.Writer = io.Discard
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
osArgs := []string{"trivy", "--cache-dir", cacheDir, "image", "--ignore-unfixed", "--format", "json",
osArgs := []string{"--cache-dir", cacheDir, "image", "--ignore-unfixed", "--format", "json",
"--skip-update", "--offline-scan", "--input", tt.input}
// Set up the output file
@@ -66,7 +60,8 @@ func TestModule(t *testing.T) {
osArgs = append(osArgs, []string{"--output", outputFile}...)
// Run Trivy
assert.Nil(t, app.Run(osArgs))
err = execute(osArgs)
assert.NoError(t, err)
// Compare want and got
compareReports(t, tt.golden, outputFile)

View File

@@ -27,8 +27,6 @@ import (
"github.com/stretchr/testify/require"
testcontainers "github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
"github.com/aquasecurity/trivy/pkg/commands"
)
const (
@@ -235,15 +233,11 @@ func scan(t *testing.T, imageRef name.Reference, baseDir, goldenFile string, opt
return "", err
}
// Setup CLI App
app := commands.NewApp("dev")
app.Writer = io.Discard
osArgs := []string{"trivy", "--cache-dir", cacheDir, "image", "--format", "json", "--skip-update",
osArgs := []string{"-q", "--cache-dir", cacheDir, "image", "--format", "json", "--skip-update",
"--output", outputFile, imageRef.Name()}
// Run Trivy
if err := app.Run(osArgs); err != nil {
if err := execute(osArgs); err != nil {
return "", err
}
return outputFile, nil

View File

@@ -1,10 +1,8 @@
//go:build integration
// +build integration
package integration
import (
"io"
"os"
"path/filepath"
"testing"
@@ -12,8 +10,6 @@ import (
cdx "github.com/CycloneDX/cyclonedx-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/commands"
)
func TestCycloneDX(t *testing.T) {
@@ -53,7 +49,7 @@ func TestCycloneDX(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
osArgs := []string{
"trivy", "--cache-dir", cacheDir, "sbom", "--skip-db-update", "--format", tt.args.format,
"--cache-dir", cacheDir, "sbom", "-q", "--skip-db-update", "--format", tt.args.format,
}
// Setup the output file
@@ -65,12 +61,9 @@ func TestCycloneDX(t *testing.T) {
osArgs = append(osArgs, "--output", outputFile)
osArgs = append(osArgs, tt.args.input)
// Setup CLI App
app := commands.NewApp("dev")
app.Writer = io.Discard
// Run "trivy sbom"
assert.Nil(t, app.Run(osArgs))
err := execute(osArgs)
assert.NoError(t, err)
// Compare want and got
want := decodeCycloneDX(t, tt.golden)

View File

@@ -1,18 +1,15 @@
//go:build integration
// +build integration
package integration
import (
"io"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/aquasecurity/trivy/pkg/commands"
"github.com/stretchr/testify/require"
)
func TestTar(t *testing.T) {
@@ -264,13 +261,9 @@ func TestTar(t *testing.T) {
// Set a temp dir so that modules will not be loaded
t.Setenv("XDG_DATA_HOME", cacheDir)
// Setup CLI App
app := commands.NewApp("dev")
app.Writer = io.Discard
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
osArgs := []string{"trivy", "--cache-dir", cacheDir, "image", "--format", tt.testArgs.Format, "--skip-update"}
osArgs := []string{"--cache-dir", cacheDir, "image", "-q", "--format", tt.testArgs.Format, "--skip-update"}
if tt.testArgs.IgnoreUnfixed {
osArgs = append(osArgs, "--ignore-unfixed")
@@ -310,7 +303,8 @@ func TestTar(t *testing.T) {
osArgs = append(osArgs, []string{"--output", outputFile}...)
// Run Trivy
assert.Nil(t, app.Run(osArgs))
err := execute(osArgs)
require.NoError(t, err)
// Compare want and got
compareReports(t, tt.golden, outputFile)

File diff suppressed because it is too large Load Diff

View File

@@ -70,37 +70,36 @@ Vulnerability DB:
name string
arguments []string // 1st argument is path to trivy binaries
want string
wantErr string
}{
{
name: "happy path. '-v' flag is used",
arguments: []string{"trivy", "-v", "--cache-dir", "testdata"},
arguments: []string{"-v", "--cache-dir", "testdata"},
want: tableOutput,
},
{
name: "happy path. '-version' flag is used",
arguments: []string{"trivy", "-version", "--cache-dir", "testdata"},
arguments: []string{"--version", "--cache-dir", "testdata"},
want: tableOutput,
},
{
name: "happy path. 'version' command is used",
arguments: []string{"trivy", "--cache-dir", "testdata", "version"},
arguments: []string{"--cache-dir", "testdata", "version"},
want: tableOutput,
},
{
name: "happy path. 'version', '--format json' flags are used",
arguments: []string{"trivy", "--cache-dir", "testdata", "version", "--format", "json"},
arguments: []string{"--cache-dir", "testdata", "version", "--format", "json"},
want: jsonOutput,
},
{
name: "sad path. '-v', '--format json' flags are used",
arguments: []string{"trivy", "-v", "--format", "json"},
wantErr: "flag provided but not defined: -format",
name: "happy path. '-v', '--format json' flags are used",
arguments: []string{"--cache-dir", "testdata", "-v", "--format", "json"},
want: jsonOutput,
},
{
name: "sad path. '-version', '--format json' flags are used",
arguments: []string{"trivy", "-version", "--format", "json"},
wantErr: "flag provided but not defined: -format",
name: "happy path. '--version', '--format json' flags are used",
arguments: []string{"--cache-dir", "testdata", "--version", "--format", "json"},
want: jsonOutput,
},
}
@@ -108,24 +107,12 @@ Vulnerability DB:
t.Run(test.name, func(t *testing.T) {
got := new(bytes.Buffer)
app := NewApp("test")
app.Writer = got
SetOut(got)
app.SetArgs(test.arguments)
err := app.Run(test.arguments)
if test.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), test.wantErr)
return
}
err := app.Execute()
require.NoError(t, err)
assert.Equal(t, test.want, got.String())
})
}
}
func TestNewCommands(t *testing.T) {
NewApp("test")
NewClientCommand()
NewFilesystemCommand()
NewImageCommand()
NewRepositoryCommand()
NewServerCommand()
}

View File

@@ -1,27 +0,0 @@
package artifact
import (
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/types"
)
// ConfigRun runs scan on config files
func ConfigRun(ctx *cli.Context) error {
opt, err := InitOption(ctx)
if err != nil {
return xerrors.Errorf("option error: %w", err)
}
// Disable OS and language analyzers
opt.DisabledAnalyzers = append(analyzer.TypeOSes, analyzer.TypeLanguages...)
// Scan only config files
opt.VulnType = nil
opt.SecurityChecks = []string{types.SecurityCheckConfig}
// Run filesystem command internally
return run(ctx.Context, opt, TargetFilesystem)
}

View File

@@ -1,38 +0,0 @@
package artifact
import (
"context"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/scanner"
)
// filesystemStandaloneScanner initializes a filesystem scanner in standalone mode
func filesystemStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
}
return s, cleanup, nil
}
// filesystemRemoteScanner initializes a filesystem scanner in client/server mode
func filesystemRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeRemoteFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption, conf.ArtifactOption)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
}
return s, cleanup, nil
}
// FilesystemRun runs scan on filesystem for language-specific dependencies and config files
func FilesystemRun(ctx *cli.Context) error {
return Run(ctx, TargetFilesystem)
}
// RootfsRun runs scan on rootfs.
func RootfsRun(ctx *cli.Context) error {
return Run(ctx, TargetRootfs)
}

View File

@@ -1,93 +0,0 @@
package artifact
import (
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/commands/option"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
)
// Option holds the artifact options
type Option struct {
option.GlobalOption
option.ArtifactOption
option.DBOption
option.ImageOption
option.ReportOption
option.CacheOption
option.ConfigOption
option.RemoteOption
option.SbomOption
option.SecretOption
option.KubernetesOption
option.OtherOption
// We don't want to allow disabled analyzers to be passed by users,
// but it differs depending on scanning modes.
DisabledAnalyzers []analyzer.Type
}
// NewOption is the factory method to return options
func NewOption(c *cli.Context) (Option, error) {
gc, err := option.NewGlobalOption(c)
if err != nil {
return Option{}, xerrors.Errorf("failed to initialize global options: %w", err)
}
return Option{
GlobalOption: gc,
ArtifactOption: option.NewArtifactOption(c),
DBOption: option.NewDBOption(c),
ImageOption: option.NewImageOption(c),
ReportOption: option.NewReportOption(c),
CacheOption: option.NewCacheOption(c),
ConfigOption: option.NewConfigOption(c),
RemoteOption: option.NewRemoteOption(c),
SbomOption: option.NewSbomOption(c),
SecretOption: option.NewSecretOption(c),
KubernetesOption: option.NewKubernetesOption(c),
OtherOption: option.NewOtherOption(c),
}, nil
}
// Init initializes the artifact options
func (c *Option) Init() error {
if err := c.initPreScanOptions(); err != nil {
return err
}
// --clear-cache, --download-db-only and --reset don't conduct the scan
if c.skipScan() {
return nil
}
if err := c.ArtifactOption.Init(c.Context, c.Logger); err != nil {
return err
}
return nil
}
func (c *Option) initPreScanOptions() error {
if err := c.ReportOption.Init(c.Context.App.Writer, c.Logger); err != nil {
return err
}
if err := c.DBOption.Init(); err != nil {
return err
}
if err := c.CacheOption.Init(); err != nil {
return err
}
if err := c.SbomOption.Init(c.Context, c.Logger); err != nil {
return err
}
c.RemoteOption.Init(c.Logger)
return nil
}
func (c *Option) skipScan() bool {
if c.ClearCache || c.DownloadDBOnly || c.Reset {
return true
}
return false
}

View File

@@ -1,334 +0,0 @@
package artifact
import (
"flag"
"net/http"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/commands/option"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/types"
)
func TestOption_Init(t *testing.T) {
tests := []struct {
name string
args []string
logs []string
want Option
wantErr string
}{
{
name: "happy path",
args: []string{"--severity", "CRITICAL", "--vuln-type", "os", "--quiet", "alpine:3.10"},
want: Option{
GlobalOption: option.GlobalOption{
Quiet: true,
},
ArtifactOption: option.ArtifactOption{
Target: "alpine:3.10",
},
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
VulnType: []string{types.VulnTypeOS},
SecurityChecks: []string{types.SecurityCheckVulnerability},
Output: os.Stdout,
},
},
},
{
name: "config scanning",
args: []string{"--severity", "CRITICAL", "--security-checks", "config", "--quiet", "alpine:3.10"},
want: Option{
GlobalOption: option.GlobalOption{
Quiet: true,
},
ArtifactOption: option.ArtifactOption{
Target: "alpine:3.10",
},
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckConfig},
Output: os.Stdout,
},
},
},
{
name: "happy path with token and token header",
args: []string{"--server", "http://localhost:8080", "--token", "secret", "--token-header", "X-Trivy-Token", "alpine:3.11"},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
ArtifactOption: option.ArtifactOption{
Target: "alpine:3.11",
},
RemoteOption: option.RemoteOption{
RemoteAddr: "http://localhost:8080",
CustomHeaders: http.Header{
"X-Trivy-Token": []string{"secret"},
},
},
},
},
{
name: "invalid option combination: token and token header without server",
args: []string{"--token", "secret", "--token-header", "X-Trivy-Token", "alpine:3.11"},
logs: []string{
`"--token" can be used only with "--server"`,
},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
ArtifactOption: option.ArtifactOption{
Target: "alpine:3.11",
},
},
},
{
name: "happy path with good custom headers",
args: []string{"--server", "http://localhost:8080", "--custom-headers", "foo:bar", "alpine:3.11"},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
ArtifactOption: option.ArtifactOption{
Target: "alpine:3.11",
},
RemoteOption: option.RemoteOption{
RemoteAddr: "http://localhost:8080",
CustomHeaders: http.Header{
"Foo": []string{"bar"},
}},
},
},
{
name: "happy path with bad custom headers",
args: []string{"--server", "http://localhost:8080", "--custom-headers", "foobaz", "alpine:3.11"},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
ArtifactOption: option.ArtifactOption{
Target: "alpine:3.11",
},
RemoteOption: option.RemoteOption{RemoteAddr: "http://localhost:8080", CustomHeaders: http.Header{}},
},
},
{
name: "happy path: reset",
args: []string{"--reset"},
want: Option{
DBOption: option.DBOption{
Reset: true,
},
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
},
},
{
name: "happy path with an unknown severity",
args: []string{"--severity", "CRITICAL,INVALID", "centos:7"},
logs: []string{
"unknown severity option: unknown severity: INVALID",
},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityUnknown},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
ArtifactOption: option.ArtifactOption{
Target: "centos:7",
},
},
},
{
name: "invalid option combination: --template enabled without --format",
args: []string{"--template", "@contrib/gitlab.tpl", "gitlab/gitlab-ce:12.7.2-ce.0"},
logs: []string{
"'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.",
},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
Template: "@contrib/gitlab.tpl",
},
ArtifactOption: option.ArtifactOption{
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
},
},
},
{
name: "invalid option combination: --template and --format json",
args: []string{"--format", "json", "--template", "@contrib/gitlab.tpl", "gitlab/gitlab-ce:12.7.2-ce.0"},
logs: []string{
"'--template' is ignored because '--format json' is specified. Use '--template' option with '--format template' option.",
},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
Template: "@contrib/gitlab.tpl",
Format: "json",
},
ArtifactOption: option.ArtifactOption{
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
},
},
},
{
name: "json and list all packages",
args: []string{"--format", "json", "--list-all-pkgs", "gitlab/gitlab-ce:12.7.2-ce.0"},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
Format: "json",
ListAllPkgs: true,
},
ArtifactOption: option.ArtifactOption{
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
},
},
},
{
name: "invalid option combination: --format template without --template",
args: []string{"--format", "template", "--severity", "MEDIUM", "gitlab/gitlab-ce:12.7.2-ce.0"},
logs: []string{
"'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.",
},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityMedium},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
Format: "template",
},
ArtifactOption: option.ArtifactOption{
Target: "gitlab/gitlab-ce:12.7.2-ce.0",
},
},
},
{
name: "github enables list-all-pkgs",
args: []string{"--format", "github", "alpine:3.15"},
want: Option{
ReportOption: option.ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Output: os.Stdout,
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
Format: report.FormatGitHub,
ListAllPkgs: true,
},
ArtifactOption: option.ArtifactOption{
Target: "alpine:3.15",
},
},
},
{
name: "sad: skip and download db",
args: []string{"--skip-db-update", "--download-db-only", "alpine:3.10"},
wantErr: "--skip-db-update and --download-db-only options can not be specified both",
},
{
name: "sad: multiple image names",
args: []string{"centos:7", "alpine:3.10"},
logs: []string{
"multiple targets cannot be specified",
},
wantErr: "arguments error",
},
}
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)
set.Bool("quiet", false, "")
set.Bool("no-progress", false, "")
set.Bool("reset", false, "")
set.Bool("skip-db-update", false, "")
set.Bool("download-db-only", false, "")
set.Bool("list-all-pkgs", false, "")
set.String("severity", "CRITICAL", "")
set.String("vuln-type", "os,library", "")
set.String("security-checks", "vuln", "")
set.String("template", "", "")
set.String("format", "", "")
set.String("server", "", "")
set.String("token", "", "")
set.String("token-header", option.DefaultTokenHeader, "")
set.Var(&cli.StringSlice{}, "custom-headers", "")
ctx := cli.NewContext(app, set, nil)
_ = set.Parse(tt.args)
c, err := NewOption(ctx)
require.NoError(t, err, err)
c.GlobalOption.Logger = logger.Sugar()
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 != "":
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
return
default:
assert.NoError(t, err, tt.name)
}
tt.want.GlobalOption.Context = ctx
tt.want.GlobalOption.Logger = logger.Sugar()
assert.Equal(t, tt.want, c, tt.name)
})
}
}

View File

@@ -1,24 +0,0 @@
package artifact
import (
"context"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/scanner"
)
// filesystemStandaloneScanner initializes a repository scanner in standalone mode
func repositoryStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
}
return s, cleanup, nil
}
// RepositoryRun runs scan on repository
func RepositoryRun(ctx *cli.Context) error {
return Run(ctx, TargetRepository)
}

View File

@@ -6,7 +6,6 @@ import (
"os"
"github.com/hashicorp/go-multierror"
"github.com/urfave/cli/v2"
"golang.org/x/exp/slices"
"golang.org/x/xerrors"
@@ -18,6 +17,7 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/secret"
"github.com/aquasecurity/trivy/pkg/fanal/artifact"
"github.com/aquasecurity/trivy/pkg/fanal/cache"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/module"
pkgReport "github.com/aquasecurity/trivy/pkg/report"
@@ -65,19 +65,19 @@ type ScannerConfig struct {
type Runner interface {
// ScanImage scans an image
ScanImage(ctx context.Context, opt Option) (types.Report, error)
ScanImage(ctx context.Context, opts flag.Options) (types.Report, error)
// ScanFilesystem scans a filesystem
ScanFilesystem(ctx context.Context, opt Option) (types.Report, error)
ScanFilesystem(ctx context.Context, opts flag.Options) (types.Report, error)
// ScanRootfs scans rootfs
ScanRootfs(ctx context.Context, opt Option) (types.Report, error)
ScanRootfs(ctx context.Context, opts flag.Options) (types.Report, error)
// ScanRepository scans repository
ScanRepository(ctx context.Context, opt Option) (types.Report, error)
ScanRepository(ctx context.Context, opts flag.Options) (types.Report, error)
// ScanSBOM scans SBOM
ScanSBOM(ctx context.Context, opt Option) (types.Report, error)
ScanSBOM(ctx context.Context, opts flag.Options) (types.Report, error)
// Filter filter a report
Filter(ctx context.Context, opt Option, report types.Report) (types.Report, error)
Filter(ctx context.Context, opts flag.Options, report types.Report) (types.Report, error)
// Report a writes a report
Report(opt Option, report types.Report) error
Report(opts flag.Options, report types.Report) error
// Close closes runner
Close(ctx context.Context) error
}
@@ -93,6 +93,7 @@ type runner struct {
type runnerOption func(*runner)
// WithCacheClient takes a custom cache implementation
// It is useful when Trivy is imported as a library.
func WithCacheClient(c cache.Cache) runnerOption {
return func(r *runner) {
r.cache = c
@@ -101,23 +102,23 @@ func WithCacheClient(c cache.Cache) runnerOption {
// NewRunner initializes Runner that provides scanning functionalities.
// It is possible to return SkipScan and it must be handled by caller.
func NewRunner(cliOption Option, opts ...runnerOption) (Runner, error) {
func NewRunner(ctx context.Context, cliOptions flag.Options, opts ...runnerOption) (Runner, error) {
r := &runner{}
for _, opt := range opts {
opt(r)
}
err := log.InitLogger(cliOption.Debug, cliOption.Quiet)
if err != nil {
return nil, xerrors.Errorf("logger error: %w", err)
}
if err = r.initCache(cliOption); err != nil {
if err := r.initCache(cliOptions); err != nil {
return nil, xerrors.Errorf("cache error: %w", err)
}
// Update the vulnerability database if needed.
if err := r.initDB(cliOptions); err != nil {
return nil, xerrors.Errorf("DB error: %w", err)
}
// Initialize WASM modules
m, err := module.NewManager(cliOption.Context.Context)
m, err := module.NewManager(ctx)
if err != nil {
return nil, xerrors.Errorf("WASM module error: %w", err)
}
@@ -146,46 +147,46 @@ func (r *runner) Close(ctx context.Context) error {
return errs
}
func (r *runner) ScanImage(ctx context.Context, opt Option) (types.Report, error) {
func (r *runner) ScanImage(ctx context.Context, opts flag.Options) (types.Report, error) {
// Disable the lock file scanning
opt.DisabledAnalyzers = analyzer.TypeLockfiles
opts.DisabledAnalyzers = analyzer.TypeLockfiles
var s InitializeScanner
switch {
case opt.Input != "" && opt.RemoteAddr == "":
case opts.Input != "" && opts.ServerAddr == "":
// Scan image tarball in standalone mode
s = archiveStandaloneScanner
case opt.Input != "" && opt.RemoteAddr != "":
case opts.Input != "" && opts.ServerAddr != "":
// Scan image tarball in client/server mode
s = archiveRemoteScanner
case opt.Input == "" && opt.RemoteAddr == "":
case opts.Input == "" && opts.ServerAddr == "":
// Scan container image in standalone mode
s = imageStandaloneScanner
case opt.Input == "" && opt.RemoteAddr != "":
case opts.Input == "" && opts.ServerAddr != "":
// Scan container image in client/server mode
s = imageRemoteScanner
}
return r.scanArtifact(ctx, opt, s)
return r.scanArtifact(ctx, opts, s)
}
func (r *runner) ScanFilesystem(ctx context.Context, opt Option) (types.Report, error) {
func (r *runner) ScanFilesystem(ctx context.Context, opts flag.Options) (types.Report, error) {
// Disable the individual package scanning
opt.DisabledAnalyzers = append(opt.DisabledAnalyzers, analyzer.TypeIndividualPkgs...)
opts.DisabledAnalyzers = append(opts.DisabledAnalyzers, analyzer.TypeIndividualPkgs...)
return r.scanFS(ctx, opt)
return r.scanFS(ctx, opts)
}
func (r *runner) ScanRootfs(ctx context.Context, opt Option) (types.Report, error) {
func (r *runner) ScanRootfs(ctx context.Context, opts flag.Options) (types.Report, error) {
// Disable the lock file scanning
opt.DisabledAnalyzers = append(opt.DisabledAnalyzers, analyzer.TypeLockfiles...)
opts.DisabledAnalyzers = append(opts.DisabledAnalyzers, analyzer.TypeLockfiles...)
return r.scanFS(ctx, opt)
return r.scanFS(ctx, opts)
}
func (r *runner) scanFS(ctx context.Context, opt Option) (types.Report, error) {
func (r *runner) scanFS(ctx context.Context, opts flag.Options) (types.Report, error) {
var s InitializeScanner
if opt.RemoteAddr == "" {
if opts.ServerAddr == "" {
// Scan filesystem in standalone mode
s = filesystemStandaloneScanner
} else {
@@ -193,26 +194,22 @@ func (r *runner) scanFS(ctx context.Context, opt Option) (types.Report, error) {
s = filesystemRemoteScanner
}
return r.scanArtifact(ctx, opt, s)
return r.scanArtifact(ctx, opts, s)
}
func (r *runner) ScanRepository(ctx context.Context, opt Option) (types.Report, error) {
func (r *runner) ScanRepository(ctx context.Context, opts flag.Options) (types.Report, error) {
// Do not scan OS packages
opt.VulnType = []string{types.VulnTypeLibrary}
opts.VulnType = []string{types.VulnTypeLibrary}
// Disable the OS analyzers and individual package analyzers
opt.DisabledAnalyzers = append(analyzer.TypeIndividualPkgs, analyzer.TypeOSes...)
opts.DisabledAnalyzers = append(analyzer.TypeIndividualPkgs, analyzer.TypeOSes...)
return r.scanArtifact(ctx, opt, repositoryStandaloneScanner)
return r.scanArtifact(ctx, opts, repositoryStandaloneScanner)
}
func (r *runner) ScanSBOM(ctx context.Context, opt Option) (types.Report, error) {
// Scan vulnerabilities
opt.ReportOption.VulnType = []string{types.VulnTypeOS, types.VulnTypeLibrary}
opt.ReportOption.SecurityChecks = []string{types.SecurityCheckVulnerability}
func (r *runner) ScanSBOM(ctx context.Context, opts flag.Options) (types.Report, error) {
var s InitializeScanner
if opt.RemoteAddr == "" {
if opts.ServerAddr == "" {
// Scan cycloneDX in standalone mode
s = sbomStandaloneScanner
} else {
@@ -220,16 +217,12 @@ func (r *runner) ScanSBOM(ctx context.Context, opt Option) (types.Report, error)
s = sbomRemoteScanner
}
return r.scanArtifact(ctx, opt, s)
return r.scanArtifact(ctx, opts, s)
}
func (r *runner) scanArtifact(ctx context.Context, opt Option, initializeScanner InitializeScanner) (types.Report, error) {
// Update the vulnerability database if needed.
if err := r.initDB(opt); err != nil {
return types.Report{}, xerrors.Errorf("DB error: %w", err)
}
func (r *runner) scanArtifact(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner) (types.Report, error) {
report, err := scan(ctx, opt, initializeScanner, r.cache)
report, err := scan(ctx, opts, initializeScanner, r.cache)
if err != nil {
return types.Report{}, xerrors.Errorf("scan error: %w", err)
}
@@ -237,13 +230,13 @@ func (r *runner) scanArtifact(ctx context.Context, opt Option, initializeScanner
return report, nil
}
func (r *runner) Filter(ctx context.Context, opt Option, report types.Report) (types.Report, error) {
func (r *runner) Filter(ctx context.Context, opts flag.Options, report types.Report) (types.Report, error) {
results := report.Results
// Filter results
for i := range results {
vulns, misconfSummary, misconfs, secrets, err := result.Filter(ctx, results[i].Vulnerabilities, results[i].Misconfigurations, results[i].Secrets,
opt.Severities, opt.IgnoreUnfixed, opt.IncludeNonFailures, opt.IgnoreFile, opt.IgnorePolicy)
opts.Severities, opts.IgnoreUnfixed, opts.IncludeNonFailures, opts.IgnoreFile, opts.IgnorePolicy)
if err != nil {
return types.Report{}, xerrors.Errorf("unable to filter vulnerabilities: %w", err)
}
@@ -255,16 +248,16 @@ func (r *runner) Filter(ctx context.Context, opt Option, report types.Report) (t
return report, nil
}
func (r *runner) Report(opt Option, report types.Report) error {
func (r *runner) Report(opts flag.Options, report types.Report) error {
if err := pkgReport.Write(report, pkgReport.Option{
AppVersion: opt.GlobalOption.AppVersion,
Format: opt.Format,
Output: opt.Output,
Tree: opt.DependencyTree,
Severities: opt.Severities,
OutputTemplate: opt.Template,
IncludeNonFailures: opt.IncludeNonFailures,
Trace: opt.Trace,
AppVersion: opts.AppVersion,
Format: opts.Format,
Output: opts.Output,
Tree: opts.DependencyTree,
Severities: opts.Severities,
OutputTemplate: opts.Template,
IncludeNonFailures: opts.IncludeNonFailures,
Trace: opts.Trace,
}); err != nil {
return xerrors.Errorf("unable to write results: %w", err)
}
@@ -272,23 +265,23 @@ func (r *runner) Report(opt Option, report types.Report) error {
return nil
}
func (r *runner) initDB(c Option) error {
func (r *runner) initDB(opts flag.Options) error {
// When scanning config files or running as client mode, it doesn't need to download the vulnerability database.
if c.RemoteAddr != "" || !slices.Contains(c.SecurityChecks, types.SecurityCheckVulnerability) {
if opts.ServerAddr != "" || !slices.Contains(opts.SecurityChecks, types.SecurityCheckVulnerability) {
return nil
}
// download the database file
noProgress := c.Quiet || c.NoProgress
if err := operation.DownloadDB(c.AppVersion, c.CacheDir, c.DBRepository, noProgress, c.Insecure, c.SkipDBUpdate); err != nil {
noProgress := opts.Quiet || opts.NoProgress
if err := operation.DownloadDB(opts.AppVersion, opts.CacheDir, opts.DBRepository, noProgress, opts.Insecure, opts.SkipDBUpdate); err != nil {
return err
}
if c.DownloadDBOnly {
if opts.DownloadDBOnly {
return SkipScan
}
if err := db.Init(c.CacheDir); err != nil {
if err := db.Init(opts.CacheDir); err != nil {
return xerrors.Errorf("error in vulnerability DB initialize: %w", err)
}
r.dbOpen = true
@@ -296,58 +289,59 @@ func (r *runner) initDB(c Option) error {
return nil
}
func (r *runner) initCache(c Option) error {
func (r *runner) initCache(opts flag.Options) error {
// Skip initializing cache when custom cache is passed
if r.cache != nil {
return nil
}
// client/server mode
if c.RemoteAddr != "" {
remoteCache := tcache.NewRemoteCache(c.RemoteAddr, c.CustomHeaders, c.Insecure)
if opts.ServerAddr != "" {
remoteCache := tcache.NewRemoteCache(opts.ServerAddr, opts.CustomHeaders, opts.Insecure)
r.cache = tcache.NopCache(remoteCache)
return nil
}
// standalone mode
utils.SetCacheDir(c.CacheDir)
cache, err := operation.NewCache(c.CacheOption)
utils.SetCacheDir(opts.CacheDir)
cacheClient, err := operation.NewCache(opts.CacheOptions)
if err != nil {
return xerrors.Errorf("unable to initialize the cache: %w", err)
}
log.Logger.Debugf("cache dir: %s", utils.CacheDir())
if c.Reset {
defer cache.Close()
if err = cache.Reset(); err != nil {
if opts.Reset {
defer cacheClient.Close()
if err = cacheClient.Reset(); err != nil {
return xerrors.Errorf("cache reset error: %w", err)
}
return SkipScan
}
if c.ClearCache {
defer cache.Close()
if err = cache.ClearArtifacts(); err != nil {
if opts.ClearCache {
defer cacheClient.Close()
if err = cacheClient.ClearArtifacts(); err != nil {
return xerrors.Errorf("cache clear error: %w", err)
}
return SkipScan
}
r.cache = cache
r.cache = cacheClient
return nil
}
// Run performs artifact scanning
func Run(cliCtx *cli.Context, targetKind TargetKind) error {
opt, err := InitOption(cliCtx)
if err != nil {
return xerrors.Errorf("InitOption: %w", err)
}
//func Run(cliCtx *cli.Context, targetKind TargetKind) error {
// opt, err := InitOption(cliCtx)
// if err != nil {
// return xerrors.Errorf("InitOption: %w", err)
// }
//
// return run(cliCtx.Context, opt, targetKind)
//}
return run(cliCtx.Context, opt, targetKind)
}
func run(ctx context.Context, opt Option, targetKind TargetKind) (err error) {
ctx, cancel := context.WithTimeout(ctx, opt.Timeout)
// Run performs artifact scanning
func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err error) {
ctx, cancel := context.WithTimeout(ctx, opts.Timeout)
defer cancel()
defer func() {
@@ -356,7 +350,7 @@ func run(ctx context.Context, opt Option, targetKind TargetKind) (err error) {
}
}()
r, err := NewRunner(opt)
r, err := NewRunner(ctx, opts)
if err != nil {
if errors.Is(err, SkipScan) {
return nil
@@ -368,121 +362,107 @@ func run(ctx context.Context, opt Option, targetKind TargetKind) (err error) {
var report types.Report
switch targetKind {
case TargetContainerImage, TargetImageArchive:
if report, err = r.ScanImage(ctx, opt); err != nil {
if report, err = r.ScanImage(ctx, opts); err != nil {
return xerrors.Errorf("image scan error: %w", err)
}
case TargetFilesystem:
if report, err = r.ScanFilesystem(ctx, opt); err != nil {
if report, err = r.ScanFilesystem(ctx, opts); err != nil {
return xerrors.Errorf("filesystem scan error: %w", err)
}
case TargetRootfs:
if report, err = r.ScanRootfs(ctx, opt); err != nil {
if report, err = r.ScanRootfs(ctx, opts); err != nil {
return xerrors.Errorf("rootfs scan error: %w", err)
}
case TargetRepository:
if report, err = r.ScanRepository(ctx, opt); err != nil {
if report, err = r.ScanRepository(ctx, opts); err != nil {
return xerrors.Errorf("repository scan error: %w", err)
}
case TargetSBOM:
if report, err = r.ScanSBOM(ctx, opt); err != nil {
if report, err = r.ScanSBOM(ctx, opts); err != nil {
return xerrors.Errorf("sbom scan error: %w", err)
}
}
report, err = r.Filter(ctx, opt, report)
report, err = r.Filter(ctx, opts, report)
if err != nil {
return xerrors.Errorf("filter error: %w", err)
}
if err = r.Report(opt, report); err != nil {
if err = r.Report(opts, report); err != nil {
return xerrors.Errorf("report error: %w", err)
}
Exit(opt, report.Results.Failed())
Exit(opts, report.Results.Failed())
return nil
}
func InitOption(ctx *cli.Context) (Option, error) {
opt, err := NewOption(ctx)
if err != nil {
return Option{}, xerrors.Errorf("option error: %w", err)
}
// initialize options
if err = opt.Init(); err != nil {
return Option{}, xerrors.Errorf("option initialize error: %w", err)
}
return opt, nil
}
func disabledAnalyzers(opt Option) []analyzer.Type {
func disabledAnalyzers(opts flag.Options) []analyzer.Type {
// Specified analyzers to be disabled depending on scanning modes
// e.g. The 'image' subcommand should disable the lock file scanning.
analyzers := opt.DisabledAnalyzers
analyzers := opts.DisabledAnalyzers
// It doesn't analyze apk commands by default.
if !opt.ScanRemovedPkgs {
if !opts.ScanRemovedPkgs {
analyzers = append(analyzers, analyzer.TypeApkCommand)
}
// Do not analyze programming language packages when not running in 'library' mode
if !slices.Contains(opt.VulnType, types.VulnTypeLibrary) {
if !slices.Contains(opts.VulnType, types.VulnTypeLibrary) {
analyzers = append(analyzers, analyzer.TypeLanguages...)
}
// Do not perform secret scanning when it is not specified.
if !slices.Contains(opt.SecurityChecks, types.SecurityCheckSecret) {
if !slices.Contains(opts.SecurityChecks, types.SecurityCheckSecret) {
analyzers = append(analyzers, analyzer.TypeSecret)
}
// Do not perform misconfiguration scanning when it is not specified.
if !slices.Contains(opt.SecurityChecks, types.SecurityCheckConfig) {
if !slices.Contains(opts.SecurityChecks, types.SecurityCheckConfig) {
analyzers = append(analyzers, analyzer.TypeConfigFiles...)
}
return analyzers
}
func initScannerConfig(opt Option, cacheClient cache.Cache) (ScannerConfig, types.ScanOptions, error) {
target := opt.Target
if opt.Input != "" {
target = opt.Input
func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfig, types.ScanOptions, error) {
target := opts.Target
if opts.Input != "" {
target = opts.Input
}
scanOptions := types.ScanOptions{
VulnType: opt.VulnType,
SecurityChecks: opt.SecurityChecks,
ScanRemovedPackages: opt.ScanRemovedPkgs, // this is valid only for 'image' subcommand
ListAllPackages: opt.ListAllPkgs,
VulnType: opts.VulnType,
SecurityChecks: opts.SecurityChecks,
ScanRemovedPackages: opts.ScanRemovedPkgs, // this is valid only for 'image' subcommand
ListAllPackages: opts.ListAllPkgs,
}
if slices.Contains(opt.SecurityChecks, types.SecurityCheckVulnerability) {
if slices.Contains(opts.SecurityChecks, types.SecurityCheckVulnerability) {
log.Logger.Info("Vulnerability scanning is enabled")
log.Logger.Debugf("Vulnerability type: %s", scanOptions.VulnType)
}
// ScannerOption is filled only when config scanning is enabled.
var configScannerOptions config.ScannerOption
if slices.Contains(opt.SecurityChecks, types.SecurityCheckConfig) {
if slices.Contains(opts.SecurityChecks, types.SecurityCheckConfig) {
log.Logger.Info("Misconfiguration scanning is enabled")
configScannerOptions = config.ScannerOption{
Trace: opt.Trace,
Namespaces: append(opt.PolicyNamespaces, defaultPolicyNamespaces...),
PolicyPaths: opt.PolicyPaths,
DataPaths: opt.DataPaths,
FilePatterns: opt.FilePatterns,
Trace: opts.Trace,
Namespaces: append(opts.PolicyNamespaces, defaultPolicyNamespaces...),
PolicyPaths: opts.PolicyPaths,
DataPaths: opts.DataPaths,
FilePatterns: opts.FilePatterns,
}
}
// Do not load config file for secret scanning
if slices.Contains(opt.SecurityChecks, types.SecurityCheckSecret) {
if slices.Contains(opts.SecurityChecks, types.SecurityCheckSecret) {
log.Logger.Info("Secret scanning is enabled")
log.Logger.Info("If your scanning is slow, please try '--security-checks vuln' to disable secret scanning")
log.Logger.Infof("Please see also https://aquasecurity.github.io/trivy/%s/docs/secret/scanning/#recommendation for faster secret detection", opt.AppVersion)
log.Logger.Infof("Please see also https://aquasecurity.github.io/trivy/%s/docs/secret/scanning/#recommendation for faster secret detection", opts.AppVersion)
} else {
opt.SecretConfigPath = ""
opts.SecretConfigPath = ""
}
return ScannerConfig{
@@ -490,33 +470,33 @@ func initScannerConfig(opt Option, cacheClient cache.Cache) (ScannerConfig, type
ArtifactCache: cacheClient,
LocalArtifactCache: cacheClient,
RemoteOption: client.ScannerOption{
RemoteURL: opt.RemoteAddr,
CustomHeaders: opt.CustomHeaders,
Insecure: opt.Insecure,
RemoteURL: opts.ServerAddr,
CustomHeaders: opts.CustomHeaders,
Insecure: opts.Insecure,
},
ArtifactOption: artifact.Option{
DisabledAnalyzers: disabledAnalyzers(opt),
SkipFiles: opt.SkipFiles,
SkipDirs: opt.SkipDirs,
InsecureSkipTLS: opt.Insecure,
Offline: opt.OfflineScan,
NoProgress: opt.NoProgress || opt.Quiet,
DisabledAnalyzers: disabledAnalyzers(opts),
SkipFiles: opts.SkipFiles,
SkipDirs: opts.SkipDirs,
InsecureSkipTLS: opts.Insecure,
Offline: opts.OfflineScan,
NoProgress: opts.NoProgress || opts.Quiet,
// For misconfiguration scanning
MisconfScannerOption: configScannerOptions,
// For secret scanning
SecretScannerOption: secret.ScannerOption{
ConfigPath: opt.SecretConfigPath,
ConfigPath: opts.SecretConfigPath,
},
},
}, scanOptions, nil
}
func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner, cacheClient cache.Cache) (
func scan(ctx context.Context, opts flag.Options, initializeScanner InitializeScanner, cacheClient cache.Cache) (
types.Report, error) {
scannerConfig, scanOptions, err := initScannerConfig(opt, cacheClient)
scannerConfig, scanOptions, err := initScannerConfig(opts, cacheClient)
if err != nil {
return types.Report{}, err
}
@@ -534,8 +514,8 @@ func scan(ctx context.Context, opt Option, initializeScanner InitializeScanner,
return report, nil
}
func Exit(c Option, failedResults bool) {
if c.ExitCode != 0 && failedResults {
os.Exit(c.ExitCode)
func Exit(opts flag.Options, failedResults bool) {
if opts.ExitCode != 0 && failedResults {
os.Exit(opts.ExitCode)
}
}

View File

@@ -1,31 +0,0 @@
package artifact
import (
"context"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/scanner"
)
// SBOMRun scans SBOM for vulnerabilities
func SBOMRun(ctx *cli.Context) error {
return Run(ctx, TargetSBOM)
}
func sbomStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeSBOMScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a cycloneDX scanner: %w", err)
}
return s, cleanup, nil
}
func sbomRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeRemoteSBOMScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption, conf.ArtifactOption)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a cycloneDX scanner: %w", err)
}
return s, cleanup, nil
}

View File

@@ -3,7 +3,6 @@ package artifact
import (
"context"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/scanner"
@@ -64,7 +63,47 @@ func archiveRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scan
return s, func() {}, nil
}
// ImageRun runs scan on container image
func ImageRun(ctx *cli.Context) error {
return Run(ctx, TargetContainerImage)
// filesystemStandaloneScanner initializes a filesystem scanner in standalone mode
func filesystemStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
}
return s, cleanup, nil
}
// filesystemRemoteScanner initializes a filesystem scanner in client/server mode
func filesystemRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeRemoteFilesystemScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption, conf.ArtifactOption)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
}
return s, cleanup, nil
}
// filesystemStandaloneScanner initializes a repository scanner in standalone mode
func repositoryStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeRepositoryScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a filesystem scanner: %w", err)
}
return s, cleanup, nil
}
// sbomStandaloneScanner initializes a SBOM scanner in standalone mode
func sbomStandaloneScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeSBOMScanner(ctx, conf.Target, conf.ArtifactCache, conf.LocalArtifactCache, conf.ArtifactOption)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a cycloneDX scanner: %w", err)
}
return s, cleanup, nil
}
// sbomRemoteScanner initializes a SBOM scanner in client/server mode
func sbomRemoteScanner(ctx context.Context, conf ScannerConfig) (scanner.Scanner, func(), error) {
s, cleanup, err := initializeRemoteSBOMScanner(ctx, conf.Target, conf.ArtifactCache, conf.RemoteOption, conf.ArtifactOption)
if err != nil {
return scanner.Scanner{}, func() {}, xerrors.Errorf("unable to initialize a cycloneDX scanner: %w", err)
}
return s, cleanup, nil
}

View File

@@ -1,58 +0,0 @@
package module
import (
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/commands/option"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/module"
)
// Install installs a module
func Install(c *cli.Context) error {
if c.NArg() != 1 {
cli.ShowSubcommandHelpAndExit(c, 1)
}
if err := initLogger(c); err != nil {
return xerrors.Errorf("log initialization error: %w", err)
}
repo := c.Args().First()
if err := module.Install(c.Context, repo, c.Bool("quiet"), c.Bool("insecure")); err != nil {
return xerrors.Errorf("module installation error: %w", err)
}
return nil
}
// Uninstall uninstalls a module
func Uninstall(c *cli.Context) error {
if c.NArg() != 1 {
cli.ShowSubcommandHelpAndExit(c, 1)
}
if err := initLogger(c); err != nil {
return xerrors.Errorf("log initialization error: %w", err)
}
repo := c.Args().First()
if err := module.Uninstall(c.Context, repo); err != nil {
return xerrors.Errorf("module uninstall error: %w", err)
}
return nil
}
func initLogger(ctx *cli.Context) error {
conf, err := option.NewGlobalOption(ctx)
if err != nil {
return xerrors.Errorf("config error: %w", err)
}
if err = log.InitLogger(conf.Debug, conf.Quiet); err != nil {
return xerrors.Errorf("failed to initialize a logger: %w", err)
}
return nil
}

View File

@@ -6,12 +6,15 @@ import (
"os"
"strings"
"github.com/samber/lo"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/go-redis/redis/v8"
"github.com/google/wire"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy-db/pkg/metadata"
"github.com/aquasecurity/trivy/pkg/commands/option"
"github.com/aquasecurity/trivy/pkg/db"
"github.com/aquasecurity/trivy/pkg/fanal/cache"
"github.com/aquasecurity/trivy/pkg/log"
@@ -31,7 +34,7 @@ type Cache struct {
}
// NewCache is the factory method for Cache
func NewCache(c option.CacheOption) (Cache, error) {
func NewCache(c flag.CacheOptions) (Cache, error) {
if strings.HasPrefix(c.CacheBackend, "redis://") {
log.Logger.Infof("Redis cache: %s", c.CacheBackendMasked())
options, err := redis.ParseURL(c.CacheBackend)
@@ -39,7 +42,7 @@ func NewCache(c option.CacheOption) (Cache, error) {
return Cache{}, err
}
if (option.RedisOption{}) != c.RedisOption {
if !lo.IsEmpty(c.RedisOptions) {
caCert, cert, err := utils.GetTLSConfig(c.RedisCACert, c.RedisCert, c.RedisKey)
if err != nil {
return Cache{}, err

View File

@@ -1,54 +0,0 @@
package option
import (
"os"
"time"
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"golang.org/x/xerrors"
)
// ArtifactOption holds the options for an artifact scanning
type ArtifactOption struct {
Input string
Timeout time.Duration
ClearCache bool
SkipDirs []string
SkipFiles []string
OfflineScan bool
// this field is populated in Init()
Target string
}
// NewArtifactOption is the factory method to return artifact option
func NewArtifactOption(c *cli.Context) ArtifactOption {
return ArtifactOption{
Input: c.String("input"),
Timeout: c.Duration("timeout"),
ClearCache: c.Bool("clear-cache"),
SkipFiles: c.StringSlice("skip-files"),
SkipDirs: c.StringSlice("skip-dirs"),
OfflineScan: c.Bool("offline-scan"),
}
}
// Init initialize the CLI context for artifact scanning
func (c *ArtifactOption) Init(ctx *cli.Context, logger *zap.SugaredLogger) (err error) {
if c.Input == "" && ctx.Args().Len() == 0 {
logger.Debug(`trivy requires at least 1 argument or --input option`)
_ = cli.ShowSubcommandHelp(ctx) // nolint: errcheck
os.Exit(0)
} else if ctx.Args().Len() > 1 && ctx.Command.Name != "kubernetes" {
logger.Error(`multiple targets cannot be specified`)
return xerrors.New("arguments error")
}
if c.Input == "" {
c.Target = ctx.Args().First()
}
return nil
}

View File

@@ -1,73 +0,0 @@
package option_test
import (
"flag"
"testing"
"github.com/aquasecurity/trivy/pkg/commands/option"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
)
func TestArtifactOption_Init(t *testing.T) {
tests := []struct {
name string
args []string
logs []string
want option.ArtifactOption
wantErr string
}{
{
name: "happy path",
args: []string{"alpine:3.10"},
want: option.ArtifactOption{
Target: "alpine:3.10",
},
},
{
name: "sad: multiple image names",
args: []string{"centos:7", "alpine:3.10"},
logs: []string{
"multiple targets cannot be specified",
},
wantErr: "arguments error",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
core, obs := observer.New(zap.DebugLevel)
logger := zap.New(core)
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
ctx := cli.NewContext(app, set, nil)
_ = set.Parse(tt.args)
c := option.NewArtifactOption(ctx)
err := c.Init(ctx, logger.Sugar())
// 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 != "":
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
return
default:
assert.NoError(t, err, tt.name)
}
assert.Equal(t, tt.want, c, tt.name)
})
}
}

View File

@@ -1,66 +0,0 @@
package option
import (
"fmt"
"strings"
"time"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
)
// CacheOption holds the options for cache
type CacheOption struct {
CacheBackend string
CacheTTL time.Duration
RedisOption
}
// RedisOption holds the options for redis cache
type RedisOption struct {
RedisCACert string
RedisCert string
RedisKey string
}
// NewCacheOption returns an instance of CacheOption
func NewCacheOption(c *cli.Context) CacheOption {
return CacheOption{
CacheBackend: c.String("cache-backend"),
CacheTTL: c.Duration("cache-ttl"),
RedisOption: RedisOption{
RedisCACert: c.String("redis-ca"),
RedisCert: c.String("redis-cert"),
RedisKey: c.String("redis-key"),
},
}
}
// Init initialize the CacheOption
func (c *CacheOption) Init() error {
// "redis://" or "fs" are allowed for now
// An empty value is also allowed for testability
if !strings.HasPrefix(c.CacheBackend, "redis://") &&
c.CacheBackend != "fs" && c.CacheBackend != "" {
return xerrors.Errorf("unsupported cache backend: %s", c.CacheBackend)
}
// if one of redis option not nil, make sure CA, cert, and key provided
if (RedisOption{}) != c.RedisOption {
if c.RedisCACert == "" || c.RedisCert == "" || c.RedisKey == "" {
return xerrors.Errorf("you must provide CA, cert and key file path when using tls")
}
}
return nil
}
// CacheBackendMasked returns the redis connection string masking credentials
func (c *CacheOption) CacheBackendMasked() string {
endIndex := strings.Index(c.CacheBackend, "@")
if endIndex == -1 {
return c.CacheBackend
}
startIndex := strings.Index(c.CacheBackend, "//")
return fmt.Sprintf("%s****%s", c.CacheBackend[:startIndex+2], c.CacheBackend[endIndex:])
}

View File

@@ -1,127 +0,0 @@
package option_test
import (
"flag"
"testing"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
"github.com/aquasecurity/trivy/pkg/commands/option"
)
func TestNewCacheOption(t *testing.T) {
tests := []struct {
name string
args []string
want option.CacheOption
}{
{
name: "happy path",
args: []string{"--cache-backend", "redis://localhost:6379"},
want: option.CacheOption{
CacheBackend: "redis://localhost:6379",
},
},
{
name: "default",
args: []string{},
want: option.CacheOption{
CacheBackend: "fs",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
set.String("cache-backend", "fs", "")
c := cli.NewContext(app, set, nil)
_ = set.Parse(tt.args)
got := option.NewCacheOption(c)
assert.Equal(t, tt.want, got, tt.name)
})
}
}
func TestCacheOption_Init(t *testing.T) {
type fields struct {
backend string
}
tests := []struct {
name string
fields fields
wantErr string
}{
{
name: "fs",
fields: fields{
backend: "fs",
},
},
{
name: "redis",
fields: fields{
backend: "redis://localhost:6379",
},
},
{
name: "sad path",
fields: fields{
backend: "unknown://",
},
wantErr: "unsupported cache backend: unknown://",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &option.CacheOption{
CacheBackend: tt.fields.backend,
}
err := c.Init()
if tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestCacheOption_CacheBackendMasked(t *testing.T) {
type fields struct {
backend string
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "redis cache backend masked",
fields: fields{
backend: "redis://root:password@localhost:6379",
},
want: "redis://****@localhost:6379",
},
{
name: "redis cache backend masked does nothing",
fields: fields{
backend: "redis://localhost:6379",
},
want: "redis://localhost:6379",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &option.CacheOption{
CacheBackend: tt.fields.backend,
}
assert.Equal(t, tt.want, c.CacheBackendMasked())
})
}
}

View File

@@ -1,31 +0,0 @@
package option
import (
"github.com/urfave/cli/v2"
)
// ConfigOption holds the options for config scanning
type ConfigOption struct {
FilePatterns []string
IncludeNonFailures bool
SkipPolicyUpdate bool
Trace bool
// Rego
PolicyPaths []string
DataPaths []string
PolicyNamespaces []string
}
// NewConfigOption is the factory method to return config scanning options
func NewConfigOption(c *cli.Context) ConfigOption {
return ConfigOption{
IncludeNonFailures: c.Bool("include-non-failures"),
SkipPolicyUpdate: c.Bool("skip-policy-update"),
Trace: c.Bool("trace"),
FilePatterns: c.StringSlice("file-patterns"),
PolicyPaths: c.StringSlice("config-policy"),
DataPaths: c.StringSlice("config-data"),
PolicyNamespaces: c.StringSlice("policy-namespaces"),
}
}

View File

@@ -1,41 +0,0 @@
package option
import (
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/log"
)
// DBOption holds the options for trivy DB
type DBOption struct {
Reset bool
DownloadDBOnly bool
SkipDBUpdate bool
Light bool
NoProgress bool
DBRepository string
}
// NewDBOption is the factory method to return the DBOption
func NewDBOption(c *cli.Context) DBOption {
return DBOption{
Reset: c.Bool("reset"),
DownloadDBOnly: c.Bool("download-db-only"),
SkipDBUpdate: c.Bool("skip-db-update"),
Light: c.Bool("light"),
NoProgress: c.Bool("no-progress"),
DBRepository: c.String("db-repository"),
}
}
// Init initialize the DBOption
func (c *DBOption) Init() (err error) {
if c.SkipDBUpdate && c.DownloadDBOnly {
return xerrors.New("--skip-db-update and --download-db-only options can not be specified both")
}
if c.Light {
log.Logger.Warn("'--light' option is deprecated and will be removed. See also: https://github.com/aquasecurity/trivy/discussions/1649")
}
return nil
}

View File

@@ -1,87 +0,0 @@
package option_test
import (
"flag"
"testing"
"github.com/aquasecurity/trivy/pkg/commands/option"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
)
func TestNewDBOption(t *testing.T) {
tests := []struct {
name string
args []string
want option.DBOption
}{
{
name: "happy path",
args: []string{"--reset", "--skip-db-update"},
want: option.DBOption{
Reset: true,
SkipDBUpdate: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
set.Bool("reset", false, "")
set.Bool("skip-db-update", false, "")
c := cli.NewContext(app, set, nil)
_ = set.Parse(tt.args)
got := option.NewDBOption(c)
assert.Equal(t, tt.want, got, tt.name)
})
}
}
func TestDBOption_Init(t *testing.T) {
type fields struct {
Reset bool
DownloadDBOnly bool
SkipUpdate bool
Light bool
}
tests := []struct {
name string
fields fields
wantErr string
}{
{
name: "happy path",
fields: fields{
Light: true,
},
},
{
name: "sad path",
fields: fields{
DownloadDBOnly: true,
SkipUpdate: true,
},
wantErr: "--skip-db-update and --download-db-only options can not be specified both",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &option.DBOption{
Reset: tt.fields.Reset,
DownloadDBOnly: tt.fields.DownloadDBOnly,
SkipDBUpdate: tt.fields.SkipUpdate,
Light: tt.fields.Light,
}
err := c.Init()
if tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr, err)
} else {
assert.NoError(t, err)
}
})
}
}

View File

@@ -1,40 +0,0 @@
package option
import (
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/log"
)
// GlobalOption holds the global options for trivy
type GlobalOption struct {
Context *cli.Context
Logger *zap.SugaredLogger
AppVersion string
Quiet bool
Debug bool
CacheDir string
}
// NewGlobalOption is the factory method to return GlobalOption
func NewGlobalOption(c *cli.Context) (GlobalOption, error) {
quiet := c.Bool("quiet")
debug := c.Bool("debug")
logger, err := log.NewLogger(debug, quiet)
if err != nil {
return GlobalOption{}, xerrors.New("failed to create a logger")
}
return GlobalOption{
Context: c,
Logger: logger,
AppVersion: c.App.Version,
Quiet: quiet,
Debug: debug,
CacheDir: c.String("cache-dir"),
}, nil
}

View File

@@ -1,45 +0,0 @@
package option_test
import (
"flag"
"testing"
"github.com/aquasecurity/trivy/pkg/commands/option"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
)
func TestNewGlobalConfig(t *testing.T) {
tests := []struct {
name string
args []string
want option.GlobalOption
}{
{
name: "happy path",
args: []string{"--quiet", "--debug"},
want: option.GlobalOption{
Quiet: true,
Debug: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
set.Bool("debug", false, "")
set.Bool("quiet", false, "")
c := cli.NewContext(app, set, nil)
_ = set.Parse(tt.args)
got, err := option.NewGlobalOption(c)
require.NoError(t, err, err)
assert.Equal(t, tt.want.Quiet, got.Quiet, tt.name)
assert.Equal(t, tt.want.Debug, got.Debug, tt.name)
assert.Equal(t, tt.want.CacheDir, got.CacheDir, tt.name)
})
}
}

View File

@@ -1,17 +0,0 @@
package option
import (
"github.com/urfave/cli/v2"
)
// ImageOption holds the options for scanning images
type ImageOption struct {
ScanRemovedPkgs bool
}
// NewImageOption is the factory method to return ImageOption
func NewImageOption(c *cli.Context) ImageOption {
return ImageOption{
ScanRemovedPkgs: c.Bool("removed-pkgs"),
}
}

View File

@@ -1,21 +0,0 @@
package option
import (
"github.com/urfave/cli/v2"
)
// KubernetesOption holds the options for Kubernetes scanning
type KubernetesOption struct {
ClusterContext string
Namespace string
ReportFormat string
}
// NewKubernetesOption is the factory method to return Kubernetes options
func NewKubernetesOption(c *cli.Context) KubernetesOption {
return KubernetesOption{
ClusterContext: c.String("context"),
Namespace: c.String("namespace"),
ReportFormat: c.String("report"),
}
}

View File

@@ -1,14 +0,0 @@
package option
import "github.com/urfave/cli/v2"
type OtherOption struct {
Insecure bool
}
// NewOtherOption is the factory method to return other option
func NewOtherOption(c *cli.Context) OtherOption {
return OtherOption{
Insecure: c.Bool("insecure"),
}
}

View File

@@ -1,81 +0,0 @@
package option
import (
"net/http"
"strings"
"github.com/urfave/cli/v2"
"go.uber.org/zap"
)
const DefaultTokenHeader = "Trivy-Token"
// RemoteOption holds options for client/server
type RemoteOption struct {
RemoteAddr string
customHeaders []string
token string
tokenHeader string
remote string // deprecated
// this field is populated in Init()
CustomHeaders http.Header
}
func NewRemoteOption(c *cli.Context) RemoteOption {
r := RemoteOption{
RemoteAddr: c.String("server"),
customHeaders: c.StringSlice("custom-headers"),
token: c.String("token"),
tokenHeader: c.String("token-header"),
remote: c.String("remote"), // deprecated
}
return r
}
// Init initialize the options for client/server mode
func (c *RemoteOption) Init(logger *zap.SugaredLogger) {
// for testability
defer func() {
c.token = ""
c.tokenHeader = ""
c.remote = ""
c.customHeaders = nil
}()
// for backward compatibility, should be removed in the future
if c.remote != "" {
c.RemoteAddr = c.remote
}
if c.RemoteAddr == "" {
switch {
case len(c.customHeaders) > 0:
logger.Warn(`"--custom-header"" can be used only with "--server"`)
case c.token != "":
logger.Warn(`"--token" can be used only with "--server"`)
case c.tokenHeader != "" && c.tokenHeader != DefaultTokenHeader:
logger.Warn(`'--token-header' can be used only with "--server"`)
}
return
}
c.CustomHeaders = splitCustomHeaders(c.customHeaders)
if c.token != "" {
c.CustomHeaders.Set(c.tokenHeader, c.token)
}
}
func splitCustomHeaders(headers []string) http.Header {
result := make(http.Header)
for _, header := range headers {
// e.g. x-api-token:XXX
s := strings.SplitN(header, ":", 2)
if len(s) != 2 {
continue
}
result.Set(s[0], s[1])
}
return result
}

View File

@@ -1,36 +0,0 @@
package option
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_splitCustomHeaders(t *testing.T) {
type args struct {
headers []string
}
tests := []struct {
name string
args args
want http.Header
}{
{
name: "happy path",
args: args{
headers: []string{"x-api-token:foo bar", "Authorization:user:password"},
},
want: http.Header{
"X-Api-Token": []string{"foo bar"},
"Authorization": []string{"user:password"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := splitCustomHeaders(tt.args.headers)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -1,169 +0,0 @@
package option
import (
"io"
"os"
"strings"
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"golang.org/x/exp/slices"
"golang.org/x/xerrors"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/types"
)
// ReportOption holds the options for reporting scan results
type ReportOption struct {
Format string
Template string
DependencyTree bool
IgnoreFile string
IgnoreUnfixed bool
ExitCode int
IgnorePolicy string
// these variables are not exported
vulnType string
securityChecks string
output string
severities string
// these variables are populated by Init()
VulnType []string
SecurityChecks []string
Output io.Writer
Severities []dbTypes.Severity
ListAllPkgs bool
}
// NewReportOption is the factory method to return ReportOption
func NewReportOption(c *cli.Context) ReportOption {
return ReportOption{
output: c.String("output"),
Format: c.String("format"),
DependencyTree: c.Bool("dependency-tree"),
Template: c.String("template"),
IgnorePolicy: c.String("ignore-policy"),
vulnType: c.String("vuln-type"),
securityChecks: c.String("security-checks"),
severities: c.String("severity"),
IgnoreFile: c.String("ignorefile"),
IgnoreUnfixed: c.Bool("ignore-unfixed"),
ExitCode: c.Int("exit-code"),
ListAllPkgs: c.Bool("list-all-pkgs"),
}
}
// Init initializes the ReportOption
func (c *ReportOption) Init(output io.Writer, logger *zap.SugaredLogger) error {
if c.Template != "" {
if c.Format == "" {
logger.Warn("'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.")
} else if c.Format != "template" {
logger.Warnf("'--template' is ignored because '--format %s' is specified. Use '--template' option with '--format template' option.", c.Format)
}
} else {
if c.Format == "template" {
logger.Warn("'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.")
}
}
// "--list-all-pkgs" option is unavailable with "--format table".
// If user specifies "--list-all-pkgs" with "--format table", we should warn it.
if c.ListAllPkgs && c.Format == "table" {
logger.Warn(`"--list-all-pkgs" cannot be used with "--format table". Try "--format json" or other formats.`)
}
// "--dependency-tree" option is available only with "--format table".
if c.DependencyTree && c.Format != "table" {
logger.Warn(`"--dependency-tree" can be used only with "--format table".`)
}
if c.forceListAllPkgs(logger) {
c.ListAllPkgs = true
}
c.Severities = splitSeverity(logger, c.severities)
if err := c.populateVulnTypes(); err != nil {
return xerrors.Errorf("vuln type: %w", err)
}
if err := c.populateSecurityChecks(); err != nil {
return xerrors.Errorf("security checks: %w", err)
}
// for testability
c.severities = ""
c.vulnType = ""
c.securityChecks = ""
// The output is os.Stdout by default
if c.output != "" {
var err error
if output, err = os.Create(c.output); err != nil {
return xerrors.Errorf("failed to create an output file: %w", err)
}
}
c.Output = output
return nil
}
func (c *ReportOption) populateVulnTypes() error {
if c.vulnType == "" {
return nil
}
for _, v := range strings.Split(c.vulnType, ",") {
if !slices.Contains(types.VulnTypes, v) {
return xerrors.Errorf("unknown vulnerability type (%s)", v)
}
c.VulnType = append(c.VulnType, v)
}
return nil
}
func (c *ReportOption) populateSecurityChecks() error {
if c.securityChecks == "" {
return nil
}
for _, v := range strings.Split(c.securityChecks, ",") {
if !slices.Contains(types.SecurityChecks, v) {
return xerrors.Errorf("unknown security check (%s)", v)
}
c.SecurityChecks = append(c.SecurityChecks, v)
}
return nil
}
func (c *ReportOption) forceListAllPkgs(logger *zap.SugaredLogger) bool {
if slices.Contains(supportedSbomFormats, c.Format) && !c.ListAllPkgs {
logger.Debugf("'github', 'cyclonedx', 'spdx', and 'spdx-json' automatically enables '--list-all-pkgs'.")
return true
}
if c.DependencyTree {
logger.Debugf("'--dependency-tree' enables '--list-all-pkgs'.")
return true
}
return false
}
func splitSeverity(logger *zap.SugaredLogger, severity string) []dbTypes.Severity {
logger.Debugf("Severities: %s", severity)
var severities []dbTypes.Severity
for _, s := range strings.Split(severity, ",") {
severity, err := dbTypes.NewSeverity(s)
if err != nil {
logger.Warnf("unknown severity option: %s", err)
}
severities = append(severities, severity)
}
return severities
}

View File

@@ -1,251 +0,0 @@
package option
import (
"flag"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/types"
)
func TestReportReportConfig_Init(t *testing.T) {
type fields struct {
output string
Format string
Template string
vulnType string
securityChecks string
severities string
IgnoreFile string
IgnoreUnfixed bool
listAllPksgs bool
ExitCode int
VulnType []string
Output *os.File
Severities []dbTypes.Severity
debug bool
}
tests := []struct {
name string
fields fields
args []string
logs []string
want ReportOption
wantErr string
}{
{
name: "happy path",
fields: fields{
severities: "CRITICAL",
vulnType: "os",
securityChecks: "vuln",
},
args: []string{"alpine:3.10"},
want: ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
VulnType: []string{types.VulnTypeOS},
SecurityChecks: []string{types.SecurityCheckVulnerability},
Output: os.Stdout,
},
},
{
name: "happy path with an unknown severity",
fields: fields{
severities: "CRITICAL,INVALID",
vulnType: "os,library",
securityChecks: "config",
},
args: []string{"centos:7"},
logs: []string{
"unknown severity option: unknown severity: INVALID",
},
want: ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical, dbTypes.SeverityUnknown},
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckConfig},
Output: os.Stdout,
},
},
{
name: "happy path with an cyclonedx",
fields: fields{
severities: "CRITICAL",
vulnType: "os,library",
securityChecks: "vuln",
Format: report.FormatCycloneDX,
listAllPksgs: true,
},
args: []string{"centos:7"},
want: ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
Format: report.FormatCycloneDX,
Output: os.Stdout,
ListAllPkgs: true,
},
},
{
name: "happy path with an cyclonedx option list-all-pkgs is false",
fields: fields{
severities: "CRITICAL",
vulnType: "os,library",
securityChecks: "vuln",
Format: "cyclonedx",
listAllPksgs: false,
debug: true,
},
args: []string{"centos:7"},
logs: []string{
"'github', 'cyclonedx', 'spdx', and 'spdx-json' automatically enables '--list-all-pkgs'.",
"Severities: CRITICAL",
},
want: ReportOption{
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
VulnType: []string{types.VulnTypeOS, types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
Format: "cyclonedx",
Output: os.Stdout,
ListAllPkgs: true,
},
},
{
name: "invalid option combination: --template enabled without --format",
fields: fields{
Template: "@contrib/gitlab.tpl",
severities: "LOW",
vulnType: "os",
securityChecks: "vuln",
},
args: []string{"gitlab/gitlab-ce:12.7.2-ce.0"},
logs: []string{
"'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.",
},
want: ReportOption{
Output: os.Stdout,
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
Template: "@contrib/gitlab.tpl",
VulnType: []string{types.VulnTypeOS},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
},
{
name: "invalid option combination: --template and --format json",
fields: fields{
Format: "json",
Template: "@contrib/gitlab.tpl",
severities: "LOW",
vulnType: "os",
securityChecks: "config",
},
args: []string{"gitlab/gitlab-ce:12.7.2-ce.0"},
logs: []string{
"'--template' is ignored because '--format json' is specified. Use '--template' option with '--format template' option.",
},
want: ReportOption{
Format: "json",
Output: os.Stdout,
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
Template: "@contrib/gitlab.tpl",
VulnType: []string{types.VulnTypeOS},
SecurityChecks: []string{types.SecurityCheckConfig},
},
},
{
name: "invalid option combination: --format template without --template",
fields: fields{
Format: "template",
severities: "LOW",
vulnType: "os",
securityChecks: "vuln",
},
args: []string{"gitlab/gitlab-ce:12.7.2-ce.0"},
logs: []string{
"'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.",
},
want: ReportOption{
Format: "template",
Output: os.Stdout,
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
VulnType: []string{types.VulnTypeOS},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
},
{
name: "invalid option combination: --list-all-pkgs with --format table",
fields: fields{
Format: "table",
severities: "LOW",
vulnType: "os",
securityChecks: "vuln",
listAllPksgs: true,
},
args: []string{"centos:7"},
logs: []string{
`"--list-all-pkgs" cannot be used with "--format table". Try "--format json" or other formats.`,
},
want: ReportOption{
Format: "table",
Output: os.Stdout,
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
VulnType: []string{types.VulnTypeOS},
SecurityChecks: []string{types.SecurityCheckVulnerability},
ListAllPkgs: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
level := zap.InfoLevel
if tt.fields.debug {
level = zap.DebugLevel
}
core, obs := observer.New(level)
logger := zap.New(core)
set := flag.NewFlagSet("test", 0)
_ = set.Parse(tt.args)
c := &ReportOption{
output: tt.fields.output,
Format: tt.fields.Format,
Template: tt.fields.Template,
vulnType: tt.fields.vulnType,
securityChecks: tt.fields.securityChecks,
severities: tt.fields.severities,
IgnoreFile: tt.fields.IgnoreFile,
IgnoreUnfixed: tt.fields.IgnoreUnfixed,
ExitCode: tt.fields.ExitCode,
ListAllPkgs: tt.fields.listAllPksgs,
Output: tt.fields.Output,
}
err := c.Init(os.Stdout, logger.Sugar())
// 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 != "":
require.NotNil(t, err)
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
return
}
assert.NoError(t, err, tt.name)
assert.Equal(t, &tt.want, c, tt.name)
})
}
}

View File

@@ -1,41 +0,0 @@
package option
import (
"github.com/urfave/cli/v2"
"go.uber.org/zap"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/report"
)
var supportedSbomFormats = []string{report.FormatCycloneDX, report.FormatSPDX, report.FormatSPDXJSON,
report.FormatGitHub}
// SbomOption holds the options for SBOM generation
type SbomOption struct {
ArtifactType string // deprecated
SbomFormat string // deprecated
}
// NewSbomOption is the factory method to return SBOM options
func NewSbomOption(c *cli.Context) SbomOption {
return SbomOption{
ArtifactType: c.String("artifact-type"),
SbomFormat: c.String("sbom-format"),
}
}
// Init initialize the CLI context for SBOM generation
func (c *SbomOption) Init(ctx *cli.Context, logger *zap.SugaredLogger) error {
if ctx.Command.Name != "sbom" {
return nil
}
if c.ArtifactType != "" || c.SbomFormat != "" {
logger.Error("'trivy sbom' is now for scanning SBOM. " +
"See https://github.com/aquasecurity/trivy/discussions/2407 for the detail")
return xerrors.New("'--artifact-type' and '--sbom-format' are no longer available")
}
return nil
}

View File

@@ -1,17 +0,0 @@
package option
import (
"github.com/urfave/cli/v2"
)
// SecretOption holds the options for secret scanning
type SecretOption struct {
SecretConfigPath string
}
// NewSecretOption is the factory method to return secret options
func NewSecretOption(c *cli.Context) SecretOption {
return SecretOption{
SecretConfigPath: c.String("secret-config"),
}
}

View File

@@ -1,179 +0,0 @@
package plugin
import (
"context"
"fmt"
"os"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/commands/option"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/plugin"
)
// Install installs a plugin
func Install(c *cli.Context) error {
if c.NArg() != 1 {
cli.ShowSubcommandHelpAndExit(c, 1)
}
if err := initLogger(c); err != nil {
return xerrors.Errorf("initialize error: %w", err)
}
url := c.Args().First()
if _, err := plugin.Install(c.Context, url, true); err != nil {
return xerrors.Errorf("plugin install error: %w", err)
}
return nil
}
// Uninstall uninstalls the plugin
func Uninstall(c *cli.Context) error {
if c.NArg() != 1 {
cli.ShowSubcommandHelpAndExit(c, 1)
}
if err := initLogger(c); err != nil {
return xerrors.Errorf("initialize error: %w", err)
}
pluginName := c.Args().First()
if err := plugin.Uninstall(pluginName); err != nil {
return xerrors.Errorf("plugin uninstall error: %w", err)
}
return nil
}
// Information displays information about the plugin
func Information(c *cli.Context) error {
if c.NArg() != 1 {
cli.ShowSubcommandHelpAndExit(c, 1)
}
if err := initLogger(c); err != nil {
return xerrors.Errorf("initialize logger error: %w", err)
}
pluginName := c.Args().First()
info, err := plugin.Information(pluginName)
if err != nil {
return xerrors.Errorf("plugin information display error: %w", err)
}
if _, err = fmt.Fprintf(os.Stdout, info); err != nil {
return xerrors.Errorf("print error: %w", err)
}
return nil
}
// List displays a list of all of installed plugins
func List(c *cli.Context) error {
if err := initLogger(c); err != nil {
return xerrors.Errorf("initialize error: %w", err)
}
info, err := plugin.List()
if err != nil {
return xerrors.Errorf("plugin list display error: %w", err)
}
if _, err = fmt.Fprintf(os.Stdout, info); err != nil {
return xerrors.Errorf("print error: %w", err)
}
return nil
}
// Update updates an existing plugin
func Update(c *cli.Context) error {
if c.NArg() != 1 {
cli.ShowSubcommandHelpAndExit(c, 1)
}
if err := initLogger(c); err != nil {
return xerrors.Errorf("initialize error: %w", err)
}
pluginName := c.Args().First()
if err := plugin.Update(pluginName); err != nil {
return xerrors.Errorf("plugin update error: %w", err)
}
return nil
}
// Run runs the plugin
func Run(c *cli.Context) error {
if c.NArg() < 1 {
cli.ShowSubcommandHelpAndExit(c, 1)
}
if err := initLogger(c); err != nil {
return xerrors.Errorf("initialize error: %w", err)
}
url := c.Args().First()
args := c.Args().Tail()
return RunWithArgs(c.Context, url, args)
}
// RunWithArgs runs the plugin with arguments
func RunWithArgs(ctx context.Context, url string, args []string) error {
pl, err := plugin.Install(ctx, url, false)
if err != nil {
return xerrors.Errorf("plugin install error: %w", err)
}
if err = pl.Run(ctx, args); err != nil {
return xerrors.Errorf("unable to run %s plugin: %w", pl.Name, err)
}
return nil
}
// LoadCommands loads plugins as subcommands
func LoadCommands() cli.Commands {
var commands cli.Commands
plugins, err := plugin.LoadAll()
if err != nil {
log.Logger.Debugf("no plugins were loaded")
return nil
}
for _, p := range plugins {
p := p
cmd := &cli.Command{
Name: p.Name,
Usage: p.Usage,
Action: func(c *cli.Context) error {
if err := initLogger(c); err != nil {
return xerrors.Errorf("initialize error: %w", err)
}
if err := p.Run(c.Context, c.Args().Slice()); err != nil {
return xerrors.Errorf("plugin error: %w", err)
}
return nil
},
SkipFlagParsing: true,
}
commands = append(commands, cmd)
}
return commands
}
func initLogger(ctx *cli.Context) error {
conf, err := option.NewGlobalOption(ctx)
if err != nil {
return xerrors.Errorf("config error: %w", err)
}
if err = log.InitLogger(conf.Debug, conf.Quiet); err != nil {
return xerrors.Errorf("failed to initialize a logger: %w", err)
}
return nil
}

View File

@@ -1,47 +0,0 @@
package server
import (
"github.com/urfave/cli/v2"
"github.com/aquasecurity/trivy/pkg/commands/option"
)
// Option holds the Trivy config
type Option struct {
option.GlobalOption
option.DBOption
option.CacheOption
option.OtherOption
Listen string
Token string
TokenHeader string
}
// NewOption is the factory method to return config
func NewOption(c *cli.Context) Option {
// the error is ignored because logger is unnecessary
gc, _ := option.NewGlobalOption(c) // nolint: errcheck
return Option{
GlobalOption: gc,
DBOption: option.NewDBOption(c),
CacheOption: option.NewCacheOption(c),
OtherOption: option.NewOtherOption(c),
Listen: c.String("listen"),
Token: c.String("token"),
TokenHeader: c.String("token-header"),
}
}
// Init initializes the config
func (c *Option) Init() (err error) {
if err := c.DBOption.Init(); err != nil {
return err
}
if err := c.CacheOption.Init(); err != nil {
return err
}
return nil
}

View File

@@ -1,108 +0,0 @@
package server_test
import (
"flag"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
"github.com/aquasecurity/trivy/pkg/commands/option"
"github.com/aquasecurity/trivy/pkg/commands/server"
)
func TestNew(t *testing.T) {
tests := []struct {
name string
args []string
want server.Option
}{
{
name: "happy path",
args: []string{"-quiet", "--no-progress", "--reset", "--skip-db-update", "--listen", "localhost:8080"},
want: server.Option{
GlobalOption: option.GlobalOption{
Quiet: true,
},
DBOption: option.DBOption{
Reset: true,
SkipDBUpdate: true,
NoProgress: true,
},
Listen: "localhost:8080",
},
},
}
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-db-update", false, "")
set.String("listen", "", "")
ctx := cli.NewContext(app, set, nil)
_ = set.Parse(tt.args)
tt.want.GlobalOption.Context = ctx
got := server.NewOption(ctx)
assert.Equal(t, tt.want.GlobalOption.Quiet, got.Quiet, tt.name)
assert.Equal(t, tt.want.DBOption, got.DBOption, tt.name)
assert.Equal(t, tt.want.Listen, got.Listen, tt.name)
})
}
}
func TestConfig_Init(t *testing.T) {
tests := []struct {
name string
globalConfig option.GlobalOption
dbConfig option.DBOption
args []string
wantErr string
}{
{
name: "happy path",
args: []string{"alpine:3.10"},
},
{
name: "happy path: reset",
dbConfig: option.DBOption{
Reset: true,
},
args: []string{"alpine:3.10"},
},
{
name: "sad: skip and download db",
dbConfig: option.DBOption{
SkipDBUpdate: true,
DownloadDBOnly: true,
},
args: []string{"alpine:3.10"},
wantErr: "--skip-db-update and --download-db-only options can not be specified both",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &server.Option{
DBOption: tt.dbConfig,
}
err := c.Init()
// test the error
switch {
case tt.wantErr != "":
require.NotNil(t, err, tt.name)
assert.Contains(t, err.Error(), tt.wantErr, tt.name)
return
default:
assert.NoError(t, err, tt.name)
}
})
}
}

View File

@@ -1,11 +1,13 @@
package server
import (
"github.com/urfave/cli/v2"
"context"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy/pkg/commands/operation"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/module"
rpcServer "github.com/aquasecurity/trivy/pkg/rpc/server"
@@ -13,53 +15,45 @@ import (
)
// Run runs the scan
func Run(ctx *cli.Context) error {
return run(NewOption(ctx))
}
func run(c Option) (err error) {
if err = log.InitLogger(c.Debug, c.Quiet); err != nil {
func Run(ctx context.Context, opts flag.Options) (err error) {
if err = log.InitLogger(opts.Debug, opts.Quiet); err != nil {
return xerrors.Errorf("failed to initialize a logger: %w", 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)
cache, err := operation.NewCache(c.CacheOption)
utils.SetCacheDir(opts.CacheDir)
cache, err := operation.NewCache(opts.CacheOptions)
if err != nil {
return xerrors.Errorf("server cache error: %w", err)
}
defer cache.Close()
log.Logger.Debugf("cache dir: %s", utils.CacheDir())
if c.Reset {
if opts.Reset {
return cache.ClearDB()
}
// download the database file
if err = operation.DownloadDB(c.AppVersion, c.CacheDir, c.DBRepository, true, c.Insecure, c.SkipDBUpdate); err != nil {
if err = operation.DownloadDB(opts.AppVersion, opts.CacheDir, opts.DBRepository,
true, opts.Insecure, opts.SkipDBUpdate); err != nil {
return err
}
if c.DownloadDBOnly {
if opts.DownloadDBOnly {
return nil
}
if err = db.Init(c.CacheDir); err != nil {
if err = db.Init(opts.CacheDir); err != nil {
return xerrors.Errorf("error in vulnerability DB initialize: %w", err)
}
// Initialize WASM modules
m, err := module.NewManager(c.Context.Context)
m, err := module.NewManager(ctx)
if err != nil {
return xerrors.Errorf("WASM module error: %w", err)
}
m.Register()
server := rpcServer.NewServer(c.AppVersion, c.Listen, c.CacheDir, c.Token, c.TokenHeader, c.DBRepository)
return server.ListenAndServe(cache, c.Insecure)
server := rpcServer.NewServer(opts.AppVersion, opts.Listen, opts.CacheDir, opts.Token, opts.TokenHeader, opts.DBRepository)
return server.ListenAndServe(cache, opts.Insecure)
}

155
pkg/flag/cache_flags.go Normal file
View File

@@ -0,0 +1,155 @@
package flag
import (
"fmt"
"strings"
"time"
"github.com/samber/lo"
"github.com/spf13/cobra"
"golang.org/x/xerrors"
)
// e.g. config yaml
// cache:
// clear: true
// backend: "redis://localhost:6379"
// redis:
// ca: ca-cert.pem
// cert: cert.pem
// key: key.pem
var (
ClearCacheFlag = Flag{
Name: "clear-cache",
ConfigName: "cache.clear",
Value: false,
Usage: "clear image caches without scanning",
}
CacheBackendFlag = Flag{
Name: "cache-backend",
ConfigName: "cache.backend",
Value: "fs",
Usage: "cache backend (e.g. redis://localhost:6379)",
}
CacheTTLFlag = Flag{
Name: "cache-ttl",
ConfigName: "cache.ttl",
Value: time.Duration(0),
Usage: "cache TTL when using redis as cache backend",
}
RedisCACertFlag = Flag{
Name: "redis-ca",
ConfigName: "cache.redis.ca",
Value: "",
Usage: "redis ca file location, if using redis as cache backend",
}
RedisCertFlag = Flag{
Name: "redis-cert",
ConfigName: "cache.redis.cert",
Value: "",
Usage: "redis certificate file location, if using redis as cache backend",
}
RedisKeyFlag = Flag{
Name: "redis-key",
ConfigName: "cache.redis.key",
Value: "",
Usage: "redis key file location, if using redis as cache backend",
}
)
// CacheFlagGroup composes common printer flag structs used for commands requiring cache logic.
type CacheFlagGroup struct {
ClearCache *Flag
CacheBackend *Flag
CacheTTL *Flag
RedisCACert *Flag
RedisCert *Flag
RedisKey *Flag
}
type CacheOptions struct {
ClearCache bool
CacheBackend string
CacheTTL time.Duration
RedisOptions
}
// RedisOptions holds the options for redis cache
type RedisOptions struct {
RedisCACert string
RedisCert string
RedisKey string
}
// NewCacheFlagGroup returns a default CacheFlagGroup
func NewCacheFlagGroup() *CacheFlagGroup {
return &CacheFlagGroup{
ClearCache: lo.ToPtr(ClearCacheFlag),
CacheBackend: lo.ToPtr(CacheBackendFlag),
CacheTTL: lo.ToPtr(CacheTTLFlag),
RedisCACert: lo.ToPtr(RedisCACertFlag),
RedisCert: lo.ToPtr(RedisCertFlag),
RedisKey: lo.ToPtr(RedisKeyFlag),
}
}
func (f *CacheFlagGroup) flags() []*Flag {
return []*Flag{f.ClearCache, f.CacheBackend, f.CacheTTL, f.RedisCACert, f.RedisCert, f.RedisKey}
}
func (f *CacheFlagGroup) AddFlags(cmd *cobra.Command) {
for _, flag := range f.flags() {
addFlag(cmd, flag)
}
}
func (f *CacheFlagGroup) Bind(cmd *cobra.Command) error {
for _, flag := range f.flags() {
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *CacheFlagGroup) ToOptions() (CacheOptions, error) {
cacheBackend := getString(f.CacheBackend)
redisOptions := RedisOptions{
RedisCACert: getString(f.RedisCACert),
RedisCert: getString(f.RedisCert),
RedisKey: getString(f.RedisKey),
}
// "redis://" or "fs" are allowed for now
// An empty value is also allowed for testability
if !strings.HasPrefix(cacheBackend, "redis://") &&
cacheBackend != "fs" && cacheBackend != "" {
return CacheOptions{}, xerrors.Errorf("unsupported cache backend: %s", cacheBackend)
}
// if one of redis option not nil, make sure CA, cert, and key provided
if !lo.IsEmpty(redisOptions) {
if redisOptions.RedisCACert == "" || redisOptions.RedisCert == "" || redisOptions.RedisKey == "" {
return CacheOptions{}, xerrors.Errorf("you must provide Redis CA, cert and key file path when using TLS")
}
}
return CacheOptions{
ClearCache: getBool(f.ClearCache),
CacheBackend: cacheBackend,
CacheTTL: getDuration(f.CacheTTL),
RedisOptions: redisOptions,
}, nil
}
// CacheBackendMasked returns the redis connection string masking credentials
func (o *CacheOptions) CacheBackendMasked() string {
endIndex := strings.Index(o.CacheBackend, "@")
if endIndex == -1 {
return o.CacheBackend
}
startIndex := strings.Index(o.CacheBackend, "//")
return fmt.Sprintf("%s****%s", o.CacheBackend[:startIndex+2], o.CacheBackend[endIndex:])
}

View File

@@ -0,0 +1,145 @@
package flag_test
import (
"testing"
"time"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/flag"
)
func TestCacheFlagGroup_ToOptions(t *testing.T) {
type fields struct {
ClearCache bool
CacheBackend string
CacheTTL time.Duration
RedisCACert string
RedisCert string
RedisKey string
}
tests := []struct {
name string
fields fields
want flag.CacheOptions
assertion require.ErrorAssertionFunc
}{
{
name: "fs",
fields: fields{
CacheBackend: "fs",
},
want: flag.CacheOptions{
CacheBackend: "fs",
},
assertion: require.NoError,
},
{
name: "redis",
fields: fields{
CacheBackend: "redis://localhost:6379",
},
want: flag.CacheOptions{
CacheBackend: "redis://localhost:6379",
},
assertion: require.NoError,
},
{
name: "redis tls",
fields: fields{
CacheBackend: "redis://localhost:6379",
RedisCACert: "ca-cert.pem",
RedisCert: "cert.pem",
RedisKey: "key.pem",
},
want: flag.CacheOptions{
CacheBackend: "redis://localhost:6379",
RedisOptions: flag.RedisOptions{
RedisCACert: "ca-cert.pem",
RedisCert: "cert.pem",
RedisKey: "key.pem",
},
},
assertion: require.NoError,
},
{
name: "unknown backend",
fields: fields{
CacheBackend: "unknown",
},
assertion: func(t require.TestingT, err error, msgs ...interface{}) {
require.ErrorContains(t, err, "unsupported cache backend")
},
},
{
name: "sad redis tls",
fields: fields{
CacheBackend: "redis://localhost:6379",
RedisCACert: "ca-cert.pem",
},
assertion: func(t require.TestingT, err error, msgs ...interface{}) {
require.ErrorContains(t, err, "you must provide Redis CA")
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
viper.Set(flag.ClearCacheFlag.ConfigName, tt.fields.ClearCache)
viper.Set(flag.CacheBackendFlag.ConfigName, tt.fields.CacheBackend)
viper.Set(flag.CacheTTLFlag.ConfigName, tt.fields.CacheTTL)
viper.Set(flag.RedisCACertFlag.ConfigName, tt.fields.RedisCACert)
viper.Set(flag.RedisCertFlag.ConfigName, tt.fields.RedisCert)
viper.Set(flag.RedisKeyFlag.ConfigName, tt.fields.RedisKey)
f := &flag.CacheFlagGroup{
ClearCache: &flag.ClearCacheFlag,
CacheBackend: &flag.CacheBackendFlag,
CacheTTL: &flag.CacheTTLFlag,
RedisCACert: &flag.RedisCACertFlag,
RedisCert: &flag.RedisCertFlag,
RedisKey: &flag.RedisKeyFlag,
}
got, err := f.ToOptions()
tt.assertion(t, err)
assert.Equalf(t, tt.want, got, "ToOptions()")
})
}
}
func TestCacheOptions_CacheBackendMasked(t *testing.T) {
type fields struct {
backend string
}
tests := []struct {
name string
fields fields
want string
}{
{
name: "redis cache backend masked",
fields: fields{
backend: "redis://root:password@localhost:6379",
},
want: "redis://****@localhost:6379",
},
{
name: "redis cache backend masked does nothing",
fields: fields{
backend: "redis://localhost:6379",
},
want: "redis://localhost:6379",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &flag.CacheOptions{
CacheBackend: tt.fields.backend,
}
assert.Equal(t, tt.want, c.CacheBackendMasked())
})
}
}

122
pkg/flag/db_flags.go Normal file
View File

@@ -0,0 +1,122 @@
package flag
import (
"github.com/samber/lo"
"github.com/spf13/cobra"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/log"
)
const defaultDBRepository = "ghcr.io/aquasecurity/trivy-db"
var (
ResetFlag = Flag{
Name: "reset",
ConfigName: "reset",
Value: false,
Usage: "remove all caches and database",
}
DownloadDBOnlyFlag = Flag{
Name: "download-db-only",
ConfigName: "db.download-only",
Value: false,
Usage: "download/update vulnerability database but don't run a scan",
}
SkipDBUpdateFlag = Flag{
Name: "skip-db-update",
ConfigName: "db.skip-update",
Value: false,
Usage: "skip updating vulnerability database",
}
NoProgressFlag = Flag{
Name: "no-progress",
ConfigName: "db.no-progress",
Value: false,
Usage: "suppress progress bar",
}
DBRepositoryFlag = Flag{
Name: "db-repository",
ConfigName: "db.repository",
Value: defaultDBRepository,
Usage: "OCI repository to retrieve trivy-db from\"",
}
LightFlag = Flag{
Name: "light",
ConfigName: "db.light",
Value: false,
Usage: "deprecated",
}
)
// DBFlagGroup composes common printer flag structs used for commands requiring DB logic.
type DBFlagGroup struct {
Reset *Flag
DownloadDBOnly *Flag
SkipDBUpdate *Flag
NoProgress *Flag
DBRepository *Flag
Light *Flag // deprecated
}
type DBOptions struct {
Reset bool
DownloadDBOnly bool
SkipDBUpdate bool
NoProgress bool
DBRepository string
Light bool // deprecated
}
// NewDBFlagGroup returns a default DBFlagGroup
func NewDBFlagGroup() *DBFlagGroup {
return &DBFlagGroup{
Reset: lo.ToPtr(ResetFlag),
DownloadDBOnly: lo.ToPtr(DownloadDBOnlyFlag),
SkipDBUpdate: lo.ToPtr(SkipDBUpdateFlag),
Light: lo.ToPtr(LightFlag),
NoProgress: lo.ToPtr(NoProgressFlag),
DBRepository: lo.ToPtr(DBRepositoryFlag),
}
}
func (f *DBFlagGroup) flags() []*Flag {
return []*Flag{f.Reset, f.DownloadDBOnly, f.SkipDBUpdate, f.NoProgress, f.DBRepository, f.Light}
}
func (f *DBFlagGroup) AddFlags(cmd *cobra.Command) {
for _, flag := range f.flags() {
addFlag(cmd, flag)
}
}
func (f *DBFlagGroup) Bind(cmd *cobra.Command) error {
for _, flag := range f.flags() {
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *DBFlagGroup) ToOptions() (DBOptions, error) {
skipDBUpdate := getBool(f.SkipDBUpdate)
downloadDBOnly := getBool(f.DownloadDBOnly)
light := getBool(f.Light)
if downloadDBOnly && skipDBUpdate {
return DBOptions{}, xerrors.New("--skip-db-update and --download-db-only options can not be specified both")
}
if light {
log.Logger.Warn("'--light' option is deprecated and will be removed. See also: https://github.com/aquasecurity/trivy/discussions/1649")
}
return DBOptions{
Reset: getBool(f.Reset),
DownloadDBOnly: downloadDBOnly,
SkipDBUpdate: skipDBUpdate,
Light: light,
NoProgress: getBool(f.NoProgress),
DBRepository: getString(f.DBRepository),
}, nil
}

94
pkg/flag/db_flags_test.go Normal file
View File

@@ -0,0 +1,94 @@
package flag_test
import (
"testing"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/log"
)
func TestDBFlagGroup_ToOptions(t *testing.T) {
type fields struct {
SkipDBUpdate bool
DownloadDBOnly bool
Light bool
}
tests := []struct {
name string
fields fields
want flag.DBOptions
wantLogs []string
assertion require.ErrorAssertionFunc
}{
{
name: "happy",
fields: fields{
SkipDBUpdate: true,
DownloadDBOnly: false,
},
want: flag.DBOptions{
SkipDBUpdate: true,
DownloadDBOnly: false,
},
assertion: require.NoError,
},
{
name: "light",
fields: fields{
Light: true,
},
want: flag.DBOptions{
Light: true,
},
wantLogs: []string{
"'--light' option is deprecated and will be removed. See also: https://github.com/aquasecurity/trivy/discussions/1649",
},
assertion: require.NoError,
},
{
name: "sad",
fields: fields{
SkipDBUpdate: true,
DownloadDBOnly: true,
},
assertion: func(t require.TestingT, err error, msgs ...interface{}) {
require.ErrorContains(t, err, "--skip-db-update and --download-db-only options can not be specified both")
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
level := zap.WarnLevel
core, obs := observer.New(level)
log.Logger = zap.New(core).Sugar()
viper.Set(flag.SkipDBUpdateFlag.ConfigName, tt.fields.SkipDBUpdate)
viper.Set(flag.DownloadDBOnlyFlag.ConfigName, tt.fields.DownloadDBOnly)
viper.Set(flag.LightFlag.ConfigName, tt.fields.Light)
// Assert options
f := &flag.DBFlagGroup{
DownloadDBOnly: &flag.DownloadDBOnlyFlag,
SkipDBUpdate: &flag.SkipDBUpdateFlag,
Light: &flag.LightFlag,
}
got, err := f.ToOptions()
tt.assertion(t, err)
assert.Equalf(t, tt.want, got, "ToOptions()")
// Assert log messages
var gotMessages []string
for _, entry := range obs.AllUntimed() {
gotMessages = append(gotMessages, entry.Message)
}
assert.Equal(t, tt.wantLogs, gotMessages, tt.name)
})
}
}

131
pkg/flag/global_flags.go Normal file
View File

@@ -0,0 +1,131 @@
package flag
import (
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/aquasecurity/trivy/pkg/utils"
)
var (
ConfigFileFlag = Flag{
Name: "config",
ConfigName: "config",
Shorthand: "c",
Value: "trivy.yaml",
Usage: "config path",
Persistent: true,
}
ShowVersionFlag = Flag{
Name: "version",
ConfigName: "version",
Shorthand: "v",
Value: false,
Usage: "show version",
Persistent: true,
}
QuietFlag = Flag{
Name: "quiet",
ConfigName: "quiet",
Shorthand: "q",
Value: false,
Usage: "suppress progress bar and log output",
Persistent: true,
}
DebugFlag = Flag{
Name: "debug",
ConfigName: "debug",
Shorthand: "d",
Value: false,
Usage: "debug mode",
Persistent: true,
}
InsecureFlag = Flag{
Name: "insecure",
ConfigName: "insecure",
Value: false,
Usage: "allow insecure server connections when using TLS",
Persistent: true,
}
TimeoutFlag = Flag{
Name: "timeout",
ConfigName: "timeout",
Value: time.Second * 300, // 5 mins
Usage: "timeout",
Persistent: true,
}
CacheDirFlag = Flag{
Name: "cache-dir",
ConfigName: "cache.dir",
Value: utils.DefaultCacheDir(),
Usage: "cache directory",
Persistent: true,
}
)
// GlobalFlagGroup composes global flags
type GlobalFlagGroup struct {
ConfigFile *Flag
ShowVersion *Flag // spf13/cobra can't override the logic of version printing like VersionPrinter in urfave/cli. -v needs to be defined ourselves.
Quiet *Flag
Debug *Flag
Insecure *Flag
Timeout *Flag
CacheDir *Flag
}
// GlobalOptions defines flags and other configuration parameters for all the subcommands
type GlobalOptions struct {
ConfigFile string
ShowVersion bool
Quiet bool
Debug bool
Insecure bool
Timeout time.Duration
CacheDir string
}
func NewGlobalFlagGroup() *GlobalFlagGroup {
return &GlobalFlagGroup{
ConfigFile: &ConfigFileFlag,
ShowVersion: &ShowVersionFlag,
Quiet: &QuietFlag,
Debug: &DebugFlag,
Insecure: &InsecureFlag,
Timeout: &TimeoutFlag,
CacheDir: &CacheDirFlag,
}
}
func (f *GlobalFlagGroup) flags() []*Flag {
return []*Flag{f.ConfigFile, f.ShowVersion, f.Quiet, f.Debug, f.Insecure, f.Timeout, f.CacheDir}
}
func (f *GlobalFlagGroup) AddFlags(cmd *cobra.Command) {
for _, flag := range f.flags() {
addFlag(cmd, flag)
}
}
func (f *GlobalFlagGroup) Bind(cmd *cobra.Command) error {
for _, flag := range f.flags() {
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *GlobalFlagGroup) ToOptions() GlobalOptions {
return GlobalOptions{
ConfigFile: getString(f.ConfigFile),
ShowVersion: getBool(f.ShowVersion),
Quiet: getBool(f.Quiet),
Debug: getBool(f.Debug),
Insecure: getBool(f.Insecure),
Timeout: viper.GetDuration(f.Timeout.ConfigName),
CacheDir: getString(f.CacheDir),
}
}

68
pkg/flag/image_flags.go Normal file
View File

@@ -0,0 +1,68 @@
package flag
import (
"github.com/spf13/cobra"
)
// e.g. config yaml
// image:
// removed-pkgs: true
// input: "/path/to/alpine"
var (
ScanRemovedPkgsFlag = Flag{
Name: "removed-pkgs",
ConfigName: "image.removed-pkgs",
Value: false,
Usage: "detect vulnerabilities of removed packages (only for Alpine)",
}
InputFlag = Flag{
Name: "input",
ConfigName: "image.input",
Value: "",
Usage: "input file path instead of image name",
}
)
type ImageFlagGroup struct {
Input *Flag // local image archive
ScanRemovedPkgs *Flag
}
type ImageOptions struct {
Input string
ScanRemovedPkgs bool
}
func NewImageFlagGroup() *ImageFlagGroup {
return &ImageFlagGroup{
Input: &InputFlag,
ScanRemovedPkgs: &ScanRemovedPkgsFlag,
}
}
func (f *ImageFlagGroup) flags() []*Flag {
return []*Flag{f.Input, f.ScanRemovedPkgs}
}
func (f *ImageFlagGroup) AddFlags(cmd *cobra.Command) {
for _, flag := range f.flags() {
addFlag(cmd, flag)
}
}
func (f *ImageFlagGroup) Bind(cmd *cobra.Command) error {
for _, flag := range f.flags() {
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *ImageFlagGroup) ToOptions() ImageOptions {
return ImageOptions{
Input: getString(f.Input),
ScanRemovedPkgs: getBool(f.ScanRemovedPkgs),
}
}

View File

@@ -0,0 +1,64 @@
package flag
import (
"github.com/samber/lo"
"github.com/spf13/cobra"
)
var (
ClusterContextFlag = Flag{
Name: "context",
ConfigName: "kubernetes.context",
Value: "",
Usage: "specify a context to scan",
}
K8sNamespaceFlag = Flag{
Name: "namespace",
ConfigName: "kubernetes.namespace",
Value: "",
Usage: "specify a namespace to sca",
}
)
type K8sFlagGroup struct {
ClusterContext *Flag
Namespace *Flag
}
type K8sOptions struct {
ClusterContext string
Namespace string
}
func NewK8sFlagGroup() *K8sFlagGroup {
return &K8sFlagGroup{
ClusterContext: lo.ToPtr(ClusterContextFlag),
Namespace: lo.ToPtr(K8sNamespaceFlag),
}
}
func (f *K8sFlagGroup) flags() []*Flag {
return []*Flag{f.ClusterContext, f.Namespace}
}
func (f *K8sFlagGroup) AddFlags(cmd *cobra.Command) {
for _, flag := range f.flags() {
addFlag(cmd, flag)
}
}
func (f *K8sFlagGroup) Bind(cmd *cobra.Command) error {
for _, flag := range f.flags() {
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *K8sFlagGroup) ToOptions() K8sOptions {
return K8sOptions{
ClusterContext: getString(f.ClusterContext),
Namespace: getString(f.Namespace),
}
}

130
pkg/flag/misconf_flags.go Normal file
View File

@@ -0,0 +1,130 @@
package flag
import (
"github.com/samber/lo"
"github.com/spf13/cobra"
"github.com/aquasecurity/trivy/pkg/log"
)
// e.g. config yaml
// misconfiguration:
// trace: true
// config-policy: "custom-policy/policy"
// policy-namespaces: "user"
var (
FilePatternsFlag = Flag{
Name: "file-patterns",
ConfigName: "misconfiguration.file-patterns",
Value: []string{},
Usage: "specify config file patterns, available with '--security-checks config'",
}
IncludeNonFailuresFlag = Flag{
Name: "include-non-failures",
ConfigName: "misconfiguration.include-non-failures",
Value: false,
Usage: "include successes and exceptions, available with '--security-checks config'",
}
SkipPolicyUpdateFlag = Flag{
Name: "skip-policy-update",
ConfigName: "misconfiguration.skip-policy-update",
Value: false,
Usage: "deprecated",
}
TraceFlag = Flag{
Name: "trace",
ConfigName: "misconfiguration.trace",
Value: false,
Usage: "enable more verbose trace output for custom queries",
}
ConfigPolicyFlag = Flag{
Name: "config-policy",
ConfigName: "misconfiguration.config-policy",
Value: []string{},
Usage: "specify paths to the Rego policy files directory, applying config files",
}
ConfigDataFlag = Flag{
Name: "config-data",
ConfigName: "misconfiguration.config-data",
Value: []string{},
Usage: "specify paths from which data for the Rego policies will be recursively loaded",
}
PolicyNamespaceFlag = Flag{
Name: "policy-namespaces",
ConfigName: "misconfiguration.policy-namespaces",
Value: []string{},
Usage: "Rego namespaces",
}
)
// MisconfFlagGroup composes common printer flag structs used for commands providing misconfinguration scanning.
type MisconfFlagGroup struct {
FilePatterns *Flag
IncludeNonFailures *Flag
SkipPolicyUpdate *Flag // deprecated
Trace *Flag
// Rego
PolicyPaths *Flag
DataPaths *Flag
PolicyNamespaces *Flag
}
type MisconfOptions struct {
FilePatterns []string
IncludeNonFailures bool
SkipPolicyUpdate bool // deprecated
Trace bool
// Rego
PolicyPaths []string
DataPaths []string
PolicyNamespaces []string
}
func NewMisconfFlagGroup() *MisconfFlagGroup {
return &MisconfFlagGroup{
FilePatterns: lo.ToPtr(FilePatternsFlag),
IncludeNonFailures: lo.ToPtr(IncludeNonFailuresFlag),
SkipPolicyUpdate: lo.ToPtr(SkipPolicyUpdateFlag),
Trace: lo.ToPtr(TraceFlag),
PolicyPaths: lo.ToPtr(ConfigPolicyFlag),
DataPaths: lo.ToPtr(ConfigDataFlag),
PolicyNamespaces: lo.ToPtr(PolicyNamespaceFlag),
}
}
func (f *MisconfFlagGroup) flags() []*Flag {
return []*Flag{f.FilePatterns, f.IncludeNonFailures, f.SkipPolicyUpdate, f.Trace, f.PolicyPaths, f.DataPaths, f.PolicyNamespaces}
}
func (f *MisconfFlagGroup) AddFlags(cmd *cobra.Command) {
for _, flag := range f.flags() {
addFlag(cmd, flag)
}
}
func (f *MisconfFlagGroup) Bind(cmd *cobra.Command) error {
for _, flag := range f.flags() {
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *MisconfFlagGroup) ToOptions() (MisconfOptions, error) {
skipPolicyUpdateFlag := getBool(f.SkipPolicyUpdate)
if skipPolicyUpdateFlag {
log.Logger.Warn("'--skip-policy-update' is no longer necessary as the built-in policies are embedded into the binary")
}
return MisconfOptions{
FilePatterns: getStringSlice(f.FilePatterns),
IncludeNonFailures: getBool(f.IncludeNonFailures),
Trace: getBool(f.Trace),
PolicyPaths: getStringSlice(f.PolicyPaths),
DataPaths: getStringSlice(f.DataPaths),
PolicyNamespaces: getStringSlice(f.PolicyNamespaces),
}, nil
}

297
pkg/flag/options.go Normal file
View File

@@ -0,0 +1,297 @@
package flag
import (
"io"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
)
type Flag struct {
// Name is for CLI flag and environment variable.
// If this field is empty, it will be available only in config file.
Name string
// ConfigName is a key in config file. It is also used as a key of viper.
ConfigName string
// Shorthand is a shorthand letter.
Shorthand string
// Value is the default value. It must be filled to determine the flag type.
Value interface{}
// Usage explains how to use the flag.
Usage string
// Persistent represents if the flag is persistent
Persistent bool
}
type FlagGroup interface {
AddFlags(cmd *cobra.Command)
Bind(cmd *cobra.Command) error
}
type Flags struct {
CacheFlagGroup *CacheFlagGroup
DBFlagGroup *DBFlagGroup
ImageFlagGroup *ImageFlagGroup
K8sFlagGroup *K8sFlagGroup
MisconfFlagGroup *MisconfFlagGroup
RemoteFlagGroup *RemoteFlagGroup
ReportFlagGroup *ReportFlagGroup
SBOMFlagGroup *SBOMFlagGroup
ScanFlagGroup *ScanFlagGroup
}
// Options holds all the runtime configuration
type Options struct {
GlobalOptions
CacheOptions
DBOptions
ImageOptions
K8sOptions
MisconfOptions
RemoteOptions
ReportOptions
SBOMOptions
ScanOptions
// Trivy's version, not populated via CLI flags
AppVersion string
// We don't want to allow disabled analyzers to be passed by users, but it is necessary for internal use.
DisabledAnalyzers []analyzer.Type
}
func addFlag(cmd *cobra.Command, flag *Flag) {
if flag == nil || flag.Name == "" {
return
}
switch v := flag.Value.(type) {
case int:
if flag.Persistent {
cmd.PersistentFlags().IntP(flag.Name, flag.Shorthand, v, flag.Usage)
} else {
cmd.Flags().IntP(flag.Name, flag.Shorthand, v, flag.Usage)
}
case string:
if flag.Persistent {
cmd.PersistentFlags().StringP(flag.Name, flag.Shorthand, v, flag.Usage)
} else {
cmd.Flags().StringP(flag.Name, flag.Shorthand, v, flag.Usage)
}
case []string:
if flag.Persistent {
cmd.PersistentFlags().StringSliceP(flag.Name, flag.Shorthand, v, flag.Usage)
} else {
cmd.Flags().StringSliceP(flag.Name, flag.Shorthand, v, flag.Usage)
}
case bool:
if flag.Persistent {
cmd.PersistentFlags().BoolP(flag.Name, flag.Shorthand, v, flag.Usage)
} else {
cmd.Flags().BoolP(flag.Name, flag.Shorthand, v, flag.Usage)
}
case time.Duration:
if flag.Persistent {
cmd.PersistentFlags().DurationP(flag.Name, flag.Shorthand, v, flag.Usage)
} else {
cmd.PersistentFlags().DurationP(flag.Name, flag.Shorthand, v, flag.Usage)
}
}
}
func bind(cmd *cobra.Command, flag *Flag) error {
if flag == nil || flag.Name == "" {
return nil
}
if flag.Persistent {
if err := viper.BindPFlag(flag.ConfigName, cmd.PersistentFlags().Lookup(flag.Name)); err != nil {
return err
}
} else {
if err := viper.BindPFlag(flag.ConfigName, cmd.Flags().Lookup(flag.Name)); err != nil {
return err
}
}
// We don't use viper.AutomaticEnv, so we need to add a prefix manually here.
if err := viper.BindEnv(flag.ConfigName, strings.ToUpper("trivy_"+flag.Name)); err != nil {
return err
}
return nil
}
func getString(flag *Flag) string {
if flag == nil {
return ""
}
return viper.GetString(flag.ConfigName)
}
func getStringSlice(flag *Flag) []string {
if flag == nil {
return nil
}
return viper.GetStringSlice(flag.ConfigName)
}
func getInt(flag *Flag) int {
if flag == nil {
return 0
}
return viper.GetInt(flag.ConfigName)
}
func getBool(flag *Flag) bool {
if flag == nil {
return false
}
return viper.GetBool(flag.ConfigName)
}
func getDuration(flag *Flag) time.Duration {
if flag == nil {
return 0
}
return viper.GetDuration(flag.ConfigName)
}
func (f *Flags) groups() []FlagGroup {
var groups []FlagGroup
if f.CacheFlagGroup != nil {
groups = append(groups, f.CacheFlagGroup)
}
if f.DBFlagGroup != nil {
groups = append(groups, f.DBFlagGroup)
}
if f.ImageFlagGroup != nil {
groups = append(groups, f.ImageFlagGroup)
}
if f.K8sFlagGroup != nil {
groups = append(groups, f.K8sFlagGroup)
}
if f.MisconfFlagGroup != nil {
groups = append(groups, f.MisconfFlagGroup)
}
if f.RemoteFlagGroup != nil {
groups = append(groups, f.RemoteFlagGroup)
}
if f.ReportFlagGroup != nil {
groups = append(groups, f.ReportFlagGroup)
}
if f.SBOMFlagGroup != nil {
groups = append(groups, f.SBOMFlagGroup)
}
if f.ScanFlagGroup != nil {
groups = append(groups, f.ScanFlagGroup)
}
return groups
}
func (f *Flags) AddFlags(cmd *cobra.Command) {
for _, group := range f.groups() {
if group == nil {
continue
}
group.AddFlags(cmd)
}
cmd.Flags().SetNormalizeFunc(flagNameNormalize)
}
func (f *Flags) Bind(cmd *cobra.Command) error {
for _, group := range f.groups() {
if group == nil {
continue
}
if err := group.Bind(cmd); err != nil {
return xerrors.Errorf("flag groups: %w", err)
}
}
return nil
}
func (f *Flags) ToOptions(appVersion string, args []string, globalFlags *GlobalFlagGroup, output io.Writer) (Options, error) {
var err error
opts := Options{
AppVersion: appVersion,
GlobalOptions: globalFlags.ToOptions(),
}
if f.CacheFlagGroup != nil {
opts.CacheOptions, err = f.CacheFlagGroup.ToOptions()
if err != nil {
return Options{}, xerrors.Errorf("cache flag error: %w", err)
}
}
if f.DBFlagGroup != nil {
opts.DBOptions, err = f.DBFlagGroup.ToOptions()
if err != nil {
return Options{}, xerrors.Errorf("flag error: %w", err)
}
}
if f.ImageFlagGroup != nil {
opts.ImageOptions = f.ImageFlagGroup.ToOptions()
}
if f.K8sFlagGroup != nil {
opts.K8sOptions = f.K8sFlagGroup.ToOptions()
}
if f.MisconfFlagGroup != nil {
opts.MisconfOptions, err = f.MisconfFlagGroup.ToOptions()
if err != nil {
return Options{}, xerrors.Errorf("misconfiguration flag error: %w", err)
}
}
if f.RemoteFlagGroup != nil {
opts.RemoteOptions = f.RemoteFlagGroup.ToOptions()
}
if f.ReportFlagGroup != nil {
opts.ReportOptions, err = f.ReportFlagGroup.ToOptions(output)
if err != nil {
return Options{}, xerrors.Errorf("report flag error: %w", err)
}
}
if f.SBOMFlagGroup != nil {
opts.SBOMOptions, err = f.SBOMFlagGroup.ToOptions()
if err != nil {
return Options{}, xerrors.Errorf("sbom flag error: %w", err)
}
}
if f.ScanFlagGroup != nil {
opts.ScanOptions = f.ScanFlagGroup.ToOptions(args)
}
return opts, nil
}
func flagNameNormalize(f *pflag.FlagSet, name string) pflag.NormalizedName {
switch name {
case "skip-update":
name = SkipDBUpdateFlag.Name
case "policy":
name = ConfigPolicyFlag.Name
case "data":
name = ConfigDataFlag.Name
case "namespaces":
name = PolicyNamespaceFlag.Name
case "ctx":
name = ClusterContextFlag.Name
}
return pflag.NormalizedName(name)
}

155
pkg/flag/remote_flags.go Normal file
View File

@@ -0,0 +1,155 @@
package flag
import (
"net/http"
"strings"
"github.com/spf13/cobra"
"github.com/aquasecurity/trivy/pkg/log"
)
const (
DefaultTokenHeader = "Trivy-Token"
)
var (
ServerTokenFlag = Flag{
Name: "token",
ConfigName: "server.token",
Value: "",
Usage: "for authentication in client/server mode",
}
ServerTokenHeaderFlag = Flag{
Name: "token-header",
ConfigName: "server.token-header",
Value: DefaultTokenHeader,
Usage: "specify a header name for token in client/server mode",
}
ServerAddrFlag = Flag{
Name: "server",
ConfigName: "server.addr",
Value: "",
Usage: "server address in client mode",
}
ServerCustomHeadersFlag = Flag{
Name: "custom-headers",
ConfigName: "server.custom-headers",
Value: []string{},
Usage: "custom headers in client mode",
}
ServerListenFlag = Flag{
Name: "listen",
ConfigName: "server.listen",
Value: "localhost:4954",
Usage: "listen address in server mode",
}
)
// RemoteFlagGroup composes common printer flag structs
// used for commands requiring reporting logic.
type RemoteFlagGroup struct {
// for client/server
Token *Flag
TokenHeader *Flag
// for client
ServerAddr *Flag
CustomHeaders *Flag
// for server
Listen *Flag
}
type RemoteOptions struct {
Token string
TokenHeader string
ServerAddr string
Listen string
CustomHeaders http.Header
}
func NewClientFlags() *RemoteFlagGroup {
return &RemoteFlagGroup{
Token: &ServerTokenFlag,
TokenHeader: &ServerTokenHeaderFlag,
ServerAddr: &ServerAddrFlag,
CustomHeaders: &ServerCustomHeadersFlag,
}
}
func NewServerFlags() *RemoteFlagGroup {
return &RemoteFlagGroup{
Token: &ServerTokenFlag,
TokenHeader: &ServerTokenHeaderFlag,
Listen: &ServerListenFlag,
}
}
func (f *RemoteFlagGroup) flags() []*Flag {
return []*Flag{f.Token, f.TokenHeader, f.ServerAddr, f.CustomHeaders, f.Listen}
}
func (f *RemoteFlagGroup) Bind(cmd *cobra.Command) error {
for _, flag := range f.flags() {
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *RemoteFlagGroup) AddFlags(cmd *cobra.Command) {
for _, flag := range f.flags() {
addFlag(cmd, flag)
}
}
func (f *RemoteFlagGroup) ToOptions() RemoteOptions {
serverAddr := getString(f.ServerAddr)
customHeaders := splitCustomHeaders(getStringSlice(f.CustomHeaders))
listen := getString(f.Listen)
token := getString(f.Token)
tokenHeader := getString(f.TokenHeader)
if serverAddr == "" && listen == "" {
switch {
case len(customHeaders) > 0:
log.Logger.Warn(`"--custom-header" can be used only with "--server"`)
case token != "":
log.Logger.Warn(`"--token" can be used only with "--server"`)
case tokenHeader != "" && tokenHeader != DefaultTokenHeader:
log.Logger.Warn(`"--token-header" can be used only with "--server"`)
}
}
if token == "" && tokenHeader != DefaultTokenHeader {
log.Logger.Warn(`"--token-header" should be used with "--token"`)
}
if token != "" && tokenHeader != "" {
customHeaders.Set(tokenHeader, token)
}
return RemoteOptions{
Token: token,
TokenHeader: tokenHeader,
ServerAddr: serverAddr,
CustomHeaders: customHeaders,
Listen: listen,
}
}
func splitCustomHeaders(headers []string) http.Header {
result := make(http.Header)
for _, header := range headers {
// e.g. x-api-token:XXX
s := strings.SplitN(header, ":", 2)
if len(s) != 2 {
continue
}
result.Set(s[0], s[1])
}
return result
}

View File

@@ -0,0 +1,127 @@
package flag_test
import (
"net/http"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/log"
)
func TestRemoteFlagGroup_ToOptions(t *testing.T) {
type fields struct {
Server string
CustomHeaders []string
Token string
TokenHeader string
}
tests := []struct {
name string
fields fields
want flag.RemoteOptions
wantLogs []string
}{
{
name: "happy",
fields: fields{
Server: "http://localhost:4954",
CustomHeaders: []string{
"x-api-token:foo bar",
"Authorization:user:password",
},
Token: "token",
TokenHeader: "Trivy-Token",
},
want: flag.RemoteOptions{
ServerAddr: "http://localhost:4954",
CustomHeaders: http.Header{
"X-Api-Token": []string{"foo bar"},
"Authorization": []string{"user:password"},
"Trivy-Token": []string{"token"},
},
Token: "token",
TokenHeader: "Trivy-Token",
},
},
{
name: "custom headers and no server",
fields: fields{
CustomHeaders: []string{
"Authorization:user:password",
},
TokenHeader: "Trivy-Token",
},
want: flag.RemoteOptions{
CustomHeaders: http.Header{
"Authorization": []string{"user:password"},
},
TokenHeader: "Trivy-Token",
},
wantLogs: []string{
`"--custom-header" can be used only with "--server"`,
},
},
{
name: "token and no server",
fields: fields{
Token: "token",
},
want: flag.RemoteOptions{
CustomHeaders: http.Header{},
Token: "token",
},
wantLogs: []string{
`"--token" can be used only with "--server"`,
},
},
{
name: "token header and no token",
fields: fields{
Server: "http://localhost:4954",
TokenHeader: "Non-Default",
},
want: flag.RemoteOptions{
CustomHeaders: http.Header{},
ServerAddr: "http://localhost:4954",
TokenHeader: "Non-Default",
},
wantLogs: []string{
`"--token-header" should be used with "--token"`,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
level := zap.WarnLevel
core, obs := observer.New(level)
log.Logger = zap.New(core).Sugar()
viper.Set(flag.ServerAddrFlag.ConfigName, tt.fields.Server)
viper.Set(flag.ServerCustomHeadersFlag.ConfigName, tt.fields.CustomHeaders)
viper.Set(flag.ServerTokenFlag.ConfigName, tt.fields.Token)
viper.Set(flag.ServerTokenHeaderFlag.ConfigName, tt.fields.TokenHeader)
// Assert options
f := &flag.RemoteFlagGroup{
ServerAddr: &flag.ServerAddrFlag,
CustomHeaders: &flag.ServerCustomHeadersFlag,
Token: &flag.ServerTokenFlag,
TokenHeader: &flag.ServerTokenHeaderFlag,
}
got := f.ToOptions()
assert.Equalf(t, tt.want, got, "ToOptions()")
// Assert log messages
var gotMessages []string
for _, entry := range obs.AllUntimed() {
gotMessages = append(gotMessages, entry.Message)
}
assert.Equal(t, tt.wantLogs, gotMessages, tt.name)
})
}
}

251
pkg/flag/report_flags.go Normal file
View File

@@ -0,0 +1,251 @@
package flag
import (
"io"
"os"
"strings"
"github.com/samber/lo"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
"golang.org/x/xerrors"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/result"
)
// e.g. config yaml
// report:
// format: table
// dependency-tree: true
// exit-code: 1
// severity: HIGH,CRITICAL
var (
FormatFlag = Flag{
Name: "format",
ConfigName: "format",
Shorthand: "f",
Value: report.FormatTable,
Usage: "format (table, json, sarif, template, cyclonedx, spdx, spdx-json, github)",
}
ReportFormatFlag = Flag{
Name: "report",
ConfigName: "report",
Value: "all",
Usage: "specify a report format for the output. (all,summary)",
}
TemplateFlag = Flag{
Name: "template",
ConfigName: "template",
Shorthand: "t",
Value: "",
Usage: "output template",
}
DependencyTreeFlag = Flag{
Name: "dependency-tree",
ConfigName: "dependency-tree",
Value: false,
Usage: "show dependency origin tree (EXPERIMENTAL)",
}
ListAllPkgsFlag = Flag{
Name: "list-all-pkgs",
ConfigName: "list-all-pkgs",
Value: false,
Usage: "enabling the option will output all packages regardless of vulnerability",
}
IgnoreFileFlag = Flag{
Name: "ignorefile",
ConfigName: "ignorefile",
Value: result.DefaultIgnoreFile,
Usage: "specify .trivyignore file",
}
IgnorePolicyFlag = Flag{
Name: "ignore-policy",
ConfigName: "ignore-policy",
Value: "",
Usage: "specify the Rego file path to evaluate each vulnerability",
}
ExitCodeFlag = Flag{
Name: "exit-code",
ConfigName: "exit-code",
Value: 0,
Usage: "specify exit code when any security issues are found",
}
OutputFlag = Flag{
Name: "output",
ConfigName: "output",
Shorthand: "o",
Value: "",
Usage: "output file name",
}
SeverityFlag = Flag{
Name: "severity",
ConfigName: "severity",
Shorthand: "s",
Value: strings.Join(dbTypes.SeverityNames, ","),
Usage: "severities of security issues to be displayed (comma separated)",
}
// Vulnerabilities
IgnoreUnfixedFlag = Flag{
Name: "ignore-unfixed",
ConfigName: "vulnerability.ignore-unfixed",
Value: false,
Usage: "display only fixed vulnerabilities",
}
)
// ReportFlagGroup composes common printer flag structs
// used for commands requiring reporting logic.
type ReportFlagGroup struct {
Format *Flag
ReportFormat *Flag
Template *Flag
DependencyTree *Flag
ListAllPkgs *Flag
IgnoreUnfixed *Flag
IgnoreFile *Flag
IgnorePolicy *Flag
ExitCode *Flag
Output *Flag
Severity *Flag
}
type ReportOptions struct {
Format string
ReportFormat string
Template string
DependencyTree bool
ListAllPkgs bool
IgnoreUnfixed bool
IgnoreFile string
ExitCode int
IgnorePolicy string
Output io.Writer
Severities []dbTypes.Severity
}
func NewReportFlagGroup() *ReportFlagGroup {
return &ReportFlagGroup{
Format: lo.ToPtr(FormatFlag),
ReportFormat: lo.ToPtr(ReportFormatFlag),
Template: lo.ToPtr(TemplateFlag),
DependencyTree: lo.ToPtr(DependencyTreeFlag),
ListAllPkgs: lo.ToPtr(ListAllPkgsFlag),
IgnoreUnfixed: lo.ToPtr(IgnoreUnfixedFlag),
IgnoreFile: lo.ToPtr(IgnoreFileFlag),
IgnorePolicy: lo.ToPtr(IgnorePolicyFlag),
ExitCode: lo.ToPtr(ExitCodeFlag),
Output: lo.ToPtr(OutputFlag),
Severity: lo.ToPtr(SeverityFlag),
}
}
func (f *ReportFlagGroup) flags() []*Flag {
return []*Flag{f.Format, f.ReportFormat, f.Template, f.DependencyTree, f.ListAllPkgs, f.IgnoreUnfixed, f.IgnoreFile, f.IgnorePolicy,
f.ExitCode, f.Output, f.Severity}
}
func (f *ReportFlagGroup) AddFlags(cmd *cobra.Command) {
for _, flag := range f.flags() {
addFlag(cmd, flag)
}
}
func (f *ReportFlagGroup) Bind(cmd *cobra.Command) error {
for _, flag := range f.flags() {
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
format := getString(f.Format)
template := getString(f.Template)
dependencyTree := getBool(f.DependencyTree)
listAllPkgs := getBool(f.ListAllPkgs)
output := getString(f.Output)
if template != "" {
if format == "" {
log.Logger.Warn("'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.")
} else if format != "template" {
log.Logger.Warnf("'--template' is ignored because '--format %s' is specified. Use '--template' option with '--format template' option.", format)
}
} else {
if format == report.FormatTemplate {
log.Logger.Warn("'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.")
}
}
// "--list-all-pkgs" option is unavailable with "--format table".
// If user specifies "--list-all-pkgs" with "--format table", we should warn it.
if listAllPkgs && format == report.FormatTable {
log.Logger.Warn(`"--list-all-pkgs" cannot be used with "--format table". Try "--format json" or other formats.`)
}
// "--dependency-tree" option is available only with "--format table".
if dependencyTree && format != report.FormatTable {
log.Logger.Warn(`"--dependency-tree" can be used only with "--format table".`)
}
// Enable '--list-all-pkgs' if needed
if f.forceListAllPkgs(format, listAllPkgs, dependencyTree) {
listAllPkgs = true
}
if output != "" {
var err error
if out, err = os.Create(output); err != nil {
return ReportOptions{}, xerrors.Errorf("failed to create an output file: %w", err)
}
}
return ReportOptions{
Format: format,
ReportFormat: getString(f.ReportFormat),
Template: template,
DependencyTree: dependencyTree,
ListAllPkgs: listAllPkgs,
IgnoreUnfixed: getBool(f.IgnoreUnfixed),
IgnoreFile: getString(f.IgnoreFile),
ExitCode: getInt(f.ExitCode),
IgnorePolicy: getString(f.IgnorePolicy),
Output: out,
Severities: splitSeverity(getString(f.Severity)),
}, nil
}
func (f *ReportFlagGroup) forceListAllPkgs(format string, listAllPkgs, dependencyTree bool) bool {
if slices.Contains(report.SupportedSBOMFormats, format) && !listAllPkgs {
log.Logger.Debugf("%q automatically enables '--list-all-pkgs'.", report.SupportedSBOMFormats)
return true
}
if dependencyTree && !listAllPkgs {
log.Logger.Debugf("'--dependency-tree' enables '--list-all-pkgs'.")
return true
}
return false
}
func splitSeverity(severity string) []dbTypes.Severity {
if severity == "" {
return nil
}
var severities []dbTypes.Severity
for _, s := range strings.Split(severity, ",") {
sev, err := dbTypes.NewSeverity(s)
if err != nil {
log.Logger.Warnf("unknown severity option: %s", err)
continue
}
severities = append(severities, sev)
}
log.Logger.Debugf("Severities: %q", severities)
return severities
}

View File

@@ -0,0 +1,208 @@
package flag_test
import (
"os"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/report"
)
func TestReportFlagGroup_ToOptions(t *testing.T) {
type fields struct {
format string
template string
dependencyTree bool
listAllPkgs bool
ignoreUnfixed bool
ignoreFile string
exitCode int
ignorePolicy string
output string
severities string
debug bool
}
tests := []struct {
name string
fields fields
want flag.ReportOptions
wantLogs []string
}{
{
name: "happy default (without flags)",
fields: fields{},
want: flag.ReportOptions{
Output: os.Stdout,
},
},
{
name: "happy path with an unknown severity",
fields: fields{
severities: "CRITICAL,INVALID",
},
want: flag.ReportOptions{
Output: os.Stdout,
Severities: []dbTypes.Severity{
dbTypes.SeverityCritical,
},
},
wantLogs: []string{
"unknown severity option: unknown severity: INVALID",
},
},
{
name: "happy path with an cyclonedx",
fields: fields{
severities: "CRITICAL",
format: "cyclonedx",
listAllPkgs: true,
},
want: flag.ReportOptions{
Output: os.Stdout,
Severities: []dbTypes.Severity{dbTypes.SeverityCritical},
Format: report.FormatCycloneDX,
ListAllPkgs: true,
},
},
{
name: "happy path with an cyclonedx option list-all-pkgs is false",
fields: fields{
severities: "CRITICAL",
format: "cyclonedx",
listAllPkgs: false,
debug: true,
},
wantLogs: []string{
`["cyclonedx" "spdx" "spdx-json" "github"] automatically enables '--list-all-pkgs'.`,
`Severities: ["CRITICAL"]`,
},
want: flag.ReportOptions{
Output: os.Stdout,
Severities: []dbTypes.Severity{
dbTypes.SeverityCritical,
},
Format: report.FormatCycloneDX,
ListAllPkgs: true,
},
},
{
name: "invalid option combination: --template enabled without --format",
fields: fields{
template: "@contrib/gitlab.tpl",
severities: "LOW",
},
wantLogs: []string{
"'--template' is ignored because '--format template' is not specified. Use '--template' option with '--format template' option.",
},
want: flag.ReportOptions{
Output: os.Stdout,
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
Template: "@contrib/gitlab.tpl",
},
},
{
name: "invalid option combination: --template and --format json",
fields: fields{
format: "json",
template: "@contrib/gitlab.tpl",
severities: "LOW",
},
wantLogs: []string{
"'--template' is ignored because '--format json' is specified. Use '--template' option with '--format template' option.",
},
want: flag.ReportOptions{
Output: os.Stdout,
Format: "json",
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
Template: "@contrib/gitlab.tpl",
},
},
{
name: "invalid option combination: --format template without --template",
fields: fields{
format: "template",
severities: "LOW",
},
wantLogs: []string{
"'--format template' is ignored because '--template' is not specified. Specify '--template' option when you use '--format template'.",
},
want: flag.ReportOptions{
Output: os.Stdout,
Format: "template",
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
},
},
{
name: "invalid option combination: --list-all-pkgs with --format table",
fields: fields{
format: "table",
severities: "LOW",
listAllPkgs: true,
},
wantLogs: []string{
`"--list-all-pkgs" cannot be used with "--format table". Try "--format json" or other formats.`,
},
want: flag.ReportOptions{
Format: "table",
Output: os.Stdout,
Severities: []dbTypes.Severity{dbTypes.SeverityLow},
ListAllPkgs: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
level := zap.WarnLevel
if tt.fields.debug {
level = zap.DebugLevel
}
core, obs := observer.New(level)
log.Logger = zap.New(core).Sugar()
viper.Set(flag.FormatFlag.ConfigName, tt.fields.format)
viper.Set(flag.TemplateFlag.ConfigName, tt.fields.template)
viper.Set(flag.DependencyTreeFlag.ConfigName, tt.fields.dependencyTree)
viper.Set(flag.ListAllPkgsFlag.ConfigName, tt.fields.listAllPkgs)
viper.Set(flag.IgnoreFileFlag.ConfigName, tt.fields.ignoreFile)
viper.Set(flag.IgnoreUnfixedFlag.ConfigName, tt.fields.ignoreUnfixed)
viper.Set(flag.IgnorePolicyFlag.ConfigName, tt.fields.ignorePolicy)
viper.Set(flag.ExitCodeFlag.ConfigName, tt.fields.exitCode)
viper.Set(flag.OutputFlag.ConfigName, tt.fields.output)
viper.Set(flag.SeverityFlag.ConfigName, tt.fields.severities)
// Assert options
f := &flag.ReportFlagGroup{
Format: &flag.FormatFlag,
Template: &flag.TemplateFlag,
DependencyTree: &flag.DependencyTreeFlag,
ListAllPkgs: &flag.ListAllPkgsFlag,
IgnoreFile: &flag.IgnoreFileFlag,
IgnoreUnfixed: &flag.IgnoreUnfixedFlag,
IgnorePolicy: &flag.IgnorePolicyFlag,
ExitCode: &flag.ExitCodeFlag,
Output: &flag.OutputFlag,
Severity: &flag.SeverityFlag,
}
got, err := f.ToOptions(os.Stdout)
assert.NoError(t, err)
assert.Equalf(t, tt.want, got, "ToOptions()")
// Assert log messages
var gotMessages []string
for _, entry := range obs.AllUntimed() {
gotMessages = append(gotMessages, entry.Message)
}
assert.Equal(t, tt.wantLogs, gotMessages, tt.name)
})
}
}

67
pkg/flag/sbom_flags.go Normal file
View File

@@ -0,0 +1,67 @@
package flag
import (
"github.com/spf13/cobra"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/log"
)
var (
ArtifactTypeFlag = Flag{
Name: "artifact-type",
Value: "",
Usage: "deprecated",
}
SBOMFormatFlag = Flag{
Name: "sbom-format",
Value: "",
Usage: "deprecated",
}
)
type SBOMFlagGroup struct {
ArtifactType *Flag // deprecated
SBOMFormat *Flag // deprecated
}
type SBOMOptions struct {
ArtifactType string // deprecated
SBOMFormat string // deprecated
}
func NewSBOMFlagGroup() *SBOMFlagGroup {
return &SBOMFlagGroup{
ArtifactType: &ArtifactTypeFlag,
SBOMFormat: &SBOMFormatFlag,
}
}
func (f *SBOMFlagGroup) AddFlags(cmd *cobra.Command) {
if f.ArtifactType != nil {
cmd.Flags().String(ArtifactTypeFlag.Name, "", "deprecated")
cmd.Flags().MarkHidden(ArtifactTypeFlag.Name) // nolint: gosec
}
if f.SBOMFormat != nil {
cmd.Flags().String(SBOMFormatFlag.Name, "", "deprecated")
cmd.Flags().MarkHidden(SBOMFormatFlag.Name) // nolint: gosec
}
}
func (f *SBOMFlagGroup) Bind(cmd *cobra.Command) error {
// All the flags are deprecated
return nil
}
func (f *SBOMFlagGroup) ToOptions() (SBOMOptions, error) {
artifactType := getString(f.ArtifactType)
sbomFormat := getString(f.SBOMFormat)
if artifactType != "" || sbomFormat != "" {
log.Logger.Error("'trivy sbom' is now for scanning SBOM. " +
"See https://github.com/aquasecurity/trivy/discussions/2407 for the detail")
return SBOMOptions{}, xerrors.New("'--artifact-type' and '--sbom-format' are no longer available")
}
return SBOMOptions{}, nil
}

161
pkg/flag/scan_flags.go Normal file
View File

@@ -0,0 +1,161 @@
package flag
import (
"fmt"
"strings"
"github.com/samber/lo"
"github.com/spf13/cobra"
"golang.org/x/exp/slices"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types"
)
var (
SkipDirsFlag = Flag{
Name: "skip-dirs",
ConfigName: "scan.skip-dirs",
Value: "",
Usage: "specify the directories where the traversal is skipped",
}
SkipFilesFlag = Flag{
Name: "skip-files",
ConfigName: "scan.skip-files",
Value: "",
Usage: "specify the file paths to skip traversal",
}
OfflineScanFlag = Flag{
Name: "offline-scan",
ConfigName: "scan.offline",
Value: false,
Usage: "do not issue API requests to identify dependencies",
}
SecurityChecksFlag = Flag{
Name: "security-checks",
ConfigName: "scan.security-checks",
Value: fmt.Sprintf("%s,%s", types.SecurityCheckVulnerability, types.SecurityCheckSecret),
Usage: "comma-separated list of what security issues to detect (vuln,config,secret)",
}
VulnTypeFlag = Flag{
Name: "vuln-type",
ConfigName: "vulnerability.type",
Value: strings.Join([]string{types.VulnTypeOS, types.VulnTypeLibrary}, ","),
Usage: "comma-separated list of vulnerability types (os,library)",
}
SecretConfigFlag = Flag{
Name: "secret-config",
ConfigName: "secret.config",
Value: "trivy-secret.yaml",
Usage: "specify a path to config file for secret scanning",
}
)
type ScanFlagGroup struct {
SkipDirs *Flag
SkipFiles *Flag
OfflineScan *Flag
SecurityChecks *Flag
VulnType *Flag
SecretConfig *Flag
}
type ScanOptions struct {
Target string
SkipDirs []string
SkipFiles []string
OfflineScan bool
SecurityChecks []string
// Vulnerabilities
VulnType []string
// Secrets
SecretConfigPath string
}
func NewScanFlagGroup() *ScanFlagGroup {
return &ScanFlagGroup{
SkipDirs: lo.ToPtr(SkipDirsFlag),
SkipFiles: lo.ToPtr(SkipFilesFlag),
OfflineScan: lo.ToPtr(OfflineScanFlag),
SecurityChecks: lo.ToPtr(SecurityChecksFlag),
VulnType: lo.ToPtr(VulnTypeFlag),
SecretConfig: lo.ToPtr(SecretConfigFlag),
}
}
func (f *ScanFlagGroup) flags() []*Flag {
return []*Flag{f.SkipDirs, f.SkipFiles, f.OfflineScan, f.SecurityChecks, f.VulnType, f.SecretConfig}
}
func (f *ScanFlagGroup) Bind(cmd *cobra.Command) error {
for _, flag := range f.flags() {
if err := bind(cmd, flag); err != nil {
return err
}
}
return nil
}
func (f *ScanFlagGroup) AddFlags(cmd *cobra.Command) {
for _, flag := range f.flags() {
addFlag(cmd, flag)
}
}
func (f *ScanFlagGroup) ToOptions(args []string) ScanOptions {
var target string
if len(args) == 1 {
target = args[0]
}
return ScanOptions{
Target: target,
SkipDirs: getStringSlice(f.SkipDirs),
SkipFiles: getStringSlice(f.SkipFiles),
OfflineScan: getBool(f.OfflineScan),
VulnType: parseVulnType(getStringSlice(f.VulnType)),
SecurityChecks: parseSecurityCheck(getStringSlice(f.SecurityChecks)),
SecretConfigPath: getString(f.SecretConfig),
}
}
func parseVulnType(vulnType []string) []string {
switch {
case len(vulnType) == 0: // no types
return nil
case len(vulnType) == 1 && strings.Contains(vulnType[0], ","): // get checks from flag
vulnType = strings.Split(vulnType[0], ",")
}
var vulnTypes []string
for _, v := range vulnType {
if !slices.Contains(types.VulnTypes, v) {
log.Logger.Warnf("unknown vulnerability type: %s", v)
continue
}
vulnTypes = append(vulnTypes, v)
}
return vulnTypes
}
func parseSecurityCheck(securityCheck []string) []string {
switch {
case len(securityCheck) == 0: // no checks
return nil
case len(securityCheck) == 1 && strings.Contains(securityCheck[0], ","): // get checks from flag
securityCheck = strings.Split(securityCheck[0], ",")
}
var securityChecks []string
for _, v := range securityCheck {
if !slices.Contains(types.SecurityChecks, v) {
log.Logger.Warnf("unknown security check: %s", v)
continue
}
securityChecks = append(securityChecks, v)
}
return securityChecks
}

175
pkg/flag/scan_flags_test.go Normal file
View File

@@ -0,0 +1,175 @@
package flag_test
import (
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types"
)
func TestScanFlagGroup_ToOptions(t *testing.T) {
type fields struct {
skipDirs []string
skipFiles []string
offlineScan bool
vulnType string
securityChecks string
}
tests := []struct {
name string
args []string
fields fields
want flag.ScanOptions
wantLogs []string
}{
{
name: "happy path",
args: []string{"alpine:latest"},
fields: fields{},
want: flag.ScanOptions{
Target: "alpine:latest",
},
},
{
name: "happy path for OS vulnerabilities",
args: []string{"alpine:latest"},
fields: fields{
vulnType: "os",
securityChecks: "vuln",
},
want: flag.ScanOptions{
Target: "alpine:latest",
VulnType: []string{types.VulnTypeOS},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
},
{
name: "happy path for library vulnerabilities",
args: []string{"alpine:latest"},
fields: fields{
vulnType: "library",
securityChecks: "vuln",
},
want: flag.ScanOptions{
Target: "alpine:latest",
VulnType: []string{types.VulnTypeLibrary},
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
},
{
name: "happy path for configs",
args: []string{"alpine:latest"},
fields: fields{
securityChecks: "config",
},
want: flag.ScanOptions{
Target: "alpine:latest",
SecurityChecks: []string{types.SecurityCheckConfig},
},
},
{
name: "with wrong security check",
fields: fields{
securityChecks: "vuln,WRONG-CHECK",
},
want: flag.ScanOptions{
SecurityChecks: []string{types.SecurityCheckVulnerability},
},
wantLogs: []string{
`unknown security check: WRONG-CHECK`,
},
},
{
name: "with wrong vuln type",
fields: fields{
vulnType: "os,nonevuln",
},
want: flag.ScanOptions{
VulnType: []string{types.VulnTypeOS},
},
wantLogs: []string{
`unknown vulnerability type: nonevuln`,
},
},
{
name: "without target (args)",
args: []string{},
fields: fields{},
want: flag.ScanOptions{},
},
{
name: "with two or more targets (args)",
args: []string{"alpine:latest", "nginx:latest"},
fields: fields{},
want: flag.ScanOptions{},
},
{
name: "skip two files",
fields: fields{
skipFiles: []string{"file1", "file2"},
},
want: flag.ScanOptions{
SkipFiles: []string{"file1", "file2"},
},
},
{
name: "skip two folders",
fields: fields{
skipDirs: []string{"dir1", "dir2"},
},
want: flag.ScanOptions{
SkipDirs: []string{"dir1", "dir2"},
},
},
{
name: "offline scan",
fields: fields{
offlineScan: true,
},
want: flag.ScanOptions{
OfflineScan: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
level := zap.WarnLevel
core, obs := observer.New(level)
log.Logger = zap.New(core).Sugar()
viper.Set(flag.SkipDirsFlag.ConfigName, tt.fields.skipDirs)
viper.Set(flag.SkipFilesFlag.ConfigName, tt.fields.skipFiles)
viper.Set(flag.OfflineScanFlag.ConfigName, tt.fields.offlineScan)
viper.Set(flag.VulnTypeFlag.ConfigName, tt.fields.vulnType)
viper.Set(flag.SecurityChecksFlag.ConfigName, tt.fields.securityChecks)
// Assert options
f := &flag.ScanFlagGroup{
SkipDirs: &flag.SkipDirsFlag,
SkipFiles: &flag.SkipFilesFlag,
OfflineScan: &flag.OfflineScanFlag,
VulnType: &flag.VulnTypeFlag,
SecurityChecks: &flag.SecurityChecksFlag,
}
got := f.ToOptions(tt.args)
assert.Equalf(t, tt.want, got, "ToOptions()")
// Assert log messages
var gotMessages []string
for _, entry := range obs.AllUntimed() {
gotMessages = append(gotMessages, entry.Message)
}
assert.Equal(t, tt.wantLogs, gotMessages, tt.name)
})
}
}

View File

@@ -1,25 +1,26 @@
package commands
import (
"context"
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
"github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s"
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
)
// clusterRun runs scan on kubernetes cluster
func clusterRun(cliCtx *cli.Context, opt cmd.Option, cluster k8s.Cluster) error {
if err := validateReportArguments(cliCtx); err != nil {
func clusterRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) error {
if err := validateReportArguments(opts); err != nil {
return err
}
artifacts, err := trivyk8s.New(cluster, log.Logger).ListArtifacts(cliCtx.Context)
artifacts, err := trivyk8s.New(cluster, log.Logger).ListArtifacts(ctx)
if err != nil {
return xerrors.Errorf("get k8s artifacts error: %w", err)
}
return run(cliCtx.Context, opt, cluster.GetCurrentContext(), artifacts)
return run(ctx, opts, cluster.GetCurrentContext(), artifacts)
}

View File

@@ -1,35 +1,35 @@
package commands
import (
"github.com/urfave/cli/v2"
"golang.org/x/xerrors"
"context"
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
"github.com/aquasecurity/trivy/pkg/log"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
"github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/log"
)
// namespaceRun runs scan on kubernetes cluster
func namespaceRun(cliCtx *cli.Context, opt cmd.Option, cluster k8s.Cluster) error {
if err := validateReportArguments(cliCtx); err != nil {
func namespaceRun(ctx context.Context, opts flag.Options, cluster k8s.Cluster) error {
if err := validateReportArguments(opts); err != nil {
return err
}
trivyk8s := trivyk8s.New(cluster, log.Logger).Namespace(getNamespace(opt, cluster.GetCurrentNamespace()))
trivyk8s := trivyk8s.New(cluster, log.Logger).Namespace(getNamespace(opts, cluster.GetCurrentNamespace()))
artifacts, err := trivyk8s.ListArtifacts(cliCtx.Context)
artifacts, err := trivyk8s.ListArtifacts(ctx)
if err != nil {
return xerrors.Errorf("get k8s artifacts error: %w", err)
}
return run(cliCtx.Context, opt, cluster.GetCurrentContext(), artifacts)
return run(ctx, opts, cluster.GetCurrentContext(), artifacts)
}
func getNamespace(opt cmd.Option, currentNamespace string) string {
if len(opt.KubernetesOption.Namespace) > 0 {
return opt.KubernetesOption.Namespace
func getNamespace(opts flag.Options, currentNamespace string) string {
if len(opts.K8sOptions.Namespace) > 0 {
return opts.K8sOptions.Namespace
}
return currentNamespace

View File

@@ -3,9 +3,9 @@ package commands
import (
"testing"
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
"github.com/aquasecurity/trivy/pkg/commands/option"
"gotest.tools/assert"
"github.com/stretchr/testify/assert"
"github.com/aquasecurity/trivy/pkg/flag"
)
func Test_getNamespace(t *testing.T) {
@@ -13,27 +13,35 @@ func Test_getNamespace(t *testing.T) {
tests := []struct {
name string
currentNamespace string
opt cmd.Option
expected string
opts flag.Options
want string
}{
{
name: "--namespace=custom",
currentNamespace: "default",
opt: cmd.Option{KubernetesOption: option.KubernetesOption{Namespace: "custom"}},
expected: "custom",
opts: flag.Options{
K8sOptions: flag.K8sOptions{
Namespace: "custom",
},
},
want: "custom",
},
{
name: "no namespaces passed",
currentNamespace: "default",
opt: cmd.Option{KubernetesOption: option.KubernetesOption{Namespace: ""}},
expected: "default",
opts: flag.Options{
K8sOptions: flag.K8sOptions{
Namespace: "",
},
},
want: "default",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got := getNamespace(test.opt, test.currentNamespace)
assert.Equal(t, test.expected, got)
got := getNamespace(test.opts, test.currentNamespace)
assert.Equal(t, test.want, got)
})
}
}

View File

@@ -1,47 +1,48 @@
package commands
import (
"context"
"strings"
"github.com/urfave/cli/v2"
"github.com/aquasecurity/trivy/pkg/flag"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
"github.com/aquasecurity/trivy-kubernetes/pkg/k8s"
"github.com/aquasecurity/trivy-kubernetes/pkg/trivyk8s"
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
"github.com/aquasecurity/trivy/pkg/log"
)
// resourceRun runs scan on kubernetes cluster
func resourceRun(cliCtx *cli.Context, opt cmd.Option, cluster k8s.Cluster) error {
kind, name, err := extractKindAndName(cliCtx.Args().Slice())
func resourceRun(ctx context.Context, args []string, opts flag.Options, cluster k8s.Cluster) error {
kind, name, err := extractKindAndName(args)
if err != nil {
return err
}
trivyk8s := trivyk8s.New(cluster, log.Logger).Namespace(getNamespace(opt, cluster.GetCurrentNamespace()))
trivyk8s := trivyk8s.New(cluster, log.Logger).Namespace(getNamespace(opts, cluster.GetCurrentNamespace()))
if len(name) == 0 { // pods or configmaps etc
if err := validateReportArguments(cliCtx); err != nil {
if err = validateReportArguments(opts); err != nil {
return err
}
targets, err := trivyk8s.Resources(kind).ListArtifacts(cliCtx.Context)
targets, err := trivyk8s.Resources(kind).ListArtifacts(ctx)
if err != nil {
return err
}
return run(cliCtx.Context, opt, cluster.GetCurrentContext(), targets)
return run(ctx, opts, cluster.GetCurrentContext(), targets)
}
// pod/NAME or pod NAME etc
artifact, err := trivyk8s.GetArtifact(cliCtx.Context, kind, name)
artifact, err := trivyk8s.GetArtifact(ctx, kind, name)
if err != nil {
return err
}
return run(cliCtx.Context, opt, cluster.GetCurrentContext(), []*artifacts.Artifact{artifact})
return run(ctx, opts, cluster.GetCurrentContext(), []*artifacts.Artifact{artifact})
}
func extractKindAndName(args []string) (string, string, error) {

View File

@@ -4,7 +4,10 @@ import (
"context"
"errors"
"github.com/urfave/cli/v2"
"github.com/spf13/viper"
"github.com/aquasecurity/trivy/pkg/flag"
"golang.org/x/xerrors"
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
@@ -22,29 +25,24 @@ const (
)
// Run runs a k8s scan
func Run(cliCtx *cli.Context) error {
opt, err := cmd.InitOption(cliCtx)
if err != nil {
return xerrors.Errorf("option error: %w", err)
}
cluster, err := k8s.GetCluster(opt.KubernetesOption.ClusterContext)
func Run(ctx context.Context, args []string, opts flag.Options) error {
cluster, err := k8s.GetCluster(opts.K8sOptions.ClusterContext)
if err != nil {
return xerrors.Errorf("failed getting k8s cluster: %w", err)
}
switch cliCtx.Args().Get(0) {
switch args[0] {
case clusterArtifact:
return clusterRun(cliCtx, opt, cluster)
return clusterRun(ctx, opts, cluster)
case allArtifact:
return namespaceRun(cliCtx, opt, cluster)
return namespaceRun(ctx, opts, cluster)
default: // resourceArtifact
return resourceRun(cliCtx, opt, cluster)
return resourceRun(ctx, args, opts, cluster)
}
}
func run(ctx context.Context, opt cmd.Option, cluster string, artifacts []*artifacts.Artifact) error {
ctx, cancel := context.WithTimeout(ctx, opt.Timeout)
func run(ctx context.Context, opts flag.Options, cluster string, artifacts []*artifacts.Artifact) error {
ctx, cancel := context.WithTimeout(ctx, opts.Timeout)
defer cancel()
var err error
@@ -54,7 +52,7 @@ func run(ctx context.Context, opt cmd.Option, cluster string, artifacts []*artif
}
}()
runner, err := cmd.NewRunner(opt)
runner, err := cmd.NewRunner(ctx, opts)
if err != nil {
if errors.Is(err, cmd.SkipScan) {
return nil
@@ -67,22 +65,22 @@ func run(ctx context.Context, opt cmd.Option, cluster string, artifacts []*artif
}
}()
s := scanner.NewScanner(cluster, runner, opt)
s := scanner.NewScanner(cluster, runner, opts)
r, err := s.Scan(ctx, artifacts)
if err != nil {
return xerrors.Errorf("k8s scan error: %w", err)
}
if err := report.Write(r, report.Option{
Format: opt.Format,
Report: opt.KubernetesOption.ReportFormat,
Output: opt.Output,
Severities: opt.Severities,
}, opt.ReportOption.SecurityChecks); err != nil {
Format: opts.Format,
Report: opts.ReportFormat,
Output: opts.Output,
Severities: opts.Severities,
}, opts.ScanOptions.SecurityChecks); err != nil {
return xerrors.Errorf("unable to write results: %w", err)
}
cmd.Exit(opt, r.Failed())
cmd.Exit(opts, r.Failed())
return nil
}
@@ -101,10 +99,10 @@ func run(ctx context.Context, opt cmd.Option, cluster string, artifacts []*artif
// Single resource scanning is allowed with implicit "--report all".
//
// e.g. $ trivy k8s pod myapp
func validateReportArguments(cliCtx *cli.Context) error {
if cliCtx.String("report") == "all" &&
!cliCtx.IsSet("report") &&
cliCtx.String("format") == "table" {
func validateReportArguments(opts flag.Options) error {
if opts.ReportFormat == "all" &&
!viper.IsSet("report") &&
opts.Format == "table" {
m := "All the results in the table format can mess up your terminal. Use \"--report all\" to tell Trivy to output it to your terminal anyway, or consider \"--report summary\" to show the summary output."

View File

@@ -3,10 +3,9 @@ package report
import (
"testing"
"github.com/aquasecurity/trivy/pkg/commands/option"
"github.com/stretchr/testify/assert"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/types"
)
@@ -214,42 +213,42 @@ func Test_separateMisConfigRoleAssessment(t *testing.T) {
tests := []struct {
name string
k8sReport Report
rp option.ReportOption
opts flag.ScanOptions
wantRbacReport Report
wantMisConfigReport Report
}{
{
name: "Role and Deployment Reports",
k8sReport: Report{Misconfigurations: []Resource{{Kind: "Role"}, {Kind: "Deployment"}}},
rp: option.ReportOption{SecurityChecks: []string{"config", "rbac"}},
opts: flag.ScanOptions{SecurityChecks: []string{"config", "rbac"}},
wantRbacReport: Report{Misconfigurations: []Resource{{Kind: "Role"}}},
wantMisConfigReport: Report{Misconfigurations: []Resource{{Kind: "Deployment"}}},
},
{
name: "Role Report Only",
k8sReport: Report{Misconfigurations: []Resource{{Kind: "Role"}, {Kind: "Deployment"}}},
rp: option.ReportOption{SecurityChecks: []string{"rbac"}},
opts: flag.ScanOptions{SecurityChecks: []string{"rbac"}},
wantRbacReport: Report{Misconfigurations: []Resource{{Kind: "Role"}}},
wantMisConfigReport: Report{Misconfigurations: []Resource{}},
},
{
name: "Deployment Report Only",
k8sReport: Report{Misconfigurations: []Resource{{Kind: "Role"}, {Kind: "Deployment"}}},
rp: option.ReportOption{SecurityChecks: []string{"config"}},
opts: flag.ScanOptions{SecurityChecks: []string{"config"}},
wantRbacReport: Report{Misconfigurations: []Resource{}},
wantMisConfigReport: Report{Misconfigurations: []Resource{{Kind: "Deployment"}}},
},
{
name: "No Deployment & No Role Reports",
k8sReport: Report{Misconfigurations: []Resource{{Kind: "Role"}, {Kind: "Deployment"}}},
rp: option.ReportOption{SecurityChecks: []string{"vuln"}},
opts: flag.ScanOptions{SecurityChecks: []string{"vuln"}},
wantRbacReport: Report{Misconfigurations: []Resource{}},
wantMisConfigReport: Report{Misconfigurations: []Resource{}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
misConfig, rbac := separateMisConfigRoleAssessment(tt.k8sReport, tt.rp.SecurityChecks)
misConfig, rbac := separateMisConfigRoleAssessment(tt.k8sReport, tt.opts.SecurityChecks)
assert.Equal(t, len(tt.wantMisConfigReport.Misconfigurations), len(misConfig.Misconfigurations))
assert.Equal(t, len(tt.wantRbacReport.Misconfigurations), len(rbac.Misconfigurations))
})

View File

@@ -6,43 +6,46 @@ import (
"github.com/stretchr/testify/assert"
"github.com/aquasecurity/trivy/pkg/commands/option"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/types"
)
func TestReport_ColumnHeading(t *testing.T) {
tests := []struct {
name string
rp option.ReportOption
opts flag.ScanOptions
availableColumns []string
want []string
}{
{
name: "all workload columns",
rp: option.ReportOption{SecurityChecks: []string{"vuln", "config", "secret", "rbac"}},
name: "all workload columns",
opts: flag.ScanOptions{SecurityChecks: []string{types.SecurityCheckVulnerability,
types.SecurityCheckConfig, types.SecurityCheckSecret, types.SecurityCheckRbac}},
availableColumns: WorkloadColumns(),
want: []string{NamespaceColumn, ResourceColumn, VulnerabilitiesColumn, MisconfigurationsColumn, SecretsColumn},
},
{
name: "all rbac columns",
rp: option.ReportOption{SecurityChecks: []string{"vuln", "config", "secret", "rbac"}},
name: "all rbac columns",
opts: flag.ScanOptions{SecurityChecks: []string{types.SecurityCheckVulnerability,
types.SecurityCheckConfig, types.SecurityCheckSecret, types.SecurityCheckRbac}},
availableColumns: RoleColumns(),
want: []string{NamespaceColumn, ResourceColumn, RbacAssessmentColumn},
},
{
name: "config column only",
rp: option.ReportOption{SecurityChecks: []string{"config"}},
opts: flag.ScanOptions{SecurityChecks: []string{types.SecurityCheckConfig}},
availableColumns: WorkloadColumns(),
want: []string{NamespaceColumn, ResourceColumn, MisconfigurationsColumn},
},
{
name: "secret column only",
rp: option.ReportOption{SecurityChecks: []string{"secret"}},
opts: flag.ScanOptions{SecurityChecks: []string{types.SecurityCheckSecret}},
availableColumns: WorkloadColumns(),
want: []string{NamespaceColumn, ResourceColumn, SecretsColumn},
},
{
name: "vuln column only",
rp: option.ReportOption{SecurityChecks: []string{"vuln"}},
opts: flag.ScanOptions{SecurityChecks: []string{types.SecurityCheckVulnerability}},
availableColumns: WorkloadColumns(),
want: []string{NamespaceColumn, ResourceColumn, VulnerabilitiesColumn},
},
@@ -50,7 +53,7 @@ func TestReport_ColumnHeading(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
column := ColumnHeading(tt.rp.SecurityChecks, tt.availableColumns)
column := ColumnHeading(tt.opts.SecurityChecks, tt.availableColumns)
if !assert.Equal(t, column, tt.want) {
t.Error(fmt.Errorf("TestReport_ColumnHeading want %v got %v", tt.want, column))
}

View File

@@ -8,28 +8,28 @@ import (
"golang.org/x/exp/slices"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
cmd "github.com/aquasecurity/trivy/pkg/commands/artifact"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/k8s/report"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types"
"github.com/aquasecurity/trivy-kubernetes/pkg/artifacts"
)
type Scanner struct {
cluster string
runner cmd.Runner
opt cmd.Option
opts flag.Options
}
func NewScanner(cluster string, runner cmd.Runner, opt cmd.Option) *Scanner {
return &Scanner{cluster, runner, opt}
func NewScanner(cluster string, runner cmd.Runner, opts flag.Options) *Scanner {
return &Scanner{cluster, runner, opts}
}
func (s *Scanner) Scan(ctx context.Context, artifacts []*artifacts.Artifact) (report.Report, error) {
// progress bar
bar := pb.StartNew(len(artifacts))
if s.opt.NoProgress {
if s.opts.NoProgress {
bar.SetWriter(io.Discard)
}
defer bar.Finish()
@@ -37,7 +37,7 @@ func (s *Scanner) Scan(ctx context.Context, artifacts []*artifacts.Artifact) (re
var vulns, misconfigs []report.Resource
// disable logs before scanning
err := log.InitLogger(s.opt.Debug, true)
err := log.InitLogger(s.opts.Debug, true)
if err != nil {
return report.Report{}, xerrors.Errorf("logger error: %w", err)
}
@@ -46,7 +46,7 @@ func (s *Scanner) Scan(ctx context.Context, artifacts []*artifacts.Artifact) (re
// to enable logs even when the function returns earlier
// due to an error
defer func() {
err = log.InitLogger(s.opt.Debug, false)
err = log.InitLogger(s.opts.Debug, false)
if err != nil {
// we use log.Fatal here because the error was to enable the logger
log.Fatal(xerrors.Errorf("can't enable logger error: %w", err))
@@ -58,7 +58,7 @@ func (s *Scanner) Scan(ctx context.Context, artifacts []*artifacts.Artifact) (re
for _, artifact := range artifacts {
bar.Increment()
if slices.Contains(s.opt.SecurityChecks, types.SecurityCheckVulnerability) {
if slices.Contains(s.opts.SecurityChecks, types.SecurityCheckVulnerability) {
resources, err := s.scanVulns(ctx, artifact)
if err != nil {
return report.Report{}, xerrors.Errorf("scanning vulnerabilities error: %w", err)
@@ -66,7 +66,7 @@ func (s *Scanner) Scan(ctx context.Context, artifacts []*artifacts.Artifact) (re
vulns = append(vulns, resources...)
}
if s.shouldScanMisconfig(s.opt.SecurityChecks) {
if s.shouldScanMisconfig(s.opts.SecurityChecks) {
resource, err := s.scanMisconfigs(ctx, artifact)
if err != nil {
return report.Report{}, xerrors.Errorf("scanning misconfigurations error: %w", err)
@@ -92,9 +92,9 @@ func (s *Scanner) scanVulns(ctx context.Context, artifact *artifacts.Artifact) (
for _, image := range artifact.Images {
s.opt.Target = image
s.opts.Target = image
imageReport, err := s.runner.ScanImage(ctx, s.opt)
imageReport, err := s.runner.ScanImage(ctx, s.opts)
if err != nil {
log.Logger.Debugf("failed to scan image %s: %s", image, err)
@@ -119,9 +119,9 @@ func (s *Scanner) scanMisconfigs(ctx context.Context, artifact *artifacts.Artifa
return report.Resource{}, xerrors.Errorf("scan error: %w", err)
}
s.opt.Target = configFile
s.opts.Target = configFile
configReport, err := s.runner.ScanFilesystem(ctx, s.opt)
configReport, err := s.runner.ScanFilesystem(ctx, s.opts)
//remove config file after scanning
removeFile(configFile)
if err != nil {
@@ -133,7 +133,7 @@ func (s *Scanner) scanMisconfigs(ctx context.Context, artifact *artifacts.Artifa
}
func (s *Scanner) filter(ctx context.Context, r types.Report, artifact *artifacts.Artifact) (report.Resource, error) {
r, err := s.runner.Filter(ctx, s.opt, r)
r, err := s.runner.Filter(ctx, s.opts, r)
if err != nil {
return report.Resource{}, xerrors.Errorf("filter error: %w", err)
}

View File

@@ -280,6 +280,19 @@ func LoadAll() ([]Plugin, error) {
return plugins, nil
}
// RunWithArgs runs the plugin with arguments
func RunWithArgs(ctx context.Context, url string, args []string) error {
pl, err := Install(ctx, url, false)
if err != nil {
return xerrors.Errorf("plugin install error: %w", err)
}
if err = pl.Run(ctx, args); err != nil {
return xerrors.Errorf("unable to run %s plugin: %w", pl.Name, err)
}
return nil
}
func loadMetadata(dir string) (Plugin, error) {
filePath := filepath.Join(dir, configFile)
f, err := os.Open(filePath)

View File

@@ -28,6 +28,10 @@ const (
FormatGitHub = "github"
)
var (
SupportedSBOMFormats = []string{FormatCycloneDX, FormatSPDX, FormatSPDXJSON, FormatGitHub}
)
type Option struct {
AppVersion string

View File

@@ -97,12 +97,12 @@ func (u *Unmarshaler) Unmarshal(r io.Reader) (sbom.SBOM, error) {
if bom.Metadata != nil {
metadata.Timestamp = bom.Metadata.Timestamp
if bom.Metadata.Component != nil {
metadata.Component = toTrivyCdxComponent(fromPtr(bom.Metadata.Component))
metadata.Component = toTrivyCdxComponent(lo.FromPtr(bom.Metadata.Component))
}
}
var components []ftypes.Component
for _, c := range fromPtr(bom.Components) {
for _, c := range lo.FromPtr(bom.Components) {
components = append(components, toTrivyCdxComponent(c))
}
@@ -196,7 +196,7 @@ func (u *Unmarshaler) walkDependencies(rootRef string) []cdx.Component {
func componentMap(metadata *cdx.Metadata, components *[]cdx.Component) map[string]cdx.Component {
cmap := make(map[string]cdx.Component)
for _, component := range fromPtr(components) {
for _, component := range lo.FromPtr(components) {
cmap[component.BOMRef] = component
}
if metadata != nil {
@@ -208,13 +208,13 @@ func componentMap(metadata *cdx.Metadata, components *[]cdx.Component) map[strin
func dependencyMap(deps *[]cdx.Dependency) map[string][]string {
depMap := make(map[string][]string)
for _, dep := range fromPtr(deps) {
for _, dep := range lo.FromPtr(deps) {
if _, ok := depMap[dep.Ref]; ok {
continue
}
var refs []string
for _, d := range fromPtr(dep.Dependencies) {
for _, d := range lo.FromPtr(dep.Dependencies) {
refs = append(refs, d.Ref)
}
@@ -270,11 +270,11 @@ func toPackage(component cdx.Component) (string, *ftypes.Package, error) {
pkg := p.Package()
pkg.Ref = component.BOMRef
for _, license := range fromPtr(component.Licenses) {
for _, license := range lo.FromPtr(component.Licenses) {
pkg.Licenses = append(pkg.Licenses, license.Expression)
}
for _, prop := range fromPtr(component.Properties) {
for _, prop := range lo.FromPtr(component.Properties) {
if strings.HasPrefix(prop.Name, Namespace) {
switch strings.TrimPrefix(prop.Name, Namespace) {
case PropertySrcName:
@@ -311,18 +311,10 @@ func toTrivyCdxComponent(component cdx.Component) ftypes.Component {
}
func lookupProperty(properties *[]cdx.Property, key string) string {
for _, p := range fromPtr(properties) {
for _, p := range lo.FromPtr(properties) {
if p.Name == Namespace+key {
return p.Value
}
}
return ""
}
func fromPtr[T any](ptr *T) T {
if ptr == nil {
var t T
return t
}
return *ptr
}