Browse code

Move docker build to client

Guillaume J. Charmes authored on 2013/05/20 02:46:24
Showing 7 changed files
... ...
@@ -370,19 +370,6 @@ func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars ma
370 370
 	return nil
371 371
 }
372 372
 
373
-func postBuild(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
374
-	in, out, err := hijackServer(w)
375
-	if err != nil {
376
-		return err
377
-	}
378
-	defer in.Close()
379
-	fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
380
-	if err := srv.ImageCreateFromFile(in, out); err != nil {
381
-		fmt.Fprintf(out, "Error: %s\n", err)
382
-	}
383
-	return nil
384
-}
385
-
386 373
 func postContainersCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
387 374
 	config := &Config{}
388 375
 	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
... ...
@@ -593,6 +580,25 @@ func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request, vars m
593 593
 	return nil
594 594
 }
595 595
 
596
+func postImagesGetCache(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
597
+	apiConfig := &ApiImageConfig{}
598
+	if err := json.NewDecoder(r.Body).Decode(apiConfig); err != nil {
599
+		return err
600
+	}
601
+
602
+	image, err := srv.ImageGetCached(apiConfig.Id, apiConfig.Config)
603
+	if err != nil {
604
+		return err
605
+	}
606
+	apiId := &ApiId{Id: image.Id}
607
+	b, err := json.Marshal(apiId)
608
+	if err != nil {
609
+		return err
610
+	}
611
+	writeJson(w, b)
612
+	return nil
613
+}
614
+
596 615
 func ListenAndServe(addr string, srv *Server, logging bool) error {
597 616
 	r := mux.NewRouter()
598 617
 	log.Printf("Listening for HTTP on %s\n", addr)
... ...
@@ -615,11 +621,11 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
615 615
 		"POST": {
616 616
 			"/auth":                         postAuth,
617 617
 			"/commit":                       postCommit,
618
-			"/build":                        postBuild,
619 618
 			"/images/create":                postImagesCreate,
620 619
 			"/images/{name:.*}/insert":      postImagesInsert,
621 620
 			"/images/{name:.*}/push":        postImagesPush,
622 621
 			"/images/{name:.*}/tag":         postImagesTag,
622
+			"/images/getCache":              postImagesGetCache,
623 623
 			"/containers/create":            postContainersCreate,
624 624
 			"/containers/{name:.*}/kill":    postContainersKill,
625 625
 			"/containers/{name:.*}/restart": postContainersRestart,
... ...
@@ -64,3 +64,8 @@ type ApiWait struct {
64 64
 type ApiAuth struct {
65 65
 	Status string
66 66
 }
67
+
68
+type ApiImageConfig struct {
69
+	Id string
70
+	*Config
71
+}
... ...
@@ -1,14 +1,9 @@
1 1
 package docker
2 2
 
3 3
 import (
4
-	"bufio"
5
-	"encoding/json"
6 4
 	"fmt"
7
-	"github.com/dotcloud/docker/utils"
8
-	"io"
9 5
 	"os"
10 6
 	"path"
11
-	"strings"
12 7
 	"time"
13 8
 )
14 9
 
... ...
@@ -16,6 +11,9 @@ type Builder struct {
16 16
 	runtime      *Runtime
17 17
 	repositories *TagStore
18 18
 	graph        *Graph
19
+
20
+	config *Config
21
+	image  *Image
19 22
 }
20 23
 
21 24
 func NewBuilder(runtime *Runtime) *Builder {
... ...
@@ -26,45 +24,6 @@ func NewBuilder(runtime *Runtime) *Builder {
26 26
 	}
27 27
 }
28 28
 
29
-func (builder *Builder) mergeConfig(userConf, imageConf *Config) {
30
-	if userConf.Hostname != "" {
31
-		userConf.Hostname = imageConf.Hostname
32
-	}
33
-	if userConf.User != "" {
34
-		userConf.User = imageConf.User
35
-	}
36
-	if userConf.Memory == 0 {
37
-		userConf.Memory = imageConf.Memory
38
-	}
39
-	if userConf.MemorySwap == 0 {
40
-		userConf.MemorySwap = imageConf.MemorySwap
41
-	}
42
-	if userConf.CpuShares == 0 {
43
-		userConf.CpuShares = imageConf.CpuShares
44
-	}
45
-	if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 {
46
-		userConf.PortSpecs = imageConf.PortSpecs
47
-	}
48
-	if !userConf.Tty {
49
-		userConf.Tty = imageConf.Tty
50
-	}
51
-	if !userConf.OpenStdin {
52
-		userConf.OpenStdin = imageConf.OpenStdin
53
-	}
54
-	if !userConf.StdinOnce {
55
-		userConf.StdinOnce = imageConf.StdinOnce
56
-	}
57
-	if userConf.Env == nil || len(userConf.Env) == 0 {
58
-		userConf.Env = imageConf.Env
59
-	}
60
-	if userConf.Cmd == nil || len(userConf.Cmd) == 0 {
61
-		userConf.Cmd = imageConf.Cmd
62
-	}
63
-	if userConf.Dns == nil || len(userConf.Dns) == 0 {
64
-		userConf.Dns = imageConf.Dns
65
-	}
66
-}
67
-
68 29
 func (builder *Builder) Create(config *Config) (*Container, error) {
69 30
 	// Lookup image
70 31
 	img, err := builder.repositories.LookupImage(config.Image)
... ...
@@ -73,7 +32,7 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
73 73
 	}
74 74
 
75 75
 	if img.Config != nil {
76
-		builder.mergeConfig(config, img.Config)
76
+		MergeConfig(config, img.Config)
77 77
 	}
78 78
 
79 79
 	if config.Cmd == nil || len(config.Cmd) == 0 {
... ...
@@ -157,312 +116,3 @@ func (builder *Builder) Commit(container *Container, repository, tag, comment, a
157 157
 	}
158 158
 	return img, nil
159 159
 }
160
-
161
-func (builder *Builder) clearTmp(containers, images map[string]struct{}) {
162
-	for c := range containers {
163
-		tmp := builder.runtime.Get(c)
164
-		builder.runtime.Destroy(tmp)
165
-		utils.Debugf("Removing container %s", c)
166
-	}
167
-	for i := range images {
168
-		builder.runtime.graph.Delete(i)
169
-		utils.Debugf("Removing image %s", i)
170
-	}
171
-}
172
-
173
-func (builder *Builder) getCachedImage(image *Image, config *Config) (*Image, error) {
174
-	// Retrieve all images
175
-	images, err := builder.graph.All()
176
-	if err != nil {
177
-		return nil, err
178
-	}
179
-
180
-	// Store the tree in a map of map (map[parentId][childId])
181
-	imageMap := make(map[string]map[string]struct{})
182
-	for _, img := range images {
183
-		if _, exists := imageMap[img.Parent]; !exists {
184
-			imageMap[img.Parent] = make(map[string]struct{})
185
-		}
186
-		imageMap[img.Parent][img.Id] = struct{}{}
187
-	}
188
-
189
-	// Loop on the children of the given image and check the config
190
-	for elem := range imageMap[image.Id] {
191
-		img, err := builder.graph.Get(elem)
192
-		if err != nil {
193
-			return nil, err
194
-		}
195
-		if CompareConfig(&img.ContainerConfig, config) {
196
-			return img, nil
197
-		}
198
-	}
199
-	return nil, nil
200
-}
201
-
202
-func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, error) {
203
-	var (
204
-		image, base   *Image
205
-		config        *Config
206
-		maintainer    string
207
-		env           map[string]string   = make(map[string]string)
208
-		tmpContainers map[string]struct{} = make(map[string]struct{})
209
-		tmpImages     map[string]struct{} = make(map[string]struct{})
210
-	)
211
-	defer builder.clearTmp(tmpContainers, tmpImages)
212
-
213
-	file := bufio.NewReader(dockerfile)
214
-	for {
215
-		line, err := file.ReadString('\n')
216
-		if err != nil {
217
-			if err == io.EOF {
218
-				break
219
-			}
220
-			return nil, err
221
-		}
222
-		line = strings.Replace(strings.TrimSpace(line), "	", " ", 1)
223
-		// Skip comments and empty line
224
-		if len(line) == 0 || line[0] == '#' {
225
-			continue
226
-		}
227
-		tmp := strings.SplitN(line, " ", 2)
228
-		if len(tmp) != 2 {
229
-			return nil, fmt.Errorf("Invalid Dockerfile format")
230
-		}
231
-		instruction := strings.Trim(tmp[0], " ")
232
-		arguments := strings.Trim(tmp[1], " ")
233
-		switch strings.ToLower(instruction) {
234
-		case "from":
235
-			fmt.Fprintf(stdout, "FROM %s\n", arguments)
236
-			image, err = builder.runtime.repositories.LookupImage(arguments)
237
-			if err != nil {
238
-				// if builder.runtime.graph.IsNotExist(err) {
239
-
240
-				// 	var tag, remote string
241
-				// 	if strings.Contains(arguments, ":") {
242
-				// 		remoteParts := strings.Split(arguments, ":")
243
-				// 		tag = remoteParts[1]
244
-				// 		remote = remoteParts[0]
245
-				// 	} else {
246
-				// 		remote = arguments
247
-				// 	}
248
-
249
-				// 	panic("TODO: reimplement this")
250
-				// 	// if err := builder.runtime.graph.PullRepository(stdout, remote, tag, builder.runtime.repositories, builder.runtime.authConfig); err != nil {
251
-				// 	// 	return nil, err
252
-				// 	// }
253
-
254
-				// 	image, err = builder.runtime.repositories.LookupImage(arguments)
255
-				// 	if err != nil {
256
-				// 		return nil, err
257
-				// 	}
258
-				// } else {
259
-				return nil, err
260
-				// }
261
-			}
262
-			config = &Config{}
263
-
264
-			break
265
-		case "maintainer":
266
-			fmt.Fprintf(stdout, "MAINTAINER %s\n", arguments)
267
-			maintainer = arguments
268
-			break
269
-		case "run":
270
-			fmt.Fprintf(stdout, "RUN %s\n", arguments)
271
-			if image == nil {
272
-				return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
273
-			}
274
-			config, _, err := ParseRun([]string{image.Id, "/bin/sh", "-c", arguments}, builder.runtime.capabilities)
275
-			if err != nil {
276
-				return nil, err
277
-			}
278
-
279
-			for key, value := range env {
280
-				config.Env = append(config.Env, fmt.Sprintf("%s=%s", key, value))
281
-			}
282
-
283
-			if cache, err := builder.getCachedImage(image, config); err != nil {
284
-				return nil, err
285
-			} else if cache != nil {
286
-				image = cache
287
-				fmt.Fprintf(stdout, "===> %s\n", image.ShortId())
288
-				break
289
-			}
290
-
291
-			utils.Debugf("Env -----> %v ------ %v\n", config.Env, env)
292
-
293
-			// Create the container and start it
294
-			c, err := builder.Create(config)
295
-			if err != nil {
296
-				return nil, err
297
-			}
298
-
299
-			if os.Getenv("DEBUG") != "" {
300
-				out, _ := c.StdoutPipe()
301
-				err2, _ := c.StderrPipe()
302
-				go io.Copy(os.Stdout, out)
303
-				go io.Copy(os.Stdout, err2)
304
-			}
305
-
306
-			if err := c.Start(); err != nil {
307
-				return nil, err
308
-			}
309
-			tmpContainers[c.Id] = struct{}{}
310
-
311
-			// Wait for it to finish
312
-			if result := c.Wait(); result != 0 {
313
-				return nil, fmt.Errorf("!!! '%s' return non-zero exit code '%d'. Aborting.", arguments, result)
314
-			}
315
-
316
-			// Commit the container
317
-			base, err = builder.Commit(c, "", "", "", maintainer, nil)
318
-			if err != nil {
319
-				return nil, err
320
-			}
321
-			tmpImages[base.Id] = struct{}{}
322
-
323
-			fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
324
-
325
-			// use the base as the new image
326
-			image = base
327
-
328
-			break
329
-		case "env":
330
-			tmp := strings.SplitN(arguments, " ", 2)
331
-			if len(tmp) != 2 {
332
-				return nil, fmt.Errorf("Invalid ENV format")
333
-			}
334
-			key := strings.Trim(tmp[0], " ")
335
-			value := strings.Trim(tmp[1], " ")
336
-			fmt.Fprintf(stdout, "ENV %s %s\n", key, value)
337
-			env[key] = value
338
-			if image != nil {
339
-				fmt.Fprintf(stdout, "===> %s\n", image.ShortId())
340
-			} else {
341
-				fmt.Fprintf(stdout, "===> <nil>\n")
342
-			}
343
-			break
344
-		case "cmd":
345
-			fmt.Fprintf(stdout, "CMD %s\n", arguments)
346
-
347
-			// Create the container and start it
348
-			c, err := builder.Create(&Config{Image: image.Id, Cmd: []string{"", ""}})
349
-			if err != nil {
350
-				return nil, err
351
-			}
352
-			if err := c.Start(); err != nil {
353
-				return nil, err
354
-			}
355
-			tmpContainers[c.Id] = struct{}{}
356
-
357
-			cmd := []string{}
358
-			if err := json.Unmarshal([]byte(arguments), &cmd); err != nil {
359
-				return nil, err
360
-			}
361
-			config.Cmd = cmd
362
-
363
-			// Commit the container
364
-			base, err = builder.Commit(c, "", "", "", maintainer, config)
365
-			if err != nil {
366
-				return nil, err
367
-			}
368
-			tmpImages[base.Id] = struct{}{}
369
-
370
-			fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
371
-			image = base
372
-			break
373
-		case "expose":
374
-			ports := strings.Split(arguments, " ")
375
-
376
-			fmt.Fprintf(stdout, "EXPOSE %v\n", ports)
377
-			if image == nil {
378
-				return nil, fmt.Errorf("Please provide a source image with `from` prior to copy")
379
-			}
380
-
381
-			// Create the container and start it
382
-			c, err := builder.Create(&Config{Image: image.Id, Cmd: []string{"", ""}})
383
-			if err != nil {
384
-				return nil, err
385
-			}
386
-			if err := c.Start(); err != nil {
387
-				return nil, err
388
-			}
389
-			tmpContainers[c.Id] = struct{}{}
390
-
391
-			config.PortSpecs = append(ports, config.PortSpecs...)
392
-
393
-			// Commit the container
394
-			base, err = builder.Commit(c, "", "", "", maintainer, config)
395
-			if err != nil {
396
-				return nil, err
397
-			}
398
-			tmpImages[base.Id] = struct{}{}
399
-
400
-			fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
401
-			image = base
402
-			break
403
-		case "insert":
404
-			if image == nil {
405
-				return nil, fmt.Errorf("Please provide a source image with `from` prior to copy")
406
-			}
407
-			tmp = strings.SplitN(arguments, " ", 2)
408
-			if len(tmp) != 2 {
409
-				return nil, fmt.Errorf("Invalid INSERT format")
410
-			}
411
-			sourceUrl := strings.Trim(tmp[0], " ")
412
-			destPath := strings.Trim(tmp[1], " ")
413
-			fmt.Fprintf(stdout, "COPY %s to %s in %s\n", sourceUrl, destPath, base.ShortId())
414
-
415
-			file, err := utils.Download(sourceUrl, stdout)
416
-			if err != nil {
417
-				return nil, err
418
-			}
419
-			defer file.Body.Close()
420
-
421
-			config, _, err := ParseRun([]string{base.Id, "echo", "insert", sourceUrl, destPath}, builder.runtime.capabilities)
422
-			if err != nil {
423
-				return nil, err
424
-			}
425
-			c, err := builder.Create(config)
426
-			if err != nil {
427
-				return nil, err
428
-			}
429
-
430
-			if err := c.Start(); err != nil {
431
-				return nil, err
432
-			}
433
-
434
-			// Wait for echo to finish
435
-			if result := c.Wait(); result != 0 {
436
-				return nil, fmt.Errorf("!!! '%s' return non-zero exit code '%d'. Aborting.", arguments, result)
437
-			}
438
-
439
-			if err := c.Inject(file.Body, destPath); err != nil {
440
-				return nil, err
441
-			}
442
-
443
-			base, err = builder.Commit(c, "", "", "", maintainer, nil)
444
-			if err != nil {
445
-				return nil, err
446
-			}
447
-			fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
448
-
449
-			image = base
450
-
451
-			break
452
-		default:
453
-			fmt.Fprintf(stdout, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
454
-		}
455
-	}
456
-	if image != nil {
457
-		// The build is successful, keep the temporary containers and images
458
-		for i := range tmpImages {
459
-			delete(tmpImages, i)
460
-		}
461
-		for i := range tmpContainers {
462
-			delete(tmpContainers, i)
463
-		}
464
-		fmt.Fprintf(stdout, "Build finished. image id: %s\n", image.ShortId())
465
-		return image, nil
466
-	}
467
-	return nil, fmt.Errorf("An error occured during the build\n")
468
-}
469 160
new file mode 100644
... ...
@@ -0,0 +1,275 @@
0
+package docker
1
+
2
+import (
3
+	"bufio"
4
+	"encoding/json"
5
+	"fmt"
6
+	"github.com/dotcloud/docker/utils"
7
+	"io"
8
+	"net/url"
9
+	"os"
10
+	"reflect"
11
+	"strings"
12
+)
13
+
14
+type BuilderClient struct {
15
+	builder *Builder
16
+	cli     *DockerCli
17
+
18
+	image      string
19
+	maintainer string
20
+	config     *Config
21
+
22
+	tmpContainers map[string]struct{}
23
+	tmpImages     map[string]struct{}
24
+
25
+	needCommit bool
26
+}
27
+
28
+func (b *BuilderClient) clearTmp(containers, images map[string]struct{}) {
29
+	for c := range containers {
30
+		tmp := b.builder.runtime.Get(c)
31
+		b.builder.runtime.Destroy(tmp)
32
+		utils.Debugf("Removing container %s", c)
33
+	}
34
+	for i := range images {
35
+		b.builder.runtime.graph.Delete(i)
36
+		utils.Debugf("Removing image %s", i)
37
+	}
38
+}
39
+
40
+func (b *BuilderClient) From(name string) error {
41
+	obj, statusCode, err := b.cli.call("GET", "/images/"+name+"/json", nil)
42
+	if statusCode == 404 {
43
+		if err := b.cli.hijack("POST", "/images/create?fromImage="+name, false); err != nil {
44
+			return err
45
+		}
46
+		obj, _, err = b.cli.call("GET", "/images/"+name+"/json", nil)
47
+		if err != nil {
48
+			return err
49
+		}
50
+	}
51
+	if err != nil {
52
+		return err
53
+	}
54
+
55
+	img := &ApiImages{}
56
+	if err := json.Unmarshal(obj, img); err != nil {
57
+		return err
58
+	}
59
+	b.image = img.Id
60
+	return nil
61
+}
62
+
63
+func (b *BuilderClient) Maintainer(name string) error {
64
+	b.needCommit = true
65
+	b.maintainer = name
66
+	return nil
67
+}
68
+
69
+func (b *BuilderClient) Run(args string) error {
70
+	if b.image == "" {
71
+		return fmt.Errorf("Please provide a source image with `from` prior to run")
72
+	}
73
+	config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, b.builder.runtime.capabilities)
74
+	if err != nil {
75
+		return err
76
+	}
77
+	MergeConfig(b.config, config)
78
+	body, statusCode, err := b.cli.call("POST", "/images/getCache", &ApiImageConfig{Id: b.image, Config: b.config})
79
+	if err != nil {
80
+		if statusCode != 404 {
81
+			return err
82
+		}
83
+	}
84
+	if statusCode != 404 {
85
+		apiId := &ApiId{}
86
+		if err := json.Unmarshal(body, apiId); err != nil {
87
+			return err
88
+		}
89
+		b.image = apiId.Id
90
+		return nil
91
+	}
92
+
93
+	body, _, err = b.cli.call("POST", "/containers/create", b.config)
94
+	if err != nil {
95
+		return err
96
+	}
97
+
98
+	out := &ApiRun{}
99
+	err = json.Unmarshal(body, out)
100
+	if err != nil {
101
+		return err
102
+	}
103
+
104
+	for _, warning := range out.Warnings {
105
+		fmt.Fprintln(os.Stderr, "WARNING: ", warning)
106
+	}
107
+
108
+	//start the container
109
+	_, _, err = b.cli.call("POST", "/containers/"+out.Id+"/start", nil)
110
+	if err != nil {
111
+		return err
112
+	}
113
+	b.tmpContainers[out.Id] = struct{}{}
114
+
115
+	// Wait for it to finish
116
+	_, _, err = b.cli.call("POST", "/containers/"+out.Id+"/wait", nil)
117
+	if err != nil {
118
+		return err
119
+	}
120
+
121
+	// Commit the container
122
+	v := url.Values{}
123
+	v.Set("container", out.Id)
124
+	v.Set("author", b.maintainer)
125
+	body, _, err = b.cli.call("POST", "/commit?"+v.Encode(), b.config)
126
+	if err != nil {
127
+		return err
128
+	}
129
+	apiId := &ApiId{}
130
+	err = json.Unmarshal(body, apiId)
131
+	if err != nil {
132
+		return err
133
+	}
134
+	b.tmpImages[apiId.Id] = struct{}{}
135
+	b.image = apiId.Id
136
+	b.needCommit = false
137
+	return nil
138
+}
139
+
140
+func (b *BuilderClient) Env(args string) error {
141
+	b.needCommit = true
142
+	tmp := strings.SplitN(args, " ", 2)
143
+	if len(tmp) != 2 {
144
+		return fmt.Errorf("Invalid ENV format")
145
+	}
146
+	key := strings.Trim(tmp[0], " ")
147
+	value := strings.Trim(tmp[1], " ")
148
+
149
+	for i, elem := range b.config.Env {
150
+		if strings.HasPrefix(elem, key+"=") {
151
+			b.config.Env[i] = key + "=" + value
152
+			return nil
153
+		}
154
+	}
155
+	b.config.Env = append(b.config.Env, key+"="+value)
156
+	return nil
157
+}
158
+
159
+func (b *BuilderClient) Cmd(args string) error {
160
+	b.needCommit = true
161
+	b.config.Cmd = []string{"/bin/sh", "-c", args}
162
+	return nil
163
+}
164
+
165
+func (b *BuilderClient) Expose(args string) error {
166
+	ports := strings.Split(args, " ")
167
+	b.config.PortSpecs = append(ports, b.config.PortSpecs...)
168
+	return nil
169
+}
170
+
171
+func (b *BuilderClient) Insert(args string) error {
172
+	// FIXME: Reimplement this once the remove_hijack branch gets merged.
173
+	// We need to retrieve the resulting Id
174
+	return fmt.Errorf("INSERT not implemented")
175
+}
176
+
177
+func NewBuilderClient(dockerfile io.Reader) (string, error) {
178
+	//	defer b.clearTmp(tmpContainers, tmpImages)
179
+
180
+	b := &BuilderClient{
181
+		cli: NewDockerCli("0.0.0.0", 4243),
182
+	}
183
+	file := bufio.NewReader(dockerfile)
184
+	for {
185
+		line, err := file.ReadString('\n')
186
+		if err != nil {
187
+			if err == io.EOF {
188
+				break
189
+			}
190
+			return "", err
191
+		}
192
+		line = strings.Replace(strings.TrimSpace(line), "	", " ", 1)
193
+		// Skip comments and empty line
194
+		if len(line) == 0 || line[0] == '#' {
195
+			continue
196
+		}
197
+		tmp := strings.SplitN(line, " ", 2)
198
+		if len(tmp) != 2 {
199
+			return "", fmt.Errorf("Invalid Dockerfile format")
200
+		}
201
+		instruction := strings.ToLower(strings.Trim(tmp[0], " "))
202
+		arguments := strings.Trim(tmp[1], " ")
203
+
204
+		fmt.Printf("%s %s\n", strings.ToUpper(instruction), arguments)
205
+
206
+		method, exists := reflect.TypeOf(b).MethodByName(strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
207
+		if !exists {
208
+			fmt.Printf("Skipping unknown instruction %s\n", strings.ToUpper(instruction))
209
+		}
210
+		ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
211
+		if ret != nil {
212
+			return "", ret.(error)
213
+		}
214
+
215
+		fmt.Printf("===> %v\n", b.image)
216
+	}
217
+	if b.needCommit {
218
+		body, _, err = b.cli.call("POST", "/containers/create", b.config)
219
+		if err != nil {
220
+			return err
221
+		}
222
+
223
+		out := &ApiRun{}
224
+		err = json.Unmarshal(body, out)
225
+		if err != nil {
226
+			return err
227
+		}
228
+
229
+		for _, warning := range out.Warnings {
230
+			fmt.Fprintln(os.Stderr, "WARNING: ", warning)
231
+		}
232
+
233
+		//start the container
234
+		_, _, err = b.cli.call("POST", "/containers/"+out.Id+"/start", nil)
235
+		if err != nil {
236
+			return err
237
+		}
238
+		b.tmpContainers[out.Id] = struct{}{}
239
+
240
+		// Wait for it to finish
241
+		_, _, err = b.cli.call("POST", "/containers/"+out.Id+"/wait", nil)
242
+		if err != nil {
243
+			return err
244
+		}
245
+
246
+		// Commit the container
247
+		v := url.Values{}
248
+		v.Set("container", out.Id)
249
+		v.Set("author", b.maintainer)
250
+		body, _, err = b.cli.call("POST", "/commit?"+v.Encode(), b.config)
251
+		if err != nil {
252
+			return err
253
+		}
254
+		apiId := &ApiId{}
255
+		err = json.Unmarshal(body, apiId)
256
+		if err != nil {
257
+			return err
258
+		}
259
+		b.tmpImages[apiId.Id] = struct{}{}
260
+		b.image = apiId.Id
261
+	}
262
+	if b.image != "" {
263
+		// The build is successful, keep the temporary containers and images
264
+		for i := range b.tmpImages {
265
+			delete(b.tmpImages, i)
266
+		}
267
+		for i := range b.tmpContainers {
268
+			delete(b.tmpContainers, i)
269
+		}
270
+		fmt.Printf("Build finished. image id: %s\n", b.image)
271
+		return b.image, nil
272
+	}
273
+	return "", fmt.Errorf("An error occured during the build\n")
274
+}
... ...
@@ -54,37 +54,37 @@ func ParseCommands(args ...string) error {
54 54
 
55 55
 func (cli *DockerCli) CmdHelp(args ...string) error {
56 56
 	help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n"
57
-	for _, cmd := range [][]string{
58
-		{"attach", "Attach to a running container"},
59
-		{"build", "Build a container from Dockerfile via stdin"},
60
-		{"commit", "Create a new image from a container's changes"},
61
-		{"diff", "Inspect changes on a container's filesystem"},
62
-		{"export", "Stream the contents of a container as a tar archive"},
63
-		{"history", "Show the history of an image"},
64
-		{"images", "List images"},
65
-		{"import", "Create a new filesystem image from the contents of a tarball"},
66
-		{"info", "Display system-wide information"},
67
-		{"insert", "Insert a file in an image"},
68
-		{"inspect", "Return low-level information on a container"},
69
-		{"kill", "Kill a running container"},
70
-		{"login", "Register or Login to the docker registry server"},
71
-		{"logs", "Fetch the logs of a container"},
72
-		{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
73
-		{"ps", "List containers"},
74
-		{"pull", "Pull an image or a repository from the docker registry server"},
75
-		{"push", "Push an image or a repository to the docker registry server"},
76
-		{"restart", "Restart a running container"},
77
-		{"rm", "Remove a container"},
78
-		{"rmi", "Remove an image"},
79
-		{"run", "Run a command in a new container"},
80
-		{"search", "Search for an image in the docker index"},
81
-		{"start", "Start a stopped container"},
82
-		{"stop", "Stop a running container"},
83
-		{"tag", "Tag an image into a repository"},
84
-		{"version", "Show the docker version information"},
85
-		{"wait", "Block until a container stops, then print its exit code"},
57
+	for cmd, description := range map[string]string{
58
+		"attach":  "Attach to a running container",
59
+		"build":   "Build a container from Dockerfile or via stdin",
60
+		"commit":  "Create a new image from a container's changes",
61
+		"diff":    "Inspect changes on a container's filesystem",
62
+		"export":  "Stream the contents of a container as a tar archive",
63
+		"history": "Show the history of an image",
64
+		"images":  "List images",
65
+		"import":  "Create a new filesystem image from the contents of a tarball",
66
+		"info":    "Display system-wide information",
67
+		"insert":  "Insert a file in an image",
68
+		"inspect": "Return low-level information on a container",
69
+		"kill":    "Kill a running container",
70
+		"login":   "Register or Login to the docker registry server",
71
+		"logs":    "Fetch the logs of a container",
72
+		"port":    "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT",
73
+		"ps":      "List containers",
74
+		"pull":    "Pull an image or a repository from the docker registry server",
75
+		"push":    "Push an image or a repository to the docker registry server",
76
+		"restart": "Restart a running container",
77
+		"rm":      "Remove a container",
78
+		"rmi":     "Remove an image",
79
+		"run":     "Run a command in a new container",
80
+		"search":  "Search for an image in the docker index",
81
+		"start":   "Start a stopped container",
82
+		"stop":    "Stop a running container",
83
+		"tag":     "Tag an image into a repository",
84
+		"version": "Show the docker version information",
85
+		"wait":    "Block until a container stops, then print its exit code",
86 86
 	} {
87
-		help += fmt.Sprintf("    %-10.10s%s\n", cmd[0], cmd[1])
87
+		help += fmt.Sprintf("    %-10.10s%s\n", cmd, description)
88 88
 	}
89 89
 	fmt.Println(help)
90 90
 	return nil
... ...
@@ -112,15 +112,29 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
112 112
 }
113 113
 
114 114
 func (cli *DockerCli) CmdBuild(args ...string) error {
115
-	cmd := Subcmd("build", "-", "Build an image from Dockerfile via stdin")
115
+	cmd := Subcmd("build", "-|Dockerfile", "Build an image from Dockerfile or via stdin")
116 116
 	if err := cmd.Parse(args); err != nil {
117 117
 		return nil
118 118
 	}
119
+	var (
120
+		file io.ReadCloser
121
+		err  error
122
+	)
119 123
 
120
-	err := cli.hijack("POST", "/build", false)
121
-	if err != nil {
122
-		return err
124
+	if cmd.NArg() == 0 {
125
+		file, err = os.Open("Dockerfile")
126
+		if err != nil {
127
+			return err
128
+		}
129
+	} else if cmd.Arg(0) == "-" {
130
+		file = os.Stdin
131
+	} else {
132
+		file, err = os.Open(cmd.Arg(0))
133
+		if err != nil {
134
+			return err
135
+		}
123 136
 	}
137
+	NewBuilderClient(file)
124 138
 	return nil
125 139
 }
126 140
 
... ...
@@ -140,8 +140,10 @@ func (srv *Server) ImagesViz(out io.Writer) error {
140 140
 }
141 141
 
142 142
 func (srv *Server) Images(all bool, filter string) ([]ApiImages, error) {
143
-	var allImages map[string]*Image
144
-	var err error
143
+	var (
144
+		allImages map[string]*Image
145
+		err       error
146
+	)
145 147
 	if all {
146 148
 		allImages, err = srv.runtime.graph.Map()
147 149
 	} else {
... ...
@@ -150,7 +152,7 @@ func (srv *Server) Images(all bool, filter string) ([]ApiImages, error) {
150 150
 	if err != nil {
151 151
 		return nil, err
152 152
 	}
153
-	var outs []ApiImages = []ApiImages{} //produce [] when empty instead of 'null'
153
+	outs := []ApiImages{} //produce [] when empty instead of 'null'
154 154
 	for name, repository := range srv.runtime.repositories.Repositories {
155 155
 		if filter != "" && name != filter {
156 156
 			continue
... ...
@@ -653,15 +655,6 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
653 653
 	return container.ShortId(), nil
654 654
 }
655 655
 
656
-func (srv *Server) ImageCreateFromFile(dockerfile io.Reader, out io.Writer) error {
657
-	img, err := NewBuilder(srv.runtime).Build(dockerfile, out)
658
-	if err != nil {
659
-		return err
660
-	}
661
-	fmt.Fprintf(out, "%s\n", img.ShortId())
662
-	return nil
663
-}
664
-
665 656
 func (srv *Server) ContainerRestart(name string, t int) error {
666 657
 	if container := srv.runtime.Get(name); container != nil {
667 658
 		if err := container.Restart(t); err != nil {
... ...
@@ -722,6 +715,36 @@ func (srv *Server) ImageDelete(name string) error {
722 722
 	return nil
723 723
 }
724 724
 
725
+func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error) {
726
+
727
+	// Retrieve all images
728
+	images, err := srv.runtime.graph.All()
729
+	if err != nil {
730
+		return nil, err
731
+	}
732
+
733
+	// Store the tree in a map of map (map[parentId][childId])
734
+	imageMap := make(map[string]map[string]struct{})
735
+	for _, img := range images {
736
+		if _, exists := imageMap[img.Parent]; !exists {
737
+			imageMap[img.Parent] = make(map[string]struct{})
738
+		}
739
+		imageMap[img.Parent][img.Id] = struct{}{}
740
+	}
741
+
742
+	// Loop on the children of the given image and check the config
743
+	for elem := range imageMap[imgId] {
744
+		img, err := srv.runtime.graph.Get(elem)
745
+		if err != nil {
746
+			return nil, err
747
+		}
748
+		if CompareConfig(&img.ContainerConfig, config) {
749
+			return img, nil
750
+		}
751
+	}
752
+	return nil, nil
753
+}
754
+
725 755
 func (srv *Server) ContainerStart(name string) error {
726 756
 	if container := srv.runtime.Get(name); container != nil {
727 757
 		if err := container.Start(); err != nil {
... ...
@@ -47,3 +47,42 @@ func CompareConfig(a, b *Config) bool {
47 47
 
48 48
 	return true
49 49
 }
50
+
51
+func MergeConfig(userConf, imageConf *Config) {
52
+	if userConf.Hostname != "" {
53
+		userConf.Hostname = imageConf.Hostname
54
+	}
55
+	if userConf.User != "" {
56
+		userConf.User = imageConf.User
57
+	}
58
+	if userConf.Memory == 0 {
59
+		userConf.Memory = imageConf.Memory
60
+	}
61
+	if userConf.MemorySwap == 0 {
62
+		userConf.MemorySwap = imageConf.MemorySwap
63
+	}
64
+	if userConf.CpuShares == 0 {
65
+		userConf.CpuShares = imageConf.CpuShares
66
+	}
67
+	if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 {
68
+		userConf.PortSpecs = imageConf.PortSpecs
69
+	}
70
+	if !userConf.Tty {
71
+		userConf.Tty = imageConf.Tty
72
+	}
73
+	if !userConf.OpenStdin {
74
+		userConf.OpenStdin = imageConf.OpenStdin
75
+	}
76
+	if !userConf.StdinOnce {
77
+		userConf.StdinOnce = imageConf.StdinOnce
78
+	}
79
+	if userConf.Env == nil || len(userConf.Env) == 0 {
80
+		userConf.Env = imageConf.Env
81
+	}
82
+	if userConf.Cmd == nil || len(userConf.Cmd) == 0 {
83
+		userConf.Cmd = imageConf.Cmd
84
+	}
85
+	if userConf.Dns == nil || len(userConf.Dns) == 0 {
86
+		userConf.Dns = imageConf.Dns
87
+	}
88
+}