Skip to content

Commit

Permalink
fix bug images use the template to print results
Browse files Browse the repository at this point in the history
Signed-off-by: Qi Wang <qiwan@redhat.com>
  • Loading branch information
QiWang19 committed Mar 11, 2019
1 parent 36605c2 commit 345ffc2
Show file tree
Hide file tree
Showing 7 changed files with 388 additions and 195 deletions.
101 changes: 72 additions & 29 deletions cmd/buildah/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ import (
"fmt"
"os"
"regexp"
"sort"
"strings"
"text/template"
"time"

"github.com/containers/buildah/imagebuildah"
buildahcli "github.com/containers/buildah/pkg/cli"
"github.com/containers/buildah/pkg/formats"
is "github.com/containers/image/storage"
"github.com/containers/storage"
units "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -59,6 +62,14 @@ type imageResults struct {
filter string
}

var imagesHeader = map[string]string{
"Name": "REPOSITORY",
"Tag": "TAG",
"ID": "IMAGE ID",
"CreatedAt": "CREATED",
"Size": "SIZE",
}

func init() {
var (
opts imageResults
Expand Down Expand Up @@ -143,10 +154,6 @@ func imagesCmd(c *cobra.Command, args []string, iopts *imageResults) error {
}
}

if len(images) > 0 && !opts.noHeading && !opts.quiet && opts.format == "" && !opts.json {
outputHeader(opts.truncate, opts.digests)
}

return outputImages(ctx, images, store, params, name, opts)
}

Expand Down Expand Up @@ -213,26 +220,33 @@ func setFilterDate(ctx context.Context, store storage.Store, images []storage.Im
return time.Time{}, fmt.Errorf("Could not locate image %q", imgName)
}

