mirror of
https://github.com/anchore/syft.git
synced 2025-11-18 00:43:20 +01:00
Merge pull request #94 from anchore/table-output
Add default table presenter
This commit is contained in:
commit
08ea496544
@ -59,7 +59,7 @@ func setGlobalCliOptions() {
|
|||||||
// output & formatting options
|
// output & formatting options
|
||||||
flag = "output"
|
flag = "output"
|
||||||
rootCmd.Flags().StringP(
|
rootCmd.Flags().StringP(
|
||||||
flag, "o", presenter.TextPresenter.String(),
|
flag, "o", presenter.TablePresenter.String(),
|
||||||
fmt.Sprintf("report output formatter, options=%v", presenter.Options),
|
fmt.Sprintf("report output formatter, options=%v", presenter.Options),
|
||||||
)
|
)
|
||||||
if err := viper.BindPFlag(flag, rootCmd.Flags().Lookup(flag)); err != nil {
|
if err := viper.BindPFlag(flag, rootCmd.Flags().Lookup(flag)); err != nil {
|
||||||
|
|||||||
1
go.mod
1
go.mod
@ -16,6 +16,7 @@ require (
|
|||||||
github.com/hashicorp/go-version v1.2.0
|
github.com/hashicorp/go-version v1.2.0
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/mitchellh/mapstructure v1.3.1
|
github.com/mitchellh/mapstructure v1.3.1
|
||||||
|
github.com/olekukonko/tablewriter v0.0.4
|
||||||
github.com/rogpeppe/go-internal v1.5.2
|
github.com/rogpeppe/go-internal v1.5.2
|
||||||
github.com/sergi/go-diff v1.1.0
|
github.com/sergi/go-diff v1.1.0
|
||||||
github.com/spf13/cobra v1.0.0
|
github.com/spf13/cobra v1.0.0
|
||||||
|
|||||||
4
go.sum
4
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 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
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.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 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
|
||||||
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
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.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/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/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.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 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.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
|||||||
@ -6,17 +6,20 @@ const (
|
|||||||
UnknownPresenter Option = iota
|
UnknownPresenter Option = iota
|
||||||
JSONPresenter
|
JSONPresenter
|
||||||
TextPresenter
|
TextPresenter
|
||||||
|
TablePresenter
|
||||||
)
|
)
|
||||||
|
|
||||||
var optionStr = []string{
|
var optionStr = []string{
|
||||||
"UnknownPresenter",
|
"UnknownPresenter",
|
||||||
"json",
|
"json",
|
||||||
"text",
|
"text",
|
||||||
|
"table",
|
||||||
}
|
}
|
||||||
|
|
||||||
var Options = []Option{
|
var Options = []Option{
|
||||||
JSONPresenter,
|
JSONPresenter,
|
||||||
TextPresenter,
|
TextPresenter,
|
||||||
|
TablePresenter,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Option int
|
type Option int
|
||||||
@ -27,6 +30,8 @@ func ParseOption(userStr string) Option {
|
|||||||
return JSONPresenter
|
return JSONPresenter
|
||||||
case strings.ToLower(TextPresenter.String()):
|
case strings.ToLower(TextPresenter.String()):
|
||||||
return TextPresenter
|
return TextPresenter
|
||||||
|
case strings.ToLower(TablePresenter.String()):
|
||||||
|
return TablePresenter
|
||||||
default:
|
default:
|
||||||
return UnknownPresenter
|
return UnknownPresenter
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/presenter/json"
|
"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/presenter/text"
|
||||||
"github.com/anchore/syft/syft/scope"
|
"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)
|
return json.NewPresenter(catalog, s)
|
||||||
case TextPresenter:
|
case TextPresenter:
|
||||||
return text.NewPresenter(catalog, s)
|
return text.NewPresenter(catalog, s)
|
||||||
|
case TablePresenter:
|
||||||
|
return table.NewPresenter(catalog, s)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
67
syft/presenter/table/presenter.go
Normal file
67
syft/presenter/table/presenter.go
Normal file
@ -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
|
||||||
|
}
|
||||||
68
syft/presenter/table/presenter_test.go
Normal file
68
syft/presenter/table/presenter_test.go
Normal file
@ -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))
|
||||||
|
}
|
||||||
@ -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 /
|
||||||
@ -0,0 +1 @@
|
|||||||
|
this file has contents
|
||||||
@ -0,0 +1 @@
|
|||||||
|
file-2 contents!
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
another file!
|
||||||
|
with lines...
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
NAME VERSION TYPE
|
||||||
|
package-1 1.0.1 deb
|
||||||
|
package-2 2.0.1 deb
|
||||||
Loading…
x
Reference in New Issue
Block a user