Browse Source

Merge pull request #3728 from vieux/container_job

Move containers to a job
Victor Vieux 11 years ago
parent
commit
e2003fea3a
8 changed files with 240 additions and 144 deletions
  1. 25 20
      api.go
  2. 0 36
      api_params.go
  3. 27 18
      commands.go
  4. 14 14
      container.go
  5. 8 0
      engine/env.go
  6. 16 7
      integration/api_test.go
  7. 113 27
      integration/server_test.go
  8. 37 22
      server.go

+ 25 - 20
api.go

@@ -291,32 +291,37 @@ func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *h
 	if err := parseForm(r); err != nil {
 	if err := parseForm(r); err != nil {
 		return err
 		return err
 	}
 	}
-	all, err := getBoolParam(r.Form.Get("all"))
-	if err != nil {
+	var (
+		err  error
+		outs *engine.Table
+		job  = srv.Eng.Job("containers")
+	)
+
+	job.Setenv("all", r.Form.Get("all"))
+	job.Setenv("size", r.Form.Get("size"))
+	job.Setenv("since", r.Form.Get("since"))
+	job.Setenv("before", r.Form.Get("before"))
+	job.Setenv("limit", r.Form.Get("limit"))
+
+	if version > 1.5 {
+		job.Stdout.Add(w)
+	} else if outs, err = job.Stdout.AddTable(); err != nil {
 		return err
 		return err
 	}
 	}
-	size, err := getBoolParam(r.Form.Get("size"))
-	if err != nil {
+	if err = job.Run(); err != nil {
 		return err
 		return err
 	}
 	}
-	since := r.Form.Get("since")
-	before := r.Form.Get("before")
-	n, err := strconv.Atoi(r.Form.Get("limit"))
-	if err != nil {
-		n = -1
-	}
-
-	outs := srv.Containers(all, size, n, since, before)
-
-	if version < 1.5 {
-		outs2 := []APIContainersOld{}
-		for _, ctnr := range outs {
-			outs2 = append(outs2, *ctnr.ToLegacy())
+	if version < 1.5 { // Convert to legacy format
+		for _, out := range outs.Data {
+			ports := engine.NewTable("", 0)
+			ports.ReadListFrom([]byte(out.Get("Ports")))
+			out.Set("Ports", displayablePorts(ports))
+		}
+		if _, err = outs.WriteListTo(w); err != nil {
+			return err
 		}
 		}
-
-		return writeJSON(w, http.StatusOK, outs2)
 	}
 	}
-	return writeJSON(w, http.StatusOK, outs)
+	return nil
 }
 }
 
 
 func postImagesTag(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 func postImagesTag(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {

+ 0 - 36
api_params.go

@@ -11,29 +11,6 @@ type (
 		Untagged string `json:",omitempty"`
 		Untagged string `json:",omitempty"`
 	}
 	}
 
 
-	APIContainers struct {
-		ID         string `json:"Id"`
-		Image      string
-		Command    string
-		Created    int64
-		Status     string
-		Ports      []APIPort
-		SizeRw     int64
-		SizeRootFs int64
-		Names      []string
-	}
-
-	APIContainersOld struct {
-		ID         string `json:"Id"`
-		Image      string
-		Command    string
-		Created    int64
-		Status     string
-		Ports      string
-		SizeRw     int64
-		SizeRootFs int64
-	}
-
 	APIID struct {
 	APIID struct {
 		ID string `json:"Id"`
 		ID string `json:"Id"`
 	}
 	}
@@ -68,16 +45,3 @@ type (
 		HostPath string
 		HostPath string
 	}
 	}
 )
 )
-
-func (api APIContainers) ToLegacy() *APIContainersOld {
-	return &APIContainersOld{
-		ID:         api.ID,
-		Image:      api.Image,
-		Command:    api.Command,
-		Created:    api.Created,
-		Status:     api.Status,
-		Ports:      displayablePorts(api.Ports),
-		SizeRw:     api.SizeRw,
-		SizeRootFs: api.SizeRootFs,
-	}
-}

+ 27 - 18
commands.go

