mirror of
https://github.com/containerd/containerd.git
synced 2026-11-06 05:17:02 +00:00
Replace manual platform formatting with containerd's platforms.Format() function to ensure consistent platform string formatting across the codebase. This removes the TODO comment and handles platform variants properly. Fixes the TODO in manifest_printer.go line 124. Signed-off-by: Osama Abdelkader <osama.abdelkader@gmail.com>
210 lines
6.5 KiB
Go
210 lines
6.5 KiB
Go
/*
|
|
Copyright The containerd Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package display
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/containerd/errdefs"
|
|
"github.com/containerd/platforms"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
|
|
"github.com/containerd/containerd/v2/core/content"
|
|
"github.com/containerd/containerd/v2/core/images"
|
|
)
|
|
|
|
// TreeFormat is used to format tree based output using 4 values.
|
|
// Each value must display with the same total width to format correctly.
|
|
//
|
|
// MiddleDrop is used to show a child element which is not the last child
|
|
// LastDrop is used to show the last child element
|
|
// SkipLine is used for displaying data from a previous child before the next child
|
|
// Spacer is used to display child data for the last child
|
|
type TreeFormat struct {
|
|
MiddleDrop string
|
|
LastDrop string
|
|
SkipLine string
|
|
Spacer string
|
|
}
|
|
|
|
// LineTreeFormat uses line drawing characters to format a tree
|
|
//
|
|
// TreeRoot
|
|
// ├── First child # MiddleDrop = "├── "
|
|
// │ Skipped line # SkipLine = "│ "
|
|
// └── Last child # LastDrop = "└── "
|
|
// ....└── Only child # Spacer="....", LastDrop = "└── "
|
|
var LineTreeFormat = TreeFormat{
|
|
MiddleDrop: "├── ",
|
|
LastDrop: "└── ",
|
|
SkipLine: "│ ",
|
|
Spacer: " ",
|
|
}
|
|
|
|
type ImageTreePrinter struct {
|
|
verbose bool
|
|
w io.Writer
|
|
format TreeFormat
|
|
}
|
|
|
|
type PrintOpt func(*ImageTreePrinter)
|
|
|
|
func Verbose(p *ImageTreePrinter) {
|
|
p.verbose = true
|
|
}
|
|
|
|
func WithWriter(w io.Writer) PrintOpt {
|
|
return func(p *ImageTreePrinter) {
|
|
p.w = w
|
|
}
|
|
}
|
|
|
|
func WithFormat(format TreeFormat) PrintOpt {
|
|
return func(p *ImageTreePrinter) {
|
|
p.format = format
|
|
}
|
|
}
|
|
|
|
func NewImageTreePrinter(opts ...PrintOpt) *ImageTreePrinter {
|
|
p := &ImageTreePrinter{
|
|
verbose: false,
|
|
w: os.Stdout,
|
|
format: LineTreeFormat,
|
|
}
|
|
for _, opt := range opts {
|
|
opt(p)
|
|
}
|
|
|
|
return p
|
|
}
|
|
|
|
// PrintImageTree prints an image and all its sub elements
|
|
func (p *ImageTreePrinter) PrintImageTree(ctx context.Context, img images.Image, store content.InfoReaderProvider) error {
|
|
fmt.Fprintln(p.w, img.Name)
|
|
subchild := p.format.SkipLine
|
|
fmt.Fprintf(p.w, "%s Created: %s\n", subchild, img.CreatedAt)
|
|
fmt.Fprintf(p.w, "%s Updated: %s\n", subchild, img.UpdatedAt)
|
|
for k, v := range img.Labels {
|
|
fmt.Fprintf(p.w, "%s Label %q: %q\n", subchild, k, v)
|
|
}
|
|
return p.printManifestTree(ctx, img.Target, store, p.format.LastDrop, p.format.Spacer)
|
|
}
|
|
|
|
// PrintManifestTree prints a manifest and all its sub elements
|
|
func (p *ImageTreePrinter) PrintManifestTree(ctx context.Context, desc ocispec.Descriptor, store content.InfoReaderProvider) error {
|
|
// start displaying tree from the root descriptor perspective, which is a single child view
|
|
return p.printManifestTree(ctx, desc, store, p.format.LastDrop, p.format.Spacer)
|
|
}
|
|
|
|
func (p *ImageTreePrinter) printManifestTree(ctx context.Context, desc ocispec.Descriptor, store content.InfoReaderProvider, prefix, childprefix string) error {
|
|
subprefix := childprefix + p.format.MiddleDrop
|
|
subchild := childprefix + p.format.SkipLine
|
|
fmt.Fprintf(p.w, "%s%s @%s (%d bytes)\n", prefix, desc.MediaType, desc.Digest, desc.Size)
|
|
|
|
if desc.Platform != nil && desc.Platform.Architecture != "" {
|
|
fmt.Fprintf(p.w, "%s Platform: %s\n", subchild, platforms.Format(*desc.Platform))
|
|
}
|
|
b, err := content.ReadBlob(ctx, store, desc)
|
|
if err != nil {
|
|
if errdefs.IsNotFound(err) {
|
|
// If the blob is not found, we can still display the tree
|
|
fmt.Fprintf(p.w, "%s Content does not exist locally, skipping\n", childprefix+p.format.LastDrop)
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
if err := p.showContent(ctx, store, desc, subchild); err != nil {
|
|
return err
|
|
}
|
|
|
|
if images.IsManifestType(desc.MediaType) {
|
|
var manifest ocispec.Manifest
|
|
if err := json.Unmarshal(b, &manifest); err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(manifest.Layers) == 0 {
|
|
subprefix = childprefix + p.format.LastDrop
|
|
subchild = childprefix + p.format.Spacer
|
|
}
|
|
fmt.Fprintf(p.w, "%s%s @%s (%d bytes)\n", subprefix, manifest.Config.MediaType, manifest.Config.Digest, manifest.Config.Size)
|
|
|
|
if err := p.showContent(ctx, store, manifest.Config, subchild); err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := range manifest.Layers {
|
|
if len(manifest.Layers) == i+1 {
|
|
subprefix = childprefix + p.format.LastDrop
|
|
}
|
|
fmt.Fprintf(p.w, "%s%s @%s (%d bytes)\n", subprefix, manifest.Layers[i].MediaType, manifest.Layers[i].Digest, manifest.Layers[i].Size)
|
|
}
|
|
} else if images.IsIndexType(desc.MediaType) {
|
|
var idx ocispec.Index
|
|
if err := json.Unmarshal(b, &idx); err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := range idx.Manifests {
|
|
if len(idx.Manifests) == i+1 {
|
|
subprefix = childprefix + p.format.LastDrop
|
|
subchild = childprefix + p.format.Spacer
|
|
}
|
|
if err := p.printManifestTree(ctx, idx.Manifests[i], store, subprefix, subchild); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *ImageTreePrinter) showContent(ctx context.Context, store content.InfoReaderProvider, desc ocispec.Descriptor, prefix string) error {
|
|
if p.verbose {
|
|
info, err := store.Info(ctx, desc.Digest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(info.Labels) > 0 {
|
|
fmt.Fprintf(p.w, "%s┌────────Labels─────────\n", prefix)
|
|
for k, v := range info.Labels {
|
|
fmt.Fprintf(p.w, "%s│%q: %q\n", prefix, k, v)
|
|
}
|
|
fmt.Fprintf(p.w, "%s└───────────────────────\n", prefix)
|
|
}
|
|
}
|
|
if p.verbose && strings.HasSuffix(desc.MediaType, "json") {
|
|
// Print content for config
|
|
cb, err := content.ReadBlob(ctx, store, desc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dst := bytes.NewBuffer(nil)
|
|
json.Indent(dst, cb, prefix+"│", " ")
|
|
fmt.Fprintf(p.w, "%s┌────────Content────────\n", prefix)
|
|
fmt.Fprintf(p.w, "%s│%s\n", prefix, strings.TrimSpace(dst.String()))
|
|
fmt.Fprintf(p.w, "%s└───────────────────────\n", prefix)
|
|
}
|
|
return nil
|
|
}
|