diff --git a/cmd/hauler/cli/store/info.go b/cmd/hauler/cli/store/info.go index b237bee..f0bb812 100644 --- a/cmd/hauler/cli/store/info.go +++ b/cmd/hauler/cli/store/info.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "sort" + "strings" "github.com/olekukonko/tablewriter" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -47,12 +48,20 @@ func InfoCmd(ctx context.Context, o *flags.InfoOpts, s *store.Layout) error { return err } - i := newItem(s, desc, internalManifest, fmt.Sprintf("%s/%s", internalDesc.Platform.OS, internalDesc.Platform.Architecture), o) + i := newItemWithDigest( + s, + internalDesc.Digest.String(), + desc, + internalManifest, + fmt.Sprintf("%s/%s", internalDesc.Platform.OS, internalDesc.Platform.Architecture), + o, + ) var emptyItem item if i != emptyItem { items = append(items, i) } } + // handle "non" multi-arch images } else if desc.MediaType == consts.DockerManifestSchema2 || desc.MediaType == consts.OCIManifestSchema1 { var m ocispec.Manifest @@ -66,14 +75,15 @@ func InfoCmd(ctx context.Context, o *flags.InfoOpts, s *store.Layout) error { } defer rc.Close() - // Unmarshal the OCI image content + // unmarshal the oci image content var internalManifest ocispec.Image if err := json.NewDecoder(rc).Decode(&internalManifest); err != nil { return err } if internalManifest.Architecture != "" { - i := newItem(s, desc, m, fmt.Sprintf("%s/%s", internalManifest.OS, internalManifest.Architecture), o) + i := newItem(s, desc, m, + fmt.Sprintf("%s/%s", internalManifest.OS, internalManifest.Architecture), o) var emptyItem item if i != emptyItem { items = append(items, i) @@ -85,7 +95,8 @@ func InfoCmd(ctx context.Context, o *flags.InfoOpts, s *store.Layout) error { items = append(items, i) } } - // handle the rest + + // handle everything else (charts, files, sigs, etc.) } else { var m ocispec.Manifest if err := json.NewDecoder(rc).Decode(&m); err != nil { @@ -118,13 +129,13 @@ func InfoCmd(ctx context.Context, o *flags.InfoOpts, s *store.Layout) error { msg = buildJson(items...) fmt.Println(msg) default: - buildTable(items...) + buildTable(o.ShowDigests, items...) } return nil } func buildListRepos(items ...item) { - // Create map to track unique repository names + // create map to track unique repository names repos := make(map[string]bool) for _, i := range items { @@ -141,39 +152,85 @@ func buildListRepos(items ...item) { repos[repoName] = true } - // Collect and print unique repository names + // collect and print unique repository names for repoName := range repos { fmt.Println(repoName) } } -func buildTable(items ...item) { - // Create a table for the results +func buildTable(showDigests bool, items ...item) { table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{"Reference", "Type", "Platform", "# Layers", "Size"}) + + if showDigests { + table.SetHeader([]string{"Reference", "Type", "Platform", "Digest", "# Layers", "Size"}) + } else { + table.SetHeader([]string{"Reference", "Type", "Platform", "# Layers", "Size"}) + } + table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) table.SetRowLine(false) table.SetAutoMergeCellsByColumnIndex([]int{0}) totalSize := int64(0) + for _, i := range items { - if i.Type != "" { - row := []string{ - i.Reference, + if i.Type == "" { + continue + } + + ref := truncateReference(i.Reference) + var row []string + + if showDigests { + digest := i.Digest + if digest == "" { + digest = "-" + } + row = []string{ + ref, + i.Type, + i.Platform, + digest, + fmt.Sprintf("%d", i.Layers), + byteCountSI(i.Size), + } + } else { + row = []string{ + ref, i.Type, i.Platform, fmt.Sprintf("%d", i.Layers), byteCountSI(i.Size), } - totalSize += i.Size - table.Append(row) } + + totalSize += i.Size + table.Append(row) + } + + // align total column based on digest visibility + if showDigests { + table.SetFooter([]string{"", "", "", "", "Total", byteCountSI(totalSize)}) + } else { + table.SetFooter([]string{"", "", "", "Total", byteCountSI(totalSize)}) } - table.SetFooter([]string{"", "", "", "Total", byteCountSI(totalSize)}) table.Render() } +// truncateReference shortens the digest of a reference +func truncateReference(ref string) string { + const prefix = "@sha256:" + idx := strings.Index(ref, prefix) + if idx == -1 { + return ref + } + if len(ref) > idx+len(prefix)+12 { + return ref[:idx+len(prefix)+12] + "…" + } + return ref +} + func buildJson(item ...item) string { data, err := json.MarshalIndent(item, "", " ") if err != nil { @@ -186,6 +243,7 @@ type item struct { Reference string Type string Platform string + Digest string Layers int Size int64 } @@ -210,6 +268,13 @@ func (a byReferenceAndArch) Less(i, j int) bool { return a[i].Reference < a[j].Reference } +// overrides the digest with a specific per platform digest +func newItemWithDigest(s *store.Layout, digestStr string, desc ocispec.Descriptor, m ocispec.Manifest, plat string, o *flags.InfoOpts) item { + item := newItem(s, desc, m, plat, o) + item.Digest = digestStr + return item +} + func newItem(s *store.Layout, desc ocispec.Descriptor, m ocispec.Manifest, plat string, o *flags.InfoOpts) item { var size int64 = 0 for _, l := range m.Layers { @@ -255,6 +320,7 @@ func newItem(s *store.Layout, desc ocispec.Descriptor, m ocispec.Manifest, plat Reference: ref.Name(), Type: ctype, Platform: plat, + Digest: desc.Digest.String(), Layers: len(m.Layers), Size: size, } diff --git a/internal/flags/info.go b/internal/flags/info.go index df03922..1a23582 100644 --- a/internal/flags/info.go +++ b/internal/flags/info.go @@ -9,6 +9,7 @@ type InfoOpts struct { TypeFilter string SizeUnit string ListRepos bool + ShowDigests bool } func (o *InfoOpts) AddFlags(cmd *cobra.Command) { @@ -17,4 +18,5 @@ func (o *InfoOpts) AddFlags(cmd *cobra.Command) { f.StringVarP(&o.OutputFormat, "output", "o", "table", "(Optional) Specify the output format (table | json)") f.StringVarP(&o.TypeFilter, "type", "t", "all", "(Optional) Filter on content type (image | chart | file | sigs | atts | sbom)") f.BoolVar(&o.ListRepos, "list-repos", false, "(Optional) List all repository names") + f.BoolVar(&o.ShowDigests, "digests", false, "(Optional) Show digests of each artifact in the output table") }