diff --git a/Makefile b/Makefile index 3f620be55..2eaff9ba4 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,10 @@ RESET := $(shell tput sgr0) TITLE := $(BOLD)$(PURPLE) SUCCESS := $(BOLD)$(GREEN) +ifndef TEMPDIR +$(error TEMPDIR is not set) +endif + .PHONY: all boostrap lint lint-fix unit coverage integration build-release all: lint unit integration @@ -26,10 +30,12 @@ bootstrap: lint: @printf '$(TITLE)Running linters$(RESET)\n' + test -z "$(shell gofmt -l -s .)" $(LINTCMD) lint-fix: @printf '$(TITLE)Running lint fixers$(RESET)\n' + gofmt -w -s . $(LINTCMD) --fix unit: diff --git a/cmd/root.go b/cmd/root.go index 62a6ad3bf..e1bef12d3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,12 +4,11 @@ import ( "fmt" "os" - "github.com/anchore/imgbom/internal" - "github.com/anchore/imgbom/imgbom" + "github.com/anchore/imgbom/imgbom/presenter" "github.com/anchore/imgbom/imgbom/scope" + "github.com/anchore/imgbom/internal" "github.com/anchore/stereoscope" - "github.com/spf13/cobra" ) @@ -18,6 +17,7 @@ const ApplicationName = "imgbom" var rootOptions struct { cfgFile string scope string + output string } var rootCmd = &cobra.Command{ @@ -50,6 +50,10 @@ func init() { // scan options rootCmd.Flags().StringVarP(&rootOptions.scope, "scope", "s", scope.AllLayersScope.String(), fmt.Sprintf("selection of layers to analyze, options=%v", scope.Options)) + + // output & formatting options + rootCmd.Flags().StringVarP(&rootOptions.output, "output", "o", presenter.JSONOption.String(), + fmt.Sprintf("report output formatter, options=%v", presenter.Options)) } func loadApplicationConfig() { @@ -57,10 +61,10 @@ func loadApplicationConfig() { } func doRunCmd(cmd *cobra.Command, args []string) { - img, err := stereoscope.GetImage(args[0]) - if err != nil { + var pres = presenter.GetPresenter(rootOptions.output) + if pres == nil { // TODO: replace with log and exit - panic(err) + panic("could not determine presenter") } scopeOption := scope.ParseOption(rootOptions.scope) @@ -69,12 +73,22 @@ func doRunCmd(cmd *cobra.Command, args []string) { panic(scopeOption) } + img, err := stereoscope.GetImage(args[0]) + if err != nil { + // TODO: replace with log and exit + panic(err) + } + defer stereoscope.Cleanup() + catalog, err := imgbom.CatalogImage(img, scopeOption) if err != nil { // TODO: replace with log and exit panic(err) } - // TODO: remove this with presenter implementation - fmt.Printf("%+v\n", catalog) + err = pres.Present(os.Stdout, img, catalog) + if err != nil { + // TODO: replace with log and exit + panic(err) + } } diff --git a/go.mod b/go.mod index 75ed9a058..f6745bdd4 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,11 @@ module github.com/anchore/imgbom go 1.14 require ( - github.com/anchore/stereoscope v0.0.0-20200518155435-f6c722e4572b + github.com/anchore/go-testutils v0.0.0-20200520222037-edc2bf1864fe + github.com/anchore/stereoscope v0.0.0-20200520221116-025e07f1c93e github.com/go-test/deep v1.0.6 - github.com/golang/protobuf v1.4.2 // indirect github.com/hashicorp/go-multierror v1.1.0 - github.com/mitchellh/mapstructure v1.1.2 - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/sirupsen/logrus v1.6.0 // indirect + github.com/mitchellh/mapstructure v1.3.0 + github.com/sergi/go-diff v1.1.0 github.com/spf13/cobra v1.0.0 - golang.org/x/net v0.0.0-20200513185701-a91f0712d120 // indirect - golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 // indirect - google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587 // indirect ) diff --git a/go.sum b/go.sum index 38291def2..73f2dd036 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,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= github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v38.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= @@ -36,9 +37,10 @@ github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdko github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/anchore/stereoscope v0.0.0-20200512135733-57b1df994b57/go.mod h1:3Ty8a3pRmrp/VIC2NQnlXG4rr3Bh6LYOTHJgcob14Nw= -github.com/anchore/stereoscope v0.0.0-20200518155435-f6c722e4572b h1:kuCcqn0R6QOGJiMRotbnE/tQ2VrcpkFXCjcz0YGpb6w= -github.com/anchore/stereoscope v0.0.0-20200518155435-f6c722e4572b/go.mod h1:TYHkOkwGBJPopUbfoqzuj2+n/xtuL6PApHs0YuNOcRQ= +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/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/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -92,7 +94,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= @@ -179,6 +180,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ 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/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -224,6 +226,8 @@ 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= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +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/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -237,11 +241,14 @@ github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88J github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.3.0 h1:iDwIio/3gk2QtLLEsqU5lInaMzos0hDTz8a6lazSFVw= +github.com/mitchellh/mapstructure v1.3.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= @@ -302,6 +309,8 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +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/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= @@ -382,6 +391,7 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -404,8 +414,8 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -438,10 +448,8 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f h1:mOhmO9WsBaJCNmaZHPtHs9wOcdqdKCjF6OPJlmDM3KI= -golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 h1:YTzHMGlqJu67/uEo1lBv0n3wBXhXNeUbB1XfN2vmTm0= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -449,6 +457,7 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -465,6 +474,7 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17 h1:a/Fd23DJvg1CaeDH0dYHahE+hCI0v9rFgxSNIThoUcM= golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 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= @@ -486,8 +496,8 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84 h1:pSLkPbrjnPyLDYUO2VM9mDLqo2V6CFBY84lFSZAfoi4= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587 h1:1Ym+vvUpq1ZHvxzn34gENJX8U4aKO+vhy2P/2+Xl6qQ= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200519141106-08726f379972 h1:6ydLqG65DIMNJf6p97WudGsmd1w3Ickm/LiZnBrREPI= +google.golang.org/genproto v0.0.0-20200519141106-08726f379972/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -511,6 +521,7 @@ gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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= diff --git a/imgbom/analyzer/analyzer.go b/imgbom/analyzer/analyzer.go index 2602fc890..4eb9f6955 100644 --- a/imgbom/analyzer/analyzer.go +++ b/imgbom/analyzer/analyzer.go @@ -7,6 +7,7 @@ import ( ) type Analyzer interface { + // TODO: add ID / Name for analyze for uniquely identifying this analyzer type SelectFiles([]*tree.FileTree) []file.Reference // NOTE: one of the errors which is returned is "IterationNeeded", which indicates to the driver to // continue with another Select/Analyze pass diff --git a/imgbom/analyzer/dpkg/analyzer.go b/imgbom/analyzer/dpkg/analyzer.go index 0ee2994dc..380c86e4c 100644 --- a/imgbom/analyzer/dpkg/analyzer.go +++ b/imgbom/analyzer/dpkg/analyzer.go @@ -54,7 +54,6 @@ func (a *Analyzer) Analyze(contents map[file.Reference]string) ([]pkg.Package, e Metadata: entry, }) } - } return packages, nil } diff --git a/imgbom/analyzer/dummy/analyzer.go b/imgbom/analyzer/dummy/analyzer.go deleted file mode 100644 index 26ba417d4..000000000 --- a/imgbom/analyzer/dummy/analyzer.go +++ /dev/null @@ -1,30 +0,0 @@ -package dummy - -import ( - "github.com/anchore/imgbom/imgbom/pkg" - "github.com/anchore/stereoscope/pkg/file" - "github.com/anchore/stereoscope/pkg/tree" -) - -// TODO: delete me - -type Analyzer struct{} - -func NewAnalyzer() *Analyzer { - return &Analyzer{} -} - -func (a *Analyzer) SelectFiles(trees []*tree.FileTree) []file.Reference { - return []file.Reference{*trees[0].File("/etc/centos-release")} -} - -func (a *Analyzer) Analyze(contents map[file.Reference]string) ([]pkg.Package, error) { - return []pkg.Package{ - { - Name: "dummy", - Version: "1.0.0", - Type: pkg.DebPkg, - Metadata: pkg.DummyPackage{Extra: "some extra metadata"}, - }, - }, nil -} diff --git a/imgbom/pkg/catalog.go b/imgbom/pkg/catalog.go index 18455dddd..159a8fef1 100644 --- a/imgbom/pkg/catalog.go +++ b/imgbom/pkg/catalog.go @@ -4,19 +4,45 @@ package pkg type Catalog struct { // TODO: catalog by package ID for potential indexing - Packages map[Type][]Package + packages map[Type][]Package } func NewCatalog() Catalog { return Catalog{ - Packages: make(map[Type][]Package), + packages: make(map[Type][]Package), } } func (c *Catalog) Add(p Package) { - _, ok := c.Packages[p.Type] + _, ok := c.packages[p.Type] if !ok { - c.Packages[p.Type] = make([]Package, 0) + c.packages[p.Type] = make([]Package, 0) } - c.Packages[p.Type] = append(c.Packages[p.Type], p) + c.packages[p.Type] = append(c.packages[p.Type], p) +} + +func (c *Catalog) Enumerate(types ...Type) <-chan Package { + channel := make(chan Package) + go func() { + defer close(channel) + for ty, packages := range c.packages { + if len(types) != 0 { + found := false + typeCheck: + for _, t := range types { + if t == ty { + found = true + break typeCheck + } + } + if !found { + continue + } + } + for _, p := range packages { + channel <- p + } + } + }() + return channel } diff --git a/imgbom/pkg/dummy_package.go b/imgbom/pkg/dummy_package.go deleted file mode 100644 index 868968f67..000000000 --- a/imgbom/pkg/dummy_package.go +++ /dev/null @@ -1,6 +0,0 @@ -package pkg - -// TODO: delete me -type DummyPackage struct { - Extra string -} diff --git a/imgbom/pkg/package.go b/imgbom/pkg/package.go index 7380c9116..fc1dbb030 100644 --- a/imgbom/pkg/package.go +++ b/imgbom/pkg/package.go @@ -4,6 +4,7 @@ import "github.com/anchore/stereoscope/pkg/file" // TODO: add package ID (random/incremental) +// TODO: add field to trace which analyzer detected this type Package struct { Name string Version string diff --git a/imgbom/pkg/type.go b/imgbom/pkg/type.go index 0de13947f..69ae13f45 100644 --- a/imgbom/pkg/type.go +++ b/imgbom/pkg/type.go @@ -14,4 +14,21 @@ const ( type Type uint -// TODO: stringer... +var typeStr = []string{ + "UnknownPackage", + "apk", + "deb", + "java", + "node", + "pacman", + "python", + "rpm", + "ruby", +} + +func (t Type) String() string { + if int(t) >= len(typeStr) { + return typeStr[0] + } + return typeStr[t] +} diff --git a/imgbom/presenter/json/presenter.go b/imgbom/presenter/json/presenter.go new file mode 100644 index 000000000..6d06c4ecb --- /dev/null +++ b/imgbom/presenter/json/presenter.go @@ -0,0 +1,114 @@ +package json + +import ( + "encoding/json" + "io" + + "github.com/anchore/imgbom/imgbom/pkg" + stereoscopeImg "github.com/anchore/stereoscope/pkg/image" +) + +type Presenter struct{} + +func NewPresenter() *Presenter { + return &Presenter{} +} + +type document struct { + Image image `json:"image"` + Artifacts []artifact `json:"artifacts"` +} + +type image struct { + Layers []layer `json:"layers"` + Size int64 `json:"size"` + Digest string `json:"digest"` + MediaType string `json:"mediaType"` + Tags []string `json:"tags"` +} + +type layer struct { + MediaType string `json:"mediaType"` + Digest string `json:"digest"` + Size int64 `json:"size"` +} + +type source struct { + Source string `json:"source"` + Layer int `json:"layer"` + Effects []string `json:"effects"` +} + +type artifact struct { + Name string `json:"name"` + Version string `json:"version"` + Type string `json:"type"` + Analyzer string `json:"analyzer"` + Sources []source `json:"sources"` + Metadata interface{} `json:"metadata"` +} + +func (pres *Presenter) Present(output io.Writer, img *stereoscopeImg.Image, catalog pkg.Catalog) error { + tags := make([]string, len(img.Metadata.Tags)) + for idx, tag := range img.Metadata.Tags { + tags[idx] = tag.String() + } + + doc := document{ + Image: image{ + Digest: img.Metadata.Digest, + Size: img.Metadata.Size, + MediaType: string(img.Metadata.MediaType), + Tags: tags, + Layers: make([]layer, len(img.Layers)), + }, + Artifacts: make([]artifact, 0), + } + + // populate image... + for idx, l := range img.Layers { + doc.Image.Layers[idx] = layer{ + MediaType: string(l.Metadata.MediaType), + Digest: l.Metadata.Digest, + Size: l.Metadata.Size, + } + } + + // populate artifacts... + for p := range catalog.Enumerate() { + art := artifact{ + Name: p.Name, + Version: p.Version, + Type: p.Type.String(), + Analyzer: "TODO", // TODO + Sources: make([]source, len(p.Source)), + Metadata: p.Metadata, + } + + for idx, src := range p.Source { + fileMetadata, err := img.FileCatalog.Get(src) + if err != nil { + // TODO: replace + panic(err) + } + + srcObj := source{ + Source: "", + Layer: int(fileMetadata.Source.Metadata.Index), + Effects: []string{}, // TODO + } + art.Sources[idx] = srcObj + } + + doc.Artifacts = append(doc.Artifacts, art) + } + + bytes, err := json.Marshal(&doc) + if err != nil { + // TODO: replace + panic(err) + } + + _, err = output.Write(bytes) + return err +} diff --git a/imgbom/presenter/json/presenter_test.go b/imgbom/presenter/json/presenter_test.go new file mode 100644 index 000000000..646dd6a41 --- /dev/null +++ b/imgbom/presenter/json/presenter_test.go @@ -0,0 +1,90 @@ +package json + +import ( + "bytes" + "flag" + "testing" + + "github.com/anchore/go-testutils" + "github.com/anchore/imgbom/imgbom/pkg" + "github.com/anchore/stereoscope/pkg/file" + "github.com/sergi/go-diff/diffmatchpatch" +) + +var update = flag.Bool("update", false, "update the *.golden files for json presenters") + +// TODO: add a JSON schema and write a test that validates output against the schema +// func validateAgainstV1Schema(t *testing.T, json string) { +// fullSchemaPath, err := filepath.Abs("v1-schema.json") +// if err != nil { +// t.Fatal("could not get path to schema:", err) +// } +// schemaLoader := gojsonschema.NewReferenceLoader(fmt.Sprintf("file://%s", fullSchemaPath)) +// documentLoader := gojsonschema.NewStringLoader(json) + +// result, err := gojsonschema.Validate(schemaLoader, documentLoader) +// if err != nil { +// t.Fatal("unable to validate json schema:", err.Error()) +// } + +// if !result.Valid() { +// t.Errorf("failed json schema validation:") +// for _, desc := range result.Errors() { +// t.Errorf(" - %s\n", desc) +// } +// } +// } + +func TestJsonPresenter(t *testing.T) { + pres := NewPresenter() + var buffer bytes.Buffer + + testImage := "image-simple" + + if *update { + testutils.UpdateGoldenFixtureImage(t, testImage) + } + + catalog := pkg.NewCatalog() + img := testutils.GetGoldenFixtureImage(t, testImage) + + // populate catalog with test data + catalog.Add(pkg.Package{ + Name: "package-1", + Version: "1.0.1", + Source: []file.Reference{ + *img.SquashedTree.File("/somefile-1.txt"), + }, + Type: pkg.DebPkg, + }) + catalog.Add(pkg.Package{ + Name: "package-2", + Version: "2.0.1", + Source: []file.Reference{ + *img.SquashedTree.File("/somefile-2.txt"), + }, + Type: pkg.DebPkg, + }) + + // run presenter + err := pres.Present(&buffer, img, catalog) + if err != nil { + t.Fatal(err) + } + actual := buffer.Bytes() + + if *update { + testutils.UpdateGoldenFileContents(t, actual) + } + + var expected = testutils.GetGoldenFileContents(t) + + if !bytes.Equal(expected, actual) { + dmp := diffmatchpatch.New() + diffs := dmp.DiffMain(string(actual), string(expected), true) + t.Errorf("mismatched output:\n%s", dmp.DiffPrettyText(diffs)) + } + + // TODO: add me back in when there is a JSON schema + // validateAgainstV1Schema(t, string(actual)) +} diff --git a/imgbom/presenter/json/test-fixtures/image-simple/Dockerfile b/imgbom/presenter/json/test-fixtures/image-simple/Dockerfile new file mode 100644 index 000000000..62fb151e4 --- /dev/null +++ b/imgbom/presenter/json/test-fixtures/image-simple/Dockerfile @@ -0,0 +1,6 @@ +# Note: changes to this file will result in updating several test values. Consider making a new image fixture instead of editing this one. +FROM scratch +ADD file-1.txt /somefile-1.txt +ADD file-2.txt /somefile-2.txt +# note: adding a directory will behave differently on docker engine v18 vs v19 +ADD target / diff --git a/imgbom/presenter/json/test-fixtures/image-simple/file-1.txt b/imgbom/presenter/json/test-fixtures/image-simple/file-1.txt new file mode 100644 index 000000000..985d3408e --- /dev/null +++ b/imgbom/presenter/json/test-fixtures/image-simple/file-1.txt @@ -0,0 +1 @@ +this file has contents \ No newline at end of file diff --git a/imgbom/presenter/json/test-fixtures/image-simple/file-2.txt b/imgbom/presenter/json/test-fixtures/image-simple/file-2.txt new file mode 100644 index 000000000..396d08bbc --- /dev/null +++ b/imgbom/presenter/json/test-fixtures/image-simple/file-2.txt @@ -0,0 +1 @@ +file-2 contents! \ No newline at end of file diff --git a/imgbom/presenter/json/test-fixtures/image-simple/target/really/nested/file-3.txt b/imgbom/presenter/json/test-fixtures/image-simple/target/really/nested/file-3.txt new file mode 100644 index 000000000..f85472c93 --- /dev/null +++ b/imgbom/presenter/json/test-fixtures/image-simple/target/really/nested/file-3.txt @@ -0,0 +1,2 @@ +another file! +with lines... \ No newline at end of file diff --git a/imgbom/presenter/json/test-fixtures/snapshot/TestJsonPresenter.golden b/imgbom/presenter/json/test-fixtures/snapshot/TestJsonPresenter.golden new file mode 100644 index 000000000..ab63044a8 --- /dev/null +++ b/imgbom/presenter/json/test-fixtures/snapshot/TestJsonPresenter.golden @@ -0,0 +1 @@ +{"image":{"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:056c0789fa9ad629ceae6d09713fb035f84115af3c4a88a43aa60f13bc683053","size":22},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:b461c48116592c570a66fed71d5b09662a8172e168b7938cf317af47872cdc9b","size":16},{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:00b80053e05c01da485015610d288ce3185fac00d251e2ada02b45a7a7c5f589","size":27}],"size":65,"digest":"sha256:3c53d2d891940f8d8e95acb77b58752f54dc5de9d91d19dd90ced2db76256cea","mediaType":"application/vnd.docker.distribution.manifest.v2+json","tags":["anchore-fixture-image-simple:04e16e44161c8888a1a963720fd0443cbf7eef8101434c431de8725cd98cc9f7"]},"artifacts":[{"name":"package-1","version":"1.0.1","type":"deb","analyzer":"TODO","sources":[{"source":"","layer":0,"effects":[]}],"metadata":null},{"name":"package-2","version":"2.0.1","type":"deb","analyzer":"TODO","sources":[{"source":"","layer":1,"effects":[]}],"metadata":null}]} \ No newline at end of file diff --git a/imgbom/presenter/json/test-fixtures/snapshot/anchore-fixture-image-simple.golden b/imgbom/presenter/json/test-fixtures/snapshot/anchore-fixture-image-simple.golden new file mode 100644 index 000000000..739b61487 Binary files /dev/null and b/imgbom/presenter/json/test-fixtures/snapshot/anchore-fixture-image-simple.golden differ diff --git a/imgbom/presenter/option.go b/imgbom/presenter/option.go new file mode 100644 index 000000000..31f59e211 --- /dev/null +++ b/imgbom/presenter/option.go @@ -0,0 +1,25 @@ +package presenter + +const ( + UnknownPresenterOption Option = iota + JSONOption +) + +var optionStr = []string{ + "UnknownPresenterOption", + "json", +} + +var Options = []Option{ + JSONOption, +} + +type Option int + +func (o Option) String() string { + if int(o) >= len(optionStr) || int(o) < 0 { + return optionStr[0] + } + + return optionStr[o] +} diff --git a/imgbom/presenter/presenter.go b/imgbom/presenter/presenter.go new file mode 100644 index 000000000..d4f88d919 --- /dev/null +++ b/imgbom/presenter/presenter.go @@ -0,0 +1,23 @@ +package presenter + +import ( + "io" + "strings" + + "github.com/anchore/imgbom/imgbom/pkg" + "github.com/anchore/imgbom/imgbom/presenter/json" + "github.com/anchore/stereoscope/pkg/image" +) + +type Presenter interface { + Present(io.Writer, *image.Image, pkg.Catalog) error +} + +func GetPresenter(userStr string) Presenter { + switch strings.ToLower(userStr) { + case JSONOption.String(): + return json.NewPresenter() + default: + return nil + } +} diff --git a/imgbom/scope/scope_test.go b/imgbom/scope/scope_test.go index 5bead2430..3131f0316 100644 --- a/imgbom/scope/scope_test.go +++ b/imgbom/scope/scope_test.go @@ -1,19 +1,20 @@ package scope import ( + "testing" + "github.com/anchore/stereoscope/pkg/image" "github.com/anchore/stereoscope/pkg/tree" - "testing" ) func testScopeImage(t *testing.T) *image.Image { t.Helper() - one := image.NewLayer(0, nil) + one := image.NewLayer(nil) one.Tree = tree.NewFileTree() one.Tree.AddPath("/tree/first/path.txt") - two := image.NewLayer(1, nil) + two := image.NewLayer(nil) two.Tree = tree.NewFileTree() two.Tree.AddPath("/tree/second/path.txt")