diff --git a/api/client/images.go b/api/client/images.go index 4cfa3abafb..b47c6d65f4 100644 --- a/api/client/images.go +++ b/api/client/images.go @@ -1,13 +1,14 @@ package client import ( + "encoding/json" "fmt" "net/url" "strings" "text/tabwriter" "time" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" @@ -18,26 +19,26 @@ import ( ) // FIXME: --viz and --tree are deprecated. Remove them in a future version. -func (cli *DockerCli) WalkTree(noTrunc bool, images *engine.Table, byParent map[string]*engine.Table, prefix string, printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string)) { - length := images.Len() +func (cli *DockerCli) WalkTree(noTrunc bool, images []*types.Image, byParent map[string][]*types.Image, prefix string, printNode func(cli *DockerCli, noTrunc bool, image *types.Image, prefix string)) { + length := len(images) if length > 1 { - for index, image := range images.Data { + for index, image := range images { if index+1 == length { printNode(cli, noTrunc, image, prefix+"└─") - if subimages, exists := byParent[image.Get("Id")]; exists { + if subimages, exists := byParent[image.ID]; exists { cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) } } else { printNode(cli, noTrunc, image, prefix+"\u251C─") - if subimages, exists := byParent[image.Get("Id")]; exists { + if subimages, exists := byParent[image.ID]; exists { cli.WalkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) } } } } else { - for _, image := range images.Data { + for _, image := range images { printNode(cli, noTrunc, image, prefix+"└─") - if subimages, exists := byParent[image.Get("Id")]; exists { + if subimages, exists := byParent[image.ID]; exists { cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) } } @@ -45,41 +46,41 @@ func (cli *DockerCli) WalkTree(noTrunc bool, images *engine.Table, byParent map[ } // FIXME: --viz and --tree are deprecated. Remove them in a future version. -func (cli *DockerCli) printVizNode(noTrunc bool, image *engine.Env, prefix string) { +func (cli *DockerCli) printVizNode(noTrunc bool, image *types.Image, prefix string) { var ( imageID string parentID string ) if noTrunc { - imageID = image.Get("Id") - parentID = image.Get("ParentId") + imageID = image.ID + parentID = image.ParentId } else { - imageID = stringid.TruncateID(image.Get("Id")) - parentID = stringid.TruncateID(image.Get("ParentId")) + imageID = stringid.TruncateID(image.ID) + parentID = stringid.TruncateID(image.ParentId) } if parentID == "" { fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", imageID) } else { fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", parentID, imageID) } - if image.GetList("RepoTags")[0] != ":" { + if image.RepoTags[0] != ":" { fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", - imageID, imageID, strings.Join(image.GetList("RepoTags"), "\\n")) + imageID, imageID, strings.Join(image.RepoTags, "\\n")) } } // FIXME: --viz and --tree are deprecated. Remove them in a future version. -func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix string) { +func (cli *DockerCli) printTreeNode(noTrunc bool, image *types.Image, prefix string) { var imageID string if noTrunc { - imageID = image.Get("Id") + imageID = image.ID } else { - imageID = stringid.TruncateID(image.Get("Id")) + imageID = stringid.TruncateID(image.ID) } - fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, units.HumanSize(float64(image.GetInt64("VirtualSize")))) - if image.GetList("RepoTags")[0] != ":" { - fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.GetList("RepoTags"), ", ")) + fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, units.HumanSize(float64(image.VirtualSize))) + if image.RepoTags[0] != ":" { + fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.RepoTags, ", ")) } else { fmt.Fprint(cli.out, "\n") } @@ -101,7 +102,6 @@ func (cli *DockerCli) CmdImages(args ...string) error { flFilter := opts.NewListOpts(nil) cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") cmd.Require(flag.Max, 1) - cmd.ParseFlags(args, true) // Consolidate all filter flags, and sanity check them early. @@ -129,44 +129,44 @@ func (cli *DockerCli) CmdImages(args ...string) error { v.Set("filters", filterJSON) } - body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, nil)) + rdr, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil) if err != nil { return err } - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(body); err != nil { + images := []types.Image{} + err = json.NewDecoder(rdr).Decode(&images) + if err != nil { return err } var ( - printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string) - startImage *engine.Env + printNode func(cli *DockerCli, noTrunc bool, image *types.Image, prefix string) + startImage *types.Image - roots = engine.NewTable("Created", outs.Len()) - byParent = make(map[string]*engine.Table) + roots = []*types.Image{} + byParent = make(map[string][]*types.Image) ) - for _, image := range outs.Data { - if image.Get("ParentId") == "" { - roots.Add(image) + for _, image := range images { + if image.ParentId == "" { + roots = append(roots, &image) } else { - if children, exists := byParent[image.Get("ParentId")]; exists { - children.Add(image) + if children, exists := byParent[image.ParentId]; exists { + children = append(children, &image) } else { - byParent[image.Get("ParentId")] = engine.NewTable("Created", 1) - byParent[image.Get("ParentId")].Add(image) + byParent[image.ParentId] = []*types.Image{&image} } } if matchName != "" { - if matchName == image.Get("Id") || matchName == stringid.TruncateID(image.Get("Id")) { - startImage = image + if matchName == image.ID || matchName == stringid.TruncateID(image.ID) { + startImage = &image } - for _, repotag := range image.GetList("RepoTags") { + for _, repotag := range image.RepoTags { if repotag == matchName { - startImage = image + startImage = &image } } } @@ -180,8 +180,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { } if startImage != nil { - root := engine.NewTable("Created", 1) - root.Add(startImage) + root := []*types.Image{startImage} cli.WalkTree(*noTrunc, root, byParent, "", printNode) } else if matchName == "" { cli.WalkTree(*noTrunc, roots, byParent, "", printNode) @@ -207,14 +206,14 @@ func (cli *DockerCli) CmdImages(args ...string) error { v.Set("all", "1") } - body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, nil)) - + rdr, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil) if err != nil { return err } - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(body); err != nil { + images := []types.Image{} + err = json.NewDecoder(rdr).Decode(&images) + if err != nil { return err } @@ -227,14 +226,14 @@ func (cli *DockerCli) CmdImages(args ...string) error { } } - for _, out := range outs.Data { - outID := out.Get("Id") + for _, image := range images { + ID := image.ID if !*noTrunc { - outID = stringid.TruncateID(outID) + ID = stringid.TruncateID(ID) } - repoTags := out.GetList("RepoTags") - repoDigests := out.GetList("RepoDigests") + repoTags := image.RepoTags + repoDigests := image.RepoDigests if len(repoTags) == 1 && repoTags[0] == ":" && len(repoDigests) == 1 && repoDigests[0] == "@" { // dangling image - clear out either repoTags or repoDigsts so we only show it once below @@ -256,12 +255,12 @@ func (cli *DockerCli) CmdImages(args ...string) error { if !*quiet { if *showDigests { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize))) } else { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize))) } } else { - fmt.Fprintln(w, outID) + fmt.Fprintln(w, ID) } } } diff --git a/api/client/ps.go b/api/client/ps.go index 35d4279a62..fdc8ef5a9a 100644 --- a/api/client/ps.go +++ b/api/client/ps.go @@ -1,6 +1,7 @@ package client import ( + "encoding/json" "fmt" "net/url" "strconv" @@ -9,7 +10,7 @@ import ( "time" "github.com/docker/docker/api" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers/filters" @@ -85,13 +86,14 @@ func (cli *DockerCli) CmdPs(args ...string) error { v.Set("filters", filterJSON) } - body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, nil)) + rdr, _, err := cli.call("GET", "/containers/json?"+v.Encode(), nil, nil) if err != nil { return err } - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(body); err != nil { + containers := []types.Container{} + err = json.NewDecoder(rdr).Decode(&containers) + if err != nil { return err } @@ -114,54 +116,50 @@ func (cli *DockerCli) CmdPs(args ...string) error { return ss } - for _, out := range outs.Data { - outID := out.Get("Id") + for _, container := range containers { + ID := container.ID if !*noTrunc { - outID = stringid.TruncateID(outID) + ID = stringid.TruncateID(ID) } if *quiet { - fmt.Fprintln(w, outID) + fmt.Fprintln(w, ID) continue } var ( - outNames = stripNamePrefix(out.GetList("Names")) - outCommand = strconv.Quote(out.Get("Command")) - ports = engine.NewTable("", 0) + names = stripNamePrefix(container.Names) + command = strconv.Quote(container.Command) ) if !*noTrunc { - outCommand = utils.Trunc(outCommand, 20) + command = utils.Trunc(command, 20) // only display the default name for the container with notrunc is passed - for _, name := range outNames { + for _, name := range names { if len(strings.Split(name, "/")) == 1 { - outNames = []string{name} - + names = []string{name} break } } } - ports.ReadListFrom([]byte(out.Get("Ports"))) - - image := out.Get("Image") + image := container.Image if image == "" { image = "" } - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, image, outCommand, - units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), - out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ",")) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", ID, image, command, + units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(container.Created), 0))), + container.Status, api.NewDisplayablePorts(container.Ports), strings.Join(names, ",")) if *size { - if out.GetInt("SizeRootFs") > 0 { - fmt.Fprintf(w, "%s (virtual %s)\n", units.HumanSize(float64(out.GetInt64("SizeRw"))), units.HumanSize(float64(out.GetInt64("SizeRootFs")))) + if container.SizeRootFs > 0 { + fmt.Fprintf(w, "%s (virtual %s)\n", units.HumanSize(float64(container.SizeRw)), units.HumanSize(float64(container.SizeRootFs))) } else { - fmt.Fprintf(w, "%s\n", units.HumanSize(float64(out.GetInt64("SizeRw")))) + fmt.Fprintf(w, "%s\n", units.HumanSize(float64(container.SizeRw))) } continue diff --git a/api/common.go b/api/common.go index 8251fdcf85..39224a9c10 100644 --- a/api/common.go +++ b/api/common.go @@ -5,9 +5,11 @@ import ( "mime" "os" "path/filepath" + "sort" "strings" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/version" @@ -31,6 +33,7 @@ func ValidateHost(val string) (string, error) { } // TODO remove, used on < 1.5 in getContainersJSON +// TODO this can go away when we get rid of engine.table func DisplayablePorts(ports *engine.Table) string { var ( result = []string{} @@ -80,6 +83,61 @@ func DisplayablePorts(ports *engine.Table) string { return strings.Join(result, ", ") } +type ByPrivatePort []types.Port + +func (r ByPrivatePort) Len() int { return len(r) } +func (r ByPrivatePort) Swap(i, j int) { r[i], r[j] = r[j], r[i] } +func (r ByPrivatePort) Less(i, j int) bool { return r[i].PrivatePort < r[j].PrivatePort } + +// TODO Rename to DisplayablePorts (remove "New") when engine.Table goes away +func NewDisplayablePorts(ports []types.Port) string { + var ( + result = []string{} + hostMappings = []string{} + firstInGroupMap map[string]int + lastInGroupMap map[string]int + ) + firstInGroupMap = make(map[string]int) + lastInGroupMap = make(map[string]int) + sort.Sort(ByPrivatePort(ports)) + for _, port := range ports { + var ( + current = port.PrivatePort + portKey = port.Type + firstInGroup int + lastInGroup int + ) + if port.IP != "" { + if port.PublicPort != current { + hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type)) + continue + } + portKey = fmt.Sprintf("%s/%s", port.IP, port.Type) + } + firstInGroup = firstInGroupMap[portKey] + lastInGroup = lastInGroupMap[portKey] + + if firstInGroup == 0 { + firstInGroupMap[portKey] = current + lastInGroupMap[portKey] = current + continue + } + + if current == (lastInGroup + 1) { + lastInGroupMap[portKey] = current + continue + } + result = append(result, FormGroup(portKey, firstInGroup, lastInGroup)) + firstInGroupMap[portKey] = current + lastInGroupMap[portKey] = current + } + for portKey, firstInGroup := range firstInGroupMap { + result = append(result, FormGroup(portKey, firstInGroup, lastInGroupMap[portKey])) + } + result = append(result, hostMappings...) + return strings.Join(result, ", ") +} + func FormGroup(key string, start, last int) string { var ( group string diff --git a/api/types/types.go b/api/types/types.go index ef3dd6fcda..931523b0b6 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -56,3 +56,36 @@ type ImageDelete struct { Untagged string `json:",omitempty"` Deleted string `json:",omitempty"` } + +// GET "/images/json" +type Image struct { + ID string `json:"Id"` + ParentId string + RepoTags []string + RepoDigests []string + Created int + Size int + VirtualSize int + Labels map[string]string +} + +// GET "/containers/json" +type Port struct { + IP string + PrivatePort int + PublicPort int + Type string +} + +type Container struct { + ID string `json:"Id"` + Names []string `json:,omitempty"` + Image string `json:,omitempty"` + Command string `json:,omitempty"` + Created int `json:,omitempty"` + Ports []Port `json:,omitempty"` + SizeRw int `json:,omitempty"` + SizeRootFs int `json:,omitempty"` + Labels map[string]string `json:,omitempty"` + Status string `json:,omitempty"` +} diff --git a/daemon/list.go b/daemon/list.go index b1c134375e..d18e4407bc 100644 --- a/daemon/list.go +++ b/daemon/list.go @@ -1,18 +1,21 @@ package daemon import ( + "encoding/json" "errors" "fmt" + "sort" "strconv" "strings" - "github.com/docker/docker/graph" - "github.com/docker/docker/pkg/graphdb" - "github.com/docker/docker/utils" - + "github.com/docker/docker/api/types" "github.com/docker/docker/engine" + "github.com/docker/docker/graph" + "github.com/docker/docker/nat" + "github.com/docker/docker/pkg/graphdb" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers/filters" + "github.com/docker/docker/utils" ) // List returns an array of all containers registered in the daemon. @@ -20,6 +23,12 @@ func (daemon *Daemon) List() []*Container { return daemon.containers.List() } +type ByCreated []types.Container + +func (r ByCreated) Len() int { return len(r) } +func (r ByCreated) Swap(i, j int) { r[i], r[j] = r[j], r[i] } +func (r ByCreated) Less(i, j int) bool { return r[i].Created < r[j].Created } + func (daemon *Daemon) Containers(job *engine.Job) error { var ( foundBefore bool @@ -32,7 +41,7 @@ func (daemon *Daemon) Containers(job *engine.Job) error { psFilters filters.Args filtExited []int ) - outs := engine.NewTable("Created", 0) + containers := []types.Container{} psFilters, err := filters.FromParam(job.Getenv("filters")) if err != nil { @@ -126,15 +135,16 @@ func (daemon *Daemon) Containers(job *engine.Job) error { return nil } displayed++ - out := &engine.Env{} - out.SetJson("Id", container.ID) - out.SetList("Names", names[container.ID]) + newC := types.Container{ + ID: container.ID, + Names: names[container.ID], + } img := container.Config.Image _, tag := parsers.ParseRepositoryTag(container.Config.Image) if tag == "" { img = utils.ImageReference(img, graph.DEFAULTTAG) } - out.SetJson("Image", img) + newC.Image = img if len(container.Args) > 0 { args := []string{} for _, arg := range container.Args { @@ -146,24 +156,41 @@ func (daemon *Daemon) Containers(job *engine.Job) error { } argsAsString := strings.Join(args, " ") - out.Set("Command", fmt.Sprintf("\"%s %s\"", container.Path, argsAsString)) + newC.Command = fmt.Sprintf("%s %s", container.Path, argsAsString) } else { - out.Set("Command", fmt.Sprintf("\"%s\"", container.Path)) + newC.Command = fmt.Sprintf("%s", container.Path) } - out.SetInt64("Created", container.Created.Unix()) - out.Set("Status", container.State.String()) - str, err := container.NetworkSettings.PortMappingAPI().ToListString() - if err != nil { - return err + newC.Created = int(container.Created.Unix()) + newC.Status = container.State.String() + + newC.Ports = []types.Port{} + for port, bindings := range container.NetworkSettings.Ports { + p, _ := nat.ParsePort(port.Port()) + if len(bindings) == 0 { + newC.Ports = append(newC.Ports, types.Port{ + PrivatePort: p, + Type: port.Proto(), + }) + continue + } + for _, binding := range bindings { + h, _ := nat.ParsePort(binding.HostPort) + newC.Ports = append(newC.Ports, types.Port{ + PrivatePort: p, + PublicPort: h, + Type: port.Proto(), + IP: binding.HostIp, + }) + } } - out.Set("Ports", str) + if size { sizeRw, sizeRootFs := container.GetSize() - out.SetInt64("SizeRw", sizeRw) - out.SetInt64("SizeRootFs", sizeRootFs) + newC.SizeRw = int(sizeRw) + newC.SizeRootFs = int(sizeRootFs) } - out.SetJson("Labels", container.Config.Labels) - outs.Add(out) + newC.Labels = container.Config.Labels + containers = append(containers, newC) return nil } @@ -175,8 +202,8 @@ func (daemon *Daemon) Containers(job *engine.Job) error { break } } - outs.ReverseSort() - if _, err := outs.WriteListTo(job.Stdout); err != nil { + sort.Sort(sort.Reverse(ByCreated(containers))) + if err = json.NewEncoder(job.Stdout).Encode(containers); err != nil { return err } return nil diff --git a/graph/list.go b/graph/list.go index 4d269e0113..5af4b87e31 100644 --- a/graph/list.go +++ b/graph/list.go @@ -1,11 +1,14 @@ package graph import ( + "encoding/json" "fmt" "log" "path" + "sort" "strings" + "github.com/docker/docker/api/types" "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/pkg/parsers/filters" @@ -17,6 +20,12 @@ var acceptedImageFilterTags = map[string]struct{}{ "label": {}, } +type ByCreated []*types.Image + +func (r ByCreated) Len() int { return len(r) } +func (r ByCreated) Swap(i, j int) { r[i], r[j] = r[j], r[i] } +func (r ByCreated) Less(i, j int) bool { return r[i].Created < r[j].Created } + func (s *TagStore) CmdImages(job *engine.Job) error { var ( allImages map[string]*image.Image @@ -53,7 +62,8 @@ func (s *TagStore) CmdImages(job *engine.Job) error { if err != nil { return err } - lookup := make(map[string]*engine.Env) + + lookup := make(map[string]*types.Image) s.Lock() for repoName, repository := range s.Repositories { if job.Getenv("filter") != "" { @@ -69,12 +79,12 @@ func (s *TagStore) CmdImages(job *engine.Job) error { continue } - if out, exists := lookup[id]; exists { + if lImage, exists := lookup[id]; exists { if filtTagged { if utils.DigestReference(ref) { - out.SetList("RepoDigests", append(out.GetList("RepoDigests"), imgRef)) + lImage.RepoDigests = append(lImage.RepoDigests, imgRef) } else { // Tag Ref. - out.SetList("RepoTags", append(out.GetList("RepoTags"), imgRef)) + lImage.RepoTags = append(lImage.RepoTags, imgRef) } } } else { @@ -84,23 +94,23 @@ func (s *TagStore) CmdImages(job *engine.Job) error { continue } if filtTagged { - out := &engine.Env{} - out.SetJson("ParentId", image.Parent) - out.SetJson("Id", image.ID) - out.SetInt64("Created", image.Created.Unix()) - out.SetInt64("Size", image.Size) - out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size) - out.SetJson("Labels", image.ContainerConfig.Labels) + newImage := new(types.Image) + newImage.ParentId = image.Parent + newImage.ID = image.ID + newImage.Created = int(image.Created.Unix()) + newImage.Size = int(image.Size) + newImage.VirtualSize = int(image.GetParentsSize(0) + image.Size) + newImage.Labels = image.ContainerConfig.Labels if utils.DigestReference(ref) { - out.SetList("RepoTags", []string{}) - out.SetList("RepoDigests", []string{imgRef}) + newImage.RepoTags = []string{} + newImage.RepoDigests = []string{imgRef} } else { - out.SetList("RepoTags", []string{imgRef}) - out.SetList("RepoDigests", []string{}) + newImage.RepoTags = []string{imgRef} + newImage.RepoDigests = []string{} } - lookup[id] = out + lookup[id] = newImage } } @@ -108,9 +118,9 @@ func (s *TagStore) CmdImages(job *engine.Job) error { } s.Unlock() - outs := engine.NewTable("Created", len(lookup)) + images := []*types.Image{} for _, value := range lookup { - outs.Add(value) + images = append(images, value) } // Display images which aren't part of a repository/tag @@ -119,21 +129,23 @@ func (s *TagStore) CmdImages(job *engine.Job) error { if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) { continue } - out := &engine.Env{} - out.SetJson("ParentId", image.Parent) - out.SetList("RepoTags", []string{":"}) - out.SetList("RepoDigests", []string{"@"}) - out.SetJson("Id", image.ID) - out.SetInt64("Created", image.Created.Unix()) - out.SetInt64("Size", image.Size) - out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size) - out.SetJson("Labels", image.ContainerConfig.Labels) - outs.Add(out) + newImage := new(types.Image) + newImage.ParentId = image.Parent + newImage.RepoTags = []string{":"} + newImage.RepoDigests = []string{"@"} + newImage.ID = image.ID + newImage.Created = int(image.Created.Unix()) + newImage.Size = int(image.Size) + newImage.VirtualSize = int(image.GetParentsSize(0) + image.Size) + newImage.Labels = image.ContainerConfig.Labels + + images = append(images, newImage) } } - outs.ReverseSort() - if _, err := outs.WriteListTo(job.Stdout); err != nil { + sort.Sort(sort.Reverse(ByCreated(images))) + + if err = json.NewEncoder(job.Stdout).Encode(images); err != nil { return err } return nil