@@ -1310,13 +1310,13 @@ func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix stri
 	}
 	}
 }
 }
 
 
-func displayablePorts(ports []APIPort) string {
+func displayablePorts(ports *engine.Table) string {
 	result := []string{}
 	result := []string{}
-	for _, port := range ports {
-		if port.IP == "" {
-			result = append(result, fmt.Sprintf("%d/%s", port.PublicPort, port.Type))
+	for _, port := range ports.Data {
+		if port.Get("IP") == "" {
+			result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PublicPort"), port.Get("Type")))
 		} else {
 		} else {
-			result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
+			result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type")))
 		}
 		}
 	}
 	}
 	sort.Strings(result)
 	sort.Strings(result)
@@ -1362,9 +1362,8 @@ func (cli *DockerCli) CmdPs(args ...string) error {
 		return err
 		return err
 	}
 	}
 
 
-	var outs []APIContainers
-	err = json.Unmarshal(body, &outs)
-	if err != nil {
+	outs := engine.NewTable("Created", 0)
+	if _, err := outs.ReadListFrom(body); err != nil {
 		return err
 		return err
 	}
 	}
 	w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
 	w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
@@ -1377,32 +1376,42 @@ func (cli *DockerCli) CmdPs(args ...string) error {
 		}
 		}
 	}
 	}
 
 
-	for _, out := range outs {
+	for _, out := range outs.Data {
+		var (
+			outID    = out.Get("ID")
+			outNames = out.GetList("Names")
+		)
+
 		if !*noTrunc {
 		if !*noTrunc {
-			out.ID = utils.TruncateID(out.ID)
+			outID = utils.TruncateID(outID)
 		}
 		}
 
 
 		// Remove the leading / from the names
 		// Remove the leading / from the names
-		for i := 0; i < len(out.Names); i++ {
-			out.Names[i] = out.Names[i][1:]
+		for i := 0; i < len(outNames); i++ {
+			outNames[i] = outNames[i][1:]
 		}
 		}
 
 
 		if !*quiet {
 		if !*quiet {
+			var (
+				outCommand = out.Get("Command")
+				ports      = engine.NewTable("", 0)
+			)
 			if !*noTrunc {
 			if !*noTrunc {
-				out.Command = utils.Trunc(out.Command, 20)
+				outCommand = utils.Trunc(outCommand, 20)
 			}
 			}
-			fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports), strings.Join(out.Names, ","))
+			ports.ReadListFrom([]byte(out.Get("Ports")))
+			fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, out.Get("Image"), outCommand, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), out.Get("Status"), displayablePorts(ports), strings.Join(outNames, ","))
 			if *size {
 			if *size {
-				if out.SizeRootFs > 0 {
-					fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.SizeRw), utils.HumanSize(out.SizeRootFs))
+				if out.GetInt("SizeRootFs") > 0 {
+					fmt.Fprintf(w, "%s (virtual %s)\n", utils.HumanSize(out.GetInt64("SizeRw")), utils.HumanSize(out.GetInt64("SizeRootFs")))
 				} else {
 				} else {
-					fmt.Fprintf(w, "%s\n", utils.HumanSize(out.SizeRw))
+					fmt.Fprintf(w, "%s\n", utils.HumanSize(out.GetInt64("SizeRw")))
 				}
 				}
 			} else {
 			} else {
 				fmt.Fprint(w, "\n")
 				fmt.Fprint(w, "\n")
 			}
 			}
 		} else {
 		} else {
-			fmt.Fprintln(w, out.ID)
+			fmt.Fprintln(w, outID)
 		}
 		}
 	}
 	}
 
 

+ 14 - 14
container.go

@@ -5,6 +5,7 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"github.com/dotcloud/docker/archive"
 	"github.com/dotcloud/docker/archive"
+	"github.com/dotcloud/docker/engine"
 	"github.com/dotcloud/docker/execdriver"
 	"github.com/dotcloud/docker/execdriver"
 	"github.com/dotcloud/docker/graphdriver"
 	"github.com/dotcloud/docker/graphdriver"
 	"github.com/dotcloud/docker/networkdriver/ipallocator"
 	"github.com/dotcloud/docker/networkdriver/ipallocator"
