diff --git a/go.mod b/go.mod index 8c0afc42..eab3f90f 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,7 @@ require ( github.com/nmcclain/ldap v0.0.0-20210720162743-7f8d1e44eeba github.com/notaryproject/notation-core-go v1.3.0 github.com/notaryproject/notation-go v1.3.2 - github.com/olekukonko/tablewriter v0.0.5 + github.com/olekukonko/tablewriter v1.0.7 github.com/opencontainers/distribution-spec/specs-go v0.0.0-20250123160558-a139cc423184 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 @@ -377,6 +377,8 @@ require ( github.com/oklog/ulid v1.3.1 // indirect github.com/oklog/ulid/v2 v2.1.0 // indirect github.com/oleiade/reflections v1.1.0 // indirect + github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 // indirect + github.com/olekukonko/ll v0.0.8 // indirect github.com/onsi/ginkgo/v2 v2.22.2 // indirect github.com/onsi/gomega v1.36.2 // indirect github.com/open-policy-agent/opa v1.4.2 // indirect diff --git a/go.sum b/go.sum index fb6fed67..8b913d73 100644 --- a/go.sum +++ b/go.sum @@ -1654,7 +1654,6 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= @@ -1776,8 +1775,12 @@ github.com/olareg/olareg v0.1.2 h1:75G8X6E9FUlzL/CSjgFcYfMgNzlc7CxULpUUNsZBIvI= github.com/olareg/olareg v0.1.2/go.mod h1:TWs+N6pO1S4bdB6eerzUm/ITRQ6kw91mVf9ZYeGtw+Y= github.com/oleiade/reflections v1.1.0 h1:D+I/UsXQB4esMathlt0kkZRJZdUDmhv5zGi/HOwYTWo= github.com/oleiade/reflections v1.1.0/go.mod h1:mCxx0QseeVCHs5Um5HhJeCKVC7AwS8kO67tky4rdisA= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 h1:r3FaAI0NZK3hSmtTDrBVREhKULp8oUeqLT5Eyl2mSPo= +github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.8 h1:sbGZ1Fx4QxJXEqL/6IG8GEFnYojUSQ45dJVwN2FH2fc= +github.com/olekukonko/ll v0.0.8/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= +github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm4/ySDGw= +github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= diff --git a/pkg/cli/client/client.go b/pkg/cli/client/client.go index e6017cfa..56a674a5 100644 --- a/pkg/cli/client/client.go +++ b/pkg/cli/client/client.go @@ -87,12 +87,17 @@ func doHTTPRequest(req *http.Request, verifyTLS bool, debug bool, host := req.Host + enableTLS := req.URL.Scheme != "http" + if verifyTLS { + // we want TLS enabled when verifyTLS is true + enableTLS = true + } + httpClientLock.Lock() if httpClientsMap[host] == nil { httpClient, err = common.CreateHTTPClient(&common.HTTPClientOptions{ - // we want TLS enabled when verifyTLS is true. - TLSEnabled: verifyTLS, + TLSEnabled: enableTLS, VerifyTLS: verifyTLS, Host: host, CertOptions: common.HTTPClientCertOptions{}, diff --git a/pkg/cli/client/service.go b/pkg/cli/client/service.go index 21bd1979..830fb87d 100644 --- a/pkg/cli/client/service.go +++ b/pkg/cli/client/service.go @@ -16,6 +16,7 @@ import ( "github.com/dustin/go-humanize" jsoniter "github.com/json-iterator/go" "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" godigest "github.com/opencontainers/go-digest" ispec "github.com/opencontainers/image-spec/specs-go/v1" "gopkg.in/yaml.v3" @@ -881,15 +882,15 @@ func (cve cveResult) stringPlainText() string { row[colCVESeverityIndex] = severity row[colCVETitleIndex] = title - table.Append(row) + table.Append(row) //nolint:errcheck for _, pkg := range cveListItem.PackageList { pkgRow := generateTableRowForVulnerablePackage(pkg) - table.Append(pkgRow) + table.Append(pkgRow) //nolint:errcheck } } - table.Render() + table.Render() //nolint:errcheck return builder.String() } @@ -953,26 +954,38 @@ func (ref referrersResult) string(format string, maxArtifactTypeLen int) (string func (ref referrersResult) stringPlainText(maxArtifactTypeLen int) (string, error) { var builder strings.Builder - table := getImageTableWriter(&builder) - - table.SetColMinWidth(refArtifactTypeIndex, maxArtifactTypeLen) - table.SetColMinWidth(refDigestIndex, digestWidth) - table.SetColMinWidth(refSizeIndex, sizeWidth) + maxDigestWidth := digestWidth + rows := [][]string{} for _, referrer := range ref { artifactType := ellipsize(referrer.ArtifactType, maxArtifactTypeLen, ellipsis) - // digest := ellipsize(godigest.Digest(referrer.Digest).Encoded(), digestWidth, "") size := ellipsize(humanize.Bytes(uint64(referrer.Size)), sizeWidth, ellipsis) //nolint:gosec,lll // refererrer.Size should >= 0 + if len(referrer.Digest) > maxDigestWidth { + maxDigestWidth = len(referrer.Digest) + } + row := make([]string, refRowWidth) row[refArtifactTypeIndex] = artifactType row[refDigestIndex] = referrer.Digest row[refSizeIndex] = size - table.Append(row) + rows = append(rows, row) //nolint:errcheck } - table.Render() + table := getCommonTableWriter(&builder) + table.Options( + tablewriter.WithColumnWidths(tw.NewMapper[int, int](). + Set(refArtifactTypeIndex, maxArtifactTypeLen). + Set(refDigestIndex, maxDigestWidth). + Set(refSizeIndex, sizeWidth)), + ) + + for _, row := range rows { + table.Append(row) //nolint:errcheck + } + + table.Render() //nolint:errcheck return builder.String(), nil } @@ -1017,17 +1030,16 @@ func (repo repoStruct) string(format string, maxImgNameLen, maxTimeLen int, verb func (repo repoStruct) stringPlainText(repoMaxLen, maxTimeLen int, verbose bool) (string, error) { var builder strings.Builder - table := getImageTableWriter(&builder) - - table.SetColMinWidth(repoNameIndex, repoMaxLen) - table.SetColMinWidth(repoSizeIndex, sizeWidth) - table.SetColMinWidth(repoLastUpdatedIndex, maxTimeLen) - table.SetColMinWidth(repoDownloadsIndex, downloadsWidth) - table.SetColMinWidth(repoStarsIndex, signedWidth) - - if verbose { - table.SetColMinWidth(repoPlatformsIndex, platformWidth) - } + table := getCommonTableWriter(&builder) + table.Options( + tablewriter.WithColumnWidths(tw.NewMapper[int, int](). + Set(repoNameIndex, repoMaxLen). + Set(repoSizeIndex, sizeWidth). + Set(repoLastUpdatedIndex, maxTimeLen). + Set(repoDownloadsIndex, downloadsWidth). + Set(repoStarsIndex, signedWidth). + Set(repoPlatformsIndex, platformWidth)), + ) repoSize, err := strconv.Atoi(repo.Size) if err != nil { @@ -1052,7 +1064,7 @@ func (repo repoStruct) stringPlainText(repoMaxLen, maxTimeLen int, verbose bool) repoPlatforms = repoPlatforms[1:] } - table.Append(row) + table.Append(row) //nolint:errcheck if verbose { for _, platform := range repoPlatforms { @@ -1060,11 +1072,11 @@ func (repo repoStruct) stringPlainText(repoMaxLen, maxTimeLen int, verbose bool) row[repoPlatformsIndex] = getPlatformStr(platform) - table.Append(row) + table.Append(row) //nolint:errcheck } } - table.Render() + table.Render() //nolint:errcheck return builder.String(), nil } @@ -1109,70 +1121,60 @@ func (img imageStruct) string(format string, maxImgNameLen, maxTagLen, maxPlatfo func (img imageStruct) stringPlainText(maxImgNameLen, maxTagLen, maxPlatformLen int, verbose bool) (string, error) { var builder strings.Builder - table := getImageTableWriter(&builder) + tagLen := max(len("TAG"), maxTagLen, tagWidth) + imageNameLen := max(len("REPOSITORY"), maxImgNameLen, imageNameWidth) + platformLen := max(len("OS/ARCH"), maxPlatformLen, platformWidth) + configLen := configWidth + layersLen := layersWidth - table.SetColMinWidth(colImageNameIndex, maxImgNameLen) - table.SetColMinWidth(colTagIndex, maxTagLen) - table.SetColMinWidth(colPlatformIndex, platformWidth) - table.SetColMinWidth(colDigestIndex, digestWidth) - table.SetColMinWidth(colSizeIndex, sizeWidth) - table.SetColMinWidth(colIsSignedIndex, isSignedWidth) - - if verbose { - table.SetColMinWidth(colConfigIndex, configWidth) - table.SetColMinWidth(colLayersIndex, layersWidth) + if !verbose { + // Ths hides the columns effectively, 0 links the neighboring columns together + configLen = 1 + layersLen = 1 } + table := getCommonTableWriter(&builder) + table.Options( + tablewriter.WithColumnWidths(tw.NewMapper[int, int](). + Set(colImageNameIndex, imageNameLen). + Set(colTagIndex, tagLen). + Set(colPlatformIndex, platformLen). + Set(colDigestIndex, digestWidth). + Set(colSizeIndex, sizeWidth). + Set(colIsSignedIndex, isSignedWidth). + Set(colConfigIndex, configLen). + Set(colLayersIndex, layersLen)), + ) + var imageName, tagName string imageName = img.RepoName tagName = img.Tag - if imageNameWidth > maxImgNameLen { - maxImgNameLen = imageNameWidth - } - - if tagWidth > maxTagLen { - maxTagLen = tagWidth - } - - // adding spaces so that image name and tag columns are aligned - // in case the name/tag are fully shown and too long - var offset string - if maxImgNameLen > len(imageName) { - offset = strings.Repeat(" ", maxImgNameLen-len(imageName)) - imageName += offset - } - - if maxTagLen > len(tagName) { - offset = strings.Repeat(" ", maxTagLen-len(tagName)) - tagName += offset - } - - err := addImageToTable(table, &img, maxPlatformLen, imageName, tagName, verbose) + err := addImageToTable(table, &img, imageName, tagName, verbose) if err != nil { return "", err } - table.Render() + table.Render() //nolint:errcheck return builder.String(), nil } -func addImageToTable(table *tablewriter.Table, img *imageStruct, maxPlatformLen int, +func addImageToTable(table *tablewriter.Table, img *imageStruct, imageName, tagName string, verbose bool, ) error { switch img.MediaType { case ispec.MediaTypeImageManifest: - return addManifestToTable(table, imageName, tagName, &img.Manifests[0], maxPlatformLen, verbose) + return addManifestToTable(table, imageName, tagName, &img.Manifests[0], verbose) case ispec.MediaTypeImageIndex: - return addImageIndexToTable(table, img, maxPlatformLen, imageName, tagName, verbose) + return addImageIndexToTable(table, img, imageName, tagName, verbose) } return nil } -func addImageIndexToTable(table *tablewriter.Table, img *imageStruct, maxPlatformLen int, +func addImageIndexToTable(table *tablewriter.Table, img *imageStruct, imageName, tagName string, verbose bool, ) error { indexDigest, err := godigest.Parse(img.Digest) @@ -1194,10 +1196,10 @@ func addImageIndexToTable(table *tablewriter.Table, img *imageStruct, maxPlatfor row[colLayersIndex] = "" } - table.Append(row) + table.Append(row) //nolint:errcheck for i := range img.Manifests { - err := addManifestToTable(table, "", "", &img.Manifests[i], maxPlatformLen, verbose) + err := addManifestToTable(table, "", "", &img.Manifests[i], verbose) if err != nil { return err } @@ -1207,7 +1209,7 @@ func addImageIndexToTable(table *tablewriter.Table, img *imageStruct, maxPlatfor } func addManifestToTable(table *tablewriter.Table, imageName, tagName string, manifest *common.ManifestSummary, - maxPlatformLen int, verbose bool, + verbose bool, ) error { manifestDigest, err := godigest.Parse(manifest.Digest) if err != nil { @@ -1220,12 +1222,6 @@ func addManifestToTable(table *tablewriter.Table, imageName, tagName string, man } platform := getPlatformStr(manifest.Platform) - - if maxPlatformLen > len(platform) { - offset := strings.Repeat(" ", maxPlatformLen-len(platform)) - platform += offset - } - manifestDigestStr := ellipsize(manifestDigest.Encoded(), digestWidth, "") configDigestStr := ellipsize(configDigest.Encoded(), configWidth, "") imgSize, _ := strconv.ParseUint(manifest.Size, 10, 64) @@ -1245,7 +1241,7 @@ func addManifestToTable(table *tablewriter.Table, imageName, tagName string, man row[colLayersIndex] = "" } - table.Append(row) + table.Append(row) //nolint:errcheck if verbose { for _, entry := range manifest.Layers { @@ -1268,7 +1264,7 @@ func addManifestToTable(table *tablewriter.Table, imageName, tagName string, man layerRow[colConfigIndex] = "" layerRow[colLayersIndex] = layerDigestStr - table.Append(layerRow) + table.Append(layerRow) //nolint:errcheck } } @@ -1346,81 +1342,18 @@ func ellipsize(text string, maxLength int, trailing string) string { return text[:maxLength-chopLength] + trailing } -func getImageTableWriter(writer io.Writer) *tablewriter.Table { - table := tablewriter.NewWriter(writer) - - table.SetAutoWrapText(false) - table.SetAutoFormatHeaders(true) - table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - table.SetAlignment(tablewriter.ALIGN_LEFT) - table.SetCenterSeparator("") - table.SetColumnSeparator("") - table.SetRowSeparator("") - table.SetHeaderLine(false) - table.SetBorder(false) - table.SetTablePadding(" ") - table.SetNoWhiteSpace(true) - - return table -} - func getCVETableWriter(writer io.Writer) *tablewriter.Table { - table := tablewriter.NewWriter(writer) - - table.SetAutoWrapText(false) - table.SetAutoFormatHeaders(true) - table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - table.SetAlignment(tablewriter.ALIGN_LEFT) - table.SetCenterSeparator("") - table.SetColumnSeparator("") - table.SetRowSeparator("") - table.SetHeaderLine(false) - table.SetBorder(false) - table.SetTablePadding(" ") - table.SetNoWhiteSpace(true) - table.SetColMinWidth(colCVEIDIndex, cveIDWidth) - table.SetColMinWidth(colCVESeverityIndex, cveSeverityWidth) - table.SetColMinWidth(colCVETitleIndex, cveTitleWidth) - table.SetColMinWidth(colCVEVulnPkgNameIndex, cveVulnPkgNameWidth) - table.SetColMinWidth(colCVEVulnPkgPathIndex, cveVulnPkgPathWidth) - table.SetColMinWidth(colCVEVulnPkgInstalledVerIndex, cveVulnPkgInstalledVerWidth) - table.SetColMinWidth(colCVEVulnPkgFixedVerIndex, cveVulnPkgFixedVerWidth) - - return table -} - -func getReferrersTableWriter(writer io.Writer) *tablewriter.Table { - table := tablewriter.NewWriter(writer) - - table.SetAutoWrapText(false) - table.SetAutoFormatHeaders(true) - table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - table.SetAlignment(tablewriter.ALIGN_LEFT) - table.SetCenterSeparator("") - table.SetColumnSeparator("") - table.SetRowSeparator("") - table.SetHeaderLine(false) - table.SetBorder(false) - table.SetTablePadding(" ") - table.SetNoWhiteSpace(true) - - return table -} - -func getRepoTableWriter(writer io.Writer) *tablewriter.Table { - table := tablewriter.NewWriter(writer) - - table.SetAutoWrapText(false) - table.SetAutoFormatHeaders(true) - table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - table.SetAlignment(tablewriter.ALIGN_LEFT) - table.SetCenterSeparator("") - table.SetColumnSeparator("") - table.SetRowSeparator("") - table.SetHeaderLine(false) - table.SetBorder(false) - table.SetTablePadding(" ") - table.SetNoWhiteSpace(true) + table := getCommonTableWriter(writer) + table.Options( + tablewriter.WithColumnWidths(tw.NewMapper[int, int](). + Set(colCVEIDIndex, cveIDWidth). + Set(colCVESeverityIndex, cveSeverityWidth). + Set(colCVETitleIndex, cveTitleWidth). + Set(colCVEVulnPkgNameIndex, cveVulnPkgNameWidth). + Set(colCVEVulnPkgPathIndex, cveVulnPkgPathWidth). + Set(colCVEVulnPkgInstalledVerIndex, cveVulnPkgInstalledVerWidth). + Set(colCVEVulnPkgFixedVerIndex, cveVulnPkgFixedVerWidth)), + ) return table } @@ -1472,9 +1405,9 @@ const ( imageNameWidth = 10 tagWidth = 8 digestWidth = 8 - platformWidth = 14 + platformWidth = 16 sizeWidth = 10 - isSignedWidth = 8 + isSignedWidth = 6 downloadsWidth = 10 signedWidth = 10 lastUpdatedWidth = 14 diff --git a/pkg/cli/client/utils.go b/pkg/cli/client/utils.go index 433056b1..2cfc1053 100644 --- a/pkg/cli/client/utils.go +++ b/pkg/cli/client/utils.go @@ -14,6 +14,8 @@ import ( "time" "github.com/briandowns/spinner" + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" "github.com/spf13/cobra" zerr "zotregistry.dev/zot/errors" @@ -128,47 +130,82 @@ type stringResult struct { type printHeader func(writer io.Writer, verbose bool, maxImageNameLen, maxTagLen, maxPlatformLen int) +func getCommonTableWriter(writer io.Writer) *tablewriter.Table { + table := tablewriter.NewWriter(writer) + + symbols := tw.NewSymbolCustom("Spaces"). + WithRow(""). + WithColumn(" "). + WithTopLeft(""). + WithTopMid(""). + WithTopRight(""). + WithMidLeft(""). + WithCenter(""). + WithMidRight(""). + WithBottomLeft(""). + WithBottomMid(""). + WithBottomRight("") + + table.Options( + tablewriter.WithRendition(tw.Rendition{ + Borders: tw.Border{ + Left: tw.Off, + Right: tw.Off, + Top: tw.Off, + Bottom: tw.Off, + }, + Symbols: symbols, + Settings: tw.Settings{ + Separators: tw.Separators{ + ShowHeader: tw.Off, + ShowFooter: tw.Off, + BetweenRows: tw.Off, + BetweenColumns: tw.On, + }, + }, + }), + tablewriter.WithPadding(tw.Padding{ + Left: "", + Right: "", + }), + tablewriter.WithHeaderAlignment(tw.AlignLeft), + tablewriter.WithRowAlignment(tw.AlignLeft), + ) + + return table +} + func printImageTableHeader(writer io.Writer, verbose bool, maxImageNameLen, maxTagLen, maxPlatformLen int) { - table := getImageTableWriter(writer) + tagLen := max(len("TAG"), maxTagLen, tagWidth) + imageNameLen := max(len("REPOSITORY"), maxImageNameLen, imageNameWidth) + platformLen := max(len("OS/ARCH"), maxPlatformLen, platformWidth) + configLen := configWidth + layersLen := layersWidth - table.SetColMinWidth(colImageNameIndex, imageNameWidth) - table.SetColMinWidth(colTagIndex, tagWidth) - table.SetColMinWidth(colPlatformIndex, platformWidth) - table.SetColMinWidth(colDigestIndex, digestWidth) - table.SetColMinWidth(colSizeIndex, sizeWidth) - table.SetColMinWidth(colIsSignedIndex, isSignedWidth) - - if verbose { - table.SetColMinWidth(colConfigIndex, configWidth) - table.SetColMinWidth(colLayersIndex, layersWidth) + if !verbose { + // Ths hides the columns effectively, 0 links the neighboring columns together + configLen = 1 + layersLen = 1 } + table := getCommonTableWriter(writer) + table.Options( + tablewriter.WithColumnWidths(tw.NewMapper[int, int](). + Set(colImageNameIndex, imageNameLen). + Set(colTagIndex, tagLen). + Set(colPlatformIndex, platformLen). + Set(colDigestIndex, digestWidth). + Set(colSizeIndex, sizeWidth). + Set(colIsSignedIndex, isSignedWidth). + Set(colConfigIndex, configLen). + Set(colLayersIndex, layersLen)), + ) + row := make([]string, 8) //nolint:mnd - // adding spaces so that repository and tag columns are aligned - // in case the name/tag are fully shown and too long - var offset string - if maxImageNameLen > len("REPOSITORY") { - offset = strings.Repeat(" ", maxImageNameLen-len("REPOSITORY")) - row[colImageNameIndex] = "REPOSITORY" + offset - } else { - row[colImageNameIndex] = "REPOSITORY" - } - - if maxTagLen > len("TAG") { - offset = strings.Repeat(" ", maxTagLen-len("TAG")) - row[colTagIndex] = "TAG" + offset - } else { - row[colTagIndex] = "TAG" - } - - if maxPlatformLen > len("OS/ARCH") { - offset = strings.Repeat(" ", maxPlatformLen-len("OS/ARCH")) - row[colPlatformIndex] = "OS/ARCH" + offset - } else { - row[colPlatformIndex] = "OS/ARCH" - } - + row[colImageNameIndex] = "REPOSITORY" + row[colTagIndex] = "TAG" + row[colPlatformIndex] = "OS/ARCH" row[colDigestIndex] = "DIGEST" row[colSizeIndex] = sizeColumn row[colIsSignedIndex] = "SIGNED" @@ -178,8 +215,8 @@ func printImageTableHeader(writer io.Writer, verbose bool, maxImageNameLen, maxT row[colLayersIndex] = "LAYERS" } - table.Append(row) - table.Render() + table.Append(row) //nolint:errcheck + table.Render() //nolint:errcheck } func printCVETableHeader(writer io.Writer) { @@ -189,8 +226,8 @@ func printCVETableHeader(writer io.Writer) { "VULNERABLE PACKAGE", "PATH", "INSTALL-VER", "FIXED-VER", } - table.Append(columnHeadingsRow) - table.Render() + table.Append(columnHeadingsRow) //nolint:errcheck + table.Render() //nolint:errcheck } func printReferrersTableHeader(config SearchConfig, writer io.Writer, maxArtifactTypeLen int) { @@ -198,11 +235,13 @@ func printReferrersTableHeader(config SearchConfig, writer io.Writer, maxArtifac return } - table := getReferrersTableWriter(writer) - - table.SetColMinWidth(refArtifactTypeIndex, maxArtifactTypeLen) - table.SetColMinWidth(refDigestIndex, digestWidth) - table.SetColMinWidth(refSizeIndex, sizeWidth) + table := getCommonTableWriter(writer) + table.Options( + tablewriter.WithColumnWidths(tw.NewMapper[int, int](). + Set(refArtifactTypeIndex, maxArtifactTypeLen). + Set(refDigestIndex, digestWidth). + Set(refSizeIndex, sizeWidth)), + ) row := make([]string, refRowWidth) @@ -220,22 +259,21 @@ func printReferrersTableHeader(config SearchConfig, writer io.Writer, maxArtifac row[refDigestIndex] = "DIGEST" row[refSizeIndex] = sizeColumn - table.Append(row) - table.Render() + table.Append(row) //nolint:errcheck + table.Render() //nolint:errcheck } func printRepoTableHeader(writer io.Writer, repoMaxLen, maxTimeLen int, verbose bool) { - table := getRepoTableWriter(writer) - - table.SetColMinWidth(repoNameIndex, repoMaxLen) - table.SetColMinWidth(repoSizeIndex, sizeWidth) - table.SetColMinWidth(repoLastUpdatedIndex, maxTimeLen) - table.SetColMinWidth(repoDownloadsIndex, sizeWidth) - table.SetColMinWidth(repoStarsIndex, sizeWidth) - - if verbose { - table.SetColMinWidth(repoPlatformsIndex, platformWidth) - } + table := getCommonTableWriter(writer) + table.Options( + tablewriter.WithColumnWidths(tw.NewMapper[int, int](). + Set(repoNameIndex, repoMaxLen). + Set(repoSizeIndex, sizeWidth). + Set(repoLastUpdatedIndex, maxTimeLen). + Set(repoDownloadsIndex, downloadsWidth). + Set(repoStarsIndex, signedWidth). + Set(repoPlatformsIndex, platformWidth)), + ) row := make([]string, repoRowWidth) @@ -265,8 +303,8 @@ func printRepoTableHeader(writer io.Writer, repoMaxLen, maxTimeLen int, verbose row[repoPlatformsIndex] = "PLATFORMS" } - table.Append(row) - table.Render() + table.Append(row) //nolint:errcheck + table.Render() //nolint:errcheck } func printReferrersResult(config SearchConfig, referrersList referrersResult, maxArtifactTypeLen int) error { diff --git a/pkg/cli/server/root.go b/pkg/cli/server/root.go index 13a1f42e..6171d201 100644 --- a/pkg/cli/server/root.go +++ b/pkg/cli/server/root.go @@ -112,6 +112,9 @@ func newScrubCmd(conf *config.Config) *cobra.Command { return nil } + // Do not show usage on errors which are not related to cummand line arguments + cmd.SilenceUsage = true + // checking if the server is already running req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, diff --git a/pkg/storage/scrub.go b/pkg/storage/scrub.go index 859252cb..18892c98 100644 --- a/pkg/storage/scrub.go +++ b/pkg/storage/scrub.go @@ -10,6 +10,7 @@ import ( "time" "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" godigest "github.com/opencontainers/go-digest" ispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -28,7 +29,7 @@ const ( imageNameWidth = 32 tagWidth = 24 statusWidth = 8 - affectedBlobWidth = 24 + affectedBlobWidth = 64 errorWidth = 8 ) @@ -356,33 +357,54 @@ func newScrubImageResult(imageName, tag, status, affectedBlob, err string) Scrub } func getScrubTableWriter(writer io.Writer) *tablewriter.Table { + symbols := tw.NewSymbolCustom("Spaces"). + WithRow(""). + WithColumn(" "). + WithTopLeft(""). + WithTopMid(""). + WithTopRight(""). + WithMidLeft(""). + WithCenter(""). + WithMidRight(""). + WithBottomLeft(""). + WithBottomMid(""). + WithBottomRight("") + table := tablewriter.NewWriter(writer) - table.SetAutoWrapText(false) - table.SetAutoFormatHeaders(true) - table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) - table.SetAlignment(tablewriter.ALIGN_LEFT) - table.SetCenterSeparator("") - table.SetColumnSeparator("") - table.SetRowSeparator("") - table.SetHeaderLine(false) - table.SetBorder(false) - table.SetTablePadding(" ") - table.SetNoWhiteSpace(true) - table.SetColMinWidth(colImageNameIndex, imageNameWidth) - table.SetColMinWidth(colTagIndex, tagWidth) - table.SetColMinWidth(colStatusIndex, statusWidth) - table.SetColMinWidth(colErrorIndex, affectedBlobWidth) - table.SetColMinWidth(colErrorIndex, errorWidth) + // Configure table using the new builder pattern + table.Options( + tablewriter.WithRendition(tw.Rendition{ + Borders: tw.Border{ + Left: tw.Off, + Right: tw.Off, + Top: tw.Off, + Bottom: tw.Off, + }, + Symbols: symbols, + Settings: tw.Settings{ + Separators: tw.Separators{ + ShowHeader: tw.Off, + ShowFooter: tw.Off, + BetweenRows: tw.Off, + BetweenColumns: tw.On, + }, + }, + }), + tablewriter.WithPadding(tw.Padding{ + Left: "", + Right: "", + }), + tablewriter.WithHeaderAlignment(tw.AlignLeft), + tablewriter.WithRowAlignment(tw.AlignLeft), + ) return table } const tableCols = 5 -func printScrubTableHeader(writer io.Writer) { - table := getScrubTableWriter(writer) - +func printScrubTableHeader(table *tablewriter.Table) { row := make([]string, tableCols) row[colImageNameIndex] = "REPOSITORY" @@ -391,42 +413,46 @@ func printScrubTableHeader(writer io.Writer) { row[colAffectedBlobIndex] = "AFFECTED BLOB" row[colErrorIndex] = "ERROR" - table.Append(row) - table.Render() -} - -func printImageResult(imageResult ScrubImageResult) string { - var builder strings.Builder - - table := getScrubTableWriter(&builder) - table.SetColMinWidth(colImageNameIndex, imageNameWidth) - table.SetColMinWidth(colTagIndex, tagWidth) - table.SetColMinWidth(colStatusIndex, statusWidth) - table.SetColMinWidth(colAffectedBlobIndex, affectedBlobWidth) - table.SetColMinWidth(colErrorIndex, errorWidth) - - row := make([]string, tableCols) - - row[colImageNameIndex] = imageResult.ImageName - row[colTagIndex] = imageResult.Tag - row[colStatusIndex] = imageResult.Status - row[colAffectedBlobIndex] = imageResult.AffectedBlob - row[colErrorIndex] = imageResult.Error - - table.Append(row) - table.Render() - - return builder.String() + table.Append(row) //nolint:errcheck } func (results ScrubResults) PrintScrubResults(resultWriter io.Writer) { var builder strings.Builder - printScrubTableHeader(&builder) - fmt.Fprint(resultWriter, builder.String()) + table := getScrubTableWriter(&builder) + printScrubTableHeader(table) - for _, res := range results.ScrubResults { - imageResult := printImageResult(res) - fmt.Fprint(resultWriter, imageResult) + imageNameLen := len("REPOSITORY") + tagLen := len("TAG") + errorLen := len("ERROR") + + for _, imageResult := range results.ScrubResults { + imageNameLen = max(imageNameLen, len(imageResult.ImageName)) + tagLen = max(tagLen, len(imageResult.Tag)) + errorLen = max(errorLen, len(imageResult.Error)) + + row := make([]string, tableCols) + row[colImageNameIndex] = imageResult.ImageName + row[colTagIndex] = imageResult.Tag + row[colStatusIndex] = imageResult.Status + row[colAffectedBlobIndex] = imageResult.AffectedBlob + row[colErrorIndex] = imageResult.Error + + table.Append(row) //nolint:errcheck } + + imageNameLen = min(imageNameLen, imageNameWidth) + tagLen = min(tagLen, tagWidth) + + table.Options( + tablewriter.WithColumnWidths(tw.NewMapper[int, int](). + Set(colImageNameIndex, imageNameLen). + Set(colTagIndex, tagLen). + Set(colStatusIndex, statusWidth). + Set(colAffectedBlobIndex, affectedBlobWidth). + Set(colErrorIndex, errorLen)), + ) + + table.Render() //nolint:errcheck + fmt.Fprint(resultWriter, builder.String()) } diff --git a/test/blackbox/cve.bats b/test/blackbox/cve.bats index 0b2a94de..79dace9b 100644 --- a/test/blackbox/cve.bats +++ b/test/blackbox/cve.bats @@ -95,7 +95,7 @@ function teardown_file() { for i in "${lines[@]}" do - if [[ "$i" = *"CVE-2011-4915 LOW fs/proc/base.c in the Linux kernel through 3..."* ]]; then + if [[ "$i" = *"CVE-2011-4915 LOW fs/proc/base.c in the Linux kernel through 3..."* ]]; then found=1 fi done