Skip to content
This repository has been archived by the owner on Jun 13, 2021. It is now read-only.

Support format option in docker app ls #743

Merged
merged 6 commits into from
Nov 20, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 104 additions & 96 deletions internal/commands/image/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package image

import (
"bytes"
"encoding/json"
"fmt"
"io"
"strings"
Expand All @@ -13,20 +14,18 @@ import (
"github.com/docker/app/internal/store"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/templates"
"github.com/docker/distribution/reference"
"github.com/docker/docker/pkg/stringid"
units "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)

type imageListOption struct {
quiet bool
digests bool
}

type imageListColumn struct {
header string
value func(p pkg) string
quiet bool
digests bool
template string
}

func listCmd(dockerCli command.Cli) *cobra.Command {
Expand All @@ -52,148 +51,157 @@ func listCmd(dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags()
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only show numeric IDs")
flags.BoolVarP(&options.digests, "digests", "", false, "Show image digests")
cmd.Flags().StringVarP(&options.template, "format", "f", "", "Format the output using the given syntax or Go template")
cmd.Flags().SetAnnotation("format", "experimentalCLI", []string{"true"}) //nolint:errcheck

return cmd
}

func runList(dockerCli command.Cli, options imageListOption, bundleStore store.BundleStore) error {
bundles, err := bundleStore.List()
if err != nil {
return err
}

pkgs, err := getPackages(bundleStore, bundles)
images, err := getImageDescriptors(bundleStore)
if err != nil {
return err
}

if options.quiet {
return printImageIDs(dockerCli, pkgs)
return printImageIDs(dockerCli, images)
}
return printImages(dockerCli, pkgs, options)
return printImages(dockerCli, images, options)
}

func getPackages(bundleStore store.BundleStore, references []reference.Reference) ([]pkg, error) {
packages := make([]pkg, len(references))
func getImageDescriptors(bundleStore store.BundleStore) ([]imageDesc, error) {
references, err := bundleStore.List()
if err != nil {
return nil, err
}
images := make([]imageDesc, len(references))
for i, ref := range references {
b, err := bundleStore.Read(ref)
if err != nil {
return nil, err
}

pk := pkg{
bundle: b,
ref: ref,
}

packages[i] = pk
images[i] = getImageDesc(b, ref)
}

return packages, nil
return images, nil
}

func printImages(dockerCli command.Cli, refs []pkg, options imageListOption) error {
func printImages(dockerCli command.Cli, list []imageDesc, options imageListOption) error {
if options.template == "json" {
bytes, err := json.MarshalIndent(list, "", " ")
if err != nil {
return errors.Errorf("Failed to marshall json: %s", err)
}
_, err = dockerCli.Out().Write(bytes)
return err
}
if options.template != "" {
tmpl, err := templates.Parse(options.template)
if err != nil {
return errors.Errorf("Template parsing error: %s", err)
}
return tmpl.Execute(dockerCli.Out(), list)
}

w := tabwriter.NewWriter(dockerCli.Out(), 0, 0, 1, ' ', 0)
listColumns := getImageListColumns(options)
printHeaders(w, listColumns)
for _, ref := range refs {
printValues(w, ref, listColumns)
printHeaders(w, options.digests)
for _, desc := range list {
desc.println(w, options.digests)
}

return w.Flush()
}

func printImageIDs(dockerCli command.Cli, refs []pkg) error {
func printImageIDs(dockerCli command.Cli, refs []imageDesc) error {
var buf bytes.Buffer

for _, ref := range refs {
id, err := getImageID(ref)
if err != nil {
return err
}
fmt.Fprintln(&buf, id)
fmt.Fprintln(&buf, ref.ID)
}
fmt.Fprint(dockerCli.Out(), buf.String())
return nil
}

func getImageID(p pkg) (string, error) {
id, ok := p.ref.(store.ID)
func getImageID(bundle *relocated.Bundle, ref reference.Reference) (string, error) {
id, ok := ref.(store.ID)
if !ok {
var err error
id, err = store.FromBundle(p.bundle)
id, err = store.FromBundle(bundle)
if err != nil {
return "", err
}
}
return stringid.TruncateID(id.String()), nil
}

func printHeaders(w io.Writer, listColumns []imageListColumn) {
var headers []string
for _, column := range listColumns {
headers = append(headers, column.header)
func printHeaders(w io.Writer, digests bool) {
headers := []string{"REPOSITORY", "TAG"}
if digests {
headers = append(headers, "DIGEST")
}
headers = append(headers, "APP IMAGE ID", "APP NAME", "CREATED")
fmt.Fprintln(w, strings.Join(headers, "\t"))
}

func printValues(w io.Writer, ref pkg, listColumns []imageListColumn) {
var values []string
for _, column := range listColumns {
values = append(values, column.value(ref))
type imageDesc struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Repository string `json:"repository,omitempty"`
Tag string `json:"tag,omitempty"`
Digest string `json:"digest,omitempty"`
Created time.Duration `json:"created,omitempty"`
}

func getImageDesc(bundle *relocated.Bundle, ref reference.Reference) imageDesc {
var id string
id, _ = getImageID(bundle, ref)
var repository string
if n, ok := ref.(reference.Named); ok {
repository = reference.FamiliarName(n)
}
var tag string
if t, ok := ref.(reference.Tagged); ok {
tag = t.Tag()
}
var digest string
if t, ok := ref.(reference.Digested); ok {
digest = t.Digest().String()
}
var created time.Duration
if payload, err := packager.CustomPayload(bundle.Bundle); err == nil {
if createdPayload, ok := payload.(packager.CustomPayloadCreated); ok {
created = time.Now().UTC().Sub(createdPayload.CreatedTime())
}
}
return imageDesc{
ID: id,
Name: bundle.Name,
Repository: repository,
Tag: tag,
Digest: digest,
Created: created,
}
fmt.Fprintln(w, strings.Join(values, "\t"))
}

func getImageListColumns(options imageListOption) []imageListColumn {
columns := []imageListColumn{
{"REPOSITORY", func(p pkg) string {
if n, ok := p.ref.(reference.Named); ok {
return reference.FamiliarName(n)
}
return "<none>"
}},
{"TAG", func(p pkg) string {
if t, ok := p.ref.(reference.Tagged); ok {
return t.Tag()
}
return "<none>"
}},
func (desc imageDesc) humanDuration() string {
if desc.Created > 0 {
return units.HumanDuration(desc.Created) + " ago"
}
if options.digests {
columns = append(columns, imageListColumn{"DIGEST", func(p pkg) string {
if t, ok := p.ref.(reference.Digested); ok {
return t.Digest().String()
}
return "<none>"
}})
return ""
}

func (desc imageDesc) println(w io.Writer, digests bool) {
values := []string{}
silvin-lubecki marked this conversation as resolved.
Show resolved Hide resolved
values = append(values, orNone(desc.Repository), orNone(desc.Tag))
if digests {
values = append(values, orNone(desc.Digest))
}
columns = append(columns,
imageListColumn{"APP IMAGE ID", func(p pkg) string {
id, err := getImageID(p)
if err != nil {
return ""
}
return id
}},
imageListColumn{"APP NAME", func(p pkg) string {
return p.bundle.Name
}},
imageListColumn{"CREATED", func(p pkg) string {
payload, err := packager.CustomPayload(p.bundle.Bundle)
if err != nil {
return ""
}
if createdPayload, ok := payload.(packager.CustomPayloadCreated); ok {
return units.HumanDuration(time.Now().UTC().Sub(createdPayload.CreatedTime())) + " ago"
}
return ""
}},
)
return columns
values = append(values, desc.ID, desc.Name, desc.humanDuration())
fmt.Fprintln(w, strings.Join(values, "\t"))
}

type pkg struct {
ref reference.Reference
bundle *relocated.Bundle
func orNone(s string) string {
if s != "" {
return s
}
return "<none>"
}
26 changes: 24 additions & 2 deletions internal/commands/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/templates"
units "github.com/docker/go-units"
"github.com/docker/go/canonical/json"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)

Expand All @@ -36,25 +39,44 @@ var (
)

func listCmd(dockerCli command.Cli) *cobra.Command {
var template string
cmd := &cobra.Command{
Use: "ls [OPTIONS]",
Short: "List running Apps",
Aliases: []string{"list"},
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runList(dockerCli)
return runList(dockerCli, template)
},
}

cmd.Flags().StringVarP(&template, "format", "f", "", "Format the output using the given syntax or Go template")
cmd.Flags().SetAnnotation("format", "experimentalCLI", []string{"true"}) //nolint:errcheck
return cmd
}

func runList(dockerCli command.Cli) error {
func runList(dockerCli command.Cli, template string) error {
installations, err := getInstallations(dockerCli.CurrentContext(), config.Dir())
if err != nil {
return err
}

if template == "json" {
bytes, err := json.MarshalIndent(installations, "", " ")
if err != nil {
return errors.Errorf("Failed to marshall json: %s", err)
}
_, err = dockerCli.Out().Write(bytes)
return err
}
if template != "" {
tmpl, err := templates.Parse(template)
if err != nil {
return errors.Errorf("Template parsing error: %s", err)
}
return tmpl.Execute(dockerCli.Out(), installations)
}

w := tabwriter.NewWriter(dockerCli.Out(), 0, 0, 1, ' ', 0)
printHeaders(w)

Expand Down