@@ -175,29 +176,28 @@ type NetworkSettings struct {
 	Ports       map[Port][]PortBinding
 	Ports       map[Port][]PortBinding
 }
 }
 
 
-func (settings *NetworkSettings) PortMappingAPI() []APIPort {
-	var mapping []APIPort
+func (settings *NetworkSettings) PortMappingAPI() *engine.Table {
+	var outs = engine.NewTable("", 0)
 	for port, bindings := range settings.Ports {
 	for port, bindings := range settings.Ports {
 		p, _ := parsePort(port.Port())
 		p, _ := parsePort(port.Port())
 		if len(bindings) == 0 {
 		if len(bindings) == 0 {
-			mapping = append(mapping, APIPort{
-				PublicPort: int64(p),
-				Type:       port.Proto(),
-			})
+			out := &engine.Env{}
+			out.SetInt("PublicPort", p)
+			out.Set("Type", port.Proto())
+			outs.Add(out)
 			continue
 			continue
 		}
 		}
 		for _, binding := range bindings {
 		for _, binding := range bindings {
-			p, _ := parsePort(port.Port())
+			out := &engine.Env{}
 			h, _ := parsePort(binding.HostPort)
 			h, _ := parsePort(binding.HostPort)
-			mapping = append(mapping, APIPort{
-				PrivatePort: int64(p),
-				PublicPort:  int64(h),
-				Type:        port.Proto(),
-				IP:          binding.HostIp,
-			})
+			out.SetInt("PrivatePort", p)
+			out.SetInt("PublicPort", h)
+			out.Set("Type", port.Proto())
+			out.Set("IP", binding.HostIp)
+			outs.Add(out)
 		}
 		}
 	}
 	}
-	return mapping
+	return outs
 }
 }
 
 
 // Inject the io.Reader at the given path. Note: do not close the reader
 // Inject the io.Reader at the given path. Note: do not close the reader

+ 8 - 0
engine/env.go

@@ -313,6 +313,14 @@ func (t *Table) WriteListTo(dst io.Writer) (n int64, err error) {
 	return n + 1, nil
 	return n + 1, nil
 }
 }
 
 