func outputHeader(truncate, digests bool) {
if truncate {
fmt.Printf("%-56s %-20s %-20s ", "IMAGE NAME", "IMAGE TAG", "IMAGE ID")
} else {
fmt.Printf("%-56s %-20s %-64s ", "IMAGE NAME", "IMAGE TAG", "IMAGE ID")
func outputHeader(opts imageOptions) string {
if opts.format != "" {
return strings.Replace(opts.format, `\t`, "\t", -1)
}

if digests {
fmt.Printf("%-71s ", "DIGEST")
if opts.quiet {
return formats.IDString
}
format := "table {{.Name}}\t{{.Tag}}\t"
if opts.noHeading {
format = "{{.Name}}\t{{.Tag}}\t"
}

fmt.Printf("%-22s %s\n", "CREATED AT", "SIZE")
if opts.digests {
format += "{{.Digest}}\t"
}
format += "{{.ID}}\t{{.CreatedAt}}\t{{.Size}}"
return format
}

type imagesSorted []imageOutputParams

func outputImages(ctx context.Context, images []storage.Image, store storage.Store, filters *filterParams, argName string, opts imageOptions) error {
found := false
var imagesParams imagesSorted
jsonImages := []jsonImage{}
for _, image := range images {
createdTime := image.Created

inspectedTime, digest, size, _ := getDateAndDigestAndSize(ctx, image, store)
if !inspectedTime.IsZero() {
if createdTime != inspectedTime {
Expand All @@ -255,6 +269,11 @@ func outputImages(ctx context.Context, images []storage.Image, store storage.Sto
}
}

imageID := "sha256:" + image.ID
if opts.truncate {
imageID = shortID(image.ID)
}

names := []string{}
if len(image.Names) > 0 {
names = image.Names
Expand All @@ -274,32 +293,25 @@ func outputImages(ctx context.Context, images []storage.Image, store storage.Sto
if !matchesFilter(ctx, image, store, name+":"+tag, filters) {
continue
}
if opts.quiet {
fmt.Printf("%-64s\n", image.ID)
// We only want to print each id once
break outer
}
if opts.json {
jsonImages = append(jsonImages, jsonImage{ID: image.ID, Names: image.Names})
// We only want to print each id once
break outer
}
params := imageOutputParams{
Tag: tag,
ID: image.ID,
ID: imageID,
Name: name,
Digest: digest,
CreatedAt: createdTime.Format("Jan 2, 2006 15:04"),
Size: formattedSize(size),
CreatedAtRaw: createdTime,
CreatedAt: units.HumanDuration(time.Since((createdTime))) + " ago",
Size: formattedSize(size),
}
if opts.format != "" {
if err := outputUsingTemplate(opts.format, params); err != nil {
return err
}
continue
imagesParams = append(imagesParams, params)
if opts.quiet {
// We only want to print each id once
break outer
}
outputUsingFormatString(opts.truncate, opts.digests, params)
}
}
}
Expand All @@ -313,11 +325,42 @@ func outputImages(ctx context.Context, images []storage.Image, store storage.Sto
return err
}
fmt.Printf("%s\n", data)
return nil
}

imagesParams = sortImagesOutput(imagesParams)
out := formats.StdoutTemplateArray{Output: imagesToGeneric(imagesParams), Template: outputHeader(opts), Fields: imagesHeader}
formats.Writer(out).Out()
return nil
}

func shortID(id string) string {
idTruncLength := 12
if len(id) > idTruncLength {
return id[:idTruncLength]
}
return id
}

func sortImagesOutput(imagesOutput imagesSorted) imagesSorted {
sort.Sort(imagesOutput)
return imagesOutput
}

func (a imagesSorted) Less(i, j int) bool {
return a[i].CreatedAtRaw.After(a[j].CreatedAtRaw)
}
func (a imagesSorted) Len() int { return len(a) }
func (a imagesSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

func imagesToGeneric(templParams []imageOutputParams) (genericParams []interface{}) {
if len(templParams) > 0 {
for _, v := range templParams {
genericParams = append(genericParams, interface{}(v))
}
}
return genericParams
}

func matchesFilter(ctx context.Context, image storage.Image, store storage.Store, name string, params *filterParams) bool {
if params == nil {
return true
Expand Down
156 changes: 2 additions & 154 deletions cmd/buildah/images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,40 +113,6 @@ func TestSizeFormatting(t *testing.T) {
}
}

func TestOutputHeader(t *testing.T) {
output := captureOutput(func() {
outputHeader(true, false)
})
expectedOutput := fmt.Sprintf("%-56s %-20s %-20s %-22s %s\n", "IMAGE NAME", "IMAGE TAG", "IMAGE ID", "CREATED AT", "SIZE")
if output != expectedOutput {
t.Errorf("Error outputting header:\n\texpected: %s\n\treceived: %s\n", expectedOutput, output)
}

output = captureOutput(func() {
outputHeader(true, true)
})
expectedOutput = fmt.Sprintf("%-56s %-20s %-20s %-71s %-22s %s\n", "IMAGE NAME", "IMAGE TAG", "IMAGE ID", "DIGEST", "CREATED AT", "SIZE")
if output != expectedOutput {
t.Errorf("Error outputting header:\n\texpected: %s\n\treceived: %s\n", expectedOutput, output)
}

output = captureOutput(func() {
outputHeader(false, false)
})
expectedOutput = fmt.Sprintf("%-56s %-20s %-64s %-22s %s\n", "IMAGE NAME", "IMAGE TAG", "IMAGE ID", "CREATED AT", "SIZE")
if output != expectedOutput {
t.Errorf("Error outputting header:\n\texpected: %s\n\treceived: %s\n", expectedOutput, output)
}

output = captureOutput(func() {
outputHeader(false, true)
})
expectedOutput = fmt.Sprintf("%-56s %-20s %-64s %-71s %-22s %s\n", "IMAGE NAME", "IMAGE TAG", "IMAGE ID", "DIGEST", "CREATED AT", "SIZE")
if output != expectedOutput {
t.Errorf("Error outputting header:\n\texpected: %s\n\treceived: %s\n", expectedOutput, output)
}
}

func TestMatchWithTag(t *testing.T) {
isMatch := matchesReference("docker.io/kubernetes/pause:latest", "pause:latest")
if !isMatch {
Expand Down Expand Up @@ -195,45 +161,6 @@ func TestNoMatchesReferenceWithoutTag(t *testing.T) {
}
}

func TestOutputImagesQuietTruncated(t *testing.T) {
// Make sure the tests are running as root
failTestIfNotRoot(t)

opts := imageOptions{
truncate: true,
quiet: true,
}

store, err := storage.GetStore(storeOptions)
if err != nil {
t.Fatal(err)
} else if store != nil {
is.Transport.SetStore(store)
}

images, err := store.Images()
if err != nil {
t.Fatalf("Error reading images: %v", err)
}

// Pull an image so that we know we have at least one
_, err = pullTestImage(t, "busybox:latest")
if err != nil {
t.Fatalf("could not pull image to remove: %v", err)
}

// Tests quiet and truncated output
output, err := captureOutputWithError(func() error {
return outputImages(getContext(), images[:1], store, nil, "", opts)
})
expectedOutput := fmt.Sprintf("%-64s\n", images[0].ID)
if err != nil {
t.Error("quiet/truncated output produces error")
} else if strings.TrimSpace(output) != strings.TrimSpace(expectedOutput) {
t.Errorf("quiet/truncated output does not match expected value\nExpected: %s\nReceived: %s\n", expectedOutput, output)
}
}

func TestOutputImagesQuietNotTruncated(t *testing.T) {
// Make sure the tests are running as root
failTestIfNotRoot(t)
Expand Down Expand Up @@ -263,7 +190,7 @@ func TestOutputImagesQuietNotTruncated(t *testing.T) {
output, err := captureOutputWithError(func() error {
return outputImages(getContext(), images[:1], store, nil, "", opts)
})
expectedOutput := fmt.Sprintf("%-64s\n", images[0].ID)
expectedOutput := fmt.Sprintf("sha256:%s\n", images[0].ID)
if err != nil {
t.Error("quiet/non-truncated output produces error")
} else if strings.TrimSpace(output) != strings.TrimSpace(expectedOutput) {
Expand Down Expand Up @@ -304,48 +231,11 @@ func TestOutputImagesFormatString(t *testing.T) {
expectedOutput := images[0].ID
if err != nil {
t.Error("format string output produces error")
} else if strings.TrimSpace(output) != strings.TrimSpace(expectedOutput) {
} else if !strings.Contains(expectedOutput, strings.TrimSpace(output)) {
t.Errorf("format string output does not match expected value\nExpected: %s\nReceived: %s\n", expectedOutput, output)
}
}

func TestOutputImagesFormatTemplate(t *testing.T) {
// Make sure the tests are running as root
failTestIfNotRoot(t)

opts := imageOptions{
quiet: true,
}
store, err := storage.GetStore(storeOptions)
if err != nil {
t.Fatal(err)
} else if store != nil {
is.Transport.SetStore(store)
}

// Pull an image so that we know we have at least one
_, err = pullTestImage(t, "busybox:latest")
if err != nil {
t.Fatalf("could not pull image to remove: %v", err)
}

images, err := store.Images()
if err != nil {
t.Fatalf("Error reading images: %v", err)
}

// Tests quiet and non-truncated output
output, err := captureOutputWithError(func() error {
return outputImages(getContext(), images[:1], store, nil, "", opts)
})
expectedOutput := fmt.Sprintf("%-64s\n", images[0].ID)
if err != nil {
t.Error("format template output produces error")
} else if strings.TrimSpace(output) != strings.TrimSpace(expectedOutput) {
t.Errorf("format template output does not match expected value\nExpected: %s\nReceived: %s\n", expectedOutput, output)
}
}

func TestOutputImagesArgNoMatch(t *testing.T) {
// Make sure the tests are running as root
failTestIfNotRoot(t)
Expand Down Expand Up @@ -382,48 +272,6 @@ func TestOutputImagesArgNoMatch(t *testing.T) {
}
}

func TestOutputMultipleImages(t *testing.T) {
// Make sure the tests are running as root
failTestIfNotRoot(t)

opts := imageOptions{
quiet: true,
truncate: true,
}
store, err := storage.GetStore(storeOptions)
if err != nil {
t.Fatal(err)
} else if store != nil {
is.Transport.SetStore(store)
}

// Pull two images so that we know we have at least two
_, err = pullTestImage(t, "busybox:latest")
if err != nil {
t.Fatalf("could not pull image to remove: %v", err)
}
_, err = pullTestImage(t, "alpine:latest")
if err != nil {
t.Fatalf("could not pull image to remove: %v", err)
}

images, err := store.Images()
if err != nil {
t.Fatalf("Error reading images: %v", err)
}

// Tests quiet and truncated output
output, err := captureOutputWithError(func() error {
return outputImages(getContext(), images[:2], store, nil, "", opts)
})
expectedOutput := fmt.Sprintf("%-64s\n%-64s\n", images[0].ID, images[1].ID)
if err != nil {
t.Error("multi-image output produces error")
} else if strings.TrimSpace(output) != strings.TrimSpace(expectedOutput) {
t.Errorf("multi-image output does not match expected value\nExpected: %s\nReceived: %s\n", expectedOutput, output)
}
}

func TestParseFilterAllParams(t *testing.T) {
// Make sure the tests are running as root
failTestIfNotRoot(t)
Expand Down
Loading

0 comments on commit 345ffc2

Please sign in to comment.