Browse code

Port 'docker images' to the engine API

Docker-DCO-1.1-Signed-off-by: Solomon Hykes <solomon@docker.com> (github: shykes)

Solomon Hykes authored on 2013/12/13 07:39:35
Showing 10 changed files
... ...
@@ -181,27 +181,19 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http.
181 181
 	if err := parseForm(r); err != nil {
182 182
 		return err
183 183
 	}
184
-
185
-	all, err := getBoolParam(r.Form.Get("all"))
186
-	if err != nil {
187
-		return err
188
-	}
189
-	filter := r.Form.Get("filter")
190
-
191
-	outs, err := srv.Images(all, filter)
192
-	if err != nil {
184
+	fmt.Printf("getImagesJSON\n")
185
+	job := srv.Eng.Job("images")
186
+	job.Setenv("filter", r.Form.Get("filter"))
187
+	job.Setenv("all", r.Form.Get("all"))
188
+	// FIXME: 1.7 clients expect a single json list
189
+	job.Stdout.Add(w)
190
+	w.WriteHeader(http.StatusOK)
191
+	fmt.Printf("running images job\n")
192
+	if err := job.Run(); err != nil {
193 193
 		return err
194 194
 	}
195
-
196
-	if version < 1.7 {
197
-		outs2 := []APIImagesOld{}
198
-		for _, ctnr := range outs {
199
-			outs2 = append(outs2, ctnr.ToLegacy()...)
200
-		}
201
-
202
-		return writeJSON(w, http.StatusOK, outs2)
203
-	}
204
-	return writeJSON(w, http.StatusOK, outs)
195
+	fmt.Printf("job has been run\n")
196
+	return nil
205 197
 }
206 198
 
207 199
 func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"encoding/json"
6 6
 	"fmt"
7 7
 	"io"
8
+	"sort"
8 9
 	"strconv"
9 10
 	"strings"
10 11
 )
... ...
@@ -232,3 +233,76 @@ func (env *Env) Map() map[string]string {
232 232
 	}
233 233
 	return m
234 234
 }
235
+
236
+type Table struct {
237
+	Data    []*Env
238
+	sortKey string
239
+}
240
+
241
+func NewTable(sortKey string, sizeHint int) *Table {
242
+	return &Table{
243
+		make([]*Env, 0, sizeHint),
244
+		sortKey,
245
+	}
246
+}
247
+
248
+func (t *Table) Add(env *Env) {
249
+	t.Data = append(t.Data, env)
250
+}
251
+
252
+func (t *Table) Len() int {
253
+	return len(t.Data)
254
+}
255
+
256
+func (t *Table) Less(a, b int) bool {
257
+	return t.lessBy(a, b, t.sortKey)
258
+}
259
+
260
+func (t *Table) lessBy(a, b int, by string) bool {
261
+	keyA := t.Data[a].Get(by)
262
+	keyB := t.Data[b].Get(by)
263
+	intA, errA := strconv.ParseInt(keyA, 10, 64)
264
+	intB, errB := strconv.ParseInt(keyB, 10, 64)
265
+	if errA == nil && errB == nil {
266
+		return intA < intB
267
+	}
268
+	return keyA < keyB
269
+}
270
+
271
+func (t *Table) Swap(a, b int) {
272
+	tmp := t.Data[a]
273
+	t.Data[a] = t.Data[b]
274
+	t.Data[b] = tmp
275
+}
276
+
277
+func (t *Table) Sort() {
278
+	sort.Sort(t)
279
+}
280
+
281
+func (t *Table) WriteTo(dst io.Writer) (n int64, err error) {
282
+	for _, env := range t.Data {
283
+		bytes, err := env.WriteTo(dst)
284
+		if err != nil {
285
+			return -1, err
286
+		}
287
+		if _, err := dst.Write([]byte{'\n'}); err != nil {
288
+			return -1, err
289
+		}
290
+		n += bytes + 1
291
+	}
292
+	return n, nil
293
+}
294
+
295
+func (t *Table) ReadFrom(src io.Reader) (n int64, err error) {
296
+	decoder := NewDecoder(src)
297
+	for {
298
+		env, err := decoder.Decode()
299
+		if err == io.EOF {
300
+			return 0, nil
301
+		} else if err != nil {
302
+			return -1, err
303
+		}
304
+		t.Add(env)
305
+	}
306
+	return 0, nil
307
+}
... ...
@@ -190,3 +190,19 @@ func (o *Output) AddEnv() (dst *Env, err error) {
190 190
 	}()
