소스 검색

Merge remote-tracking branch 'upstream/master'

Conflicts:
	docs/sources/examples/python_web_app.rst
Eric Hanchrow 12 년 전
부모
커밋
b4198de6bf
12개의 변경된 파일481개의 추가작업 그리고 544개의 파일을 삭제
  1. 24 8
      api.go
  2. 5 0
      api_params.go
  3. 0 35
      api_test.go
  4. 4 354
      builder.go
  5. 311 0
      builder_client.go
  6. 0 89
      builder_test.go
  7. 49 33
      commands.go
  8. 2 1
      docs/sources/api/docker_remote_api.rst
  9. 4 4
      docs/sources/examples/python_web_app.rst
  10. 4 3
      docs/sources/use/builder.rst
  11. 39 17
      server.go
  12. 39 0
      utils.go

+ 24 - 8
api.go

@@ -352,13 +352,6 @@ func postImagesPush(srv *Server, w http.ResponseWriter, r *http.Request, vars ma
 	return nil
 }
 
-func postBuild(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
-	if err := srv.ImageCreateFromFile(r.Body, w); err != nil {
-		return err
-	}
-	return nil
-}
-
 func postContainersCreate(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
 	config := &Config{}
 	if err := json.NewDecoder(r.Body).Decode(config); err != nil {
@@ -569,6 +562,29 @@ func getImagesByName(srv *Server, w http.ResponseWriter, r *http.Request, vars m
 	return nil
 }
 
+func postImagesGetCache(srv *Server, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
+	apiConfig := &ApiImageConfig{}
+	if err := json.NewDecoder(r.Body).Decode(apiConfig); err != nil {
+		return err
+	}
+
+	image, err := srv.ImageGetCached(apiConfig.Id, apiConfig.Config)
+	if err != nil {
+		return err
+	}
+	if image == nil {
+		w.WriteHeader(http.StatusNotFound)
+		return nil
+	}
+	apiId := &ApiId{Id: image.Id}
+	b, err := json.Marshal(apiId)
+	if err != nil {
+		return err
+	}
+	writeJson(w, b)
+	return nil
+}
+
 func ListenAndServe(addr string, srv *Server, logging bool) error {
 	r := mux.NewRouter()
 	log.Printf("Listening for HTTP on %s\n", addr)
@@ -591,11 +607,11 @@ func ListenAndServe(addr string, srv *Server, logging bool) error {
 		"POST": {
 			"/auth":                         postAuth,
 			"/commit":                       postCommit,
-			"/build":                        postBuild,
 			"/images/create":                postImagesCreate,
 			"/images/{name:.*}/insert":      postImagesInsert,
 			"/images/{name:.*}/push":        postImagesPush,
 			"/images/{name:.*}/tag":         postImagesTag,
+			"/images/getCache":              postImagesGetCache,
 			"/containers/create":            postContainersCreate,
 			"/containers/{name:.*}/kill":    postContainersKill,
 			"/containers/{name:.*}/restart": postContainersRestart,

+ 5 - 0
api_params.go

@@ -64,3 +64,8 @@ type ApiWait struct {
 type ApiAuth struct {
 	Status string
 }
+
+type ApiImageConfig struct {
+	Id string
+	*Config
+}

+ 0 - 35
api_test.go

@@ -14,7 +14,6 @@ import (
 	"net/http/httptest"
 	"os"
 	"path"
-	"strings"
 	"testing"
 	"time"
 )
@@ -579,40 +578,6 @@ func TestPostCommit(t *testing.T) {
 	}
 }
 
-func TestPostBuild(t *testing.T) {
-	runtime, err := newTestRuntime()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer nuke(runtime)
-
-	srv := &Server{runtime: runtime}
-
-	imgs, err := runtime.graph.All()
-	if err != nil {
-		t.Fatal(err)
-	}
-	beginCount := len(imgs)
-
-	req, err := http.NewRequest("POST", "/build", strings.NewReader(Dockerfile))
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	r := httptest.NewRecorder()
-	if err := postBuild(srv, r, req, nil); err != nil {
-		t.Fatal(err)
-	}
-
-	imgs, err = runtime.graph.All()
-	if err != nil {
-		t.Fatal(err)
-	}
-	if len(imgs) != beginCount+3 {
-		t.Fatalf("Expected %d images, %d found", beginCount+3, len(imgs))
-	}
-}
-
 func TestPostImagesCreate(t *testing.T) {
 	// FIXME: Use the staging in order to perform tests
 

+ 4 - 354
builder.go

@@ -1,14 +1,9 @@
 package docker
 
 import (
-	"bufio"
-	"encoding/json"
 	"fmt"
-	"github.com/dotcloud/docker/utils"
-	"io"
 	"os"
 	"path"
-	"strings"
 	"time"
 )
 
@@ -16,6 +11,9 @@ type Builder struct {
 	runtime      *Runtime
 	repositories *TagStore
 	graph        *Graph
+
+	config *Config
+	image  *Image
 }
 
 func NewBuilder(runtime *Runtime) *Builder {
@@ -26,45 +24,6 @@ func NewBuilder(runtime *Runtime) *Builder {
 	}
 }
 
-func (builder *Builder) mergeConfig(userConf, imageConf *Config) {
-	if userConf.Hostname != "" {
-		userConf.Hostname = imageConf.Hostname
-	}
-	if userConf.User != "" {
-		userConf.User = imageConf.User
-	}
-	if userConf.Memory == 0 {
-		userConf.Memory = imageConf.Memory
-	}
-	if userConf.MemorySwap == 0 {
-		userConf.MemorySwap = imageConf.MemorySwap
-	}
-	if userConf.CpuShares == 0 {
-		userConf.CpuShares = imageConf.CpuShares
-	}
-	if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 {
-		userConf.PortSpecs = imageConf.PortSpecs
-	}
-	if !userConf.Tty {
-		userConf.Tty = imageConf.Tty
-	}
-	if !userConf.OpenStdin {
-		userConf.OpenStdin = imageConf.OpenStdin
-	}
-	if !userConf.StdinOnce {
-		userConf.StdinOnce = imageConf.StdinOnce
-	}
-	if userConf.Env == nil || len(userConf.Env) == 0 {
-		userConf.Env = imageConf.Env
-	}
-	if userConf.Cmd == nil || len(userConf.Cmd) == 0 {
-		userConf.Cmd = imageConf.Cmd
-	}
-	if userConf.Dns == nil || len(userConf.Dns) == 0 {
-		userConf.Dns = imageConf.Dns
-	}
-}
-
 func (builder *Builder) Create(config *Config) (*Container, error) {
 	// Lookup image
 	img, err := builder.repositories.LookupImage(config.Image)
@@ -73,7 +32,7 @@ func (builder *Builder) Create(config *Config) (*Container, error) {
 	}
 
 	if img.Config != nil {
-		builder.mergeConfig(config, img.Config)
+		MergeConfig(config, img.Config)
 	}
 
 	if config.Cmd == nil || len(config.Cmd) == 0 {
@@ -157,312 +116,3 @@ func (builder *Builder) Commit(container *Container, repository, tag, comment, a
 	}
 	return img, nil
 }
-
-func (builder *Builder) clearTmp(containers, images map[string]struct{}) {
-	for c := range containers {
-		tmp := builder.runtime.Get(c)
-		builder.runtime.Destroy(tmp)
-		utils.Debugf("Removing container %s", c)
-	}
-	for i := range images {
-		builder.runtime.graph.Delete(i)
-		utils.Debugf("Removing image %s", i)
-	}
-}
-
-func (builder *Builder) getCachedImage(image *Image, config *Config) (*Image, error) {
-	// Retrieve all images
-	images, err := builder.graph.All()
-	if err != nil {
-		return nil, err
-	}
-
-	// Store the tree in a map of map (map[parentId][childId])
-	imageMap := make(map[string]map[string]struct{})
-	for _, img := range images {
-		if _, exists := imageMap[img.Parent]; !exists {
-			imageMap[img.Parent] = make(map[string]struct{})
-		}
-		imageMap[img.Parent][img.Id] = struct{}{}
-	}
-
-	// Loop on the children of the given image and check the config
-	for elem := range imageMap[image.Id] {
-		img, err := builder.graph.Get(elem)
-		if err != nil {
-			return nil, err
-		}
-		if CompareConfig(&img.ContainerConfig, config) {
-			return img, nil
-		}
-	}
-	return nil, nil
-}
-
-func (builder *Builder) Build(dockerfile io.Reader, stdout io.Writer) (*Image, error) {
-	var (
-		image, base   *Image
-		config        *Config
-		maintainer    string
-		env           map[string]string   = make(map[string]string)
-		tmpContainers map[string]struct{} = make(map[string]struct{})
-		tmpImages     map[string]struct{} = make(map[string]struct{})
-	)
-	defer builder.clearTmp(tmpContainers, tmpImages)
-
-	file := bufio.NewReader(dockerfile)
-	for {
-		line, err := file.ReadString('\n')
-		if err != nil {
-			if err == io.EOF {
-				break
-			}
-			return nil, err
-		}
-		line = strings.Replace(strings.TrimSpace(line), "	", " ", 1)
-		// Skip comments and empty line
-		if len(line) == 0 || line[0] == '#' {
-			continue
-		}
-		tmp := strings.SplitN(line, " ", 2)
-		if len(tmp) != 2 {
-			return nil, fmt.Errorf("Invalid Dockerfile format")
-		}
-		instruction := strings.Trim(tmp[0], " ")
-		arguments := strings.Trim(tmp[1], " ")
-		switch strings.ToLower(instruction) {
-		case "from":
-			fmt.Fprintf(stdout, "FROM %s\n", arguments)
-			image, err = builder.runtime.repositories.LookupImage(arguments)
-			if err != nil {
-				// if builder.runtime.graph.IsNotExist(err) {
-
-				// 	var tag, remote string
-				// 	if strings.Contains(arguments, ":") {
-				// 		remoteParts := strings.Split(arguments, ":")
-				// 		tag = remoteParts[1]
-				// 		remote = remoteParts[0]
-				// 	} else {
-				// 		remote = arguments
-				// 	}
-
-				// 	panic("TODO: reimplement this")
-				// 	// if err := builder.runtime.graph.PullRepository(stdout, remote, tag, builder.runtime.repositories, builder.runtime.authConfig); err != nil {
-				// 	// 	return nil, err
-				// 	// }
-
-				// 	image, err = builder.runtime.repositories.LookupImage(arguments)
-				// 	if err != nil {
-				// 		return nil, err
-				// 	}
-				// } else {
-				return nil, err
-				// }
-			}
-			config = &Config{}
-
-			break
-		case "maintainer":
-			fmt.Fprintf(stdout, "MAINTAINER %s\n", arguments)
-			maintainer = arguments
-			break
-		case "run":
-			fmt.Fprintf(stdout, "RUN %s\n", arguments)
-			if image == nil {
-				return nil, fmt.Errorf("Please provide a source image with `from` prior to run")
-			}
-			config, _, err := ParseRun([]string{image.Id, "/bin/sh", "-c", arguments}, builder.runtime.capabilities)
-			if err != nil {
-				return nil, err
-			}
-
-			for key, value := range env {
-				config.Env = append(config.Env, fmt.Sprintf("%s=%s", key, value))
-			}
-
-			if cache, err := builder.getCachedImage(image, config); err != nil {
-				return nil, err
-			} else if cache != nil {
-				image = cache
-				fmt.Fprintf(stdout, "===> %s\n", image.ShortId())
-				break
-			}
-
-			utils.Debugf("Env -----> %v ------ %v\n", config.Env, env)
-
-			// Create the container and start it
-			c, err := builder.Create(config)
-			if err != nil {
-				return nil, err
-			}
-
-			if os.Getenv("DEBUG") != "" {
-				out, _ := c.StdoutPipe()
-				err2, _ := c.StderrPipe()
-				go io.Copy(os.Stdout, out)
-				go io.Copy(os.Stdout, err2)
-			}
-
-			if err := c.Start(); err != nil {
-				return nil, err
-			}
-			tmpContainers[c.Id] = struct{}{}
-
-			// Wait for it to finish
-			if result := c.Wait(); result != 0 {
-				return nil, fmt.Errorf("!!! '%s' return non-zero exit code '%d'. Aborting.", arguments, result)
-			}
-
-			// Commit the container
-			base, err = builder.Commit(c, "", "", "", maintainer, nil)
-			if err != nil {
-				return nil, err
-			}
-			tmpImages[base.Id] = struct{}{}
-
-			fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
-
-			// use the base as the new image
-			image = base
-
-			break
-		case "env":
-			tmp := strings.SplitN(arguments, " ", 2)
-			if len(tmp) != 2 {
-				return nil, fmt.Errorf("Invalid ENV format")
-			}
-			key := strings.Trim(tmp[0], " ")
-			value := strings.Trim(tmp[1], " ")
-			fmt.Fprintf(stdout, "ENV %s %s\n", key, value)
-			env[key] = value
-			if image != nil {
-				fmt.Fprintf(stdout, "===> %s\n", image.ShortId())
-			} else {
-				fmt.Fprintf(stdout, "===> <nil>\n")
-			}
-			break
-		case "cmd":
-			fmt.Fprintf(stdout, "CMD %s\n", arguments)
-
-			// Create the container and start it
-			c, err := builder.Create(&Config{Image: image.Id, Cmd: []string{"", ""}})
-			if err != nil {
-				return nil, err
-			}
-			if err := c.Start(); err != nil {
-				return nil, err
-			}
-			tmpContainers[c.Id] = struct{}{}
-
-			cmd := []string{}
-			if err := json.Unmarshal([]byte(arguments), &cmd); err != nil {
-				return nil, err
-			}
-			config.Cmd = cmd
-
-			// Commit the container
-			base, err = builder.Commit(c, "", "", "", maintainer, config)
-			if err != nil {
-				return nil, err
-			}
-			tmpImages[base.Id] = struct{}{}
-
-			fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
-			image = base
-			break
-		case "expose":
-			ports := strings.Split(arguments, " ")
-
-			fmt.Fprintf(stdout, "EXPOSE %v\n", ports)
-			if image == nil {
-				return nil, fmt.Errorf("Please provide a source image with `from` prior to copy")
-			}
-
-			// Create the container and start it
-			c, err := builder.Create(&Config{Image: image.Id, Cmd: []string{"", ""}})
-			if err != nil {
-				return nil, err
-			}
-			if err := c.Start(); err != nil {
-				return nil, err
-			}
-			tmpContainers[c.Id] = struct{}{}
-
-			config.PortSpecs = append(ports, config.PortSpecs...)
-
-			// Commit the container
-			base, err = builder.Commit(c, "", "", "", maintainer, config)
-			if err != nil {
-				return nil, err
-			}
-			tmpImages[base.Id] = struct{}{}
-
-			fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
-			image = base
-			break
-		case "insert":
-			if image == nil {
-				return nil, fmt.Errorf("Please provide a source image with `from` prior to copy")
-			}
-			tmp = strings.SplitN(arguments, " ", 2)
-			if len(tmp) != 2 {
-				return nil, fmt.Errorf("Invalid INSERT format")
-			}
-			sourceUrl := strings.Trim(tmp[0], " ")
-			destPath := strings.Trim(tmp[1], " ")
-			fmt.Fprintf(stdout, "COPY %s to %s in %s\n", sourceUrl, destPath, base.ShortId())
-
-			file, err := utils.Download(sourceUrl, stdout)
-			if err != nil {
-				return nil, err
-			}
-			defer file.Body.Close()
-
-			config, _, err := ParseRun([]string{base.Id, "echo", "insert", sourceUrl, destPath}, builder.runtime.capabilities)
-			if err != nil {
-				return nil, err
-			}
-			c, err := builder.Create(config)
-			if err != nil {
-				return nil, err
-			}
-
-			if err := c.Start(); err != nil {
-				return nil, err
-			}
-
-			// Wait for echo to finish
-			if result := c.Wait(); result != 0 {
-				return nil, fmt.Errorf("!!! '%s' return non-zero exit code '%d'. Aborting.", arguments, result)
-			}
-
-			if err := c.Inject(file.Body, destPath); err != nil {
-				return nil, err
-			}
-
-			base, err = builder.Commit(c, "", "", "", maintainer, nil)
-			if err != nil {
-				return nil, err
-			}
-			fmt.Fprintf(stdout, "===> %s\n", base.ShortId())
-
-			image = base
-
-			break
-		default:
-			fmt.Fprintf(stdout, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
-		}
-	}
-	if image != nil {
-		// The build is successful, keep the temporary containers and images
-		for i := range tmpImages {
-			delete(tmpImages, i)
-		}
-		for i := range tmpContainers {
-			delete(tmpContainers, i)
-		}
-		fmt.Fprintf(stdout, "Build finished. image id: %s\n", image.ShortId())
-		return image, nil
-	}
-	return nil, fmt.Errorf("An error occured during the build\n")
-}

+ 311 - 0
builder_client.go

@@ -0,0 +1,311 @@
+package docker
+
+import (
+	"bufio"
+	"encoding/json"
+	"fmt"
+	"github.com/dotcloud/docker/utils"
+	"io"
+	"net/url"
+	"os"
+	"reflect"
+	"strings"
+)
+
+type BuilderClient interface {
+	Build(io.Reader) (string, error)
+	CmdFrom(string) error
+	CmdRun(string) error
+}
+
+type builderClient struct {
+	cli *DockerCli
+
+	image      string
+	maintainer string
+	config     *Config
+
+	tmpContainers map[string]struct{}
+	tmpImages     map[string]struct{}
+
+	needCommit bool
+}
+
+func (b *builderClient) clearTmp(containers, images map[string]struct{}) {
+	for c := range containers {
+		if _, _, err := b.cli.call("DELETE", "/containers/"+c, nil); err != nil {
+			utils.Debugf("%s", err)
+		}
+		utils.Debugf("Removing container %s", c)
+	}
+	for i := range images {
+		if _, _, err := b.cli.call("DELETE", "/images/"+i, nil); err != nil {
+			utils.Debugf("%s", err)
+		}
+		utils.Debugf("Removing image %s", i)
+	}
+}
+
+func (b *builderClient) CmdFrom(name string) error {
+	obj, statusCode, err := b.cli.call("GET", "/images/"+name+"/json", nil)
+	if statusCode == 404 {
+
+		remote := name
+		var tag string
+		if strings.Contains(remote, ":") {
+			remoteParts := strings.Split(remote, ":")
+			tag = remoteParts[1]
+			remote = remoteParts[0]
+		}
+		var out io.Writer
+		if os.Getenv("DEBUG") != "" {
+			out = os.Stdout
+		} else {
+			out = &utils.NopWriter{}
+		}
+		if err := b.cli.stream("POST", "/images/create?fromImage="+remote+"&tag="+tag, nil, out); err != nil {
+			return err
+		}
+		obj, _, err = b.cli.call("GET", "/images/"+name+"/json", nil)
+		if err != nil {
+			return err
+		}
+	}
+	if err != nil {
+		return err
+	}
+
+	img := &ApiId{}
+	if err := json.Unmarshal(obj, img); err != nil {
+		return err
+	}
+	b.image = img.Id
+	utils.Debugf("Using image %s", b.image)
+	return nil
+}
+
+func (b *builderClient) CmdMaintainer(name string) error {
+	b.needCommit = true
+	b.maintainer = name
+	return nil
+}
+
+func (b *builderClient) CmdRun(args string) error {
+	if b.image == "" {
+		return fmt.Errorf("Please provide a source image with `from` prior to run")
+	}
+	config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
+	if err != nil {
+		return err
+	}
+
+	cmd, env := b.config.Cmd, b.config.Env
+	b.config.Cmd = nil
+	MergeConfig(b.config, config)
+
+	body, statusCode, err := b.cli.call("POST", "/images/getCache", &ApiImageConfig{Id: b.image, Config: b.config})
+	if err != nil {
+		if statusCode != 404 {
+			return err
+		}
+	}
+	if statusCode != 404 {
+		apiId := &ApiId{}
+		if err := json.Unmarshal(body, apiId); err != nil {
+			return err
+		}
+		utils.Debugf("Use cached version")
+		b.image = apiId.Id
+		return nil
+	}
+	cid, err := b.run()
+	if err != nil {
+		return err
+	}
+	b.config.Cmd, b.config.Env = cmd, env
+	return b.commit(cid)
+}
+
+func (b *builderClient) CmdEnv(args string) error {
+	b.needCommit = true
+	tmp := strings.SplitN(args, " ", 2)
+	if len(tmp) != 2 {
+		return fmt.Errorf("Invalid ENV format")
+	}
+	key := strings.Trim(tmp[0], " ")
+	value := strings.Trim(tmp[1], " ")
+
+	for i, elem := range b.config.Env {
+		if strings.HasPrefix(elem, key+"=") {
+			b.config.Env[i] = key + "=" + value
+			return nil
+		}
+	}
+	b.config.Env = append(b.config.Env, key+"="+value)
+	return nil
+}
+
+func (b *builderClient) CmdCmd(args string) error {
+	b.needCommit = true
+	var cmd []string
+	if err := json.Unmarshal([]byte(args), &cmd); err != nil {
+		utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
+		b.config.Cmd = []string{"/bin/sh", "-c", args}
+	} else {
+		b.config.Cmd = cmd
+	}
+	return nil
+}
+
+func (b *builderClient) CmdExpose(args string) error {
+	ports := strings.Split(args, " ")
+	b.config.PortSpecs = append(ports, b.config.PortSpecs...)
+	return nil
+}
+
+func (b *builderClient) CmdInsert(args string) error {
+	// FIXME: Reimplement this once the remove_hijack branch gets merged.
+	// We need to retrieve the resulting Id
+	return fmt.Errorf("INSERT not implemented")
+}
+
+func (b *builderClient) run() (string, error) {
+	if b.image == "" {
+		return "", fmt.Errorf("Please provide a source image with `from` prior to run")
+	}
+	b.config.Image = b.image
+	body, _, err := b.cli.call("POST", "/containers/create", b.config)
+	if err != nil {
+		return "", err
+	}
+
+	apiRun := &ApiRun{}
+	if err := json.Unmarshal(body, apiRun); err != nil {
+		return "", err
+	}
+	for _, warning := range apiRun.Warnings {
+		fmt.Fprintln(os.Stderr, "WARNING: ", warning)
+	}
+
+	//start the container
+	_, _, err = b.cli.call("POST", "/containers/"+apiRun.Id+"/start", nil)
+	if err != nil {
+		return "", err
+	}
+	b.tmpContainers[apiRun.Id] = struct{}{}
+
+	// Wait for it to finish
+	body, _, err = b.cli.call("POST", "/containers/"+apiRun.Id+"/wait", nil)
+	if err != nil {
+		return "", err
+	}
+	apiWait := &ApiWait{}
+	if err := json.Unmarshal(body, apiWait); err != nil {
+		return "", err
+	}
+	if apiWait.StatusCode != 0 {
+		return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, apiWait.StatusCode)
+	}
+
+	return apiRun.Id, nil
+}
+
+func (b *builderClient) commit(id string) error {
+	if b.image == "" {
+		return fmt.Errorf("Please provide a source image with `from` prior to run")
+	}
+	b.config.Image = b.image
+
+	if id == "" {
+		cmd := b.config.Cmd
+		b.config.Cmd = []string{"true"}
+		if cid, err := b.run(); err != nil {
+			return err
+		} else {
+			id = cid
+		}
+		b.config.Cmd = cmd
+	}
+
+	// Commit the container
+	v := url.Values{}
+	v.Set("container", id)
+	v.Set("author", b.maintainer)
+
+	body, _, err := b.cli.call("POST", "/commit?"+v.Encode(), b.config)
+	if err != nil {
+		return err
+	}
+	apiId := &ApiId{}
+	if err := json.Unmarshal(body, apiId); err != nil {
+		return err
+	}
+	b.tmpImages[apiId.Id] = struct{}{}
+	b.image = apiId.Id
+	b.needCommit = false
+	return nil
+}
+
+func (b *builderClient) Build(dockerfile io.Reader) (string, error) {
+	defer b.clearTmp(b.tmpContainers, b.tmpImages)
+	file := bufio.NewReader(dockerfile)
+	for {
+		line, err := file.ReadString('\n')
+		if err != nil {
+			if err == io.EOF {
+				break
+			}
+			return "", err
+		}
+		line = strings.Replace(strings.TrimSpace(line), "	", " ", 1)
+		// Skip comments and empty line
+		if len(line) == 0 || line[0] == '#' {
+			continue
+		}
+		tmp := strings.SplitN(line, " ", 2)
+		if len(tmp) != 2 {
+			return "", fmt.Errorf("Invalid Dockerfile format")
+		}
+		instruction := strings.ToLower(strings.Trim(tmp[0], " "))
+		arguments := strings.Trim(tmp[1], " ")
+
+		fmt.Printf("%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)
+
+		method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
+		if !exists {
+			fmt.Printf("Skipping unknown instruction %s\n", strings.ToUpper(instruction))
+		}
+		ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
+		if ret != nil {
+			return "", ret.(error)
+		}
+
+		fmt.Printf("===> %v\n", b.image)
+	}
+	if b.needCommit {
+		if err := b.commit(""); err != nil {
+			return "", err
+		}
+	}
+	if b.image != "" {
+		// The build is successful, keep the temporary containers and images
+		for i := range b.tmpImages {
+			delete(b.tmpImages, i)
+		}
+		for i := range b.tmpContainers {
+			delete(b.tmpContainers, i)
+		}
+		fmt.Printf("Build finished. image id: %s\n", b.image)
+		return b.image, nil
+	}
+	return "", fmt.Errorf("An error occured during the build\n")
+}
+
+func NewBuilderClient(addr string, port int) BuilderClient {
+	return &builderClient{
+		cli:           NewDockerCli(addr, port),
+		config:        &Config{},
+		tmpContainers: make(map[string]struct{}),
+		tmpImages:     make(map[string]struct{}),
+	}
+}

+ 0 - 89
builder_test.go

@@ -1,89 +0,0 @@
-package docker
-
-import (
-	"github.com/dotcloud/docker/utils"
-	"strings"
-	"testing"
-)
-
-const Dockerfile = `
-# VERSION		0.1
-# DOCKER-VERSION	0.2
-
-from   ` + unitTestImageName + `
-run    sh -c 'echo root:testpass > /tmp/passwd'
-run    mkdir -p /var/run/sshd
-insert https://raw.github.com/dotcloud/docker/master/CHANGELOG.md /tmp/CHANGELOG.md
-`
-
-func TestBuild(t *testing.T) {
-	runtime, err := newTestRuntime()
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer nuke(runtime)
-
-	builder := NewBuilder(runtime)
-
-	img, err := builder.Build(strings.NewReader(Dockerfile), &utils.NopWriter{})
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	container, err := builder.Create(
-		&Config{
-			Image: img.Id,
-			Cmd:   []string{"cat", "/tmp/passwd"},
-		},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer runtime.Destroy(container)
-
-	output, err := container.Output()
-	if err != nil {
-		t.Fatal(err)
-	}
-	if string(output) != "root:testpass\n" {
-		t.Fatalf("Unexpected output. Read '%s', expected '%s'", output, "root:testpass\n")
-	}
-
-	container2, err := builder.Create(
-		&Config{
-			Image: img.Id,
-			Cmd:   []string{"ls", "-d", "/var/run/sshd"},
-		},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer runtime.Destroy(container2)
-
-	output, err = container2.Output()
-	if err != nil {
-		t.Fatal(err)
-	}
-	if string(output) != "/var/run/sshd\n" {
-		t.Fatal("/var/run/sshd has not been created")
-	}
-
-	container3, err := builder.Create(
-		&Config{
-			Image: img.Id,
-			Cmd:   []string{"cat", "/tmp/CHANGELOG.md"},
-		},
-	)
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer runtime.Destroy(container3)
-
-	output, err = container3.Output()
-	if err != nil {
-		t.Fatal(err)
-	}
-	if len(output) == 0 {
-		t.Fatal("/tmp/CHANGELOG.md has not been copied")
-	}
-}

+ 49 - 33
commands.go

@@ -54,37 +54,37 @@ func ParseCommands(args ...string) error {
 
 func (cli *DockerCli) CmdHelp(args ...string) error {
 	help := "Usage: docker COMMAND [arg...]\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n"
-	for _, cmd := range [][]string{
-		{"attach", "Attach to a running container"},
-		{"build", "Build a container from Dockerfile via stdin"},
-		{"commit", "Create a new image from a container's changes"},
-		{"diff", "Inspect changes on a container's filesystem"},
-		{"export", "Stream the contents of a container as a tar archive"},
-		{"history", "Show the history of an image"},
-		{"images", "List images"},
-		{"import", "Create a new filesystem image from the contents of a tarball"},
-		{"info", "Display system-wide information"},
-		{"insert", "Insert a file in an image"},
-		{"inspect", "Return low-level information on a container"},
-		{"kill", "Kill a running container"},
-		{"login", "Register or Login to the docker registry server"},
-		{"logs", "Fetch the logs of a container"},
-		{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
-		{"ps", "List containers"},
-		{"pull", "Pull an image or a repository from the docker registry server"},
-		{"push", "Push an image or a repository to the docker registry server"},
-		{"restart", "Restart a running container"},
-		{"rm", "Remove a container"},
-		{"rmi", "Remove an image"},
-		{"run", "Run a command in a new container"},
-		{"search", "Search for an image in the docker index"},
-		{"start", "Start a stopped container"},
-		{"stop", "Stop a running container"},
-		{"tag", "Tag an image into a repository"},
-		{"version", "Show the docker version information"},
-		{"wait", "Block until a container stops, then print its exit code"},
+	for cmd, description := range map[string]string{
+		"attach":  "Attach to a running container",
+		"build":   "Build a container from Dockerfile or via stdin",
+		"commit":  "Create a new image from a container's changes",
+		"diff":    "Inspect changes on a container's filesystem",
+		"export":  "Stream the contents of a container as a tar archive",
+		"history": "Show the history of an image",
+		"images":  "List images",
+		"import":  "Create a new filesystem image from the contents of a tarball",
+		"info":    "Display system-wide information",
+		"insert":  "Insert a file in an image",
+		"inspect": "Return low-level information on a container",
+		"kill":    "Kill a running container",
+		"login":   "Register or Login to the docker registry server",
+		"logs":    "Fetch the logs of a container",
+		"port":    "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT",
+		"ps":      "List containers",
+		"pull":    "Pull an image or a repository from the docker registry server",
+		"push":    "Push an image or a repository to the docker registry server",
+		"restart": "Restart a running container",
+		"rm":      "Remove a container",
+		"rmi":     "Remove an image",
+		"run":     "Run a command in a new container",
+		"search":  "Search for an image in the docker index",
+		"start":   "Start a stopped container",
+		"stop":    "Stop a running container",
+		"tag":     "Tag an image into a repository",
+		"version": "Show the docker version information",
+		"wait":    "Block until a container stops, then print its exit code",
 	} {
-		help += fmt.Sprintf("    %-10.10s%s\n", cmd[0], cmd[1])
+		help += fmt.Sprintf("    %-10.10s%s\n", cmd, description)
 	}
 	fmt.Println(help)
 	return nil
@@ -112,13 +112,29 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
 }
 
 func (cli *DockerCli) CmdBuild(args ...string) error {
-	cmd := Subcmd("build", "-", "Build an image from Dockerfile via stdin")
+	cmd := Subcmd("build", "-|Dockerfile", "Build an image from Dockerfile or via stdin")
 	if err := cmd.Parse(args); err != nil {
 		return nil
 	}
+	var (
+		file io.ReadCloser
+		err  error
+	)
 
-	err := cli.stream("POST", "/build", os.Stdin, os.Stdout)
-	if err != nil {
+	if cmd.NArg() == 0 {
+		file, err = os.Open("Dockerfile")
+		if err != nil {
+			return err
+		}
+	} else if cmd.Arg(0) == "-" {
+		file = os.Stdin
+	} else {
+		file, err = os.Open(cmd.Arg(0))
+		if err != nil {
+			return err
+		}
+	}
+	if _, err := NewBuilderClient("0.0.0.0", 4243).Build(file); err != nil {
 		return err
 	}
 	return nil

+ 2 - 1
docs/sources/api/docker_remote_api.rst

@@ -118,7 +118,8 @@ Create a container
 	.. sourcecode:: http
 
 	   HTTP/1.1 201 OK
-	   
+	   Content-Type: application/json
+
 	   {
 		"Id":"e90e34656806"
 		"Warnings":[]

+ 4 - 4
docs/sources/examples/python_web_app.rst

@@ -40,7 +40,7 @@ We attach to the new container to see what is going on. Ctrl-C to disconnect
 
 .. code-block:: bash
 
-    BUILD_IMG=$(docker commit $BUILD_JOB _/builds/github.com/hykes/helloflask/master)
+    BUILD_IMG=$(docker commit $BUILD_JOB _/builds/github.com/shykes/helloflask/master)
 
 Save the changed we just made in the container to a new image called "_/builds/github.com/hykes/helloflask/master" and save the image id in the BUILD_IMG variable name.
 
@@ -58,7 +58,7 @@ Use the new image we just created and create a new container with network port 5
 .. code-block:: bash
 
     docker logs $WEB_WORKER
-     * Running on \http://0.0.0.0:5000/
+     * Running on http://0.0.0.0:5000/
 
 view the logs for the new container using the WEB_WORKER variable, and if everything worked as planned you should see the line "Running on http://0.0.0.0:5000/" in the log output.
 
@@ -70,8 +70,8 @@ lookup the public-facing port which is NAT-ed store the private port used by the
 
 .. code-block:: bash
 
-    sudo aptitude install curl
-    curl http://127.0.0.1:$WEB_PORT
+    # install curl if necessary, then ...
+    curl http://`hostname`:$WEB_PORT
       Hello world!
 
 access the web app using curl. If everything worked as planned you should see the line "Hello world!" inside of your console.

+ 4 - 3
docs/sources/use/builder.rst

@@ -107,8 +107,7 @@ The `ENV` instruction sets the environment variable `<key>` to the value
 functionally equivalent to prefixing the command with `<key>=<value>`
 
 .. note::
-    The environment variables are local to the Dockerfile, they will not persist
-    when a container is run from the resulting image.
+    The environment variables will persist when a container is run from the resulting image.
 
 2.7 INSERT
 ----------
@@ -122,6 +121,8 @@ curl was installed within the image.
 .. note::
     The path must include the file name.
 
+.. note::
+    This instruction has temporarily disabled
 
 3. Dockerfile Examples
 ======================
@@ -179,4 +180,4 @@ curl was installed within the image.
     # Will output something like ===> 695d7793cbe4
 
     # You'll now have two images, 907ad6c2736f with /bar, and 695d7793cbe4 with
-    # /oink.
+    # /oink.

+ 39 - 17
server.go

@@ -142,8 +142,10 @@ func (srv *Server) ImagesViz(out io.Writer) error {
 }
 
 func (srv *Server) Images(all bool, filter string) ([]ApiImages, error) {
-	var allImages map[string]*Image
-	var err error
+	var (
+		allImages map[string]*Image
+		err       error
+	)
 	if all {
 		allImages, err = srv.runtime.graph.Map()
 	} else {
@@ -152,7 +154,7 @@ func (srv *Server) Images(all bool, filter string) ([]ApiImages, error) {
 	if err != nil {
 		return nil, err
 	}
-	var outs []ApiImages = []ApiImages{} //produce [] when empty instead of 'null'
+	outs := []ApiImages{} //produce [] when empty instead of 'null'
 	for name, repository := range srv.runtime.repositories.Repositories {
 		if filter != "" && name != filter {
 			continue
@@ -361,7 +363,7 @@ func (srv *Server) pullRepository(out io.Writer, remote, askedTag string) error
 
 	for _, img := range repoData.ImgList {
 		if askedTag != "" && img.Tag != askedTag {
-			utils.Debugf("%s does not match %s, skipping", img.Tag, askedTag)
+			utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.Id)
 			continue
 		}
 		fmt.Fprintf(out, "Pulling image %s (%s) from %s\n", img.Id, img.Tag, remote)
@@ -378,11 +380,10 @@ func (srv *Server) pullRepository(out io.Writer, remote, askedTag string) error
 			return fmt.Errorf("Could not find repository on any of the indexed registries.")
 		}
 	}
-	// If we asked for a specific tag, do not register the others
-	if askedTag != "" {
-		return nil
-	}
 	for tag, id := range tagsList {
+		if askedTag != "" && tag != askedTag {
+			continue
+		}
 		if err := srv.runtime.repositories.Set(remote, tag, id, true); err != nil {
 			return err
 		}
@@ -656,15 +657,6 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) {
 	return container.ShortId(), nil
 }
 
-func (srv *Server) ImageCreateFromFile(dockerfile io.Reader, out io.Writer) error {
-	img, err := NewBuilder(srv.runtime).Build(dockerfile, out)
-	if err != nil {
-		return err
-	}
-	fmt.Fprintf(out, "%s\n", img.ShortId())
-	return nil
-}
-
 func (srv *Server) ContainerRestart(name string, t int) error {
 	if container := srv.runtime.Get(name); container != nil {
 		if err := container.Restart(t); err != nil {
@@ -725,6 +717,36 @@ func (srv *Server) ImageDelete(name string) error {
 	return nil
 }
 
+func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error) {
+
+	// Retrieve all images
+	images, err := srv.runtime.graph.All()
+	if err != nil {
+		return nil, err
+	}
+
+	// Store the tree in a map of map (map[parentId][childId])
+	imageMap := make(map[string]map[string]struct{})
+	for _, img := range images {
+		if _, exists := imageMap[img.Parent]; !exists {
+			imageMap[img.Parent] = make(map[string]struct{})
+		}
+		imageMap[img.Parent][img.Id] = struct{}{}
+	}
+
+	// Loop on the children of the given image and check the config
+	for elem := range imageMap[imgId] {
+		img, err := srv.runtime.graph.Get(elem)
+		if err != nil {
+			return nil, err
+		}
+		if CompareConfig(&img.ContainerConfig, config) {
+			return img, nil
+		}
+	}
+	return nil, nil
+}
+
 func (srv *Server) ContainerStart(name string) error {
 	if container := srv.runtime.Get(name); container != nil {
 		if err := container.Start(); err != nil {

+ 39 - 0
utils.go

@@ -47,3 +47,42 @@ func CompareConfig(a, b *Config) bool {
 
 	return true
 }
+
+func MergeConfig(userConf, imageConf *Config) {
+	if userConf.Hostname != "" {
+		userConf.Hostname = imageConf.Hostname
+	}
+	if userConf.User != "" {
+		userConf.User = imageConf.User
+	}
+	if userConf.Memory == 0 {
+		userConf.Memory = imageConf.Memory
+	}
+	if userConf.MemorySwap == 0 {
+		userConf.MemorySwap = imageConf.MemorySwap
+	}
+	if userConf.CpuShares == 0 {
+		userConf.CpuShares = imageConf.CpuShares
+	}
+	if userConf.PortSpecs == nil || len(userConf.PortSpecs) == 0 {
+		userConf.PortSpecs = imageConf.PortSpecs
+	}
+	if !userConf.Tty {
+		userConf.Tty = imageConf.Tty
+	}
+	if !userConf.OpenStdin {
+		userConf.OpenStdin = imageConf.OpenStdin
+	}
+	if !userConf.StdinOnce {
+		userConf.StdinOnce = imageConf.StdinOnce
+	}
+	if userConf.Env == nil || len(userConf.Env) == 0 {
+		userConf.Env = imageConf.Env
+	}
+	if userConf.Cmd == nil || len(userConf.Cmd) == 0 {
+		userConf.Cmd = imageConf.Cmd
+	}
+	if userConf.Dns == nil || len(userConf.Dns) == 0 {
+		userConf.Dns = imageConf.Dns
+	}
+}