mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-13 00:00:19 -08:00
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:
@@ -31,7 +31,6 @@ linters:
|
||||
- ineffassign
|
||||
- typecheck
|
||||
- govet
|
||||
- errcheck
|
||||
- varcheck
|
||||
- deadcode
|
||||
- revive
|
||||
|
||||
@@ -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
41
go.mod
@@ -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
107
go.sum
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
1738
pkg/commands/app.go
1738
pkg/commands/app.go
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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:])
|
||||
}
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
155
pkg/flag/cache_flags.go
Normal 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:])
|
||||
}
|
||||
145
pkg/flag/cache_flags_test.go
Normal file
145
pkg/flag/cache_flags_test.go
Normal 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
122
pkg/flag/db_flags.go
Normal 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
94
pkg/flag/db_flags_test.go
Normal 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
131
pkg/flag/global_flags.go
Normal 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
68
pkg/flag/image_flags.go
Normal 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),
|
||||
}
|
||||
}
|
||||
64
pkg/flag/kubernetes_flags.go
Normal file
64
pkg/flag/kubernetes_flags.go
Normal 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
130
pkg/flag/misconf_flags.go
Normal 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
297
pkg/flag/options.go
Normal 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
155
pkg/flag/remote_flags.go
Normal 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
|
||||
}
|
||||
127
pkg/flag/remote_flags_test.go
Normal file
127
pkg/flag/remote_flags_test.go
Normal 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
251
pkg/flag/report_flags.go
Normal 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
|
||||
}
|
||||
208
pkg/flag/report_flags_test.go
Normal file
208
pkg/flag/report_flags_test.go
Normal 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
67
pkg/flag/sbom_flags.go
Normal 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
161
pkg/flag/scan_flags.go
Normal 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
175
pkg/flag/scan_flags_test.go
Normal 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)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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."
|
||||
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -28,6 +28,10 @@ const (
|
||||
FormatGitHub = "github"
|
||||
)
|
||||
|
||||
var (
|
||||
SupportedSBOMFormats = []string{FormatCycloneDX, FormatSPDX, FormatSPDXJSON, FormatGitHub}
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
AppVersion string
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user