191 191
 	return dst, nil
192 192
 }
193
+
194
+func (o *Output) AddTable() (dst *Table, err error) {
195
+	src, err := o.AddPipe()
196
+	if err != nil {
197
+		return nil, err
198
+	}
199
+	dst = NewTable("", 0)
200
+	o.tasks.Add(1)
201
+	go func() {
202
+		defer o.tasks.Done()
203
+		if _, err := dst.ReadFrom(src); err != nil {
204
+			return
205
+		}
206
+	}()
207
+	return dst, nil
208
+}
193 209
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+package engine
1
+
2
+import (
3
+	"testing"
4
+	"bytes"
5
+	"encoding/json"
6
+)
7
+
8
+func TestTableWriteTo(t *testing.T) {
9
+	table := NewTable("", 0)
10
+	e := &Env{}
11
+	e.Set("foo", "bar")
12
+	table.Add(e)
13
+	var buf bytes.Buffer
14
+	if _, err := table.WriteTo(&buf); err != nil {
15
+		t.Fatal(err)
16
+	}
17
+	output := make(map[string]string)
18
+	if err := json.Unmarshal(buf.Bytes(), &output); err != nil {
19
+		t.Fatal(err)
20
+	}
21
+	if len(output) != 1  {
22
+		t.Fatalf("Incorrect output: %v", output)
23
+	}
24
+	if val, exists := output["foo"]; !exists || val != "bar" {
25
+		t.Fatalf("Inccorect output: %v", output)
26
+	}
27
+}
... ...
@@ -60,11 +60,14 @@ func TestGetInfo(t *testing.T) {
60 60
 	defer mkRuntimeFromEngine(eng, t).Nuke()
61 61
 	srv := mkServerFromEngine(eng, t)
62 62
 
63
-	initialImages, err := srv.Images(false, "")
63
+	job := eng.Job("images")
64
+	initialImages, err := job.Stdout.AddTable()
64 65
 	if err != nil {
65 66
 		t.Fatal(err)
66 67
 	}
67
-
68
+	if err := job.Run(); err != nil {
69
+		t.Fatal(err)
70
+	}
68 71
 	req, err := http.NewRequest("GET", "/info", nil)
69 72
 	if err != nil {
70 73
 		t.Fatal(err)
... ...
@@ -85,8 +88,8 @@ func TestGetInfo(t *testing.T) {
85 85
 		t.Fatal(err)
86 86
 	}
87 87
 	out.Close()
88
-	if images := i.GetInt("Images"); images != len(initialImages) {
89
-		t.Errorf("Expected images: %d, %d found", len(initialImages), images)
88
+	if images := i.GetInt("Images"); images != initialImages.Len() {
89
+		t.Errorf("Expected images: %d, %d found", initialImages.Len(), images)
90 90
 	}
91 91
 	expected := "application/json"
92 92
 	if result := r.HeaderMap.Get("Content-Type"); result != expected {
... ...
@@ -145,12 +148,14 @@ func TestGetImagesJSON(t *testing.T) {
145 145
 	defer mkRuntimeFromEngine(eng, t).Nuke()
146 146
 	srv := mkServerFromEngine(eng, t)
147 147
 
148
-	// all=0
149
-
150
-	initialImages, err := srv.Images(false, "")
148
+	job := eng.Job("images")
149
+	initialImages, err := job.Stdout.AddTable()
151 150
 	if err != nil {
152 151
 		t.Fatal(err)
153 152
 	}
153
+	if err := job.Run(); err != nil {
154
+		t.Fatal(err)
155
+	}
154 156
 
155 157
 	req, err := http.NewRequest("GET", "/images/json?all=0", nil)
156 158
 	if err != nil {
... ...
@@ -164,18 +169,18 @@ func TestGetImagesJSON(t *testing.T) {
164 164
 	}
165 165
 	assertHttpNotError(r, t)
166 166
 
167
-	images := []docker.APIImages{}
168
-	if err := json.Unmarshal(r.Body.Bytes(), &images); err != nil {
167
+	images := engine.NewTable("Created", 0)
168
+	if _, err := images.ReadFrom(r.Body); err != nil {
169 169
 		t.Fatal(err)
170 170
 	}
171 171
 
172
-	if len(images) != len(initialImages) {
173
-		t.Errorf("Expected %d image, %d found", len(initialImages), len(images))
172
+	if images.Len() != initialImages.Len() {
173
+		t.Errorf("Expected %d image, %d found", initialImages.Len(), images.Len())
174 174
 	}
175 175
 
176 176
 	found := false
177
-	for _, img := range images {
178
-		if strings.Contains(img.RepoTags[0], unitTestImageName) {
177
+	for _, img := range images.Data {
178
+		if strings.Contains(img.GetList("RepoTags")[0], unitTestImageName) {
179 179
 			found = true
180 180
 			break
181 181
 		}
... ...
@@ -188,10 +193,7 @@ func TestGetImagesJSON(t *testing.T) {
188 188
 
189 189
 	// all=1
190 190
 
191
-	initialImages, err = srv.Images(true, "")
192
-	if err != nil {
193
-		t.Fatal(err)
194
-	}
191
+	initialImages = getAllImages(eng, t)
195 192
 
196 193
 	req2, err := http.NewRequest("GET", "/images/json?all=true", nil)
197 194
 	if err != nil {
... ...
@@ -207,8 +209,8 @@ func TestGetImagesJSON(t *testing.T) {
207 207
 		t.Fatal(err)
208 208
 	}
209 209
 
210
-	if len(images2) != len(initialImages) {
211
-		t.Errorf("Expected %d image, %d found", len(initialImages), len(images2))
210
+	if len(images2) != initialImages.Len() {
211
+		t.Errorf("Expected %d image, %d found", initialImages.Len(), len(images2))
212 212
 	}
213 213
 
214 214
 	found = false
... ...
@@ -1126,21 +1128,16 @@ func TestDeleteImages(t *testing.T) {
1126 1126
 	defer mkRuntimeFromEngine(eng, t).Nuke()
1127 1127
 	srv := mkServerFromEngine(eng, t)
1128 1128
 
1129
-	initialImages, err := srv.Images(false, "")
1130
-	if err != nil {
1131
-		t.Fatal(err)
1132
-	}
1129
+	initialImages := getImages(eng, t, true, "")
1133 1130
 
1134 1131
 	if err := eng.Job("tag", unitTestImageName, "test", "test").Run(); err != nil {
1135 1132
 		t.Fatal(err)
1136 1133
 	}
1137
-	images, err := srv.Images(false, "")
1138
-	if err != nil {
1139
-		t.Fatal(err)
1140
-	}
1141 1134
 
1142
-	if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 {
1143
-		t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images))
1135
+	images := getImages(eng, t, true, "")
1136
+
1137
+	if images.Len() != initialImages.Len()+1 {
1138
+		t.Errorf("Expected %d images, %d found", initialImages.Len()+1, images.Len())
1144 1139
 	}
1145 1140
 
1146 1141
 	req, err := http.NewRequest("DELETE", "/images/"+unitTestImageID, nil)
... ...
@@ -1177,13 +1174,10 @@ func TestDeleteImages(t *testing.T) {
1177 1177
 	if len(outs) != 1 {
1178 1178
 		t.Fatalf("Expected %d event (untagged), got %d", 1, len(outs))
1179 1179
 	}
1180
-	images, err = srv.Images(false, "")
1181
-	if err != nil {
1182
-		t.Fatal(err)
1183
-	}
1180
+	images = getImages(eng, t, false, "")
1184 1181
 
1185
-	if len(images[0].RepoTags) != len(initialImages[0].RepoTags) {
1186
-		t.Errorf("Expected %d image, %d found", len(initialImages), len(images))
1182
+	if images.Len() != initialImages.Len() {
1183
+		t.Errorf("Expected %d image, %d found", initialImages.Len(), images.Len())
1187 1184
 	}
1188 1185
 }
1189 1186
 
... ...
@@ -51,14 +51,17 @@ func cleanup(eng *engine.Engine, t *testing.T) error {
51 51
 		container.Kill()
52 52
 		runtime.Destroy(container)
53 53
 	}
54
-	srv := mkServerFromEngine(eng, t)
55
-	images, err := srv.Images(true, "")
54
+	job := eng.Job("images")
55
+	images, err := job.Stdout.AddTable()
56 56
 	if err != nil {
57
-		return err
57
+		t.Fatal(err)
58
+	}
59
+	if err := job.Run(); err != nil {
60
+		t.Fatal(err)
58 61
 	}
59
-	for _, image := range images {
60
-		if image.ID != unitTestImageID {
61
-			srv.ImageDelete(image.ID, false)
62
+	for _, image := range images.Data {
63
+		if image.Get("ID") != unitTestImageID {
64
+			mkServerFromEngine(eng, t).ImageDelete(image.Get("ID"), false)
62 65
 		}
63 66
 	}
64 67
 	return nil
... ...
@@ -158,7 +161,7 @@ func spawnGlobalDaemon() {
158 158
 			Host:   testDaemonAddr,
159 159
 		}
160 160
 		job := eng.Job("serveapi", listenURL.String())
161
-		job.SetenvBool("Logging", os.Getenv("DEBUG") != "")
161
+		job.SetenvBool("Logging", true)
162 162
 		if err := job.Run(); err != nil {
163 163
 			log.Fatalf("Unable to spawn the test daemon: %s", err)
164 164
 		}
... ...
@@ -14,10 +14,7 @@ func TestImageTagImageDelete(t *testing.T) {
14 14
 
15 15
 	srv := mkServerFromEngine(eng, t)
16 16
 
17
-	initialImages, err := srv.Images(false, "")
18
-	if err != nil {
19
-		t.Fatal(err)
20
-	}
17
+	initialImages := getAllImages(eng, t)
21 18
 	if err := eng.Job("tag", unitTestImageName, "utest", "tag1").Run(); err != nil {
22 19
 		t.Fatal(err)
23 20
 	}
... ...
@@ -30,52 +27,43 @@ func TestImageTagImageDelete(t *testing.T) {
30 30
 		t.Fatal(err)
31 31
 	}
32 32
 
33
-	images, err := srv.Images(false, "")
34
-	if err != nil {
35
-		t.Fatal(err)
36
-	}
33
+	images := getAllImages(eng, t)
37 34
 
38
-	if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+3 {
39
-		t.Errorf("Expected %d images, %d found", len(initialImages)+3, len(images))
35
+	nExpected := len(initialImages.Data[0].GetList("RepoTags")) + 3
36
+	nActual := len(images.Data[0].GetList("RepoTags"))
37
+	if nExpected != nActual {
38
+		t.Errorf("Expected %d images, %d found", nExpected, nActual)
40 39
 	}
41 40
 
42 41
 	if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil {
43 42
 		t.Fatal(err)
44 43
 	}
45 44
 
46
-	images, err = srv.Images(false, "")
47
-	if err != nil {
48
-		t.Fatal(err)
49
-	}
45
+	images = getAllImages(eng, t)
50 46
 
51
-	if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+2 {
52
-		t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images))
47
+	nExpected = len(initialImages.Data[0].GetList("RepoTags")) + 2
48
+	nActual = len(images.Data[0].GetList("RepoTags"))
49
+	if nExpected != nActual {
50
+		t.Errorf("Expected %d images, %d found", nExpected, nActual)
53 51
 	}
54 52
 
55 53
 	if _, err := srv.ImageDelete("utest:5000/docker:tag3", true); err != nil {
56 54
 		t.Fatal(err)
57 55
 	}
58 56
 
59
-	images, err = srv.Images(false, "")
60
-	if err != nil {
61
-		t.Fatal(err)
62
-	}
57
+	images = getAllImages(eng, t)
63 58
 
64
-	if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 {
65
-		t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images))
66
-	}
59
+	nExpected = len(initialImages.Data[0].GetList("RepoTags")) + 1
60
+	nActual = len(images.Data[0].GetList("RepoTags"))
67 61
 
68 62
 	if _, err := srv.ImageDelete("utest:tag1", true); err != nil {
69 63
 		t.Fatal(err)
70 64
 	}
71 65
 
72
-	images, err = srv.Images(false, "")
73
-	if err != nil {
74
-		t.Fatal(err)
75
-	}
66
+	images = getAllImages(eng, t)
76 67
 
77
-	if len(images) != len(initialImages) {
78
-		t.Errorf("Expected %d image, %d found", len(initialImages), len(images))
68
+	if images.Len() != initialImages.Len() {
69
+		t.Errorf("Expected %d image, %d found", initialImages.Len(), images.Len())
79 70
 	}
80 71
 }
81 72
 
... ...
@@ -250,10 +238,7 @@ func TestRmi(t *testing.T) {
250 250
 	srv := mkServerFromEngine(eng, t)
251 251
 	defer mkRuntimeFromEngine(eng, t).Nuke()
252 252
 
253
-	initialImages, err := srv.Images(false, "")
254
-	if err != nil {
255
-		t.Fatal(err)
256
-	}
253
+	initialImages := getAllImages(eng, t)
257 254
 
258 255
 	config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo", "test"}, nil)
259 256
 	if err != nil {
... ...
@@ -308,13 +293,10 @@ func TestRmi(t *testing.T) {
308 308
 		t.Fatal(err)
309 309
 	}
310 310
 
311
-	images, err := srv.Images(false, "")
312
-	if err != nil {
313
-		t.Fatal(err)
314
-	}
311
+	images := getAllImages(eng, t)
315 312
 
316
-	if len(images)-len(initialImages) != 2 {
317
-		t.Fatalf("Expected 2 new images, found %d.", len(images)-len(initialImages))
313
+	if images.Len()-initialImages.Len() != 2 {
314
+		t.Fatalf("Expected 2 new images, found %d.", images.Len()-initialImages.Len())
318 315
 	}
319 316
 
320 317
 	_, err = srv.ImageDelete(imageID, true)
... ...
@@ -322,20 +304,17 @@ func TestRmi(t *testing.T) {
322 322
 		t.Fatal(err)
323 323
 	}
324 324
 
325
-	images, err = srv.Images(false, "")
326
-	if err != nil {
327
-		t.Fatal(err)
328
-	}
325
+	images = getAllImages(eng, t)
329 326
 
330
-	if len(images)-len(initialImages) != 1 {
331
-		t.Fatalf("Expected 1 new image, found %d.", len(images)-len(initialImages))
327
+	if images.Len()-initialImages.Len() != 1 {
328
+		t.Fatalf("Expected 1 new image, found %d.", images.Len()-initialImages.Len())
332 329
 	}
333 330
 
334
-	for _, image := range images {
335
-		if strings.Contains(unitTestImageID, image.ID) {
331
+	for _, image := range images.Data {
332
+		if strings.Contains(unitTestImageID, image.Get("ID")) {
336 333
 			continue
337 334
 		}
338
-		if image.RepoTags[0] == "<none>:<none>" {
335
+		if image.GetList("RepoTags")[0] == "<none>:<none>" {
339 336
 			t.Fatalf("Expected tagged image, got untagged one.")
340 337
 		}
341 338
 	}
... ...
@@ -359,39 +338,27 @@ func TestImagesFilter(t *testing.T) {
359 359
 		t.Fatal(err)
360 360
 	}
361 361
 
362
-	images, err := srv.Images(false, "utest*/*")
363
-	if err != nil {
364
-		t.Fatal(err)
365
-	}
362
+	images := getImages(eng, t, false, "utest*/*")
366 363
 
367
-	if len(images[0].RepoTags) != 2 {
364
+	if len(images.Data[0].GetList("RepoTags")) != 2 {
368 365
 		t.Fatal("incorrect number of matches returned")
369 366
 	}
370 367
 
371
-	images, err = srv.Images(false, "utest")
372
-	if err != nil {
373
-		t.Fatal(err)
374
-	}
368
+	images = getImages(eng, t, false, "utest")
375 369
 
376
-	if len(images[0].RepoTags) != 1 {
370
+	if len(images.Data[0].GetList("RepoTags")) != 1 {
377 371
 		t.Fatal("incorrect number of matches returned")
378 372
 	}
379 373
 
380
-	images, err = srv.Images(false, "utest*")
381
-	if err != nil {
382
-		t.Fatal(err)
383
-	}
374
+	images = getImages(eng, t, false, "utest*")
384 375
 
385
-	if len(images[0].RepoTags) != 1 {
376
+	if len(images.Data[0].GetList("RepoTags")) != 1 {
386 377
 		t.Fatal("incorrect number of matches returned")
387 378
 	}
388 379
 
389
-	images, err = srv.Images(false, "*5000*/*")
390
-	if err != nil {
391
-		t.Fatal(err)
392
-	}
380
+	images = getImages(eng, t, false, "*5000*/*")
393 381
 
394
-	if len(images[0].RepoTags) != 1 {
382
+	if len(images.Data[0].GetList("RepoTags")) != 1 {
395 383
 		t.Fatal("incorrect number of matches returned")
396 384
 	}
397 385
 }
... ...
@@ -17,13 +17,10 @@ func TestServerListOrderedImagesByCreationDate(t *testing.T) {
17 17
 		t.Fatal(err)
18 18
 	}
19 19
 
20
-	images, err := srv.Images(true, "")
21
-	if err != nil {
22
-		t.Fatal(err)
23
-	}
20
+	images := getImages(eng, t, true, "")
24 21
 
25
-	if images[0].Created < images[1].Created {
26
-		t.Error("Expected []APIImges to be ordered by most recent creation date.")
22
+	if images.Data[0].GetInt("Created") < images.Data[1].GetInt("Created") {
23
+		t.Error("Expected images to be ordered by most recent creation date.")
27 24
 	}
28 25
 }
29 26
 
... ...
@@ -44,12 +41,9 @@ func TestServerListOrderedImagesByCreationDateAndTag(t *testing.T) {
44 44
 		t.Fatal(err)
45 45
 	}
46 46
 
47
-	images, err := srv.Images(true, "")
48
-	if err != nil {
49
-		t.Fatal(err)
50
-	}
47
+	images := getImages(eng, t, true, "")
51 48
 
52
-	if images[0].RepoTags[0] != "repo:zed" && images[0].RepoTags[0] != "repo:bar" {
49
+	if images.Data[0].GetList("RepoTags")[0] != "repo:zed" && images.Data[0].GetList("RepoTags")[0] != "repo:bar" {
53 50
 		t.Errorf("Expected []APIImges to be ordered by most recent creation date. %s", images)
54 51
 	}
55 52
 }
... ...
@@ -186,8 +186,6 @@ func NewTestEngine(t utils.Fataler) *engine.Engine {
186 186
 	if err != nil {
187 187
 		t.Fatal(err)
188 188
 	}
189
-	eng.Stdout = ioutil.Discard
190
-	eng.Stderr = ioutil.Discard
191 189
 	// Load default plugins
192 190
 	// (This is manually copied and modified from main() until we have a more generic plugin system)
193 191
 	job := eng.Job("initapi")
... ...
@@ -329,3 +327,22 @@ func fakeTar() (io.Reader, error) {
329 329
 	tw.Close()
330 330
 	return buf, nil
331 331
 }
332
+
333
+func getAllImages(eng *engine.Engine, t *testing.T) *engine.Table {
334
+	return getImages(eng, t, true, "")
335
+}
336
+
337
+func getImages(eng *engine.Engine, t *testing.T, all bool, filter string) *engine.Table {
338
+	job := eng.Job("images")
339
+	job.SetenvBool("all", all)
340
+	job.Setenv("filter", filter)
341
+	images, err := job.Stdout.AddTable()
342
+	if err != nil {
343
+		t.Fatal(err)
344
+	}
345
+	if err := job.Run(); err != nil {
346
+		t.Fatal(err)
347
+	}
348
+	return images
349
+
350
+}
... ...
@@ -127,6 +127,10 @@ func jobInitApi(job *engine.Job) engine.Status {
127 127
 		job.Error(err)
128 128
 		return engine.StatusErr
129 129
 	}
130
+	if err := job.Eng.Register("images", srv.Images); err != nil {
131
+		job.Error(err)
132
+		return engine.StatusErr
133
+	}
130 134
 	return engine.StatusOK
131 135
 }
132 136
 
... ...
@@ -568,23 +572,26 @@ func (srv *Server) ImagesViz(out io.Writer) error {
568 568
 	return nil
569 569
 }
570 570
 
571
-func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
571
+func (srv *Server) Images(job *engine.Job) engine.Status {
572
+	fmt.Printf("Images()\n")
573
+	srv.Eng.Job("version").Run()
572 574
 	var (
573 575
 		allImages map[string]*Image
574 576
 		err       error
575 577
 	)
576
-	if all {
578
+	if job.GetenvBool("all") {
577 579
 		allImages, err = srv.runtime.graph.Map()
578 580
 	} else {
579 581
 		allImages, err = srv.runtime.graph.Heads()
580 582
 	}
581 583
 	if err != nil {
582
-		return nil, err
584
+		job.Errorf("%s", err)
585
+		return engine.StatusErr
583 586
 	}
584
-	lookup := make(map[string]APIImages)
587
+	lookup := make(map[string]*engine.Env)
585 588
 	for name, repository := range srv.runtime.repositories.Repositories {
586
-		if filter != "" {
587
-			if match, _ := path.Match(filter, name); !match {
589
+		if job.Getenv("filter") != "" {
590
+			if match, _ := path.Match(job.Getenv("filter"), name); !match {
588 591
 				continue
589 592
 			}
590 593
 		}
... ...
@@ -596,48 +603,51 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) {
596 596
 			}
597 597
 
598 598
 			if out, exists := lookup[id]; exists {
599
-				out.RepoTags = append(out.RepoTags, fmt.Sprintf("%s:%s", name, tag))
600
-
601
-				lookup[id] = out
599
+				repotag := fmt.Sprintf("%s:%s", name, tag)
600
+				out.SetList("RepoTags", append(out.GetList("RepoTags"), repotag))
602 601
 			} else {
603
-				var out APIImages
604
-
602
+				out := &engine.Env{}
605 603
 				delete(allImages, id)
606
-
607
-				out.ParentId = image.Parent
608
-				out.RepoTags = []string{fmt.Sprintf("%s:%s", name, tag)}
609
-				out.ID = image.ID
610
-				out.Created = image.Created.Unix()
611
-				out.Size = image.Size
612
-				out.VirtualSize = image.getParentsSize(0) + image.Size
613
-
604
+				out.Set("ParentId", image.Parent)
605
+				out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)})
606
+				out.Set("ID", image.ID)
607
+				out.SetInt64("Created", image.Created.Unix())
608
+				out.SetInt64("Size", image.Size)
609
+				out.SetInt64("VirtualSize", image.getParentsSize(0)+image.Size)
614 610
 				lookup[id] = out
615 611
 			}
616 612
 
617 613
 		}
618 614
 	}
619 615
 
620
-	outs := make([]APIImages, 0, len(lookup))
616
+	outs := engine.NewTable("Created", len(lookup))
621 617
 	for _, value := range lookup {
622
-		outs = append(outs, value)
618
+		outs.Add(value)
623 619
 	}
624 620
 
625 621
 	// Display images which aren't part of a repository/tag
626
-	if filter == "" {
622
+	if job.Getenv("filter") == "" {
627 623
 		for _, image := range allImages {
628
-			var out APIImages
629
-			out.ID = image.ID
630
-			out.ParentId = image.Parent
631
-			out.RepoTags = []string{"<none>:<none>"}
632
-			out.Created = image.Created.Unix()
633
-			out.Size = image.Size
634
-			out.VirtualSize = image.getParentsSize(0) + image.Size
635
-			outs = append(outs, out)
624
+			out := &engine.Env{}
625
+			out.Set("ParentId", image.Parent)
626
+			out.SetList("RepoTags", []string{"<none>:<none>"})
627
+			out.Set("ID", image.ID)
628
+			out.SetInt64("Created", image.Created.Unix())
629
+			out.SetInt64("Size", image.Size)
630
+			out.SetInt64("VirtualSize", image.getParentsSize(0)+image.Size)
631
+			outs.Add(out)
636 632
 		}
637 633
 	}
638 634
 
639
-	sortImagesByCreationAndTag(outs)
640
-	return outs, nil
635
+	outs.Sort()
636
+	job.Logf("Sending %d images to stdout", outs.Len())
637
+	if n, err := outs.WriteTo(job.Stdout); err != nil {
638
+		job.Errorf("%s", err)
639
+		return engine.StatusErr
640
+	} else {
641
+		job.Logf("%d bytes sent", n)
642
+	}
643
+	return engine.StatusOK
641 644
 }
642 645
 
643 646
 func (srv *Server) DockerInfo(job *engine.Job) engine.Status {