From 32071b0bf1bca3212a196195d63fd247b3677038 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Thu, 23 Jul 2020 21:48:12 -0400 Subject: [PATCH] add default table presenter --- cmd/cmd.go | 2 +- go.mod | 1 + go.sum | 4 ++ syft/presenter/option.go | 5 ++ syft/presenter/presenter.go | 3 + syft/presenter/table/presenter.go | 67 ++++++++++++++++++ syft/presenter/table/presenter_test.go | 68 +++++++++++++++++++ .../test-fixtures/image-simple/Dockerfile | 6 ++ .../test-fixtures/image-simple/file-1.txt | 1 + .../test-fixtures/image-simple/file-2.txt | 1 + .../target/really/nested/file-3.txt | 2 + .../snapshot/TestTablePresenter.golden | 3 + 12 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 syft/presenter/table/presenter.go create mode 100644 syft/presenter/table/presenter_test.go create mode 100644 syft/presenter/table/test-fixtures/image-simple/Dockerfile create mode 100644 syft/presenter/table/test-fixtures/image-simple/file-1.txt create mode 100644 syft/presenter/table/test-fixtures/image-simple/file-2.txt create mode 100644 syft/presenter/table/test-fixtures/image-simple/target/really/nested/file-3.txt create mode 100644 syft/presenter/table/test-fixtures/snapshot/TestTablePresenter.golden diff --git a/cmd/cmd.go b/cmd/cmd.go index b8569126f..0c9b3830e 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -59,7 +59,7 @@ func setGlobalCliOptions() { // output & formatting options flag = "output" rootCmd.Flags().StringP( - flag, "o", presenter.TextPresenter.String(), + flag, "o", presenter.TablePresenter.String(), fmt.Sprintf("report output formatter, options=%v", presenter.Options), ) if err := viper.BindPFlag(flag, rootCmd.Flags().Lookup(flag)); err != nil { diff --git a/go.mod b/go.mod index 0b12539a4..605a21b24 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/hashicorp/go-version v1.2.0 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.3.1 + github.com/olekukonko/tablewriter v0.0.4 github.com/rogpeppe/go-internal v1.5.2 github.com/sergi/go-diff v1.1.0 github.com/spf13/cobra v1.0.0 diff --git a/go.sum b/go.sum index 51185319a..c8bb6cd66 100644 --- a/go.sum +++ b/go.sum @@ -587,6 +587,8 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA 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.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= 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= @@ -632,6 +634,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= diff --git a/syft/presenter/option.go b/syft/presenter/option.go index 91f628fc1..daa0f6275 100644 --- a/syft/presenter/option.go +++ b/syft/presenter/option.go @@ -6,17 +6,20 @@ const ( UnknownPresenter Option = iota JSONPresenter TextPresenter + TablePresenter ) var optionStr = []string{ "UnknownPresenter", "json", "text", + "table", } var Options = []Option{ JSONPresenter, TextPresenter, + TablePresenter, } type Option int @@ -27,6 +30,8 @@ func ParseOption(userStr string) Option { return JSONPresenter case strings.ToLower(TextPresenter.String()): return TextPresenter + case strings.ToLower(TablePresenter.String()): + return TablePresenter default: return UnknownPresenter } diff --git a/syft/presenter/presenter.go b/syft/presenter/presenter.go index f3859b039..e6a52d3ef 100644 --- a/syft/presenter/presenter.go +++ b/syft/presenter/presenter.go @@ -5,6 +5,7 @@ import ( "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/presenter/json" + "github.com/anchore/syft/syft/presenter/table" "github.com/anchore/syft/syft/presenter/text" "github.com/anchore/syft/syft/scope" ) @@ -20,6 +21,8 @@ func GetPresenter(option Option, s scope.Scope, catalog *pkg.Catalog) Presenter return json.NewPresenter(catalog, s) case TextPresenter: return text.NewPresenter(catalog, s) + case TablePresenter: + return table.NewPresenter(catalog, s) default: return nil } diff --git a/syft/presenter/table/presenter.go b/syft/presenter/table/presenter.go new file mode 100644 index 000000000..f00947ce3 --- /dev/null +++ b/syft/presenter/table/presenter.go @@ -0,0 +1,67 @@ +package table + +import ( + "io" + "sort" + + "github.com/olekukonko/tablewriter" + + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/scope" +) + +type Presenter struct { + catalog *pkg.Catalog + scope scope.Scope +} + +func NewPresenter(catalog *pkg.Catalog, s scope.Scope) *Presenter { + return &Presenter{ + catalog: catalog, + scope: s, + } +} + +func (pres *Presenter) Present(output io.Writer) error { + rows := make([][]string, 0) + + columns := []string{"Name", "Version", "Type"} + for p := range pres.catalog.Enumerate() { + row := []string{ + p.Name, + p.Version, + p.Type.String(), + } + rows = append(rows, row) + } + + // sort by name, version, then type + sort.SliceStable(rows, func(i, j int) bool { + for col := 0; col < len(columns); col++ { + if rows[i][0] != rows[j][0] { + return rows[i][col] < rows[j][col] + } + } + return false + }) + + table := tablewriter.NewWriter(output) + + table.SetHeader(columns) + table.SetHeaderLine(false) + table.SetBorder(false) + table.SetAutoWrapText(false) + table.SetAutoFormatHeaders(true) + table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) + table.SetAlignment(tablewriter.ALIGN_LEFT) + table.SetCenterSeparator("") + table.SetColumnSeparator("") + table.SetRowSeparator("") + table.SetTablePadding("\t") + table.SetNoWhiteSpace(true) + + table.AppendBulk(rows) + table.Render() + + return nil +} diff --git a/syft/presenter/table/presenter_test.go b/syft/presenter/table/presenter_test.go new file mode 100644 index 000000000..1a2d201cd --- /dev/null +++ b/syft/presenter/table/presenter_test.go @@ -0,0 +1,68 @@ +package table + +import ( + "bytes" + "flag" + "testing" + + "github.com/anchore/go-testutils" + "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/syft/syft/pkg" + "github.com/anchore/syft/syft/scope" + "github.com/sergi/go-diff/diffmatchpatch" +) + +var update = flag.Bool("update", false, "update the *.golden files for table presenters") + +func TestTablePresenter(t *testing.T) { + var buffer bytes.Buffer + + testImage := "image-simple" + + catalog := pkg.NewCatalog() + img, cleanup := testutils.GetFixtureImage(t, "docker-archive", testImage) + defer cleanup() + + // 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, + }) + + s, err := scope.NewScopeFromImage(img, scope.AllLayersScope) + pres := NewPresenter(catalog, s) + + // run presenter + err = pres.Present(&buffer) + 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/syft/presenter/table/test-fixtures/image-simple/Dockerfile b/syft/presenter/table/test-fixtures/image-simple/Dockerfile new file mode 100644 index 000000000..62fb151e4 --- /dev/null +++ b/syft/presenter/table/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/syft/presenter/table/test-fixtures/image-simple/file-1.txt b/syft/presenter/table/test-fixtures/image-simple/file-1.txt new file mode 100644 index 000000000..985d3408e --- /dev/null +++ b/syft/presenter/table/test-fixtures/image-simple/file-1.txt @@ -0,0 +1 @@ +this file has contents \ No newline at end of file diff --git a/syft/presenter/table/test-fixtures/image-simple/file-2.txt b/syft/presenter/table/test-fixtures/image-simple/file-2.txt new file mode 100644 index 000000000..396d08bbc --- /dev/null +++ b/syft/presenter/table/test-fixtures/image-simple/file-2.txt @@ -0,0 +1 @@ +file-2 contents! \ No newline at end of file diff --git a/syft/presenter/table/test-fixtures/image-simple/target/really/nested/file-3.txt b/syft/presenter/table/test-fixtures/image-simple/target/really/nested/file-3.txt new file mode 100644 index 000000000..f85472c93 --- /dev/null +++ b/syft/presenter/table/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/syft/presenter/table/test-fixtures/snapshot/TestTablePresenter.golden b/syft/presenter/table/test-fixtures/snapshot/TestTablePresenter.golden new file mode 100644 index 000000000..fd2b6d0f6 --- /dev/null +++ b/syft/presenter/table/test-fixtures/snapshot/TestTablePresenter.golden @@ -0,0 +1,3 @@ +NAME VERSION TYPE +package-1 1.0.1 deb +package-2 2.0.1 deb