Merge pull request #27 from anchore/curate-db-file

Add curation of db file
This commit is contained in:
Alex Goodman 2020-06-22 15:09:58 -04:00 committed by GitHub
commit 89ed62696d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 1316 additions and 179 deletions

2
.gitignore vendored
View file

@ -1,5 +1,7 @@
.vscode/
.server/
*.db
!**/test-fixtures/**/*.db
*.tar
.idea/
*.log

View file

@ -1,4 +1,7 @@
linter-settings:
linters-settings:
funlen:
lines: 70
statements: 50
linters:
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
disable-all: true

View file

@ -46,6 +46,7 @@ coverage:
@printf '$(TITLE)Running unit tests + coverage$(RESET)\n'
$(TEMPDIR)/bin/go-acc -o $(TEMPDIR)/coverage.txt ./...
# TODO: add benchmarks
integration:

View file

@ -45,7 +45,7 @@ func init() {
func Execute() {
if err := rootCmd.Execute(); err != nil {
log.Errorf("could not start application: %w", err)
log.Errorf("could not start application: %+v", err)
os.Exit(1)
}
}

View file

@ -1,8 +1,10 @@
package cmd
import (
"fmt"
"os"
"github.com/anchore/vulnscan/vulnscan/db"
"github.com/spf13/cobra"
)
@ -10,7 +12,11 @@ var dbCheckCmd = &cobra.Command{
Use: "check",
Short: "check to see if there is a database update available",
Run: func(cmd *cobra.Command, args []string) {
os.Exit(runDbCheckCmd(cmd, args))
ret := runDbCheckCmd(cmd, args)
if ret != 0 {
fmt.Println("Unable to check for vulnerability database updates")
}
os.Exit(ret)
},
}
@ -18,7 +24,26 @@ func init() {
dbCmd.AddCommand(dbCheckCmd)
}
func runDbCheckCmd(cmd *cobra.Command, args []string) int {
log.Error("database CHECK command...")
func runDbCheckCmd(_ *cobra.Command, _ []string) int {
dbCurator, err := db.NewCurator(appConfig.Db.ToCuratorConfig())
if err != nil {
log.Errorf("could not curate database: %w", err)
return 1
}
updateAvailable, _, err := dbCurator.IsUpdateAvailable()
if err != nil {
// TODO: should this be so fatal? we can certainly continue with a warning...
log.Errorf("unable to check for vulnerability database update: %w", err)
return 1
}
if !updateAvailable {
fmt.Println("No update available")
return 0
}
fmt.Println("Update available!")
return 0
}

View file

@ -1,24 +0,0 @@
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var dbClearCmd = &cobra.Command{
Use: "clear",
Short: "delete the vulnerability database",
Run: func(cmd *cobra.Command, args []string) {
os.Exit(runDbClearCmd(cmd, args))
},
}
func init() {
dbCmd.AddCommand(dbClearCmd)
}
func runDbClearCmd(cmd *cobra.Command, args []string) int {
log.Error("database CLEAR command...")
return 0
}

43
cmd/db_delete.go Normal file
View file

@ -0,0 +1,43 @@
package cmd
import (
"fmt"
"os"
"github.com/anchore/vulnscan/vulnscan/db"
"github.com/spf13/cobra"
)
var dbDeleteCmd = &cobra.Command{
Use: "delete",
Short: "delete the vulnerability database",
Run: func(cmd *cobra.Command, args []string) {
ret := runDbDeleteCmd(cmd, args)
if ret != 0 {
fmt.Println("Unable to delete vulnerability database")
}
os.Exit(ret)
},
}
func init() {
dbCmd.AddCommand(dbDeleteCmd)
}
func runDbDeleteCmd(_ *cobra.Command, _ []string) int {
dbCurator, err := db.NewCurator(appConfig.Db.ToCuratorConfig())
if err != nil {
log.Errorf("could not curate database: %w", err)
return 1
}
err = dbCurator.Delete()
if err != nil {
log.Errorf("unable to delete vulnerability database: %w", err)
return 1
}
fmt.Println("Vulnerability database deleted")
return 0
}

View file

@ -1,8 +1,10 @@
package cmd
import (
"fmt"
"os"
"github.com/anchore/vulnscan/vulnscan/db"
"github.com/spf13/cobra"
)
@ -10,7 +12,11 @@ var dbUpdateCmd = &cobra.Command{
Use: "update",
Short: "download the latest vulnerability database",
Run: func(cmd *cobra.Command, args []string) {
os.Exit(runDbUpdateCmd(cmd, args))
ret := runDbUpdateCmd(cmd, args)
if ret != 0 {
fmt.Println("Unable to update vulnerability database")
}
os.Exit(ret)
},
}
@ -18,7 +24,30 @@ func init() {
dbCmd.AddCommand(dbUpdateCmd)
}
func runDbUpdateCmd(cmd *cobra.Command, args []string) int {
log.Error("database UPDATE command...")
func runDbUpdateCmd(_ *cobra.Command, _ []string) int {
dbCurator, err := db.NewCurator(appConfig.Db.ToCuratorConfig())
if err != nil {
log.Errorf("could not curate database: %w", err)
return 1
}
updateAvailable, updateEntry, err := dbCurator.IsUpdateAvailable()
if err != nil {
// TODO: should this be so fatal? we can certainly continue with a warning...
log.Errorf("unable to check for vulnerability database update: %+v", err)
return 1
}
if updateAvailable {
err = dbCurator.UpdateTo(updateEntry)
if err != nil {
log.Errorf("unable to update vulnerability database: %+v", err)
return 1
}
} else {
fmt.Println("No vulnerability database update available")
return 0
}
fmt.Println("Vulnerability database updated!")
return 0
}

View file

@ -9,9 +9,9 @@ import (
"github.com/anchore/imgbom/imgbom/scope"
"github.com/anchore/stereoscope"
"github.com/anchore/vulnscan/internal"
"github.com/anchore/vulnscan/internal/db"
"github.com/anchore/vulnscan/internal/format"
"github.com/anchore/vulnscan/vulnscan"
"github.com/anchore/vulnscan/vulnscan/db"
"github.com/anchore/vulnscan/vulnscan/presenter"
"github.com/anchore/vulnscan/vulnscan/vulnerability"
"github.com/spf13/cobra"
@ -83,7 +83,34 @@ func runDefaultCmd(_ *cobra.Command, args []string) int {
return 1
}
store := db.GetStore()
dbCurator, err := db.NewCurator(appConfig.Db.ToCuratorConfig())
if err != nil {
log.Errorf("could not curate database: %w", err)
return 1
}
if appConfig.Db.UpdateOnStartup {
updateAvailable, updateEntry, err := dbCurator.IsUpdateAvailable()
if err != nil {
// TODO: should this be so fatal? we can certainly continue with a warning...
log.Errorf("unable to check for vulnerability database update: %+v", err)
return 1
}
if updateAvailable {
err = dbCurator.UpdateTo(updateEntry)
if err != nil {
log.Errorf("unable to update vulnerability database: %+v", err)
return 1
}
}
}
store, err := dbCurator.GetStore()
if err != nil {
log.Errorf("failed to load vulnerability database: %w", err)
return 1
}
provider := vulnerability.NewProviderFromStore(store)
results := vulnscan.FindAllVulnerabilities(provider, *osObj, catalog)

7
go.mod
View file

@ -4,18 +4,21 @@ go 1.14
require (
github.com/adrg/xdg v0.2.1
github.com/anchore/go-testutils v0.0.0-20200520222037-edc2bf1864fe
github.com/anchore/imgbom v0.0.0-20200616171024-2cb7dad96784
github.com/anchore/stereoscope v0.0.0-20200616152009-189722bdb61b
github.com/anchore/vulnscan-db v0.0.0-20200604185950-6a9f5a2c9ddf
github.com/go-test/deep v1.0.6
github.com/hashicorp/go-getter v1.4.1
github.com/hashicorp/go-version v1.2.0
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
github.com/knqyf263/go-version v1.1.1
github.com/mitchellh/go-homedir v1.1.0
github.com/sergi/go-diff v1.1.0
github.com/spf13/afero v1.3.0
github.com/spf13/cobra v1.0.0
github.com/spf13/viper v1.7.0
go.uber.org/zap v1.15.0
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect
gopkg.in/ini.v1 v1.57.0 // indirect
gopkg.in/yaml.v2 v2.3.0
)

92
go.sum
View file

@ -14,22 +14,27 @@ cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0 h1:EpMNVUorLiZIELdMZbCYX/ByTFCdoYopYAGxaGVz9ms=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
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 h1:a/O/bK/vWrYGOTFtH8di4rBxMZnmkjy+Y5LxpDwo+dA=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
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/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=
cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0 h1:86K1Gel7BQ9/WmNWn7dTKMvTLFzwtBe5FNqYbi9X35g=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
code.gitea.io/sdk/gitea v0.12.0/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
contrib.go.opencensus.io/exporter/aws v0.0.0-20181029163544-2befc13012d0/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA=
@ -103,40 +108,16 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/anchore/go-feeds-client v0.0.0-20200608121036-d9c3f68622a7 h1:IpwJkSqsmdZHN6CmUYNKKx5J25J3g/6yzwRmpvERqhQ=
github.com/anchore/go-feeds-client v0.0.0-20200608121036-d9c3f68622a7/go.mod h1:sugnhPPnoDCFWKzmQxSj444vF3T6UTiCdMLn8YZLyVg=
github.com/anchore/go-testutils v0.0.0-20200520222037-edc2bf1864fe h1:YMXe4RA3qy4Ri5fmGQii/Gn+Pxv3oBfiS/LqzeOVuwo=
github.com/anchore/go-testutils v0.0.0-20200520222037-edc2bf1864fe/go.mod h1:D3rc2L/q4Hcp9eeX6AIJH4Q+kPjOtJCFhG9za90j+nU=
github.com/anchore/imgbom v0.0.0-20200601214218-f0b8aaacdae2 h1:hGKC1CpgK1x8HIUJ1hkZP8kuUXcywWIa7wz3Mi4aFus=
github.com/anchore/imgbom v0.0.0-20200601214218-f0b8aaacdae2/go.mod h1:Ttau0/FsMvXMTlPQvSpyQPi0VZQDosjwuHUv1zUwl7k=
github.com/anchore/imgbom v0.0.0-20200603004815-b6122a413ba8 h1:k8e5yQI3mnkoxeEI+s7ujBEtjLuicxyOn7IfKsQsotY=
github.com/anchore/imgbom v0.0.0-20200603004815-b6122a413ba8/go.mod h1:Ttau0/FsMvXMTlPQvSpyQPi0VZQDosjwuHUv1zUwl7k=
github.com/anchore/imgbom v0.0.0-20200604184352-e88669c536ce h1:t/2K7VPuKX7DrYnPeclLNrH0gsRbW8ZBig7o50pqq50=
github.com/anchore/imgbom v0.0.0-20200604184352-e88669c536ce/go.mod h1:oVcJ4sEuqz/7XTPaJYIZRc4NYVl3zPP96g7RtWG31SE=
github.com/anchore/imgbom v0.0.0-20200605135927-64a9125895b5 h1:0ylgLfUfao/4DiuhuYLV9vX9fs2N+VfmgWZ0d8HqsxA=
github.com/anchore/imgbom v0.0.0-20200605135927-64a9125895b5/go.mod h1:c4LPvBC2SvyzgOdpbjUM6Ys4n10aX6gSOGQUYzfryWw=
github.com/anchore/imgbom v0.0.0-20200616171024-2cb7dad96784 h1:s9zoF5uqN8rvp6TBtFgwIoBsP6gwhqaCw43XfELh3C4=
github.com/anchore/imgbom v0.0.0-20200616171024-2cb7dad96784/go.mod h1:QCQcQ4EfM+ejNaFu3Ep24YwR5t72PcwAbfSBqXWJeB4=
github.com/anchore/stereoscope v0.0.0-20200520221116-025e07f1c93e h1:QBwtrM0MXi0z+GcHk3RoSyzaQ+CLgas0bC/uOd1P+PQ=
github.com/anchore/stereoscope v0.0.0-20200520221116-025e07f1c93e/go.mod h1:bkyLl5VITnrmgErv4S1vDfVz/TGAZ5il6161IQo7w2g=
github.com/anchore/stereoscope v0.0.0-20200523232006-be5f3c18958f h1:aPJQyXi8Y7PhnzhUszZfS/23TA5o29UCc3XGreflaqo=
github.com/anchore/stereoscope v0.0.0-20200523232006-be5f3c18958f/go.mod h1:bkyLl5VITnrmgErv4S1vDfVz/TGAZ5il6161IQo7w2g=
github.com/anchore/stereoscope v0.0.0-20200602123205-6c2ce3c0b2d5 h1:eViCIr4O1e4M93nbbMZdrRW0JjqDjPYdtMXEOC3jQQQ=
github.com/anchore/stereoscope v0.0.0-20200602123205-6c2ce3c0b2d5/go.mod h1:OeCrFeSu8+p02qC7u9/u8wBOh50VQa8eHJjXVuANvLo=
github.com/anchore/stereoscope v0.0.0-20200604133300-7e63b350b6d6 h1:Fu779yw004jyFH1UkQD8lTf0GmGRfrOQIK5QiqmIwU8=
github.com/anchore/stereoscope v0.0.0-20200604133300-7e63b350b6d6/go.mod h1:eQ2/Al6XDA7RFk3FVfpjyGRErITRjNciUPIWixHc7kQ=
github.com/anchore/stereoscope v0.0.0-20200616152009-189722bdb61b h1:LmFKsQi4oj2VJjch7JhQNzJg1A56FjwHqWZz1ZZKgIw=
github.com/anchore/stereoscope v0.0.0-20200616152009-189722bdb61b/go.mod h1:eQ2/Al6XDA7RFk3FVfpjyGRErITRjNciUPIWixHc7kQ=
github.com/anchore/vulnscan-db v0.0.0-20200528193934-4a3f5d48b4c8 h1:pSGrFVpRZe2P9Ov+6GQSz0vh1yrUEmkMTEpjn+HT2Sw=
github.com/anchore/vulnscan-db v0.0.0-20200528193934-4a3f5d48b4c8/go.mod h1:VX8yNH+ERoyXQ0Qsd+Ct8FxRvMSyOPcno/zwq/pjg0s=
github.com/anchore/vulnscan-db v0.0.0-20200603183000-b64fbcd7044b h1:cFBPQpPv/Nk8G1bVsMU8xxwqXLXCe3GdyGn9AEsVuNE=
github.com/anchore/vulnscan-db v0.0.0-20200603183000-b64fbcd7044b/go.mod h1:OVROq5+BT+g+ES+heRewy7NU2f147o2QyMortckSXek=
github.com/anchore/vulnscan-db v0.0.0-20200604185950-6a9f5a2c9ddf h1:U1KgI8Lk6acUjjmtBLWOCoL9U7AV8tFmHnqAtFikJ7E=
github.com/anchore/vulnscan-db v0.0.0-20200604185950-6a9f5a2c9ddf/go.mod h1:OVROq5+BT+g+ES+heRewy7NU2f147o2QyMortckSXek=
github.com/anchore/vulnscan-db v0.0.0-20200608121247-dc89e4e50bb5 h1:V3vDhg/YdBCaL2AqukuRMwSD/MdKdrg81dEHJ+YsfrQ=
github.com/anchore/vulnscan-db v0.0.0-20200608121247-dc89e4e50bb5/go.mod h1:HNoSeIuRTZC30XBlOIFSOEVMMuj7vkijUZR/nl6L2Uc=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apex/log v1.1.4/go.mod h1:AlpoD9aScyQfJDVHmLMEcx4oU6LqzkWp4Mg9GdAcEvQ=
github.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs=
github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=
@ -148,17 +129,21 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.19.45/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.25.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.31.6 h1:nKjQbpXhdImctBh1e0iLg9iQW/X297LPPuY/9f92R2k=
github.com/aws/aws-sdk-go v1.31.6/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
@ -173,6 +158,7 @@ github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oD
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -224,7 +210,6 @@ github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BU
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible h1:G2hY8RD7jB9QaSmcb8mYEIg8QbEvVAB7se8+lXHZHfg=
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ=
@ -311,6 +296,7 @@ github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4er
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@ -362,10 +348,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-containerregistry v0.0.0-20200430153450-5cbd060f5c92/go.mod h1:pD1UFYs7MCAx+ZLShBdttcaOSbyc8F9Na/9IZLNwJeA=
github.com/google/go-containerregistry v0.0.0-20200521151920-a873a21aff23 h1:42qjTtGOVRefA89EPBmd2Vm+m9x/WrZiCq9n022khSE=
github.com/google/go-containerregistry v0.0.0-20200521151920-a873a21aff23/go.mod h1:pD1UFYs7MCAx+ZLShBdttcaOSbyc8F9Na/9IZLNwJeA=
github.com/google/go-containerregistry v0.0.0-20200601195303-96cf69f03a3c h1:skQHUoy/S0WT+HFlN4MuDQCHsc6vTgKTMaKA3QKGABI=
github.com/google/go-containerregistry v0.0.0-20200601195303-96cf69f03a3c/go.mod h1:npTSyywOeILcgWqd+rvtzGWflIPPcBQhYoOONaY4ltM=
github.com/google/go-containerregistry v0.1.0 h1:hL5mVw7cTX3SBr64Arpv+cJH93L+Z9Q6WjckImYLB3g=
github.com/google/go-containerregistry v0.1.0/go.mod h1:npTSyywOeILcgWqd+rvtzGWflIPPcBQhYoOONaY4ltM=
github.com/google/go-containerregistry v0.1.1 h1:AG8FSAfXglim2l5qSrqp5VK2Xl03PiBf25NiTGGamws=
@ -377,6 +359,7 @@ github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwG
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible h1:xmapqc1AyLoB+ddYT6r04bD9lIjlOqGaREovi0SzFaE=
github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -392,13 +375,14 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.3.0/go.mod h1:i1DMg/Lu8Sz5yYl25iOdmc5CT5qusaa+zmRWs16741s=
github.com/google/wire v0.4.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww=
github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gookit/color v1.2.4/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw=
@ -425,7 +409,11 @@ github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBt
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-getter v1.4.1 h1:3A2Mh8smGFcf5M+gmcv898mZdrxpseik45IpcyISLsA=
github.com/hashicorp/go-getter v1.4.1/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
@ -435,10 +423,13 @@ github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+
github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
@ -466,6 +457,7 @@ github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=
@ -478,12 +470,11 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@ -495,12 +486,11 @@ github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d h1:X4cedH4
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d/go.mod h1:o8sgWoz3JADecfc/cTYD92/Et1yMqMy0utV1z+VaZao=
github.com/knqyf263/go-version v1.1.1 h1:+MpcBC9b7rk5ihag8Y/FLG8get1H2GjniwKQ+9DxI2o=
github.com/knqyf263/go-version v1.1.1/go.mod h1:0tBvHvOBSf5TqGNcY+/ih9o8qo3R16iZCpB9rP0D3VM=
github.com/knqyf263/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/knqyf263/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
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=
@ -516,6 +506,7 @@ github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.5.2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
@ -537,15 +528,14 @@ github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
@ -561,6 +551,7 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
@ -624,6 +615,7 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -668,6 +660,7 @@ github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83/go.mod h1:vvbZ2Ae7A
github.com/securego/gosec v0.0.0-20200401082031-e946c8c39989/go.mod h1:i9l/TNj+yDFh9SZXUTvspXTjbFXgZGP/UvhU1S65A4A=
github.com/securego/gosec/v2 v2.3.0/go.mod h1:UzeVyUXbxukhLeHKV3VVqo7HdoQR9MrRfFmZYotn8ME=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
@ -676,7 +669,6 @@ github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOms
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
@ -698,6 +690,8 @@ github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/afero v1.3.0 h1:Ysnmjh1Di8EaWaBv40CYR4IdaIsBc5996Gh1oZzCBKk=
github.com/spf13/afero v1.3.0/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
@ -752,7 +746,9 @@ github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaoz
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4=
github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
@ -765,12 +761,6 @@ github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOV
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vdemeester/k8s-pkg-credentialprovider v1.17.4/go.mod h1:inCTmtUdr5KJbreVojo06krnTgaeAz/Z7lynpPk/Q2c=
github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d h1:KOxOL6qpmqwoPloNwi+CEgc1ayjHNOFNrvoOmeDOjDg=
github.com/wagoodman/go-partybus v0.0.0-20200526224238-eb215533f07d/go.mod h1:JPirS5jde/CF5qIjcK4WX+eQmKXdPc6vcZkJ/P0hfPw=
github.com/wagoodman/go-progress v0.0.0-20200526224006-dd1404d54b0b h1:UDJoympq2F2QqhIu0wF6PtI+Apq1sW3TobBoZOrUTa8=
github.com/wagoodman/go-progress v0.0.0-20200526224006-dd1404d54b0b/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA=
github.com/wagoodman/jotframe v0.0.0-20200414171733-a67b24625ea8 h1:mHqUlTt7fcYbRw0fR6WTgk+XkWb7u6394PQsXzRcifo=
github.com/wagoodman/jotframe v0.0.0-20200414171733-a67b24625ea8/go.mod h1:DzXZ1wfRedNhC3KQTick8Gf3CEPMFHsP5k4R/ldjKtw=
github.com/xanzy/go-gitlab v0.31.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug=
github.com/xanzy/go-gitlab v0.32.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
@ -787,6 +777,7 @@ go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@ -806,12 +797,12 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -924,7 +915,6 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190302025703-b6889370fb10/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -964,8 +954,6 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602100848-8d3cce7afc34 h1:u6CI7A++8r4SItZHYe2cWeAEndN4p1p+3Oum/Ft2EzM=
golang.org/x/sys v0.0.0-20200602100848-8d3cce7afc34/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8Hk91vNtfvrymzwiei38=
@ -1076,6 +1064,7 @@ google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.25.0 h1:LodzhlzZEUfhXzNUMIfVlf9Gr6Ua5MMtoFWh7+f47qA=
google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -1116,14 +1105,8 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaR
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece h1:1YM0uhfumvoDu9sx8+RyWwTI63zoCQvI23IYFRlvte0=
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200602104108-2bb8d6132df6 h1:fsxmG3uIxSjgTNy6zSkdHSyElfRV0Tq+yzS+Ukjthx0=
google.golang.org/genproto v0.0.0-20200602104108-2bb8d6132df6/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200603110839-e855014d5736 h1:+IE3xTD+6Eb7QWG5JFp+dQr/XjKpjmrNkh4pdjTdHEs=
google.golang.org/genproto v0.0.0-20200603110839-e855014d5736/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200604104852-0b0486081ffb h1:ek2py5bOqzR7MR/6obzk0rXUgYCLmjyLnaO9ssT+l6w=
google.golang.org/genproto v0.0.0-20200604104852-0b0486081ffb/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200605102947-12044bf5ea91 h1:ES+5k7Xz+sYByd2L7mvcanaIuY0Iz3L3O6OhN+cRdu8=
google.golang.org/genproto v0.0.0-20200605102947-12044bf5ea91/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200615140333-fd031eab31e7 h1:1N7l1PuXZwEK7OhHdmKQROOM75PnUjABGwvVRbLBgFk=
google.golang.org/genproto v0.0.0-20200615140333-fd031eab31e7/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@ -1159,6 +1142,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=

View file

@ -8,6 +8,7 @@ import (
"github.com/adrg/xdg"
"github.com/anchore/imgbom/imgbom/scope"
"github.com/anchore/vulnscan/internal"
"github.com/anchore/vulnscan/vulnscan/db"
"github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
"go.uber.org/zap/zapcore"
@ -25,6 +26,7 @@ type Application struct {
Quiet bool `mapstructure:"quiet"`
Log Logging `mapstructure:"log"`
CliOptions CliOnlyOptions
Db Database `mapstructure:"db"`
}
type Logging struct {
@ -34,10 +36,28 @@ type Logging struct {
FileLocation string `mapstructure:"file"`
}
type Database struct {
Dir string `mapstructure:"cache-dir"`
UpdateURL string `mapstructure:"update-url"`
UpdateOnStartup bool `mapstructure:"update-on-startup"`
}
func (d Database) ToCuratorConfig() db.Config {
return db.Config{
DbDir: d.Dir,
ListingURL: d.UpdateURL,
}
}
func setNonCliDefaultValues(v *viper.Viper) {
v.SetDefault("log.level", "")
v.SetDefault("log.file", "")
v.SetDefault("log.structured", false)
// e.g. ~/.cache/appname/db
v.SetDefault("db.cache-dir", path.Join(xdg.CacheHome, internal.ApplicationName, "db"))
// TODO: change me to the production URL at release
v.SetDefault("db.update-url", "http://localhost:5000/listing.json")
v.SetDefault("db.update-on-startup", true)
}
func LoadConfigFromFile(v *viper.Viper, cliOpts *CliOnlyOptions) (*Application, error) {

View file

@ -1,17 +0,0 @@
package db
import (
"github.com/anchore/vulnscan-db/pkg/db"
"github.com/anchore/vulnscan-db/pkg/sqlite"
)
func GetStore() db.VulnStore {
// TODO: add connection options and info
// TODO: we are ignoreing cleanup/close function (not good)
store, _, err := sqlite.NewStore(nil)
if err != nil {
// TODO: replace me
panic(err)
}
return store
}

View file

@ -1,39 +0,0 @@
package db
import (
"github.com/anchore/vulnscan-db/pkg/db"
)
type MockDb struct {
data map[string]map[string][]db.Vulnerability
}
func NewMockDb() *MockDb {
d := MockDb{
data: make(map[string]map[string][]db.Vulnerability),
}
d.populate()
return &d
}
func (d *MockDb) populate() {
d.data["debian:8"] = map[string][]db.Vulnerability{
"neutron": {
{
Name: "neutron",
NamespaceName: "debian:8",
Version: "2014.1.3-6",
VulnerabilityID: "CVE-2014-fake",
VersionFormat: "dpkg",
},
},
}
}
func (d *MockDb) Add(v *db.Vulnerability) error {
return nil
}
func (d *MockDb) Get(namespace, name string) ([]db.Vulnerability, error) {
return d.data[namespace][name], nil
}

69
internal/file/copy.go Normal file
View file

@ -0,0 +1,69 @@
package file
import (
"fmt"
"io"
"io/ioutil"
"os"
"path"
"github.com/spf13/afero"
)
func CopyDir(fs afero.Fs, src string, dst string) error {
var err error
var fds []os.FileInfo
var srcinfo os.FileInfo
if srcinfo, err = fs.Stat(src); err != nil {
return err
}
if err = fs.MkdirAll(dst, srcinfo.Mode()); err != nil {
return err
}
if fds, err = ioutil.ReadDir(src); err != nil {
return err
}
for _, fd := range fds {
srcPath := path.Join(src, fd.Name())
dstPath := path.Join(dst, fd.Name())
if fd.IsDir() {
if err = CopyDir(fs, srcPath, dstPath); err != nil {
return fmt.Errorf("could not copy dir (%s -> %s): %w", srcPath, dstPath, err)
}
} else {
if err = CopyFile(fs, srcPath, dstPath); err != nil {
return fmt.Errorf("could not copy file (%s -> %s): %w", srcPath, dstPath, err)
}
}
}
return nil
}
func CopyFile(fs afero.Fs, src, dst string) error {
var err error
var srcFd afero.File
var dstFd afero.File
var srcinfo os.FileInfo
if srcFd, err = fs.Open(src); err != nil {
return err
}
defer srcFd.Close()
if dstFd, err = fs.Create(dst); err != nil {
return err
}
defer dstFd.Close()
if _, err = io.Copy(dstFd, srcFd); err != nil {
return err
}
if srcinfo, err = fs.Stat(src); err != nil {
return err
}
return fs.Chmod(dst, srcinfo.Mode())
}

15
internal/file/exists.go Normal file
View file

@ -0,0 +1,15 @@
package file
import (
"os"
"github.com/spf13/afero"
)
func Exists(fs afero.Fs, path string) bool {
info, err := fs.Stat(path)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}

22
internal/file/getter.go Normal file
View file

@ -0,0 +1,22 @@
package file
import "github.com/hashicorp/go-getter"
type Getter interface {
// GetFile downloads the give URL into the given path. The URL must reference a single file.
GetFile(dst, src string) error
// Get downloads the given URL into the given directory. The directory must already exist.
GetToDir(dst, src string) error
}
type HashiGoGetter struct {
}
func (g HashiGoGetter) GetFile(dst, src string) error {
return getter.GetFile(dst, src)
}
func (g HashiGoGetter) GetToDir(dst, src string) error {
return getter.Get(dst, src)
}

45
internal/file/hasher.go Normal file
View file

@ -0,0 +1,45 @@
package file
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"
"io"
"strings"
"github.com/spf13/afero"
)
func ValidateByHash(fs afero.Fs, path, hashStr string) (bool, error) {
var hasher hash.Hash
switch {
case strings.HasPrefix(hashStr, "sha256:"):
hasher = sha256.New()
default:
return false, fmt.Errorf("hasher not supported or specified (given: %s)", hashStr)
}
hashNoPrefix := strings.Split(hashStr, ":")[1]
actualHash, err := HashFile(fs, path, hasher)
if err != nil {
return false, err
}
return actualHash == hashNoPrefix, nil
}
func HashFile(fs afero.Fs, path string, hasher hash.Hash) (string, error) {
f, err := fs.Open(path)
if err != nil {
return "", fmt.Errorf("failed to open file '%s': %w", path, err)
}
defer f.Close()
if _, err := io.Copy(hasher, f); err != nil {
return "", fmt.Errorf("failed to hash file '%s': %w", path, err)
}
return hex.EncodeToString(hasher.Sum(nil)), nil
}

View file

@ -1,20 +1,20 @@
package internal
type Set map[string]struct{}
type StringSet map[string]struct{}
func NewStringSet() Set {
return make(Set)
func NewStringSet() StringSet {
return make(StringSet)
}
func (s Set) Add(i string) {
func (s StringSet) Add(i string) {
s[i] = struct{}{}
}
func (s Set) Remove(i string) {
func (s StringSet) Remove(i string) {
delete(s, i)
}
func (s Set) Contains(i string) bool {
func (s StringSet) Contains(i string) bool {
_, ok := s[i]
return ok
}

208
vulnscan/db/curator.go Normal file
View file

@ -0,0 +1,208 @@
package db
import (
"fmt"
"io/ioutil"
"os"
"path"
"github.com/anchore/vulnscan-db/pkg/db"
"github.com/anchore/vulnscan-db/pkg/sqlite"
"github.com/anchore/vulnscan/internal/file"
"github.com/anchore/vulnscan/internal/log"
"github.com/hashicorp/go-version"
"github.com/spf13/afero"
)
const (
supportedVersion = ">=1.0.0, <2.0.0"
dbFileName = "vulnerability.db"
)
type Config struct {
DbDir string
ListingURL string
}
type Curator struct {
fs afero.Fs
config Config
client file.Getter
versionConstraint version.Constraints
}
func NewCurator(cfg Config) (Curator, error) {
constraint, err := version.NewConstraint(supportedVersion)
if err != nil {
return Curator{}, fmt.Errorf("unable to set DB curator version constraint (%s): %w", supportedVersion, err)
}
return Curator{
config: cfg,
fs: afero.NewOsFs(),
versionConstraint: constraint,
client: &file.HashiGoGetter{},
}, nil
}
func (c *Curator) GetStore() (db.VulnStore, error) {
// ensure the DB is ok
err := c.Validate()
if err != nil {
return nil, fmt.Errorf("vulnerability database is corrupt (run db update to correct): %+v", err)
}
// provide an abstraction for the underlying store
connectOptions := sqlite.Options{
FilePath: path.Join(c.config.DbDir, dbFileName),
}
store, _, err := sqlite.NewStore(&connectOptions)
if err != nil {
return nil, fmt.Errorf("unable to get vulnerability store: %w", err)
}
return store, nil
}
func (c *Curator) Delete() error {
return c.fs.RemoveAll(c.config.DbDir)
}
func (c *Curator) IsUpdateAvailable() (bool, *ListingEntry, error) {
log.Debugf("checking for available database updates")
listing, err := newListingFromURL(c.fs, c.client, c.config.ListingURL)
if err != nil {
return false, nil, fmt.Errorf("failed to get listing file: %w", err)
}
updateEntry := listing.bestUpdate(c.versionConstraint)
if updateEntry == nil {
return false, nil, fmt.Errorf("no db candidates with correct version available (maybe there is an application update available?)")
}
log.Debugf("found database update candidate: %s", updateEntry)
// compare created data to current db date
current, err := newMetadataFromDir(c.fs, c.config.DbDir)
if err != nil {
return false, nil, fmt.Errorf("current metadata corrupt: %w", err)
}
if current.isSupercededBy(updateEntry) {
log.Debugf("database update available: %s", updateEntry)
return true, updateEntry, nil
}
log.Debugf("no database update available")
return false, nil, nil
}
// Validate checks the current database to ensure file integrity and if it can be used by this version of the application.
func (c *Curator) Validate() error {
return c.validate(c.config.DbDir)
}
// TODO: implement me
// func (c *Curator) ImportFrom(manualDbPath string) error {
// // TODO: ...
// // cp file to tempdir
// // validate tempdir
// // activate
// return nil
// }
func (c *Curator) UpdateTo(listing *ListingEntry) error {
// note: the temp directory is persisted upon download/validation/activation failure to allow for investigation
tempDir, err := c.download(listing)
if err != nil {
return err
}
err = c.validate(tempDir)
if err != nil {
return err
}
err = c.activate(tempDir)
if err != nil {
return err
}
return c.fs.RemoveAll(tempDir)
}
func (c *Curator) download(listing *ListingEntry) (string, error) {
// get a temp dir
tempDir, err := ioutil.TempDir("", "vulnscan-scratch")
if err != nil {
return "", fmt.Errorf("unable to create db temp dir: %w", err)
}
// download the db to the temp dir
url := listing.URL
// from go-getter, adding a checksum as a query string will validate the payload after download
// note: the checksum query parameter is not sent to the server
query := url.Query()
query.Add("checksum", listing.Checksum)
url.RawQuery = query.Encode()
// go-getter will automatically extract all files within the archive to the temp dir
err = c.client.GetToDir(tempDir, listing.URL.String())
if err != nil {
return "", fmt.Errorf("unable to download db: %w", err)
}
return tempDir, nil
}
func (c *Curator) validate(dbDirPath string) error {
// check that the disk checksum still matches the db payload
metadata, err := newMetadataFromDir(c.fs, dbDirPath)
if err != nil {
return fmt.Errorf("failed to parse database metadata (%s): %w", dbDirPath, err)
}
if metadata == nil {
return fmt.Errorf("database metadata not found: %s", dbDirPath)
}
dbPath := path.Join(dbDirPath, dbFileName)
valid, err := file.ValidateByHash(c.fs, dbPath, metadata.Checksum)
if err != nil {
return err
}
if !valid {
return fmt.Errorf("bad db checksum (%s)", dbDirPath)
}
if !c.versionConstraint.Check(metadata.Version) {
return fmt.Errorf("unsupported database version: version=%s constraint=%s", metadata.Version.String(), c.versionConstraint.String())
}
// TODO: add version checks here to ensure this version of the application can use this database version (relative to what the DB says, not JUST the metadata!)
return nil
}
// activate swaps over the downloaded db to the application directory
func (c *Curator) activate(aDbDirPath string) error {
_, err := c.fs.Stat(c.config.DbDir)
if !os.IsNotExist(err) {
// remove any previous databases
err = c.Delete()
if err != nil {
return fmt.Errorf("failed to purge existing database: %w", err)
}
}
// ensure there is an application db directory
err = c.fs.MkdirAll(c.config.DbDir, 0755)
if err != nil {
return fmt.Errorf("failed to create db directory: %w", err)
}
// activate the new db cache
return file.CopyDir(c.fs, aDbDirPath, c.config.DbDir)
}

182
vulnscan/db/curator_test.go Normal file
View file

@ -0,0 +1,182 @@
package db
import (
"fmt"
"net/url"
"testing"
"time"
"github.com/anchore/vulnscan/internal"
"github.com/anchore/vulnscan/internal/file"
"github.com/hashicorp/go-version"
"github.com/spf13/afero"
)
type testGetter struct {
file map[string]string
dir map[string]string
calls internal.StringSet
fs afero.Fs
}
func newTestGetter(fs afero.Fs, f, d map[string]string) *testGetter {
return &testGetter{
file: f,
dir: d,
calls: internal.NewStringSet(),
fs: fs,
}
}
// GetFile downloads the give URL into the given path. The URL must reference a single file.
func (g *testGetter) GetFile(dst, src string) error {
g.calls.Add(src)
if _, ok := g.file[src]; !ok {
return fmt.Errorf("blerg, no file!")
}
return afero.WriteFile(g.fs, dst, []byte(g.file[src]), 0755)
}
// Get downloads the given URL into the given directory. The directory must already exist.
func (g *testGetter) GetToDir(dst, src string) error {
g.calls.Add(src)
if _, ok := g.dir[src]; !ok {
return fmt.Errorf("blerg, no file!")
}
return afero.WriteFile(g.fs, dst, []byte(g.dir[src]), 0755)
}
func newTestCurator(fs afero.Fs, getter file.Getter, dbDir, metadataUrl string) (Curator, error) {
c, err := NewCurator(Config{
DbDir: dbDir,
ListingURL: metadataUrl,
})
c.client = getter
c.fs = fs
return c, err
}
func TestCuratorDownload(t *testing.T) {
tests := []struct {
name string
entry *ListingEntry
expectedURL string
err bool
}{
{
name: "download populates returned tempdir",
entry: &ListingEntry{
Built: time.Date(2020, 06, 13, 17, 13, 13, 0, time.UTC),
URL: mustUrl(url.Parse("http://a-url/payload.tar.gz")),
Checksum: "sha256:deadbeefcafe",
},
expectedURL: "http://a-url/payload.tar.gz?checksum=sha256%3Adeadbeefcafe",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
metadataUrl := "http://metadata.io"
contents := "CONTENTS!!!"
files := map[string]string{}
dirs := map[string]string{
test.expectedURL: contents,
}
fs := afero.NewMemMapFs()
getter := newTestGetter(fs, files, dirs)
cur, err := newTestCurator(fs, getter, "/tmp/dbdir", metadataUrl)
if err != nil {
t.Fatalf("failed making curator: %+v", err)
}
path, err := cur.download(test.entry)
if !getter.calls.Contains(test.expectedURL) {
t.Fatalf("never made the appropriate fetch call: %+v", getter.calls)
}
f, err := fs.Open(path)
if err != nil {
t.Fatalf("no db file: %+v", err)
}
actual, err := afero.ReadAll(f)
if err != nil {
t.Fatalf("bad db file read: %+v", err)
}
if string(actual) != contents {
t.Fatalf("bad contents: %+v", string(actual))
}
})
}
}
func TestCuratorValidate(t *testing.T) {
tests := []struct {
name string
fixture string
constraint string
err bool
}{
{
name: "good checksum & good constraint",
fixture: "test-fixtures/curator-validate/good-checksum",
constraint: ">=1.0.0, <2.0.0",
err: false,
},
{
name: "good checksum & bad constraint",
fixture: "test-fixtures/curator-validate/good-checksum",
constraint: ">=0.0.0, <1.0.0",
err: true,
},
{
name: "bad checksum & good constraint",
fixture: "test-fixtures/curator-validate/bad-checksum",
constraint: ">=1.0.0, <2.0.0",
err: true,
},
{
name: "bad checksum & bad constraint",
fixture: "test-fixtures/curator-validate/bad-checksum",
constraint: ">=0.0.0, <1.0.0",
err: true,
},
{
name: "allow equal version",
fixture: "test-fixtures/curator-validate/good-checksum",
constraint: ">=1.1.0",
err: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
metadataUrl := "http://metadata.io"
fs := afero.NewOsFs()
getter := newTestGetter(fs, nil, nil)
cur, err := newTestCurator(fs, getter, "/tmp/dbdir", metadataUrl)
if err != nil {
t.Fatalf("failed making curator: %+v", err)
}
constraint, err := version.NewConstraint(test.constraint)
if err != nil {
t.Errorf("unable to set DB curator version constraint (%s): %w", test.constraint, err)
}
cur.versionConstraint = constraint
err = cur.validate(test.fixture)
if err == nil && test.err {
t.Errorf("expected an error but got none")
} else if err != nil && !test.err {
t.Errorf("expected no error, got: %+v", err)
}
})
}
}

82
vulnscan/db/listing.go Normal file
View file

@ -0,0 +1,82 @@
package db
import (
"encoding/json"
"fmt"
"github.com/anchore/vulnscan/internal/file"
"github.com/anchore/vulnscan/internal/log"
"github.com/hashicorp/go-version"
"github.com/spf13/afero"
)
// TODO: move all of this to vulnscan-db
type Listing struct {
Latest ListingEntry `json:"latest"`
Available []ListingEntry `json:"available"`
}
func newListingFromPath(fs afero.Fs, path string) (Listing, error) {
f, err := fs.Open(path)
if err != nil {
return Listing{}, fmt.Errorf("unable to open DB listing path: %w", err)
}
defer f.Close()
var l Listing
err = json.NewDecoder(f).Decode(&l)
if err != nil {
return Listing{}, fmt.Errorf("unable to parse DB listing: %w", err)
}
return l, nil
}
func newListingFromURL(fs afero.Fs, getter file.Getter, listingURL string) (Listing, error) {
tempFile, err := afero.TempFile(fs, "", "vulnscan-listing")
if err != nil {
return Listing{}, fmt.Errorf("unable to create listing temp file: %w", err)
}
defer func() {
err := fs.RemoveAll(tempFile.Name())
if err != nil {
log.Errorf("failed to remove file (%s): %w", tempFile.Name(), err)
}
}()
// download the listing file
err = getter.GetFile(tempFile.Name(), listingURL)
if err != nil {
return Listing{}, fmt.Errorf("unable to download listing: %w", err)
}
// parse the listing file
listing, err := newListingFromPath(fs, tempFile.Name())
if err != nil {
return Listing{}, err
}
return listing, nil
}
func (l *Listing) bestUpdate(constraint version.Constraints) *ListingEntry {
// extract the latest available db
candidates := []ListingEntry{l.Latest}
candidates = append(candidates, l.Available...)
// TODO: sort candidates by version and built date
for _, candidate := range candidates {
log.Debugf("found update: %s", candidate)
}
var updateEntry *ListingEntry
for _, candidate := range candidates {
if constraint.Check(candidate.Version) {
copy := candidate
updateEntry = &copy
break
}
}
return updateEntry
}

View file

@ -0,0 +1,67 @@
package db
import (
"encoding/json"
"fmt"
"net/url"
"time"
"github.com/hashicorp/go-version"
)
// TODO: move all of this to vulnscan-db
type ListingEntry struct {
Built time.Time // RFC 3339
Version *version.Version
URL *url.URL
Checksum string
}
type ListingEntryJSON struct {
Built string `json:"built"`
Version string `json:"version"`
URL string `json:"url"`
Checksum string `json:"checksum"`
}
func (l ListingEntryJSON) ToListingEntry() (ListingEntry, error) {
build, err := time.Parse(time.RFC3339, l.Built)
if err != nil {
return ListingEntry{}, fmt.Errorf("cannot convert built time (%s): %+v", l.Built, err)
}
ver, err := version.NewVersion(l.Version)
if err != nil {
return ListingEntry{}, fmt.Errorf("cannot parse version (%s): %+v", l.Version, err)
}
u, err := url.Parse(l.URL)
if err != nil {
return ListingEntry{}, fmt.Errorf("cannot parse url (%s): %+v", l.URL, err)
}
return ListingEntry{
Built: build.UTC(),
Version: ver,
URL: u,
Checksum: l.Checksum,
}, nil
}
func (l *ListingEntry) UnmarshalJSON(data []byte) error {
var lej ListingEntryJSON
if err := json.Unmarshal(data, &lej); err != nil {
return err
}
le, err := lej.ToListingEntry()
if err != nil {
return err
}
*l = le
return nil
}
func (l ListingEntry) String() string {
return fmt.Sprintf("Listing(url=%s)", l.URL)
}

115
vulnscan/db/listing_test.go Normal file
View file

@ -0,0 +1,115 @@
package db
import (
"net/url"
"testing"
"time"
"github.com/go-test/deep"
"github.com/hashicorp/go-version"
"github.com/spf13/afero"
)
func mustUrl(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
return u
}
func mustConst(u version.Constraints, err error) version.Constraints {
if err != nil {
panic(err)
}
return u
}
func TestNewListingFromPath(t *testing.T) {
tests := []struct {
fixture string
expected Listing
err bool
}{
{
fixture: "test-fixtures/listing.json",
expected: Listing{
Latest: ListingEntry{
Built: time.Date(2020, 06, 13, 17, 13, 13, 0, time.UTC),
URL: mustUrl(url.Parse("http://localhost:5000/vulnerability-db-v1.1.0+2020-6-13.tar.gz")),
Version: version.Must(version.NewVersion("1.1.0")),
Checksum: "sha256:dcd6a285c839a7c65939e20c251202912f64826be68609dfc6e48df7f853ddc8",
},
Available: []ListingEntry{
{
Built: time.Date(2020, 06, 12, 16, 12, 12, 0, time.UTC),
URL: mustUrl(url.Parse("http://localhost:5000/vulnerability-db-v0.2.0+2020-6-12.tar.gz")),
Version: version.Must(version.NewVersion("0.2.0")),
Checksum: "sha256:e20c251202948df7f853ddc812f64826bdcd6a285c839a7c65939e68609dfc6e",
},
},
},
},
}
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
listing, err := newListingFromPath(afero.NewOsFs(), test.fixture)
if err != nil && !test.err {
t.Fatalf("failed to get metadata: %+v", err)
} else if err == nil && test.err {
t.Fatalf("expected errer but got none")
}
for _, diff := range deep.Equal(listing, test.expected) {
t.Errorf("listing difference: %s", diff)
}
})
}
}
func TestListingBestUpdate(t *testing.T) {
tests := []struct {
fixture string
constraint version.Constraints
expected *ListingEntry
}{
{
fixture: "test-fixtures/listing.json",
constraint: mustConst(version.NewConstraint("> 1.0.0, < 2.0.0")),
expected: &ListingEntry{
Built: time.Date(2020, 06, 13, 17, 13, 13, 0, time.UTC),
URL: mustUrl(url.Parse("http://localhost:5000/vulnerability-db-v1.1.0+2020-6-13.tar.gz")),
Version: version.Must(version.NewVersion("1.1.0")),
Checksum: "sha256:dcd6a285c839a7c65939e20c251202912f64826be68609dfc6e48df7f853ddc8",
},
},
{
fixture: "test-fixtures/listing.json",
constraint: mustConst(version.NewConstraint("> 0.0.0, < 1.0.0")),
expected: &ListingEntry{
Built: time.Date(2020, 06, 12, 16, 12, 12, 0, time.UTC),
URL: mustUrl(url.Parse("http://localhost:5000/vulnerability-db-v0.2.0+2020-6-12.tar.gz")),
Version: version.Must(version.NewVersion("0.2.0")),
Checksum: "sha256:e20c251202948df7f853ddc812f64826bdcd6a285c839a7c65939e68609dfc6e",
},
},
}
for _, test := range tests {
t.Run(test.constraint.String(), func(t *testing.T) {
listing, err := newListingFromPath(afero.NewOsFs(), test.fixture)
if err != nil {
t.Fatalf("failed to get metadata: %+v", err)
}
actual := listing.bestUpdate(test.constraint)
if actual == nil && test.expected != nil || actual != nil && test.expected == nil {
t.Fatalf("mismatched best candidate expectations")
}
for _, diff := range deep.Equal(actual, test.expected) {
t.Errorf("listing entry difference: %s", diff)
}
})
}
}

110
vulnscan/db/metadata.go Normal file
View file

@ -0,0 +1,110 @@
package db
import (
"encoding/json"
"fmt"
"path"
"time"
"github.com/anchore/vulnscan/internal/file"
"github.com/anchore/vulnscan/internal/log"
"github.com/hashicorp/go-version"
"github.com/spf13/afero"
)
const metadataFileName = "metadata.json"
type Metadata struct {
Built time.Time
Version *version.Version
Checksum string
}
type MetadataJSON struct {
Built string `json:"built"` // RFC 3339
Version string `json:"version"`
Checksum string `json:"checksum"`
}
func (m MetadataJSON) ToMetadata() (Metadata, error) {
build, err := time.Parse(time.RFC3339, m.Built)
if err != nil {
return Metadata{}, fmt.Errorf("cannot convert built time (%s): %+v", m.Built, err)
}
ver, err := version.NewVersion(m.Version)
if err != nil {
return Metadata{}, fmt.Errorf("cannot parse version (%s): %+v", m.Version, err)
}
metadata := Metadata{
Built: build.UTC(),
Version: ver,
Checksum: m.Checksum,
}
return metadata, nil
}
func metadataPath(dir string) string {
return path.Join(dir, metadataFileName)
}
func newMetadataFromDir(fs afero.Fs, dir string) (*Metadata, error) {
metadataFilePath := metadataPath(dir)
if !file.Exists(fs, metadataFilePath) {
return nil, nil
}
f, err := fs.Open(metadataFilePath)
if err != nil {
return nil, fmt.Errorf("unable to open DB metadata path (%s): %w", metadataFilePath, err)
}
defer f.Close()
var m Metadata
err = json.NewDecoder(f).Decode(&m)
if err != nil {
return nil, fmt.Errorf("unable to parse DB metadata (%s): %w", metadataFilePath, err)
}
return &m, nil
}
func (m *Metadata) UnmarshalJSON(data []byte) error {
var mj MetadataJSON
if err := json.Unmarshal(data, &mj); err != nil {
return err
}
me, err := mj.ToMetadata()
if err != nil {
return err
}
*m = me
return nil
}
func (m *Metadata) isSupercededBy(entry *ListingEntry) bool {
if m == nil {
log.Debugf("cannot find existing metadata, using update...")
// any valid update beats no database, use it!
return true
}
if entry.Version.GreaterThan(m.Version) {
log.Debugf("update is a newer version than the current database, using update...")
// the listing is newer than the existing db, use it!
return true
}
if entry.Built.After(m.Built) {
log.Debugf("existing database (%s) is older than candidate update (%s), using update...", m.Built.String(), entry.Built.String())
// the listing is newer than the existing db, use it!
return true
}
log.Debugf("existing database is already up to date")
return false
}
func (m Metadata) String() string {
return fmt.Sprintf("Metadata(built=%s version=%s checksum=%s)", m.Built, m.Version, m.Checksum)
}

View file

@ -0,0 +1,107 @@
package db
import (
"testing"
"time"
"github.com/go-test/deep"
"github.com/hashicorp/go-version"
"github.com/spf13/afero"
)
func TestMetadataParse(t *testing.T) {
tests := []struct {
fixture string
expected Metadata
err bool
}{
{
fixture: "test-fixtures/metadata-gocase",
expected: Metadata{
Built: time.Date(2020, 06, 15, 14, 02, 36, 0, time.UTC),
Version: version.Must(version.NewVersion("0.2.0")),
Checksum: "sha256:dcd6a285c839a7c65939e20c251202912f64826be68609dfc6e48df7f853ddc8",
},
},
{
fixture: "test-fixtures/metadata-edt-timezone",
expected: Metadata{
Built: time.Date(2020, 06, 15, 18, 02, 36, 0, time.UTC),
Version: version.Must(version.NewVersion("0.2.0")),
Checksum: "sha256:dcd6a285c839a7c65939e20c251202912f64826be68609dfc6e48df7f853ddc8",
},
},
}
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
metadata, err := newMetadataFromDir(afero.NewOsFs(), test.fixture)
if err != nil && !test.err {
t.Fatalf("failed to get metadata: %+v", err)
} else if err == nil && test.err {
t.Fatalf("expected errer but got none")
}
if metadata == nil {
t.Fatalf("metadata not found: %+v", test.fixture)
}
for _, diff := range deep.Equal(*metadata, test.expected) {
t.Errorf("metadata difference: %s", diff)
}
})
}
}
func TestMetadataIsSupercededBy(t *testing.T) {
tests := []struct {
name string
current *Metadata
update *ListingEntry
expectedToSupercede bool
}{
{
name: "prefer updated versions over later dates",
expectedToSupercede: true,
current: &Metadata{
Built: time.Date(2020, 06, 15, 14, 02, 36, 0, time.UTC),
Version: version.Must(version.NewVersion("0.2.0")),
},
update: &ListingEntry{
Built: time.Date(2020, 06, 13, 17, 13, 13, 0, time.UTC),
Version: version.Must(version.NewVersion("0.3.0")),
},
},
{
name: "prefer later dates when version is the same",
expectedToSupercede: false,
current: &Metadata{
Built: time.Date(2020, 06, 15, 14, 02, 36, 0, time.UTC),
Version: version.Must(version.NewVersion("1.1.0")),
},
update: &ListingEntry{
Built: time.Date(2020, 06, 13, 17, 13, 13, 0, time.UTC),
Version: version.Must(version.NewVersion("1.1.0")),
},
},
{
name: "prefer something over nothing",
expectedToSupercede: true,
current: nil,
update: &ListingEntry{
Built: time.Date(2020, 06, 13, 17, 13, 13, 0, time.UTC),
Version: version.Must(version.NewVersion("1.1.0")),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := test.current.isSupercededBy(test.update)
if test.expectedToSupercede != actual {
t.Errorf("failed supercede assertion: got %+v", actual)
}
})
}
}

View file

@ -0,0 +1,5 @@
{
"built": "2020-06-15T14:02:36Z",
"version": "1.1.0",
"checksum": "sha256:deadbeefcafe"
}

View file

@ -0,0 +1 @@
I can haz cve?

View file

@ -0,0 +1,5 @@
{
"built": "2020-06-15T14:02:36Z",
"version": "1.1.0",
"checksum": "sha256:3baf9c50c94e7f1e65bafac2e6a6d559fb177461dd25bf8fca7e6e9e9c266cb4"
}

View file

@ -0,0 +1 @@
I can haz cve?

View file

@ -0,0 +1,16 @@
{
"latest": {
"built": "2020-06-13T13:13:13-04:00",
"version": "1.1.0",
"url": "http://localhost:5000/vulnerability-db-v1.1.0+2020-6-13.tar.gz",
"checksum": "sha256:dcd6a285c839a7c65939e20c251202912f64826be68609dfc6e48df7f853ddc8"
},
"available": [
{
"built": "2020-06-12T12:12:12-04:00",
"version": "0.2.0",
"url": "http://localhost:5000/vulnerability-db-v0.2.0+2020-6-12.tar.gz",
"checksum": "sha256:e20c251202948df7f853ddc812f64826bdcd6a285c839a7c65939e68609dfc6e"
}
]
}

View file

@ -0,0 +1,7 @@
{
"built": "2020-06-15T14:02:36-04:00",
"updated": "2020-06-15T14:02:36-04:00",
"last-check": "2020-06-15T14:02:36-04:00",
"version": "0.2.0",
"checksum": "sha256:dcd6a285c839a7c65939e20c251202912f64826be68609dfc6e48df7f853ddc8"
}

View file

@ -0,0 +1,5 @@
{
"built": "2020-06-15T14:02:36Z",
"version": "0.2.0",
"checksum": "sha256:dcd6a285c839a7c65939e20c251202912f64826be68609dfc6e48df7f853ddc8"
}

View file

@ -42,15 +42,12 @@ func (pres *Presenter) Present(output io.Writer, catalog *pkg.Catalog, results r
Package: Package{Name: pkg.Name, Version: pkg.Version}},
)
//for _, match := range matches {
doc = append(
doc,
ResultObj{
Cve: match.Vulnerability.ID,
Package: Package{Name: pkg.Name, Version: pkg.Version}},
)
//}
}
bytes, err := json.Marshal(&doc)