+func (t *Table) ToListString() (string, error) {
+	buffer := bytes.NewBuffer(nil)
+	if _, err := t.WriteListTo(buffer); err != nil {
+		return "", err
+	}
+	return buffer.String(), nil
+}
+
 func (t *Table) WriteTo(dst io.Writer) (n int64, err error) {
 func (t *Table) WriteTo(dst io.Writer) (n int64, err error) {
 	for _, env := range t.Data {
 	for _, env := range t.Data {
 		bytes, err := env.WriteTo(dst)
 		bytes, err := env.WriteTo(dst)

+ 16 - 7
integration/api_test.go

@@ -302,7 +302,16 @@ func TestGetContainersJSON(t *testing.T) {
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 	srv := mkServerFromEngine(eng, t)
 	srv := mkServerFromEngine(eng, t)
 
 
-	beginLen := len(srv.Containers(true, false, -1, "", ""))
+	job := eng.Job("containers")
+	job.SetenvBool("all", true)
+	outs, err := job.Stdout.AddTable()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
+		t.Fatal(err)
+	}
+	beginLen := len(outs.Data)
 
 
 	containerID := createTestContainer(eng, &docker.Config{
 	containerID := createTestContainer(eng, &docker.Config{
 		Image: unitTestImageID,
 		Image: unitTestImageID,
@@ -323,15 +332,15 @@ func TestGetContainersJSON(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 	assertHttpNotError(r, t)
 	assertHttpNotError(r, t)
-	containers := []docker.APIContainers{}
-	if err := json.Unmarshal(r.Body.Bytes(), &containers); err != nil {
+	containers := engine.NewTable("", 0)
+	if _, err := containers.ReadListFrom(r.Body.Bytes()); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
-	if len(containers) != beginLen+1 {
-		t.Fatalf("Expected %d container, %d found (started with: %d)", beginLen+1, len(containers), beginLen)
+	if len(containers.Data) != beginLen+1 {
+		t.Fatalf("Expected %d container, %d found (started with: %d)", beginLen+1, len(containers.Data), beginLen)
 	}
 	}
-	if containers[0].ID != containerID {
-		t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", containerID, containers[0].ID)
+	if id := containers.Data[0].Get("ID"); id != containerID {
+		t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", containerID, id)
 	}
 	}
 }
 }
 
 

+ 113 - 27
integration/server_test.go

@@ -69,7 +69,6 @@ func TestImageTagImageDelete(t *testing.T) {
 
 
 func TestCreateRm(t *testing.T) {
 func TestCreateRm(t *testing.T) {
 	eng := NewTestEngine(t)
 	eng := NewTestEngine(t)
-	srv := mkServerFromEngine(eng, t)
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
 
 	config, _, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil)
 	config, _, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil)
@@ -79,25 +78,44 @@ func TestCreateRm(t *testing.T) {
 
 
 	id := createTestContainer(eng, config, t)
 	id := createTestContainer(eng, config, t)
 
 
-	if c := srv.Containers(true, false, -1, "", ""); len(c) != 1 {
-		t.Errorf("Expected 1 container, %v found", len(c))
+	job := eng.Job("containers")
+	job.SetenvBool("all", true)
+	outs, err := job.Stdout.AddListTable()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
+		t.Fatal(err)
+	}
+
+	if len(outs.Data) != 1 {
+		t.Errorf("Expected 1 container, %v found", len(outs.Data))
 	}
 	}
 
 
-	job := eng.Job("container_delete", id)
+	job = eng.Job("container_delete", id)
 	job.SetenvBool("removeVolume", true)
 	job.SetenvBool("removeVolume", true)
 	if err := job.Run(); err != nil {
 	if err := job.Run(); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	if c := srv.Containers(true, false, -1, "", ""); len(c) != 0 {
-		t.Errorf("Expected 0 container, %v found", len(c))
+	job = eng.Job("containers")
+	job.SetenvBool("all", true)
+	outs, err = job.Stdout.AddListTable()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
+		t.Fatal(err)
+	}
+
+	if len(outs.Data) != 0 {
+		t.Errorf("Expected 0 container, %v found", len(outs.Data))
 	}
 	}
 
 
 }
 }
 
 
 func TestCreateRmVolumes(t *testing.T) {
 func TestCreateRmVolumes(t *testing.T) {
 	eng := NewTestEngine(t)
 	eng := NewTestEngine(t)
-	srv := mkServerFromEngine(eng, t)
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 	defer mkRuntimeFromEngine(eng, t).Nuke()
 
 
 	config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo", "test"}, nil)
 	config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo", "test"}, nil)
@@ -107,11 +125,21 @@ func TestCreateRmVolumes(t *testing.T) {
 
 
 	id := createTestContainer(eng, config, t)
 	id := createTestContainer(eng, config, t)
 
 
-	if c := srv.Containers(true, false, -1, "", ""); len(c) != 1 {
-		t.Errorf("Expected 1 container, %v found", len(c))
+	job := eng.Job("containers")
+	job.SetenvBool("all", true)
+	outs, err := job.Stdout.AddListTable()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
+		t.Fatal(err)
 	}
 	}
 
 
-	job := eng.Job("start", id)
+	if len(outs.Data) != 1 {
+		t.Errorf("Expected 1 container, %v found", len(outs.Data))
+	}
+
+	job = eng.Job("start", id)
 	if err := job.ImportEnv(hostConfig); err != nil {
 	if err := job.ImportEnv(hostConfig); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -131,8 +159,18 @@ func TestCreateRmVolumes(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	if c := srv.Containers(true, false, -1, "", ""); len(c) != 0 {
-		t.Errorf("Expected 0 container, %v found", len(c))
+	job = eng.Job("containers")
+	job.SetenvBool("all", true)
+	outs, err = job.Stdout.AddListTable()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
+		t.Fatal(err)
+	}
+
+	if len(outs.Data) != 0 {
+		t.Errorf("Expected 0 container, %v found", len(outs.Data))
 	}
 	}
 }
 }
 
 
@@ -169,11 +207,21 @@ func TestRestartKillWait(t *testing.T) {
 
 
 	id := createTestContainer(eng, config, t)
 	id := createTestContainer(eng, config, t)
 
 
-	if c := srv.Containers(true, false, -1, "", ""); len(c) != 1 {
-		t.Errorf("Expected 1 container, %v found", len(c))
+	job := eng.Job("containers")
+	job.SetenvBool("all", true)
+	outs, err := job.Stdout.AddListTable()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
+		t.Fatal(err)
 	}
 	}
 
 
-	job := eng.Job("start", id)
+	if len(outs.Data) != 1 {
+		t.Errorf("Expected 1 container, %v found", len(outs.Data))
+	}
+
+	job = eng.Job("start", id)
 	if err := job.ImportEnv(hostConfig); err != nil {
 	if err := job.ImportEnv(hostConfig); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -200,13 +248,23 @@ func TestRestartKillWait(t *testing.T) {
 	}
 	}
 
 
 	srv = mkServerFromEngine(eng, t)
 	srv = mkServerFromEngine(eng, t)
-	c := srv.Containers(true, false, -1, "", "")
-	if len(c) != 1 {
-		t.Errorf("Expected 1 container, %v found", len(c))
+
+	job = srv.Eng.Job("containers")
+	job.SetenvBool("all", true)
+	outs, err = job.Stdout.AddListTable()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
+		t.Fatal(err)
+	}
+
+	if len(outs.Data) != 1 {
+		t.Errorf("Expected 1 container, %v found", len(outs.Data))
 	}
 	}
 
 
 	setTimeout(t, "Waiting on stopped container timedout", 5*time.Second, func() {
 	setTimeout(t, "Waiting on stopped container timedout", 5*time.Second, func() {
-		job = srv.Eng.Job("wait", c[0].ID)
+		job = srv.Eng.Job("wait", outs.Data[0].Get("ID"))
 		var statusStr string
 		var statusStr string
 		job.Stdout.AddString(&statusStr)
 		job.Stdout.AddString(&statusStr)
 		if err := job.Run(); err != nil {
 		if err := job.Run(); err != nil {
@@ -227,11 +285,21 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
 
 
 	id := createTestContainer(eng, config, t)
 	id := createTestContainer(eng, config, t)
 
 
-	if c := srv.Containers(true, false, -1, "", ""); len(c) != 1 {
-		t.Errorf("Expected 1 container, %v found", len(c))
+	job := srv.Eng.Job("containers")
+	job.SetenvBool("all", true)
+	outs, err := job.Stdout.AddListTable()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
+		t.Fatal(err)
+	}
+
+	if len(outs.Data) != 1 {
+		t.Errorf("Expected 1 container, %v found", len(outs.Data))
 	}
 	}
 
 
-	job := eng.Job("start", id)
+	job = eng.Job("start", id)
 	if err := job.ImportEnv(hostConfig); err != nil {
 	if err := job.ImportEnv(hostConfig); err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -270,8 +338,18 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
 
 
-	if c := srv.Containers(true, false, -1, "", ""); len(c) != 0 {
-		t.Errorf("Expected 0 container, %v found", len(c))
+	job = srv.Eng.Job("containers")
+	job.SetenvBool("all", true)
+	outs, err = job.Stdout.AddListTable()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
+		t.Fatal(err)
+	}
+
+	if len(outs.Data) != 0 {
+		t.Errorf("Expected 0 container, %v found", len(outs.Data))
 	}
 	}
 }
 }
 
 
@@ -465,10 +543,18 @@ func TestDeleteTagWithExistingContainers(t *testing.T) {
 		t.Fatal("No id returned")
 		t.Fatal("No id returned")
 	}
 	}
 
 
-	containers := srv.Containers(true, false, -1, "", "")
+	job := srv.Eng.Job("containers")
+	job.SetenvBool("all", true)
+	outs, err := job.Stdout.AddListTable()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := job.Run(); err != nil {
+		t.Fatal(err)
+	}
 
 
-	if len(containers) != 1 {
-		t.Fatalf("Expected 1 container got %d", len(containers))
+	if len(outs.Data) != 1 {
+		t.Fatalf("Expected 1 container got %d", len(outs.Data))
 	}
 	}
 
 
 	// Try to remove the tag
 	// Try to remove the tag

+ 37 - 22
server.go

@@ -103,6 +103,7 @@ func jobInitApi(job *engine.Job) engine.Status {
 		"inspect":          srv.JobInspect,
 		"inspect":          srv.JobInspect,
 		"events":           srv.Events,
 		"events":           srv.Events,
 		"push":             srv.ImagePush,
 		"push":             srv.ImagePush,
+		"containers":       srv.Containers,
 	} {
 	} {
 		if err := job.Eng.Register(name, handler); err != nil {
 		if err := job.Eng.Register(name, handler); err != nil {
 			job.Error(err)
 			job.Error(err)
@@ -1055,10 +1056,17 @@ func (srv *Server) ContainerChanges(job *engine.Job) engine.Status {
 	return engine.StatusOK
 	return engine.StatusOK
 }
 }
 
 
-func (srv *Server) Containers(all, size bool, n int, since, before string) []APIContainers {
-	var foundBefore bool
-	var displayed int
-	out := []APIContainers{}
+func (srv *Server) Containers(job *engine.Job) engine.Status {
+	var (
+		foundBefore bool
+		displayed   int
+		all         = job.GetenvBool("all")
+		since       = job.Getenv("since")
+		before      = job.Getenv("before")
+		n           = job.GetenvInt("limit")
+		size        = job.GetenvBool("size")
+	)
+	outs := engine.NewTable("Created", 0)
 
 
 	names := map[string][]string{}
 	names := map[string][]string{}
 	srv.runtime.containerGraph.Walk("/", func(p string, e *graphdb.Entity) error {
 	srv.runtime.containerGraph.Walk("/", func(p string, e *graphdb.Entity) error {
@@ -1083,27 +1091,34 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
 			break
 			break
 		}
 		}
 		displayed++
 		displayed++
-		c := createAPIContainer(names[container.ID], container, size, srv.runtime)
-		out = append(out, c)
-	}
-	return out
-}
-
-func createAPIContainer(names []string, container *Container, size bool, runtime *Runtime) APIContainers {
-	c := APIContainers{
-		ID: container.ID,
+		out := &engine.Env{}
+		out.Set("ID", container.ID)
+		out.SetList("Names", names[container.ID])
+		out.Set("Image", srv.runtime.repositories.ImageName(container.Image))
+		out.Set("Command", fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")))
+		out.SetInt64("Created", container.Created.Unix())
+		out.Set("Status", container.State.String())
+		str, err := container.NetworkSettings.PortMappingAPI().ToListString()
+		if err != nil {
+			job.Error(err)
+			return engine.StatusErr
+		}
+		out.Set("Ports", str)
+		if size {
+			sizeRw, sizeRootFs := container.GetSize()
+			out.SetInt64("SizeRw", sizeRw)
+			out.SetInt64("SizeRootFs", sizeRootFs)
+		}
+		outs.Add(out)
 	}
 	}
-	c.Names = names
-	c.Image = runtime.repositories.ImageName(container.Image)
-	c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
-	c.Created = container.Created.Unix()
-	c.Status = container.State.String()
-	c.Ports = container.NetworkSettings.PortMappingAPI()
-	if size {
-		c.SizeRw, c.SizeRootFs = container.GetSize()
+	outs.ReverseSort()
+	if _, err := outs.WriteListTo(job.Stdout); err != nil {
+		job.Error(err)
+		return engine.StatusErr
 	}
 	}
-	return c
+	return engine.StatusOK
 }
 }
+
 func (srv *Server) ContainerCommit(job *engine.Job) engine.Status {
 func (srv *Server) ContainerCommit(job *engine.Job) engine.Status {
 	if len(job.Args) != 1 {
 	if len(job.Args) != 1 {
 		job.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name)
 		job.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name)