View file

@ -15,34 +15,35 @@ import (
var update = flag.Bool("update", false, "update the *.golden files for json presenters")
var pkg1 = pkg.Package{
Name: "package-1",
Version: "1.0.1",
Type: pkg.DebPkg,
}
var pkg2 = pkg.Package{
Name: "package-2",
Version: "2.0.1",
Type: pkg.DebPkg,
}
var match1 = match.Match{
Type: match.ExactDirectMatch,
Vulnerability: vulnerability.Vulnerability{ID: "CVE-1999-0001"},
// XXX: matches also have a `pkg *pkg.Pkg` field
}
var match2 = match.Match{
Type: match.ExactIndirectMatch,
Vulnerability: vulnerability.Vulnerability{ID: "CVE-1999-0002"},
}
func TestJsonPresenter(t *testing.T) {
pres := NewPresenter()
var buffer bytes.Buffer
var pkg1 = pkg.Package{
Name: "package-1",
Version: "1.0.1",
Type: pkg.DebPkg,
}
var pkg2 = pkg.Package{
Name: "package-2",
Version: "2.0.1",
Type: pkg.DebPkg,
}
var match1 = match.Match{
Type: match.ExactDirectMatch,
Vulnerability: vulnerability.Vulnerability{ID: "CVE-1999-0001"},
Package: &pkg1,
}
var match2 = match.Match{
Type: match.ExactIndirectMatch,
Vulnerability: vulnerability.Vulnerability{ID: "CVE-1999-0002"},
Package: &pkg1,
}
results := result.NewResult()
results.Add(&pkg1, match1, match2)

View file

@ -1 +1 @@
[{"cve":"CVE-1999-0001","package":{"name":"package-1","version":"1.0.1"}},{"cve":"CVE-1999-0002","package":{"name":"package-1","version":"1.0.1"}}]
[{"cve":"CVE-1999-0001","package":{"name":"package-1","version":"1.0.1"}},{"cve":"CVE-1999-0001","package":{"name":"package-1","version":"1.0.1"}},{"cve":"CVE-1999-0002","package":{"name":"package-1","version":"1.0.1"}},{"cve":"CVE-1999-0002","package":{"name":"package-1","version":"1.0.1"}}]

View file

@ -19,6 +19,26 @@ func TestDistroNamespace_AllDistros(t *testing.T) {
version: "8",
expected: "debian:8",
},
{
dist: distro.Busybox,
version: "3.1.1",
expected: "busybox:3.1.1",
},
{
dist: distro.CentOS,
version: "7",
expected: "centos:7",
},
{
dist: distro.Ubuntu,
version: "18.04",
expected: "ubuntu:18.04",
},
{
dist: distro.RedHat,
version: "6",
expected: "redhat:6",
},
}
for _, test := range tests {