From 7626e9fd6448cbaa92632f2ce9cab70e9e142d8a Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 8 Jan 2014 00:27:50 -0700 Subject: [PATCH 001/364] Add more specific lvm2 version to PACKAGERS document I personally tested this using our container, and this was the lowest version that compiles and runs properly. Docker-DCO-1.0-Signed-off-by: Andrew Page (github: tianon) --- hack/PACKAGERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index 05283b909b..1dd039c3e3 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -38,7 +38,7 @@ To build docker, you will need the following system dependencies * A recent version of git and mercurial * Go version 1.2 or later * SQLite version 3.7.9 or later -* libdevmapper from lvm2 version 1.02.77 or later (http://www.sourceware.org/lvm2/) +* libdevmapper version 1.02.68-cvs (2012-01-26) or later from lvm2 version 2.02.89 or later * A clean checkout of the source must be added to a valid Go [workspace](http://golang.org/doc/code.html#Workspaces) under the path *src/github.com/dotcloud/docker*. From 5aa304f9696d8fcc2e39628ce68a39bfc932adb4 Mon Sep 17 00:00:00 2001 From: Danny Yates Date: Mon, 9 Dec 2013 14:58:04 +0000 Subject: [PATCH 002/364] Fix registry pushing/tagging * docker push host:port/namespace/repo wouldn't push multiple tags for the same image * getImageList was unnecessarily complex returning a nested array of ImgData when a correctly ordered list of images was sufficient * removed various bits of redundancy Docker-DCO-1.0-Signed-off-by: Danny Yates (github: codeaholics) --- server.go | 144 +++++++++++++++++++++++++----------------------------- 1 file changed, 67 insertions(+), 77 deletions(-) diff --git a/server.go b/server.go index 2405380ea3..80ac5128fc 100644 --- a/server.go +++ b/server.go @@ -1126,122 +1126,112 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut } // Retrieve the all the images to be uploaded in the correct order -// Note: we can't use a map as it is not ordered -func (srv *Server) getImageList(localRepo map[string]string) ([][]*registry.ImgData, error) { - imgList := map[string]*registry.ImgData{} - depGraph := utils.NewDependencyGraph() +func (srv *Server) getImageList(localRepo map[string]string) ([]string, map[string][]string, error) { + var ( + imageList []string + imagesSeen map[string]bool = make(map[string]bool) + tagsByImage map[string][]string = make(map[string][]string) + ) for tag, id := range localRepo { - img, err := srv.runtime.graph.Get(id) + var imageListForThisTag []string + + tagsByImage[id] = append(tagsByImage[id], tag) + + for img, err := srv.runtime.graph.Get(id); img != nil; img, err = img.GetParent() { if err != nil { - return nil, err + return nil, nil, err } - depGraph.NewNode(img.ID) - img.WalkHistory(func(current *Image) error { - imgList[current.ID] = ®istry.ImgData{ - ID: current.ID, - Tag: tag, + + if imagesSeen[img.ID] { + // This image is already on the list, we can ignore it and all its parents + break } - parent, err := current.GetParent() - if err != nil { - return err - } - if parent == nil { - return nil - } - depGraph.NewNode(parent.ID) - depGraph.AddDependency(current.ID, parent.ID) - return nil - }) + + imagesSeen[img.ID] = true + imageListForThisTag = append(imageListForThisTag, img.ID) } - traversalMap, err := depGraph.GenerateTraversalMap() - if err != nil { - return nil, err + // reverse the image list for this tag (so the "most"-parent image is first) + for i, j := 0, len(imageListForThisTag) - 1; i < j; i, j = i + 1, j - 1 { + imageListForThisTag[i], imageListForThisTag[j] = imageListForThisTag[j], imageListForThisTag[i] } - utils.Debugf("Traversal map: %v", traversalMap) - result := [][]*registry.ImgData{} - for _, round := range traversalMap { - dataRound := []*registry.ImgData{} - for _, imgID := range round { - dataRound = append(dataRound, imgList[imgID]) - } - result = append(result, dataRound) - } - return result, nil + // append to main image list + imageList = append(imageList, imageListForThisTag...) } -func flatten(slc [][]*registry.ImgData) []*registry.ImgData { - result := []*registry.ImgData{} - for _, x := range slc { - result = append(result, x...) - } - return result + utils.Debugf("Image list: %v", imageList) + utils.Debugf("Tags by image: %v", tagsByImage) + + return imageList, tagsByImage, nil } func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, sf *utils.StreamFormatter) error { out = utils.NewWriteFlusher(out) - imgList, err := srv.getImageList(localRepo) + utils.Debugf("Local repo: %s", localRepo) + imgList, tagsByImage, err := srv.getImageList(localRepo) if err != nil { return err } - flattenedImgList := flatten(imgList) + out.Write(sf.FormatStatus("", "Sending image list")) var repoData *registry.RepositoryData - repoData, err = r.PushImageJSONIndex(remoteName, flattenedImgList, false, nil) + var imageIndex []*registry.ImgData + + for imgId, tags := range tagsByImage { + for _, tag := range tags { + imageIndex = append(imageIndex, ®istry.ImgData{ + ID: imgId, + Tag: tag, + }) + } + } + + repoData, err = r.PushImageJSONIndex(remoteName, imageIndex, false, nil) if err != nil { return err } for _, ep := range repoData.Endpoints { out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", localName, len(localRepo))) - // This section can not be parallelized (each round depends on the previous one) - for i, round := range imgList { - // FIXME: This section can be parallelized - for _, elem := range round { + + for _, imgId := range imgList { var pushTags func() error pushTags = func() error { - if i < (len(imgList) - 1) { - // Only tag the top layer in the repository - return nil - } + for _, tag := range tagsByImage[imgId] { + out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", utils.TruncateID(imgId), ep+"repositories/"+remoteName+"/tags/"+tag)) - out.Write(sf.FormatStatus("", "Pushing tags for rev [%s] on {%s}", utils.TruncateID(elem.ID), ep+"repositories/"+remoteName+"/tags/"+elem.Tag)) - if err := r.PushRegistryTag(remoteName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil { + if err := r.PushRegistryTag(remoteName, imgId, tag, ep, repoData.Tokens); err != nil { return err } + } + return nil } - if _, exists := repoData.ImgList[elem.ID]; exists { + + if r.LookupRemoteImage(imgId, ep, repoData.Tokens) { if err := pushTags(); err != nil { return err } - out.Write(sf.FormatProgress(utils.TruncateID(elem.ID), "Image already pushed, skipping", nil)) + out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", utils.TruncateID(imgId))) continue - } else if r.LookupRemoteImage(elem.ID, ep, repoData.Tokens) { - if err := pushTags(); err != nil { - return err } - out.Write(sf.FormatProgress(utils.TruncateID(elem.ID), "Image already pushed, skipping", nil)) - continue - } - checksum, err := srv.pushImage(r, out, remoteName, elem.ID, ep, repoData.Tokens, sf) + + _, err := srv.pushImage(r, out, remoteName, imgId, ep, repoData.Tokens, sf) if err != nil { // FIXME: Continue on error? return err } - elem.Checksum = checksum if err := pushTags(); err != nil { return err } } } - } - if _, err := r.PushImageJSONIndex(remoteName, flattenedImgList, true, repoData.Endpoints); err != nil { + if _, err := r.PushImageJSONIndex(remoteName, imageIndex, true, repoData.Endpoints); err != nil { return err } @@ -1667,24 +1657,24 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) { } } return srv.deleteImage(img, repository, tag) -} + } func (srv *Server) canDeleteImage(imgID string) error { - for _, container := range srv.runtime.List() { - parent, err := srv.runtime.repositories.LookupImage(container.Image) - if err != nil { + for _, container := range srv.runtime.List() { + parent, err := srv.runtime.repositories.LookupImage(container.Image) + if err != nil { return err - } + } - if err := parent.WalkHistory(func(p *Image) error { + if err := parent.WalkHistory(func(p *Image) error { if imgID == p.ID { return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it", utils.TruncateID(imgID), utils.TruncateID(container.ID)) - } - return nil - }); err != nil { + } + return nil + }); err != nil { return err + } } - } return nil } @@ -1700,7 +1690,7 @@ func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error) imageMap := make(map[string][]string) for _, img := range images { imageMap[img.Parent] = append(imageMap[img.Parent], img.ID) - } + } sort.Strings(imageMap[imgID]) // Loop on the children of the given image and check the config for _, elem := range imageMap[imgID] { From 6b48761ce92e5f9590fd8b2723176ab72d297e6c Mon Sep 17 00:00:00 2001 From: Danny Yates Date: Mon, 9 Dec 2013 15:05:46 +0000 Subject: [PATCH 003/364] Remove unneeded DependencyGraph Docker-DCO-1.0-Signed-off-by: Danny Yates (github: codeaholics) --- utils/utils.go | 116 -------------------------------------------- utils/utils_test.go | 56 --------------------- 2 files changed, 172 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index d5dbf35f0a..d079fd068a 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -890,122 +890,6 @@ func UserLookup(uid string) (*User, error) { return nil, fmt.Errorf("User not found in /etc/passwd") } -type DependencyGraph struct { - nodes map[string]*DependencyNode -} - -type DependencyNode struct { - id string - deps map[*DependencyNode]bool -} - -func NewDependencyGraph() DependencyGraph { - return DependencyGraph{ - nodes: map[string]*DependencyNode{}, - } -} - -func (graph *DependencyGraph) addNode(node *DependencyNode) string { - if graph.nodes[node.id] == nil { - graph.nodes[node.id] = node - } - return node.id -} - -func (graph *DependencyGraph) NewNode(id string) string { - if graph.nodes[id] != nil { - return id - } - nd := &DependencyNode{ - id: id, - deps: map[*DependencyNode]bool{}, - } - graph.addNode(nd) - return id -} - -func (graph *DependencyGraph) AddDependency(node, to string) error { - if graph.nodes[node] == nil { - return fmt.Errorf("Node %s does not belong to this graph", node) - } - - if graph.nodes[to] == nil { - return fmt.Errorf("Node %s does not belong to this graph", to) - } - - if node == to { - return fmt.Errorf("Dependency loops are forbidden!") - } - - graph.nodes[node].addDependency(graph.nodes[to]) - return nil -} - -func (node *DependencyNode) addDependency(to *DependencyNode) bool { - node.deps[to] = true - return node.deps[to] -} - -func (node *DependencyNode) Degree() int { - return len(node.deps) -} - -// The magic happens here :: -func (graph *DependencyGraph) GenerateTraversalMap() ([][]string, error) { - Debugf("Generating traversal map. Nodes: %d", len(graph.nodes)) - result := [][]string{} - processed := map[*DependencyNode]bool{} - // As long as we haven't processed all nodes... - for len(processed) < len(graph.nodes) { - // Use a temporary buffer for processed nodes, otherwise - // nodes that depend on each other could end up in the same round. - tmpProcessed := []*DependencyNode{} - for _, node := range graph.nodes { - // If the node has more dependencies than what we have cleared, - // it won't be valid for this round. - if node.Degree() > len(processed) { - continue - } - // If it's already processed, get to the next one - if processed[node] { - continue - } - // It's not been processed yet and has 0 deps. Add it! - // (this is a shortcut for what we're doing below) - if node.Degree() == 0 { - tmpProcessed = append(tmpProcessed, node) - continue - } - // If at least one dep hasn't been processed yet, we can't - // add it. - ok := true - for dep := range node.deps { - if !processed[dep] { - ok = false - break - } - } - // All deps have already been processed. Add it! - if ok { - tmpProcessed = append(tmpProcessed, node) - } - } - Debugf("Round %d: found %d available nodes", len(result), len(tmpProcessed)) - // If no progress has been made this round, - // that means we have circular dependencies. - if len(tmpProcessed) == 0 { - return nil, fmt.Errorf("Could not find a solution to this dependency graph") - } - round := []string{} - for _, nd := range tmpProcessed { - round = append(round, nd.id) - processed[nd] = true - } - result = append(result, round) - } - return result, nil -} - // An StatusError reports an unsuccessful exit by a command. type StatusError struct { Status string diff --git a/utils/utils_test.go b/utils/utils_test.go index 1f23755d11..c8be7b1928 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -416,62 +416,6 @@ func TestParseRelease(t *testing.T) { assertParseRelease(t, "3.8.0-19-generic", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "19-generic"}, 0) } -func TestDependencyGraphCircular(t *testing.T) { - g1 := NewDependencyGraph() - a := g1.NewNode("a") - b := g1.NewNode("b") - g1.AddDependency(a, b) - g1.AddDependency(b, a) - res, err := g1.GenerateTraversalMap() - if res != nil { - t.Fatalf("Expected nil result") - } - if err == nil { - t.Fatalf("Expected error (circular graph can not be resolved)") - } -} - -func TestDependencyGraph(t *testing.T) { - g1 := NewDependencyGraph() - a := g1.NewNode("a") - b := g1.NewNode("b") - c := g1.NewNode("c") - d := g1.NewNode("d") - g1.AddDependency(b, a) - g1.AddDependency(c, a) - g1.AddDependency(d, c) - g1.AddDependency(d, b) - res, err := g1.GenerateTraversalMap() - - if err != nil { - t.Fatalf("%s", err) - } - - if res == nil { - t.Fatalf("Unexpected nil result") - } - - if len(res) != 3 { - t.Fatalf("Expected map of length 3, found %d instead", len(res)) - } - - if len(res[0]) != 1 || res[0][0] != "a" { - t.Fatalf("Expected [a], found %v instead", res[0]) - } - - if len(res[1]) != 2 { - t.Fatalf("Expected 2 nodes for step 2, found %d", len(res[1])) - } - - if (res[1][0] != "b" && res[1][1] != "b") || (res[1][0] != "c" && res[1][1] != "c") { - t.Fatalf("Expected [b, c], found %v instead", res[1]) - } - - if len(res[2]) != 1 || res[2][0] != "d" { - t.Fatalf("Expected [d], found %v instead", res[2]) - } -} - func TestParsePortMapping(t *testing.T) { data, err := PartParser("ip:public:private", "192.168.1.1:80:8080") if err != nil { From df4ea946a653fa3864574fd8110c6c618dde41d8 Mon Sep 17 00:00:00 2001 From: Danny Yates Date: Mon, 9 Dec 2013 17:01:32 +0000 Subject: [PATCH 004/364] Further simplification Docker-DCO-1.0-Signed-off-by: Danny Yates (github: codeaholics) --- server.go | 42 ++++++++++++++---------------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/server.go b/server.go index 80ac5128fc..813ab6db5b 100644 --- a/server.go +++ b/server.go @@ -1139,9 +1139,9 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]string, map[stri tagsByImage[id] = append(tagsByImage[id], tag) for img, err := srv.runtime.graph.Get(id); img != nil; img, err = img.GetParent() { - if err != nil { + if err != nil { return nil, nil, err - } + } if imagesSeen[img.ID] { // This image is already on the list, we can ignore it and all its parents @@ -1150,16 +1150,16 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]string, map[stri imagesSeen[img.ID] = true imageListForThisTag = append(imageListForThisTag, img.ID) - } + } // reverse the image list for this tag (so the "most"-parent image is first) for i, j := 0, len(imageListForThisTag) - 1; i < j; i, j = i + 1, j - 1 { imageListForThisTag[i], imageListForThisTag[j] = imageListForThisTag[j], imageListForThisTag[i] - } + } // append to main image list imageList = append(imageList, imageListForThisTag...) -} + } utils.Debugf("Image list: %v", imageList) utils.Debugf("Tags by image: %v", tagsByImage) @@ -1198,38 +1198,24 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", localName, len(localRepo))) for _, imgId := range imgList { - var pushTags func() error - pushTags = func() error { - for _, tag := range tagsByImage[imgId] { - out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", utils.TruncateID(imgId), ep+"repositories/"+remoteName+"/tags/"+tag)) - - if err := r.PushRegistryTag(remoteName, imgId, tag, ep, repoData.Tokens); err != nil { - return err - } - } - - return nil - } - if r.LookupRemoteImage(imgId, ep, repoData.Tokens) { - if err := pushTags(); err != nil { + out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", utils.TruncateID(imgId))) + } else { + if _, err := srv.pushImage(r, out, remoteName, imgId, ep, repoData.Tokens, sf); err != nil { + // FIXME: Continue on error? return err } - out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", utils.TruncateID(imgId))) - continue - } - - _, err := srv.pushImage(r, out, remoteName, imgId, ep, repoData.Tokens, sf) - if err != nil { - // FIXME: Continue on error? - return err } - if err := pushTags(); err != nil { + for _, tag := range tagsByImage[imgId] { + out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", utils.TruncateID(imgId), ep+"repositories/"+remoteName+"/tags/"+tag)) + + if err := r.PushRegistryTag(remoteName, imgId, tag, ep, repoData.Tokens); err != nil { return err } } } + } if _, err := r.PushImageJSONIndex(remoteName, imageIndex, true, repoData.Endpoints); err != nil { return err From 145501bee3f37ccb022189e7b76f31abcbdbed3c Mon Sep 17 00:00:00 2001 From: Danny Yates Date: Mon, 9 Dec 2013 17:01:53 +0000 Subject: [PATCH 005/364] Fix indentation to match rest of file (tabs, not spaces) Docker-DCO-1.0-Signed-off-by: Danny Yates (github: codeaholics) --- server.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/server.go b/server.go index 813ab6db5b..7695b44a5c 100644 --- a/server.go +++ b/server.go @@ -1134,14 +1134,14 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]string, map[stri ) for tag, id := range localRepo { - var imageListForThisTag []string + var imageListForThisTag []string tagsByImage[id] = append(tagsByImage[id], tag) for img, err := srv.runtime.graph.Get(id); img != nil; img, err = img.GetParent() { - if err != nil { - return nil, nil, err - } + if err != nil { + return nil, nil, err + } if imagesSeen[img.ID] { // This image is already on the list, we can ignore it and all its parents @@ -1152,13 +1152,13 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]string, map[stri imageListForThisTag = append(imageListForThisTag, img.ID) } - // reverse the image list for this tag (so the "most"-parent image is first) - for i, j := 0, len(imageListForThisTag) - 1; i < j; i, j = i + 1, j - 1 { - imageListForThisTag[i], imageListForThisTag[j] = imageListForThisTag[j], imageListForThisTag[i] - } + // reverse the image list for this tag (so the "most"-parent image is first) + for i, j := 0, len(imageListForThisTag) - 1; i < j; i, j = i + 1, j - 1 { + imageListForThisTag[i], imageListForThisTag[j] = imageListForThisTag[j], imageListForThisTag[i] + } - // append to main image list - imageList = append(imageList, imageListForThisTag...) + // append to main image list + imageList = append(imageList, imageListForThisTag...) } utils.Debugf("Image list: %v", imageList) @@ -1201,16 +1201,16 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName if r.LookupRemoteImage(imgId, ep, repoData.Tokens) { out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", utils.TruncateID(imgId))) } else { - if _, err := srv.pushImage(r, out, remoteName, imgId, ep, repoData.Tokens, sf); err != nil { - // FIXME: Continue on error? - return err - } + if _, err := srv.pushImage(r, out, remoteName, imgId, ep, repoData.Tokens, sf); err != nil { + // FIXME: Continue on error? + return err } + } - for _, tag := range tagsByImage[imgId] { - out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", utils.TruncateID(imgId), ep+"repositories/"+remoteName+"/tags/"+tag)) + for _, tag := range tagsByImage[imgId] { + out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", utils.TruncateID(imgId), ep+"repositories/"+remoteName+"/tags/"+tag)) - if err := r.PushRegistryTag(remoteName, imgId, tag, ep, repoData.Tokens); err != nil { + if err := r.PushRegistryTag(remoteName, imgId, tag, ep, repoData.Tokens); err != nil { return err } } From c4535c833dfdaef275d765968a2ed5513ef09097 Mon Sep 17 00:00:00 2001 From: Danny Yates Date: Wed, 8 Jan 2014 10:37:22 +0000 Subject: [PATCH 006/364] gofmt Docker-DCO-1.0-Signed-off-by: Danny Yates (github: codeaholics) --- server.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/server.go b/server.go index 7695b44a5c..32553105fd 100644 --- a/server.go +++ b/server.go @@ -1128,8 +1128,8 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut // Retrieve the all the images to be uploaded in the correct order func (srv *Server) getImageList(localRepo map[string]string) ([]string, map[string][]string, error) { var ( - imageList []string - imagesSeen map[string]bool = make(map[string]bool) + imageList []string + imagesSeen map[string]bool = make(map[string]bool) tagsByImage map[string][]string = make(map[string][]string) ) @@ -1153,7 +1153,7 @@ func (srv *Server) getImageList(localRepo map[string]string) ([]string, map[stri } // reverse the image list for this tag (so the "most"-parent image is first) - for i, j := 0, len(imageListForThisTag) - 1; i < j; i, j = i + 1, j - 1 { + for i, j := 0, len(imageListForThisTag)-1; i < j; i, j = i+1, j-1 { imageListForThisTag[i], imageListForThisTag[j] = imageListForThisTag[j], imageListForThisTag[i] } @@ -1183,7 +1183,7 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName for imgId, tags := range tagsByImage { for _, tag := range tags { imageIndex = append(imageIndex, ®istry.ImgData{ - ID: imgId, + ID: imgId, Tag: tag, }) } @@ -1643,24 +1643,24 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) { } } return srv.deleteImage(img, repository, tag) - } +} func (srv *Server) canDeleteImage(imgID string) error { - for _, container := range srv.runtime.List() { - parent, err := srv.runtime.repositories.LookupImage(container.Image) - if err != nil { + for _, container := range srv.runtime.List() { + parent, err := srv.runtime.repositories.LookupImage(container.Image) + if err != nil { return err - } + } - if err := parent.WalkHistory(func(p *Image) error { + if err := parent.WalkHistory(func(p *Image) error { if imgID == p.ID { return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it", utils.TruncateID(imgID), utils.TruncateID(container.ID)) - } - return nil - }); err != nil { - return err } + return nil + }); err != nil { + return err } + } return nil } @@ -1676,7 +1676,7 @@ func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error) imageMap := make(map[string][]string) for _, img := range images { imageMap[img.Parent] = append(imageMap[img.Parent], img.ID) - } + } sort.Strings(imageMap[imgID]) // Loop on the children of the given image and check the config for _, elem := range imageMap[imgID] { From 14acf6883db8069a10a727ed26f7ef801f460353 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 9 Jan 2014 09:58:40 -0800 Subject: [PATCH 007/364] Remove volumes on docker run -rm If we don't care about the container then we don't care about any volumes created with the run of that container Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 10d3697c19..fd231abeea 100644 --- a/commands.go +++ b/commands.go @@ -2138,7 +2138,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { if _, status, err = getExitCode(cli, runResult.ID); err != nil { return err } - if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID, nil); err != nil { + if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID+"?v=1", nil); err != nil { return err } } else { From e4af8b2ddc7be8d984762ea3bf91729f85284eb9 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Thu, 9 Jan 2014 10:06:48 -0800 Subject: [PATCH 008/364] CONTRIBUTING.md: wrap DCO to 78 chars per line The DCO is a bit hard to read on Github right now because there is lots of horizontal scrolling. Reformat it to 78 chars per line like the rest of the file. Example: http://imgur.com/LtJEIsl Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- CONTRIBUTING.md | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f318e41922..5a5393ba27 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -115,16 +115,28 @@ can certify the below: ``` Docker Developer Grant and Certificate of Origin 1.1 -By making a contribution to the Docker Project ("Project"), I represent and warrant that: +By making a contribution to the Docker Project ("Project"), I represent and +warrant that: -a. The contribution was created in whole or in part by me and I have the right to submit the contribution on my own behalf or on behalf of a third party who has authorized me to submit this contribution to the Project; or +a. The contribution was created in whole or in part by me and I have the right +to submit the contribution on my own behalf or on behalf of a third party who +has authorized me to submit this contribution to the Project; or -b. The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right and authorization to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license) that I have identified in the contribution; or +b. The contribution is based upon previous work that, to the best of my +knowledge, is covered under an appropriate open source license and I have the +right and authorization to submit that work with modifications, whether +created in whole or in part by me, under the same open source license (unless +I am permitted to submit under a different license) that I have identified in +the contribution; or -c. The contribution was provided directly to me by some other person who represented and warranted (a) or (b) and I have not modified it. - -d. I understand and agree that this Project and the contribution are publicly known and that a record of the contribution (including all personal information I submit with it, including my sign-off record) is maintained indefinitely and may be redistributed consistent with this Project or the open source license(s) involved. +c. The contribution was provided directly to me by some other person who +represented and warranted (a) or (b) and I have not modified it. +d. I understand and agree that this Project and the contribution are publicly +known and that a record of the contribution (including all personal +information I submit with it, including my sign-off record) is maintained +indefinitely and may be redistributed consistent with this Project or the open +source license(s) involved. ``` then you just add a line to every git commit message: From c3c89abfd65cb3d0d8c7796b035dde314fb9a2c3 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 9 Jan 2014 11:36:30 -0800 Subject: [PATCH 009/364] Change version to v0.7.5 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 8bd6ba8c5c..6230604d31 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.5 +0.7.5-dev From d69cf2986eb864ffcad026bfbdd30beda5061175 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Date: Thu, 9 Jan 2014 21:43:33 +0100 Subject: [PATCH 010/364] Update docker_remote_api_v1.8.rst Docker-DCO-1.1-Signed-off-by: Jean-Baptiste Dalido (github: jeandalido) --- docs/sources/api/docker_remote_api_v1.8.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/api/docker_remote_api_v1.8.rst b/docs/sources/api/docker_remote_api_v1.8.rst index 3fe5cd73e0..3db06fc942 100644 --- a/docs/sources/api/docker_remote_api_v1.8.rst +++ b/docs/sources/api/docker_remote_api_v1.8.rst @@ -658,7 +658,7 @@ List Images [ { - "RepoTag": [ + "RepoTags": [ "ubuntu:12.04", "ubuntu:precise", "ubuntu:latest" @@ -669,7 +669,7 @@ List Images "VirtualSize": 131506275 }, { - "RepoTag": [ + "RepoTags": [ "ubuntu:12.10", "ubuntu:quantal" ], From 2fd4c39c86a87660cedae19936d5fc5164edd859 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Dalido Date: Fri, 10 Jan 2014 11:35:01 +0100 Subject: [PATCH 011/364] Update docker_remote_api_v1.7.rst and non-versionned Docker-DCO-1.1-Signed-off-by: Jean-Baptiste Dalido (github: jeandalido) --- docs/sources/api/docker_remote_api.rst | 4 ++-- docs/sources/api/docker_remote_api_v1.7.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index b6615ad7d6..84b340f9a3 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -139,7 +139,7 @@ What's new [ { - "RepoTag": [ + "RepoTags": [ "ubuntu:12.04", "ubuntu:precise", "ubuntu:latest" @@ -150,7 +150,7 @@ What's new "VirtualSize": 131506275 }, { - "RepoTag": [ + "RepoTags": [ "ubuntu:12.10", "ubuntu:quantal" ], diff --git a/docs/sources/api/docker_remote_api_v1.7.rst b/docs/sources/api/docker_remote_api_v1.7.rst index d47f672df0..28c5ba30f2 100644 --- a/docs/sources/api/docker_remote_api_v1.7.rst +++ b/docs/sources/api/docker_remote_api_v1.7.rst @@ -643,7 +643,7 @@ List Images [ { - "RepoTag": [ + "RepoTags": [ "ubuntu:12.04", "ubuntu:precise", "ubuntu:latest" @@ -654,7 +654,7 @@ List Images "VirtualSize": 131506275 }, { - "RepoTag": [ + "RepoTags": [ "ubuntu:12.10", "ubuntu:quantal" ], From 051e7a0befaddb6ddbd047abff5f524a7247af6c Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 10 Jan 2014 10:20:31 -0700 Subject: [PATCH 012/364] Inline the test.docker.io fingerprint in the install.sh script as well As long as we're doing it, we ought to do it for all the "official" Docker properties at least Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/install.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hack/install.sh b/hack/install.sh index 1f37018a7b..02d812f388 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -110,6 +110,8 @@ case "$lsb_dist" in set -x if [ "https://get.docker.io/" = "$url" ]; then $sh_c "apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9" + elif [ "https://test.docker.io/" = "$url" ]; then + $sh_c "apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 740B314AE3941731B942C66ADF4FD13717AAD7D6" else $sh_c "$curl ${url}gpg | apt-key add -" fi From c2a59dfe48e55bc93e3bed5ecde021f372151b09 Mon Sep 17 00:00:00 2001 From: Evan Krall Date: Thu, 9 Jan 2014 19:12:56 -0800 Subject: [PATCH 013/364] Use gzip compression in Ubuntu packages to make it at least possible to install on Lucid. Docker-DCO-1.1-Signed-off-by: Evan Krall (github: EvanKrall) --- Dockerfile | 2 +- hack/make/ubuntu | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9da4e8f039..3980f8713a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -72,7 +72,7 @@ RUN cd /usr/local/go/src && bash -xc 'for platform in $DOCKER_CROSSPLATFORMS; do RUN go get code.google.com/p/go.tools/cmd/cover # TODO replace FPM with some very minimal debhelper stuff -RUN gem install --no-rdoc --no-ri fpm --version 1.0.1 +RUN gem install --no-rdoc --no-ri fpm --version 1.0.2 # Setup s3cmd config RUN /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY' > /.s3cfg diff --git a/hack/make/ubuntu b/hack/make/ubuntu index bcffc6560f..6e130659d5 100644 --- a/hack/make/ubuntu +++ b/hack/make/ubuntu @@ -123,7 +123,7 @@ EOF --config-files /etc/init/docker.conf \ --config-files /etc/init.d/docker \ --config-files /etc/default/docker \ - --deb-compression xz \ + --deb-compression gz \ -t deb . mkdir empty fpm -s dir -C empty \ @@ -134,7 +134,7 @@ EOF --maintainer "$PACKAGE_MAINTAINER" \ --url "$PACKAGE_URL" \ --license "$PACKAGE_LICENSE" \ - --deb-compression xz \ + --deb-compression gz \ -t deb . ) } From e5189b5dd2c044030e7e2ab7029eba0fe91c3b25 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 10 Jan 2014 13:03:59 -0700 Subject: [PATCH 014/364] Add ca-certificates to our package Recommends It's only in "Recommends" because it's only required for all but the esoteric configurations (since you can't "docker pull" from the index without it, but that's about it). Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/make/ubuntu | 1 + 1 file changed, 1 insertion(+) diff --git a/hack/make/ubuntu b/hack/make/ubuntu index bcffc6560f..f15608e920 100644 --- a/hack/make/ubuntu +++ b/hack/make/ubuntu @@ -111,6 +111,7 @@ EOF --depends lxc \ --depends aufs-tools \ --depends iptables \ + --deb-recommends ca-certificates \ --description "$PACKAGE_DESCRIPTION" \ --maintainer "$PACKAGE_MAINTAINER" \ --conflicts lxc-docker-virtual-package \ From aa3339cf39b88e4a4b3d830d9b0831d4abff1dcb Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 11 Dec 2013 15:23:38 -0800 Subject: [PATCH 015/364] move container_delete to job handle error remove useless errStr Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 24 +++++++------------- integration/server_test.go | 12 +++++++--- server.go | 45 ++++++++++++++++++++++++++++---------- 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/api.go b/api.go index 1128c7a2b7..5a82375620 100644 --- a/api.go +++ b/api.go @@ -71,13 +71,13 @@ func httpError(w http.ResponseWriter, err error) { // create appropriate error types with clearly defined meaning. if strings.Contains(err.Error(), "No such") { statusCode = http.StatusNotFound - } else if strings.HasPrefix(err.Error(), "Bad parameter") { + } else if strings.Contains(err.Error(), "Bad parameter") { statusCode = http.StatusBadRequest - } else if strings.HasPrefix(err.Error(), "Conflict") { + } else if strings.Contains(err.Error(), "Conflict") { statusCode = http.StatusConflict - } else if strings.HasPrefix(err.Error(), "Impossible") { + } else if strings.Contains(err.Error(), "Impossible") { statusCode = http.StatusNotAcceptable - } else if strings.HasPrefix(err.Error(), "Wrong login/password") { + } else if strings.Contains(err.Error(), "Wrong login/password") { statusCode = http.StatusUnauthorized } else if strings.Contains(err.Error(), "hasn't been activated") { statusCode = http.StatusForbidden @@ -618,18 +618,10 @@ func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *ht if vars == nil { return fmt.Errorf("Missing parameter") } - name := vars["name"] - - removeVolume, err := getBoolParam(r.Form.Get("v")) - if err != nil { - return err - } - removeLink, err := getBoolParam(r.Form.Get("link")) - if err != nil { - return err - } - - if err := srv.ContainerDestroy(name, removeVolume, removeLink); err != nil { + job := srv.Eng.Job("container_delete", vars["name"]) + job.Setenv("removeVolume", r.Form.Get("v")) + job.Setenv("removeLink", r.Form.Get("link")) + if err := job.Run(); err != nil { return err } w.WriteHeader(http.StatusNoContent) diff --git a/integration/server_test.go b/integration/server_test.go index 2650311c36..e225c4dd35 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -95,7 +95,9 @@ func TestCreateRm(t *testing.T) { t.Errorf("Expected 1 container, %v found", len(c)) } - if err = srv.ContainerDestroy(id, true, false); err != nil { + job := eng.Job("container_delete", id) + job.SetenvBool("removeVolume", true) + if err := job.Run(); err != nil { t.Fatal(err) } @@ -135,7 +137,9 @@ func TestCreateRmVolumes(t *testing.T) { t.Fatal(err) } - if err = srv.ContainerDestroy(id, true, false); err != nil { + job = eng.Job("container_delete", id) + job.SetenvBool("removeVolume", true) + if err := job.Run(); err != nil { t.Fatal(err) } @@ -211,7 +215,9 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { } // FIXME: this failed once with a race condition ("Unable to remove filesystem for xxx: directory not empty") - if err := srv.ContainerDestroy(id, true, false); err != nil { + job = eng.Job("container_delete", id) + job.SetenvBool("removeVolume", true) + if err := job.Run(); err != nil { t.Fatal(err) } diff --git a/server.go b/server.go index c9c10fe0ae..cb7c673d24 100644 --- a/server.go +++ b/server.go @@ -116,6 +116,10 @@ func jobInitApi(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + if err := job.Eng.Register("container_delete", srv.ContainerDestroy); err != nil { + job.Error(err) + return engine.StatusErr + } return engine.StatusOK } @@ -1431,24 +1435,36 @@ func (srv *Server) ContainerRestart(name string, t int) error { return nil } -func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool) error { +func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { + if len(job.Args) != 1 { + job.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) + return engine.StatusErr + } + name := job.Args[0] + removeVolume := job.GetenvBool("removeVolume") + removeLink := job.GetenvBool("removeLink") + container := srv.runtime.Get(name) if removeLink { if container == nil { - return fmt.Errorf("No such link: %s", name) + job.Errorf("No such link: %s", name) + return engine.StatusErr } name, err := srv.runtime.getFullName(name) if err != nil { - return err + job.Error(err) + return engine.StatusErr } parent, n := path.Split(name) if parent == "/" { - return fmt.Errorf("Conflict, cannot remove the default name of the container") + job.Errorf("Conflict, cannot remove the default name of the container") + return engine.StatusErr } pe := srv.runtime.containerGraph.Get(parent) if pe == nil { - return fmt.Errorf("Cannot get parent %s for name %s", parent, name) + job.Errorf("Cannot get parent %s for name %s", parent, name) + return engine.StatusErr } parentContainer := srv.runtime.Get(pe.ID()) @@ -1461,14 +1477,16 @@ func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool) } if err := srv.runtime.containerGraph.Delete(name); err != nil { - return err + job.Error(err) + return engine.StatusErr } - return nil + return engine.StatusOK } if container != nil { if container.State.IsRunning() { - return fmt.Errorf("Impossible to remove a running container, please stop it first") + job.Errorf("Impossible to remove a running container, please stop it first") + return engine.StatusErr } volumes := make(map[string]struct{}) @@ -1493,7 +1511,8 @@ func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool) volumes[volumeId] = struct{}{} } if err := srv.runtime.Destroy(container); err != nil { - return fmt.Errorf("Cannot destroy container %s: %s", name, err) + job.Errorf("Cannot destroy container %s: %s", name, err) + return engine.StatusErr } srv.LogEvent("destroy", container.ID, srv.runtime.repositories.ImageName(container.Image)) @@ -1513,14 +1532,16 @@ func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool) continue } if err := srv.runtime.volumes.Delete(volumeId); err != nil { - return err + job.Error(err) + return engine.StatusErr } } } } else { - return fmt.Errorf("No such container: %s", name) + job.Errorf("No such container: %s", name) + return engine.StatusErr } - return nil + return engine.StatusOK } var ErrImageReferenced = errors.New("Image referenced by a repository") From 15689b56c8e397d3207dad5b23937cb9b1e453cd Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 6 Jan 2014 17:34:51 -0800 Subject: [PATCH 016/364] Move restart to a job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 9 +++------ integration/server_test.go | 4 +++- server.go | 26 +++++++++++++++++++++----- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/api.go b/api.go index 1128c7a2b7..da7849d16b 100644 --- a/api.go +++ b/api.go @@ -596,15 +596,12 @@ func postContainersRestart(srv *Server, version float64, w http.ResponseWriter, if err := parseForm(r); err != nil { return err } - t, err := strconv.Atoi(r.Form.Get("t")) - if err != nil || t < 0 { - t = 10 - } if vars == nil { return fmt.Errorf("Missing parameter") } - name := vars["name"] - if err := srv.ContainerRestart(name, t); err != nil { + job := srv.Eng.Job("restart", vars["name"]) + job.Setenv("t", r.Form.Get("t")) + if err := job.Run(); err != nil { return err } w.WriteHeader(http.StatusNoContent) diff --git a/integration/server_test.go b/integration/server_test.go index 2650311c36..60f6209ab9 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -188,7 +188,9 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { t.Fatal(err) } - if err := srv.ContainerRestart(id, 15); err != nil { + job = eng.Job("restart", id) + job.SetenvInt("t", 15) + if err := job.Run(); err != nil { t.Fatal(err) } diff --git a/server.go b/server.go index 2405380ea3..d303d235e4 100644 --- a/server.go +++ b/server.go @@ -84,6 +84,10 @@ func jobInitApi(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + if err := job.Eng.Register("restart", srv.ContainerRestart); err != nil { + job.Error(err) + return engine.StatusErr + } if err := job.Eng.Register("start", srv.ContainerStart); err != nil { job.Error(err) return engine.StatusErr @@ -1419,16 +1423,28 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { return engine.StatusOK } -func (srv *Server) ContainerRestart(name string, t int) error { +func (srv *Server) ContainerRestart(job *engine.Job) engine.Status { + if len(job.Args) != 1 { + job.Errorf("Usage: %s CONTAINER\n", job.Name) + return engine.StatusErr + } + name := job.Args[0] + t := job.GetenvInt("t") + if t == -1 { + t = 10 + } if container := srv.runtime.Get(name); container != nil { - if err := container.Restart(t); err != nil { - return fmt.Errorf("Cannot restart container %s: %s", name, err) + if err := container.Restart(int(t)); err != nil { + job.Errorf("Cannot restart container %s: %s\n", name, err) + return engine.StatusErr } srv.LogEvent("restart", container.ID, srv.runtime.repositories.ImageName(container.Image)) } else { - return fmt.Errorf("No such container: %s", name) + job.Errorf("No such container: %s\n", name) + return engine.StatusErr } - return nil + return engine.StatusOK + } func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool) error { From 5264914e574c20c8fefcd6e5d858f51f341dd9da Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 7 Jan 2014 12:39:15 -0800 Subject: [PATCH 017/364] Move image_export to a job Docker-DCO-1.0-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 10 ++++++++-- server.go | 42 ++++++++++++++++++++++++++++++------------ 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/api.go b/api.go index 21e045e03d..c768ef9344 100644 --- a/api.go +++ b/api.go @@ -536,11 +536,17 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http } func getImagesGet(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - name := vars["name"] + if vars == nil { + return fmt.Errorf("Missing parameter") + } if version > 1.0 { w.Header().Set("Content-Type", "application/x-tar") } - return srv.ImageExport(name, w) + job := srv.Eng.Job("image_export", vars["name"]) + if err := job.Stdout.Add(w); err != nil { + return err + } + return job.Run() } func postImagesLoad(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/server.go b/server.go index f1466e81d3..c1fda6aeb9 100644 --- a/server.go +++ b/server.go @@ -123,6 +123,10 @@ func jobInitApi(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + if err := job.Eng.Register("image_export", srv.ImageExport); err != nil { + job.Error(err) + return engine.StatusErr + } return engine.StatusOK } @@ -251,11 +255,17 @@ func (srv *Server) ContainerExport(job *engine.Job) engine.Status { // uncompressed tar ball. // name is the set of tags to export. // out is the writer where the images are written to. -func (srv *Server) ImageExport(name string, out io.Writer) error { +func (srv *Server) ImageExport(job *engine.Job) engine.Status { + if len(job.Args) != 1 { + job.Errorf("Usage: %s CONTAINER\n", job.Name) + return engine.StatusErr + } + name := job.Args[0] // get image json tempdir, err := ioutil.TempDir("", "docker-export-") if err != nil { - return err + job.Error(err) + return engine.StatusErr } defer os.RemoveAll(tempdir) @@ -263,17 +273,20 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { rootRepo, err := srv.runtime.repositories.Get(name) if err != nil { - return err + job.Error(err) + return engine.StatusErr } if rootRepo != nil { for _, id := range rootRepo { image, err := srv.ImageInspect(id) if err != nil { - return err + job.Error(err) + return engine.StatusErr } if err := srv.exportImage(image, tempdir); err != nil { - return err + job.Error(err) + return engine.StatusErr } } @@ -283,27 +296,32 @@ func (srv *Server) ImageExport(name string, out io.Writer) error { rootRepoJson, _ := json.Marshal(rootRepoMap) if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.ModeAppend); err != nil { - return err + job.Error(err) + return engine.StatusErr } } else { image, err := srv.ImageInspect(name) if err != nil { - return err + job.Error(err) + return engine.StatusErr } if err := srv.exportImage(image, tempdir); err != nil { - return err + job.Error(err) + return engine.StatusErr } } fs, err := archive.Tar(tempdir, archive.Uncompressed) if err != nil { - return err + job.Error(err) + return engine.StatusErr } - if _, err := io.Copy(out, fs); err != nil { - return err + if _, err := io.Copy(job.Stdout, fs); err != nil { + job.Error(err) + return engine.StatusErr } - return nil + return engine.StatusOK } func (srv *Server) exportImage(image *Image, tempdir string) error { From a02450d048d0a858aceda2343ef4d7633eca6ebb Mon Sep 17 00:00:00 2001 From: Jordan Sissel Date: Fri, 10 Jan 2014 12:08:28 -0800 Subject: [PATCH 018/364] Use '-s empty' instead of '-s dir' This *should* have the same effect as the previous strategy: Instead of 'mkdir empty; fpm -s dir -C empty ...' we can simply do 'fpm -s empty' Docker-DCO-1.1-Signed-off-by: Jordan Sissel (github: jordansissel) --- hack/make/ubuntu | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hack/make/ubuntu b/hack/make/ubuntu index 6e130659d5..e52a592a71 100644 --- a/hack/make/ubuntu +++ b/hack/make/ubuntu @@ -125,8 +125,7 @@ EOF --config-files /etc/default/docker \ --deb-compression gz \ -t deb . - mkdir empty - fpm -s dir -C empty \ + fpm -s empty \ --name lxc-docker --version $PKGVERSION \ --architecture "$PACKAGE_ARCHITECTURE" \ --depends lxc-docker-$VERSION \ @@ -135,7 +134,7 @@ EOF --url "$PACKAGE_URL" \ --license "$PACKAGE_LICENSE" \ --deb-compression gz \ - -t deb . + -t deb ) } From fd78128870b78c282b65976cfff100c4101569b1 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 10 Jan 2014 14:37:07 -0700 Subject: [PATCH 019/364] Add support for `RUN [""]` in Dockerfiles for greater specificity in what is actually executed Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- buildfile.go | 2 +- integration/buildfile_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/buildfile.go b/buildfile.go index de03e5879f..5071070216 100644 --- a/buildfile.go +++ b/buildfile.go @@ -124,7 +124,7 @@ func (b *buildFile) 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) + config, _, _, err := ParseRun(append([]string{b.image}, b.buildCmdFromJson(args)...), nil) if err != nil { return err } diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 5dd403274e..b7cbebd402 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -223,6 +223,31 @@ run [ "$(cat /bar/withfile)" = "test2" ] }, nil, }, + + // JSON! + { + ` +FROM {IMAGE} +RUN ["/bin/echo","hello","world"] +CMD ["/bin/true"] +ENTRYPOINT ["/bin/echo","your command -->"] +`, + nil, + nil, + }, + { + ` +FROM {IMAGE} +ADD test /test +RUN ["chmod","+x","/test"] +RUN ["/test"] +RUN [ "$(cat /testfile)" = 'test!' ] +`, + [][2]string{ + {"test", "#!/bin/sh\necho 'test!' > /testfile"}, + }, + nil, + }, } // FIXME: test building with 2 successive overlapping ADD commands From fb63cfa9a502e2410597422f8877cf16b0bbaad2 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sat, 11 Jan 2014 05:46:11 -0700 Subject: [PATCH 020/364] Stop ADD from following symlinks outside the context when passed as the first argument Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- buildfile.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/buildfile.go b/buildfile.go index de03e5879f..6b568d7563 100644 --- a/buildfile.go +++ b/buildfile.go @@ -287,6 +287,11 @@ func (b *buildFile) CmdVolume(args string) error { func (b *buildFile) checkPathForAddition(orig string) error { origPath := path.Join(b.contextPath, orig) + if p, err := filepath.EvalSymlinks(origPath); err != nil { + return err + } else { + origPath = p + } if !strings.HasPrefix(origPath, b.contextPath) { return fmt.Errorf("Forbidden path outside the build context: %s (%s)", orig, origPath) } From f30ca8935d128bbd060a04b47cec2f26d682797b Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 10 Jan 2014 15:33:19 +1000 Subject: [PATCH 021/364] add info and example of image cache in use when building Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) Docker-DCO-1.0-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/use/builder.rst | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index 81145a6ee8..e61db62689 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -40,9 +40,31 @@ build succeeds: ``sudo docker build -t shykes/myapp .`` The Docker daemon will run your steps one-by-one, committing the -result if necessary, before finally outputting the ID of your new -image. The Docker daemon will automatically clean up the context you -sent. +result to a new image if necessary, before finally outputting the +ID of your new image. The Docker daemon will automatically clean +up the context you sent. + +Note that each instruction is run independently, and causes a new image +to be created - so ``RUN cd /tmp`` will not have any effect on the next +instructions. + +Whenever possible, Docker will re-use the intermediate images, +accelerating ``docker build`` significantly (indicated by ``Using cache``: + +.. code-block:: bash + + $ docker build -t SvenDowideit/ambassador . + Uploading context 10.24 kB + Uploading context + Step 1 : FROM docker-ut + ---> cbba202fe96b + Step 2 : MAINTAINER SvenDowideit@home.org.au + ---> Using cache + ---> 51182097be13 + Step 3 : CMD env | grep _TCP= | sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' | sh && top + ---> Using cache + ---> 1a5ffc17324d + Successfully built 1a5ffc17324d When you're done with your build, you're ready to look into :ref:`image_push`. From d54ce8087aba23663856c81a3fb5475979bdf453 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 16 Dec 2013 13:47:09 +0100 Subject: [PATCH 022/364] Don't shell out to tar for ExportChanges This changes ExportChanges to use the go tar support so we can directly create tar layer files. This has several advantages: * We don't have to create the whiteout files on disk to get them added to the layer * We can later guarantee specific features (such as xattrs) being supported by the tar implementation. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- archive/changes.go | 128 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 108 insertions(+), 20 deletions(-) diff --git a/archive/changes.go b/archive/changes.go index 8fe9ff2233..5fc56b8863 100644 --- a/archive/changes.go +++ b/archive/changes.go @@ -1,7 +1,10 @@ package archive import ( + "archive/tar" "fmt" + "github.com/dotcloud/docker/utils" + "io" "os" "path/filepath" "strings" @@ -310,24 +313,109 @@ func ChangesSize(newDir string, changes []Change) int64 { return size } -func ExportChanges(dir string, changes []Change) (Archive, error) { - files := make([]string, 0) - deletions := make([]string, 0) - for _, change := range changes { - if change.Kind == ChangeModify || change.Kind == ChangeAdd { - files = append(files, change.Path) - } - if change.Kind == ChangeDelete { - base := filepath.Base(change.Path) - dir := filepath.Dir(change.Path) - deletions = append(deletions, filepath.Join(dir, ".wh."+base)) - } - } - // FIXME: Why do we create whiteout files inside Tar code ? - return TarFilter(dir, &TarOptions{ - Compression: Uncompressed, - Includes: files, - Recursive: false, - CreateFiles: deletions, - }) +func major(device uint64) uint64 { + return (device >> 8) & 0xfff +} + +func minor(device uint64) uint64 { + return (device & 0xff) | ((device >> 12) & 0xfff00) +} + +func ExportChanges(dir string, changes []Change) (Archive, error) { + reader, writer := io.Pipe() + tw := tar.NewWriter(writer) + + go func() { + // In general we log errors here but ignore them because + // during e.g. a diff operation the container can continue + // mutating the filesystem and we can see transient errors + // from this + for _, change := range changes { + if change.Kind == ChangeDelete { + whiteOutDir := filepath.Dir(change.Path) + whiteOutBase := filepath.Base(change.Path) + whiteOut := filepath.Join(whiteOutDir, ".wh."+whiteOutBase) + hdr := &tar.Header{ + Name: whiteOut[1:], + Size: 0, + ModTime: time.Now(), + AccessTime: time.Now(), + ChangeTime: time.Now(), + } + if err := tw.WriteHeader(hdr); err != nil { + utils.Debugf("Can't write whiteout header: %s\n", err) + } + } else { + path := filepath.Join(dir, change.Path) + + var stat syscall.Stat_t + if err := syscall.Lstat(path, &stat); err != nil { + utils.Debugf("Can't stat source file: %s\n", err) + continue + } + + mtim := getLastModification(&stat) + atim := getLastAccess(&stat) + hdr := &tar.Header{ + Name: change.Path[1:], + Mode: int64(stat.Mode & 07777), + Uid: int(stat.Uid), + Gid: int(stat.Gid), + ModTime: time.Unix(mtim.Sec, mtim.Nsec), + AccessTime: time.Unix(atim.Sec, atim.Nsec), + } + + if stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR { + hdr.Typeflag = tar.TypeDir + } else if stat.Mode&syscall.S_IFLNK == syscall.S_IFLNK { + hdr.Typeflag = tar.TypeSymlink + if link, err := os.Readlink(path); err != nil { + utils.Debugf("Can't readlink source file: %s\n", err) + continue + } else { + hdr.Linkname = link + } + } else if stat.Mode&syscall.S_IFBLK == syscall.S_IFBLK || + stat.Mode&syscall.S_IFCHR == syscall.S_IFCHR { + if stat.Mode&syscall.S_IFBLK == syscall.S_IFBLK { + hdr.Typeflag = tar.TypeBlock + } else { + hdr.Typeflag = tar.TypeChar + } + hdr.Devmajor = int64(major(stat.Rdev)) + hdr.Devminor = int64(minor(stat.Rdev)) + } else if stat.Mode&syscall.S_IFIFO == syscall.S_IFIFO || + stat.Mode&syscall.S_IFSOCK == syscall.S_IFSOCK { + hdr.Typeflag = tar.TypeFifo + } else if stat.Mode&syscall.S_IFREG == syscall.S_IFREG { + hdr.Typeflag = tar.TypeReg + hdr.Size = stat.Size + } else { + utils.Debugf("Unknown file type: %s\n", path) + continue + } + + if err := tw.WriteHeader(hdr); err != nil { + utils.Debugf("Can't write tar header: %s\n", err) + } + if hdr.Typeflag == tar.TypeReg { + if file, err := os.Open(path); err != nil { + utils.Debugf("Can't open file: %s\n", err) + } else { + _, err := io.Copy(tw, file) + if err != nil { + utils.Debugf("Can't copy file: %s\n", err) + } + file.Close() + } + } + } + } + // Make sure to check the error on Close. + if err := tw.Close(); err != nil { + utils.Debugf("Can't close layer: %s\n", err) + } + writer.Close() + }() + return reader, nil } From 93e120e7d67313086d8bdecbcb57ea68958f91e4 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 17 Dec 2013 09:12:44 +0100 Subject: [PATCH 023/364] Discard all data on devicemapper devices when deleting them This works around the fact that deleting a device in a thin pool doesn't discard the free space. Unfortunately even this is not perfect, as it seems discards are respected only for blocks that has never been shared in the thin device code. However, this has been fixed in the upstream kernel device-mapper tree: http://git.kernel.org/cgit/linux/kernel/git/device-mapper/linux-dm.git/commit/?h=for-next&id=0ab1c92ff748b745c1ed7cde31bb37ad2c5f901a When this hits the kernel I belive this will fully return space for removed images/containers to the host FS. For now it only helps partially (which is better than nothing). Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/devmapper/deviceset.go | 9 ++++++++ graphdriver/devmapper/devmapper.go | 24 ++++++++++++++++++++++ graphdriver/devmapper/devmapper_wrapper.go | 1 + graphdriver/devmapper/driver_test.go | 4 ++++ graphdriver/devmapper/ioctl.go | 11 ++++++++++ 5 files changed, 49 insertions(+) diff --git a/graphdriver/devmapper/deviceset.go b/graphdriver/devmapper/deviceset.go index 7308c0e922..6e3caf657d 100644 --- a/graphdriver/devmapper/deviceset.go +++ b/graphdriver/devmapper/deviceset.go @@ -568,6 +568,15 @@ func (devices *DeviceSet) removeDevice(hash string) error { return fmt.Errorf("hash %s doesn't exists", hash) } + // This is a workaround for the kernel not discarding block so + // on the thin pool when we remove a thinp device, so we do it + // manually + if err := devices.activateDeviceIfNeeded(hash); err == nil { + if err := BlockDeviceDiscard(info.DevName()); err != nil { + utils.Debugf("Error discarding block on device: %s (ignoring)\n", err) + } + } + devinfo, _ := getInfo(info.Name()) if devinfo != nil && devinfo.Exists != 0 { if err := removeDevice(info.Name()); err != nil { diff --git a/graphdriver/devmapper/devmapper.go b/graphdriver/devmapper/devmapper.go index dfbdf385d7..d3eba78a27 100644 --- a/graphdriver/devmapper/devmapper.go +++ b/graphdriver/devmapper/devmapper.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/dotcloud/docker/utils" "runtime" + "syscall" ) type DevmapperLogger interface { @@ -288,6 +289,29 @@ func GetBlockDeviceSize(file *osFile) (uint64, error) { return uint64(size), nil } +func BlockDeviceDiscard(path string) error { + file, err := osOpenFile(path, osORdWr, 0) + if err != nil { + return err + } + defer file.Close() + + size, err := GetBlockDeviceSize(file) + if err != nil { + return err + } + + if err := ioctlBlkDiscard(file.Fd(), 0, size); err != nil { + return err + } + + // Without this sometimes the remove of the device that happens after + // discard fails with EBUSY. + syscall.Sync() + + return nil +} + // This is the programmatic example of "dmsetup create" func createPool(poolName string, dataFile, metadataFile *osFile) error { task, err := createTask(DeviceCreate, poolName) diff --git a/graphdriver/devmapper/devmapper_wrapper.go b/graphdriver/devmapper/devmapper_wrapper.go index 80d430e2bf..7e6dd5e0cb 100644 --- a/graphdriver/devmapper/devmapper_wrapper.go +++ b/graphdriver/devmapper/devmapper_wrapper.go @@ -66,6 +66,7 @@ type ( // IOCTL consts const ( BlkGetSize64 = C.BLKGETSIZE64 + BlkDiscard = C.BLKDISCARD LoopSetFd = C.LOOP_SET_FD LoopCtlGetFree = C.LOOP_CTL_GET_FREE diff --git a/graphdriver/devmapper/driver_test.go b/graphdriver/devmapper/driver_test.go index b6d997bc2f..9a2e409b63 100644 --- a/graphdriver/devmapper/driver_test.go +++ b/graphdriver/devmapper/driver_test.go @@ -641,6 +641,10 @@ func TestDriverRemove(t *testing.T) { "DmTaskSetMessage", "DmTaskCreate", "DmTaskGetInfo", + "DmTaskSetCookie", + "DmTaskSetTarget", + "DmTaskSetAddNode", + "DmUdevWait", "Mounted", "sysUnmount", ) diff --git a/graphdriver/devmapper/ioctl.go b/graphdriver/devmapper/ioctl.go index 448d2d5a50..f9bf34f353 100644 --- a/graphdriver/devmapper/ioctl.go +++ b/graphdriver/devmapper/ioctl.go @@ -58,3 +58,14 @@ func ioctlBlkGetSize64(fd uintptr) (int64, error) { } return size, nil } + +func ioctlBlkDiscard(fd uintptr, offset, length uint64) error { + var r [2]uint64 + r[0] = offset + r[1] = length + + if _, _, err := sysSyscall(sysSysIoctl, fd, BlkDiscard, uintptr(unsafe.Pointer(&r[0]))); err != 0 { + return err + } + return nil +} From 89bed4337d2314c3ffaac231c141b635ecc65ac5 Mon Sep 17 00:00:00 2001 From: Fabio Falci Date: Mon, 13 Jan 2014 20:28:30 +0000 Subject: [PATCH 024/364] Use https to get the latest docker version To avoid unexpected results since docker was using http. For instance, my broadband doesn't return not found when it's down but a html page saying that the internet is down. Docker was showing that html instead of ignoring it. Fix #3570 Docker-DCO-1.1-Signed-off-by: Fabio Falci (github: fabiofalci) --- utils/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/utils.go b/utils/utils.go index 4dfadb793f..e046dfa2a5 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -827,7 +827,7 @@ func ParseHost(defaultHost string, defaultPort int, defaultUnix, addr string) (s } func GetReleaseVersion() string { - resp, err := http.Get("http://get.docker.io/latest") + resp, err := http.Get("https://get.docker.io/latest") if err != nil { return "" } From 1004c2d3d8ed826e6568f6640ac6aa3160f31a8d Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 13 Jan 2014 16:25:23 -0700 Subject: [PATCH 025/364] Add xz-utils to our deb-recommends since it's necessary for some images Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/make/ubuntu | 1 + 1 file changed, 1 insertion(+) diff --git a/hack/make/ubuntu b/hack/make/ubuntu index 2877e7e262..0ec0716ba4 100644 --- a/hack/make/ubuntu +++ b/hack/make/ubuntu @@ -112,6 +112,7 @@ EOF --depends aufs-tools \ --depends iptables \ --deb-recommends ca-certificates \ + --deb-recommends xz-utils \ --description "$PACKAGE_DESCRIPTION" \ --maintainer "$PACKAGE_MAINTAINER" \ --conflicts lxc-docker-virtual-package \ From 17a806c8a0b6add2aa773dfca272acefee9b638c Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 12 Dec 2013 22:39:35 +0000 Subject: [PATCH 026/364] Port 'docker images' to the engine API Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- api.go | 30 ++++------- engine/env.go | 74 +++++++++++++++++++++++++ engine/streams.go | 16 ++++++ engine/table_test.go | 28 ++++++++++ integration/api_test.go | 64 ++++++++++------------ integration/runtime_test.go | 17 +++--- integration/server_test.go | 105 +++++++++++++----------------------- integration/sorter_test.go | 16 ++---- integration/utils_test.go | 21 +++++++- server.go | 74 ++++++++++++++----------- 10 files changed, 270 insertions(+), 175 deletions(-) create mode 100644 engine/table_test.go diff --git a/api.go b/api.go index c768ef9344..10dab5fae2 100644 --- a/api.go +++ b/api.go @@ -181,27 +181,19 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http. if err := parseForm(r); err != nil { return err } - - all, err := getBoolParam(r.Form.Get("all")) - if err != nil { + fmt.Printf("getImagesJSON\n") + job := srv.Eng.Job("images") + job.Setenv("filter", r.Form.Get("filter")) + job.Setenv("all", r.Form.Get("all")) + // FIXME: 1.7 clients expect a single json list + job.Stdout.Add(w) + w.WriteHeader(http.StatusOK) + fmt.Printf("running images job\n") + if err := job.Run(); err != nil { return err } - filter := r.Form.Get("filter") - - outs, err := srv.Images(all, filter) - if err != nil { - return err - } - - if version < 1.7 { - outs2 := []APIImagesOld{} - for _, ctnr := range outs { - outs2 = append(outs2, ctnr.ToLegacy()...) - } - - return writeJSON(w, http.StatusOK, outs2) - } - return writeJSON(w, http.StatusOK, outs) + fmt.Printf("job has been run\n") + return nil } func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/engine/env.go b/engine/env.go index a65c8438d2..f4cc124240 100644 --- a/engine/env.go +++ b/engine/env.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "sort" "strconv" "strings" ) @@ -232,3 +233,76 @@ func (env *Env) Map() map[string]string { } return m } + +type Table struct { + Data []*Env + sortKey string +} + +func NewTable(sortKey string, sizeHint int) *Table { + return &Table{ + make([]*Env, 0, sizeHint), + sortKey, + } +} + +func (t *Table) Add(env *Env) { + t.Data = append(t.Data, env) +} + +func (t *Table) Len() int { + return len(t.Data) +} + +func (t *Table) Less(a, b int) bool { + return t.lessBy(a, b, t.sortKey) +} + +func (t *Table) lessBy(a, b int, by string) bool { + keyA := t.Data[a].Get(by) + keyB := t.Data[b].Get(by) + intA, errA := strconv.ParseInt(keyA, 10, 64) + intB, errB := strconv.ParseInt(keyB, 10, 64) + if errA == nil && errB == nil { + return intA < intB + } + return keyA < keyB +} + +func (t *Table) Swap(a, b int) { + tmp := t.Data[a] + t.Data[a] = t.Data[b] + t.Data[b] = tmp +} + +func (t *Table) Sort() { + sort.Sort(t) +} + +func (t *Table) WriteTo(dst io.Writer) (n int64, err error) { + for _, env := range t.Data { + bytes, err := env.WriteTo(dst) + if err != nil { + return -1, err + } + if _, err := dst.Write([]byte{'\n'}); err != nil { + return -1, err + } + n += bytes + 1 + } + return n, nil +} + +func (t *Table) ReadFrom(src io.Reader) (n int64, err error) { + decoder := NewDecoder(src) + for { + env, err := decoder.Decode() + if err == io.EOF { + return 0, nil + } else if err != nil { + return -1, err + } + t.Add(env) + } + return 0, nil +} diff --git a/engine/streams.go b/engine/streams.go index 7cd4a60cf7..824f0a4ab2 100644 --- a/engine/streams.go +++ b/engine/streams.go @@ -190,3 +190,19 @@ func (o *Output) AddEnv() (dst *Env, err error) { }() return dst, nil } + +func (o *Output) AddTable() (dst *Table, err error) { + src, err := o.AddPipe() + if err != nil { + return nil, err + } + dst = NewTable("", 0) + o.tasks.Add(1) + go func() { + defer o.tasks.Done() + if _, err := dst.ReadFrom(src); err != nil { + return + } + }() + return dst, nil +} diff --git a/engine/table_test.go b/engine/table_test.go new file mode 100644 index 0000000000..0a81d690eb --- /dev/null +++ b/engine/table_test.go @@ -0,0 +1,28 @@ +package engine + +import ( + "testing" + "bytes" + "encoding/json" +) + +func TestTableWriteTo(t *testing.T) { + table := NewTable("", 0) + e := &Env{} + e.Set("foo", "bar") + table.Add(e) + var buf bytes.Buffer + if _, err := table.WriteTo(&buf); err != nil { + t.Fatal(err) + } + output := make(map[string]string) + if err := json.Unmarshal(buf.Bytes(), &output); err != nil { + t.Fatal(err) + } + if len(output) != 1 { + t.Fatalf("Incorrect output: %v", output) + } + if val, exists := output["foo"]; !exists || val != "bar" { + t.Fatalf("Inccorect output: %v", output) + } +} diff --git a/integration/api_test.go b/integration/api_test.go index ff42afac5a..de54078dea 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -60,11 +60,14 @@ func TestGetInfo(t *testing.T) { defer mkRuntimeFromEngine(eng, t).Nuke() srv := mkServerFromEngine(eng, t) - initialImages, err := srv.Images(false, "") + job := eng.Job("images") + initialImages, err := job.Stdout.AddTable() if err != nil { t.Fatal(err) } - + if err := job.Run(); err != nil { + t.Fatal(err) + } req, err := http.NewRequest("GET", "/info", nil) if err != nil { t.Fatal(err) @@ -85,8 +88,8 @@ func TestGetInfo(t *testing.T) { t.Fatal(err) } out.Close() - if images := i.GetInt("Images"); images != len(initialImages) { - t.Errorf("Expected images: %d, %d found", len(initialImages), images) + if images := i.GetInt("Images"); images != initialImages.Len() { + t.Errorf("Expected images: %d, %d found", initialImages.Len(), images) } expected := "application/json" if result := r.HeaderMap.Get("Content-Type"); result != expected { @@ -145,12 +148,14 @@ func TestGetImagesJSON(t *testing.T) { defer mkRuntimeFromEngine(eng, t).Nuke() srv := mkServerFromEngine(eng, t) - // all=0 - - initialImages, err := srv.Images(false, "") + job := eng.Job("images") + initialImages, err := job.Stdout.AddTable() if err != nil { t.Fatal(err) } + if err := job.Run(); err != nil { + t.Fatal(err) + } req, err := http.NewRequest("GET", "/images/json?all=0", nil) if err != nil { @@ -164,18 +169,18 @@ func TestGetImagesJSON(t *testing.T) { } assertHttpNotError(r, t) - images := []docker.APIImages{} - if err := json.Unmarshal(r.Body.Bytes(), &images); err != nil { + images := engine.NewTable("Created", 0) + if _, err := images.ReadFrom(r.Body); err != nil { t.Fatal(err) } - if len(images) != len(initialImages) { - t.Errorf("Expected %d image, %d found", len(initialImages), len(images)) + if images.Len() != initialImages.Len() { + t.Errorf("Expected %d image, %d found", initialImages.Len(), images.Len()) } found := false - for _, img := range images { - if strings.Contains(img.RepoTags[0], unitTestImageName) { + for _, img := range images.Data { + if strings.Contains(img.GetList("RepoTags")[0], unitTestImageName) { found = true break } @@ -188,10 +193,7 @@ func TestGetImagesJSON(t *testing.T) { // all=1 - initialImages, err = srv.Images(true, "") - if err != nil { - t.Fatal(err) - } + initialImages = getAllImages(eng, t) req2, err := http.NewRequest("GET", "/images/json?all=true", nil) if err != nil { @@ -207,8 +209,8 @@ func TestGetImagesJSON(t *testing.T) { t.Fatal(err) } - if len(images2) != len(initialImages) { - t.Errorf("Expected %d image, %d found", len(initialImages), len(images2)) + if len(images2) != initialImages.Len() { + t.Errorf("Expected %d image, %d found", initialImages.Len(), len(images2)) } found = false @@ -1126,21 +1128,16 @@ func TestDeleteImages(t *testing.T) { defer mkRuntimeFromEngine(eng, t).Nuke() srv := mkServerFromEngine(eng, t) - initialImages, err := srv.Images(false, "") - if err != nil { - t.Fatal(err) - } + initialImages := getImages(eng, t, true, "") if err := eng.Job("tag", unitTestImageName, "test", "test").Run(); err != nil { t.Fatal(err) } - images, err := srv.Images(false, "") - if err != nil { - t.Fatal(err) - } - if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 { - t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images)) + images := getImages(eng, t, true, "") + + if images.Len() != initialImages.Len()+1 { + t.Errorf("Expected %d images, %d found", initialImages.Len()+1, images.Len()) } req, err := http.NewRequest("DELETE", "/images/"+unitTestImageID, nil) @@ -1177,13 +1174,10 @@ func TestDeleteImages(t *testing.T) { if len(outs) != 1 { t.Fatalf("Expected %d event (untagged), got %d", 1, len(outs)) } - images, err = srv.Images(false, "") - if err != nil { - t.Fatal(err) - } + images = getImages(eng, t, false, "") - if len(images[0].RepoTags) != len(initialImages[0].RepoTags) { - t.Errorf("Expected %d image, %d found", len(initialImages), len(images)) + if images.Len() != initialImages.Len() { + t.Errorf("Expected %d image, %d found", initialImages.Len(), images.Len()) } } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index cdd4818934..f3d8384082 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -51,14 +51,17 @@ func cleanup(eng *engine.Engine, t *testing.T) error { container.Kill() runtime.Destroy(container) } - srv := mkServerFromEngine(eng, t) - images, err := srv.Images(true, "") + job := eng.Job("images") + images, err := job.Stdout.AddTable() if err != nil { - return err + t.Fatal(err) } - for _, image := range images { - if image.ID != unitTestImageID { - srv.ImageDelete(image.ID, false) + if err := job.Run(); err != nil { + t.Fatal(err) + } + for _, image := range images.Data { + if image.Get("ID") != unitTestImageID { + mkServerFromEngine(eng, t).ImageDelete(image.Get("ID"), false) } } return nil @@ -158,7 +161,7 @@ func spawnGlobalDaemon() { Host: testDaemonAddr, } job := eng.Job("serveapi", listenURL.String()) - job.SetenvBool("Logging", os.Getenv("DEBUG") != "") + job.SetenvBool("Logging", true) if err := job.Run(); err != nil { log.Fatalf("Unable to spawn the test daemon: %s", err) } diff --git a/integration/server_test.go b/integration/server_test.go index a29c2acbf3..106bff9c2b 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -14,10 +14,7 @@ func TestImageTagImageDelete(t *testing.T) { srv := mkServerFromEngine(eng, t) - initialImages, err := srv.Images(false, "") - if err != nil { - t.Fatal(err) - } + initialImages := getAllImages(eng, t) if err := eng.Job("tag", unitTestImageName, "utest", "tag1").Run(); err != nil { t.Fatal(err) } @@ -30,52 +27,43 @@ func TestImageTagImageDelete(t *testing.T) { t.Fatal(err) } - images, err := srv.Images(false, "") - if err != nil { - t.Fatal(err) - } + images := getAllImages(eng, t) - if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+3 { - t.Errorf("Expected %d images, %d found", len(initialImages)+3, len(images)) + nExpected := len(initialImages.Data[0].GetList("RepoTags")) + 3 + nActual := len(images.Data[0].GetList("RepoTags")) + if nExpected != nActual { + t.Errorf("Expected %d images, %d found", nExpected, nActual) } if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil { t.Fatal(err) } - images, err = srv.Images(false, "") - if err != nil { - t.Fatal(err) - } + images = getAllImages(eng, t) - if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+2 { - t.Errorf("Expected %d images, %d found", len(initialImages)+2, len(images)) + nExpected = len(initialImages.Data[0].GetList("RepoTags")) + 2 + nActual = len(images.Data[0].GetList("RepoTags")) + if nExpected != nActual { + t.Errorf("Expected %d images, %d found", nExpected, nActual) } if _, err := srv.ImageDelete("utest:5000/docker:tag3", true); err != nil { t.Fatal(err) } - images, err = srv.Images(false, "") - if err != nil { - t.Fatal(err) - } + images = getAllImages(eng, t) - if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 { - t.Errorf("Expected %d images, %d found", len(initialImages)+1, len(images)) - } + nExpected = len(initialImages.Data[0].GetList("RepoTags")) + 1 + nActual = len(images.Data[0].GetList("RepoTags")) if _, err := srv.ImageDelete("utest:tag1", true); err != nil { t.Fatal(err) } - images, err = srv.Images(false, "") - if err != nil { - t.Fatal(err) - } + images = getAllImages(eng, t) - if len(images) != len(initialImages) { - t.Errorf("Expected %d image, %d found", len(initialImages), len(images)) + if images.Len() != initialImages.Len() { + t.Errorf("Expected %d image, %d found", initialImages.Len(), images.Len()) } } @@ -250,10 +238,7 @@ func TestRmi(t *testing.T) { srv := mkServerFromEngine(eng, t) defer mkRuntimeFromEngine(eng, t).Nuke() - initialImages, err := srv.Images(false, "") - if err != nil { - t.Fatal(err) - } + initialImages := getAllImages(eng, t) config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo", "test"}, nil) if err != nil { @@ -308,13 +293,10 @@ func TestRmi(t *testing.T) { t.Fatal(err) } - images, err := srv.Images(false, "") - if err != nil { - t.Fatal(err) - } + images := getAllImages(eng, t) - if len(images)-len(initialImages) != 2 { - t.Fatalf("Expected 2 new images, found %d.", len(images)-len(initialImages)) + if images.Len()-initialImages.Len() != 2 { + t.Fatalf("Expected 2 new images, found %d.", images.Len()-initialImages.Len()) } _, err = srv.ImageDelete(imageID, true) @@ -322,20 +304,17 @@ func TestRmi(t *testing.T) { t.Fatal(err) } - images, err = srv.Images(false, "") - if err != nil { - t.Fatal(err) + images = getAllImages(eng, t) + + if images.Len()-initialImages.Len() != 1 { + t.Fatalf("Expected 1 new image, found %d.", images.Len()-initialImages.Len()) } - if len(images)-len(initialImages) != 1 { - t.Fatalf("Expected 1 new image, found %d.", len(images)-len(initialImages)) - } - - for _, image := range images { - if strings.Contains(unitTestImageID, image.ID) { + for _, image := range images.Data { + if strings.Contains(unitTestImageID, image.Get("ID")) { continue } - if image.RepoTags[0] == ":" { + if image.GetList("RepoTags")[0] == ":" { t.Fatalf("Expected tagged image, got untagged one.") } } @@ -359,39 +338,27 @@ func TestImagesFilter(t *testing.T) { t.Fatal(err) } - images, err := srv.Images(false, "utest*/*") - if err != nil { - t.Fatal(err) - } + images := getImages(eng, t, false, "utest*/*") - if len(images[0].RepoTags) != 2 { + if len(images.Data[0].GetList("RepoTags")) != 2 { t.Fatal("incorrect number of matches returned") } - images, err = srv.Images(false, "utest") - if err != nil { - t.Fatal(err) - } + images = getImages(eng, t, false, "utest") - if len(images[0].RepoTags) != 1 { + if len(images.Data[0].GetList("RepoTags")) != 1 { t.Fatal("incorrect number of matches returned") } - images, err = srv.Images(false, "utest*") - if err != nil { - t.Fatal(err) - } + images = getImages(eng, t, false, "utest*") - if len(images[0].RepoTags) != 1 { + if len(images.Data[0].GetList("RepoTags")) != 1 { t.Fatal("incorrect number of matches returned") } - images, err = srv.Images(false, "*5000*/*") - if err != nil { - t.Fatal(err) - } + images = getImages(eng, t, false, "*5000*/*") - if len(images[0].RepoTags) != 1 { + if len(images.Data[0].GetList("RepoTags")) != 1 { t.Fatal("incorrect number of matches returned") } } diff --git a/integration/sorter_test.go b/integration/sorter_test.go index 77848c7ddf..1c089a2997 100644 --- a/integration/sorter_test.go +++ b/integration/sorter_test.go @@ -17,13 +17,10 @@ func TestServerListOrderedImagesByCreationDate(t *testing.T) { t.Fatal(err) } - images, err := srv.Images(true, "") - if err != nil { - t.Fatal(err) - } + images := getImages(eng, t, true, "") - if images[0].Created < images[1].Created { - t.Error("Expected []APIImges to be ordered by most recent creation date.") + if images.Data[0].GetInt("Created") < images.Data[1].GetInt("Created") { + t.Error("Expected images to be ordered by most recent creation date.") } } @@ -44,12 +41,9 @@ func TestServerListOrderedImagesByCreationDateAndTag(t *testing.T) { t.Fatal(err) } - images, err := srv.Images(true, "") - if err != nil { - t.Fatal(err) - } + images := getImages(eng, t, true, "") - if images[0].RepoTags[0] != "repo:zed" && images[0].RepoTags[0] != "repo:bar" { + if images.Data[0].GetList("RepoTags")[0] != "repo:zed" && images.Data[0].GetList("RepoTags")[0] != "repo:bar" { t.Errorf("Expected []APIImges to be ordered by most recent creation date. %s", images) } } diff --git a/integration/utils_test.go b/integration/utils_test.go index 85ba13d698..4ab1c96cca 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -186,8 +186,6 @@ func NewTestEngine(t utils.Fataler) *engine.Engine { if err != nil { t.Fatal(err) } - eng.Stdout = ioutil.Discard - eng.Stderr = ioutil.Discard // Load default plugins // (This is manually copied and modified from main() until we have a more generic plugin system) job := eng.Job("initapi") @@ -329,3 +327,22 @@ func fakeTar() (io.Reader, error) { tw.Close() return buf, nil } + +func getAllImages(eng *engine.Engine, t *testing.T) *engine.Table { + return getImages(eng, t, true, "") +} + +func getImages(eng *engine.Engine, t *testing.T, all bool, filter string) *engine.Table { + job := eng.Job("images") + job.SetenvBool("all", all) + job.Setenv("filter", filter) + images, err := job.Stdout.AddTable() + if err != nil { + t.Fatal(err) + } + if err := job.Run(); err != nil { + t.Fatal(err) + } + return images + +} diff --git a/server.go b/server.go index c1fda6aeb9..556b342c16 100644 --- a/server.go +++ b/server.go @@ -127,6 +127,10 @@ func jobInitApi(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + if err := job.Eng.Register("images", srv.Images); err != nil { + job.Error(err) + return engine.StatusErr + } return engine.StatusOK } @@ -568,23 +572,26 @@ func (srv *Server) ImagesViz(out io.Writer) error { return nil } -func (srv *Server) Images(all bool, filter string) ([]APIImages, error) { +func (srv *Server) Images(job *engine.Job) engine.Status { + fmt.Printf("Images()\n") + srv.Eng.Job("version").Run() var ( allImages map[string]*Image err error ) - if all { + if job.GetenvBool("all") { allImages, err = srv.runtime.graph.Map() } else { allImages, err = srv.runtime.graph.Heads() } if err != nil { - return nil, err + job.Errorf("%s", err) + return engine.StatusErr } - lookup := make(map[string]APIImages) + lookup := make(map[string]*engine.Env) for name, repository := range srv.runtime.repositories.Repositories { - if filter != "" { - if match, _ := path.Match(filter, name); !match { + if job.Getenv("filter") != "" { + if match, _ := path.Match(job.Getenv("filter"), name); !match { continue } } @@ -596,48 +603,51 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) { } if out, exists := lookup[id]; exists { - out.RepoTags = append(out.RepoTags, fmt.Sprintf("%s:%s", name, tag)) - - lookup[id] = out + repotag := fmt.Sprintf("%s:%s", name, tag) + out.SetList("RepoTags", append(out.GetList("RepoTags"), repotag)) } else { - var out APIImages - + out := &engine.Env{} delete(allImages, id) - - out.ParentId = image.Parent - out.RepoTags = []string{fmt.Sprintf("%s:%s", name, tag)} - out.ID = image.ID - out.Created = image.Created.Unix() - out.Size = image.Size - out.VirtualSize = image.getParentsSize(0) + image.Size - + out.Set("ParentId", image.Parent) + out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)}) + out.Set("ID", image.ID) + out.SetInt64("Created", image.Created.Unix()) + out.SetInt64("Size", image.Size) + out.SetInt64("VirtualSize", image.getParentsSize(0)+image.Size) lookup[id] = out } } } - outs := make([]APIImages, 0, len(lookup)) + outs := engine.NewTable("Created", len(lookup)) for _, value := range lookup { - outs = append(outs, value) + outs.Add(value) } // Display images which aren't part of a repository/tag - if filter == "" { + if job.Getenv("filter") == "" { for _, image := range allImages { - var out APIImages - out.ID = image.ID - out.ParentId = image.Parent - out.RepoTags = []string{":"} - out.Created = image.Created.Unix() - out.Size = image.Size - out.VirtualSize = image.getParentsSize(0) + image.Size - outs = append(outs, out) + out := &engine.Env{} + out.Set("ParentId", image.Parent) + out.SetList("RepoTags", []string{":"}) + out.Set("ID", image.ID) + out.SetInt64("Created", image.Created.Unix()) + out.SetInt64("Size", image.Size) + out.SetInt64("VirtualSize", image.getParentsSize(0)+image.Size) + outs.Add(out) } } - sortImagesByCreationAndTag(outs) - return outs, nil + outs.Sort() + job.Logf("Sending %d images to stdout", outs.Len()) + if n, err := outs.WriteTo(job.Stdout); err != nil { + job.Errorf("%s", err) + return engine.StatusErr + } else { + job.Logf("%d bytes sent", n) + } + return engine.StatusOK } func (srv *Server) DockerInfo(job *engine.Job) engine.Status { From 3a610f754f425ea6042c3f8b5452273656a06c90 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 13 Dec 2013 10:26:00 -0800 Subject: [PATCH 027/364] Add compat 1.8 Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 7 +-- commands.go | 105 +++++++++++++++++++------------------ engine/env.go | 39 ++++++++++++-- integration/api_test.go | 40 +++++--------- integration/server_test.go | 2 - integration/sorter_test.go | 4 +- server.go | 14 ++--- 7 files changed, 112 insertions(+), 99 deletions(-) diff --git a/api.go b/api.go index 10dab5fae2..7e874c9f12 100644 --- a/api.go +++ b/api.go @@ -28,7 +28,7 @@ import ( ) const ( - APIVERSION = 1.8 + APIVERSION = 1.9 DEFAULTHTTPHOST = "127.0.0.1" DEFAULTHTTPPORT = 4243 DEFAULTUNIXSOCKET = "/var/run/docker.sock" @@ -181,18 +181,15 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http. if err := parseForm(r); err != nil { return err } - fmt.Printf("getImagesJSON\n") job := srv.Eng.Job("images") job.Setenv("filter", r.Form.Get("filter")) job.Setenv("all", r.Form.Get("all")) - // FIXME: 1.7 clients expect a single json list + job.SetenvBool("list", version <= 1.8) job.Stdout.Add(w) w.WriteHeader(http.StatusOK) - fmt.Printf("running images job\n") if err := job.Run(); err != nil { return err } - fmt.Printf("job has been run\n") return nil } diff --git a/commands.go b/commands.go index fd231abeea..40c450928c 100644 --- a/commands.go +++ b/commands.go @@ -1137,36 +1137,38 @@ func (cli *DockerCli) CmdImages(args ...string) error { return err } - var outs []APIImages - if err := json.Unmarshal(body, &outs); err != nil { + outs := engine.NewTable("Created", 0) + + if _, err := outs.ReadFrom(bytes.NewReader(body)); err != nil { return err } var ( - printNode func(cli *DockerCli, noTrunc bool, image APIImages, prefix string) - startImage APIImages + printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string) + startImage *engine.Env - roots []APIImages - byParent = make(map[string][]APIImages) + roots = engine.NewTable("Created", outs.Len()) + byParent = make(map[string]*engine.Table) ) - for _, image := range outs { - if image.ParentId == "" { - roots = append(roots, image) + for _, image := range outs.Data { + if image.Get("ParentId") == "" { + roots.Add(image) } else { - if children, exists := byParent[image.ParentId]; exists { - byParent[image.ParentId] = append(children, image) + if children, exists := byParent[image.Get("ParentId")]; exists { + children.Add(image) } else { - byParent[image.ParentId] = []APIImages{image} + byParent[image.Get("ParentId")] = engine.NewTable("Created", 1) + byParent[image.Get("ParentId")].Add(image) } } if filter != "" { - if filter == image.ID || filter == utils.TruncateID(image.ID) { + if filter == image.Get("ID") || filter == utils.TruncateID(image.Get("ID")) { startImage = image } - for _, repotag := range image.RepoTags { + for _, repotag := range image.GetList("RepoTags") { if repotag == filter { startImage = image } @@ -1181,10 +1183,12 @@ func (cli *DockerCli) CmdImages(args ...string) error { printNode = (*DockerCli).printTreeNode } - if startImage.ID != "" { - cli.WalkTree(*noTrunc, &[]APIImages{startImage}, byParent, "", printNode) + if startImage != nil { + root := engine.NewTable("Created", 1) + root.Add(startImage) + cli.WalkTree(*noTrunc, root, byParent, "", printNode) } else if filter == "" { - cli.WalkTree(*noTrunc, &roots, byParent, "", printNode) + cli.WalkTree(*noTrunc, roots, byParent, "", printNode) } if *flViz { fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") @@ -1203,9 +1207,8 @@ func (cli *DockerCli) CmdImages(args ...string) error { return err } - var outs []APIImages - err = json.Unmarshal(body, &outs) - if err != nil { + outs := engine.NewTable("Created", 0) + if _, err := outs.ReadFrom(bytes.NewReader(body)); err != nil { return err } @@ -1214,19 +1217,19 @@ func (cli *DockerCli) CmdImages(args ...string) error { fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE") } - for _, out := range outs { - for _, repotag := range out.RepoTags { + for _, out := range outs.Data { + for _, repotag := range out.GetList("RepoTags") { repo, tag := utils.ParseRepositoryTag(repotag) - + outID := out.Get("ID") if !*noTrunc { - out.ID = utils.TruncateID(out.ID) + outID = utils.TruncateID(outID) } if !*quiet { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, out.ID, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.Created, 0))), utils.HumanSize(out.VirtualSize)) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), utils.HumanSize(out.GetInt64("VirtualSize"))) } else { - fmt.Fprintln(w, out.ID) + fmt.Fprintln(w, outID) } } } @@ -1238,66 +1241,66 @@ func (cli *DockerCli) CmdImages(args ...string) error { return nil } -func (cli *DockerCli) WalkTree(noTrunc bool, images *[]APIImages, byParent map[string][]APIImages, prefix string, printNode func(cli *DockerCli, noTrunc bool, image APIImages, prefix string)) { - length := len(*images) +func (cli *DockerCli) WalkTree(noTrunc bool, images *engine.Table, byParent map[string]*engine.Table, prefix string, printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string)) { + length := images.Len() if length > 1 { - for index, image := range *images { + for index, image := range images.Data { if index+1 == length { printNode(cli, noTrunc, image, prefix+"└─") - if subimages, exists := byParent[image.ID]; exists { - cli.WalkTree(noTrunc, &subimages, byParent, prefix+" ", printNode) + if subimages, exists := byParent[image.Get("ID")]; exists { + cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) } } else { - printNode(cli, noTrunc, image, prefix+"├─") - if subimages, exists := byParent[image.ID]; exists { - cli.WalkTree(noTrunc, &subimages, byParent, prefix+"│ ", printNode) + printNode(cli, noTrunc, image, prefix+"\u251C─") + if subimages, exists := byParent[image.Get("ID")]; exists { + cli.WalkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) } } } } else { - for _, image := range *images { + for _, image := range images.Data { printNode(cli, noTrunc, image, prefix+"└─") - if subimages, exists := byParent[image.ID]; exists { - cli.WalkTree(noTrunc, &subimages, byParent, prefix+" ", printNode) + if subimages, exists := byParent[image.Get("ID")]; exists { + cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) } } } } -func (cli *DockerCli) printVizNode(noTrunc bool, image APIImages, prefix string) { +func (cli *DockerCli) printVizNode(noTrunc bool, image *engine.Env, prefix string) { var ( imageID string parentID string ) if noTrunc { - imageID = image.ID - parentID = image.ParentId + imageID = image.Get("ID") + parentID = image.Get("ParentId") } else { - imageID = utils.TruncateID(image.ID) - parentID = utils.TruncateID(image.ParentId) + imageID = utils.TruncateID(image.Get("ID")) + parentID = utils.TruncateID(image.Get("ParentId")) } - if image.ParentId == "" { + if parentID == "" { fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", imageID) } else { fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", parentID, imageID) } - if image.RepoTags[0] != ":" { + if image.GetList("RepoTags")[0] != ":" { fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", - imageID, imageID, strings.Join(image.RepoTags, "\\n")) + imageID, imageID, strings.Join(image.GetList("RepoTags"), "\\n")) } } -func (cli *DockerCli) printTreeNode(noTrunc bool, image APIImages, prefix string) { +func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix string) { var imageID string if noTrunc { - imageID = image.ID + imageID = image.Get("ID") } else { - imageID = utils.TruncateID(image.ID) + imageID = utils.TruncateID(image.Get("ID")) } - fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, utils.HumanSize(image.VirtualSize)) - if image.RepoTags[0] != ":" { - fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.RepoTags, ", ")) + fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, utils.HumanSize(image.GetInt64("VirtualSize"))) + if image.GetList("RepoTags")[0] != ":" { + fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.GetList("RepoTags"), ", ")) } else { fmt.Fprint(cli.out, "\n") } diff --git a/engine/env.go b/engine/env.go index f4cc124240..0593bfa012 100644 --- a/engine/env.go +++ b/engine/env.go @@ -237,15 +237,21 @@ func (env *Env) Map() map[string]string { type Table struct { Data []*Env sortKey string + Chan chan *Env } func NewTable(sortKey string, sizeHint int) *Table { return &Table{ make([]*Env, 0, sizeHint), sortKey, + make(chan *Env), } } +func (t *Table) SetKey(sortKey string) { + t.sortKey = sortKey +} + func (t *Table) Add(env *Env) { t.Data = append(t.Data, env) } @@ -279,16 +285,41 @@ func (t *Table) Sort() { sort.Sort(t) } +func (t *Table) ReverseSort() { + sort.Sort(sort.Reverse(t)) +} + +func (t *Table) WriteListTo(dst io.Writer) (n int64, err error) { + if _, err := dst.Write([]byte{'['}); err != nil { + return -1, err + } + n = 1 + for i, env := range t.Data { + bytes, err := env.WriteTo(dst) + if err != nil { + return -1, err + } + n += bytes + if i != len(t.Data)-1 { + if _, err := dst.Write([]byte{','}); err != nil { + return -1, err + } + n += 1 + } + } + if _, err := dst.Write([]byte{']'}); err != nil { + return -1, err + } + return n + 1, nil +} + func (t *Table) WriteTo(dst io.Writer) (n int64, err error) { for _, env := range t.Data { bytes, err := env.WriteTo(dst) if err != nil { return -1, err } - if _, err := dst.Write([]byte{'\n'}); err != nil { - return -1, err - } - n += bytes + 1 + n += bytes } return n, nil } diff --git a/integration/api_test.go b/integration/api_test.go index de54078dea..47d63efb1d 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -204,18 +204,18 @@ func TestGetImagesJSON(t *testing.T) { } assertHttpNotError(r2, t) - images2 := []docker.APIImages{} - if err := json.Unmarshal(r2.Body.Bytes(), &images2); err != nil { + images2 := engine.NewTable("ID", 0) + if _, err := images2.ReadFrom(r2.Body); err != nil { t.Fatal(err) } - if len(images2) != initialImages.Len() { - t.Errorf("Expected %d image, %d found", initialImages.Len(), len(images2)) + if images2.Len() != initialImages.Len() { + t.Errorf("Expected %d image, %d found", initialImages.Len(), images2.Len()) } found = false - for _, img := range images2 { - if img.ID == unitTestImageID { + for _, img := range images2.Data { + if img.Get("ID") == unitTestImageID { found = true break } @@ -237,29 +237,13 @@ func TestGetImagesJSON(t *testing.T) { } assertHttpNotError(r3, t) - images3 := []docker.APIImages{} - if err := json.Unmarshal(r3.Body.Bytes(), &images3); err != nil { + images3 := engine.NewTable("ID", 0) + if _, err := images3.ReadFrom(r3.Body); err != nil { t.Fatal(err) } - if len(images3) != 0 { - t.Errorf("Expected 0 image, %d found", len(images3)) - } - - r4 := httptest.NewRecorder() - - // all=foobar - req4, err := http.NewRequest("GET", "/images/json?all=foobar", nil) - if err != nil { - t.Fatal(err) - } - - if err := docker.ServeRequest(srv, docker.APIVERSION, r4, req4); err != nil { - t.Fatal(err) - } - // Don't assert against HTTP error since we expect an error - if r4.Code != http.StatusBadRequest { - t.Fatalf("%d Bad Request expected, received %d\n", http.StatusBadRequest, r4.Code) + if images3.Len() != 0 { + t.Errorf("Expected 0 image, %d found", images3.Len()) } } @@ -1136,8 +1120,8 @@ func TestDeleteImages(t *testing.T) { images := getImages(eng, t, true, "") - if images.Len() != initialImages.Len()+1 { - t.Errorf("Expected %d images, %d found", initialImages.Len()+1, images.Len()) + if len(images.Data[0].GetList("RepoTags")) != len(initialImages.Data[0].GetList("RepoTags"))+1 { + t.Errorf("Expected %d images, %d found", len(initialImages.Data[0].GetList("RepoTags"))+1, len(images.Data[0].GetList("RepoTags"))) } req, err := http.NewRequest("DELETE", "/images/"+unitTestImageID, nil) diff --git a/integration/server_test.go b/integration/server_test.go index 106bff9c2b..e755d14728 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -324,8 +324,6 @@ func TestImagesFilter(t *testing.T) { eng := NewTestEngine(t) defer nuke(mkRuntimeFromEngine(eng, t)) - srv := mkServerFromEngine(eng, t) - if err := eng.Job("tag", unitTestImageName, "utest", "tag1").Run(); err != nil { t.Fatal(err) } diff --git a/integration/sorter_test.go b/integration/sorter_test.go index 1c089a2997..02d08d3409 100644 --- a/integration/sorter_test.go +++ b/integration/sorter_test.go @@ -43,8 +43,8 @@ func TestServerListOrderedImagesByCreationDateAndTag(t *testing.T) { images := getImages(eng, t, true, "") - if images.Data[0].GetList("RepoTags")[0] != "repo:zed" && images.Data[0].GetList("RepoTags")[0] != "repo:bar" { - t.Errorf("Expected []APIImges to be ordered by most recent creation date. %s", images) + if repoTags := images.Data[0].GetList("RepoTags"); repoTags[0] != "repo:zed" && repoTags[0] != "repo:bar" { + t.Errorf("Expected Images to be ordered by most recent creation date.") } } diff --git a/server.go b/server.go index 556b342c16..a0c545fbc4 100644 --- a/server.go +++ b/server.go @@ -573,8 +573,6 @@ func (srv *Server) ImagesViz(out io.Writer) error { } func (srv *Server) Images(job *engine.Job) engine.Status { - fmt.Printf("Images()\n") - srv.Eng.Job("version").Run() var ( allImages map[string]*Image err error @@ -639,13 +637,15 @@ func (srv *Server) Images(job *engine.Job) engine.Status { } } - outs.Sort() - job.Logf("Sending %d images to stdout", outs.Len()) - if n, err := outs.WriteTo(job.Stdout); err != nil { + outs.ReverseSort() + if job.GetenvBool("list") { + if _, err := outs.WriteListTo(job.Stdout); err != nil { + job.Errorf("%s", err) + return engine.StatusErr + } + } else if _, err := outs.WriteTo(job.Stdout); err != nil { job.Errorf("%s", err) return engine.StatusErr - } else { - job.Logf("%d bytes sent", n) } return engine.StatusOK } From aca1270104802347f5aa3104959282924b631daa Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 13 Jan 2014 11:20:33 -0800 Subject: [PATCH 028/364] add legacy compat Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 1 + api_params.go | 36 ------------------------------------ server.go | 31 +++++++++++++++++++++++++------ sorter.go | 33 --------------------------------- 4 files changed, 26 insertions(+), 75 deletions(-) diff --git a/api.go b/api.go index 7e874c9f12..3bf19fd948 100644 --- a/api.go +++ b/api.go @@ -185,6 +185,7 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http. job.Setenv("filter", r.Form.Get("filter")) job.Setenv("all", r.Form.Get("all")) job.SetenvBool("list", version <= 1.8) + job.SetenvBool("legacy", version <= 1.7) job.Stdout.Add(w) w.WriteHeader(http.StatusOK) if err := job.Run(); err != nil { diff --git a/api_params.go b/api_params.go index fa9eab0c15..cfe80c8b0b 100644 --- a/api_params.go +++ b/api_params.go @@ -1,7 +1,5 @@ package docker -import "strings" - type ( APIHistory struct { ID string `json:"Id"` @@ -11,24 +9,6 @@ type ( Size int64 } - APIImages struct { - ID string `json:"Id"` - RepoTags []string `json:",omitempty"` - Created int64 - Size int64 - VirtualSize int64 - ParentId string `json:",omitempty"` - } - - APIImagesOld struct { - Repository string `json:",omitempty"` - Tag string `json:",omitempty"` - ID string `json:"Id"` - Created int64 - Size int64 - VirtualSize int64 - } - APITop struct { Titles []string Processes [][]string @@ -101,22 +81,6 @@ type ( } ) -func (api APIImages) ToLegacy() []APIImagesOld { - outs := []APIImagesOld{} - for _, repotag := range api.RepoTags { - components := strings.SplitN(repotag, ":", 2) - outs = append(outs, APIImagesOld{ - ID: api.ID, - Repository: components[0], - Tag: components[1], - Created: api.Created, - Size: api.Size, - VirtualSize: api.VirtualSize, - }) - } - return outs -} - func (api APIContainers) ToLegacy() *APIContainersOld { return &APIContainersOld{ ID: api.ID, diff --git a/server.go b/server.go index a0c545fbc4..18bda2abd2 100644 --- a/server.go +++ b/server.go @@ -601,13 +601,27 @@ func (srv *Server) Images(job *engine.Job) engine.Status { } if out, exists := lookup[id]; exists { - repotag := fmt.Sprintf("%s:%s", name, tag) - out.SetList("RepoTags", append(out.GetList("RepoTags"), repotag)) + if job.GetenvBool("legacy") { + out2 := &engine.Env{} + out2.Set("Repository", name) + out2.Set("Tag", tag) + out2.Set("ID", out.Get("ID")) + out2.SetInt64("Created", out.GetInt64("Created")) + out2.SetInt64("Size", out.GetInt64("Size")) + out2.SetInt64("VirtualSize", out.GetInt64("VirtualSize")) + } else { + out.SetList("RepoTags", append(out.GetList("RepoTags"), fmt.Sprintf("%s:%s", name, tag))) + } } else { out := &engine.Env{} delete(allImages, id) - out.Set("ParentId", image.Parent) - out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)}) + if job.GetenvBool("legacy") { + out.Set("Repository", name) + out.Set("Tag", tag) + } else { + out.Set("ParentId", image.Parent) + out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)}) + } out.Set("ID", image.ID) out.SetInt64("Created", image.Created.Unix()) out.SetInt64("Size", image.Size) @@ -627,8 +641,13 @@ func (srv *Server) Images(job *engine.Job) engine.Status { if job.Getenv("filter") == "" { for _, image := range allImages { out := &engine.Env{} - out.Set("ParentId", image.Parent) - out.SetList("RepoTags", []string{":"}) + if job.GetenvBool("legacy") { + out.Set("Repository", "") + out.Set("Tag", "") + } else { + out.Set("ParentId", image.Parent) + out.SetList("RepoTags", []string{":"}) + } out.Set("ID", image.ID) out.SetInt64("Created", image.Created.Unix()) out.SetInt64("Size", image.Size) diff --git a/sorter.go b/sorter.go index c9a86b45c0..9b3e1a9486 100644 --- a/sorter.go +++ b/sorter.go @@ -2,39 +2,6 @@ package docker import "sort" -type imageSorter struct { - images []APIImages - by func(i1, i2 *APIImages) bool // Closure used in the Less method. -} - -// Len is part of sort.Interface. -func (s *imageSorter) Len() int { - return len(s.images) -} - -// Swap is part of sort.Interface. -func (s *imageSorter) Swap(i, j int) { - s.images[i], s.images[j] = s.images[j], s.images[i] -} - -// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter. -func (s *imageSorter) Less(i, j int) bool { - return s.by(&s.images[i], &s.images[j]) -} - -// Sort []ApiImages by most recent creation date and tag name. -func sortImagesByCreationAndTag(images []APIImages) { - creationAndTag := func(i1, i2 *APIImages) bool { - return i1.Created > i2.Created - } - - sorter := &imageSorter{ - images: images, - by: creationAndTag} - - sort.Sort(sorter) -} - type portSorter struct { ports []Port by func(i, j Port) bool From 16ca6a1c12ffe9a02da4e823646bee6461ffbad5 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 13 Jan 2014 14:55:31 -0800 Subject: [PATCH 029/364] move legacy stuff outside the job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 49 +++++++++++++++++-- engine/table_test.go | 4 +- registry/registry.go | 2 +- server.go | 37 +++----------- .../src/github.com/gorilla/context/context.go | 2 +- vendor/src/github.com/gorilla/mux/old_test.go | 8 +-- 6 files changed, 58 insertions(+), 44 deletions(-) diff --git a/api.go b/api.go index 3bf19fd948..c8bd7368b4 100644 --- a/api.go +++ b/api.go @@ -10,6 +10,7 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" + "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/pkg/systemd" "github.com/dotcloud/docker/utils" "github.com/gorilla/mux" @@ -181,16 +182,54 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http. if err := parseForm(r); err != nil { return err } - job := srv.Eng.Job("images") + + var ( + buffer *bytes.Buffer + job = srv.Eng.Job("images") + ) + job.Setenv("filter", r.Form.Get("filter")) job.Setenv("all", r.Form.Get("all")) - job.SetenvBool("list", version <= 1.8) - job.SetenvBool("legacy", version <= 1.7) - job.Stdout.Add(w) - w.WriteHeader(http.StatusOK) + + if version >= 1.9 { + job.Stdout.Add(w) + } else { + buffer = bytes.NewBuffer(nil) + job.Stdout.Add(buffer) + } + if err := job.Run(); err != nil { return err } + + if version < 1.9 { // Send as a valide JSON array + outs := engine.NewTable("Created", 0) + if _, err := outs.ReadFrom(buffer); err != nil { + return err + } + if version < 1.8 { // Convert to legacy format + outsLegacy := engine.NewTable("Created", 0) + for _, out := range outs.Data { + for _, repoTag := range out.GetList("RepoTags") { + parts := strings.Split(repoTag, ":") + outLegacy := &engine.Env{} + outLegacy.Set("Repository", parts[0]) + outLegacy.Set("Tag", parts[1]) + outLegacy.Set("ID", out.Get("ID")) + outLegacy.SetInt64("Created", out.GetInt64("Created")) + outLegacy.SetInt64("Size", out.GetInt64("Size")) + outLegacy.SetInt64("VirtualSize", out.GetInt64("VirtualSize")) + outsLegacy.Add(outLegacy) + } + } + if _, err := outsLegacy.WriteListTo(w); err != nil { + return err + } + } else if _, err := outs.WriteListTo(w); err != nil { + return err + } + } + return nil } diff --git a/engine/table_test.go b/engine/table_test.go index 0a81d690eb..3e8e4ff1b3 100644 --- a/engine/table_test.go +++ b/engine/table_test.go @@ -1,9 +1,9 @@ package engine import ( - "testing" "bytes" "encoding/json" + "testing" ) func TestTableWriteTo(t *testing.T) { @@ -19,7 +19,7 @@ func TestTableWriteTo(t *testing.T) { if err := json.Unmarshal(buf.Bytes(), &output); err != nil { t.Fatal(err) } - if len(output) != 1 { + if len(output) != 1 { t.Fatalf("Incorrect output: %v", output) } if val, exists := output["foo"]; !exists || val != "bar" { diff --git a/registry/registry.go b/registry/registry.go index a038fdfb66..a0da733ed9 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -59,7 +59,7 @@ func pingRegistryEndpoint(endpoint string) (bool, error) { // versions of the registry if standalone == "" { return true, nil - // Accepted values are "true" (case-insensitive) and "1". + // Accepted values are "true" (case-insensitive) and "1". } else if strings.EqualFold(standalone, "true") || standalone == "1" { return true, nil } diff --git a/server.go b/server.go index 18bda2abd2..0c7f1a2680 100644 --- a/server.go +++ b/server.go @@ -601,27 +601,12 @@ func (srv *Server) Images(job *engine.Job) engine.Status { } if out, exists := lookup[id]; exists { - if job.GetenvBool("legacy") { - out2 := &engine.Env{} - out2.Set("Repository", name) - out2.Set("Tag", tag) - out2.Set("ID", out.Get("ID")) - out2.SetInt64("Created", out.GetInt64("Created")) - out2.SetInt64("Size", out.GetInt64("Size")) - out2.SetInt64("VirtualSize", out.GetInt64("VirtualSize")) - } else { - out.SetList("RepoTags", append(out.GetList("RepoTags"), fmt.Sprintf("%s:%s", name, tag))) - } + out.SetList("RepoTags", append(out.GetList("RepoTags"), fmt.Sprintf("%s:%s", name, tag))) } else { out := &engine.Env{} delete(allImages, id) - if job.GetenvBool("legacy") { - out.Set("Repository", name) - out.Set("Tag", tag) - } else { - out.Set("ParentId", image.Parent) - out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)}) - } + out.Set("ParentId", image.Parent) + out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)}) out.Set("ID", image.ID) out.SetInt64("Created", image.Created.Unix()) out.SetInt64("Size", image.Size) @@ -641,13 +626,8 @@ func (srv *Server) Images(job *engine.Job) engine.Status { if job.Getenv("filter") == "" { for _, image := range allImages { out := &engine.Env{} - if job.GetenvBool("legacy") { - out.Set("Repository", "") - out.Set("Tag", "") - } else { - out.Set("ParentId", image.Parent) - out.SetList("RepoTags", []string{":"}) - } + out.Set("ParentId", image.Parent) + out.SetList("RepoTags", []string{":"}) out.Set("ID", image.ID) out.SetInt64("Created", image.Created.Unix()) out.SetInt64("Size", image.Size) @@ -657,12 +637,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { } outs.ReverseSort() - if job.GetenvBool("list") { - if _, err := outs.WriteListTo(job.Stdout); err != nil { - job.Errorf("%s", err) - return engine.StatusErr - } - } else if _, err := outs.WriteTo(job.Stdout); err != nil { + if _, err := outs.WriteTo(job.Stdout); err != nil { job.Errorf("%s", err) return engine.StatusErr } diff --git a/vendor/src/github.com/gorilla/context/context.go b/vendor/src/github.com/gorilla/context/context.go index 35d65561f3..12accb114a 100644 --- a/vendor/src/github.com/gorilla/context/context.go +++ b/vendor/src/github.com/gorilla/context/context.go @@ -92,7 +92,7 @@ func Purge(maxAge int) int { datat = make(map[*http.Request]int64) } else { min := time.Now().Unix() - int64(maxAge) - for r, _ := range data { + for r := range data { if datat[r] < min { clear(r) count++ diff --git a/vendor/src/github.com/gorilla/mux/old_test.go b/vendor/src/github.com/gorilla/mux/old_test.go index 7e266bb695..42530590e7 100644 --- a/vendor/src/github.com/gorilla/mux/old_test.go +++ b/vendor/src/github.com/gorilla/mux/old_test.go @@ -96,8 +96,8 @@ func TestRouteMatchers(t *testing.T) { method = "GET" headers = map[string]string{"X-Requested-With": "XMLHttpRequest"} resultVars = map[bool]map[string]string{ - true: map[string]string{"var1": "www", "var2": "product", "var3": "42"}, - false: map[string]string{}, + true: {"var1": "www", "var2": "product", "var3": "42"}, + false: {}, } } @@ -110,8 +110,8 @@ func TestRouteMatchers(t *testing.T) { method = "POST" headers = map[string]string{"Content-Type": "application/json"} resultVars = map[bool]map[string]string{ - true: map[string]string{"var4": "google", "var5": "product", "var6": "42"}, - false: map[string]string{}, + true: {"var4": "google", "var5": "product", "var6": "42"}, + false: {}, } } From a92699ab6c0f93ee155b44145790dfeaf54cf1c8 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Mon, 13 Jan 2014 17:12:53 -0800 Subject: [PATCH 030/364] Fixes 3497 Docker-DCO-1.1-Signed-off-by: Andy Rothfusz (github: @metalivedev) --- NOTICE | 2 +- docs/sources/conf.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/NOTICE b/NOTICE index fb6810bc28..d0e0639a5a 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Docker -Copyright 2012-2013 Docker, Inc. +Copyright 2012-2014 Docker, Inc. This product includes software developed at Docker, Inc. (http://www.docker.com). diff --git a/docs/sources/conf.py b/docs/sources/conf.py index a143e821be..c1480eb0c2 100644 --- a/docs/sources/conf.py +++ b/docs/sources/conf.py @@ -62,7 +62,7 @@ master_doc = 'toctree' # General information about the project. project = u'Docker' -copyright = u'2013, Team Docker' +copyright = u'2014 Docker, Inc.' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -175,7 +175,7 @@ html_show_sourcelink = False #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the From 4087a643eaf6265f8d238caa2883348eeb5e908a Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 13 Jan 2014 18:22:23 -0700 Subject: [PATCH 031/364] Fix docs Dockerfile to install correctly again Previously we've been getting: ``` Step 6 : RUN pip install Sphinx==1.1.3 ---> Running in 397eab32f57a Wheel installs require setuptools >= 0.8 for dist-info support. pip's wheel support requires setuptools >= 0.8 for dist-info support. Storing debug log for failure in /.pip/pip.log 2014/01/13 18:01:34 The command [/bin/sh -c pip install Sphinx==1.1.3] returned a non-zero code: 1 make: *** [docs] Error 1 ``` This fixes that by telling pip not to use sudo to install (since we're already root). Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- docs/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index 53a5dfba9c..7d9ce4244c 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -8,8 +8,8 @@ run apt-get update run apt-get install -y python-setuptools make run easy_install pip #from docs/requirements.txt, but here to increase cacheability -run pip install Sphinx==1.1.3 -run pip install sphinxcontrib-httpdomain==1.1.9 +run pip install --no-use-wheel Sphinx==1.1.3 +run pip install --no-use-wheel sphinxcontrib-httpdomain==1.1.9 add . /docs run cd /docs; make docs From 658f57fdfa488968cc63d866b9e3689345a3f199 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 13 Jan 2014 23:32:21 -0700 Subject: [PATCH 032/364] Remove reference to nonexistent CONFIG_UID_NS Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- docs/sources/installation/kernel.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/sources/installation/kernel.rst b/docs/sources/installation/kernel.rst index 8338cfdc88..88822ad37d 100644 --- a/docs/sources/installation/kernel.rst +++ b/docs/sources/installation/kernel.rst @@ -136,7 +136,6 @@ Namespaces: - CONFIG_NAMESPACES - CONFIG_UTS_NS - CONFIG_IPC_NS -- CONFIG_UID_NS - CONFIG_PID_NS - CONFIG_NET_NS From 710d5a48fb751623fbf77a51b89f2dfbf0edac68 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 20 Dec 2013 11:30:45 +0100 Subject: [PATCH 033/364] archive: Extract createTarFile() from ApplyLayer This way we can reuse it for Untar() Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- archive/archive.go | 79 ++++++++++++++++++++++++++++++++++++++++++ archive/diff.go | 86 ++++------------------------------------------ 2 files changed, 85 insertions(+), 80 deletions(-) diff --git a/archive/archive.go b/archive/archive.go index 4dd5f006ef..8fa6f55333 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -13,6 +13,7 @@ import ( "os/exec" "path" "path/filepath" + "syscall" ) type Archive io.Reader @@ -124,6 +125,84 @@ func (compression *Compression) Extension() string { return "" } +func createTarFile(path, extractDir string, hdr *tar.Header, reader *tar.Reader) error { + switch hdr.Typeflag { + case tar.TypeDir: + // Create directory unless it exists as a directory already. + // In that case we just want to merge the two + if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) { + if err := os.Mkdir(path, os.FileMode(hdr.Mode)); err != nil { + return err + } + } + + case tar.TypeReg, tar.TypeRegA: + // Source is regular file + file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, os.FileMode(hdr.Mode)) + if err != nil { + return err + } + if _, err := io.Copy(file, reader); err != nil { + file.Close() + return err + } + file.Close() + + case tar.TypeBlock, tar.TypeChar, tar.TypeFifo: + mode := uint32(hdr.Mode & 07777) + switch hdr.Typeflag { + case tar.TypeBlock: + mode |= syscall.S_IFBLK + case tar.TypeChar: + mode |= syscall.S_IFCHR + case tar.TypeFifo: + mode |= syscall.S_IFIFO + } + + if err := syscall.Mknod(path, mode, int(mkdev(hdr.Devmajor, hdr.Devminor))); err != nil { + return err + } + + case tar.TypeLink: + if err := os.Link(filepath.Join(extractDir, hdr.Linkname), path); err != nil { + return err + } + + case tar.TypeSymlink: + if err := os.Symlink(hdr.Linkname, path); err != nil { + return err + } + + default: + return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag) + } + + if err := syscall.Lchown(path, hdr.Uid, hdr.Gid); err != nil { + return err + } + + // There is no LChmod, so ignore mode for symlink. Also, this + // must happen after chown, as that can modify the file mode + if hdr.Typeflag != tar.TypeSymlink { + if err := syscall.Chmod(path, uint32(hdr.Mode&07777)); err != nil { + return err + } + } + + ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} + // syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and + if hdr.Typeflag != tar.TypeSymlink { + if err := syscall.UtimesNano(path, ts); err != nil { + return err + } + } else { + if err := LUtimesNano(path, ts); err != nil { + return err + } + } + return nil +} + // Tar creates an archive from the directory at `path`, and returns it as a // stream of bytes. func Tar(path string, compression Compression) (io.Reader, error) { diff --git a/archive/diff.go b/archive/diff.go index 464d57a742..cdf06dd055 100644 --- a/archive/diff.go +++ b/archive/diff.go @@ -2,7 +2,6 @@ package archive import ( "archive/tar" - "github.com/dotcloud/docker/utils" "io" "os" "path/filepath" @@ -89,95 +88,22 @@ func ApplyLayer(dest string, layer Archive) error { // The only exception is when it is a directory *and* the file from // the layer is also a directory. Then we want to merge them (i.e. // just apply the metadata from the layer). - hasDir := false if fi, err := os.Lstat(path); err == nil { - if fi.IsDir() && hdr.Typeflag == tar.TypeDir { - hasDir = true - } else { + if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { if err := os.RemoveAll(path); err != nil { return err } } } - switch hdr.Typeflag { - case tar.TypeDir: - if !hasDir { - err = os.Mkdir(path, os.FileMode(hdr.Mode)) - if err != nil { - return err - } - } - dirs = append(dirs, hdr) - - case tar.TypeReg, tar.TypeRegA: - // Source is regular file - file, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, os.FileMode(hdr.Mode)) - if err != nil { - return err - } - if _, err := io.Copy(file, tr); err != nil { - file.Close() - return err - } - file.Close() - - case tar.TypeBlock, tar.TypeChar, tar.TypeFifo: - mode := uint32(hdr.Mode & 07777) - switch hdr.Typeflag { - case tar.TypeBlock: - mode |= syscall.S_IFBLK - case tar.TypeChar: - mode |= syscall.S_IFCHR - case tar.TypeFifo: - mode |= syscall.S_IFIFO - } - - if err := syscall.Mknod(path, mode, int(mkdev(hdr.Devmajor, hdr.Devminor))); err != nil { - return err - } - - case tar.TypeLink: - if err := os.Link(filepath.Join(dest, hdr.Linkname), path); err != nil { - return err - } - - case tar.TypeSymlink: - if err := os.Symlink(hdr.Linkname, path); err != nil { - return err - } - - default: - utils.Debugf("unhandled type %d\n", hdr.Typeflag) - } - - if err = syscall.Lchown(path, hdr.Uid, hdr.Gid); err != nil { + if err := createTarFile(path, dest, hdr, tr); err != nil { return err } - // There is no LChmod, so ignore mode for symlink. Also, this - // must happen after chown, as that can modify the file mode - if hdr.Typeflag != tar.TypeSymlink { - err = syscall.Chmod(path, uint32(hdr.Mode&07777)) - if err != nil { - return err - } - } - - // Directories must be handled at the end to avoid further - // file creation in them to modify the mtime - if hdr.Typeflag != tar.TypeDir { - ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} - // syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and - if hdr.Typeflag != tar.TypeSymlink { - if err := syscall.UtimesNano(path, ts); err != nil { - return err - } - } else { - if err := LUtimesNano(path, ts); err != nil { - return err - } - } + // Directory mtimes must be handled at the end to avoid further + // file creation in them to modify the directory mtime + if hdr.Typeflag == tar.TypeDir { + dirs = append(dirs, hdr) } } } From a4868e233c7ebce259fc02d3dbfb241c23471a4a Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 20 Dec 2013 12:08:34 +0100 Subject: [PATCH 034/364] Implement UnTar via archive/tar This replaces the shelling out to tar with a reimplementation of untar based on the archive/tar code and the pre-existing code from ApplyLayer to create real files from tar headers. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- archive/archive.go | 98 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 25 deletions(-) diff --git a/archive/archive.go b/archive/archive.go index 8fa6f55333..89a360c906 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -13,6 +13,7 @@ import ( "os/exec" "path" "path/filepath" + "strings" "syscall" ) @@ -285,45 +286,92 @@ func TarFilter(path string, options *TarOptions) (io.Reader, error) { // The archive may be compressed with one of the following algorithms: // identity (uncompressed), gzip, bzip2, xz. // FIXME: specify behavior when target path exists vs. doesn't exist. -func Untar(archive io.Reader, path string, options *TarOptions) error { +func Untar(archive io.Reader, dest string, options *TarOptions) error { if archive == nil { return fmt.Errorf("Empty archive") } - buf := make([]byte, 10) - totalN := 0 - for totalN < 10 { - n, err := archive.Read(buf[totalN:]) + archive, err := DecompressStream(archive) + if err != nil { + return err + } + + tr := tar.NewReader(archive) + + var dirs []*tar.Header + + // Iterate through the files in the archive. + for { + hdr, err := tr.Next() + if err == io.EOF { + // end of tar archive + break + } if err != nil { - if err == io.EOF { - return fmt.Errorf("Tarball too short") - } return err } - totalN += n - utils.Debugf("[tar autodetect] n: %d", n) - } - compression := DetectCompression(buf) + if options != nil { + excludeFile := false + for _, exclude := range options.Excludes { + if strings.HasPrefix(hdr.Name, exclude) { + excludeFile = true + break + } + } + if excludeFile { + continue + } + } - utils.Debugf("Archive compression detected: %s", compression.Extension()) - args := []string{"--numeric-owner", "-f", "-", "-C", path, "-x" + compression.Flag()} + // Normalize name, for safety and for a simple is-root check + hdr.Name = filepath.Clean(hdr.Name) - if options != nil { - for _, exclude := range options.Excludes { - args = append(args, fmt.Sprintf("--exclude=%s", exclude)) + if !strings.HasSuffix(hdr.Name, "/") { + // Not the root directory, ensure that the parent directory exists + parent := filepath.Dir(hdr.Name) + parentPath := filepath.Join(dest, parent) + if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { + err = os.MkdirAll(parentPath, 600) + if err != nil { + return err + } + } + } + + path := filepath.Join(dest, hdr.Name) + + // If path exits we almost always just want to remove and replace it + // The only exception is when it is a directory *and* the file from + // the layer is also a directory. Then we want to merge them (i.e. + // just apply the metadata from the layer). + if fi, err := os.Lstat(path); err == nil { + if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { + if err := os.RemoveAll(path); err != nil { + return err + } + } + } + + if err := createTarFile(path, dest, hdr, tr); err != nil { + return err + } + + // Directory mtimes must be handled at the end to avoid further + // file creation in them to modify the directory mtime + if hdr.Typeflag == tar.TypeDir { + dirs = append(dirs, hdr) } } - cmd := exec.Command("tar", args...) - cmd.Stdin = io.MultiReader(bytes.NewReader(buf), archive) - // Hardcode locale environment for predictable outcome regardless of host configuration. - // (see https://github.com/dotcloud/docker/issues/355) - cmd.Env = []string{"LANG=en_US.utf-8", "LC_ALL=en_US.utf-8"} - output, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("%s: %s", err, output) + for _, hdr := range dirs { + path := filepath.Join(dest, hdr.Name) + ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} + if err := syscall.UtimesNano(path, ts); err != nil { + return err + } } + return nil } From c7dcd19b28c069a396b3b9688886f9c88730e86d Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 14 Jan 2014 04:18:31 -0700 Subject: [PATCH 035/364] Fix docs typo: "Gento" -> "Gentoo" Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- docs/sources/faq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/faq.rst b/docs/sources/faq.rst index e2e16c362b..426eb9641e 100644 --- a/docs/sources/faq.rst +++ b/docs/sources/faq.rst @@ -172,7 +172,7 @@ Linux: - Fedora 19/20+ - RHEL 6.5+ - Centos 6+ -- Gento +- Gentoo - ArchLinux Cloud: From 5a3fa3c901bfc226456f3caa13d26b77e5ca2f4d Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 14 Jan 2014 04:19:42 -0700 Subject: [PATCH 036/364] Add a few small tweaks to mkimage-debootstrap, especially to help with lucid Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- contrib/mkimage-debootstrap.sh | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/contrib/mkimage-debootstrap.sh b/contrib/mkimage-debootstrap.sh index 3f268b52da..e3e88d8c9c 100755 --- a/contrib/mkimage-debootstrap.sh +++ b/contrib/mkimage-debootstrap.sh @@ -117,6 +117,11 @@ target="/tmp/docker-rootfs-debootstrap-$suite-$$-$RANDOM" cd "$(dirname "$(readlink -f "$BASH_SOURCE")")" returnTo="$(pwd -P)" +if [ "$suite" = 'lucid' ]; then + # lucid fails and doesn't include gpgv in minbase; "apt-get update" fails + include+=',gpgv' +fi + set -x # bootstrap @@ -138,18 +143,26 @@ if [ -z "$strictDebootstrap" ]; then # shrink the image, since apt makes us fat (wheezy: ~157.5MB vs ~120MB) sudo chroot . apt-get clean - # while we're at it, apt is unnecessarily slow inside containers - # this forces dpkg not to call sync() after package extraction and speeds up install - # the benefit is huge on spinning disks, and the penalty is nonexistent on SSD or decent server virtualization - echo 'force-unsafe-io' | sudo tee etc/dpkg/dpkg.cfg.d/02apt-speedup > /dev/null - # we want to effectively run "apt-get clean" after every install to keep images small (see output of "apt-get clean -s" for context) + if strings usr/bin/dpkg | grep -q unsafe-io; then + # while we're at it, apt is unnecessarily slow inside containers + # this forces dpkg not to call sync() after package extraction and speeds up install + # the benefit is huge on spinning disks, and the penalty is nonexistent on SSD or decent server virtualization + echo 'force-unsafe-io' | sudo tee etc/dpkg/dpkg.cfg.d/02apt-speedup > /dev/null + # we have this wrapped up in an "if" because the "force-unsafe-io" + # option was added in dpkg 1.15.8.6 + # (see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=584254#82), + # and ubuntu lucid/10.04 only has 1.15.5.6 + fi + + # we want to effectively run "apt-get clean" after every install to keep images small (see output of "apt-get clean -s" for context) { aptGetClean='"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true";' echo "DPkg::Post-Invoke { ${aptGetClean} };" echo "APT::Update::Post-Invoke { ${aptGetClean} };" echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";' } | sudo tee etc/apt/apt.conf.d/no-cache > /dev/null - # and remove the translations, too + + # and remove the translations, too echo 'Acquire::Languages "none";' | sudo tee etc/apt/apt.conf.d/no-languages > /dev/null # helpful undo lines for each the above tweaks (for lack of a better home to keep track of them): @@ -190,6 +203,9 @@ if [ -z "$strictDebootstrap" ]; then ;; esac fi + + # make sure our packages lists are as up to date as we can get them + sudo chroot . apt-get update fi if [ "$justTar" ]; then From 3a1ce011a02ee42441b895b1f636c9e7363e2625 Mon Sep 17 00:00:00 2001 From: Flavio Castelli Date: Tue, 14 Jan 2014 12:04:37 +0100 Subject: [PATCH 037/364] Added document explaining openSUSE installation Added a new documentation page explaining how to install Docker on openSUSE. Docker-DCO-1.1-Signed-off-by: Flavio Castelli (github: flavio) --- docs/sources/faq.rst | 1 + docs/sources/installation/index.rst | 1 + docs/sources/installation/openSUSE.rst | 73 ++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 docs/sources/installation/openSUSE.rst diff --git a/docs/sources/faq.rst b/docs/sources/faq.rst index e2e16c362b..a5534e5969 100644 --- a/docs/sources/faq.rst +++ b/docs/sources/faq.rst @@ -174,6 +174,7 @@ Linux: - Centos 6+ - Gento - ArchLinux +- openSUSE 12.3+ Cloud: diff --git a/docs/sources/installation/index.rst b/docs/sources/installation/index.rst index 9026b1f7f4..9ebe484f6e 100644 --- a/docs/sources/installation/index.rst +++ b/docs/sources/installation/index.rst @@ -22,6 +22,7 @@ Contents: fedora archlinux gentoolinux + openSUSE frugalware vagrant windows diff --git a/docs/sources/installation/openSUSE.rst b/docs/sources/installation/openSUSE.rst new file mode 100644 index 0000000000..ded5de44a4 --- /dev/null +++ b/docs/sources/installation/openSUSE.rst @@ -0,0 +1,73 @@ +:title: Installation on openSUSE +:description: Docker installation on openSUSE. +:keywords: openSUSE, virtualbox, docker, documentation, installation + +.. _openSUSE: + +openSUSE +======== + +.. include:: install_header.inc + +.. include:: install_unofficial.inc + +Docker is available in **openSUSE 12.3 and later**. Please note that due to the +current Docker limitations Docker is able to run only on the **64 bit** +architecture. + +Installation +------------ + +The ``docker`` package from the `Virtualization project`_ on `OBS`_ provides +Docker on openSUSE. + + +To proceed with Docker installation please add the right Virtualization +repository. + +.. code-block:: bash + + # openSUSE 12.3 + sudo zypper ar -f http://download.opensuse.org/repositories/Virtualization/openSUSE_12.3/ Virtualization + + # openSUSE 13.1 + sudo zypper ar -f http://download.opensuse.org/repositories/Virtualization/openSUSE_13.1/ Virtualization + + +Install the Docker package. + +.. code-block:: bash + + sudo zypper in docker + +It's also possible to install Docker using openSUSE's 1-click install. Just +visit `this`_ page, select your openSUSE version and click on the installation +link. This will add the right repository to your system and it will +also install the `docker` package. + +Now that it's installed, let's start the Docker daemon. + +.. code-block:: bash + + sudo systemctl start docker + +If we want Docker to start at boot, we should also: + +.. code-block:: bash + + sudo systemctl enable docker + +The `docker` package creates a new group named `docker`. Users, other than +`root` user, need to be part of this group in order to interact with the +Docker daemon. + +.. code-block:: bash + + sudo usermod -G docker + + +**Done!**, now continue with the :ref:`hello_world` example. + +.. _Virtualization project: https://build.opensuse.org/project/show/Virtualization +.. _OBS: https://build.opensuse.org/ +.. _this: http://software.opensuse.org/package/docker From e8ffc2eee00beaa8456d69853ad9ca3db29ea013 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 14 Jan 2014 10:54:19 -0700 Subject: [PATCH 038/364] Un-gofmt ./vendor We can't be modifying this except to update from upstream. Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- vendor/src/github.com/gorilla/context/context.go | 2 +- vendor/src/github.com/gorilla/mux/old_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/vendor/src/github.com/gorilla/context/context.go b/vendor/src/github.com/gorilla/context/context.go index 12accb114a..35d65561f3 100644 --- a/vendor/src/github.com/gorilla/context/context.go +++ b/vendor/src/github.com/gorilla/context/context.go @@ -92,7 +92,7 @@ func Purge(maxAge int) int { datat = make(map[*http.Request]int64) } else { min := time.Now().Unix() - int64(maxAge) - for r := range data { + for r, _ := range data { if datat[r] < min { clear(r) count++ diff --git a/vendor/src/github.com/gorilla/mux/old_test.go b/vendor/src/github.com/gorilla/mux/old_test.go index 42530590e7..7e266bb695 100644 --- a/vendor/src/github.com/gorilla/mux/old_test.go +++ b/vendor/src/github.com/gorilla/mux/old_test.go @@ -96,8 +96,8 @@ func TestRouteMatchers(t *testing.T) { method = "GET" headers = map[string]string{"X-Requested-With": "XMLHttpRequest"} resultVars = map[bool]map[string]string{ - true: {"var1": "www", "var2": "product", "var3": "42"}, - false: {}, + true: map[string]string{"var1": "www", "var2": "product", "var3": "42"}, + false: map[string]string{}, } } @@ -110,8 +110,8 @@ func TestRouteMatchers(t *testing.T) { method = "POST" headers = map[string]string{"Content-Type": "application/json"} resultVars = map[bool]map[string]string{ - true: {"var4": "google", "var5": "product", "var6": "42"}, - false: {}, + true: map[string]string{"var4": "google", "var5": "product", "var6": "42"}, + false: map[string]string{}, } } From f700aa15705c418d6523b7ecb7c6e7413deaed13 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 14 Jan 2014 11:03:45 -0700 Subject: [PATCH 039/364] Fix Travis to not check gofmt on vendor/* Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/travis/gofmt.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hack/travis/gofmt.py b/hack/travis/gofmt.py index 1bb062e2f6..dc724bc90e 100755 --- a/hack/travis/gofmt.py +++ b/hack/travis/gofmt.py @@ -11,6 +11,9 @@ files = subprocess.check_output([ exit_status = 0 for filename in files.split('\n'): + if filename.startswith('vendor/'): + continue # we can't be changing our upstream vendors for gofmt, so don't even check them + if filename.endswith('.go'): try: out = subprocess.check_output(['gofmt', '-s', '-l', filename]) From 32565a4451c9c7f929a37d61ded70fc1de7fe993 Mon Sep 17 00:00:00 2001 From: Fernando Date: Wed, 8 Jan 2014 18:38:59 +0100 Subject: [PATCH 040/364] added capabilities needed by new sysinit Docker-DCO-1.1-Signed-off-by: Fernando Mayo (github: fermayo) --- contrib/mkseccomp.pl | 2 +- contrib/mkseccomp.sample | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/mkseccomp.pl b/contrib/mkseccomp.pl index 44088f952c..5c583cc3d3 100755 --- a/contrib/mkseccomp.pl +++ b/contrib/mkseccomp.pl @@ -41,7 +41,7 @@ use warnings; if( -t ) { print STDERR "Helper script to make seccomp filters for Docker/LXC.\n"; - print STDERR "Usage: mkseccomp.pl [files...]\n"; + print STDERR "Usage: mkseccomp.pl < [files...]\n"; exit 1; } diff --git a/contrib/mkseccomp.sample b/contrib/mkseccomp.sample index 25bf4822dc..7a0c8d1925 100644 --- a/contrib/mkseccomp.sample +++ b/contrib/mkseccomp.sample @@ -195,6 +195,7 @@ shutdown socket // (*) socketcall socketpair +sethostname // (*) // Signal related pause @@ -261,7 +262,7 @@ vmsplice // Process control capget -//capset +capset // (*) clone // (*) execve // (*) exit // (*) @@ -401,7 +402,6 @@ tkill //quotactl //reboot //setdomainname -//sethostname //setns //settimeofday //sgetmask // Obsolete From 7a6255efbcb83458ca179b2148fda7a0160a4bd7 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 14 Jan 2014 11:42:03 -0700 Subject: [PATCH 041/364] Fix "foo: no such file or directory" test failure, and normalize creation of custom error to always depend on if os.IsNotExist(err) so we don't hide other errors that might crop up in these tests Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- buildfile.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/buildfile.go b/buildfile.go index 6b568d7563..2b6d40c15d 100644 --- a/buildfile.go +++ b/buildfile.go @@ -288,6 +288,9 @@ func (b *buildFile) CmdVolume(args string) error { func (b *buildFile) checkPathForAddition(orig string) error { origPath := path.Join(b.contextPath, orig) if p, err := filepath.EvalSymlinks(origPath); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("%s: no such file or directory", orig) + } return err } else { origPath = p @@ -297,7 +300,10 @@ func (b *buildFile) checkPathForAddition(orig string) error { } _, err := os.Stat(origPath) if err != nil { - return fmt.Errorf("%s: no such file or directory", orig) + if os.IsNotExist(err) { + return fmt.Errorf("%s: no such file or directory", orig) + } + return err } return nil } @@ -313,7 +319,10 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error { } fi, err := os.Stat(origPath) if err != nil { - return fmt.Errorf("%s: no such file or directory", orig) + if os.IsNotExist(err) { + return fmt.Errorf("%s: no such file or directory", orig) + } + return err } if fi.IsDir() { if err := archive.CopyWithTar(origPath, destPath); err != nil { From bea6dd3888adefd070544241177860864f62fb25 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 13 Jan 2014 16:50:10 -0800 Subject: [PATCH 042/364] move history to a job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 30 +- commands.go | 24 +- docs/sources/api/docker_remote_api.rst | 23 +- docs/sources/api/docker_remote_api_v1.9.rst | 1277 +++++++++++++++++++ server.go | 38 +- 5 files changed, 1360 insertions(+), 32 deletions(-) create mode 100644 docs/sources/api/docker_remote_api_v1.9.rst diff --git a/api.go b/api.go index c8bd7368b4..bb076034e4 100644 --- a/api.go +++ b/api.go @@ -202,7 +202,7 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http. return err } - if version < 1.9 { // Send as a valide JSON array + if version < 1.9 { // Send as a valid JSON array outs := engine.NewTable("Created", 0) if _, err := outs.ReadFrom(buffer); err != nil { return err @@ -313,13 +313,31 @@ func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *ht if vars == nil { return fmt.Errorf("Missing parameter") } - name := vars["name"] - outs, err := srv.ImageHistory(name) - if err != nil { + + var ( + buffer *bytes.Buffer + job = srv.Eng.Job("history", vars["name"]) + ) + + if version >= 1.9 { + job.Stdout.Add(w) + } else { + buffer = bytes.NewBuffer(nil) + job.Stdout.Add(buffer) + } + if err := job.Run(); err != nil { return err } - - return writeJSON(w, http.StatusOK, outs) + if version < 1.9 { // Send as a valid JSON array + outs := engine.NewTable("Created", 0) + if _, err := outs.ReadFrom(buffer); err != nil { + return err + } + if _, err := outs.WriteListTo(w); err != nil { + return err + } + } + return nil } func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/commands.go b/commands.go index 40c450928c..454a02c797 100644 --- a/commands.go +++ b/commands.go @@ -862,9 +862,8 @@ func (cli *DockerCli) CmdHistory(args ...string) error { return err } - var outs []APIHistory - err = json.Unmarshal(body, &outs) - if err != nil { + outs := engine.NewTable("Created", 0) + if _, err := outs.ReadFrom(bytes.NewReader(body)); err != nil { return err } @@ -873,27 +872,28 @@ func (cli *DockerCli) CmdHistory(args ...string) error { fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE") } - for _, out := range outs { + for _, out := range outs.Data { + outID := out.Get("ID") if !*quiet { if *noTrunc { - fmt.Fprintf(w, "%s\t", out.ID) + fmt.Fprintf(w, "%s\t", outID) } else { - fmt.Fprintf(w, "%s\t", utils.TruncateID(out.ID)) + fmt.Fprintf(w, "%s\t", utils.TruncateID(outID)) } - fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.Created, 0)))) + fmt.Fprintf(w, "%s ago\t", utils.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0)))) if *noTrunc { - fmt.Fprintf(w, "%s\t", out.CreatedBy) + fmt.Fprintf(w, "%s\t", out.Get("CreatedBy")) } else { - fmt.Fprintf(w, "%s\t", utils.Trunc(out.CreatedBy, 45)) + fmt.Fprintf(w, "%s\t", utils.Trunc(out.Get("CreatedBy"), 45)) } - fmt.Fprintf(w, "%s\n", utils.HumanSize(out.Size)) + fmt.Fprintf(w, "%s\n", utils.HumanSize(out.GetInt64("Size"))) } else { if *noTrunc { - fmt.Fprintln(w, out.ID) + fmt.Fprintln(w, outID) } else { - fmt.Fprintln(w, utils.TruncateID(out.ID)) + fmt.Fprintln(w, utils.TruncateID(outID)) } } } diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 84b340f9a3..bcac603717 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -26,15 +26,34 @@ Docker Remote API 2. Versions =========== -The current version of the API is 1.8 +The current version of the API is 1.9 Calling /images//insert is the same as calling -/v1.8/images//insert +/v1.9/images//insert You can still call an old version of the api using /v1.0/images//insert +v1.9 +**** + +Full Documentation +------------------ + +:doc:`docker_remote_api_v1.9` + +What's new +---------- + +.. http:get:: /images/json + + **New!** This endpoint now returns a list of json message, like the events endpoint + +.. http:get:: /images/(name)/history + + **New!** This endpoint now returns a list of json message, like the events endpoint + v1.8 **** diff --git a/docs/sources/api/docker_remote_api_v1.9.rst b/docs/sources/api/docker_remote_api_v1.9.rst new file mode 100644 index 0000000000..a7a9ef53cb --- /dev/null +++ b/docs/sources/api/docker_remote_api_v1.9.rst @@ -0,0 +1,1277 @@ +:title: Remote API v1.9 +:description: API Documentation for Docker +:keywords: API, Docker, rcli, REST, documentation + +:orphan: + +====================== +Docker Remote API v1.9 +====================== + +.. contents:: Table of Contents + +1. Brief introduction +===================== + +- The Remote API has replaced rcli +- The daemon listens on ``unix:///var/run/docker.sock``, but you can + :ref:`bind_docker`. +- The API tends to be REST, but for some complex commands, like + ``attach`` or ``pull``, the HTTP connection is hijacked to transport + ``stdout, stdin`` and ``stderr`` + +2. Endpoints +============ + +2.1 Containers +-------------- + +List containers +*************** + +.. http:get:: /containers/json + + List containers + + **Example request**: + + .. sourcecode:: http + + GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id": "8dfafdbc3a40", + "Image": "base:latest", + "Command": "echo 1", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "9cd87474be90", + "Image": "base:latest", + "Command": "echo 222222", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "3176a2479c92", + "Image": "base:latest", + "Command": "echo 3333333333333333", + "Created": 1367854154, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "4cb07b47f9fb", + "Image": "base:latest", + "Command": "echo 444444444444444444444444444444444", + "Created": 1367854152, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + } + ] + + :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default + :query limit: Show ``limit`` last created containers, include non-running ones. + :query since: Show only containers created since Id, include non-running ones. + :query before: Show only containers created before Id, include non-running ones. + :query size: 1/True/true or 0/False/false, Show the containers sizes + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 500: server error + + +Create a container +****************** + +.. http:post:: /containers/create + + Create a container + + **Example request**: + + .. sourcecode:: http + + POST /containers/create HTTP/1.1 + Content-Type: application/json + + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":true, + "AttachStderr":true, + "PortSpecs":null, + "Tty":false, + "OpenStdin":false, + "StdinOnce":false, + "Env":null, + "Cmd":[ + "date" + ], + "Dns":null, + "Image":"base", + "Volumes":{ + "/tmp": {} + }, + "VolumesFrom":"", + "WorkingDir":"", + "ExposedPorts":{ + "22/tcp": {} + } + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 201 OK + Content-Type: application/json + + { + "Id":"e90e34656806" + "Warnings":[] + } + + :jsonparam config: the container's configuration + :query name: Assign the specified name to the container. Must match ``/?[a-zA-Z0-9_-]+``. + :statuscode 201: no error + :statuscode 404: no such container + :statuscode 406: impossible to attach (container not running) + :statuscode 500: server error + + +Inspect a container +******************* + +.. http:get:: /containers/(id)/json + + Return low-level information on the container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/json HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", + "Created": "2013-05-07T14:51:42.041847+02:00", + "Path": "date", + "Args": [], + "Config": { + "Hostname": "4fa6e0f0c678", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "PortSpecs": null, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Dns": null, + "Image": "base", + "Volumes": {}, + "VolumesFrom": "", + "WorkingDir":"" + + }, + "State": { + "Running": false, + "Pid": 0, + "ExitCode": 0, + "StartedAt": "2013-05-07T14:51:42.087658+02:01360", + "Ghost": false + }, + "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "NetworkSettings": { + "IpAddress": "", + "IpPrefixLen": 0, + "Gateway": "", + "Bridge": "", + "PortMapping": null + }, + "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", + "ResolvConfPath": "/etc/resolv.conf", + "Volumes": {}, + "HostConfig": { + "Binds": null, + "ContainerIDFile": "", + "LxcConf": [], + "Privileged": false, + "PortBindings": { + "80/tcp": [ + { + "HostIp": "0.0.0.0", + "HostPort": "49153" + } + ] + }, + "Links": null, + "PublishAllPorts": false + } + } + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +List processes running inside a container +***************************************** + +.. http:get:: /containers/(id)/top + + List processes running inside the container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/top HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Titles":[ + "USER", + "PID", + "%CPU", + "%MEM", + "VSZ", + "RSS", + "TTY", + "STAT", + "START", + "TIME", + "COMMAND" + ], + "Processes":[ + ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"], + ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"] + ] + } + + :query ps_args: ps arguments to use (eg. aux) + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Inspect changes on a container's filesystem +******************************************* + +.. http:get:: /containers/(id)/changes + + Inspect changes on container ``id`` 's filesystem + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/changes HTTP/1.1 + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Path":"/dev", + "Kind":0 + }, + { + "Path":"/dev/kmsg", + "Kind":1 + }, + { + "Path":"/test", + "Kind":1 + } + ] + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Export a container +****************** + +.. http:get:: /containers/(id)/export + + Export the contents of container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/export HTTP/1.1 + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Start a container +***************** + +.. http:post:: /containers/(id)/start + + Start the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/(id)/start HTTP/1.1 + Content-Type: application/json + + { + "Binds":["/tmp:/tmp"], + "LxcConf":{"lxc.utsname":"docker"}, + "PortBindings":{ "22/tcp": [{ "HostPort": "11022" }] }, + "PublishAllPorts":false, + "Privileged":false + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 No Content + Content-Type: text/plain + + :jsonparam hostConfig: the container's host configuration (optional) + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Stop a container +**************** + +.. http:post:: /containers/(id)/stop + + Stop the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/stop?t=5 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Restart a container +******************* + +.. http:post:: /containers/(id)/restart + + Restart the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/restart?t=5 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Kill a container +**************** + +.. http:post:: /containers/(id)/kill + + Kill the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/kill HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Attach to a container +********************* + +.. http:post:: /containers/(id)/attach + + Attach to the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/vnd.docker.raw-stream + + {{ STREAM }} + + :query logs: 1/True/true or 0/False/false, return logs. Default false + :query stream: 1/True/true or 0/False/false, return stream. Default false + :query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false + :query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false + :query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error + + **Stream details**: + + When using the TTY setting is enabled in + :http:post:`/containers/create`, the stream is the raw data + from the process PTY and client's stdin. When the TTY is + disabled, then the stream is multiplexed to separate stdout + and stderr. + + The format is a **Header** and a **Payload** (frame). + + **HEADER** + + The header will contain the information on which stream write + the stream (stdout or stderr). It also contain the size of + the associated frame encoded on the last 4 bytes (uint32). + + It is encoded on the first 8 bytes like this:: + + header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4} + + ``STREAM_TYPE`` can be: + + - 0: stdin (will be writen on stdout) + - 1: stdout + - 2: stderr + + ``SIZE1, SIZE2, SIZE3, SIZE4`` are the 4 bytes of the uint32 size encoded as big endian. + + **PAYLOAD** + + The payload is the raw stream. + + **IMPLEMENTATION** + + The simplest way to implement the Attach protocol is the following: + + 1) Read 8 bytes + 2) chose stdout or stderr depending on the first byte + 3) Extract the frame size from the last 4 byets + 4) Read the extracted size and output it on the correct output + 5) Goto 1) + + + +Wait a container +**************** + +.. http:post:: /containers/(id)/wait + + Block until container ``id`` stops, then returns the exit code + + **Example request**: + + .. sourcecode:: http + + POST /containers/16253994b7c4/wait HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"StatusCode":0} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Remove a container +******************* + +.. http:delete:: /containers/(id) + + Remove the container ``id`` from the filesystem + + **Example request**: + + .. sourcecode:: http + + DELETE /containers/16253994b7c4?v=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false + :statuscode 204: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error + + +Copy files or folders from a container +************************************** + +.. http:post:: /containers/(id)/copy + + Copy files or folders of container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/4fa6e0f0c678/copy HTTP/1.1 + Content-Type: application/json + + { + "Resource":"test.txt" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +2.2 Images +---------- + +List Images +*********** + +.. http:get:: /images/json + + **Example request**: + + .. sourcecode:: http + + GET /images/json?all=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "RepoTags": [ + "ubuntu:12.04", + "ubuntu:precise", + "ubuntu:latest" + ], + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Created": 1365714795, + "Size": 131506275, + "VirtualSize": 131506275 + } + { + "RepoTags": [ + "ubuntu:12.10", + "ubuntu:quantal" + ], + "ParentId": "27cf784147099545", + "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Created": 1364102658, + "Size": 24653, + "VirtualSize": 180116135 + } + + +Create an image +*************** + +.. http:post:: /images/create + + Create an image, either by pull it from the registry or by importing it + + **Example request**: + + .. sourcecode:: http + + POST /images/create?fromImage=base HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Pulling..."} + {"status":"Pulling", "progress":"1 B/ 100 B", "progressDetail":{"current":1, "total":100}} + {"error":"Invalid..."} + ... + + When using this endpoint to pull an image from the registry, + the ``X-Registry-Auth`` header can be used to include a + base64-encoded AuthConfig object. + + :query fromImage: name of the image to pull + :query fromSrc: source to import, - means stdin + :query repo: repository + :query tag: tag + :query registry: the registry to pull from + :reqheader X-Registry-Auth: base64-encoded AuthConfig object + :statuscode 200: no error + :statuscode 500: server error + + + +Insert a file in an image +************************* + +.. http:post:: /images/(name)/insert + + Insert a file from ``url`` in the image ``name`` at ``path`` + + **Example request**: + + .. sourcecode:: http + + POST /images/test/insert?path=/usr&url=myurl HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Inserting..."} + {"status":"Inserting", "progress":"1/? (n/a)", "progressDetail":{"current":1}} + {"error":"Invalid..."} + ... + + :statuscode 200: no error + :statuscode 500: server error + + +Inspect an image +**************** + +.. http:get:: /images/(name)/json + + Return low-level information on the image ``name`` + + **Example request**: + + .. sourcecode:: http + + GET /images/base/json HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "parent":"27cf784147099545", + "created":"2013-03-23T22:24:18.818426-07:00", + "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", + "container_config": + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":false, + "AttachStderr":false, + "PortSpecs":null, + "Tty":true, + "OpenStdin":true, + "StdinOnce":false, + "Env":null, + "Cmd": ["/bin/bash"] + ,"Dns":null, + "Image":"base", + "Volumes":null, + "VolumesFrom":"", + "WorkingDir":"" + }, + "Size": 6824592 + } + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Get the history of an image +*************************** + +.. http:get:: /images/(name)/history + + Return the history of the image ``name`` + + **Example request**: + + .. sourcecode:: http + + GET /images/base/history HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Id":"b750fe79269d", + "Created":1364102658, + "CreatedBy":"/bin/bash" + } + { + "Id":"27cf78414709", + "Created":1364068391, + "CreatedBy":"" + } + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Push an image on the registry +***************************** + +.. http:post:: /images/(name)/push + + Push the image ``name`` on the registry + + **Example request**: + + .. sourcecode:: http + + POST /images/test/push HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Pushing..."} + {"status":"Pushing", "progress":"1/? (n/a)", "progressDetail":{"current":1}}} + {"error":"Invalid..."} + ... + + :query registry: the registry you wan to push, optional + :reqheader X-Registry-Auth: include a base64-encoded AuthConfig object. + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Tag an image into a repository +****************************** + +.. http:post:: /images/(name)/tag + + Tag the image ``name`` into a repository + + **Example request**: + + .. sourcecode:: http + + POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :query repo: The repository to tag in + :query force: 1/True/true or 0/False/false, default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such image + :statuscode 409: conflict + :statuscode 500: server error + + +Remove an image +*************** + +.. http:delete:: /images/(name) + + Remove the image ``name`` from the filesystem + + **Example request**: + + .. sourcecode:: http + + DELETE /images/test HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-type: application/json + + [ + {"Untagged":"3e2f21a89f"}, + {"Deleted":"3e2f21a89f"}, + {"Deleted":"53b4f83ac9"} + ] + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 409: conflict + :statuscode 500: server error + + +Search images +************* + +.. http:get:: /images/search + + Search for an image in the docker index. + + .. note:: + + The response keys have changed from API v1.6 to reflect the JSON + sent by the registry server to the docker daemon's request. + + **Example request**: + + .. sourcecode:: http + + GET /images/search?term=sshd HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "description": "", + "is_official": false, + "is_trusted": false, + "name": "wma55/u1210sshd", + "star_count": 0 + }, + { + "description": "", + "is_official": false, + "is_trusted": false, + "name": "jdswinbank/sshd", + "star_count": 0 + }, + { + "description": "", + "is_official": false, + "is_trusted": false, + "name": "vgauthier/sshd", + "star_count": 0 + } + ... + ] + + :query term: term to search + :statuscode 200: no error + :statuscode 500: server error + + +2.3 Misc +-------- + +Build an image from Dockerfile via stdin +**************************************** + +.. http:post:: /build + + Build an image from Dockerfile via stdin + + **Example request**: + + .. sourcecode:: http + + POST /build HTTP/1.1 + + {{ STREAM }} + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"stream":"Step 1..."} + {"stream":"..."} + {"error":"Error...", "errorDetail":{"code": 123, "message": "Error..."}} + + + The stream must be a tar archive compressed with one of the + following algorithms: identity (no compression), gzip, bzip2, + xz. + + The archive must include a file called ``Dockerfile`` at its + root. It may include any number of other files, which will be + accessible in the build context (See the :ref:`ADD build command + `). + + :query t: repository name (and optionally a tag) to be applied to the resulting image in case of success + :query q: suppress verbose build output + :query nocache: do not use the cache when building the image + :reqheader Content-type: should be set to ``"application/tar"``. + :reqheader X-Registry-Auth: base64-encoded AuthConfig object + :statuscode 200: no error + :statuscode 500: server error + + + +Check auth configuration +************************ + +.. http:post:: /auth + + Get the default username and email + + **Example request**: + + .. sourcecode:: http + + POST /auth HTTP/1.1 + Content-Type: application/json + + { + "username":"hannibal", + "password:"xxxx", + "email":"hannibal@a-team.com", + "serveraddress":"https://index.docker.io/v1/" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :statuscode 204: no error + :statuscode 500: server error + + +Display system-wide information +******************************* + +.. http:get:: /info + + Display system-wide information + + **Example request**: + + .. sourcecode:: http + + GET /info HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Containers":11, + "Images":16, + "Debug":false, + "NFd": 11, + "NGoroutines":21, + "MemoryLimit":true, + "SwapLimit":false, + "IPv4Forwarding":true + } + + :statuscode 200: no error + :statuscode 500: server error + + +Show the docker version information +*********************************** + +.. http:get:: /version + + Show the docker version information + + **Example request**: + + .. sourcecode:: http + + GET /version HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Version":"0.2.2", + "GitCommit":"5a2a5cc+CHANGES", + "GoVersion":"go1.0.3" + } + + :statuscode 200: no error + :statuscode 500: server error + + +Create a new image from a container's changes +********************************************* + +.. http:post:: /commit + + Create a new image from a container's changes + + **Example request**: + + .. sourcecode:: http + + POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 201 OK + Content-Type: application/vnd.docker.raw-stream + + {"Id":"596069db4bf5"} + + :query container: source container + :query repo: repository + :query tag: tag + :query m: commit message + :query author: author (eg. "John Hannibal Smith ") + :query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]}) + :statuscode 201: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Monitor Docker's events +*********************** + +.. http:get:: /events + + Get events from docker, either in real time via streaming, or via polling (using `since`) + + **Example request**: + + .. sourcecode:: http + + GET /events?since=1374067924 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966} + {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970} + + :query since: timestamp used for polling + :statuscode 200: no error + :statuscode 500: server error + +Get a tarball containing all images and tags in a repository +************************************************************ + +.. http:get:: /images/(name)/get + + Get a tarball containing all images and metadata for the repository specified by ``name``. + + **Example request** + + .. sourcecode:: http + + GET /images/ubuntu/get + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/x-tar + + Binary data stream + :statuscode 200: no error + :statuscode 500: server error + +Load a tarball with a set of images and tags into docker +******************************************************** + +.. http:post:: /images/load + + Load a set of images and tags into the docker repository. + + **Example request** + + .. sourcecode:: http + + POST /images/load + + Tarball in body + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :statuscode 500: server error + +3. Going further +================ + +3.1 Inside 'docker run' +----------------------- + +Here are the steps of 'docker run' : + +* Create the container +* If the status code is 404, it means the image doesn't exists: + * Try to pull it + * Then retry to create the container +* Start the container +* If you are not in detached mode: + * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1 +* If in detached mode or only stdin is attached: + * Display the container's id + + +3.2 Hijacking +------------- + +In this version of the API, /attach, uses hijacking to transport stdin, stdout and stderr on the same socket. This might change in the future. + +3.3 CORS Requests +----------------- + +To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. + +.. code-block:: bash + + docker -d -H="192.168.1.9:4243" -api-enable-cors + diff --git a/server.go b/server.go index 0c7f1a2680..ce0b3f91c5 100644 --- a/server.go +++ b/server.go @@ -131,6 +131,10 @@ func jobInitApi(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + if err := job.Eng.Register("history", srv.ImageHistory); err != nil { + job.Error(err) + return engine.StatusErr + } return engine.StatusOK } @@ -695,10 +699,16 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { return engine.StatusOK } -func (srv *Server) ImageHistory(name string) ([]APIHistory, error) { +func (srv *Server) ImageHistory(job *engine.Job) engine.Status { + if n := len(job.Args); n != 1 { + job.Errorf("Usage: %s IMAGE", job.Name) + return engine.StatusErr + } + name := job.Args[0] image, err := srv.runtime.repositories.LookupImage(name) if err != nil { - return nil, err + job.Error(err) + return engine.StatusErr } lookupMap := make(map[string][]string) @@ -712,19 +722,23 @@ func (srv *Server) ImageHistory(name string) ([]APIHistory, error) { } } - outs := []APIHistory{} //produce [] when empty instead of 'null' + outs := engine.NewTable("Created", 0) err = image.WalkHistory(func(img *Image) error { - var out APIHistory - out.ID = img.ID - out.Created = img.Created.Unix() - out.CreatedBy = strings.Join(img.ContainerConfig.Cmd, " ") - out.Tags = lookupMap[img.ID] - out.Size = img.Size - outs = append(outs, out) + out := &engine.Env{} + out.Set("ID", img.ID) + out.SetInt64("Created", img.Created.Unix()) + out.Set("CreatedBy", strings.Join(img.ContainerConfig.Cmd, " ")) + out.SetList("Tags", lookupMap[img.ID]) + out.SetInt64("Size", img.Size) + outs.Add(out) return nil }) - return outs, nil - + outs.ReverseSort() + if _, err := outs.WriteTo(job.Stdout); err != nil { + job.Errorf("%s", err) + return engine.StatusErr + } + return engine.StatusOK } func (srv *Server) ContainerTop(name, psArgs string) (*APITop, error) { From f231539e995a70a49903e9a852535d2c8a8eca43 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 14 Jan 2014 14:28:19 -0800 Subject: [PATCH 043/364] Add remount for bind mounts in ro Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 2 +- mount/mount_test.go | 45 +++++++++++++++++++++++++++++++++++++++++- mount/mounter_linux.go | 10 +++++++++- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/container.go b/container.go index cea316ae5c..9e4495890a 100644 --- a/container.go +++ b/container.go @@ -716,7 +716,7 @@ func (container *Container) Start() (err error) { for r, v := range container.Volumes { mountAs := "ro" - if container.VolumesRW[v] { + if container.VolumesRW[r] { mountAs = "rw" } diff --git a/mount/mount_test.go b/mount/mount_test.go index 5dc9dc256a..6edc31d410 100644 --- a/mount/mount_test.go +++ b/mount/mount_test.go @@ -48,7 +48,7 @@ func TestMounted(t *testing.T) { } f.Close() - if err := Mount(sourcePath, targetPath, "none", "bind,ro"); err != nil { + if err := Mount(sourcePath, targetPath, "none", "bind,rw"); err != nil { t.Fatal(err) } defer func() { @@ -64,4 +64,47 @@ func TestMounted(t *testing.T) { if !mounted { t.Fatalf("Expected %s to be mounted", targetPath) } + if _, err := os.Stat(targetPath); err != nil { + t.Fatal(err) + } +} + +func TestMountReadonly(t *testing.T) { + tmp := path.Join(os.TempDir(), "mount-tests") + if err := os.MkdirAll(tmp, 0777); err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + + var ( + sourcePath = path.Join(tmp, "sourcefile.txt") + targetPath = path.Join(tmp, "targetfile.txt") + ) + + f, err := os.Create(sourcePath) + if err != nil { + t.Fatal(err) + } + f.WriteString("hello") + f.Close() + + f, err = os.Create(targetPath) + if err != nil { + t.Fatal(err) + } + f.Close() + + if err := Mount(sourcePath, targetPath, "none", "bind,ro"); err != nil { + t.Fatal(err) + } + defer func() { + if err := Unmount(targetPath); err != nil { + t.Fatal(err) + } + }() + + f, err = os.OpenFile(targetPath, os.O_RDWR, 0777) + if err == nil { + t.Fatal("Should not be able to open a ro file as rw") + } } diff --git a/mount/mounter_linux.go b/mount/mounter_linux.go index 1371f72bd9..dd4280c777 100644 --- a/mount/mounter_linux.go +++ b/mount/mounter_linux.go @@ -5,7 +5,15 @@ import ( ) func mount(device, target, mType string, flag uintptr, data string) error { - return syscall.Mount(device, target, mType, flag, data) + if err := syscall.Mount(device, target, mType, flag, data); err != nil { + return err + } + + // If we have a bind mount or remount, remount... + if flag&syscall.MS_BIND == syscall.MS_BIND && flag&syscall.MS_RDONLY == syscall.MS_RDONLY { + return syscall.Mount(device, target, mType, flag|syscall.MS_REMOUNT, data) + } + return nil } func unmount(target string, flag int) error { From eaa9c85511620aa82f1876e1503604f591f00d23 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 14 Jan 2014 19:23:30 -0700 Subject: [PATCH 044/364] Fix odd issues with "make docs", add "make docs-shell", and canonicalize our docs Dockerfile a bit more Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- Makefile | 11 ++++++++--- docs/Dockerfile | 27 +++++++++++++-------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index d5253d86ce..275f9dc84c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all binary build cross default docs shell test +.PHONY: all binary build cross default docs docs-build docs-shell shell test GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) DOCKER_IMAGE := docker:$(GIT_BRANCH) @@ -16,10 +16,12 @@ binary: build cross: build $(DOCKER_RUN_DOCKER) hack/make.sh binary cross -docs: - docker build -rm -t "$(DOCKER_DOCS_IMAGE)" docs +docs: docs-build docker run -rm -i -t -p 8000:8000 "$(DOCKER_DOCS_IMAGE)" +docs-shell: docs-build + docker run -rm -i -t -p 8000:8000 "$(DOCKER_DOCS_IMAGE)" bash + test: build $(DOCKER_RUN_DOCKER) hack/make.sh test test-integration @@ -29,5 +31,8 @@ shell: build build: bundles docker build -rm -t "$(DOCKER_IMAGE)" . +docs-build: + docker build -rm -t "$(DOCKER_DOCS_IMAGE)" docs + bundles: mkdir bundles diff --git a/docs/Dockerfile b/docs/Dockerfile index 7d9ce4244c..ae0fa85e0d 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,20 +1,19 @@ -from ubuntu:12.04 -maintainer Nick Stinemates +FROM stackbrew/ubuntu:12.04 +MAINTAINER Nick Stinemates # # docker build -t docker:docs . && docker run -p 8000:8000 docker:docs # -run apt-get update -run apt-get install -y python-setuptools make -run easy_install pip -#from docs/requirements.txt, but here to increase cacheability -run pip install --no-use-wheel Sphinx==1.1.3 -run pip install --no-use-wheel sphinxcontrib-httpdomain==1.1.9 -add . /docs -run cd /docs; make docs +# TODO switch to http://packages.ubuntu.com/trusty/python-sphinxcontrib-httpdomain once trusty is released -expose 8000 +RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq make python-pip python-setuptools +# pip installs from docs/requirements.txt, but here to increase cacheability +RUN pip install Sphinx==1.1.3 +RUN pip install sphinxcontrib-httpdomain==1.1.9 +ADD . /docs +RUN make -C /docs clean docs -workdir /docs/_build/html - -entrypoint ["python", "-m", "SimpleHTTPServer"] +WORKDIR /docs/_build/html +CMD ["python", "-m", "SimpleHTTPServer"] +# note, EXPOSE is only last because of https://github.com/dotcloud/docker/issues/3525 +EXPOSE 8000 From 221454f315a8993a6996ba9867f98bddbdb1eb16 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 14 Jan 2014 19:29:02 -0700 Subject: [PATCH 045/364] Fix release.sh sed of install.sh so we preserve tests against 'https://get.docker.io/' Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/release.sh b/hack/release.sh index 8256faa2dc..729f1b5eff 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -295,7 +295,7 @@ EOF # Upload the index script release_index() { - sed "s,https://get.docker.io/,$(s3_url)/," hack/install.sh | write_to_s3 s3://$BUCKET/index + sed "s,url='https://get.docker.io/',url='$(s3_url)/'," hack/install.sh | write_to_s3 s3://$BUCKET/index } release_test() { From 37fcbfa1f4586e4437664239fc271dda31ac1837 Mon Sep 17 00:00:00 2001 From: James Allen Date: Wed, 15 Jan 2014 17:29:46 +0000 Subject: [PATCH 046/364] Add RWMutex lock into TruncIndex to fix race condition when inserting values The idx.index array is overwritten when a new value is inserted to the index. When two containers are created concurrently, their ids are inserted to the index and one can overwrite the other leaving one of ids missing from the index. Adding a RWMutex lock around read/write operations fixes this. Docker-DCO-1.1-Signed-off-by: James Allen (github: jpallen) --- utils/utils.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/utils/utils.go b/utils/utils.go index e046dfa2a5..25573d46b4 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -418,6 +418,7 @@ func GetTotalUsedFds() int { // TruncIndex allows the retrieval of string identifiers by any of their unique prefixes. // This is used to retrieve image and container IDs by more convenient shorthand prefixes. type TruncIndex struct { + sync.RWMutex index *suffixarray.Index ids map[string]bool bytes []byte @@ -432,6 +433,8 @@ func NewTruncIndex() *TruncIndex { } func (idx *TruncIndex) Add(id string) error { + idx.Lock() + defer idx.Unlock() if strings.Contains(id, " ") { return fmt.Errorf("Illegal character: ' '") } @@ -445,6 +448,8 @@ func (idx *TruncIndex) Add(id string) error { } func (idx *TruncIndex) Delete(id string) error { + idx.Lock() + defer idx.Unlock() if _, exists := idx.ids[id]; !exists { return fmt.Errorf("No such id: %s", id) } @@ -470,6 +475,8 @@ func (idx *TruncIndex) lookup(s string) (int, int, error) { } func (idx *TruncIndex) Get(s string) (string, error) { + idx.RLock() + defer idx.RUnlock() before, after, err := idx.lookup(s) //log.Printf("Get(%s) bytes=|%s| before=|%d| after=|%d|\n", s, idx.bytes, before, after) if err != nil { From 7898dca8b3d1835d15812bd5249fcfb0de73257b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 14 Jan 2014 11:43:58 -0800 Subject: [PATCH 047/364] fix tests & small code improvment Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api_params.go | 8 -------- integration/api_test.go | 8 ++++---- server.go | 6 +++--- 3 files changed, 7 insertions(+), 15 deletions(-) diff --git a/api_params.go b/api_params.go index cfe80c8b0b..92dde2906a 100644 --- a/api_params.go +++ b/api_params.go @@ -1,14 +1,6 @@ package docker type ( - APIHistory struct { - ID string `json:"Id"` - Tags []string `json:",omitempty"` - Created int64 - CreatedBy string `json:",omitempty"` - Size int64 - } - APITop struct { Titles []string Processes [][]string diff --git a/integration/api_test.go b/integration/api_test.go index 47d63efb1d..3c807aecd7 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -263,12 +263,12 @@ func TestGetImagesHistory(t *testing.T) { } assertHttpNotError(r, t) - history := []docker.APIHistory{} - if err := json.Unmarshal(r.Body.Bytes(), &history); err != nil { + outs := engine.NewTable("Created", 0) + if _, err := outs.ReadFrom(r.Body); err != nil { t.Fatal(err) } - if len(history) != 1 { - t.Errorf("Expected 1 line, %d found", len(history)) + if len(outs.Data) != 1 { + t.Errorf("Expected 1 line, %d found", len(outs.Data)) } } diff --git a/server.go b/server.go index ce0b3f91c5..2a56d5f439 100644 --- a/server.go +++ b/server.go @@ -587,7 +587,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { allImages, err = srv.runtime.graph.Heads() } if err != nil { - job.Errorf("%s", err) + job.Error(err) return engine.StatusErr } lookup := make(map[string]*engine.Env) @@ -642,7 +642,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { outs.ReverseSort() if _, err := outs.WriteTo(job.Stdout); err != nil { - job.Errorf("%s", err) + job.Error(err) return engine.StatusErr } return engine.StatusOK @@ -735,7 +735,7 @@ func (srv *Server) ImageHistory(job *engine.Job) engine.Status { }) outs.ReverseSort() if _, err := outs.WriteTo(job.Stdout); err != nil { - job.Errorf("%s", err) + job.Error(err) return engine.StatusErr } return engine.StatusOK From 7e00ba03ecd4f1b30e49dbc07160b09929dd786c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 14 Jan 2014 14:12:21 -0800 Subject: [PATCH 048/364] fix doc, load and get Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- docs/sources/api/docker_remote_api_v1.8.rst | 1184 +++++++++---------- docs/sources/api/docker_remote_api_v1.9.rst | 1176 +++++++++--------- 2 files changed, 1180 insertions(+), 1180 deletions(-) diff --git a/docs/sources/api/docker_remote_api_v1.8.rst b/docs/sources/api/docker_remote_api_v1.8.rst index 3db06fc942..6ccc6eca94 100644 --- a/docs/sources/api/docker_remote_api_v1.8.rst +++ b/docs/sources/api/docker_remote_api_v1.8.rst @@ -31,72 +31,72 @@ List containers .. http:get:: /containers/json - List containers + List containers - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1 - - **Example response**: + GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1 - .. sourcecode:: http + **Example response**: - HTTP/1.1 200 OK - Content-Type: application/json - - [ - { - "Id": "8dfafdbc3a40", - "Image": "base:latest", - "Command": "echo 1", - "Created": 1367854155, - "Status": "Exit 0", - "Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], - "SizeRw":12288, - "SizeRootFs":0 - }, - { - "Id": "9cd87474be90", - "Image": "base:latest", - "Command": "echo 222222", - "Created": 1367854155, - "Status": "Exit 0", - "Ports":[], - "SizeRw":12288, - "SizeRootFs":0 - }, - { - "Id": "3176a2479c92", - "Image": "base:latest", - "Command": "echo 3333333333333333", - "Created": 1367854154, - "Status": "Exit 0", - "Ports":[], - "SizeRw":12288, - "SizeRootFs":0 - }, - { - "Id": "4cb07b47f9fb", - "Image": "base:latest", - "Command": "echo 444444444444444444444444444444444", - "Created": 1367854152, - "Status": "Exit 0", - "Ports":[], - "SizeRw":12288, - "SizeRootFs":0 - } - ] - - :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default - :query limit: Show ``limit`` last created containers, include non-running ones. - :query since: Show only containers created since Id, include non-running ones. - :query before: Show only containers created before Id, include non-running ones. - :query size: 1/True/true or 0/False/false, Show the containers sizes - :statuscode 200: no error - :statuscode 400: bad parameter - :statuscode 500: server error + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id": "8dfafdbc3a40", + "Image": "base:latest", + "Command": "echo 1", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "9cd87474be90", + "Image": "base:latest", + "Command": "echo 222222", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "3176a2479c92", + "Image": "base:latest", + "Command": "echo 3333333333333333", + "Created": 1367854154, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "4cb07b47f9fb", + "Image": "base:latest", + "Command": "echo 444444444444444444444444444444444", + "Created": 1367854152, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + } + ] + + :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default + :query limit: Show ``limit`` last created containers, include non-running ones. + :query since: Show only containers created since Id, include non-running ones. + :query before: Show only containers created before Id, include non-running ones. + :query size: 1/True/true or 0/False/false, Show the containers sizes + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 500: server error Create a container @@ -104,61 +104,61 @@ Create a container .. http:post:: /containers/create - Create a container + Create a container - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /containers/create HTTP/1.1 - Content-Type: application/json + POST /containers/create HTTP/1.1 + Content-Type: application/json - { - "Hostname":"", - "User":"", - "Memory":0, - "MemorySwap":0, - "AttachStdin":false, - "AttachStdout":true, - "AttachStderr":true, - "PortSpecs":null, - "Tty":false, - "OpenStdin":false, - "StdinOnce":false, - "Env":null, - "Cmd":[ - "date" - ], - "Dns":null, - "Image":"base", - "Volumes":{ - "/tmp": {} - }, - "VolumesFrom":"", - "WorkingDir":"", - "ExposedPorts":{ - "22/tcp": {} - } - } + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":true, + "AttachStderr":true, + "PortSpecs":null, + "Tty":false, + "OpenStdin":false, + "StdinOnce":false, + "Env":null, + "Cmd":[ + "date" + ], + "Dns":null, + "Image":"base", + "Volumes":{ + "/tmp": {} + }, + "VolumesFrom":"", + "WorkingDir":"", + "ExposedPorts":{ + "22/tcp": {} + } + } - **Example response**: + **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 201 OK - Content-Type: application/json + HTTP/1.1 201 OK + Content-Type: application/json - { - "Id":"e90e34656806" - "Warnings":[] - } - - :jsonparam config: the container's configuration - :query name: Assign the specified name to the container. Must match ``/?[a-zA-Z0-9_-]+``. - :statuscode 201: no error - :statuscode 404: no such container - :statuscode 406: impossible to attach (container not running) - :statuscode 500: server error + { + "Id":"e90e34656806" + "Warnings":[] + } + + :jsonparam config: the container's configuration + :query name: Assign the specified name to the container. Must match ``/?[a-zA-Z0-9_-]+``. + :statuscode 201: no error + :statuscode 404: no such container + :statuscode 406: impossible to attach (container not running) + :statuscode 500: server error Inspect a container @@ -166,67 +166,67 @@ Inspect a container .. http:get:: /containers/(id)/json - Return low-level information on the container ``id`` + Return low-level information on the container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - GET /containers/4fa6e0f0c678/json HTTP/1.1 - - **Example response**: + GET /containers/4fa6e0f0c678/json HTTP/1.1 - .. sourcecode:: http + **Example response**: - HTTP/1.1 200 OK - Content-Type: application/json + .. sourcecode:: http - { - "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", - "Created": "2013-05-07T14:51:42.041847+02:00", - "Path": "date", - "Args": [], - "Config": { - "Hostname": "4fa6e0f0c678", - "User": "", - "Memory": 0, - "MemorySwap": 0, - "AttachStdin": false, - "AttachStdout": true, - "AttachStderr": true, - "PortSpecs": null, - "Tty": false, - "OpenStdin": false, - "StdinOnce": false, - "Env": null, - "Cmd": [ - "date" - ], - "Dns": null, - "Image": "base", - "Volumes": {}, - "VolumesFrom": "", - "WorkingDir":"" + HTTP/1.1 200 OK + Content-Type: application/json - }, - "State": { - "Running": false, - "Pid": 0, - "ExitCode": 0, - "StartedAt": "2013-05-07T14:51:42.087658+02:01360", - "Ghost": false - }, - "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "NetworkSettings": { - "IpAddress": "", - "IpPrefixLen": 0, - "Gateway": "", - "Bridge": "", - "PortMapping": null - }, - "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", - "ResolvConfPath": "/etc/resolv.conf", - "Volumes": {}, + { + "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", + "Created": "2013-05-07T14:51:42.041847+02:00", + "Path": "date", + "Args": [], + "Config": { + "Hostname": "4fa6e0f0c678", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "PortSpecs": null, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Dns": null, + "Image": "base", + "Volumes": {}, + "VolumesFrom": "", + "WorkingDir":"" + + }, + "State": { + "Running": false, + "Pid": 0, + "ExitCode": 0, + "StartedAt": "2013-05-07T14:51:42.087658+02:01360", + "Ghost": false + }, + "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "NetworkSettings": { + "IpAddress": "", + "IpPrefixLen": 0, + "Gateway": "", + "Bridge": "", + "PortMapping": null + }, + "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", + "ResolvConfPath": "/etc/resolv.conf", + "Volumes": {}, "HostConfig": { "Binds": null, "ContainerIDFile": "", @@ -243,11 +243,11 @@ Inspect a container "Links": null, "PublishAllPorts": false } - } + } - :statuscode 200: no error - :statuscode 404: no such container - :statuscode 500: server error + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error List processes running inside a container @@ -255,45 +255,45 @@ List processes running inside a container .. http:get:: /containers/(id)/top - List processes running inside the container ``id`` + List processes running inside the container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - GET /containers/4fa6e0f0c678/top HTTP/1.1 + GET /containers/4fa6e0f0c678/top HTTP/1.1 - **Example response**: + **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 200 OK - Content-Type: application/json + HTTP/1.1 200 OK + Content-Type: application/json - { - "Titles":[ - "USER", - "PID", - "%CPU", - "%MEM", - "VSZ", - "RSS", - "TTY", - "STAT", - "START", - "TIME", - "COMMAND" - ], - "Processes":[ - ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"], - ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"] - ] - } + { + "Titles":[ + "USER", + "PID", + "%CPU", + "%MEM", + "VSZ", + "RSS", + "TTY", + "STAT", + "START", + "TIME", + "COMMAND" + ], + "Processes":[ + ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"], + ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"] + ] + } - :query ps_args: ps arguments to use (eg. aux) - :statuscode 200: no error - :statuscode 404: no such container - :statuscode 500: server error + :query ps_args: ps arguments to use (eg. aux) + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error Inspect changes on a container's filesystem @@ -301,40 +301,40 @@ Inspect changes on a container's filesystem .. http:get:: /containers/(id)/changes - Inspect changes on container ``id`` 's filesystem + Inspect changes on container ``id`` 's filesystem - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - GET /containers/4fa6e0f0c678/changes HTTP/1.1 + GET /containers/4fa6e0f0c678/changes HTTP/1.1 - - **Example response**: - .. sourcecode:: http + **Example response**: - HTTP/1.1 200 OK - Content-Type: application/json - - [ - { - "Path":"/dev", - "Kind":0 - }, - { - "Path":"/dev/kmsg", - "Kind":1 - }, - { - "Path":"/test", - "Kind":1 - } - ] + .. sourcecode:: http - :statuscode 200: no error - :statuscode 404: no such container - :statuscode 500: server error + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Path":"/dev", + "Kind":0 + }, + { + "Path":"/dev/kmsg", + "Kind":1 + }, + { + "Path":"/test", + "Kind":1 + } + ] + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error Export a container @@ -342,27 +342,27 @@ Export a container .. http:get:: /containers/(id)/export - Export the contents of container ``id`` + Export the contents of container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - GET /containers/4fa6e0f0c678/export HTTP/1.1 + GET /containers/4fa6e0f0c678/export HTTP/1.1 - - **Example response**: - .. sourcecode:: http + **Example response**: - HTTP/1.1 200 OK - Content-Type: application/octet-stream - - {{ STREAM }} + .. sourcecode:: http - :statuscode 200: no error - :statuscode 404: no such container - :statuscode 500: server error + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error Start a container @@ -405,24 +405,24 @@ Stop a container .. http:post:: /containers/(id)/stop - Stop the container ``id`` + Stop the container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /containers/e90e34656806/stop?t=5 HTTP/1.1 - - **Example response**: + POST /containers/e90e34656806/stop?t=5 HTTP/1.1 - .. sourcecode:: http + **Example response**: - HTTP/1.1 204 OK - - :query t: number of seconds to wait before killing the container - :statuscode 204: no error - :statuscode 404: no such container - :statuscode 500: server error + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error Restart a container @@ -430,24 +430,24 @@ Restart a container .. http:post:: /containers/(id)/restart - Restart the container ``id`` + Restart the container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /containers/e90e34656806/restart?t=5 HTTP/1.1 - - **Example response**: + POST /containers/e90e34656806/restart?t=5 HTTP/1.1 - .. sourcecode:: http + **Example response**: - HTTP/1.1 204 OK - - :query t: number of seconds to wait before killing the container - :statuscode 204: no error - :statuscode 404: no such container - :statuscode 500: server error + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error Kill a container @@ -455,23 +455,23 @@ Kill a container .. http:post:: /containers/(id)/kill - Kill the container ``id`` + Kill the container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /containers/e90e34656806/kill HTTP/1.1 - - **Example response**: + POST /containers/e90e34656806/kill HTTP/1.1 - .. sourcecode:: http + **Example response**: - HTTP/1.1 204 OK - - :statuscode 204: no error - :statuscode 404: no such container - :statuscode 500: server error + .. sourcecode:: http + + HTTP/1.1 204 OK + + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error Attach to a container @@ -479,74 +479,74 @@ Attach to a container .. http:post:: /containers/(id)/attach - Attach to the container ``id`` + Attach to the container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1 - - **Example response**: + POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1 - .. sourcecode:: http + **Example response**: - HTTP/1.1 200 OK - Content-Type: application/vnd.docker.raw-stream + .. sourcecode:: http - {{ STREAM }} - - :query logs: 1/True/true or 0/False/false, return logs. Default false - :query stream: 1/True/true or 0/False/false, return stream. Default false - :query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false - :query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false - :query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false - :statuscode 200: no error - :statuscode 400: bad parameter - :statuscode 404: no such container - :statuscode 500: server error + HTTP/1.1 200 OK + Content-Type: application/vnd.docker.raw-stream - **Stream details**: + {{ STREAM }} - When using the TTY setting is enabled in - :http:post:`/containers/create`, the stream is the raw data - from the process PTY and client's stdin. When the TTY is - disabled, then the stream is multiplexed to separate stdout - and stderr. + :query logs: 1/True/true or 0/False/false, return logs. Default false + :query stream: 1/True/true or 0/False/false, return stream. Default false + :query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false + :query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false + :query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error - The format is a **Header** and a **Payload** (frame). + **Stream details**: - **HEADER** + When using the TTY setting is enabled in + :http:post:`/containers/create`, the stream is the raw data + from the process PTY and client's stdin. When the TTY is + disabled, then the stream is multiplexed to separate stdout + and stderr. - The header will contain the information on which stream write - the stream (stdout or stderr). It also contain the size of - the associated frame encoded on the last 4 bytes (uint32). + The format is a **Header** and a **Payload** (frame). - It is encoded on the first 8 bytes like this:: + **HEADER** - header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4} + The header will contain the information on which stream write + the stream (stdout or stderr). It also contain the size of + the associated frame encoded on the last 4 bytes (uint32). - ``STREAM_TYPE`` can be: + It is encoded on the first 8 bytes like this:: - - 0: stdin (will be writen on stdout) - - 1: stdout - - 2: stderr + header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4} - ``SIZE1, SIZE2, SIZE3, SIZE4`` are the 4 bytes of the uint32 size encoded as big endian. + ``STREAM_TYPE`` can be: - **PAYLOAD** + - 0: stdin (will be writen on stdout) + - 1: stdout + - 2: stderr - The payload is the raw stream. + ``SIZE1, SIZE2, SIZE3, SIZE4`` are the 4 bytes of the uint32 size encoded as big endian. - **IMPLEMENTATION** + **PAYLOAD** - The simplest way to implement the Attach protocol is the following: + The payload is the raw stream. - 1) Read 8 bytes - 2) chose stdout or stderr depending on the first byte - 3) Extract the frame size from the last 4 byets - 4) Read the extracted size and output it on the correct output - 5) Goto 1) + **IMPLEMENTATION** + + The simplest way to implement the Attach protocol is the following: + + 1) Read 8 bytes + 2) chose stdout or stderr depending on the first byte + 3) Extract the frame size from the last 4 byets + 4) Read the extracted size and output it on the correct output + 5) Goto 1) @@ -555,26 +555,26 @@ Wait a container .. http:post:: /containers/(id)/wait - Block until container ``id`` stops, then returns the exit code + Block until container ``id`` stops, then returns the exit code - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /containers/16253994b7c4/wait HTTP/1.1 - - **Example response**: + POST /containers/16253994b7c4/wait HTTP/1.1 - .. sourcecode:: http + **Example response**: - HTTP/1.1 200 OK - Content-Type: application/json + .. sourcecode:: http - {"StatusCode":0} - - :statuscode 200: no error - :statuscode 404: no such container - :statuscode 500: server error + HTTP/1.1 200 OK + Content-Type: application/json + + {"StatusCode":0} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error Remove a container @@ -582,9 +582,9 @@ Remove a container .. http:delete:: /containers/(id) - Remove the container ``id`` from the filesystem + Remove the container ``id`` from the filesystem - **Example request**: + **Example request**: .. sourcecode:: http @@ -594,11 +594,11 @@ Remove a container .. sourcecode:: http - HTTP/1.1 204 OK + HTTP/1.1 204 OK - :query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false + :query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false :statuscode 204: no error - :statuscode 400: bad parameter + :statuscode 400: bad parameter :statuscode 404: no such container :statuscode 500: server error @@ -608,31 +608,31 @@ Copy files or folders from a container .. http:post:: /containers/(id)/copy - Copy files or folders of container ``id`` + Copy files or folders of container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /containers/4fa6e0f0c678/copy HTTP/1.1 - Content-Type: application/json + POST /containers/4fa6e0f0c678/copy HTTP/1.1 + Content-Type: application/json - { - "Resource":"test.txt" - } + { + "Resource":"test.txt" + } - **Example response**: + **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 200 OK - Content-Type: application/octet-stream - - {{ STREAM }} + HTTP/1.1 200 OK + Content-Type: application/octet-stream - :statuscode 200: no error - :statuscode 404: no such container - :statuscode 500: server error + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error 2.2 Images @@ -643,43 +643,43 @@ List Images .. http:get:: /images/json - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - GET /images/json?all=0 HTTP/1.1 + GET /images/json?all=0 HTTP/1.1 - **Example response**: + **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 200 OK - Content-Type: application/json - - [ - { - "RepoTags": [ - "ubuntu:12.04", - "ubuntu:precise", - "ubuntu:latest" - ], - "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", - "Created": 1365714795, - "Size": 131506275, - "VirtualSize": 131506275 - }, - { - "RepoTags": [ - "ubuntu:12.10", - "ubuntu:quantal" - ], - "ParentId": "27cf784147099545", - "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "Created": 1364102658, - "Size": 24653, - "VirtualSize": 180116135 - } - ] + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "RepoTags": [ + "ubuntu:12.04", + "ubuntu:precise", + "ubuntu:latest" + ], + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Created": 1365714795, + "Size": 131506275, + "VirtualSize": 131506275 + }, + { + "RepoTags": [ + "ubuntu:12.10", + "ubuntu:quantal" + ], + "ParentId": "27cf784147099545", + "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Created": 1364102658, + "Size": 24653, + "VirtualSize": 180116135 + } + ] Create an image @@ -687,9 +687,9 @@ Create an image .. http:post:: /images/create - Create an image, either by pull it from the registry or by importing it + Create an image, either by pull it from the registry or by importing it - **Example request**: + **Example request**: .. sourcecode:: http @@ -700,23 +700,23 @@ Create an image .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: application/json + Content-Type: application/json - {"status":"Pulling..."} - {"status":"Pulling", "progress":"1 B/ 100 B", "progressDetail":{"current":1, "total":100}} - {"error":"Invalid..."} - ... + {"status":"Pulling..."} + {"status":"Pulling", "progress":"1 B/ 100 B", "progressDetail":{"current":1, "total":100}} + {"error":"Invalid..."} + ... - When using this endpoint to pull an image from the registry, - the ``X-Registry-Auth`` header can be used to include a - base64-encoded AuthConfig object. + When using this endpoint to pull an image from the registry, + the ``X-Registry-Auth`` header can be used to include a + base64-encoded AuthConfig object. :query fromImage: name of the image to pull - :query fromSrc: source to import, - means stdin + :query fromSrc: source to import, - means stdin :query repo: repository - :query tag: tag - :query registry: the registry to pull from - :reqheader X-Registry-Auth: base64-encoded AuthConfig object + :query tag: tag + :query registry: the registry to pull from + :reqheader X-Registry-Auth: base64-encoded AuthConfig object :statuscode 200: no error :statuscode 500: server error @@ -727,27 +727,27 @@ Insert a file in an image .. http:post:: /images/(name)/insert - Insert a file from ``url`` in the image ``name`` at ``path`` + Insert a file from ``url`` in the image ``name`` at ``path`` - **Example request**: + **Example request**: .. sourcecode:: http POST /images/test/insert?path=/usr&url=myurl HTTP/1.1 - **Example response**: + **Example response**: .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: application/json + Content-Type: application/json - {"status":"Inserting..."} - {"status":"Inserting", "progress":"1/? (n/a)", "progressDetail":{"current":1}} - {"error":"Invalid..."} - ... + {"status":"Inserting..."} + {"status":"Inserting", "progress":"1/? (n/a)", "progressDetail":{"current":1}} + {"error":"Invalid..."} + ... - :statuscode 200: no error + :statuscode 200: no error :statuscode 500: server error @@ -756,52 +756,52 @@ Inspect an image .. http:get:: /images/(name)/json - Return low-level information on the image ``name`` + Return low-level information on the image ``name`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - GET /images/base/json HTTP/1.1 + GET /images/base/json HTTP/1.1 - **Example response**: + **Example response**: .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: application/json + Content-Type: application/json - { - "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "parent":"27cf784147099545", - "created":"2013-03-23T22:24:18.818426-07:00", - "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", - "container_config": - { - "Hostname":"", - "User":"", - "Memory":0, - "MemorySwap":0, - "AttachStdin":false, - "AttachStdout":false, - "AttachStderr":false, - "PortSpecs":null, - "Tty":true, - "OpenStdin":true, - "StdinOnce":false, - "Env":null, - "Cmd": ["/bin/bash"] - ,"Dns":null, - "Image":"base", - "Volumes":null, - "VolumesFrom":"", - "WorkingDir":"" - }, - "Size": 6824592 - } + { + "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "parent":"27cf784147099545", + "created":"2013-03-23T22:24:18.818426-07:00", + "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", + "container_config": + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":false, + "AttachStderr":false, + "PortSpecs":null, + "Tty":true, + "OpenStdin":true, + "StdinOnce":false, + "Env":null, + "Cmd": ["/bin/bash"] + ,"Dns":null, + "Image":"base", + "Volumes":null, + "VolumesFrom":"", + "WorkingDir":"" + }, + "Size": 6824592 + } - :statuscode 200: no error - :statuscode 404: no such image + :statuscode 200: no error + :statuscode 404: no such image :statuscode 500: server error @@ -823,20 +823,20 @@ Get the history of an image .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: application/json + Content-Type: application/json - [ - { - "Id":"b750fe79269d", - "Created":1364102658, - "CreatedBy":"/bin/bash" - }, - { - "Id":"27cf78414709", - "Created":1364068391, - "CreatedBy":"" - } - ] + [ + { + "Id":"b750fe79269d", + "Created":1364102658, + "CreatedBy":"/bin/bash" + }, + { + "Id":"27cf78414709", + "Created":1364068391, + "CreatedBy":"" + } + ] :statuscode 200: no error :statuscode 404: no such image @@ -880,26 +880,26 @@ Tag an image into a repository .. http:post:: /images/(name)/tag - Tag the image ``name`` into a repository + Tag the image ``name`` into a repository **Example request**: .. sourcecode:: http - - POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1 - **Example response**: + POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1 + + **Example response**: .. sourcecode:: http HTTP/1.1 200 OK - :query repo: The repository to tag in - :query force: 1/True/true or 0/False/false, default false - :statuscode 200: no error - :statuscode 400: bad parameter - :statuscode 404: no such image - :statuscode 409: conflict + :query repo: The repository to tag in + :query force: 1/True/true or 0/False/false, default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such image + :statuscode 409: conflict :statuscode 500: server error @@ -908,30 +908,30 @@ Remove an image .. http:delete:: /images/(name) - Remove the image ``name`` from the filesystem - - **Example request**: + Remove the image ``name`` from the filesystem - .. sourcecode:: http - - DELETE /images/test HTTP/1.1 - - **Example response**: + **Example request**: .. sourcecode:: http - HTTP/1.1 200 OK - Content-type: application/json + DELETE /images/test HTTP/1.1 - [ - {"Untagged":"3e2f21a89f"}, - {"Deleted":"3e2f21a89f"}, - {"Deleted":"53b4f83ac9"} - ] + **Example response**: - :statuscode 200: no error + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-type: application/json + + [ + {"Untagged":"3e2f21a89f"}, + {"Deleted":"3e2f21a89f"}, + {"Deleted":"53b4f83ac9"} + ] + + :statuscode 200: no error :statuscode 404: no such image - :statuscode 409: conflict + :statuscode 409: conflict :statuscode 500: server error @@ -940,54 +940,54 @@ Search images .. http:get:: /images/search - Search for an image in the docker index. - - .. note:: - - The response keys have changed from API v1.6 to reflect the JSON - sent by the registry server to the docker daemon's request. - - **Example request**: + Search for an image in the docker index. + + .. note:: + + The response keys have changed from API v1.6 to reflect the JSON + sent by the registry server to the docker daemon's request. + + **Example request**: .. sourcecode:: http GET /images/search?term=sshd HTTP/1.1 - **Example response**: + **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 200 OK - Content-Type: application/json - - [ - { - "description": "", - "is_official": false, - "is_trusted": false, - "name": "wma55/u1210sshd", - "star_count": 0 - }, - { - "description": "", - "is_official": false, - "is_trusted": false, - "name": "jdswinbank/sshd", - "star_count": 0 - }, - { - "description": "", - "is_official": false, - "is_trusted": false, - "name": "vgauthier/sshd", - "star_count": 0 - } - ... - ] + HTTP/1.1 200 OK + Content-Type: application/json - :query term: term to search - :statuscode 200: no error - :statuscode 500: server error + [ + { + "description": "", + "is_official": false, + "is_trusted": false, + "name": "wma55/u1210sshd", + "star_count": 0 + }, + { + "description": "", + "is_official": false, + "is_trusted": false, + "name": "jdswinbank/sshd", + "star_count": 0 + }, + { + "description": "", + "is_official": false, + "is_trusted": false, + "name": "vgauthier/sshd", + "star_count": 0 + } + ... + ] + + :query term: term to search + :statuscode 200: no error + :statuscode 500: server error 2.3 Misc @@ -1022,7 +1022,7 @@ Build an image from Dockerfile via stdin The stream must be a tar archive compressed with one of the following algorithms: identity (no compression), gzip, bzip2, - xz. + xz. The archive must include a file called ``Dockerfile`` at its root. It may include any number of other files, which will be @@ -1051,14 +1051,14 @@ Check auth configuration .. sourcecode:: http POST /auth HTTP/1.1 - Content-Type: application/json + Content-Type: application/json - { - "username":"hannibal", - "password:"xxxx", - "email":"hannibal@a-team.com", - "serveraddress":"https://index.docker.io/v1/" - } + { + "username":"hannibal", + "password:"xxxx", + "email":"hannibal@a-team.com", + "serveraddress":"https://index.docker.io/v1/" + } **Example response**: @@ -1076,9 +1076,9 @@ Display system-wide information .. http:get:: /info - Display system-wide information - - **Example request**: + Display system-wide information + + **Example request**: .. sourcecode:: http @@ -1089,18 +1089,18 @@ Display system-wide information .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: application/json + Content-Type: application/json - { - "Containers":11, - "Images":16, - "Debug":false, - "NFd": 11, - "NGoroutines":21, - "MemoryLimit":true, - "SwapLimit":false, - "IPv4Forwarding":true - } + { + "Containers":11, + "Images":16, + "Debug":false, + "NFd": 11, + "NGoroutines":21, + "MemoryLimit":true, + "SwapLimit":false, + "IPv4Forwarding":true + } :statuscode 200: no error :statuscode 500: server error @@ -1111,9 +1111,9 @@ Show the docker version information .. http:get:: /version - Show the docker version information + Show the docker version information - **Example request**: + **Example request**: .. sourcecode:: http @@ -1124,16 +1124,16 @@ Show the docker version information .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: application/json + Content-Type: application/json - { - "Version":"0.2.2", - "GitCommit":"5a2a5cc+CHANGES", - "GoVersion":"go1.0.3" - } + { + "Version":"0.2.2", + "GitCommit":"5a2a5cc+CHANGES", + "GoVersion":"go1.0.3" + } :statuscode 200: no error - :statuscode 500: server error + :statuscode 500: server error Create a new image from a container's changes @@ -1154,7 +1154,7 @@ Create a new image from a container's changes .. sourcecode:: http HTTP/1.1 201 OK - Content-Type: application/vnd.docker.raw-stream + Content-Type: application/vnd.docker.raw-stream {"Id":"596069db4bf5"} @@ -1174,11 +1174,11 @@ Monitor Docker's events .. http:get:: /events - Get events from docker, either in real time via streaming, or via polling (using `since`) + Get events from docker, either in real time via streaming, or via polling (using `since`) - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http GET /events?since=1374067924 @@ -1187,14 +1187,14 @@ Monitor Docker's events .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: application/json + Content-Type: application/json - {"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924} - {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924} - {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966} - {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970} + {"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966} + {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970} - :query since: timestamp used for polling + :query since: timestamp used for polling :statuscode 200: no error :statuscode 500: server error @@ -1203,22 +1203,23 @@ Get a tarball containing all images and tags in a repository .. http:get:: /images/(name)/get - Get a tarball containing all images and metadata for the repository specified by ``name``. + Get a tarball containing all images and metadata for the repository specified by ``name``. - **Example request** + **Example request** + + .. sourcecode:: http - .. sourcecode:: http - GET /images/ubuntu/get - **Example response**: + **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 200 OK - Content-Type: application/x-tar + HTTP/1.1 200 OK + Content-Type: application/x-tar + + Binary data stream - Binary data stream :statuscode 200: no error :statuscode 500: server error @@ -1227,24 +1228,24 @@ Load a tarball with a set of images and tags into docker .. http:post:: /images/load - Load a set of images and tags into the docker repository. + Load a set of images and tags into the docker repository. - **Example request** + **Example request** - .. sourcecode:: http + .. sourcecode:: http - POST /images/load + POST /images/load - Tarball in body + Tarball in body - **Example response**: + **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 200 OK + HTTP/1.1 200 OK - :statuscode 200: no error - :statuscode 500: server error + :statuscode 200: no error + :statuscode 500: server error 3. Going further ================ @@ -1262,7 +1263,7 @@ Here are the steps of 'docker run' : * If you are not in detached mode: * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1 * If in detached mode or only stdin is attached: - * Display the container's id + * Display the container's id 3.2 Hijacking @@ -1278,4 +1279,3 @@ To enable cross origin requests to the remote api add the flag "-api-enable-cors .. code-block:: bash docker -d -H="192.168.1.9:4243" -api-enable-cors - diff --git a/docs/sources/api/docker_remote_api_v1.9.rst b/docs/sources/api/docker_remote_api_v1.9.rst index a7a9ef53cb..5a8f9311b8 100644 --- a/docs/sources/api/docker_remote_api_v1.9.rst +++ b/docs/sources/api/docker_remote_api_v1.9.rst @@ -31,72 +31,72 @@ List containers .. http:get:: /containers/json - List containers + List containers - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1 - - **Example response**: + GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1 - .. sourcecode:: http + **Example response**: - HTTP/1.1 200 OK - Content-Type: application/json - - [ - { - "Id": "8dfafdbc3a40", - "Image": "base:latest", - "Command": "echo 1", - "Created": 1367854155, - "Status": "Exit 0", - "Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], - "SizeRw":12288, - "SizeRootFs":0 - }, - { - "Id": "9cd87474be90", - "Image": "base:latest", - "Command": "echo 222222", - "Created": 1367854155, - "Status": "Exit 0", - "Ports":[], - "SizeRw":12288, - "SizeRootFs":0 - }, - { - "Id": "3176a2479c92", - "Image": "base:latest", - "Command": "echo 3333333333333333", - "Created": 1367854154, - "Status": "Exit 0", - "Ports":[], - "SizeRw":12288, - "SizeRootFs":0 - }, - { - "Id": "4cb07b47f9fb", - "Image": "base:latest", - "Command": "echo 444444444444444444444444444444444", - "Created": 1367854152, - "Status": "Exit 0", - "Ports":[], - "SizeRw":12288, - "SizeRootFs":0 - } - ] - - :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default - :query limit: Show ``limit`` last created containers, include non-running ones. - :query since: Show only containers created since Id, include non-running ones. - :query before: Show only containers created before Id, include non-running ones. - :query size: 1/True/true or 0/False/false, Show the containers sizes - :statuscode 200: no error - :statuscode 400: bad parameter - :statuscode 500: server error + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id": "8dfafdbc3a40", + "Image": "base:latest", + "Command": "echo 1", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "9cd87474be90", + "Image": "base:latest", + "Command": "echo 222222", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "3176a2479c92", + "Image": "base:latest", + "Command": "echo 3333333333333333", + "Created": 1367854154, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "4cb07b47f9fb", + "Image": "base:latest", + "Command": "echo 444444444444444444444444444444444", + "Created": 1367854152, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + } + ] + + :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default + :query limit: Show ``limit`` last created containers, include non-running ones. + :query since: Show only containers created since Id, include non-running ones. + :query before: Show only containers created before Id, include non-running ones. + :query size: 1/True/true or 0/False/false, Show the containers sizes + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 500: server error Create a container @@ -104,61 +104,61 @@ Create a container .. http:post:: /containers/create - Create a container + Create a container - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /containers/create HTTP/1.1 - Content-Type: application/json + POST /containers/create HTTP/1.1 + Content-Type: application/json - { - "Hostname":"", - "User":"", - "Memory":0, - "MemorySwap":0, - "AttachStdin":false, - "AttachStdout":true, - "AttachStderr":true, - "PortSpecs":null, - "Tty":false, - "OpenStdin":false, - "StdinOnce":false, - "Env":null, - "Cmd":[ - "date" - ], - "Dns":null, - "Image":"base", - "Volumes":{ - "/tmp": {} - }, - "VolumesFrom":"", - "WorkingDir":"", - "ExposedPorts":{ - "22/tcp": {} - } - } + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":true, + "AttachStderr":true, + "PortSpecs":null, + "Tty":false, + "OpenStdin":false, + "StdinOnce":false, + "Env":null, + "Cmd":[ + "date" + ], + "Dns":null, + "Image":"base", + "Volumes":{ + "/tmp": {} + }, + "VolumesFrom":"", + "WorkingDir":"", + "ExposedPorts":{ + "22/tcp": {} + } + } - **Example response**: + **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 201 OK - Content-Type: application/json + HTTP/1.1 201 OK + Content-Type: application/json - { - "Id":"e90e34656806" - "Warnings":[] - } - - :jsonparam config: the container's configuration - :query name: Assign the specified name to the container. Must match ``/?[a-zA-Z0-9_-]+``. - :statuscode 201: no error - :statuscode 404: no such container - :statuscode 406: impossible to attach (container not running) - :statuscode 500: server error + { + "Id":"e90e34656806" + "Warnings":[] + } + + :jsonparam config: the container's configuration + :query name: Assign the specified name to the container. Must match ``/?[a-zA-Z0-9_-]+``. + :statuscode 201: no error + :statuscode 404: no such container + :statuscode 406: impossible to attach (container not running) + :statuscode 500: server error Inspect a container @@ -166,67 +166,67 @@ Inspect a container .. http:get:: /containers/(id)/json - Return low-level information on the container ``id`` + Return low-level information on the container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - GET /containers/4fa6e0f0c678/json HTTP/1.1 - - **Example response**: + GET /containers/4fa6e0f0c678/json HTTP/1.1 - .. sourcecode:: http + **Example response**: - HTTP/1.1 200 OK - Content-Type: application/json + .. sourcecode:: http - { - "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", - "Created": "2013-05-07T14:51:42.041847+02:00", - "Path": "date", - "Args": [], - "Config": { - "Hostname": "4fa6e0f0c678", - "User": "", - "Memory": 0, - "MemorySwap": 0, - "AttachStdin": false, - "AttachStdout": true, - "AttachStderr": true, - "PortSpecs": null, - "Tty": false, - "OpenStdin": false, - "StdinOnce": false, - "Env": null, - "Cmd": [ - "date" - ], - "Dns": null, - "Image": "base", - "Volumes": {}, - "VolumesFrom": "", - "WorkingDir":"" + HTTP/1.1 200 OK + Content-Type: application/json - }, - "State": { - "Running": false, - "Pid": 0, - "ExitCode": 0, - "StartedAt": "2013-05-07T14:51:42.087658+02:01360", - "Ghost": false - }, - "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "NetworkSettings": { - "IpAddress": "", - "IpPrefixLen": 0, - "Gateway": "", - "Bridge": "", - "PortMapping": null - }, - "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", - "ResolvConfPath": "/etc/resolv.conf", - "Volumes": {}, + { + "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", + "Created": "2013-05-07T14:51:42.041847+02:00", + "Path": "date", + "Args": [], + "Config": { + "Hostname": "4fa6e0f0c678", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "PortSpecs": null, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Dns": null, + "Image": "base", + "Volumes": {}, + "VolumesFrom": "", + "WorkingDir":"" + + }, + "State": { + "Running": false, + "Pid": 0, + "ExitCode": 0, + "StartedAt": "2013-05-07T14:51:42.087658+02:01360", + "Ghost": false + }, + "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "NetworkSettings": { + "IpAddress": "", + "IpPrefixLen": 0, + "Gateway": "", + "Bridge": "", + "PortMapping": null + }, + "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", + "ResolvConfPath": "/etc/resolv.conf", + "Volumes": {}, "HostConfig": { "Binds": null, "ContainerIDFile": "", @@ -243,11 +243,11 @@ Inspect a container "Links": null, "PublishAllPorts": false } - } + } - :statuscode 200: no error - :statuscode 404: no such container - :statuscode 500: server error + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error List processes running inside a container @@ -255,45 +255,45 @@ List processes running inside a container .. http:get:: /containers/(id)/top - List processes running inside the container ``id`` + List processes running inside the container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - GET /containers/4fa6e0f0c678/top HTTP/1.1 + GET /containers/4fa6e0f0c678/top HTTP/1.1 - **Example response**: + **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 200 OK - Content-Type: application/json + HTTP/1.1 200 OK + Content-Type: application/json - { - "Titles":[ - "USER", - "PID", - "%CPU", - "%MEM", - "VSZ", - "RSS", - "TTY", - "STAT", - "START", - "TIME", - "COMMAND" - ], - "Processes":[ - ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"], - ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"] - ] - } + { + "Titles":[ + "USER", + "PID", + "%CPU", + "%MEM", + "VSZ", + "RSS", + "TTY", + "STAT", + "START", + "TIME", + "COMMAND" + ], + "Processes":[ + ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"], + ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"] + ] + } - :query ps_args: ps arguments to use (eg. aux) - :statuscode 200: no error - :statuscode 404: no such container - :statuscode 500: server error + :query ps_args: ps arguments to use (eg. aux) + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error Inspect changes on a container's filesystem @@ -301,40 +301,40 @@ Inspect changes on a container's filesystem .. http:get:: /containers/(id)/changes - Inspect changes on container ``id`` 's filesystem + Inspect changes on container ``id`` 's filesystem - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - GET /containers/4fa6e0f0c678/changes HTTP/1.1 + GET /containers/4fa6e0f0c678/changes HTTP/1.1 - - **Example response**: - .. sourcecode:: http + **Example response**: - HTTP/1.1 200 OK - Content-Type: application/json - - [ - { - "Path":"/dev", - "Kind":0 - }, - { - "Path":"/dev/kmsg", - "Kind":1 - }, - { - "Path":"/test", - "Kind":1 - } - ] + .. sourcecode:: http - :statuscode 200: no error - :statuscode 404: no such container - :statuscode 500: server error + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Path":"/dev", + "Kind":0 + }, + { + "Path":"/dev/kmsg", + "Kind":1 + }, + { + "Path":"/test", + "Kind":1 + } + ] + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error Export a container @@ -342,27 +342,27 @@ Export a container .. http:get:: /containers/(id)/export - Export the contents of container ``id`` + Export the contents of container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - GET /containers/4fa6e0f0c678/export HTTP/1.1 + GET /containers/4fa6e0f0c678/export HTTP/1.1 - - **Example response**: - .. sourcecode:: http + **Example response**: - HTTP/1.1 200 OK - Content-Type: application/octet-stream - - {{ STREAM }} + .. sourcecode:: http - :statuscode 200: no error - :statuscode 404: no such container - :statuscode 500: server error + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error Start a container @@ -405,24 +405,24 @@ Stop a container .. http:post:: /containers/(id)/stop - Stop the container ``id`` + Stop the container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /containers/e90e34656806/stop?t=5 HTTP/1.1 - - **Example response**: + POST /containers/e90e34656806/stop?t=5 HTTP/1.1 - .. sourcecode:: http + **Example response**: - HTTP/1.1 204 OK - - :query t: number of seconds to wait before killing the container - :statuscode 204: no error - :statuscode 404: no such container - :statuscode 500: server error + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error Restart a container @@ -430,24 +430,24 @@ Restart a container .. http:post:: /containers/(id)/restart - Restart the container ``id`` + Restart the container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /containers/e90e34656806/restart?t=5 HTTP/1.1 - - **Example response**: + POST /containers/e90e34656806/restart?t=5 HTTP/1.1 - .. sourcecode:: http + **Example response**: - HTTP/1.1 204 OK - - :query t: number of seconds to wait before killing the container - :statuscode 204: no error - :statuscode 404: no such container - :statuscode 500: server error + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error Kill a container @@ -455,23 +455,23 @@ Kill a container .. http:post:: /containers/(id)/kill - Kill the container ``id`` + Kill the container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /containers/e90e34656806/kill HTTP/1.1 - - **Example response**: + POST /containers/e90e34656806/kill HTTP/1.1 - .. sourcecode:: http + **Example response**: - HTTP/1.1 204 OK - - :statuscode 204: no error - :statuscode 404: no such container - :statuscode 500: server error + .. sourcecode:: http + + HTTP/1.1 204 OK + + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error Attach to a container @@ -479,74 +479,74 @@ Attach to a container .. http:post:: /containers/(id)/attach - Attach to the container ``id`` + Attach to the container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1 - - **Example response**: + POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1 - .. sourcecode:: http + **Example response**: - HTTP/1.1 200 OK - Content-Type: application/vnd.docker.raw-stream + .. sourcecode:: http - {{ STREAM }} - - :query logs: 1/True/true or 0/False/false, return logs. Default false - :query stream: 1/True/true or 0/False/false, return stream. Default false - :query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false - :query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false - :query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false - :statuscode 200: no error - :statuscode 400: bad parameter - :statuscode 404: no such container - :statuscode 500: server error + HTTP/1.1 200 OK + Content-Type: application/vnd.docker.raw-stream - **Stream details**: + {{ STREAM }} - When using the TTY setting is enabled in - :http:post:`/containers/create`, the stream is the raw data - from the process PTY and client's stdin. When the TTY is - disabled, then the stream is multiplexed to separate stdout - and stderr. + :query logs: 1/True/true or 0/False/false, return logs. Default false + :query stream: 1/True/true or 0/False/false, return stream. Default false + :query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false + :query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false + :query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error - The format is a **Header** and a **Payload** (frame). + **Stream details**: - **HEADER** + When using the TTY setting is enabled in + :http:post:`/containers/create`, the stream is the raw data + from the process PTY and client's stdin. When the TTY is + disabled, then the stream is multiplexed to separate stdout + and stderr. - The header will contain the information on which stream write - the stream (stdout or stderr). It also contain the size of - the associated frame encoded on the last 4 bytes (uint32). + The format is a **Header** and a **Payload** (frame). - It is encoded on the first 8 bytes like this:: + **HEADER** - header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4} + The header will contain the information on which stream write + the stream (stdout or stderr). It also contain the size of + the associated frame encoded on the last 4 bytes (uint32). - ``STREAM_TYPE`` can be: + It is encoded on the first 8 bytes like this:: - - 0: stdin (will be writen on stdout) - - 1: stdout - - 2: stderr + header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4} - ``SIZE1, SIZE2, SIZE3, SIZE4`` are the 4 bytes of the uint32 size encoded as big endian. + ``STREAM_TYPE`` can be: - **PAYLOAD** + - 0: stdin (will be writen on stdout) + - 1: stdout + - 2: stderr - The payload is the raw stream. + ``SIZE1, SIZE2, SIZE3, SIZE4`` are the 4 bytes of the uint32 size encoded as big endian. - **IMPLEMENTATION** + **PAYLOAD** - The simplest way to implement the Attach protocol is the following: + The payload is the raw stream. - 1) Read 8 bytes - 2) chose stdout or stderr depending on the first byte - 3) Extract the frame size from the last 4 byets - 4) Read the extracted size and output it on the correct output - 5) Goto 1) + **IMPLEMENTATION** + + The simplest way to implement the Attach protocol is the following: + + 1) Read 8 bytes + 2) chose stdout or stderr depending on the first byte + 3) Extract the frame size from the last 4 byets + 4) Read the extracted size and output it on the correct output + 5) Goto 1) @@ -555,26 +555,26 @@ Wait a container .. http:post:: /containers/(id)/wait - Block until container ``id`` stops, then returns the exit code + Block until container ``id`` stops, then returns the exit code - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /containers/16253994b7c4/wait HTTP/1.1 - - **Example response**: + POST /containers/16253994b7c4/wait HTTP/1.1 - .. sourcecode:: http + **Example response**: - HTTP/1.1 200 OK - Content-Type: application/json + .. sourcecode:: http - {"StatusCode":0} - - :statuscode 200: no error - :statuscode 404: no such container - :statuscode 500: server error + HTTP/1.1 200 OK + Content-Type: application/json + + {"StatusCode":0} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error Remove a container @@ -582,9 +582,9 @@ Remove a container .. http:delete:: /containers/(id) - Remove the container ``id`` from the filesystem + Remove the container ``id`` from the filesystem - **Example request**: + **Example request**: .. sourcecode:: http @@ -594,11 +594,11 @@ Remove a container .. sourcecode:: http - HTTP/1.1 204 OK + HTTP/1.1 204 OK - :query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false + :query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false :statuscode 204: no error - :statuscode 400: bad parameter + :statuscode 400: bad parameter :statuscode 404: no such container :statuscode 500: server error @@ -608,31 +608,31 @@ Copy files or folders from a container .. http:post:: /containers/(id)/copy - Copy files or folders of container ``id`` + Copy files or folders of container ``id`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - POST /containers/4fa6e0f0c678/copy HTTP/1.1 - Content-Type: application/json + POST /containers/4fa6e0f0c678/copy HTTP/1.1 + Content-Type: application/json - { - "Resource":"test.txt" - } + { + "Resource":"test.txt" + } - **Example response**: + **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 200 OK - Content-Type: application/octet-stream - - {{ STREAM }} + HTTP/1.1 200 OK + Content-Type: application/octet-stream - :statuscode 200: no error - :statuscode 404: no such container - :statuscode 500: server error + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error 2.2 Images @@ -643,41 +643,41 @@ List Images .. http:get:: /images/json - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - GET /images/json?all=0 HTTP/1.1 + GET /images/json?all=0 HTTP/1.1 - **Example response**: + **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 200 OK - Content-Type: application/json - - { - "RepoTags": [ - "ubuntu:12.04", - "ubuntu:precise", - "ubuntu:latest" - ], - "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", - "Created": 1365714795, - "Size": 131506275, - "VirtualSize": 131506275 - } - { - "RepoTags": [ - "ubuntu:12.10", - "ubuntu:quantal" - ], - "ParentId": "27cf784147099545", - "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "Created": 1364102658, - "Size": 24653, - "VirtualSize": 180116135 - } + HTTP/1.1 200 OK + Content-Type: application/json + + { + "RepoTags": [ + "ubuntu:12.04", + "ubuntu:precise", + "ubuntu:latest" + ], + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Created": 1365714795, + "Size": 131506275, + "VirtualSize": 131506275 + } + { + "RepoTags": [ + "ubuntu:12.10", + "ubuntu:quantal" + ], + "ParentId": "27cf784147099545", + "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Created": 1364102658, + "Size": 24653, + "VirtualSize": 180116135 + } Create an image @@ -685,9 +685,9 @@ Create an image .. http:post:: /images/create - Create an image, either by pull it from the registry or by importing it + Create an image, either by pull it from the registry or by importing it - **Example request**: + **Example request**: .. sourcecode:: http @@ -698,23 +698,23 @@ Create an image .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: application/json + Content-Type: application/json - {"status":"Pulling..."} - {"status":"Pulling", "progress":"1 B/ 100 B", "progressDetail":{"current":1, "total":100}} - {"error":"Invalid..."} - ... + {"status":"Pulling..."} + {"status":"Pulling", "progress":"1 B/ 100 B", "progressDetail":{"current":1, "total":100}} + {"error":"Invalid..."} + ... - When using this endpoint to pull an image from the registry, - the ``X-Registry-Auth`` header can be used to include a - base64-encoded AuthConfig object. + When using this endpoint to pull an image from the registry, + the ``X-Registry-Auth`` header can be used to include a + base64-encoded AuthConfig object. :query fromImage: name of the image to pull - :query fromSrc: source to import, - means stdin + :query fromSrc: source to import, - means stdin :query repo: repository - :query tag: tag - :query registry: the registry to pull from - :reqheader X-Registry-Auth: base64-encoded AuthConfig object + :query tag: tag + :query registry: the registry to pull from + :reqheader X-Registry-Auth: base64-encoded AuthConfig object :statuscode 200: no error :statuscode 500: server error @@ -725,27 +725,27 @@ Insert a file in an image .. http:post:: /images/(name)/insert - Insert a file from ``url`` in the image ``name`` at ``path`` + Insert a file from ``url`` in the image ``name`` at ``path`` - **Example request**: + **Example request**: .. sourcecode:: http POST /images/test/insert?path=/usr&url=myurl HTTP/1.1 - **Example response**: + **Example response**: .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: application/json + Content-Type: application/json - {"status":"Inserting..."} - {"status":"Inserting", "progress":"1/? (n/a)", "progressDetail":{"current":1}} - {"error":"Invalid..."} - ... + {"status":"Inserting..."} + {"status":"Inserting", "progress":"1/? (n/a)", "progressDetail":{"current":1}} + {"error":"Invalid..."} + ... - :statuscode 200: no error + :statuscode 200: no error :statuscode 500: server error @@ -754,52 +754,52 @@ Inspect an image .. http:get:: /images/(name)/json - Return low-level information on the image ``name`` + Return low-level information on the image ``name`` - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http - GET /images/base/json HTTP/1.1 + GET /images/base/json HTTP/1.1 - **Example response**: + **Example response**: .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: application/json + Content-Type: application/json - { - "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "parent":"27cf784147099545", - "created":"2013-03-23T22:24:18.818426-07:00", - "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", - "container_config": - { - "Hostname":"", - "User":"", - "Memory":0, - "MemorySwap":0, - "AttachStdin":false, - "AttachStdout":false, - "AttachStderr":false, - "PortSpecs":null, - "Tty":true, - "OpenStdin":true, - "StdinOnce":false, - "Env":null, - "Cmd": ["/bin/bash"] - ,"Dns":null, - "Image":"base", - "Volumes":null, - "VolumesFrom":"", - "WorkingDir":"" - }, - "Size": 6824592 - } + { + "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "parent":"27cf784147099545", + "created":"2013-03-23T22:24:18.818426-07:00", + "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", + "container_config": + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":false, + "AttachStderr":false, + "PortSpecs":null, + "Tty":true, + "OpenStdin":true, + "StdinOnce":false, + "Env":null, + "Cmd": ["/bin/bash"] + ,"Dns":null, + "Image":"base", + "Volumes":null, + "VolumesFrom":"", + "WorkingDir":"" + }, + "Size": 6824592 + } - :statuscode 200: no error - :statuscode 404: no such image + :statuscode 200: no error + :statuscode 404: no such image :statuscode 500: server error @@ -821,18 +821,18 @@ Get the history of an image .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: application/json + Content-Type: application/json - { - "Id":"b750fe79269d", - "Created":1364102658, - "CreatedBy":"/bin/bash" - } - { - "Id":"27cf78414709", - "Created":1364068391, - "CreatedBy":"" - } + { + "Id":"b750fe79269d", + "Created":1364102658, + "CreatedBy":"/bin/bash" + } + { + "Id":"27cf78414709", + "Created":1364068391, + "CreatedBy":"" + } :statuscode 200: no error :statuscode 404: no such image @@ -876,26 +876,26 @@ Tag an image into a repository .. http:post:: /images/(name)/tag - Tag the image ``name`` into a repository + Tag the image ``name`` into a repository **Example request**: .. sourcecode:: http - - POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1 - **Example response**: + POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1 + + **Example response**: .. sourcecode:: http HTTP/1.1 200 OK - :query repo: The repository to tag in - :query force: 1/True/true or 0/False/false, default false - :statuscode 200: no error - :statuscode 400: bad parameter - :statuscode 404: no such image - :statuscode 409: conflict + :query repo: The repository to tag in + :query force: 1/True/true or 0/False/false, default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such image + :statuscode 409: conflict :statuscode 500: server error @@ -904,30 +904,30 @@ Remove an image .. http:delete:: /images/(name) - Remove the image ``name`` from the filesystem - - **Example request**: + Remove the image ``name`` from the filesystem - .. sourcecode:: http - - DELETE /images/test HTTP/1.1 - - **Example response**: + **Example request**: .. sourcecode:: http - HTTP/1.1 200 OK - Content-type: application/json + DELETE /images/test HTTP/1.1 - [ - {"Untagged":"3e2f21a89f"}, - {"Deleted":"3e2f21a89f"}, - {"Deleted":"53b4f83ac9"} - ] + **Example response**: - :statuscode 200: no error + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-type: application/json + + [ + {"Untagged":"3e2f21a89f"}, + {"Deleted":"3e2f21a89f"}, + {"Deleted":"53b4f83ac9"} + ] + + :statuscode 200: no error :statuscode 404: no such image - :statuscode 409: conflict + :statuscode 409: conflict :statuscode 500: server error @@ -936,54 +936,54 @@ Search images .. http:get:: /images/search - Search for an image in the docker index. - - .. note:: - - The response keys have changed from API v1.6 to reflect the JSON - sent by the registry server to the docker daemon's request. - - **Example request**: + Search for an image in the docker index. + + .. note:: + + The response keys have changed from API v1.6 to reflect the JSON + sent by the registry server to the docker daemon's request. + + **Example request**: .. sourcecode:: http GET /images/search?term=sshd HTTP/1.1 - **Example response**: + **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 200 OK - Content-Type: application/json - - [ - { - "description": "", - "is_official": false, - "is_trusted": false, - "name": "wma55/u1210sshd", - "star_count": 0 - }, - { - "description": "", - "is_official": false, - "is_trusted": false, - "name": "jdswinbank/sshd", - "star_count": 0 - }, - { - "description": "", - "is_official": false, - "is_trusted": false, - "name": "vgauthier/sshd", - "star_count": 0 - } - ... - ] + HTTP/1.1 200 OK + Content-Type: application/json - :query term: term to search - :statuscode 200: no error - :statuscode 500: server error + [ + { + "description": "", + "is_official": false, + "is_trusted": false, + "name": "wma55/u1210sshd", + "star_count": 0 + }, + { + "description": "", + "is_official": false, + "is_trusted": false, + "name": "jdswinbank/sshd", + "star_count": 0 + }, + { + "description": "", + "is_official": false, + "is_trusted": false, + "name": "vgauthier/sshd", + "star_count": 0 + } + ... + ] + + :query term: term to search + :statuscode 200: no error + :statuscode 500: server error 2.3 Misc @@ -1018,7 +1018,7 @@ Build an image from Dockerfile via stdin The stream must be a tar archive compressed with one of the following algorithms: identity (no compression), gzip, bzip2, - xz. + xz. The archive must include a file called ``Dockerfile`` at its root. It may include any number of other files, which will be @@ -1047,14 +1047,14 @@ Check auth configuration .. sourcecode:: http POST /auth HTTP/1.1 - Content-Type: application/json + Content-Type: application/json - { - "username":"hannibal", - "password:"xxxx", - "email":"hannibal@a-team.com", - "serveraddress":"https://index.docker.io/v1/" - } + { + "username":"hannibal", + "password:"xxxx", + "email":"hannibal@a-team.com", + "serveraddress":"https://index.docker.io/v1/" + } **Example response**: @@ -1072,9 +1072,9 @@ Display system-wide information .. http:get:: /info - Display system-wide information - - **Example request**: + Display system-wide information + + **Example request**: .. sourcecode:: http @@ -1085,18 +1085,18 @@ Display system-wide information .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: application/json + Content-Type: application/json - { - "Containers":11, - "Images":16, - "Debug":false, - "NFd": 11, - "NGoroutines":21, - "MemoryLimit":true, - "SwapLimit":false, - "IPv4Forwarding":true - } + { + "Containers":11, + "Images":16, + "Debug":false, + "NFd": 11, + "NGoroutines":21, + "MemoryLimit":true, + "SwapLimit":false, + "IPv4Forwarding":true + } :statuscode 200: no error :statuscode 500: server error @@ -1107,9 +1107,9 @@ Show the docker version information .. http:get:: /version - Show the docker version information + Show the docker version information - **Example request**: + **Example request**: .. sourcecode:: http @@ -1120,16 +1120,16 @@ Show the docker version information .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: application/json + Content-Type: application/json - { - "Version":"0.2.2", - "GitCommit":"5a2a5cc+CHANGES", - "GoVersion":"go1.0.3" - } + { + "Version":"0.2.2", + "GitCommit":"5a2a5cc+CHANGES", + "GoVersion":"go1.0.3" + } :statuscode 200: no error - :statuscode 500: server error + :statuscode 500: server error Create a new image from a container's changes @@ -1150,7 +1150,7 @@ Create a new image from a container's changes .. sourcecode:: http HTTP/1.1 201 OK - Content-Type: application/vnd.docker.raw-stream + Content-Type: application/vnd.docker.raw-stream {"Id":"596069db4bf5"} @@ -1170,11 +1170,11 @@ Monitor Docker's events .. http:get:: /events - Get events from docker, either in real time via streaming, or via polling (using `since`) + Get events from docker, either in real time via streaming, or via polling (using `since`) - **Example request**: + **Example request**: - .. sourcecode:: http + .. sourcecode:: http GET /events?since=1374067924 @@ -1183,14 +1183,14 @@ Monitor Docker's events .. sourcecode:: http HTTP/1.1 200 OK - Content-Type: application/json + Content-Type: application/json - {"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924} - {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924} - {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966} - {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970} + {"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966} + {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970} - :query since: timestamp used for polling + :query since: timestamp used for polling :statuscode 200: no error :statuscode 500: server error @@ -1199,22 +1199,23 @@ Get a tarball containing all images and tags in a repository .. http:get:: /images/(name)/get - Get a tarball containing all images and metadata for the repository specified by ``name``. + Get a tarball containing all images and metadata for the repository specified by ``name``. - **Example request** + **Example request** + + .. sourcecode:: http - .. sourcecode:: http - GET /images/ubuntu/get - **Example response**: + **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 200 OK - Content-Type: application/x-tar + HTTP/1.1 200 OK + Content-Type: application/x-tar + + Binary data stream - Binary data stream :statuscode 200: no error :statuscode 500: server error @@ -1223,24 +1224,24 @@ Load a tarball with a set of images and tags into docker .. http:post:: /images/load - Load a set of images and tags into the docker repository. + Load a set of images and tags into the docker repository. - **Example request** + **Example request** - .. sourcecode:: http + .. sourcecode:: http - POST /images/load + POST /images/load - Tarball in body + Tarball in body - **Example response**: + **Example response**: - .. sourcecode:: http + .. sourcecode:: http - HTTP/1.1 200 OK + HTTP/1.1 200 OK - :statuscode 200: no error - :statuscode 500: server error + :statuscode 200: no error + :statuscode 500: server error 3. Going further ================ @@ -1258,7 +1259,7 @@ Here are the steps of 'docker run' : * If you are not in detached mode: * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1 * If in detached mode or only stdin is attached: - * Display the container's id + * Display the container's id 3.2 Hijacking @@ -1274,4 +1275,3 @@ To enable cross origin requests to the remote api add the flag "-api-enable-cors .. code-block:: bash docker -d -H="192.168.1.9:4243" -api-enable-cors - From 7fbc315aa96f75322643c3e94b418fead6925144 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 15 Jan 2014 13:52:35 -0800 Subject: [PATCH 049/364] move viz to a job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 5 +---- engine/http.go | 6 ++++-- server.go | 23 ++++++++++++++--------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/api.go b/api.go index bb076034e4..cd9a840eb3 100644 --- a/api.go +++ b/api.go @@ -238,10 +238,7 @@ func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.R w.WriteHeader(http.StatusNotFound) return fmt.Errorf("This is now implemented in the client.") } - - if err := srv.ImagesViz(w); err != nil { - return err - } + srv.Eng.ServeHTTP(w, r) return nil } diff --git a/engine/http.go b/engine/http.go index b115912e2c..c0418bcfb0 100644 --- a/engine/http.go +++ b/engine/http.go @@ -16,8 +16,10 @@ import ( // as the exit status. // func (eng *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) { - jobName := path.Base(r.URL.Path) - jobArgs, exists := r.URL.Query()["a"] + var ( + jobName = path.Base(r.URL.Path) + jobArgs, exists = r.URL.Query()["a"] + ) if !exists { jobArgs = []string{} } diff --git a/server.go b/server.go index 2a56d5f439..ad163d11be 100644 --- a/server.go +++ b/server.go @@ -135,6 +135,10 @@ func jobInitApi(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + if err := job.Eng.Register("viz", srv.ImagesViz); err != nil { + job.Error(err) + return engine.StatusErr + } return engine.StatusOK } @@ -538,12 +542,12 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. return nil } -func (srv *Server) ImagesViz(out io.Writer) error { +func (srv *Server) ImagesViz(job *engine.Job) engine.Status { images, _ := srv.runtime.graph.Map() if images == nil { - return nil + return engine.StatusOK } - out.Write([]byte("digraph docker {\n")) + job.Stdout.Write([]byte("digraph docker {\n")) var ( parentImage *Image @@ -552,12 +556,13 @@ func (srv *Server) ImagesViz(out io.Writer) error { for _, image := range images { parentImage, err = image.GetParent() if err != nil { - return fmt.Errorf("Error while getting parent image: %v", err) + job.Errorf("Error while getting parent image: %v", err) + return engine.StatusErr } if parentImage != nil { - out.Write([]byte(" \"" + parentImage.ID + "\" -> \"" + image.ID + "\"\n")) + job.Stdout.Write([]byte(" \"" + parentImage.ID + "\" -> \"" + image.ID + "\"\n")) } else { - out.Write([]byte(" base -> \"" + image.ID + "\" [style=invis]\n")) + job.Stdout.Write([]byte(" base -> \"" + image.ID + "\" [style=invis]\n")) } } @@ -570,10 +575,10 @@ func (srv *Server) ImagesViz(out io.Writer) error { } for id, repos := range reporefs { - out.Write([]byte(" \"" + id + "\" [label=\"" + id + "\\n" + strings.Join(repos, "\\n") + "\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n")) + job.Stdout.Write([]byte(" \"" + id + "\" [label=\"" + id + "\\n" + strings.Join(repos, "\\n") + "\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n")) } - out.Write([]byte(" base [style=invisible]\n}\n")) - return nil + job.Stdout.Write([]byte(" base [style=invisible]\n}\n")) + return engine.StatusOK } func (srv *Server) Images(job *engine.Job) engine.Status { From a98e40fa73cdba36b7a7032917e58233ef31ea59 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 15 Jan 2014 14:47:35 -0800 Subject: [PATCH 050/364] update TODO Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- REMOTE_TODO.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/REMOTE_TODO.md b/REMOTE_TODO.md index 5bc8d0bf7f..2b7dad5aeb 100644 --- a/REMOTE_TODO.md +++ b/REMOTE_TODO.md @@ -4,16 +4,16 @@ TODO "/events": getEvents, N ok "/info": getInfo, 1 ok "/version": getVersion, 1 -... "/images/json": getImagesJSON, N -TODO "/images/viz": getImagesViz, 0 yes -TODO "/images/search": getImagesSearch, N -#3490 "/images/{name:.*}/get": getImagesGet, 0 -TODO "/images/{name:.*}/history": getImagesHistory, N +ok "/images/json": getImagesJSON, N +ok "/images/viz": getImagesViz, 0 yes +#3615 "/images/search": getImagesSearch, N +ok "/images/{name:.*}/get": getImagesGet, 0 +ok "/images/{name:.*}/history": getImagesHistory, N TODO "/images/{name:.*}/json": getImagesByName, 1 TODO "/containers/ps": getContainersJSON, N TODO "/containers/json": getContainersJSON, 1 ok "/containers/{name:.*}/export": getContainersExport, 0 -TODO "/containers/{name:.*}/changes": getContainersChanges, N +... "/containers/{name:.*}/changes": getContainersChanges, N TODO "/containers/{name:.*}/json": getContainersByName, 1 TODO "/containers/{name:.*}/top": getContainersTop, N #3512 "/containers/{name:.*}/attach/ws": wsContainersAttach, 0 yes @@ -23,22 +23,22 @@ TODO "/auth": postAuth, 0 ok "/commit": postCommit, 0 TODO "/build": postBuild, 0 yes TODO "/images/create": postImagesCreate, N yes yes (pull) -TODO "/images/{name:.*}/insert": postImagesInsert, N yes yes +#3559 "/images/{name:.*}/insert": postImagesInsert, N yes yes TODO "/images/load": postImagesLoad, 1 yes (stdin) TODO "/images/{name:.*}/push": postImagesPush, N yes ok "/images/{name:.*}/tag": postImagesTag, 0 ok "/containers/create": postContainersCreate, 0 ok "/containers/{name:.*}/kill": postContainersKill, 0 -#3476 "/containers/{name:.*}/restart": postContainersRestart, 0 +ok "/containers/{name:.*}/restart": postContainersRestart, 0 ok "/containers/{name:.*}/start": postContainersStart, 0 ok "/containers/{name:.*}/stop": postContainersStop, 0 ok "/containers/{name:.*}/wait": postContainersWait, 0 ok "/containers/{name:.*}/resize": postContainersResize, 0 #3512 "/containers/{name:.*}/attach": postContainersAttach, 0 yes -TODO "/containers/{name:.*}/copy": postContainersCopy, 0 yes +#3560 "/containers/{name:.*}/copy": postContainersCopy, 0 yes **DELETE** -#3180 "/containers/{name:.*}": deleteContainers, 0 +ok "/containers/{name:.*}": deleteContainers, 0 TODO "/images/{name:.*}": deleteImages, N **OPTIONS** From e2fa3f56df02818f007f929824293f9da6b2db75 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 9 Jan 2014 16:37:56 -0800 Subject: [PATCH 051/364] move copy to a job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 6 +++--- server.go | 29 +++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/api.go b/api.go index cd9a840eb3..4391d5cd24 100644 --- a/api.go +++ b/api.go @@ -1035,7 +1035,6 @@ func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r * if vars == nil { return fmt.Errorf("Missing parameter") } - name := vars["name"] copyData := &APICopy{} contentType := r.Header.Get("Content-Type") @@ -1054,9 +1053,10 @@ func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r * copyData.Resource = copyData.Resource[1:] } - if err := srv.ContainerCopy(name, copyData.Resource, w); err != nil { + job := srv.Eng.Job("container_copy", vars["name"], copyData.Resource) + job.Stdout.Add(w) + if err := job.Run(); err != nil { utils.Errorf("%s", err.Error()) - return err } return nil } diff --git a/server.go b/server.go index ad163d11be..01e986a00b 100644 --- a/server.go +++ b/server.go @@ -139,6 +139,10 @@ func jobInitApi(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + if err := job.Eng.Register("container_copy", srv.ContainerCopy); err != nil { + job.Error(err) + return engine.StatusErr + } return engine.StatusOK } @@ -2058,20 +2062,33 @@ func (srv *Server) ImageInspect(name string) (*Image, error) { return nil, fmt.Errorf("No such image: %s", name) } -func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) error { +func (srv *Server) ContainerCopy(job *engine.Job) engine.Status { + if len(job.Args) != 2 { + job.Errorf("Usage: %s CONTAINER RESOURCE\n", job.Name) + return engine.StatusErr + } + + var ( + name = job.Args[0] + resource = job.Args[1] + ) + if container := srv.runtime.Get(name); container != nil { data, err := container.Copy(resource) if err != nil { - return err + job.Error(err) + return engine.StatusErr } - if _, err := io.Copy(out, data); err != nil { - return err + if _, err := io.Copy(job.Stdout, data); err != nil { + job.Error(err) + return engine.StatusErr } - return nil + return engine.StatusOK } - return fmt.Errorf("No such container: %s", name) + job.Errorf("No such container: %s", name) + return engine.StatusErr } From 0fb8a55753269603eb8f01f725675ceacae79c29 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 9 Jan 2014 10:06:16 -0800 Subject: [PATCH 052/364] move insert to a job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 19 +++++++---------- integration/server_test.go | 9 +++----- server.go | 43 +++++++++++++++++++++++++++++--------- 3 files changed, 43 insertions(+), 28 deletions(-) diff --git a/api.go b/api.go index 4391d5cd24..5e456bbd85 100644 --- a/api.go +++ b/api.go @@ -510,24 +510,19 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht if err := parseForm(r); err != nil { return err } - - url := r.Form.Get("url") - path := r.Form.Get("path") if vars == nil { return fmt.Errorf("Missing parameter") } - name := vars["name"] if version > 1.0 { w.Header().Set("Content-Type", "application/json") } - sf := utils.NewStreamFormatter(version > 1.0) - err := srv.ImageInsert(name, url, path, w, sf) - if err != nil { - if sf.Used() { - w.Write(sf.FormatError(err)) - return nil - } - return err + + job := srv.Eng.Job("insert", vars["name"], r.Form.Get("url"), r.Form.Get("path")) + job.SetenvBool("json", version > 1.0) + job.Stdout.Add(w) + if err := job.Run(); err != nil { + sf := utils.NewStreamFormatter(version > 1.0) + w.Write(sf.FormatError(err)) } return nil diff --git a/integration/server_test.go b/integration/server_test.go index e755d14728..6c26f56174 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -2,8 +2,6 @@ package docker import ( "github.com/dotcloud/docker" - "github.com/dotcloud/docker/utils" - "io/ioutil" "strings" "testing" ) @@ -365,20 +363,19 @@ func TestImageInsert(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() srv := mkServerFromEngine(eng, t) - sf := utils.NewStreamFormatter(true) // bad image name fails - if err := srv.ImageInsert("foo", "https://www.docker.io/static/img/docker-top-logo.png", "/foo", ioutil.Discard, sf); err == nil { + if err := srv.Eng.Job("insert", "foo", "https://www.docker.io/static/img/docker-top-logo.png", "/foo").Run(); err == nil { t.Fatal("expected an error and got none") } // bad url fails - if err := srv.ImageInsert(unitTestImageID, "http://bad_host_name_that_will_totally_fail.com/", "/foo", ioutil.Discard, sf); err == nil { + if err := srv.Eng.Job("insert", unitTestImageID, "http://bad_host_name_that_will_totally_fail.com/", "/foo").Run(); err == nil { t.Fatal("expected an error and got none") } // success returns nil - if err := srv.ImageInsert(unitTestImageID, "https://www.docker.io/static/img/docker-top-logo.png", "/foo", ioutil.Discard, sf); err != nil { + if err := srv.Eng.Job("insert", unitTestImageID, "https://www.docker.io/static/img/docker-top-logo.png", "/foo").Run(); err != nil { t.Fatalf("expected no error, but got %v", err) } } diff --git a/server.go b/server.go index 01e986a00b..ec3e101068 100644 --- a/server.go +++ b/server.go @@ -143,6 +143,10 @@ func jobInitApi(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + if err := job.Eng.Register("insert", srv.ImageInsert); err != nil { + job.Error(err) + return engine.StatusErr + } return engine.StatusOK } @@ -511,39 +515,58 @@ func (srv *Server) ImagesSearch(term string) ([]registry.SearchResult, error) { return results.Results, nil } -func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.StreamFormatter) error { - out = utils.NewWriteFlusher(out) +func (srv *Server) ImageInsert(job *engine.Job) engine.Status { + if len(job.Args) != 3 { + job.Errorf("Usage: %s IMAGE URL PATH\n", job.Name) + return engine.StatusErr + } + + var ( + name = job.Args[0] + url = job.Args[1] + path = job.Args[2] + ) + + sf := utils.NewStreamFormatter(job.GetenvBool("json")) + + out := utils.NewWriteFlusher(job.Stdout) img, err := srv.runtime.repositories.LookupImage(name) if err != nil { - return err + job.Error(err) + return engine.StatusErr } file, err := utils.Download(url) if err != nil { - return err + job.Error(err) + return engine.StatusErr } defer file.Body.Close() config, _, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.capabilities) if err != nil { - return err + job.Error(err) + return engine.StatusErr } c, _, err := srv.runtime.Create(config, "") if err != nil { - return err + job.Error(err) + return engine.StatusErr } if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf, false, "", "Downloading"), path); err != nil { - return err + job.Error(err) + return engine.StatusErr } // FIXME: Handle custom repo, tag comment, author img, err = srv.runtime.Commit(c, "", "", img.Comment, img.Author, nil) if err != nil { - return err + out.Write(sf.FormatError(err)) + return engine.StatusOK } - out.Write(sf.FormatStatus(img.ID, "")) - return nil + out.Write(sf.FormatStatus("", img.ID)) + return engine.StatusOK } func (srv *Server) ImagesViz(job *engine.Job) engine.Status { From a33bc3018bf091725b7faca9a6039a07b6fd0ca7 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 10 Jan 2014 14:54:54 -0800 Subject: [PATCH 053/364] use the same 'Used' method as before Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 3 +++ engine/streams.go | 9 +++++++++ server.go | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/api.go b/api.go index 5e456bbd85..148202c2e3 100644 --- a/api.go +++ b/api.go @@ -521,6 +521,9 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht job.SetenvBool("json", version > 1.0) job.Stdout.Add(w) if err := job.Run(); err != nil { + if !job.Stdout.Used() { + return err + } sf := utils.NewStreamFormatter(version > 1.0) w.Write(sf.FormatError(err)) } diff --git a/engine/streams.go b/engine/streams.go index 824f0a4ab2..fbce6e632a 100644 --- a/engine/streams.go +++ b/engine/streams.go @@ -12,6 +12,7 @@ type Output struct { sync.Mutex dests []io.Writer tasks sync.WaitGroup + used bool } // NewOutput returns a new Output object with no destinations attached. @@ -20,6 +21,13 @@ func NewOutput() *Output { return &Output{} } +// Return true if something was written on this output +func (o *Output) Used() bool { + o.Mutex.Lock() + defer o.Mutex.Unlock() + return o.used +} + // Add attaches a new destination to the Output. Any data subsequently written // to the output will be written to the new destination in addition to all the others. // This method is thread-safe. @@ -82,6 +90,7 @@ func (o *Output) AddString(dst *string) error { func (o *Output) Write(p []byte) (n int, err error) { o.Mutex.Lock() defer o.Mutex.Unlock() + o.used = true var firstErr error for _, dst := range o.dests { _, err := dst.Write(p) diff --git a/server.go b/server.go index ec3e101068..2057b477a1 100644 --- a/server.go +++ b/server.go @@ -563,7 +563,7 @@ func (srv *Server) ImageInsert(job *engine.Job) engine.Status { img, err = srv.runtime.Commit(c, "", "", img.Comment, img.Author, nil) if err != nil { out.Write(sf.FormatError(err)) - return engine.StatusOK + return engine.StatusErr } out.Write(sf.FormatStatus("", img.ID)) return engine.StatusOK From 2edcebc0275b726e87575830bb3e96dfb69d7665 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 15 Jan 2014 15:41:57 -0800 Subject: [PATCH 054/364] fix docker insert display Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 2057b477a1..b1d145e3c2 100644 --- a/server.go +++ b/server.go @@ -555,7 +555,7 @@ func (srv *Server) ImageInsert(job *engine.Job) engine.Status { return engine.StatusErr } - if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf, false, "", "Downloading"), path); err != nil { + if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf, false, utils.TruncateID(img.ID), "Downloading"), path); err != nil { job.Error(err) return engine.StatusErr } From e1d8543c7889c1bf456641fa1ee5f676820076b6 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 8 Jan 2014 14:05:05 -0800 Subject: [PATCH 055/364] move attach to a job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- REMOTE_TODO.md | 22 +++++++------- api.go | 73 +++++++++++++++-------------------------------- engine/streams.go | 11 +++++++ server.go | 46 ++++++++++++++++++++--------- 4 files changed, 78 insertions(+), 74 deletions(-) diff --git a/REMOTE_TODO.md b/REMOTE_TODO.md index 2b7dad5aeb..bef1d4a673 100644 --- a/REMOTE_TODO.md +++ b/REMOTE_TODO.md @@ -4,26 +4,26 @@ TODO "/events": getEvents, N ok "/info": getInfo, 1 ok "/version": getVersion, 1 -ok "/images/json": getImagesJSON, N +ok "/images/json": getImagesJSON, N ok "/images/viz": getImagesViz, 0 yes -#3615 "/images/search": getImagesSearch, N -ok "/images/{name:.*}/get": getImagesGet, 0 -ok "/images/{name:.*}/history": getImagesHistory, N -TODO "/images/{name:.*}/json": getImagesByName, 1 +ok "/images/search": getImagesSearch, N +ok "/images/{name:.*}/get": getImagesGet, 0 +ok "/images/{name:.*}/history": getImagesHistory, N +... "/images/{name:.*}/json": getImagesByName, 1 TODO "/containers/ps": getContainersJSON, N TODO "/containers/json": getContainersJSON, 1 ok "/containers/{name:.*}/export": getContainersExport, 0 -... "/containers/{name:.*}/changes": getContainersChanges, N -TODO "/containers/{name:.*}/json": getContainersByName, 1 +#3616 "/containers/{name:.*}/changes": getContainersChanges, N +... "/containers/{name:.*}/json": getContainersByName, 1 TODO "/containers/{name:.*}/top": getContainersTop, N -#3512 "/containers/{name:.*}/attach/ws": wsContainersAttach, 0 yes +ok "/containers/{name:.*}/attach/ws": wsContainersAttach, 0 yes **POST** TODO "/auth": postAuth, 0 yes ok "/commit": postCommit, 0 TODO "/build": postBuild, 0 yes TODO "/images/create": postImagesCreate, N yes yes (pull) -#3559 "/images/{name:.*}/insert": postImagesInsert, N yes yes +ok "/images/{name:.*}/insert": postImagesInsert, N yes yes TODO "/images/load": postImagesLoad, 1 yes (stdin) TODO "/images/{name:.*}/push": postImagesPush, N yes ok "/images/{name:.*}/tag": postImagesTag, 0 @@ -34,8 +34,8 @@ ok "/containers/{name:.*}/start": postContainersStart, 0 ok "/containers/{name:.*}/stop": postContainersStop, 0 ok "/containers/{name:.*}/wait": postContainersWait, 0 ok "/containers/{name:.*}/resize": postContainersResize, 0 -#3512 "/containers/{name:.*}/attach": postContainersAttach, 0 yes -#3560 "/containers/{name:.*}/copy": postContainersCopy, 0 yes +ok "/containers/{name:.*}/attach": postContainersAttach, 0 yes +ok "/containers/{name:.*}/copy": postContainersCopy, 0 yes **DELETE** ok "/containers/{name:.*}": deleteContainers, 0 diff --git a/api.go b/api.go index 148202c2e3..cd8d174ad3 100644 --- a/api.go +++ b/api.go @@ -769,33 +769,11 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r if err := parseForm(r); err != nil { return err } - logs, err := getBoolParam(r.Form.Get("logs")) - if err != nil { - return err - } - stream, err := getBoolParam(r.Form.Get("stream")) - if err != nil { - return err - } - stdin, err := getBoolParam(r.Form.Get("stdin")) - if err != nil { - return err - } - stdout, err := getBoolParam(r.Form.Get("stdout")) - if err != nil { - return err - } - stderr, err := getBoolParam(r.Form.Get("stderr")) - if err != nil { - return err - } - if vars == nil { return fmt.Errorf("Missing parameter") } - name := vars["name"] - c, err := srv.ContainerInspect(name) + c, err := srv.ContainerInspect(vars["name"]) if err != nil { return err } @@ -830,51 +808,46 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r errStream = outStream } - if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, inStream, outStream, errStream); err != nil { + job := srv.Eng.Job("attach", vars["name"]) + job.Setenv("logs", r.Form.Get("logs")) + job.Setenv("stream", r.Form.Get("stream")) + job.Setenv("stdin", r.Form.Get("stdin")) + job.Setenv("stdout", r.Form.Get("stdout")) + job.Setenv("stderr", r.Form.Get("stderr")) + job.Stdin.Add(inStream) + job.Stdout.Add(outStream) + job.Stderr.Add(errStream) + if err := job.Run(); err != nil { fmt.Fprintf(outStream, "Error: %s\n", err) + } return nil } func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { return err } - logs, err := getBoolParam(r.Form.Get("logs")) - if err != nil { - return err - } - stream, err := getBoolParam(r.Form.Get("stream")) - if err != nil { - return err - } - stdin, err := getBoolParam(r.Form.Get("stdin")) - if err != nil { - return err - } - stdout, err := getBoolParam(r.Form.Get("stdout")) - if err != nil { - return err - } - stderr, err := getBoolParam(r.Form.Get("stderr")) - if err != nil { - return err - } - if vars == nil { return fmt.Errorf("Missing parameter") } - name := vars["name"] - if _, err := srv.ContainerInspect(name); err != nil { + if _, err := srv.ContainerInspect(vars["name"]); err != nil { return err } h := websocket.Handler(func(ws *websocket.Conn) { defer ws.Close() - - if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, ws, ws, ws); err != nil { + job := srv.Eng.Job("attach", vars["name"]) + job.Setenv("logs", r.Form.Get("logs")) + job.Setenv("stream", r.Form.Get("stream")) + job.Setenv("stdin", r.Form.Get("stdin")) + job.Setenv("stdout", r.Form.Get("stdout")) + job.Setenv("stderr", r.Form.Get("stderr")) + job.Stdin.Add(ws) + job.Stdout.Add(ws) + job.Stderr.Add(ws) + if err := job.Run(); err != nil { utils.Errorf("Error: %s", err) } }) diff --git a/engine/streams.go b/engine/streams.go index fbce6e632a..75b3c768b2 100644 --- a/engine/streams.go +++ b/engine/streams.go @@ -141,6 +141,17 @@ func (i *Input) Read(p []byte) (n int, err error) { return i.src.Read(p) } +// Closes the src +// Not thread safe on purpose +func (i *Input) Close() error { + if i.src != nil { + if closer, ok := i.src.(io.WriteCloser); ok { + return closer.Close() + } + } + return nil +} + // Add attaches a new source to the input. // Add can only be called once per input. Subsequent calls will // return an error. diff --git a/server.go b/server.go index b1d145e3c2..8e85235b09 100644 --- a/server.go +++ b/server.go @@ -147,6 +147,10 @@ func jobInitApi(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + if err := job.Eng.Register("attach", srv.ContainerAttach); err != nil { + job.Error(err) + return engine.StatusErr + } return engine.StatusOK } @@ -1980,10 +1984,25 @@ func (srv *Server) ContainerResize(job *engine.Job) engine.Status { return engine.StatusErr } -func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, stderr bool, inStream io.ReadCloser, outStream, errStream io.Writer) error { +func (srv *Server) ContainerAttach(job *engine.Job) engine.Status { + if len(job.Args) != 1 { + job.Errorf("Usage: %s CONTAINER\n", job.Name) + return engine.StatusErr + } + + var ( + name = job.Args[0] + logs = job.GetenvBool("logs") + stream = job.GetenvBool("stream") + stdin = job.GetenvBool("stdin") + stdout = job.GetenvBool("stdout") + stderr = job.GetenvBool("stderr") + ) + container := srv.runtime.Get(name) if container == nil { - return fmt.Errorf("No such container: %s", name) + job.Errorf("No such container: %s", name) + return engine.StatusErr } //logs @@ -1991,12 +2010,12 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std cLog, err := container.ReadLog("json") if err != nil && os.IsNotExist(err) { // Legacy logs - utils.Errorf("Old logs format") + utils.Debugf("Old logs format") if stdout { cLog, err := container.ReadLog("stdout") if err != nil { utils.Errorf("Error reading logs (stdout): %s", err) - } else if _, err := io.Copy(outStream, cLog); err != nil { + } else if _, err := io.Copy(job.Stdout, cLog); err != nil { utils.Errorf("Error streaming logs (stdout): %s", err) } } @@ -2004,7 +2023,7 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std cLog, err := container.ReadLog("stderr") if err != nil { utils.Errorf("Error reading logs (stderr): %s", err) - } else if _, err := io.Copy(errStream, cLog); err != nil { + } else if _, err := io.Copy(job.Stderr, cLog); err != nil { utils.Errorf("Error streaming logs (stderr): %s", err) } } @@ -2022,10 +2041,10 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std break } if l.Stream == "stdout" && stdout { - fmt.Fprintf(outStream, "%s", l.Log) + fmt.Fprintf(job.Stdout, "%s", l.Log) } if l.Stream == "stderr" && stderr { - fmt.Fprintf(errStream, "%s", l.Log) + fmt.Fprintf(job.Stderr, "%s", l.Log) } } } @@ -2034,7 +2053,8 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std //stream if stream { if container.State.IsGhost() { - return fmt.Errorf("Impossible to attach to a ghost container") + job.Errorf("Impossible to attach to a ghost container") + return engine.StatusErr } var ( @@ -2048,16 +2068,16 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std go func() { defer w.Close() defer utils.Debugf("Closing buffered stdin pipe") - io.Copy(w, inStream) + io.Copy(w, job.Stdin) }() cStdin = r - cStdinCloser = inStream + cStdinCloser = job.Stdin } if stdout { - cStdout = outStream + cStdout = job.Stdout } if stderr { - cStderr = errStream + cStderr = job.Stderr } <-container.Attach(cStdin, cStdinCloser, cStdout, cStderr) @@ -2068,7 +2088,7 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std container.Wait() } } - return nil + return engine.StatusOK } func (srv *Server) ContainerInspect(name string) (*Container, error) { From 1669b802cc3e7ce30f05e61630542c88696bbca1 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 15 Jan 2014 14:43:12 -0800 Subject: [PATCH 056/364] move search to a job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 26 +++++++++++++---- commands.go | 18 ++++++------ docs/sources/api/docker_remote_api_v1.9.rst | 14 ++++------ server.go | 31 ++++++++++++++++++--- 4 files changed, 62 insertions(+), 27 deletions(-) diff --git a/api.go b/api.go index cd8d174ad3..38871bcdfa 100644 --- a/api.go +++ b/api.go @@ -497,13 +497,29 @@ func getImagesSearch(srv *Server, version float64, w http.ResponseWriter, r *htt return err } - term := r.Form.Get("term") - outs, err := srv.ImagesSearch(term) - if err != nil { + var ( + buffer *bytes.Buffer + job = srv.Eng.Job("search", r.Form.Get("term")) + ) + if version >= 1.9 { + job.Stdout.Add(w) + } else { + buffer = bytes.NewBuffer(nil) + job.Stdout.Add(buffer) + } + if err := job.Run(); err != nil { return err } - - return writeJSON(w, http.StatusOK, outs) + if version < 1.9 { // Send as a valid JSON array + outs := engine.NewTable("", 0) + if _, err := outs.ReadFrom(buffer); err != nil { + return err + } + if _, err := outs.WriteListTo(w); err != nil { + return err + } + } + return nil } func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/commands.go b/commands.go index 454a02c797..4776a4312b 100644 --- a/commands.go +++ b/commands.go @@ -1655,30 +1655,28 @@ func (cli *DockerCli) CmdSearch(args ...string) error { if err != nil { return err } - - outs := []registry.SearchResult{} - err = json.Unmarshal(body, &outs) - if err != nil { + outs := engine.NewTable("star_count", 0) + if _, err := outs.ReadFrom(bytes.NewReader(body)); err != nil { return err } w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tTRUSTED\n") - for _, out := range outs { - if (*trusted && !out.IsTrusted) || (*stars > out.StarCount) { + for _, out := range outs.Data { + if (*trusted && !out.GetBool("is_trusted")) || (*stars > out.GetInt("star_count")) { continue } - desc := strings.Replace(out.Description, "\n", " ", -1) + desc := strings.Replace(out.Get("description"), "\n", " ", -1) desc = strings.Replace(desc, "\r", " ", -1) if !*noTrunc && len(desc) > 45 { desc = utils.Trunc(desc, 42) + "..." } - fmt.Fprintf(w, "%s\t%s\t%d\t", out.Name, desc, out.StarCount) - if out.IsOfficial { + fmt.Fprintf(w, "%s\t%s\t%d\t", out.Get("name"), desc, out.GetInt("star_count")) + if out.GetBool("is_official") { fmt.Fprint(w, "[OK]") } fmt.Fprint(w, "\t") - if out.IsTrusted { + if out.GetBool("is_trusted") { fmt.Fprint(w, "[OK]") } fmt.Fprint(w, "\n") diff --git a/docs/sources/api/docker_remote_api_v1.9.rst b/docs/sources/api/docker_remote_api_v1.9.rst index 5a8f9311b8..ba7f85031a 100644 --- a/docs/sources/api/docker_remote_api_v1.9.rst +++ b/docs/sources/api/docker_remote_api_v1.9.rst @@ -956,30 +956,28 @@ Search images HTTP/1.1 200 OK Content-Type: application/json - [ - { + { "description": "", "is_official": false, "is_trusted": false, "name": "wma55/u1210sshd", "star_count": 0 - }, - { + } + { "description": "", "is_official": false, "is_trusted": false, "name": "jdswinbank/sshd", "star_count": 0 - }, - { + } + { "description": "", "is_official": false, "is_trusted": false, "name": "vgauthier/sshd", "star_count": 0 - } + } ... - ] :query term: term to search :statuscode 200: no error diff --git a/server.go b/server.go index 8e85235b09..ebd10fd01c 100644 --- a/server.go +++ b/server.go @@ -151,6 +151,10 @@ func jobInitApi(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + if err := job.Eng.Register("search", srv.ImagesSearch); err != nil { + job.Error(err) + return engine.StatusErr + } return engine.StatusOK } @@ -507,16 +511,35 @@ func (srv *Server) recursiveLoad(address, tmpImageDir string) error { return nil } -func (srv *Server) ImagesSearch(term string) ([]registry.SearchResult, error) { +func (srv *Server) ImagesSearch(job *engine.Job) engine.Status { + if n := len(job.Args); n != 1 { + job.Errorf("Usage: %s TERM", job.Name) + return engine.StatusErr + } + term := job.Args[0] + r, err := registry.NewRegistry(nil, srv.HTTPRequestFactory(nil), auth.IndexServerAddress()) if err != nil { - return nil, err + job.Error(err) + return engine.StatusErr } results, err := r.SearchRepositories(term) if err != nil { - return nil, err + job.Error(err) + return engine.StatusErr } - return results.Results, nil + outs := engine.NewTable("star_count", 0) + for _, result := range results.Results { + out := &engine.Env{} + out.Import(result) + outs.Add(out) + } + outs.ReverseSort() + if _, err := outs.WriteTo(job.Stdout); err != nil { + job.Error(err) + return engine.StatusErr + } + return engine.StatusOK } func (srv *Server) ImageInsert(job *engine.Job) engine.Status { From 90ec5de430a79ab6b145899a9842c1db968341cf Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 14 Jan 2014 16:51:59 -0800 Subject: [PATCH 057/364] move changes to a job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 27 ++++++++++++--- commands.go | 18 +++++++--- docs/sources/api/docker_remote_api_v1.9.rst | 14 ++++---- integration/api_test.go | 8 ++--- server.go | 37 ++++++++++++++++++--- 5 files changed, 78 insertions(+), 26 deletions(-) diff --git a/api.go b/api.go index 38871bcdfa..3d6b49f4a7 100644 --- a/api.go +++ b/api.go @@ -341,13 +341,30 @@ func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r if vars == nil { return fmt.Errorf("Missing parameter") } - name := vars["name"] - changesStr, err := srv.ContainerChanges(name) - if err != nil { + var ( + buffer *bytes.Buffer + job = srv.Eng.Job("changes", vars["name"]) + ) + + if version >= 1.9 { + job.Stdout.Add(w) + } else { + buffer = bytes.NewBuffer(nil) + job.Stdout.Add(buffer) + } + if err := job.Run(); err != nil { return err } - - return writeJSON(w, http.StatusOK, changesStr) + if version < 1.9 { // Send as a valid JSON array + outs := engine.NewTable("", 0) + if _, err := outs.ReadFrom(buffer); err != nil { + return err + } + if _, err := outs.WriteListTo(w); err != nil { + return err + } + } + return nil } func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/commands.go b/commands.go index 4776a4312b..297fcdf786 100644 --- a/commands.go +++ b/commands.go @@ -1525,13 +1525,21 @@ func (cli *DockerCli) CmdDiff(args ...string) error { return err } - changes := []Change{} - err = json.Unmarshal(body, &changes) - if err != nil { + outs := engine.NewTable("", 0) + if _, err := outs.ReadFrom(bytes.NewReader(body)); err != nil { return err } - for _, change := range changes { - fmt.Fprintf(cli.out, "%s\n", change.String()) + for _, change := range outs.Data { + var kind string + switch change.GetInt("Kind") { + case archive.ChangeModify: + kind = "C" + case archive.ChangeAdd: + kind = "A" + case archive.ChangeDelete: + kind = "D" + } + fmt.Fprintf(cli.out, "%s %s\n", kind, change.Get("Path")) } return nil } diff --git a/docs/sources/api/docker_remote_api_v1.9.rst b/docs/sources/api/docker_remote_api_v1.9.rst index ba7f85031a..6546035a57 100644 --- a/docs/sources/api/docker_remote_api_v1.9.rst +++ b/docs/sources/api/docker_remote_api_v1.9.rst @@ -317,20 +317,18 @@ Inspect changes on a container's filesystem HTTP/1.1 200 OK Content-Type: application/json - [ - { + { "Path":"/dev", "Kind":0 - }, - { + } + { "Path":"/dev/kmsg", "Kind":1 - }, - { + } + { "Path":"/test", "Kind":1 - } - ] + } :statuscode 200: no error :statuscode 404: no such container diff --git a/integration/api_test.go b/integration/api_test.go index 3c807aecd7..30409c907b 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -408,15 +408,15 @@ func TestGetContainersChanges(t *testing.T) { t.Fatal(err) } assertHttpNotError(r, t) - changes := []docker.Change{} - if err := json.Unmarshal(r.Body.Bytes(), &changes); err != nil { + outs := engine.NewTable("", 0) + if _, err := outs.ReadFrom(r.Body); err != nil { t.Fatal(err) } // Check the changelog success := false - for _, elem := range changes { - if elem.Path == "/etc/passwd" && elem.Kind == 2 { + for _, elem := range outs.Data { + if elem.Get("Path") == "/etc/passwd" && elem.GetInt("Kind") == 2 { success = true } } diff --git a/server.go b/server.go index ebd10fd01c..dc05b4d0b5 100644 --- a/server.go +++ b/server.go @@ -155,6 +155,10 @@ func jobInitApi(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + if err := job.Eng.Register("changes", srv.ContainerChanges); err != nil { + job.Error(err) + return engine.StatusErr + } return engine.StatusOK } @@ -860,11 +864,36 @@ func (srv *Server) ContainerTop(name, psArgs string) (*APITop, error) { return nil, fmt.Errorf("No such container: %s", name) } -func (srv *Server) ContainerChanges(name string) ([]archive.Change, error) { - if container := srv.runtime.Get(name); container != nil { - return container.Changes() +func (srv *Server) ContainerChanges(job *engine.Job) engine.Status { + if n := len(job.Args); n != 1 { + job.Errorf("Usage: %s CONTAINER", job.Name) + return engine.StatusErr } - return nil, fmt.Errorf("No such container: %s", name) + name := job.Args[0] + if container := srv.runtime.Get(name); container != nil { + outs := engine.NewTable("", 0) + changes, err := container.Changes() + if err != nil { + job.Error(err) + return engine.StatusErr + } + for _, change := range changes { + out := &engine.Env{} + if err := out.Import(change); err != nil { + job.Error(err) + return engine.StatusErr + } + outs.Add(out) + } + if _, err := outs.WriteTo(job.Stdout); err != nil { + job.Error(err) + return engine.StatusErr + } + } else { + job.Errorf("No such container: %s", name) + return engine.StatusErr + } + return engine.StatusOK } func (srv *Server) Containers(all, size bool, n int, since, before string) []APIContainers { From 98111a4a94b8fdc0a44a1c7bfba2e83fbd0f9534 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 15 Jan 2014 22:12:32 -0700 Subject: [PATCH 058/364] Symlink vendor/MAINTAINERS over to hack/MAINTAINERS (since it depends explicitly on hack/vendor.sh) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- vendor/MAINTAINERS | 1 + 1 file changed, 1 insertion(+) create mode 120000 vendor/MAINTAINERS diff --git a/vendor/MAINTAINERS b/vendor/MAINTAINERS new file mode 120000 index 0000000000..72e53509b2 --- /dev/null +++ b/vendor/MAINTAINERS @@ -0,0 +1 @@ +../hack/MAINTAINERS \ No newline at end of file From 40b40cffb13758c03eff2cf05b1e5b0aa47bdb9d Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 15 Jan 2014 22:16:23 -0700 Subject: [PATCH 059/364] Clean and simplify vendor.sh while making it easier to maintain as well Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/vendor.sh | 84 ++++++++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index f76b30bba1..3326d5b004 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -1,54 +1,52 @@ #!/bin/bash +set -e + +cd "$(dirname "$BASH_SOURCE")/.." # Downloads dependencies into vendor/ directory -if [[ ! -d vendor ]]; then - mkdir vendor -fi -vendor_dir=${PWD}/vendor +mkdir -p vendor +cd vendor -rm_pkg_dir () { - PKG=$1 - REV=$2 - ( - set -e - cd $vendor_dir - if [[ -d src/$PKG ]]; then - echo "src/$PKG already exists. Removing." - rm -fr src/$PKG - fi - ) +clone() { + vcs=$1 + pkg=$2 + rev=$3 + + pkg_url=https://$pkg + target_dir=src/$pkg + + echo -n "$pkg @ $rev: " + + if [ -d $target_dir ]; then + echo -n 'rm old, ' + rm -fr $target_dir + fi + + echo -n 'clone, ' + case $vcs in + git) + git clone --quiet --no-checkout $pkg_url $target_dir + ( cd $target_dir && git reset --quiet --hard $rev ) + ;; + hg) + hg clone --quiet --updaterev $rev $pkg_url $target_dir + ;; + esac + + echo -n 'rm VCS, ' + ( cd $target_dir && rm -rf .{git,hg} ) + + echo done } -git_clone () { - PKG=$1 - REV=$2 - ( - set -e - rm_pkg_dir $PKG $REV - cd $vendor_dir && git clone http://$PKG src/$PKG - cd src/$PKG && git checkout -f $REV && rm -fr .git - ) -} +clone git github.com/kr/pty 3b1f6487b -hg_clone () { - PKG=$1 - REV=$2 - ( - set -e - rm_pkg_dir $PKG $REV - cd $vendor_dir && hg clone http://$PKG src/$PKG - cd src/$PKG && hg checkout -r $REV && rm -fr .hg - ) -} +clone git github.com/gorilla/context 708054d61e5 -git_clone github.com/kr/pty 3b1f6487b +clone git github.com/gorilla/mux 9b36453141c -git_clone github.com/gorilla/context/ 708054d61e5 +clone git github.com/syndtr/gocapability 3454319be2 -git_clone github.com/gorilla/mux/ 9b36453141c +clone hg code.google.com/p/go.net 84a4013f96e0 -git_clone github.com/syndtr/gocapability 3454319be2 - -hg_clone code.google.com/p/go.net 84a4013f96e0 - -hg_clone code.google.com/p/gosqlite 74691fb6f837 +clone hg code.google.com/p/gosqlite 74691fb6f837 From 812798a7d6659178300d00c470a19c7fe5e68d1c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 16 Jan 2014 14:58:20 -0800 Subject: [PATCH 060/364] move top job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 9 ++++----- server.go | 57 +++++++++++++++++++++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 23 deletions(-) diff --git a/api.go b/api.go index 3d6b49f4a7..cfef7a50ce 100644 --- a/api.go +++ b/api.go @@ -377,11 +377,10 @@ func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *ht if err := parseForm(r); err != nil { return err } - procsStr, err := srv.ContainerTop(vars["name"], r.Form.Get("ps_args")) - if err != nil { - return err - } - return writeJSON(w, http.StatusOK, procsStr) + + job := srv.Eng.Job("top", vars["name"], r.Form.Get("ps_args")) + job.Stdout.Add(w) + return job.Run() } func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/server.go b/server.go index dc05b4d0b5..3eaf5b13f5 100644 --- a/server.go +++ b/server.go @@ -159,6 +159,10 @@ func jobInitApi(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + if err := job.Eng.Register("top", srv.ContainerTop); err != nil { + job.Error(err) + return engine.StatusErr + } return engine.StatusOK } @@ -804,28 +808,40 @@ func (srv *Server) ImageHistory(job *engine.Job) engine.Status { return engine.StatusOK } -func (srv *Server) ContainerTop(name, psArgs string) (*APITop, error) { +func (srv *Server) ContainerTop(job *engine.Job) engine.Status { + if len(job.Args) != 1 && len(job.Args) != 2 { + job.Errorf("Not enough arguments. Usage: %s CONTAINER [PS_ARGS]\n", job.Name) + return engine.StatusErr + } + var ( + name = job.Args[0] + psArgs = "-ef" + ) + + if len(job.Args) == 2 && job.Args[1] != "" { + psArgs = job.Args[1] + } + if container := srv.runtime.Get(name); container != nil { if !container.State.IsRunning() { - return nil, fmt.Errorf("Container %s is not running", name) + job.Errorf("Container %s is not running", name) + return engine.StatusErr } pids, err := cgroups.GetPidsForContainer(container.ID) if err != nil { - return nil, err - } - if len(psArgs) == 0 { - psArgs = "-ef" + job.Error(err) + return engine.StatusErr } output, err := exec.Command("ps", psArgs).Output() if err != nil { - return nil, fmt.Errorf("Error running ps: %s", err) + job.Errorf("Error running ps: %s", err) + return engine.StatusErr } lines := strings.Split(string(output), "\n") header := strings.Fields(lines[0]) - procs := APITop{ - Titles: header, - } + out := &engine.Env{} + out.SetList("Titles", header) pidIndex := -1 for i, name := range header { @@ -834,9 +850,11 @@ func (srv *Server) ContainerTop(name, psArgs string) (*APITop, error) { } } if pidIndex == -1 { - return nil, errors.New("Couldn't find PID field in ps output") + job.Errorf("Couldn't find PID field in ps output") + return engine.StatusErr } + processes := [][]string{} for _, line := range lines[1:] { if len(line) == 0 { continue @@ -844,24 +862,27 @@ func (srv *Server) ContainerTop(name, psArgs string) (*APITop, error) { fields := strings.Fields(line) p, err := strconv.Atoi(fields[pidIndex]) if err != nil { - return nil, fmt.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) + job.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) + return engine.StatusErr } for _, pid := range pids { if pid == p { // Make sure number of fields equals number of header titles // merging "overhanging" fields - processes := fields[:len(procs.Titles)-1] - processes = append(processes, strings.Join(fields[len(procs.Titles)-1:], " ")) - - procs.Processes = append(procs.Processes, processes) + process := fields[:len(header)-1] + process = append(process, strings.Join(fields[len(header)-1:], " ")) + processes = append(processes, process) } } } - return &procs, nil + out.SetJson("Processes", processes) + out.WriteTo(job.Stdout) + return engine.StatusOK } - return nil, fmt.Errorf("No such container: %s", name) + job.Errorf("No such container: %s", name) + return engine.StatusErr } func (srv *Server) ContainerChanges(job *engine.Job) engine.Status { From 8cbabf2c3e24b6fc0f676a8696133742622f7249 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 16 Jan 2014 15:47:18 -0800 Subject: [PATCH 061/364] clean up test log Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- engine/engine.go | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/engine.go b/engine/engine.go index ad830ce2e4..ff69dcd138 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -46,7 +46,6 @@ func (eng *Engine) Root() string { } func (eng *Engine) Register(name string, handler Handler) error { - eng.Logf("Register(%s) (handlers=%v)", name, eng.handlers) _, exists := eng.handlers[name] if exists { return fmt.Errorf("Can't overwrite handler for command %s", name) From 11ef2f0db46b8507007c740300fbf486934aa996 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 16 Jan 2014 17:55:03 -0800 Subject: [PATCH 062/364] Return error and not panic when cannot exec in dockerinit Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- sysinit/sysinit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sysinit/sysinit.go b/sysinit/sysinit.go index f906b7d2dd..ce46e06f14 100644 --- a/sysinit/sysinit.go +++ b/sysinit/sysinit.go @@ -209,7 +209,7 @@ func executeProgram(args *DockerInitArgs) error { } if err := syscall.Exec(path, args.args, os.Environ()); err != nil { - panic(err) + return fmt.Errorf("dockerinit unable to execute %s - %s", path, err) } // Will never reach here From 2f6fb41dd149719ddbc7fd2bd891f03695a3769a Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 16 Jan 2014 20:13:51 -0700 Subject: [PATCH 063/364] Update .mailmap and AUTHORS - we're up to 316 unique contributors by my count! Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- .mailmap | 31 +++++++++--- AUTHORS | 148 +++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 159 insertions(+), 20 deletions(-) diff --git a/.mailmap b/.mailmap index 2875a92678..00e655c1c0 100644 --- a/.mailmap +++ b/.mailmap @@ -1,9 +1,11 @@ -# Generate AUTHORS: git log --format='%aN <%aE>' | sort -uf | grep -v vagrant-ubuntu-12 - +# Generate AUTHORS: git log --format='%aN <%aE>' | sort -uf + -Guillaume J. Charmes - +Guillaume J. Charmes + + + Thatcher Peskens dhrp @@ -15,8 +17,11 @@ Joffrey F Tim Terhorst Andy Smith - - + + + + + Thatcher Peskens @@ -38,3 +43,17 @@ Jean-Baptiste Barth Matthew Mueller Shih-Yuan Lee +Daniel Mizyrycki root +Jean-Baptiste Dalido + + + + + + + + + + + +Sven Dowideit ¨Sven <¨SvenDowideit@home.org.au¨> diff --git a/AUTHORS b/AUTHORS index 35cfc687bd..0985fed915 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,29 +3,39 @@ # # For a list of active project maintainers, see the MAINTAINERS file. # -Al Tobey -Alex Gaynor +Aanand Prasad Alexander Larsson Alexey Shamrin +Alex Gaynor +Alexis THOMAS +Al Tobey Andrea Luzzardi Andreas Savvides Andreas Tiefenthaler +Andrew Duckworth Andrew Macgregor Andrew Munsell Andrews Medina +Andy Chambers +andy diller Andy Rothfusz Andy Smith Anthony Bishopric +Anton Nikitin Antony Messerli +apocas Asbjørn Enge Barry Allard +Benoit Chesneau +Ben Sargent Ben Toews Ben Wiklund -Benoit Chesneau Bhiraj Butala Bouke Haarsma Brandon Liu -Brandon Philips +Brandon Philips +Brian Dorsey +Brian Goff Brian McCallister Brian Olsen Brian Shumate @@ -33,75 +43,126 @@ Briehan Lombaard Bruno Bigras Caleb Spare Calen Pennington +Carl X. Su Charles Hooper Christopher Currie +Christopher Rigor +Christophe Troestler +Clayton Coleman Colin Dunklau Colin Rice +Cory Forsyth +cressie176 Dan Buch +Dan Hirsch +Daniel Exner Daniel Garcia Daniel Gasienica Daniel Mizyrycki +Daniel Norberg +Daniel Norberg Daniel Nordberg Daniel Robinson Daniel Von Fange Daniel YC Lin +Danny Yates Darren Coxall +David Anderson David Calavera +David Mcanulty David Sissitka Deni Bertovic +Dinesh Subhraveti +dkumor +Dmitry Demeshchuk Dominik Honnef Don Spaulding -Dr Nic Williams Dražen Lučanin +Dr Nic Williams +Dustin Sallings +Edmund Wagner Elias Probst +Emil Hernvall Emily Rose Eric Hanchrow +Eric Lee Eric Myhre Erno Hopearuoho +eugenkrizo +Evan Krall Evan Phoenix Evan Wies +Eystein Måløy Stenberg ezbercih +Fabio Falci +Fabio Rehm Fabrizio Regini Faiz Khan Fareed Dudhia +Fernando Flavio Castelli Francisco Souza +Frank Macreery Frederick F. Kautz IV +Frederik Loeffert +Freek Kalter +Gabe Rosenhouse Gabriel Monroy +Galen Sampson Gareth Rushgrove +Gert van Valkenhoef Graydon Hoare Greg Thornton -Guillaume J. Charmes +grunny +Guillaume J. Charmes Gurjeet Singh Guruprasad Harley Laue Hector Castro Hunter Blanks +inglesp +Isaac Dupree Isao Jonas +James Allen James Carr James Turnbull +jaseg Jason McVetta Jean-Baptiste Barth +Jean-Baptiste Dalido Jeff Lindsay Jeremy Grosser +Jérôme Petazzoni +Jesse Dubay Jim Alateras Jimmy Cuadra +Joe Beda Joe Van Dyk Joffrey F Johan Euphrosine +Johannes 'fish' Ziemke +Johan Rydberg John Costa -Jon Wedaman +John Feminella +John Warwick Jonas Pfenniger Jonathan Mueller Jonathan Rudenberg +Jon Wedaman Joost Cassee Jordan Arentsen +Jordan Sissel Joseph Anthony Pasquale Holsten +Joseph Hager +Josh Hawn Josh Poimboeuf +JP Julien Barbier -Jérôme Petazzoni +Julien Dubois +Justin Force +Justin Plock Karan Lyons -Karl Grzeszczak +Karl Grzeszczak Kawsar Saiyeed Keli Hu Ken Cochrane @@ -113,24 +174,34 @@ Kiran Gangadharan Konstantin Pelykh Kyle Conroy Laurie Voss +Liang-Chi Hsieh +Lokesh Mandvekar Louis Opter +lukaspustina +Mahesh Tiyyagura Manuel Meurer -Manuel Woelker +Manuel Woelker +Marc Kuo Marco Hennings Marcus Farkas Marcus Ramberg +Marek Goldmann +Mark Allen Mark McGranaghan Marko Mikulicic Markus Fix +Martijn van Oosterhout Martin Redmond -Matt Apperson Mathieu Le Marec - Pasquet +Matt Apperson Matt Bachmann +Matt Haggard Matthew Mueller Maxim Treskin meejah -Michael Crosby +Michael Crosby Michael Gorsuch +Michael Stapelberg Miguel Angel Fernández Mike Gaffney Mikhail Sobolev @@ -138,64 +209,113 @@ Mohit Soni Morten Siebuhr Nan Monnand Deng Nate Jones +Nathan Kleyn Nelson Chen Niall O'Higgins Nick Payne Nick Stenning Nick Stinemates +Nicolas Dudebout +Nicolas Kaiser Nolan Darilek odk- +Oguz Bilgic +Ole Reifschneider +pandrew Pascal Borreli +pattichen Paul Bowsher Paul Hammond -Paul Liétar +Paul Lietar +Paul Morie Paul Nasrat +Paul +Peter Braden Phil Spitler +Pierre-Alain RIVIERE Piotr Bogdan pysqz +Quentin Brossard +Ramkumar Ramachandra Ramon van Alteren Renato Riccieri Santos Zannon +rgstephens Rhys Hiltner +Richo Healey +Rick Bradley Robert Obryk Roberto Hashioka +Rodrigo Vaz +Roel Van Nyen +Roger Peppe Ryan Fowler +Ryan O'Donnell +Ryan Seto Sam Alba Sam J Sharpe Scott Bessler +Sean Cronin Sean P. Kane +Shawn Landden Shawn Siefkas Shih-Yuan Lee +shin- Silas Sewell +Sjoerd Langkemper Solomon Hykes Song Gao Sridatta Thatipamala Sridhar Ratnakumar Steeve Morin Stefan Praszalowicz +sudosurootdev Sven Dowideit +Sylvain Bellemare +tang0th +Tatsuki Sugiura +Tehmasp Chaudhri Thatcher Peskens Thermionix Thijs Terlouw Thomas Bikeev Thomas Frössman Thomas Hansen +Thomas LEVEIL Tianon Gravi +Tim Bosse Tim Terhorst Tobias Bieniek Tobias Schmidt Tobias Schwab +Todd Lunter Tom Hulihan Tommaso Visconti +Travis Cline Tyler Brock +Tzu-Jung Lee +Ulysse Carion unclejack Victor Coisne Victor Lyuboslavsky -Victor Vieux +Victor Vieux +Vincent Batts Vincent Bernat +Vincent Woo +Vinod Kulkarni +Vitor Monteiro Vivek Agarwal Vladimir Kirillov +Vladimir Rutsky Walter Stanish +WarheadsSE Wes Morgan Will Dietz +William Delanoue +Will Rouesnel +Xiuming Chen Yang Bai +Yurii Rashkovskii +Zain Memon Zaiste! +Zilin Du +zimbatm From ba52130873395a44d637fc57f98ed174f0ac87bb Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 15 Jan 2014 10:07:47 +0100 Subject: [PATCH 064/364] Fix cross compile stat.Rdev and time.* is 32bit on OSX, which breaks cross builds with eg: cannot use stat.Rdev (type int32) as type uint64 in function argument We fix this with an extra conversion to uint64. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- archive/changes.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/archive/changes.go b/archive/changes.go index 5fc56b8863..c67bec8ce2 100644 --- a/archive/changes.go +++ b/archive/changes.go @@ -361,8 +361,8 @@ func ExportChanges(dir string, changes []Change) (Archive, error) { Mode: int64(stat.Mode & 07777), Uid: int(stat.Uid), Gid: int(stat.Gid), - ModTime: time.Unix(mtim.Sec, mtim.Nsec), - AccessTime: time.Unix(atim.Sec, atim.Nsec), + ModTime: time.Unix(int64(mtim.Sec), int64(mtim.Nsec)), + AccessTime: time.Unix(int64(atim.Sec), int64(atim.Nsec)), } if stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR { @@ -382,8 +382,8 @@ func ExportChanges(dir string, changes []Change) (Archive, error) { } else { hdr.Typeflag = tar.TypeChar } - hdr.Devmajor = int64(major(stat.Rdev)) - hdr.Devminor = int64(minor(stat.Rdev)) + hdr.Devmajor = int64(major(uint64(stat.Rdev))) + hdr.Devminor = int64(minor(uint64(stat.Rdev))) } else if stat.Mode&syscall.S_IFIFO == syscall.S_IFIFO || stat.Mode&syscall.S_IFSOCK == syscall.S_IFSOCK { hdr.Typeflag = tar.TypeFifo From 4f53722dee6c8ea18a9c30bde2cc6fee24b03d32 Mon Sep 17 00:00:00 2001 From: Gereon Frey Date: Fri, 17 Jan 2014 08:44:18 +0100 Subject: [PATCH 065/364] detect compressed archives in API /build call AFAIK in some previous version it was possible to give a compressed docker file to the API's build command and that was handled properly (aka compression was detected and archive uncompressed accordingly). Fails with at least 0.7.5. Fixed this using the DecompressStream method from the archive package. Docker-DCO-1.1-Signed-off-by: Gereon Frey (github: gfrey) --- buildfile.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/buildfile.go b/buildfile.go index 2b6d40c15d..19249b04c4 100644 --- a/buildfile.go +++ b/buildfile.go @@ -630,7 +630,13 @@ func (b *buildFile) Build(context io.Reader) (string, error) { if err != nil { return "", err } - b.context = &utils.TarSum{Reader: context, DisableCompression: true} + + decompressedStream, err := archive.DecompressStream(context) + if err != nil { + return "", err + } + + b.context = &utils.TarSum{Reader: decompressedStream, DisableCompression: true} if err := archive.Untar(b.context, tmpdirPath, nil); err != nil { return "", err } From fdd8d4b7d9dbc32a76a708d0d51c201cf9c977f0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 16 Jan 2014 11:02:51 -0800 Subject: [PATCH 066/364] Stream the cp operation on the client Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- commands.go | 116 ++++++++++++++++++++++++++++--------------------- utils/utils.go | 16 +++++++ 2 files changed, 82 insertions(+), 50 deletions(-) diff --git a/commands.go b/commands.go index 297fcdf786..1da2da8648 100644 --- a/commands.go +++ b/commands.go @@ -332,7 +332,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig.ServerAddress = serverAddress cli.configFile.Configs[serverAddress] = authconfig - body, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress]) + body, statusCode, err := readBody(cli.call("POST", "/auth", cli.configFile.Configs[serverAddress])) if statusCode == 401 { delete(cli.configFile.Configs, serverAddress) auth.SaveConfig(cli.configFile) @@ -397,7 +397,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error { fmt.Fprintf(cli.out, "Git commit (client): %s\n", GITCOMMIT) } - body, _, err := cli.call("GET", "/version", nil) + body, _, err := readBody(cli.call("GET", "/version", nil)) if err != nil { return err } @@ -438,7 +438,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { return nil } - body, _, err := cli.call("GET", "/info", nil) + body, _, err := readBody(cli.call("GET", "/info", nil)) if err != nil { return err } @@ -518,7 +518,7 @@ func (cli *DockerCli) CmdStop(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - _, _, err := cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil) + _, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to stop one or more containers") @@ -545,7 +545,7 @@ func (cli *DockerCli) CmdRestart(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - _, _, err := cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil) + _, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to restart one or more containers") @@ -564,7 +564,7 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { if s == syscall.SIGCHLD { continue } - if _, _, err := cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%d", cid, s), nil); err != nil { + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%d", cid, s), nil)); err != nil { utils.Debugf("Error sending signal: %s", err) } } @@ -591,7 +591,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { return fmt.Errorf("Impossible to start and attach multiple containers at once.") } - body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil) + body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)) if err != nil { return err } @@ -627,7 +627,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - _, _, err := cli.call("POST", "/containers/"+name+"/start", nil) + _, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil)) if err != nil { if !*attach || !*openStdin { fmt.Fprintf(cli.err, "%s\n", err) @@ -684,9 +684,9 @@ func (cli *DockerCli) CmdInspect(args ...string) error { status := 0 for _, name := range cmd.Args() { - obj, _, err := cli.call("GET", "/containers/"+name+"/json", nil) + obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil)) if err != nil { - obj, _, err = cli.call("GET", "/images/"+name+"/json", nil) + obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil)) if err != nil { if strings.Contains(err.Error(), "No such") { fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name) @@ -752,7 +752,7 @@ func (cli *DockerCli) CmdTop(args ...string) error { val.Set("ps_args", strings.Join(cmd.Args()[1:], " ")) } - body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil) + body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil)) if err != nil { return err } @@ -787,7 +787,7 @@ func (cli *DockerCli) CmdPort(args ...string) error { port = parts[0] proto = parts[1] } - body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil) + body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)) if err != nil { return err } @@ -820,7 +820,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - body, _, err := cli.call("DELETE", "/images/"+name, nil) + body, _, err := readBody(cli.call("DELETE", "/images/"+name, nil)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") @@ -857,7 +857,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { return nil } - body, _, err := cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil) + body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil)) if err != nil { return err } @@ -923,7 +923,7 @@ func (cli *DockerCli) CmdRm(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - _, _, err := cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil) + _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more containers") @@ -947,7 +947,7 @@ func (cli *DockerCli) CmdKill(args ...string) error { var encounteredError error for _, name := range args { - if _, _, err := cli.call("POST", "/containers/"+name+"/kill", nil); err != nil { + if _, _, err := readBody(cli.call("POST", "/containers/"+name+"/kill", nil)); err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to kill one or more containers") } else { @@ -1132,7 +1132,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { filter := cmd.Arg(0) if *flViz || *flTree { - body, _, err := cli.call("GET", "/images/json?all=1", nil) + body, _, err := readBody(cli.call("GET", "/images/json?all=1", nil)) if err != nil { return err } @@ -1202,7 +1202,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { v.Set("all", "1") } - body, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil) + body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil)) if err != nil { return err } @@ -1353,7 +1353,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { v.Set("size", "1") } - body, _, err := cli.call("GET", "/containers/json?"+v.Encode(), nil) + body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil)) if err != nil { return err } @@ -1445,7 +1445,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error { return err } } - body, _, err := cli.call("POST", "/commit?"+v.Encode(), config) + body, _, err := readBody(cli.call("POST", "/commit?"+v.Encode(), config)) if err != nil { return err } @@ -1520,7 +1520,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error { return nil } - body, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil) + body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil)) if err != nil { return err } @@ -1555,7 +1555,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error { return nil } name := cmd.Arg(0) - body, _, err := cli.call("GET", "/containers/"+name+"/json", nil) + body, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil)) if err != nil { return err } @@ -1592,7 +1592,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { return nil } name := cmd.Arg(0) - body, _, err := cli.call("GET", "/containers/"+name+"/json", nil) + body, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil)) if err != nil { return err } @@ -1659,7 +1659,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { v := url.Values{} v.Set("term", cmd.Arg(0)) - body, _, err := cli.call("GET", "/images/search?"+v.Encode(), nil) + body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil)) if err != nil { return err } @@ -1724,7 +1724,7 @@ func (cli *DockerCli) CmdTag(args ...string) error { v.Set("force", "1") } - if _, _, err := cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil); err != nil { + if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil)); err != nil { return err } return nil @@ -1973,7 +1973,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } //create the container - body, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config) + body, statusCode, err := readBody(cli.call("POST", "/containers/create?"+containerValues.Encode(), config)) //if image not found try to pull it if statusCode == 404 { _, tag := utils.ParseRepositoryTag(config.Image) @@ -2010,7 +2010,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil { return err } - if body, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config); err != nil { + if body, _, err = readBody(cli.call("POST", "/containers/create?"+containerValues.Encode(), config)); err != nil { return err } } else if err != nil { @@ -2111,7 +2111,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } //start the container - if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil { + if _, _, err = readBody(cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig)); err != nil { return err } @@ -2141,13 +2141,13 @@ func (cli *DockerCli) CmdRun(args ...string) error { if autoRemove { // Autoremove: wait for the container to finish, retrieve // the exit code and remove the container - if _, _, err := cli.call("POST", "/containers/"+runResult.ID+"/wait", nil); err != nil { + if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.ID+"/wait", nil)); err != nil { return err } if _, status, err = getExitCode(cli, runResult.ID); err != nil { return err } - if _, _, err := cli.call("DELETE", "/containers/"+runResult.ID+"?v=1", nil); err != nil { + if _, _, err := readBody(cli.call("DELETE", "/containers/"+runResult.ID+"?v=1", nil)); err != nil { return err } } else { @@ -2183,14 +2183,16 @@ func (cli *DockerCli) CmdCp(args ...string) error { copyData.Resource = info[1] copyData.HostPath = cmd.Arg(1) - data, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData) + stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData) + if stream != nil { + defer stream.Close() + } if err != nil { return err } if statusCode == 200 { - r := bytes.NewReader(data) - if err := archive.Untar(r, copyData.HostPath, nil); err != nil { + if err := archive.Untar(stream, copyData.HostPath, nil); err != nil { return err } } @@ -2232,7 +2234,7 @@ func (cli *DockerCli) CmdLoad(args ...string) error { return nil } -func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) { +func (cli *DockerCli) call(method, path string, data interface{}) (io.ReadCloser, int, error) { var params io.Reader if data != nil { buf, err := json.Marshal(data) @@ -2266,26 +2268,20 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, } clientconn := httputil.NewClientConn(dial, nil) resp, err := clientconn.Do(req) - defer clientconn.Close() if err != nil { + clientconn.Close() if strings.Contains(err.Error(), "connection refused") { return nil, -1, ErrConnectionRefused } return nil, -1, err } - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, -1, err - } - if resp.StatusCode < 200 || resp.StatusCode >= 400 { - if len(body) == 0 { - return nil, resp.StatusCode, fmt.Errorf("Error: %s", http.StatusText(resp.StatusCode)) + wrapper := utils.NewReadCloserWrapper(resp.Body, func() error { + if resp != nil && resp.Body != nil { + resp.Body.Close() } - return nil, resp.StatusCode, fmt.Errorf("Error: %s", bytes.TrimSpace(body)) - } - return body, resp.StatusCode, nil + return clientconn.Close() + }) + return wrapper, resp.StatusCode, nil } func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error { @@ -2480,7 +2476,7 @@ func (cli *DockerCli) resizeTty(id string) { v := url.Values{} v.Set("h", strconv.Itoa(height)) v.Set("w", strconv.Itoa(width)) - if _, _, err := cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil); err != nil { + if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil)); err != nil { utils.Errorf("Error resize: %s", err) } } @@ -2517,7 +2513,7 @@ func (cli *DockerCli) LoadConfigFile() (err error) { } func waitForExit(cli *DockerCli, containerId string) (int, error) { - body, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil) + body, _, err := readBody(cli.call("POST", "/containers/"+containerId+"/wait", nil)) if err != nil { return -1, err } @@ -2532,7 +2528,7 @@ func waitForExit(cli *DockerCli, containerId string) (int, error) { // getExitCode perform an inspect on the container. It returns // the running state and the exit code. func getExitCode(cli *DockerCli, containerId string) (bool, int, error) { - body, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil) + body, _, err := readBody(cli.call("GET", "/containers/"+containerId+"/json", nil)) if err != nil { // If we can't connect, then the daemon probably died. if err != ErrConnectionRefused { @@ -2547,6 +2543,26 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) { return c.State.IsRunning(), c.State.GetExitCode(), nil } +func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, error) { + if stream != nil { + defer stream.Close() + } + if err != nil { + return nil, statusCode, err + } + body, err := ioutil.ReadAll(stream) + if err != nil { + return nil, -1, err + } + if statusCode < 200 || statusCode >= 400 { + if len(body) == 0 { + return nil, statusCode, fmt.Errorf("Error: %s", http.StatusText(statusCode)) + } + return nil, statusCode, fmt.Errorf("Error: %s", bytes.TrimSpace(body)) + } + return body, statusCode, nil +} + func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli { var ( isTerminal = false diff --git a/utils/utils.go b/utils/utils.go index 25573d46b4..4b3fcd6603 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1142,3 +1142,19 @@ func CopyFile(src, dst string) (int64, error) { defer df.Close() return io.Copy(df, sf) } + +type readCloserWrapper struct { + io.Reader + closer func() error +} + +func (r *readCloserWrapper) Close() error { + return r.closer() +} + +func NewReadCloserWrapper(r io.Reader, closer func() error) io.ReadCloser { + return &readCloserWrapper{ + Reader: r, + closer: closer, + } +} From 95df957f73f4e7bb7225111bd4a4b4ff337af529 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 17 Jan 2014 11:02:44 -0800 Subject: [PATCH 067/364] Replace creating buffer Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- commands.go | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/commands.go b/commands.go index 1da2da8648..6553c0af4b 100644 --- a/commands.go +++ b/commands.go @@ -857,13 +857,16 @@ func (cli *DockerCli) CmdHistory(args ...string) error { return nil } - body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil)) + stream, _, err := cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil) + if stream != nil { + defer stream.Close() + } if err != nil { return err } outs := engine.NewTable("Created", 0) - if _, err := outs.ReadFrom(bytes.NewReader(body)); err != nil { + if _, err := outs.ReadFrom(stream); err != nil { return err } @@ -1132,14 +1135,16 @@ func (cli *DockerCli) CmdImages(args ...string) error { filter := cmd.Arg(0) if *flViz || *flTree { - body, _, err := readBody(cli.call("GET", "/images/json?all=1", nil)) + stream, _, err := cli.call("GET", "/images/json?all=1", nil) + if stream != nil { + defer stream.Close() + } if err != nil { return err } outs := engine.NewTable("Created", 0) - - if _, err := outs.ReadFrom(bytes.NewReader(body)); err != nil { + if _, err := outs.ReadFrom(stream); err != nil { return err } @@ -1202,13 +1207,16 @@ func (cli *DockerCli) CmdImages(args ...string) error { v.Set("all", "1") } - body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil)) + stream, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil) + if stream != nil { + defer stream.Close() + } if err != nil { return err } outs := engine.NewTable("Created", 0) - if _, err := outs.ReadFrom(bytes.NewReader(body)); err != nil { + if _, err := outs.ReadFrom(stream); err != nil { return err } @@ -1520,13 +1528,16 @@ func (cli *DockerCli) CmdDiff(args ...string) error { return nil } - body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil)) + stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil) + if stream != nil { + defer stream.Close() + } if err != nil { return err } outs := engine.NewTable("", 0) - if _, err := outs.ReadFrom(bytes.NewReader(body)); err != nil { + if _, err := outs.ReadFrom(stream); err != nil { return err } for _, change := range outs.Data { @@ -1659,12 +1670,15 @@ func (cli *DockerCli) CmdSearch(args ...string) error { v := url.Values{} v.Set("term", cmd.Arg(0)) - body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil)) + stream, _, err := cli.call("GET", "/images/search?"+v.Encode(), nil) + if stream != nil { + defer stream.Close() + } if err != nil { return err } outs := engine.NewTable("star_count", 0) - if _, err := outs.ReadFrom(bytes.NewReader(body)); err != nil { + if _, err := outs.ReadFrom(stream); err != nil { return err } w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) From 26726dc9ff3ac8ccc7f40f7672e6494d0e77611d Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Thu, 16 Jan 2014 23:41:29 -0800 Subject: [PATCH 068/364] netlink: add default Route to NetworkGetRoutes Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) --- network.go | 4 ++-- network_test.go | 6 ++++-- pkg/netlink/netlink_linux.go | 27 ++++++++++++++++----------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/network.go b/network.go index 22ea8ba757..8f0170a710 100644 --- a/network.go +++ b/network.go @@ -71,9 +71,9 @@ func networkSize(mask net.IPMask) int32 { return int32(binary.BigEndian.Uint32(m)) + 1 } -func checkRouteOverlaps(networks []*net.IPNet, dockerNetwork *net.IPNet) error { +func checkRouteOverlaps(networks []netlink.Route, dockerNetwork *net.IPNet) error { for _, network := range networks { - if networkOverlaps(dockerNetwork, network) { + if networkOverlaps(dockerNetwork, network.IPNet) { return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork, network) } } diff --git a/network_test.go b/network_test.go index 69fcba01a2..0b6857ba76 100644 --- a/network_test.go +++ b/network_test.go @@ -2,7 +2,9 @@ package docker import ( "github.com/dotcloud/docker/pkg/iptables" + "github.com/dotcloud/docker/pkg/netlink" "github.com/dotcloud/docker/proxy" + "net" "testing" ) @@ -289,10 +291,10 @@ func TestNetworkOverlaps(t *testing.T) { func TestCheckRouteOverlaps(t *testing.T) { routesData := []string{"10.0.2.0/32", "10.0.3.0/24", "10.0.42.0/24", "172.16.42.0/24", "192.168.142.0/24"} - routes := []*net.IPNet{} + routes := []netlink.Route{} for _, addr := range routesData { _, netX, _ := net.ParseCIDR(addr) - routes = append(routes, netX) + routes = append(routes, netlink.Route{IPNet: netX}) } _, netX, _ := net.ParseCIDR("172.16.0.1/24") diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index 9a937d1218..a65bdaf5e9 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -471,9 +471,16 @@ func NetworkLinkAdd(name string, linkType string) error { return s.HandleAck(wb.Seq) } +// A Route is a subnet associated with the interface to reach it. +type Route struct { + *net.IPNet + Iface *net.Interface + Default bool +} + // Returns an array of IPNet for all the currently routed subnets on ipv4 // This is similar to the first column of "ip route" output -func NetworkGetRoutes() ([]*net.IPNet, error) { +func NetworkGetRoutes() ([]Route, error) { native := nativeEndian() s, err := getNetlinkSocket() @@ -496,7 +503,7 @@ func NetworkGetRoutes() ([]*net.IPNet, error) { return nil, err } - res := make([]*net.IPNet, 0) + res := make([]Route, 0) done: for { @@ -525,8 +532,7 @@ done: continue } - var iface *net.Interface = nil - var ipNet *net.IPNet = nil + var r Route msg := (*RtMsg)(unsafe.Pointer(&m.Data[0:syscall.SizeofRtMsg][0])) @@ -546,8 +552,8 @@ done: } if msg.Dst_len == 0 { - // Ignore default routes - continue + // Default routes + r.Default = true } attrs, err := syscall.ParseNetlinkRouteAttr(&m) @@ -558,18 +564,17 @@ done: switch attr.Attr.Type { case syscall.RTA_DST: ip := attr.Value - ipNet = &net.IPNet{ + r.IPNet = &net.IPNet{ IP: ip, Mask: net.CIDRMask(int(msg.Dst_len), 8*len(ip)), } case syscall.RTA_OIF: index := int(native.Uint32(attr.Value[0:4])) - iface, _ = net.InterfaceByIndex(index) - _ = iface + r.Iface, _ = net.InterfaceByIndex(index) } } - if ipNet != nil { - res = append(res, ipNet) + if r.Default || r.IPNet != nil { + res = append(res, r) } } } From a886fbfa4a01f7d73c9c2d836da89ecf23a40a33 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Fri, 17 Jan 2014 11:05:08 -0800 Subject: [PATCH 069/364] network: exclude default routes from checkRouteOverlaps Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) --- network.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network.go b/network.go index 8f0170a710..64be094f0c 100644 --- a/network.go +++ b/network.go @@ -73,7 +73,7 @@ func networkSize(mask net.IPMask) int32 { func checkRouteOverlaps(networks []netlink.Route, dockerNetwork *net.IPNet) error { for _, network := range networks { - if networkOverlaps(dockerNetwork, network.IPNet) { + if !network.Default && networkOverlaps(dockerNetwork, network.IPNet) { return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork, network) } } From 89ff488b55c9ebb8d458508df035cac0c55ee01c Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 15 Jan 2014 19:33:01 -0500 Subject: [PATCH 070/364] Added Reference Manual Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/sources/{ => reference}/api/MAINTAINERS | 0 docs/sources/{ => reference}/api/README.md | 0 .../{ => reference}/api/docker_remote_api.rst | 0 .../api/docker_remote_api_v1.0.rst | 0 .../api/docker_remote_api_v1.1.rst | 0 .../api/docker_remote_api_v1.2.rst | 0 .../api/docker_remote_api_v1.3.rst | 0 .../api/docker_remote_api_v1.4.rst | 0 .../api/docker_remote_api_v1.5.rst | 0 .../api/docker_remote_api_v1.6.rst | 0 .../api/docker_remote_api_v1.7.rst | 0 .../api/docker_remote_api_v1.8.rst | 0 .../api/docker_remote_api_v1.9.rst | 0 docs/sources/{ => reference}/api/index.rst | 0 docs/sources/{ => reference}/api/index_api.rst | 0 .../{ => reference}/api/registry_api.rst | 0 .../api/registry_index_spec.rst | 0 .../api/remote_api_client_libraries.rst | 0 docs/sources/{use => reference}/builder.rst | 0 .../{ => reference}/commandline/cli.rst | 0 .../commandline/docker_images.gif | Bin .../{ => reference}/commandline/index.rst | 0 docs/sources/reference/index.rst | 17 +++++++++++++++++ docs/sources/toctree.rst | 3 +-- docs/sources/use/index.rst | 1 - docs/sources/use/working_with_volumes.rst | 2 +- 26 files changed, 19 insertions(+), 4 deletions(-) rename docs/sources/{ => reference}/api/MAINTAINERS (100%) rename docs/sources/{ => reference}/api/README.md (100%) rename docs/sources/{ => reference}/api/docker_remote_api.rst (100%) rename docs/sources/{ => reference}/api/docker_remote_api_v1.0.rst (100%) rename docs/sources/{ => reference}/api/docker_remote_api_v1.1.rst (100%) rename docs/sources/{ => reference}/api/docker_remote_api_v1.2.rst (100%) rename docs/sources/{ => reference}/api/docker_remote_api_v1.3.rst (100%) rename docs/sources/{ => reference}/api/docker_remote_api_v1.4.rst (100%) rename docs/sources/{ => reference}/api/docker_remote_api_v1.5.rst (100%) rename docs/sources/{ => reference}/api/docker_remote_api_v1.6.rst (100%) rename docs/sources/{ => reference}/api/docker_remote_api_v1.7.rst (100%) rename docs/sources/{ => reference}/api/docker_remote_api_v1.8.rst (100%) rename docs/sources/{ => reference}/api/docker_remote_api_v1.9.rst (100%) rename docs/sources/{ => reference}/api/index.rst (100%) rename docs/sources/{ => reference}/api/index_api.rst (100%) rename docs/sources/{ => reference}/api/registry_api.rst (100%) rename docs/sources/{ => reference}/api/registry_index_spec.rst (100%) rename docs/sources/{ => reference}/api/remote_api_client_libraries.rst (100%) rename docs/sources/{use => reference}/builder.rst (100%) rename docs/sources/{ => reference}/commandline/cli.rst (100%) rename docs/sources/{ => reference}/commandline/docker_images.gif (100%) rename docs/sources/{ => reference}/commandline/index.rst (100%) create mode 100644 docs/sources/reference/index.rst diff --git a/docs/sources/api/MAINTAINERS b/docs/sources/reference/api/MAINTAINERS similarity index 100% rename from docs/sources/api/MAINTAINERS rename to docs/sources/reference/api/MAINTAINERS diff --git a/docs/sources/api/README.md b/docs/sources/reference/api/README.md similarity index 100% rename from docs/sources/api/README.md rename to docs/sources/reference/api/README.md diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/reference/api/docker_remote_api.rst similarity index 100% rename from docs/sources/api/docker_remote_api.rst rename to docs/sources/reference/api/docker_remote_api.rst diff --git a/docs/sources/api/docker_remote_api_v1.0.rst b/docs/sources/reference/api/docker_remote_api_v1.0.rst similarity index 100% rename from docs/sources/api/docker_remote_api_v1.0.rst rename to docs/sources/reference/api/docker_remote_api_v1.0.rst diff --git a/docs/sources/api/docker_remote_api_v1.1.rst b/docs/sources/reference/api/docker_remote_api_v1.1.rst similarity index 100% rename from docs/sources/api/docker_remote_api_v1.1.rst rename to docs/sources/reference/api/docker_remote_api_v1.1.rst diff --git a/docs/sources/api/docker_remote_api_v1.2.rst b/docs/sources/reference/api/docker_remote_api_v1.2.rst similarity index 100% rename from docs/sources/api/docker_remote_api_v1.2.rst rename to docs/sources/reference/api/docker_remote_api_v1.2.rst diff --git a/docs/sources/api/docker_remote_api_v1.3.rst b/docs/sources/reference/api/docker_remote_api_v1.3.rst similarity index 100% rename from docs/sources/api/docker_remote_api_v1.3.rst rename to docs/sources/reference/api/docker_remote_api_v1.3.rst diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/reference/api/docker_remote_api_v1.4.rst similarity index 100% rename from docs/sources/api/docker_remote_api_v1.4.rst rename to docs/sources/reference/api/docker_remote_api_v1.4.rst diff --git a/docs/sources/api/docker_remote_api_v1.5.rst b/docs/sources/reference/api/docker_remote_api_v1.5.rst similarity index 100% rename from docs/sources/api/docker_remote_api_v1.5.rst rename to docs/sources/reference/api/docker_remote_api_v1.5.rst diff --git a/docs/sources/api/docker_remote_api_v1.6.rst b/docs/sources/reference/api/docker_remote_api_v1.6.rst similarity index 100% rename from docs/sources/api/docker_remote_api_v1.6.rst rename to docs/sources/reference/api/docker_remote_api_v1.6.rst diff --git a/docs/sources/api/docker_remote_api_v1.7.rst b/docs/sources/reference/api/docker_remote_api_v1.7.rst similarity index 100% rename from docs/sources/api/docker_remote_api_v1.7.rst rename to docs/sources/reference/api/docker_remote_api_v1.7.rst diff --git a/docs/sources/api/docker_remote_api_v1.8.rst b/docs/sources/reference/api/docker_remote_api_v1.8.rst similarity index 100% rename from docs/sources/api/docker_remote_api_v1.8.rst rename to docs/sources/reference/api/docker_remote_api_v1.8.rst diff --git a/docs/sources/api/docker_remote_api_v1.9.rst b/docs/sources/reference/api/docker_remote_api_v1.9.rst similarity index 100% rename from docs/sources/api/docker_remote_api_v1.9.rst rename to docs/sources/reference/api/docker_remote_api_v1.9.rst diff --git a/docs/sources/api/index.rst b/docs/sources/reference/api/index.rst similarity index 100% rename from docs/sources/api/index.rst rename to docs/sources/reference/api/index.rst diff --git a/docs/sources/api/index_api.rst b/docs/sources/reference/api/index_api.rst similarity index 100% rename from docs/sources/api/index_api.rst rename to docs/sources/reference/api/index_api.rst diff --git a/docs/sources/api/registry_api.rst b/docs/sources/reference/api/registry_api.rst similarity index 100% rename from docs/sources/api/registry_api.rst rename to docs/sources/reference/api/registry_api.rst diff --git a/docs/sources/api/registry_index_spec.rst b/docs/sources/reference/api/registry_index_spec.rst similarity index 100% rename from docs/sources/api/registry_index_spec.rst rename to docs/sources/reference/api/registry_index_spec.rst diff --git a/docs/sources/api/remote_api_client_libraries.rst b/docs/sources/reference/api/remote_api_client_libraries.rst similarity index 100% rename from docs/sources/api/remote_api_client_libraries.rst rename to docs/sources/reference/api/remote_api_client_libraries.rst diff --git a/docs/sources/use/builder.rst b/docs/sources/reference/builder.rst similarity index 100% rename from docs/sources/use/builder.rst rename to docs/sources/reference/builder.rst diff --git a/docs/sources/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst similarity index 100% rename from docs/sources/commandline/cli.rst rename to docs/sources/reference/commandline/cli.rst diff --git a/docs/sources/commandline/docker_images.gif b/docs/sources/reference/commandline/docker_images.gif similarity index 100% rename from docs/sources/commandline/docker_images.gif rename to docs/sources/reference/commandline/docker_images.gif diff --git a/docs/sources/commandline/index.rst b/docs/sources/reference/commandline/index.rst similarity index 100% rename from docs/sources/commandline/index.rst rename to docs/sources/reference/commandline/index.rst diff --git a/docs/sources/reference/index.rst b/docs/sources/reference/index.rst new file mode 100644 index 0000000000..49099d5621 --- /dev/null +++ b/docs/sources/reference/index.rst @@ -0,0 +1,17 @@ +:title: Docker Reference Manual +:description: References +:keywords: docker, references, api, command line, commands + +.. _references: + +Reference Manual +================ + +Contents: + +.. toctree:: + :maxdepth: 1 + + commandline/index + builder + api/index diff --git a/docs/sources/toctree.rst b/docs/sources/toctree.rst index c7c368a3f9..2807ea5932 100644 --- a/docs/sources/toctree.rst +++ b/docs/sources/toctree.rst @@ -14,8 +14,7 @@ This documentation has the following resources: installation/index use/index examples/index - commandline/index + reference/index contributing/index - api/index terms/index faq diff --git a/docs/sources/use/index.rst b/docs/sources/use/index.rst index 7bcd1dd81e..23428226a7 100644 --- a/docs/sources/use/index.rst +++ b/docs/sources/use/index.rst @@ -13,7 +13,6 @@ Contents: :maxdepth: 1 basics - builder workingwithrepository baseimages port_redirection diff --git a/docs/sources/use/working_with_volumes.rst b/docs/sources/use/working_with_volumes.rst index 86576b05e4..d908157473 100644 --- a/docs/sources/use/working_with_volumes.rst +++ b/docs/sources/use/working_with_volumes.rst @@ -9,7 +9,7 @@ Share Directories via Volumes .. versionadded:: v0.3.0 Data volumes have been available since version 1 of the - :doc:`../api/docker_remote_api` + :doc:`../reference/api/docker_remote_api` A *data volume* is a specially-designated directory within one or more containers that bypasses the :ref:`ufs_def` to provide several useful From dadd54dba395f68c492a8ce5e2bf9488f9db0830 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Fri, 17 Jan 2014 13:12:08 -0800 Subject: [PATCH 071/364] netlink: move Route type to common arch file Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) --- pkg/netlink/netlink.go | 15 +++++++++++++++ pkg/netlink/netlink_linux.go | 7 ------- 2 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 pkg/netlink/netlink.go diff --git a/pkg/netlink/netlink.go b/pkg/netlink/netlink.go new file mode 100644 index 0000000000..5098b4b816 --- /dev/null +++ b/pkg/netlink/netlink.go @@ -0,0 +1,15 @@ +// Packet netlink provide access to low level Netlink sockets and messages. +// +// Actual implementations are in: +// netlink_linux.go +// netlink_darwin.go +package netlink + +import "net" + +// A Route is a subnet associated with the interface to reach it. +type Route struct { + *net.IPNet + Iface *net.Interface + Default bool +} diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index a65bdaf5e9..ab572e397a 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -471,13 +471,6 @@ func NetworkLinkAdd(name string, linkType string) error { return s.HandleAck(wb.Seq) } -// A Route is a subnet associated with the interface to reach it. -type Route struct { - *net.IPNet - Iface *net.Interface - Default bool -} - // Returns an array of IPNet for all the currently routed subnets on ipv4 // This is similar to the first column of "ip route" output func NetworkGetRoutes() ([]Route, error) { From 77d9fd262828c0c626b2093e8a62e05cc130905e Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Fri, 17 Jan 2014 16:30:27 -0800 Subject: [PATCH 072/364] netlink: make darwin happy Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: proppy) --- network.go | 2 +- pkg/netlink/netlink_darwin.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/network.go b/network.go index 64be094f0c..1139f6ad37 100644 --- a/network.go +++ b/network.go @@ -73,7 +73,7 @@ func networkSize(mask net.IPMask) int32 { func checkRouteOverlaps(networks []netlink.Route, dockerNetwork *net.IPNet) error { for _, network := range networks { - if !network.Default && networkOverlaps(dockerNetwork, network.IPNet) { + if network.IPNet != nil && networkOverlaps(dockerNetwork, network.IPNet) { return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork, network) } } diff --git a/pkg/netlink/netlink_darwin.go b/pkg/netlink/netlink_darwin.go index dcc60b6764..298508ad10 100644 --- a/pkg/netlink/netlink_darwin.go +++ b/pkg/netlink/netlink_darwin.go @@ -5,7 +5,7 @@ import ( "net" ) -func NetworkGetRoutes() ([]*net.IPNet, error) { +func NetworkGetRoutes() ([]Route, error) { return nil, fmt.Errorf("Not implemented") } From 3ff5f2374cf7d68c0905840a571c521d79c7e091 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 17 Jan 2014 16:57:43 -0800 Subject: [PATCH 073/364] refactor registers Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- server.go | 120 +++++++++++++----------------------------------------- 1 file changed, 29 insertions(+), 91 deletions(-) diff --git a/server.go b/server.go index 3eaf5b13f5..3892ba70ea 100644 --- a/server.go +++ b/server.go @@ -71,97 +71,35 @@ func jobInitApi(job *engine.Job) engine.Status { if srv.runtime.networkManager.bridgeNetwork != nil { job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", srv.runtime.networkManager.bridgeNetwork.IP) } - if err := job.Eng.Register("export", srv.ContainerExport); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("create", srv.ContainerCreate); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("stop", srv.ContainerStop); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("restart", srv.ContainerRestart); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("start", srv.ContainerStart); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("kill", srv.ContainerKill); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("serveapi", srv.ListenAndServe); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("wait", srv.ContainerWait); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("tag", srv.ImageTag); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("resize", srv.ContainerResize); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("commit", srv.ContainerCommit); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("info", srv.DockerInfo); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("container_delete", srv.ContainerDestroy); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("image_export", srv.ImageExport); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("images", srv.Images); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("history", srv.ImageHistory); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("viz", srv.ImagesViz); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("container_copy", srv.ContainerCopy); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("insert", srv.ImageInsert); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("attach", srv.ContainerAttach); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("search", srv.ImagesSearch); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("changes", srv.ContainerChanges); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("top", srv.ContainerTop); err != nil { - job.Error(err) - return engine.StatusErr + for name, handler := range map[string]engine.Handler{ + "export": srv.ContainerExport, + "create": srv.ContainerCreate, + "stop": srv.ContainerStop, + "restart": srv.ContainerRestart, + "start": srv.ContainerStart, + "kill": srv.ContainerKill, + "serveapi": srv.ListenAndServe, + "wait": srv.ContainerWait, + "tag": srv.ImageTag, + "resize": srv.ContainerResize, + "commit": srv.ContainerCommit, + "info": srv.DockerInfo, + "container_delete": srv.ContainerDestroy, + "image_export": srv.ImageExport, + "images": srv.Images, + "history": srv.ImageHistory, + "viz": srv.ImagesViz, + "container_copy": srv.ContainerCopy, + "insert": srv.ImageInsert, + "attach": srv.ContainerAttach, + "search": srv.ImagesSearch, + "changes": srv.ContainerChanges, + "top": srv.ContainerTop, + } { + if err := job.Eng.Register(name, handler); err != nil { + job.Error(err) + return engine.StatusErr + } } return engine.StatusOK } From e71dbf4ee5add5736f595948fc20bd01af56a744 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 23 Dec 2013 11:43:54 -0800 Subject: [PATCH 074/364] update commands.go update docker.go move to pkg update docs update name and copyright change --sinceId to --since-id, update completion and docs Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- commands.go | 136 +-- contrib/completion/bash/docker | 32 +- contrib/completion/zsh/_docker | 8 +- docker/docker.go | 36 +- .../sources/examples/couchdb_data_volumes.rst | 2 +- docs/sources/examples/python_web_app.rst | 2 +- .../examples/running_redis_service.rst | 4 +- docs/sources/reference/commandline/cli.rst | 193 ++-- pkg/mflag/LICENSE | 27 + pkg/mflag/README.md | 40 + pkg/mflag/example/example.go | 27 + pkg/mflag/export_test.go | 17 + pkg/mflag/flag.go | 863 ++++++++++++++++++ pkg/mflag/flag_test.go | 377 ++++++++ 14 files changed, 1558 insertions(+), 206 deletions(-) create mode 100644 pkg/mflag/LICENSE create mode 100644 pkg/mflag/README.md create mode 100644 pkg/mflag/example/example.go create mode 100644 pkg/mflag/export_test.go create mode 100644 pkg/mflag/flag.go create mode 100644 pkg/mflag/flag_test.go diff --git a/commands.go b/commands.go index 6553c0af4b..09b65ad163 100644 --- a/commands.go +++ b/commands.go @@ -7,11 +7,11 @@ import ( "encoding/base64" "encoding/json" "errors" - "flag" "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/engine" + flag "github.com/dotcloud/docker/pkg/mflag" "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" @@ -164,10 +164,10 @@ func MkBuildContext(dockerfile string, files [][2]string) (archive.Archive, erro func (cli *DockerCli) CmdBuild(args ...string) error { cmd := cli.Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH") - tag := cmd.String("t", "", "Repository name (and optionally a tag) to be applied to the resulting image in case of success") - suppressOutput := cmd.Bool("q", false, "Suppress verbose build output") - noCache := cmd.Bool("no-cache", false, "Do not use cache when building the image") - rm := cmd.Bool("rm", false, "Remove intermediate containers after a successful build") + tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) to be applied to the resulting image in case of success") + suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress verbose build output") + noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image") + rm := cmd.Bool([]string{"#rm", "-rm"}, false, "Remove intermediate containers after a successful build") if err := cmd.Parse(args); err != nil { return nil } @@ -253,9 +253,9 @@ func (cli *DockerCli) CmdLogin(args ...string) error { var username, password, email string - cmd.StringVar(&username, "u", "", "username") - cmd.StringVar(&password, "p", "", "password") - cmd.StringVar(&email, "e", "", "email") + cmd.StringVar(&username, []string{"u", "-username"}, "", "username") + cmd.StringVar(&password, []string{"p", "-password"}, "", "password") + cmd.StringVar(&email, []string{"e", "-email"}, "", "email") err := cmd.Parse(args) if err != nil { return nil @@ -504,7 +504,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { func (cli *DockerCli) CmdStop(args ...string) error { cmd := cli.Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container (Send SIGTERM, and then SIGKILL after grace period)") - nSeconds := cmd.Int("t", 10, "Number of seconds to wait for the container to stop before killing it.") + nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to wait for the container to stop before killing it.") if err := cmd.Parse(args); err != nil { return nil } @@ -531,7 +531,7 @@ func (cli *DockerCli) CmdStop(args ...string) error { func (cli *DockerCli) CmdRestart(args ...string) error { cmd := cli.Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container") - nSeconds := cmd.Int("t", 10, "Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10") + nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10") if err := cmd.Parse(args); err != nil { return nil } @@ -574,8 +574,8 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { func (cli *DockerCli) CmdStart(args ...string) error { cmd := cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container") - attach := cmd.Bool("a", false, "Attach container's stdout/stderr and forward all signals to the process") - openStdin := cmd.Bool("i", false, "Attach container's stdin") + attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach container's stdout/stderr and forward all signals to the process") + openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's stdin") if err := cmd.Parse(args); err != nil { return nil } @@ -660,7 +660,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { func (cli *DockerCli) CmdInspect(args ...string) error { cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container/image") - tmplStr := cmd.String("format", "", "Format the output using the given go template.") + tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template.") if err := cmd.Parse(args); err != nil { return nil } @@ -846,8 +846,8 @@ func (cli *DockerCli) CmdRmi(args ...string) error { func (cli *DockerCli) CmdHistory(args ...string) error { cmd := cli.Subcmd("history", "[OPTIONS] IMAGE", "Show the history of an image") - quiet := cmd.Bool("q", false, "only show numeric IDs") - noTrunc := cmd.Bool("notrunc", false, "Don't truncate output") + quiet := cmd.Bool([]string{"q", "-quiet"}, false, "only show numeric IDs") + noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") if err := cmd.Parse(args); err != nil { return nil @@ -906,8 +906,8 @@ func (cli *DockerCli) CmdHistory(args ...string) error { func (cli *DockerCli) CmdRm(args ...string) error { cmd := cli.Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers") - v := cmd.Bool("v", false, "Remove the volumes associated to the container") - link := cmd.Bool("link", false, "Remove the specified link and not the underlying container") + v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated to the container") + link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link and not the underlying container") if err := cmd.Parse(args); err != nil { return nil @@ -1058,7 +1058,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { func (cli *DockerCli) CmdPull(args ...string) error { cmd := cli.Subcmd("pull", "NAME", "Pull an image or a repository from the registry") - tag := cmd.String("t", "", "Download tagged image in repository") + tag := cmd.String([]string{"t", "-tag"}, "", "Download tagged image in repository") if err := cmd.Parse(args); err != nil { return nil } @@ -1118,11 +1118,11 @@ func (cli *DockerCli) CmdPull(args ...string) error { func (cli *DockerCli) CmdImages(args ...string) error { cmd := cli.Subcmd("images", "[OPTIONS] [NAME]", "List images") - quiet := cmd.Bool("q", false, "only show numeric IDs") - all := cmd.Bool("a", false, "show all images (by default filter out the intermediate images used to build)") - noTrunc := cmd.Bool("notrunc", false, "Don't truncate output") - flViz := cmd.Bool("viz", false, "output graph in graphviz format") - flTree := cmd.Bool("tree", false, "output graph in tree format") + quiet := cmd.Bool([]string{"q", "-quiet"}, false, "only show numeric IDs") + all := cmd.Bool([]string{"a", "-all"}, false, "show all images (by default filter out the intermediate images used to build)") + noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") + flViz := cmd.Bool([]string{"v", "#viz", "-viz"}, false, "output graph in graphviz format") + flTree := cmd.Bool([]string{"t", "#tree", "-tree"}, false, "output graph in tree format") if err := cmd.Parse(args); err != nil { return nil @@ -1329,14 +1329,14 @@ func displayablePorts(ports []APIPort) string { func (cli *DockerCli) CmdPs(args ...string) error { cmd := cli.Subcmd("ps", "[OPTIONS]", "List containers") - quiet := cmd.Bool("q", false, "Only display numeric IDs") - size := cmd.Bool("s", false, "Display sizes") - all := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.") - noTrunc := cmd.Bool("notrunc", false, "Don't truncate output") - nLatest := cmd.Bool("l", false, "Show only the latest created container, include non-running ones.") - since := cmd.String("sinceId", "", "Show only containers created since Id, include non-running ones.") - before := cmd.String("beforeId", "", "Show only container created before Id, include non-running ones.") - last := cmd.Int("n", -1, "Show n last created containers, include non-running ones.") + quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs") + size := cmd.Bool([]string{"s", "-size"}, false, "Display sizes") + all := cmd.Bool([]string{"a", "-all"}, false, "Show all containers. Only running containers are shown by default.") + noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") + nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show only the latest created container, include non-running ones.") + since := cmd.String([]string{"#sinceId", "-since-id"}, "", "Show only containers created since Id, include non-running ones.") + before := cmd.String([]string{"#beforeId", "-before-id"}, "", "Show only container created before Id, include non-running ones.") + last := cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running ones.") if err := cmd.Parse(args); err != nil { return nil @@ -1418,9 +1418,9 @@ func (cli *DockerCli) CmdPs(args ...string) error { func (cli *DockerCli) CmdCommit(args ...string) error { cmd := cli.Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes") - flComment := cmd.String("m", "", "Commit message") - flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith \"") - flConfig := cmd.String("run", "", "Config automatically applied when the image is run. "+`(ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`) + flComment := cmd.String([]string{"m", "-message"}, "", "Commit message") + flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (eg. \"John Hannibal Smith \"") + flConfig := cmd.String([]string{"#run", "-run"}, "", "Config automatically applied when the image is run. "+`(ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`) if err := cmd.Parse(args); err != nil { return nil } @@ -1470,7 +1470,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error { func (cli *DockerCli) CmdEvents(args ...string) error { cmd := cli.Subcmd("events", "[OPTIONS]", "Get real time events from the server") - since := cmd.String("since", "", "Show previously created events and then stream.") + since := cmd.String([]string{"#since", "-since"}, "", "Show previously created events and then stream.") if err := cmd.Parse(args); err != nil { return nil } @@ -1557,7 +1557,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error { func (cli *DockerCli) CmdLogs(args ...string) error { cmd := cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container") - follow := cmd.Bool("f", false, "Follow log output") + follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output") if err := cmd.Parse(args); err != nil { return nil } @@ -1593,8 +1593,8 @@ func (cli *DockerCli) CmdLogs(args ...string) error { func (cli *DockerCli) CmdAttach(args ...string) error { cmd := cli.Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container") - noStdin := cmd.Bool("nostdin", false, "Do not attach stdin") - proxy := cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)") + noStdin := cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach stdin") + proxy := cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)") if err := cmd.Parse(args); err != nil { return nil } @@ -1657,9 +1657,9 @@ func (cli *DockerCli) CmdAttach(args ...string) error { func (cli *DockerCli) CmdSearch(args ...string) error { cmd := cli.Subcmd("search", "TERM", "Search the docker index for images") - noTrunc := cmd.Bool("notrunc", false, "Don't truncate output") - trusted := cmd.Bool("trusted", false, "Only show trusted builds") - stars := cmd.Int("stars", 0, "Only displays with at least xxx stars") + noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") + trusted := cmd.Bool([]string{"t", "#trusted", "-trusted"}, false, "Only show trusted builds") + stars := cmd.Int([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least xxx stars") if err := cmd.Parse(args); err != nil { return nil } @@ -1712,7 +1712,7 @@ type ports []int func (cli *DockerCli) CmdTag(args ...string) error { cmd := cli.Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY[:TAG]", "Tag an image into a repository") - force := cmd.Bool("f", false, "Force") + force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force") if err := cmd.Parse(args); err != nil { return nil } @@ -1766,36 +1766,36 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co flVolumesFrom ListOpts flLxcOpts ListOpts - flAutoRemove = cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)") - flDetach = cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id") - flNetwork = cmd.Bool("n", true, "Enable networking for this container") - flPrivileged = cmd.Bool("privileged", false, "Give extended privileges to this container") - flPublishAll = cmd.Bool("P", false, "Publish all exposed ports to the host interfaces") - flStdin = cmd.Bool("i", false, "Keep stdin open even if not attached") - flTty = cmd.Bool("t", false, "Allocate a pseudo-tty") - flContainerIDFile = cmd.String("cidfile", "", "Write the container ID to the file") - flEntrypoint = cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image") - flHostname = cmd.String("h", "", "Container host name") - flMemoryString = cmd.String("m", "", "Memory limit (format: , where unit = b, k, m or g)") - flUser = cmd.String("u", "", "Username or UID") - flWorkingDir = cmd.String("w", "", "Working directory inside the container") - flCpuShares = cmd.Int64("c", 0, "CPU shares (relative weight)") + flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)") + flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run container in the background, print new container id") + flNetwork = cmd.Bool([]string{"n", "-networking"}, true, "Enable networking for this container") + flPrivileged = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container") + flPublishAll = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to the host interfaces") + flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep stdin open even if not attached") + flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-tty") + flContainerIDFile = cmd.String([]string{"#cidfile", "-cidfile"}, "", "Write the container ID to the file") + flEntrypoint = cmd.String([]string{"#entrypoint", "-entrypoint"}, "", "Overwrite the default entrypoint of the image") + flHostname = cmd.String([]string{"h", "-hostname"}, "", "Container host name") + flMemoryString = cmd.String([]string{"m", "-memory"}, "", "Memory limit (format: , where unit = b, k, m or g)") + flUser = cmd.String([]string{"u", "-username"}, "", "Username or UID") + flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container") + flCpuShares = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") // For documentation purpose - _ = cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)") - _ = cmd.String("name", "", "Assign a name to the container") + _ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)") + _ = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container") ) - cmd.Var(&flAttach, "a", "Attach to stdin, stdout or stderr.") - cmd.Var(&flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)") - cmd.Var(&flLinks, "link", "Add link to another container (name:alias)") - cmd.Var(&flEnv, "e", "Set environment variables") + cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to stdin, stdout or stderr.") + cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)") + cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container (name:alias)") + cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables") - cmd.Var(&flPublish, "p", fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", PortSpecTemplateFormat)) - cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host") - cmd.Var(&flDns, "dns", "Set custom dns servers") - cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)") - cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") + cmd.Var(&flPublish, []string{"p", "-publish"}, fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", PortSpecTemplateFormat)) + cmd.Var(&flExpose, []string{"#expose", "-expose"}, "Expose a port from the container without publishing it to your host") + cmd.Var(&flDns, []string{"#dns", "-dns"}, "Set custom dns servers") + cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)") + cmd.Var(&flLxcOpts, []string{"#lxc-conf", "-lxc-conf"}, "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err @@ -1892,7 +1892,7 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co // Merge in exposed ports to the map of published ports for _, e := range flExpose.GetAll() { if strings.Contains(e, ":") { - return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e) + return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e) } p := NewPort(splitProtoPort(e)) if _, exists := ports[p]; !exists { diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index f1a515d00a..4ebe45e083 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -25,7 +25,7 @@ __docker_containers_all() { local containers containers="$( docker ps -a -q )" - names="$( docker inspect -format '{{.Name}}' $containers | sed 's,^/,,' )" + names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) ) } @@ -33,7 +33,7 @@ __docker_containers_running() { local containers containers="$( docker ps -q )" - names="$( docker inspect -format '{{.Name}}' $containers | sed 's,^/,,' )" + names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) ) } @@ -41,7 +41,7 @@ __docker_containers_stopped() { local containers containers="$( comm -13 <(docker ps -q | sort -u) <(docker ps -a -q | sort -u) )" - names="$( docker inspect -format '{{.Name}}' $containers | sed 's,^/,,' )" + names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) ) } @@ -73,7 +73,7 @@ __docker_containers_and_images() { local containers images containers="$( docker ps -a -q )" - names="$( docker inspect -format '{{.Name}}' $containers | sed 's,^/,,' )" + names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" images="$( docker images | awk 'NR>1{print $1":"$2}' )" COMPREPLY=( $( compgen -W "$images $names $containers" -- "$cur" ) ) __ltrim_colon_completions "$cur" @@ -118,7 +118,7 @@ _docker_build() case "$cur" in -*) - COMPREPLY=( $( compgen -W "-no-cache -t -q -rm" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--no-cache -t -q --rm" -- "$cur" ) ) ;; *) _filedir @@ -138,7 +138,7 @@ _docker_commit() case "$cur" in -*) - COMPREPLY=( $( compgen -W "-author -m -run" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--author -m --run" -- "$cur" ) ) ;; *) local counter=$cpos @@ -191,7 +191,7 @@ _docker_events() case "$cur" in -*) - COMPREPLY=( $( compgen -W "-since" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--since" -- "$cur" ) ) ;; *) ;; @@ -223,7 +223,7 @@ _docker_images() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "-a -notrunc -q -viz" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "-a --no-trunc -q --viz" -- "$cur" ) ) ;; *) local counter=$cpos @@ -308,7 +308,7 @@ _docker_port() _docker_ps() { case "$prev" in - -beforeId|-n|-sinceId) + -before-id|-n|-since-id) return ;; *) @@ -317,7 +317,7 @@ _docker_ps() case "$cur" in -*) - COMPREPLY=( $( compgen -W "-a -beforeId -l -n -notrunc -q -s -sinceId" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "-a --before-id -l -n --no-trunc -q -s --since-id" -- "$cur" ) ) ;; *) ;; @@ -388,13 +388,13 @@ _docker_rmi() _docker_run() { case "$prev" in - -cidfile) + --cidfile) _filedir ;; - -volumes-from) + --volumes-from) __docker_containers_all ;; - -a|-c|-dns|-e|-entrypoint|-h|-lxc-conf|-m|-p|-u|-v|-w) + -a|-c|--dns|-e|--entrypoint|-h|--lxc-conf|-m|-p|-u|-v|-w) return ;; *) @@ -403,13 +403,13 @@ _docker_run() case "$cur" in -*) - COMPREPLY=( $( compgen -W "-a -c -cidfile -d -dns -e -entrypoint -h -i -lxc-conf -m -n -p -privileged -t -u -v -volumes-from -w" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "-a -c --cidfile -d --dns -e --entrypoint -h -i --lxc-conf -m -n -p --privileged -t -u -v --volumes-from -w" -- "$cur" ) ) ;; *) local counter=$cpos while [ $counter -le $cword ]; do case "${words[$counter]}" in - -a|-c|-cidfile|-dns|-e|-entrypoint|-h|-lxc-conf|-m|-p|-u|-v|-volumes-from|-w) + -a|-c|--cidfile|--dns|-e|--entrypoint|-h|--lxc-conf|-m|-p|-u|-v|--volumes-from|-w) (( counter++ )) ;; -*) @@ -430,7 +430,7 @@ _docker_run() _docker_search() { - COMPREPLY=( $( compgen -W "-notrunc" "-stars" "-trusted" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--no-trunc" "--stars" "--trusted" -- "$cur" ) ) } _docker_start() diff --git a/contrib/completion/zsh/_docker b/contrib/completion/zsh/_docker index 92acdb13dd..8b50bac01b 100755 --- a/contrib/completion/zsh/_docker +++ b/contrib/completion/zsh/_docker @@ -174,7 +174,7 @@ __docker_subcommand () { (ps) _arguments '-a[Show all containers. Only running containers are shown by default]' \ '-h[Show help]' \ - '-beforeId=-[Show only container created before Id, include non-running one]:containers:__docker_containers' \ + '-before-id=-[Show only container created before Id, include non-running one]:containers:__docker_containers' \ '-n=-[Show n last created containers, include non-running one]:n:(1 5 10 25 50)' ;; (tag) @@ -189,9 +189,9 @@ __docker_subcommand () { '-a=-[Attach to stdin, stdout or stderr]:toggle:(true false)' \ '-c=-[CPU shares (relative weight)]:CPU shares: ' \ '-d[Detached mode: leave the container running in the background]' \ - '*-dns=[Set custom dns servers]:dns server: ' \ + '*--dns=[Set custom dns servers]:dns server: ' \ '*-e=[Set environment variables]:environment variable: ' \ - '-entrypoint=-[Overwrite the default entrypoint of the image]:entry point: ' \ + '--entrypoint=-[Overwrite the default entrypoint of the image]:entry point: ' \ '-h=-[Container host name]:hostname:_hosts' \ '-i[Keep stdin open even if not attached]' \ '-m=-[Memory limit (in bytes)]:limit: ' \ @@ -199,7 +199,7 @@ __docker_subcommand () { '-t=-[Allocate a pseudo-tty]:toggle:(true false)' \ '-u=-[Username or UID]:user:_users' \ '*-v=-[Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)]:volume: '\ - '-volumes-from=-[Mount volumes from the specified container]:volume: ' \ + '--volumes-from=-[Mount volumes from the specified container]:volume: ' \ '(-):images:__docker_images' \ '(-):command: _command_names -e' \ '*::arguments: _normal' diff --git a/docker/docker.go b/docker/docker.go index 2d7e04ce92..5f5b3c17ce 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -1,10 +1,10 @@ package main import ( - "flag" "fmt" "github.com/dotcloud/docker" "github.com/dotcloud/docker/engine" + flag "github.com/dotcloud/docker/pkg/mflag" "github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/utils" "log" @@ -25,25 +25,25 @@ func main() { } var ( - flVersion = flag.Bool("v", false, "Print version information and quit") - flDaemon = flag.Bool("d", false, "Enable daemon mode") - flDebug = flag.Bool("D", false, "Enable debug mode") - flAutoRestart = flag.Bool("r", true, "Restart previously running containers") - bridgeName = flag.String("b", "", "Attach containers to a pre-existing network bridge; use 'none' to disable container networking") - bridgeIp = flag.String("bip", "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b") - pidfile = flag.String("p", "/var/run/docker.pid", "Path to use for daemon PID file") - flRoot = flag.String("g", "/var/lib/docker", "Path to use as the root of the docker runtime") - flEnableCors = flag.Bool("api-enable-cors", false, "Enable CORS headers in the remote API") + flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") + flDaemon = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode") + flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode") + flAutoRestart = flag.Bool([]string{"r", "-restart"}, true, "Restart previously running containers") + bridgeName = flag.String([]string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge; use 'none' to disable container networking") + bridgeIp = flag.String([]string{"#bip", "-bip"}, "", "Use this CIDR notation address for the network bridge's IP, not compatible with -b") + pidfile = flag.String([]string{"p", "-pidfile"}, "/var/run/docker.pid", "Path to use for daemon PID file") + flRoot = flag.String([]string{"g", "-graph"}, "/var/lib/docker", "Path to use as the root of the docker runtime") + flEnableCors = flag.Bool([]string{"#api-enable-cors", "-api-enable-cors"}, false, "Enable CORS headers in the remote API") flDns = docker.NewListOpts(docker.ValidateIp4Address) - flEnableIptables = flag.Bool("iptables", true, "Disable docker's addition of iptables rules") - flDefaultIp = flag.String("ip", "0.0.0.0", "Default IP address to use when binding container ports") - flInterContainerComm = flag.Bool("icc", true, "Enable inter-container communication") - flGraphDriver = flag.String("s", "", "Force the docker runtime to use a specific storage driver") + flEnableIptables = flag.Bool([]string{"#iptables", "-iptables"}, true, "Disable docker's addition of iptables rules") + flDefaultIp = flag.String([]string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports") + flInterContainerComm = flag.Bool([]string{"#icc", "-icc"}, true, "Enable inter-container communication") + flGraphDriver = flag.String([]string{"s", "-storage-driver"}, "", "Force the docker runtime to use a specific storage driver") flHosts = docker.NewListOpts(docker.ValidateHost) - flMtu = flag.Int("mtu", docker.DefaultNetworkMtu, "Set the containers network mtu") + flMtu = flag.Int([]string{"#mtu", "-mtu"}, docker.DefaultNetworkMtu, "Set the containers network mtu") ) - flag.Var(&flDns, "dns", "Force docker to use specific DNS servers") - flag.Var(&flHosts, "H", "Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise") + flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers") + flag.Var(&flHosts, []string{"H", "-host"}, "Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise") flag.Parse() @@ -62,7 +62,7 @@ func main() { } if *bridgeName != "" && *bridgeIp != "" { - log.Fatal("You specified -b & -bip, mutually exclusive options. Please specify only one.") + log.Fatal("You specified -b & --bip, mutually exclusive options. Please specify only one.") } if *flDebug { diff --git a/docs/sources/examples/couchdb_data_volumes.rst b/docs/sources/examples/couchdb_data_volumes.rst index 1f6b4b7910..6cf3fab68c 100644 --- a/docs/sources/examples/couchdb_data_volumes.rst +++ b/docs/sources/examples/couchdb_data_volumes.rst @@ -41,7 +41,7 @@ This time, we're requesting shared access to ``$COUCH1``'s volumes. .. code-block:: bash - COUCH2=$(sudo docker run -d -p 5984 -volumes-from $COUCH1 shykes/couchdb:2013-05-03) + COUCH2=$(sudo docker run -d -p 5984 --volumes-from $COUCH1 shykes/couchdb:2013-05-03) Browse data on the second database ---------------------------------- diff --git a/docs/sources/examples/python_web_app.rst b/docs/sources/examples/python_web_app.rst index 3034bf980a..02160d0753 100644 --- a/docs/sources/examples/python_web_app.rst +++ b/docs/sources/examples/python_web_app.rst @@ -43,7 +43,7 @@ container. The ``BUILD_JOB`` environment variable will be set with the new conta [...] While this container is running, we can attach to the new container to -see what is going on. The flag ``-sig-proxy`` set as ``false`` allows you to connect and +see what is going on. The flag ``--sig-proxy`` set as ``false`` allows you to connect and disconnect (Ctrl-C) to it without stopping the container. .. code-block:: bash diff --git a/docs/sources/examples/running_redis_service.rst b/docs/sources/examples/running_redis_service.rst index 886f473ef2..9687f0cfa8 100644 --- a/docs/sources/examples/running_redis_service.rst +++ b/docs/sources/examples/running_redis_service.rst @@ -44,7 +44,7 @@ use a container link to provide access to our Redis database. .. code-block:: bash - sudo docker run -name redis -d /redis + sudo docker run --name redis -d /redis Create your web application container ------------------------------------- @@ -56,7 +56,7 @@ Redis instance running inside that container to only this container. .. code-block:: bash - sudo docker run -link redis:db -i -t ubuntu:12.10 /bin/bash + sudo docker run --link redis:db -i -t ubuntu:12.10 /bin/bash Once inside our freshly created container we need to install Redis to get the ``redis-cli`` binary to test our connection. diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 67c8b06189..a6ae75076b 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -26,22 +26,21 @@ To list available commands, either run ``docker`` with no parameters or execute :: Usage of docker: - -D=false: Enable debug mode - -H=[unix:///var/run/docker.sock]: tcp://[host[:port]] to bind or unix://[/path/to/socket] to use. When host=[0.0.0.0], port=[4243] or path=[/var/run/docker.sock] is omitted, default values are used. - -api-enable-cors=false: Enable CORS headers in the remote API - -b="": Attach containers to a pre-existing network bridge; use 'none' to disable container networking - -bip="": Use the provided CIDR notation address for the dynamically created bridge (docker0); Mutually exclusive of -b - -d=false: Enable daemon mode - -dns="": Force docker to use specific DNS servers - -g="/var/lib/docker": Path to use as the root of the docker runtime - -icc=true: Enable inter-container communication - -ip="0.0.0.0": Default IP address to use when binding container ports - -iptables=true: Disable docker's addition of iptables rules - -mtu=1500: Set the containers network mtu - -p="/var/run/docker.pid": Path to use for daemon PID file - -r=true: Restart previously running containers - -s="": Force the docker runtime to use a specific storage driver - -v=false: Print version information and quit + -D, --debug=false: Enable debug mode + -H, --host=[]: Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise + --api-enable-cors=false: Enable CORS headers in the remote API + -b, --bridge="": Attach containers to a pre-existing network bridge; use 'none' to disable container networking + --bip="": Use this CIDR notation address for the network bridge's IP, not compatible with -b + -d, --daemon=false: Enable daemon mode + --dns=[]: Force docker to use specific DNS servers + -g, --graph="/var/lib/docker": Path to use as the root of the docker runtime + --icc=true: Enable inter-container communication + --ip="0.0.0.0": Default IP address to use when binding container ports + --iptables=true: Disable docker's addition of iptables rules + -p, --pidfile="/var/run/docker.pid": Path to use for daemon PID file + -r, --restart=true: Restart previously running containers + -s, --storage-driver="": Force the docker runtime to use a specific storage driver + -v, --version=false: Print version information and quit The Docker daemon is the persistent process that manages containers. Docker uses the same binary for both the daemon and client. To run the daemon you provide the ``-d`` flag. @@ -75,8 +74,8 @@ the ``-H`` flag for the client. Attach to a running container. - -nostdin=false: Do not attach stdin - -sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) + --no-stdin=false: Do not attach stdin + --sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) You can detach from the container again (and leave it running) with ``CTRL-c`` (for a quiet exit) or ``CTRL-\`` to get a stacktrace of @@ -135,11 +134,11 @@ Examples: Usage: docker build [OPTIONS] PATH | URL | - Build a new container image from the source code at PATH - -t="": Repository name (and optionally a tag) to be applied + -t, --time="": Repository name (and optionally a tag) to be applied to the resulting image in case of success. - -q=false: Suppress verbose build output. - -no-cache: Do not use the cache when building the image. - -rm: Remove intermediate containers after a successful build + -q, --quiet=false: Suppress verbose build output. + --no-cache: Do not use the cache when building the image. + --rm: Remove intermediate containers after a successful build The files at ``PATH`` or ``URL`` are called the "context" of the build. The build process may refer to any of the files in the context, for example when @@ -233,9 +232,9 @@ by using the ``git://`` schema. Create a new image from a container's changes - -m="": Commit message - -author="": Author (eg. "John Hannibal Smith " - -run="": Configuration to be applied when the image is launched with `docker run`. + -m, --message="": Commit message + -a, --author="": Author (eg. "John Hannibal Smith " + --run="": Configuration to be applied when the image is launched with `docker run`. (ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}') .. _cli_commit_examples: @@ -279,7 +278,7 @@ run ``ls /etc``. Full -run example ................. -The ``-run`` JSON hash changes the ``Config`` section when running ``docker inspect CONTAINERID`` +The ``--run`` JSON hash changes the ``Config`` section when running ``docker inspect CONTAINERID`` or ``config`` when running ``docker inspect IMAGEID``. (Multiline is okay within a single quote ``'``) @@ -379,7 +378,7 @@ For example: Get real time events from the server - -since="": Show previously created events and then stream. + --since="": Show previously created events and then stream. (either seconds since epoch, or date string as below) .. _cli_events_example: @@ -459,8 +458,8 @@ For example: Show the history of an image - -notrunc=false: Don't truncate output - -q=false: only show numeric IDs + --no-trunc=false: Don't truncate output + -q, --quiet=false: only show numeric IDs To see how the ``docker:latest`` image was built: @@ -507,11 +506,11 @@ To see how the ``docker:latest`` image was built: List images - -a=false: show all images (by default filter out the intermediate images used to build) - -notrunc=false: Don't truncate output - -q=false: only show numeric IDs - -tree=false: output graph in tree format - -viz=false: output graph in graphviz format + -a, --all=false: show all images (by default filter out the intermediate images used to build) + --no-trunc=false: Don't truncate output + -q, --quiet=false: only show numeric IDs + --tree=false: output graph in tree format + --viz=false: output graph in graphviz format Listing the most recently created images ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -535,7 +534,7 @@ Listing the full length image IDs .. code-block:: bash - $ sudo docker images -notrunc | head + $ sudo docker images --no-trunc | head REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE 77af4d6b9913e693e8d0b4b294fa62ade6054e6b2f1ffb617ac955dd63fb0182 19 hours ago 1.089 GB committest latest b6fa739cedf5ea12a620a439402b6004d057da800f91c7524b5086a5e4749c9f 19 hours ago 1.089 GB @@ -552,7 +551,7 @@ Displaying images visually .. code-block:: bash - $ sudo docker images -viz | dot -Tpng -o docker.png + $ sudo docker images --viz | dot -Tpng -o docker.png .. image:: docker_images.gif :alt: Example inheritance graph of Docker images. @@ -563,7 +562,7 @@ Displaying image hierarchy .. code-block:: bash - $ sudo docker images -tree + $ sudo docker images --tree ├─8dbd9e392a96 Size: 131.5 MB (virtual 131.5 MB) Tags: ubuntu:12.04,ubuntu:latest,ubuntu:precise └─27cf78414709 Size: 180.1 MB (virtual 180.1 MB) @@ -702,7 +701,7 @@ Insert file from GitHub Return low-level information on a container/image - -format="": Format the output using the given go template. + -f, --format="": Format the output using the given go template. By default, this will render all results in a JSON array. If a format is specified, the given template will be executed for each result. @@ -721,7 +720,7 @@ fairly straightforward manner. .. code-block:: bash - $ sudo docker inspect -format='{{.NetworkSettings.IPAddress}}' $INSTANCE_ID + $ sudo docker inspect --format='{{.NetworkSettings.IPAddress}}' $INSTANCE_ID List All Port Bindings ...................... @@ -790,9 +789,9 @@ Known Issues (kill) Register or Login to the docker registry server - -e="": email - -p="": password - -u="": username + -e, --email="": email + -p, --password="": password + -u, --username="": username If you want to login to a private registry you can specify this by adding the server name. @@ -812,12 +811,14 @@ Known Issues (kill) Fetch the logs of a container + -f, --follow=false: Follow log output + The ``docker logs`` command is a convenience which batch-retrieves whatever logs are present at the time of execution. This does not guarantee execution order when combined with a ``docker run`` (i.e. your run may not have generated any logs at the time you execute ``docker logs``). -The ``docker logs -f`` command combines ``docker logs`` and ``docker attach``: +The ``docker logs --follow`` command combines ``docker logs`` and ``docker attach``: it will first return all logs from the beginning and then continue streaming new output from the container's stdout and stderr. @@ -845,9 +846,9 @@ new output from the container's stdout and stderr. List containers - -a=false: Show all containers. Only running containers are shown by default. - -notrunc=false: Don't truncate output - -q=false: Only display numeric IDs + -a, --all=false: Show all containers. Only running containers are shown by default. + --no-trunc=false: Don't truncate output + -q, --quiet=false: Only display numeric IDs Running ``docker ps`` showing 2 linked containers. @@ -903,7 +904,7 @@ Running ``docker ps`` showing 2 linked containers. Usage: docker rm [OPTIONS] CONTAINER Remove one or more containers - -link="": Remove the link instead of the actual container + --link="": Remove the link instead of the actual container Known Issues (rm) ~~~~~~~~~~~~~~~~~ @@ -926,7 +927,7 @@ This will remove the container referenced under the link ``/redis``. .. code-block:: bash - $ sudo docker rm -link /webapp/redis + $ sudo docker rm --link /webapp/redis /webapp/redis @@ -996,31 +997,31 @@ image is removed. Run a command in a new container - -a=map[]: Attach to stdin, stdout or stderr - -c=0: CPU shares (relative weight) - -cidfile="": Write the container ID to the file - -d=false: Detached mode: Run container in the background, print new container id - -e=[]: Set environment variables - -h="": Container host name - -i=false: Keep stdin open even if not attached - -privileged=false: Give extended privileges to this container - -m="": Memory limit (format: , where unit = b, k, m or g) - -n=true: Enable networking for this container - -p=[]: Map a network port to the container - -rm=false: Automatically remove the container when it exits (incompatible with -d) - -t=false: Allocate a pseudo-tty - -u="": Username or UID - -dns=[]: Set custom dns servers for the container - -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "container-dir" is missing, then docker creates a new volume. - -volumes-from="": Mount all volumes from the given container(s) - -entrypoint="": Overwrite the default entrypoint set by the image - -w="": Working directory inside the container - -lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" - -sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) - -expose=[]: Expose a port from the container without publishing it to your host - -link="": Add link to another container (name:alias) - -name="": Assign the specified name to the container. If no name is specific docker will generate a random name - -P=false: Publish all exposed ports to the host interfaces + -a, --attach=map[]: Attach to stdin, stdout or stderr + -c, --cpu-shares=0: CPU shares (relative weight) + --cidfile="": Write the container ID to the file + -d, --detach=false: Detached mode: Run container in the background, print new container id + -e, --env=[]: Set environment variables + -h, --host="": Container host name + -i, --interactive=false: Keep stdin open even if not attached + --privileged=false: Give extended privileges to this container + -m, --memory="": Memory limit (format: , where unit = b, k, m or g) + -n, --networking=true: Enable networking for this container + -p, --publish=[]: Map a network port to the container + --rm=false: Automatically remove the container when it exits (incompatible with -d) + -t, --tty=false: Allocate a pseudo-tty + -u, --username="": Username or UID + --dns=[]: Set custom dns servers for the container + -v, --volume=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "container-dir" is missing, then docker creates a new volume. + --volumes-from="": Mount all volumes from the given container(s) + --entrypoint="": Overwrite the default entrypoint set by the image + -w, --workdir="": Working directory inside the container + --lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" + --sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) + --expose=[]: Expose a port from the container without publishing it to your host + --link="": Add link to another container (name:alias) + --name="": Assign the specified name to the container. If no name is specific docker will generate a random name + -P, --publish-all=false: Publish all exposed ports to the host interfaces The ``docker run`` command first ``creates`` a writeable container layer over the specified image, and then ``starts`` it using the specified command. That @@ -1042,7 +1043,7 @@ Examples: .. code-block:: bash - $ sudo docker run -cidfile /tmp/docker_test.cid ubuntu echo "test" + $ sudo docker run --cidfile /tmp/docker_test.cid ubuntu echo "test" This will create a container and print ``test`` to the console. The ``cidfile`` flag makes Docker attempt to create a new file and write the @@ -1051,7 +1052,7 @@ error. Docker will close this file when ``docker run`` exits. .. code-block:: bash - $ sudo docker run -t -i -rm ubuntu bash + $ sudo docker run -t -i --rm ubuntu bash root@bc338942ef20:/# mount -t tmpfs none /mnt mount: permission denied @@ -1063,7 +1064,7 @@ allow it to run: .. code-block:: bash - $ sudo docker run -privileged ubuntu bash + $ sudo docker run --privileged ubuntu bash root@50e3f57e16e6:/# mount -t tmpfs none /mnt root@50e3f57e16e6:/# df -h Filesystem Size Used Avail Use% Mounted on @@ -1104,7 +1105,7 @@ in Docker. .. code-block:: bash - $ sudo docker run -expose 80 ubuntu bash + $ sudo docker run --expose 80 ubuntu bash This exposes port ``80`` of the container for use within a link without publishing the port to the host system's interfaces. :ref:`port_redirection` @@ -1112,28 +1113,28 @@ explains in detail how to manipulate ports in Docker. .. code-block:: bash - $ sudo docker run -name console -t -i ubuntu bash + $ sudo docker run --name console -t -i ubuntu bash This will create and run a new container with the container name being ``console``. .. code-block:: bash - $ sudo docker run -link /redis:redis -name console ubuntu bash + $ sudo docker run --link /redis:redis --name console ubuntu bash -The ``-link`` flag will link the container named ``/redis`` into the +The ``--link`` flag will link the container named ``/redis`` into the newly created container with the alias ``redis``. The new container can access the network and environment of the redis container via -environment variables. The ``-name`` flag will assign the name ``console`` +environment variables. The ``--name`` flag will assign the name ``console`` to the newly created container. .. code-block:: bash - $ sudo docker run -volumes-from 777f7dc92da7,ba8c0c54f0f2:ro -i -t ubuntu pwd + $ sudo docker run --volumes-from 777f7dc92da7,ba8c0c54f0f2:ro -i -t ubuntu pwd -The ``-volumes-from`` flag mounts all the defined volumes from the +The ``--volumes-from`` flag mounts all the defined volumes from the referenced containers. Containers can be specified by a comma seperated -list or by repetitions of the ``-volumes-from`` argument. The container +list or by repetitions of the ``--volumes-from`` argument. The container ID may be optionally suffixed with ``:ro`` or ``:rw`` to mount the volumes in read-only or read-write mode, respectively. By default, the volumes are mounted in the same mode (read write or read only) as the reference container. @@ -1143,11 +1144,11 @@ A complete example .. code-block:: bash - $ sudo docker run -d -name static static-web-files sh - $ sudo docker run -d -expose=8098 -name riak riakserver - $ sudo docker run -d -m 100m -e DEVELOPMENT=1 -e BRANCH=example-code -v $(pwd):/app/bin:ro -name app appserver - $ sudo docker run -d -p 1443:443 -dns=dns.dev.org -v /var/log/httpd -volumes-from static -link riak -link app -h www.sven.dev.org -name web webserver - $ sudo docker run -t -i -rm -volumes-from web -w /var/log/httpd busybox tail -f access.log + $ sudo docker run -d --name static static-web-files sh + $ sudo docker run -d --expose=8098 --name riak riakserver + $ sudo docker run -d -m 100m -e DEVELOPMENT=1 -e BRANCH=example-code -v $(pwd):/app/bin:ro --name app appserver + $ sudo docker run -d -p 1443:443 --dns=dns.dev.org -v /var/log/httpd --volumes-from static --link riak --link app -h www.sven.dev.org --name web webserver + $ sudo docker run -t -i --rm --volumes-from web -w /var/log/httpd busybox tail -f access.log This example shows 5 containers that might be set up to test a web application change: @@ -1181,9 +1182,9 @@ This example shows 5 containers that might be set up to test a web application c Search the docker index for images - -notrunc=false: Don't truncate output - -stars=0: Only displays with at least xxx stars - -trusted=false: Only show trusted builds + --no-trunc=false: Don't truncate output + -s, --stars=0: Only displays with at least xxx stars + -t, --trusted=false: Only show trusted builds .. _cli_start: @@ -1196,8 +1197,8 @@ This example shows 5 containers that might be set up to test a web application c Start a stopped container - -a=false: Attach container's stdout/stderr and forward all signals to the process - -i=false: Attach container's stdin + -a, --attach=false: Attach container's stdout/stderr and forward all signals to the process + -i, --interactive=false: Attach container's stdin .. _cli_stop: @@ -1210,7 +1211,7 @@ This example shows 5 containers that might be set up to test a web application c Stop a running container (Send SIGTERM, and then SIGKILL after grace period) - -t=10: Number of seconds to wait for the container to stop before killing it. + -t, --time=10: Number of seconds to wait for the container to stop before killing it. The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL @@ -1225,7 +1226,7 @@ The main process inside the container will receive SIGTERM, and after a grace pe Tag an image into a repository - -f=false: Force + -f, --force=false: Force .. _cli_top: diff --git a/pkg/mflag/LICENSE b/pkg/mflag/LICENSE new file mode 100644 index 0000000000..ebcfbcc779 --- /dev/null +++ b/pkg/mflag/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2014 The Docker & Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pkg/mflag/README.md b/pkg/mflag/README.md new file mode 100644 index 0000000000..da00efa336 --- /dev/null +++ b/pkg/mflag/README.md @@ -0,0 +1,40 @@ +Package mflag (aka multiple-flag) implements command-line flag parsing. +It's an **hacky** fork of the [official golang package](http://golang.org/pkg/flag/) + +It adds: + +* both short and long flag version +`./example -s red` `./example --string blue` + +* multiple names for the same option +``` +$>./example -h +Usage of example: + -s, --string="": a simple string +``` + +___ +It is very flexible on purpose, so you can do things like: +``` +$>./example -h +Usage of example: + -s, -string, --string="": a simple string +``` + +Or: +``` +$>./example -h +Usage of example: + -oldflag, --newflag="": a simple string +``` + +You can also hide some flags from the usage, so if we want only `--newflag`: +``` +$>./example -h +Usage of example: + --newflag="": a simple string +$>./example -oldflag str +str +``` + +See [example.go](example/example.go) for more details. diff --git a/pkg/mflag/example/example.go b/pkg/mflag/example/example.go new file mode 100644 index 0000000000..dcff4277e2 --- /dev/null +++ b/pkg/mflag/example/example.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/flag" +) + +var ( + i int + str string + b, h bool +) + +func init() { + flag.BoolVar(&b, []string{"b"}, false, "a simple bool") + flag.IntVar(&i, []string{"#integer", "-integer"}, -1, "a simple integer") + flag.StringVar(&str, []string{"s", "#hidden", "-string"}, "", "a simple string") //-s -hidden and --string will work, but -hidden won't be in the usage + flag.BoolVar(&h, []string{"h", "#help", "-help"}, false, "display the help") + flag.Parse() +} +func main() { + if h { + flag.PrintDefaults() + } + fmt.Printf("%s\n", str) + fmt.Printf("%s\n", flag.Lookup("s").Value.String()) +} diff --git a/pkg/mflag/export_test.go b/pkg/mflag/export_test.go new file mode 100644 index 0000000000..7c1cea0bd7 --- /dev/null +++ b/pkg/mflag/export_test.go @@ -0,0 +1,17 @@ +// Copyright 2014 The Docker & Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mflag + +import "os" + +// Additional routines compiled into the package only during testing. + +// ResetForTesting clears all flag state and sets the usage function as directed. +// After calling ResetForTesting, parse errors in flag handling will not +// exit the program. +func ResetForTesting(usage func()) { + CommandLine = NewFlagSet(os.Args[0], ContinueOnError) + Usage = usage +} diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go new file mode 100644 index 0000000000..8e22c0e959 --- /dev/null +++ b/pkg/mflag/flag.go @@ -0,0 +1,863 @@ +// Copyright 2014 The Docker & Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* + Package flag implements command-line flag parsing. + + Usage: + + Define flags using flag.String(), Bool(), Int(), etc. + + This declares an integer flag, -f or --flagname, stored in the pointer ip, with type *int. + import "flag" + var ip = flag.Int([]string{"f", "-flagname"}, 1234, "help message for flagname") + If you like, you can bind the flag to a variable using the Var() functions. + var flagvar int + func init() { + // -flaghidden will work, but will be hidden from the usage + flag.IntVar(&flagvar, []string{"f", "#flaghidden", "-flagname"}, 1234, "help message for flagname") + } + Or you can create custom flags that satisfy the Value interface (with + pointer receivers) and couple them to flag parsing by + flag.Var(&flagVal, []string{"name"}, "help message for flagname") + For such flags, the default value is just the initial value of the variable. + + After all flags are defined, call + flag.Parse() + to parse the command line into the defined flags. + + Flags may then be used directly. If you're using the flags themselves, + they are all pointers; if you bind to variables, they're values. + fmt.Println("ip has value ", *ip) + fmt.Println("flagvar has value ", flagvar) + + After parsing, the arguments after the flag are available as the + slice flag.Args() or individually as flag.Arg(i). + The arguments are indexed from 0 through flag.NArg()-1. + + Command line flag syntax: + -flag + -flag=x + -flag x // non-boolean flags only + One or two minus signs may be used; they are equivalent. + The last form is not permitted for boolean flags because the + meaning of the command + cmd -x * + will change if there is a file called 0, false, etc. You must + use the -flag=false form to turn off a boolean flag. + + Flag parsing stops just before the first non-flag argument + ("-" is a non-flag argument) or after the terminator "--". + + Integer flags accept 1234, 0664, 0x1234 and may be negative. + Boolean flags may be 1, 0, t, f, true, false, TRUE, FALSE, True, False. + Duration flags accept any input valid for time.ParseDuration. + + The default set of command-line flags is controlled by + top-level functions. The FlagSet type allows one to define + independent sets of flags, such as to implement subcommands + in a command-line interface. The methods of FlagSet are + analogous to the top-level functions for the command-line + flag set. +*/ +package mflag + +import ( + "errors" + "fmt" + "io" + "os" + "sort" + "strconv" + "strings" + "time" +) + +// ErrHelp is the error returned if the flag -help is invoked but no such flag is defined. +var ErrHelp = errors.New("flag: help requested") + +// -- bool Value +type boolValue bool + +func newBoolValue(val bool, p *bool) *boolValue { + *p = val + return (*boolValue)(p) +} + +func (b *boolValue) Set(s string) error { + v, err := strconv.ParseBool(s) + *b = boolValue(v) + return err +} + +func (b *boolValue) Get() interface{} { return bool(*b) } + +func (b *boolValue) String() string { return fmt.Sprintf("%v", *b) } + +func (b *boolValue) IsBoolFlag() bool { return true } + +// optional interface to indicate boolean flags that can be +// supplied without "=value" text +type boolFlag interface { + Value + IsBoolFlag() bool +} + +// -- int Value +type intValue int + +func newIntValue(val int, p *int) *intValue { + *p = val + return (*intValue)(p) +} + +func (i *intValue) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 64) + *i = intValue(v) + return err +} + +func (i *intValue) Get() interface{} { return int(*i) } + +func (i *intValue) String() string { return fmt.Sprintf("%v", *i) } + +// -- int64 Value +type int64Value int64 + +func newInt64Value(val int64, p *int64) *int64Value { + *p = val + return (*int64Value)(p) +} + +func (i *int64Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 64) + *i = int64Value(v) + return err +} + +func (i *int64Value) Get() interface{} { return int64(*i) } + +func (i *int64Value) String() string { return fmt.Sprintf("%v", *i) } + +// -- uint Value +type uintValue uint + +func newUintValue(val uint, p *uint) *uintValue { + *p = val + return (*uintValue)(p) +} + +func (i *uintValue) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 64) + *i = uintValue(v) + return err +} + +func (i *uintValue) Get() interface{} { return uint(*i) } + +func (i *uintValue) String() string { return fmt.Sprintf("%v", *i) } + +// -- uint64 Value +type uint64Value uint64 + +func newUint64Value(val uint64, p *uint64) *uint64Value { + *p = val + return (*uint64Value)(p) +} + +func (i *uint64Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 64) + *i = uint64Value(v) + return err +} + +func (i *uint64Value) Get() interface{} { return uint64(*i) } + +func (i *uint64Value) String() string { return fmt.Sprintf("%v", *i) } + +// -- string Value +type stringValue string + +func newStringValue(val string, p *string) *stringValue { + *p = val + return (*stringValue)(p) +} + +func (s *stringValue) Set(val string) error { + *s = stringValue(val) + return nil +} + +func (s *stringValue) Get() interface{} { return string(*s) } + +func (s *stringValue) String() string { return fmt.Sprintf("%s", *s) } + +// -- float64 Value +type float64Value float64 + +func newFloat64Value(val float64, p *float64) *float64Value { + *p = val + return (*float64Value)(p) +} + +func (f *float64Value) Set(s string) error { + v, err := strconv.ParseFloat(s, 64) + *f = float64Value(v) + return err +} + +func (f *float64Value) Get() interface{} { return float64(*f) } + +func (f *float64Value) String() string { return fmt.Sprintf("%v", *f) } + +// -- time.Duration Value +type durationValue time.Duration + +func newDurationValue(val time.Duration, p *time.Duration) *durationValue { + *p = val + return (*durationValue)(p) +} + +func (d *durationValue) Set(s string) error { + v, err := time.ParseDuration(s) + *d = durationValue(v) + return err +} + +func (d *durationValue) Get() interface{} { return time.Duration(*d) } + +func (d *durationValue) String() string { return (*time.Duration)(d).String() } + +// Value is the interface to the dynamic value stored in a flag. +// (The default value is represented as a string.) +// +// If a Value has an IsBoolFlag() bool method returning true, +// the command-line parser makes -name equivalent to -name=true +// rather than using the next command-line argument. +type Value interface { + String() string + Set(string) error +} + +// Getter is an interface that allows the contents of a Value to be retrieved. +// It wraps the Value interface, rather than being part of it, because it +// appeared after Go 1 and its compatibility rules. All Value types provided +// by this package satisfy the Getter interface. +type Getter interface { + Value + Get() interface{} +} + +// ErrorHandling defines how to handle flag parsing errors. +type ErrorHandling int + +const ( + ContinueOnError ErrorHandling = iota + ExitOnError + PanicOnError +) + +// A FlagSet represents a set of defined flags. The zero value of a FlagSet +// has no name and has ContinueOnError error handling. +type FlagSet struct { + // Usage is the function called when an error occurs while parsing flags. + // The field is a function (not a method) that may be changed to point to + // a custom error handler. + Usage func() + + name string + parsed bool + actual map[string]*Flag + formal map[string]*Flag + args []string // arguments after flags + errorHandling ErrorHandling + output io.Writer // nil means stderr; use out() accessor +} + +// A Flag represents the state of a flag. +type Flag struct { + Names []string // name as it appears on command line + Usage string // help message + Value Value // value as set + DefValue string // default value (as text); for usage message +} + +// sortFlags returns the flags as a slice in lexicographical sorted order. +func sortFlags(flags map[string]*Flag) []*Flag { + var list sort.StringSlice + for _, f := range flags { + found := false + fName := strings.TrimPrefix(strings.TrimPrefix(f.Names[0], "#"), "-") + for _, name := range list { + if name == fName { + found = true + break + } + } + if !found { + list = append(list, fName) + } + } + list.Sort() + result := make([]*Flag, len(list)) + for i, name := range list { + result[i] = flags[name] + } + return result +} + +func (f *FlagSet) out() io.Writer { + if f.output == nil { + return os.Stderr + } + return f.output +} + +// SetOutput sets the destination for usage and error messages. +// If output is nil, os.Stderr is used. +func (f *FlagSet) SetOutput(output io.Writer) { + f.output = output +} + +// VisitAll visits the flags in lexicographical order, calling fn for each. +// It visits all flags, even those not set. +func (f *FlagSet) VisitAll(fn func(*Flag)) { + for _, flag := range sortFlags(f.formal) { + fn(flag) + } +} + +// VisitAll visits the command-line flags in lexicographical order, calling +// fn for each. It visits all flags, even those not set. +func VisitAll(fn func(*Flag)) { + CommandLine.VisitAll(fn) +} + +// Visit visits the flags in lexicographical order, calling fn for each. +// It visits only those flags that have been set. +func (f *FlagSet) Visit(fn func(*Flag)) { + for _, flag := range sortFlags(f.actual) { + fn(flag) + } +} + +// Visit visits the command-line flags in lexicographical order, calling fn +// for each. It visits only those flags that have been set. +func Visit(fn func(*Flag)) { + CommandLine.Visit(fn) +} + +// Lookup returns the Flag structure of the named flag, returning nil if none exists. +func (f *FlagSet) Lookup(name string) *Flag { + return f.formal[name] +} + +// Lookup returns the Flag structure of the named command-line flag, +// returning nil if none exists. +func Lookup(name string) *Flag { + return CommandLine.formal[name] +} + +// Set sets the value of the named flag. +func (f *FlagSet) Set(name, value string) error { + flag, ok := f.formal[name] + if !ok { + return fmt.Errorf("no such flag -%v", name) + } + err := flag.Value.Set(value) + if err != nil { + return err + } + if f.actual == nil { + f.actual = make(map[string]*Flag) + } + f.actual[name] = flag + return nil +} + +// Set sets the value of the named command-line flag. +func Set(name, value string) error { + return CommandLine.Set(name, value) +} + +// PrintDefaults prints, to standard error unless configured +// otherwise, the default values of all defined flags in the set. +func (f *FlagSet) PrintDefaults() { + f.VisitAll(func(flag *Flag) { + format := " -%s=%s: %s\n" + if _, ok := flag.Value.(*stringValue); ok { + // put quotes on the value + format = " -%s=%q: %s\n" + } + names := []string{} + for _, name := range flag.Names { + if name[0] != '#' { + names = append(names, name) + } + } + fmt.Fprintf(f.out(), format, strings.Join(names, ", -"), flag.DefValue, flag.Usage) + }) +} + +// PrintDefaults prints to standard error the default values of all defined command-line flags. +func PrintDefaults() { + CommandLine.PrintDefaults() +} + +// defaultUsage is the default function to print a usage message. +func defaultUsage(f *FlagSet) { + if f.name == "" { + fmt.Fprintf(f.out(), "Usage:\n") + } else { + fmt.Fprintf(f.out(), "Usage of %s:\n", f.name) + } + f.PrintDefaults() +} + +// NOTE: Usage is not just defaultUsage(CommandLine) +// because it serves (via godoc flag Usage) as the example +// for how to write your own usage function. + +// Usage prints to standard error a usage message documenting all defined command-line flags. +// The function is a variable that may be changed to point to a custom function. +var Usage = func() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + PrintDefaults() +} + +// NFlag returns the number of flags that have been set. +func (f *FlagSet) NFlag() int { return len(f.actual) } + +// NFlag returns the number of command-line flags that have been set. +func NFlag() int { return len(CommandLine.actual) } + +// Arg returns the i'th argument. Arg(0) is the first remaining argument +// after flags have been processed. +func (f *FlagSet) Arg(i int) string { + if i < 0 || i >= len(f.args) { + return "" + } + return f.args[i] +} + +// Arg returns the i'th command-line argument. Arg(0) is the first remaining argument +// after flags have been processed. +func Arg(i int) string { + return CommandLine.Arg(i) +} + +// NArg is the number of arguments remaining after flags have been processed. +func (f *FlagSet) NArg() int { return len(f.args) } + +// NArg is the number of arguments remaining after flags have been processed. +func NArg() int { return len(CommandLine.args) } + +// Args returns the non-flag arguments. +func (f *FlagSet) Args() []string { return f.args } + +// Args returns the non-flag command-line arguments. +func Args() []string { return CommandLine.args } + +// BoolVar defines a bool flag with specified name, default value, and usage string. +// The argument p points to a bool variable in which to store the value of the flag. +func (f *FlagSet) BoolVar(p *bool, names []string, value bool, usage string) { + f.Var(newBoolValue(value, p), names, usage) +} + +// BoolVar defines a bool flag with specified name, default value, and usage string. +// The argument p points to a bool variable in which to store the value of the flag. +func BoolVar(p *bool, names []string, value bool, usage string) { + CommandLine.Var(newBoolValue(value, p), names, usage) +} + +// Bool defines a bool flag with specified name, default value, and usage string. +// The return value is the address of a bool variable that stores the value of the flag. +func (f *FlagSet) Bool(names []string, value bool, usage string) *bool { + p := new(bool) + f.BoolVar(p, names, value, usage) + return p +} + +// Bool defines a bool flag with specified name, default value, and usage string. +// The return value is the address of a bool variable that stores the value of the flag. +func Bool(names []string, value bool, usage string) *bool { + return CommandLine.Bool(names, value, usage) +} + +// IntVar defines an int flag with specified name, default value, and usage string. +// The argument p points to an int variable in which to store the value of the flag. +func (f *FlagSet) IntVar(p *int, names []string, value int, usage string) { + f.Var(newIntValue(value, p), names, usage) +} + +// IntVar defines an int flag with specified name, default value, and usage string. +// The argument p points to an int variable in which to store the value of the flag. +func IntVar(p *int, names []string, value int, usage string) { + CommandLine.Var(newIntValue(value, p), names, usage) +} + +// Int defines an int flag with specified name, default value, and usage string. +// The return value is the address of an int variable that stores the value of the flag. +func (f *FlagSet) Int(names []string, value int, usage string) *int { + p := new(int) + f.IntVar(p, names, value, usage) + return p +} + +// Int defines an int flag with specified name, default value, and usage string. +// The return value is the address of an int variable that stores the value of the flag. +func Int(names []string, value int, usage string) *int { + return CommandLine.Int(names, value, usage) +} + +// Int64Var defines an int64 flag with specified name, default value, and usage string. +// The argument p points to an int64 variable in which to store the value of the flag. +func (f *FlagSet) Int64Var(p *int64, names []string, value int64, usage string) { + f.Var(newInt64Value(value, p), names, usage) +} + +// Int64Var defines an int64 flag with specified name, default value, and usage string. +// The argument p points to an int64 variable in which to store the value of the flag. +func Int64Var(p *int64, names []string, value int64, usage string) { + CommandLine.Var(newInt64Value(value, p), names, usage) +} + +// Int64 defines an int64 flag with specified name, default value, and usage string. +// The return value is the address of an int64 variable that stores the value of the flag. +func (f *FlagSet) Int64(names []string, value int64, usage string) *int64 { + p := new(int64) + f.Int64Var(p, names, value, usage) + return p +} + +// Int64 defines an int64 flag with specified name, default value, and usage string. +// The return value is the address of an int64 variable that stores the value of the flag. +func Int64(names []string, value int64, usage string) *int64 { + return CommandLine.Int64(names, value, usage) +} + +// UintVar defines a uint flag with specified name, default value, and usage string. +// The argument p points to a uint variable in which to store the value of the flag. +func (f *FlagSet) UintVar(p *uint, names []string, value uint, usage string) { + f.Var(newUintValue(value, p), names, usage) +} + +// UintVar defines a uint flag with specified name, default value, and usage string. +// The argument p points to a uint variable in which to store the value of the flag. +func UintVar(p *uint, names []string, value uint, usage string) { + CommandLine.Var(newUintValue(value, p), names, usage) +} + +// Uint defines a uint flag with specified name, default value, and usage string. +// The return value is the address of a uint variable that stores the value of the flag. +func (f *FlagSet) Uint(names []string, value uint, usage string) *uint { + p := new(uint) + f.UintVar(p, names, value, usage) + return p +} + +// Uint defines a uint flag with specified name, default value, and usage string. +// The return value is the address of a uint variable that stores the value of the flag. +func Uint(names []string, value uint, usage string) *uint { + return CommandLine.Uint(names, value, usage) +} + +// Uint64Var defines a uint64 flag with specified name, default value, and usage string. +// The argument p points to a uint64 variable in which to store the value of the flag. +func (f *FlagSet) Uint64Var(p *uint64, names []string, value uint64, usage string) { + f.Var(newUint64Value(value, p), names, usage) +} + +// Uint64Var defines a uint64 flag with specified name, default value, and usage string. +// The argument p points to a uint64 variable in which to store the value of the flag. +func Uint64Var(p *uint64, names []string, value uint64, usage string) { + CommandLine.Var(newUint64Value(value, p), names, usage) +} + +// Uint64 defines a uint64 flag with specified name, default value, and usage string. +// The return value is the address of a uint64 variable that stores the value of the flag. +func (f *FlagSet) Uint64(names []string, value uint64, usage string) *uint64 { + p := new(uint64) + f.Uint64Var(p, names, value, usage) + return p +} + +// Uint64 defines a uint64 flag with specified name, default value, and usage string. +// The return value is the address of a uint64 variable that stores the value of the flag. +func Uint64(names []string, value uint64, usage string) *uint64 { + return CommandLine.Uint64(names, value, usage) +} + +// StringVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a string variable in which to store the value of the flag. +func (f *FlagSet) StringVar(p *string, names []string, value string, usage string) { + f.Var(newStringValue(value, p), names, usage) +} + +// StringVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a string variable in which to store the value of the flag. +func StringVar(p *string, names []string, value string, usage string) { + CommandLine.Var(newStringValue(value, p), names, usage) +} + +// String defines a string flag with specified name, default value, and usage string. +// The return value is the address of a string variable that stores the value of the flag. +func (f *FlagSet) String(names []string, value string, usage string) *string { + p := new(string) + f.StringVar(p, names, value, usage) + return p +} + +// String defines a string flag with specified name, default value, and usage string. +// The return value is the address of a string variable that stores the value of the flag. +func String(names []string, value string, usage string) *string { + return CommandLine.String(names, value, usage) +} + +// Float64Var defines a float64 flag with specified name, default value, and usage string. +// The argument p points to a float64 variable in which to store the value of the flag. +func (f *FlagSet) Float64Var(p *float64, names []string, value float64, usage string) { + f.Var(newFloat64Value(value, p), names, usage) +} + +// Float64Var defines a float64 flag with specified name, default value, and usage string. +// The argument p points to a float64 variable in which to store the value of the flag. +func Float64Var(p *float64, names []string, value float64, usage string) { + CommandLine.Var(newFloat64Value(value, p), names, usage) +} + +// Float64 defines a float64 flag with specified name, default value, and usage string. +// The return value is the address of a float64 variable that stores the value of the flag. +func (f *FlagSet) Float64(names []string, value float64, usage string) *float64 { + p := new(float64) + f.Float64Var(p, names, value, usage) + return p +} + +// Float64 defines a float64 flag with specified name, default value, and usage string. +// The return value is the address of a float64 variable that stores the value of the flag. +func Float64(names []string, value float64, usage string) *float64 { + return CommandLine.Float64(names, value, usage) +} + +// DurationVar defines a time.Duration flag with specified name, default value, and usage string. +// The argument p points to a time.Duration variable in which to store the value of the flag. +func (f *FlagSet) DurationVar(p *time.Duration, names []string, value time.Duration, usage string) { + f.Var(newDurationValue(value, p), names, usage) +} + +// DurationVar defines a time.Duration flag with specified name, default value, and usage string. +// The argument p points to a time.Duration variable in which to store the value of the flag. +func DurationVar(p *time.Duration, names []string, value time.Duration, usage string) { + CommandLine.Var(newDurationValue(value, p), names, usage) +} + +// Duration defines a time.Duration flag with specified name, default value, and usage string. +// The return value is the address of a time.Duration variable that stores the value of the flag. +func (f *FlagSet) Duration(names []string, value time.Duration, usage string) *time.Duration { + p := new(time.Duration) + f.DurationVar(p, names, value, usage) + return p +} + +// Duration defines a time.Duration flag with specified name, default value, and usage string. +// The return value is the address of a time.Duration variable that stores the value of the flag. +func Duration(names []string, value time.Duration, usage string) *time.Duration { + return CommandLine.Duration(names, value, usage) +} + +// Var defines a flag with the specified name and usage string. The type and +// value of the flag are represented by the first argument, of type Value, which +// typically holds a user-defined implementation of Value. For instance, the +// caller could create a flag that turns a comma-separated string into a slice +// of strings by giving the slice the methods of Value; in particular, Set would +// decompose the comma-separated string into the slice. +func (f *FlagSet) Var(value Value, names []string, usage string) { + // Remember the default value as a string; it won't change. + flag := &Flag{names, usage, value, value.String()} + for _, name := range names { + name = strings.TrimPrefix(name, "#") + _, alreadythere := f.formal[name] + if alreadythere { + var msg string + if f.name == "" { + msg = fmt.Sprintf("flag redefined: %s", name) + } else { + msg = fmt.Sprintf("%s flag redefined: %s", f.name, name) + } + fmt.Fprintln(f.out(), msg) + panic(msg) // Happens only if flags are declared with identical names + } + if f.formal == nil { + f.formal = make(map[string]*Flag) + } + f.formal[name] = flag + } +} + +// Var defines a flag with the specified name and usage string. The type and +// value of the flag are represented by the first argument, of type Value, which +// typically holds a user-defined implementation of Value. For instance, the +// caller could create a flag that turns a comma-separated string into a slice +// of strings by giving the slice the methods of Value; in particular, Set would +// decompose the comma-separated string into the slice. +func Var(value Value, names []string, usage string) { + CommandLine.Var(value, names, usage) +} + +// failf prints to standard error a formatted error and usage message and +// returns the error. +func (f *FlagSet) failf(format string, a ...interface{}) error { + err := fmt.Errorf(format, a...) + fmt.Fprintln(f.out(), err) + f.usage() + return err +} + +// usage calls the Usage method for the flag set, or the usage function if +// the flag set is CommandLine. +func (f *FlagSet) usage() { + if f == CommandLine { + Usage() + } else if f.Usage == nil { + defaultUsage(f) + } else { + f.Usage() + } +} + +// parseOne parses one flag. It reports whether a flag was seen. +func (f *FlagSet) parseOne() (bool, error) { + if len(f.args) == 0 { + return false, nil + } + s := f.args[0] + if len(s) == 0 || s[0] != '-' || len(s) == 1 { + return false, nil + } + if s[1] == '-' && len(s) == 2 { // "--" terminates the flags + f.args = f.args[1:] + return false, nil + } + name := s[1:] + if len(name) == 0 || name[0] == '=' { + return false, f.failf("bad flag syntax: %s", s) + } + + // it's a flag. does it have an argument? + f.args = f.args[1:] + has_value := false + value := "" + for i := 1; i < len(name); i++ { // equals cannot be first + if name[i] == '=' { + value = name[i+1:] + has_value = true + name = name[0:i] + break + } + } + m := f.formal + flag, alreadythere := m[name] // BUG + if !alreadythere { + if name == "-help" || name == "help" || name == "h" { // special case for nice help message. + f.usage() + return false, ErrHelp + } + return false, f.failf("flag provided but not defined: -%s", name) + } + if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg + if has_value { + if err := fv.Set(value); err != nil { + return false, f.failf("invalid boolean value %q for -%s: %v", value, name, err) + } + } else { + fv.Set("true") + } + } else { + // It must have a value, which might be the next argument. + if !has_value && len(f.args) > 0 { + // value is the next arg + has_value = true + value, f.args = f.args[0], f.args[1:] + } + if !has_value { + return false, f.failf("flag needs an argument: -%s", name) + } + if err := flag.Value.Set(value); err != nil { + return false, f.failf("invalid value %q for flag -%s: %v", value, name, err) + } + } + if f.actual == nil { + f.actual = make(map[string]*Flag) + } + f.actual[name] = flag + return true, nil +} + +// Parse parses flag definitions from the argument list, which should not +// include the command name. Must be called after all flags in the FlagSet +// are defined and before flags are accessed by the program. +// The return value will be ErrHelp if -help was set but not defined. +func (f *FlagSet) Parse(arguments []string) error { + f.parsed = true + f.args = arguments + for { + seen, err := f.parseOne() + if seen { + continue + } + if err == nil { + break + } + switch f.errorHandling { + case ContinueOnError: + return err + case ExitOnError: + os.Exit(2) + case PanicOnError: + panic(err) + } + } + return nil +} + +// Parsed reports whether f.Parse has been called. +func (f *FlagSet) Parsed() bool { + return f.parsed +} + +// Parse parses the command-line flags from os.Args[1:]. Must be called +// after all flags are defined and before flags are accessed by the program. +func Parse() { + // Ignore errors; CommandLine is set for ExitOnError. + CommandLine.Parse(os.Args[1:]) +} + +// Parsed returns true if the command-line flags have been parsed. +func Parsed() bool { + return CommandLine.Parsed() +} + +// CommandLine is the default set of command-line flags, parsed from os.Args. +// The top-level functions such as BoolVar, Arg, and on are wrappers for the +// methods of CommandLine. +var CommandLine = NewFlagSet(os.Args[0], ExitOnError) + +// NewFlagSet returns a new, empty flag set with the specified name and +// error handling property. +func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet { + f := &FlagSet{ + name: name, + errorHandling: errorHandling, + } + return f +} + +// Init sets the name and error handling property for a flag set. +// By default, the zero FlagSet uses an empty name and the +// ContinueOnError error handling policy. +func (f *FlagSet) Init(name string, errorHandling ErrorHandling) { + f.name = name + f.errorHandling = errorHandling +} diff --git a/pkg/mflag/flag_test.go b/pkg/mflag/flag_test.go new file mode 100644 index 0000000000..aecbbd0fbb --- /dev/null +++ b/pkg/mflag/flag_test.go @@ -0,0 +1,377 @@ +// Copyright 2014 The Docker & Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mflag_test + +import ( + "bytes" + "fmt" + . "github.com/dotcloud/docker/pkg/mflag" + "os" + "sort" + "strings" + "testing" + "time" +) + +func boolString(s string) string { + if s == "0" { + return "false" + } + return "true" +} + +func TestEverything(t *testing.T) { + ResetForTesting(nil) + Bool([]string{"test_bool"}, false, "bool value") + Int([]string{"test_int"}, 0, "int value") + Int64([]string{"test_int64"}, 0, "int64 value") + Uint([]string{"test_uint"}, 0, "uint value") + Uint64([]string{"test_uint64"}, 0, "uint64 value") + String([]string{"test_string"}, "0", "string value") + Float64([]string{"test_float64"}, 0, "float64 value") + Duration([]string{"test_duration"}, 0, "time.Duration value") + + m := make(map[string]*Flag) + desired := "0" + visitor := func(f *Flag) { + for _, name := range f.Names { + if len(name) > 5 && name[0:5] == "test_" { + m[name] = f + ok := false + switch { + case f.Value.String() == desired: + ok = true + case name == "test_bool" && f.Value.String() == boolString(desired): + ok = true + case name == "test_duration" && f.Value.String() == desired+"s": + ok = true + } + if !ok { + t.Error("Visit: bad value", f.Value.String(), "for", name) + } + } + } + } + VisitAll(visitor) + if len(m) != 8 { + t.Error("VisitAll misses some flags") + for k, v := range m { + t.Log(k, *v) + } + } + m = make(map[string]*Flag) + Visit(visitor) + if len(m) != 0 { + t.Errorf("Visit sees unset flags") + for k, v := range m { + t.Log(k, *v) + } + } + // Now set all flags + Set("test_bool", "true") + Set("test_int", "1") + Set("test_int64", "1") + Set("test_uint", "1") + Set("test_uint64", "1") + Set("test_string", "1") + Set("test_float64", "1") + Set("test_duration", "1s") + desired = "1" + Visit(visitor) + if len(m) != 8 { + t.Error("Visit fails after set") + for k, v := range m { + t.Log(k, *v) + } + } + // Now test they're visited in sort order. + var flagNames []string + Visit(func(f *Flag) { + for _, name := range f.Names { + flagNames = append(flagNames, name) + } + }) + if !sort.StringsAreSorted(flagNames) { + t.Errorf("flag names not sorted: %v", flagNames) + } +} + +func TestGet(t *testing.T) { + ResetForTesting(nil) + Bool([]string{"test_bool"}, true, "bool value") + Int([]string{"test_int"}, 1, "int value") + Int64([]string{"test_int64"}, 2, "int64 value") + Uint([]string{"test_uint"}, 3, "uint value") + Uint64([]string{"test_uint64"}, 4, "uint64 value") + String([]string{"test_string"}, "5", "string value") + Float64([]string{"test_float64"}, 6, "float64 value") + Duration([]string{"test_duration"}, 7, "time.Duration value") + + visitor := func(f *Flag) { + for _, name := range f.Names { + if len(name) > 5 && name[0:5] == "test_" { + g, ok := f.Value.(Getter) + if !ok { + t.Errorf("Visit: value does not satisfy Getter: %T", f.Value) + return + } + switch name { + case "test_bool": + ok = g.Get() == true + case "test_int": + ok = g.Get() == int(1) + case "test_int64": + ok = g.Get() == int64(2) + case "test_uint": + ok = g.Get() == uint(3) + case "test_uint64": + ok = g.Get() == uint64(4) + case "test_string": + ok = g.Get() == "5" + case "test_float64": + ok = g.Get() == float64(6) + case "test_duration": + ok = g.Get() == time.Duration(7) + } + if !ok { + t.Errorf("Visit: bad value %T(%v) for %s", g.Get(), g.Get(), name) + } + } + } + } + VisitAll(visitor) +} + +func TestUsage(t *testing.T) { + called := false + ResetForTesting(func() { called = true }) + if CommandLine.Parse([]string{"-x"}) == nil { + t.Error("parse did not fail for unknown flag") + } + if !called { + t.Error("did not call Usage for unknown flag") + } +} + +func testParse(f *FlagSet, t *testing.T) { + if f.Parsed() { + t.Error("f.Parse() = true before Parse") + } + boolFlag := f.Bool([]string{"bool"}, false, "bool value") + bool2Flag := f.Bool([]string{"bool2"}, false, "bool2 value") + intFlag := f.Int([]string{"-int"}, 0, "int value") + int64Flag := f.Int64([]string{"-int64"}, 0, "int64 value") + uintFlag := f.Uint([]string{"uint"}, 0, "uint value") + uint64Flag := f.Uint64([]string{"-uint64"}, 0, "uint64 value") + stringFlag := f.String([]string{"string"}, "0", "string value") + float64Flag := f.Float64([]string{"float64"}, 0, "float64 value") + durationFlag := f.Duration([]string{"duration"}, 5*time.Second, "time.Duration value") + extra := "one-extra-argument" + args := []string{ + "-bool", + "-bool2=true", + "--int", "22", + "--int64", "0x23", + "-uint", "24", + "--uint64", "25", + "-string", "hello", + "-float64", "2718e28", + "-duration", "2m", + extra, + } + if err := f.Parse(args); err != nil { + t.Fatal(err) + } + if !f.Parsed() { + t.Error("f.Parse() = false after Parse") + } + if *boolFlag != true { + t.Error("bool flag should be true, is ", *boolFlag) + } + if *bool2Flag != true { + t.Error("bool2 flag should be true, is ", *bool2Flag) + } + if *intFlag != 22 { + t.Error("int flag should be 22, is ", *intFlag) + } + if *int64Flag != 0x23 { + t.Error("int64 flag should be 0x23, is ", *int64Flag) + } + if *uintFlag != 24 { + t.Error("uint flag should be 24, is ", *uintFlag) + } + if *uint64Flag != 25 { + t.Error("uint64 flag should be 25, is ", *uint64Flag) + } + if *stringFlag != "hello" { + t.Error("string flag should be `hello`, is ", *stringFlag) + } + if *float64Flag != 2718e28 { + t.Error("float64 flag should be 2718e28, is ", *float64Flag) + } + if *durationFlag != 2*time.Minute { + t.Error("duration flag should be 2m, is ", *durationFlag) + } + if len(f.Args()) != 1 { + t.Error("expected one argument, got", len(f.Args())) + } else if f.Args()[0] != extra { + t.Errorf("expected argument %q got %q", extra, f.Args()[0]) + } +} + +func TestParse(t *testing.T) { + ResetForTesting(func() { t.Error("bad parse") }) + testParse(CommandLine, t) +} + +func TestFlagSetParse(t *testing.T) { + testParse(NewFlagSet("test", ContinueOnError), t) +} + +// Declare a user-defined flag type. +type flagVar []string + +func (f *flagVar) String() string { + return fmt.Sprint([]string(*f)) +} + +func (f *flagVar) Set(value string) error { + *f = append(*f, value) + return nil +} + +func TestUserDefined(t *testing.T) { + var flags FlagSet + flags.Init("test", ContinueOnError) + var v flagVar + flags.Var(&v, []string{"v"}, "usage") + if err := flags.Parse([]string{"-v", "1", "-v", "2", "-v=3"}); err != nil { + t.Error(err) + } + if len(v) != 3 { + t.Fatal("expected 3 args; got ", len(v)) + } + expect := "[1 2 3]" + if v.String() != expect { + t.Errorf("expected value %q got %q", expect, v.String()) + } +} + +// Declare a user-defined boolean flag type. +type boolFlagVar struct { + count int +} + +func (b *boolFlagVar) String() string { + return fmt.Sprintf("%d", b.count) +} + +func (b *boolFlagVar) Set(value string) error { + if value == "true" { + b.count++ + } + return nil +} + +func (b *boolFlagVar) IsBoolFlag() bool { + return b.count < 4 +} + +func TestUserDefinedBool(t *testing.T) { + var flags FlagSet + flags.Init("test", ContinueOnError) + var b boolFlagVar + var err error + flags.Var(&b, []string{"b"}, "usage") + if err = flags.Parse([]string{"-b", "-b", "-b", "-b=true", "-b=false", "-b", "barg", "-b"}); err != nil { + if b.count < 4 { + t.Error(err) + } + } + + if b.count != 4 { + t.Errorf("want: %d; got: %d", 4, b.count) + } + + if err == nil { + t.Error("expected error; got none") + } +} + +func TestSetOutput(t *testing.T) { + var flags FlagSet + var buf bytes.Buffer + flags.SetOutput(&buf) + flags.Init("test", ContinueOnError) + flags.Parse([]string{"-unknown"}) + if out := buf.String(); !strings.Contains(out, "-unknown") { + t.Logf("expected output mentioning unknown; got %q", out) + } +} + +// This tests that one can reset the flags. This still works but not well, and is +// superseded by FlagSet. +func TestChangingArgs(t *testing.T) { + ResetForTesting(func() { t.Fatal("bad parse") }) + oldArgs := os.Args + defer func() { os.Args = oldArgs }() + os.Args = []string{"cmd", "-before", "subcmd", "-after", "args"} + before := Bool([]string{"before"}, false, "") + if err := CommandLine.Parse(os.Args[1:]); err != nil { + t.Fatal(err) + } + cmd := Arg(0) + os.Args = Args() + after := Bool([]string{"after"}, false, "") + Parse() + args := Args() + + if !*before || cmd != "subcmd" || !*after || len(args) != 1 || args[0] != "args" { + t.Fatalf("expected true subcmd true [args] got %v %v %v %v", *before, cmd, *after, args) + } +} + +// Test that -help invokes the usage message and returns ErrHelp. +func TestHelp(t *testing.T) { + var helpCalled = false + fs := NewFlagSet("help test", ContinueOnError) + fs.Usage = func() { helpCalled = true } + var flag bool + fs.BoolVar(&flag, []string{"flag"}, false, "regular flag") + // Regular flag invocation should work + err := fs.Parse([]string{"-flag=true"}) + if err != nil { + t.Fatal("expected no error; got ", err) + } + if !flag { + t.Error("flag was not set by -flag") + } + if helpCalled { + t.Error("help called for regular flag") + helpCalled = false // reset for next test + } + // Help flag should work as expected. + err = fs.Parse([]string{"-help"}) + if err == nil { + t.Fatal("error expected") + } + if err != ErrHelp { + t.Fatal("expected ErrHelp; got ", err) + } + if !helpCalled { + t.Fatal("help was not called") + } + // If we define a help flag, that should override. + var help bool + fs.BoolVar(&help, []string{"help"}, false, "help flag") + helpCalled = false + err = fs.Parse([]string{"-help"}) + if err != nil { + t.Fatal("expected no error for defined -help; got ", err) + } + if helpCalled { + t.Fatal("help was called; should not have been for defined help flag") + } +} From 1ceb049118e16367c2f4015e61657f03d0eb8e29 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 8 Jan 2014 18:47:57 -0800 Subject: [PATCH 075/364] Initial driver changes proposal Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- execdriver/driver.go | 46 +++++++++++++ execdriver/lxc/driver.go | 144 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 execdriver/driver.go create mode 100644 execdriver/lxc/driver.go diff --git a/execdriver/driver.go b/execdriver/driver.go new file mode 100644 index 0000000000..225f23cf26 --- /dev/null +++ b/execdriver/driver.go @@ -0,0 +1,46 @@ +package execdriver + +import ( + "io" + "net" +) + +// Network settings of the container +type Network struct { + Gateway string + IPAddress net.IPAddr + IPPrefixLen int + Mtu int +} + +// Container / Process / Whatever, we can redefine the conatiner here +// to be what it should be and not have to carry the baggage of the +// container type in the core with backward compat. This is what a +// driver needs to execute a process inside of a conatiner. This is what +// a container is at it's core. +type Container struct { + Name string // unique name for the conatienr + Privileged bool + User string + Dir string // root fs of the container + InitPath string // dockerinit + Entrypoint string + Args []string + Environment map[string]string + WorkingDir string + Network *Network // if network is nil then networking is disabled + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer + + Context interface{} +} + +// State can be handled internally in the drivers +type Driver interface { + Start(c *Container) error + Stop(c *Container) error + Kill(c *Container, sig int) error + Running(c *Container) (bool, error) + Wait(c *Container, seconds int) error +} diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go new file mode 100644 index 0000000000..c146571970 --- /dev/null +++ b/execdriver/lxc/driver.go @@ -0,0 +1,144 @@ +package lxc + +import ( + "github.com/dotcloud/docker/execdriver" + "os/exec" + "strconv" + "sync" + "syscall" +) + +const ( + startPath = "lxc-start" +) + +type driver struct { + containerLock map[string]*sync.Mutex +} + +func NewDriver() (execdriver.Driver, error) { + // setup unconfined symlink +} + +func (d *driver) Start(c *execdriver.Container) error { + l := d.getLock(c) + l.Lock() + defer l.Unlock() + + running, err := d.running(c) + if err != nil { + return err + } + if running { + return nil + } + + configPath, err := d.generateConfig(c) + if err != nil { + return err + } + + params := []string{ + startPath, + "-n", c.Name, + "-f", configPath, + "--", + c.InitPath, + } + + if c.Network != nil { + params = append(params, + "-g", c.Network.Gateway, + "-i", fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network, IPPrefixLen), + "-mtu", c.Network.Mtu, + ) + } + + if c.User != "" { + params = append(params, "-u", c.User) + } + + if c.Privileged { + params = append(params, "-privileged") + } + + if c.WorkingDir != "" { + params = append(params, "-w", c.WorkingDir) + } + + params = append(params, "--", c.Entrypoint) + params = append(params, c.Args...) + + cmd := exec.Command(params[0], params[1:]...) + cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} + + cmd.Stdout = c.Stdout + cmd.Stderr = c.Stderr + cmd.Stdin = c.Stdin + + if err := cmd.Start(); err != nil { + return err + } + + // Poll for running + if err := d.waitForStart(cmd, c); err != nil { + return err + } + return nil +} + +func (d *driver) Stop(c *execdriver.Container) error { + l := d.getLock(c) + l.Lock() + defer l.Unlock() + + if err := d.kill(c, 15); err != nil { + if err := d.kill(c, 9); err != nil { + return err + } + } + + if err := d.wait(c, 10); err != nil { + return d.kill(c, 9) + } + return nil +} + +func (d *driver) Wait(c *execdriver.Container, seconds int) error { + l := d.getLock(c) + l.Lock() + defer l.Unlock() + + return d.wait(c, seconds) +} + +// If seconds < 0 then wait forever +func (d *driver) wait(c *execdriver.Container, seconds int) error { + +} + +func (d *driver) kill(c *execdriver.Container, sig int) error { + return exec.Command("lxc-kill", "-n", c.Name, strconv.Itoa(sig)).Run() +} + +func (d *driver) running(c *execdriver.Container) (bool, error) { + +} + +// Generate the lxc configuration and return the path to the file +func (d *driver) generateConfig(c *execdriver.Container) (string, error) { + +} + +func (d *driver) waitForStart(cmd *exec.Cmd, c *execdriver.Container) error { + +} + +func (d *driver) getLock(c *execdriver.Container) *sync.Mutex { + l, ok := d.containerLock[c.Name] + if !ok { + l = &sync.Mutex{} + d.containerLock[c.Name] = l + } + return l +} From 203b69c8c907b9cf57c887774b6f492d405a0621 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 9 Jan 2014 15:04:45 -0800 Subject: [PATCH 076/364] Implement lxc driver Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- execdriver/driver.go | 92 ++++++++++++++++-- execdriver/lxc/driver.go | 202 ++++++++++++++++++++++++++++----------- 2 files changed, 228 insertions(+), 66 deletions(-) diff --git a/execdriver/driver.go b/execdriver/driver.go index 225f23cf26..d5b2073669 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -1,10 +1,26 @@ package execdriver import ( + "errors" "io" "net" + "os/exec" + "sync" + "syscall" + "time" ) +var ( + ErrCommandIsNil = errors.New("Process's cmd is nil") +) + +type Driver interface { + Start(c *Process) error + Stop(c *Process) error + Kill(c *Process, sig int) error + Wait(c *Process, duration time.Duration) error +} + // Network settings of the container type Network struct { Gateway string @@ -13,12 +29,44 @@ type Network struct { Mtu int } +type State struct { + sync.RWMutex + running bool + pid int + exitCode int + startedAt time.Time + finishedAt time.Time +} + +func (s *State) IsRunning() bool { + s.RLock() + defer s.RUnlock() + return s.running +} + +func (s *State) SetRunning() error { + s.Lock() + defer s.Unlock() + s.running = true + return nil +} + +func (s *State) SetStopped(exitCode int) error { + s.Lock() + defer s.Unlock() + s.exitCode = exitCode + s.running = false + return nil +} + // Container / Process / Whatever, we can redefine the conatiner here // to be what it should be and not have to carry the baggage of the // container type in the core with backward compat. This is what a // driver needs to execute a process inside of a conatiner. This is what // a container is at it's core. -type Container struct { +type Process struct { + State State + Name string // unique name for the conatienr Privileged bool User string @@ -28,19 +76,45 @@ type Container struct { Args []string Environment map[string]string WorkingDir string + ConfigPath string Network *Network // if network is nil then networking is disabled Stdin io.Reader Stdout io.Writer Stderr io.Writer - Context interface{} + cmd *exec.Cmd } -// State can be handled internally in the drivers -type Driver interface { - Start(c *Container) error - Stop(c *Container) error - Kill(c *Container, sig int) error - Running(c *Container) (bool, error) - Wait(c *Container, seconds int) error +func (c *Process) SetCmd(cmd *exec.Cmd) error { + c.cmd = cmd + cmd.Stdout = c.Stdout + cmd.Stderr = c.Stderr + cmd.Stdin = c.Stdin + return nil +} + +func (c *Process) StdinPipe() (io.WriteCloser, error) { + return c.cmd.StdinPipe() +} + +func (c *Process) StderrPipe() (io.ReadCloser, error) { + return c.cmd.StderrPipe() +} + +func (c *Process) StdoutPipe() (io.ReadCloser, error) { + return c.cmd.StdoutPipe() +} + +func (c *Process) GetExitCode() int { + if c.cmd != nil { + return c.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() + } + return -1 +} + +func (c *Process) Wait() error { + if c.cmd != nil { + return c.cmd.Wait() + } + return ErrCommandIsNil } diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index c146571970..61223ce8b7 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -1,47 +1,58 @@ package lxc import ( + "errors" + "fmt" "github.com/dotcloud/docker/execdriver" "os/exec" "strconv" - "sync" + "strings" "syscall" + "time" ) const ( startPath = "lxc-start" ) +var ( + ErrNotRunning = errors.New("Process could not be started") + ErrWaitTimeoutReached = errors.New("Wait timeout reached") +) + +func init() { + // Register driver +} + type driver struct { - containerLock map[string]*sync.Mutex + root string // root path for the driver to use + containers map[string]*execdriver.Process } -func NewDriver() (execdriver.Driver, error) { +func NewDriver(root string) (execdriver.Driver, error) { // setup unconfined symlink + return &driver{ + root: root, + containers: make(map[string]*execdriver.Process), + }, nil } -func (d *driver) Start(c *execdriver.Container) error { - l := d.getLock(c) - l.Lock() - defer l.Unlock() - - running, err := d.running(c) - if err != nil { - return err - } - if running { +func (d *driver) Start(c *execdriver.Process) error { + if c.State.IsRunning() { return nil } - configPath, err := d.generateConfig(c) - if err != nil { - return err - } + /* + configPath, err := d.generateConfig(c) + if err != nil { + return err + } + */ params := []string{ startPath, "-n", c.Name, - "-f", configPath, + "-f", c.ConfigPath, "--", c.InitPath, } @@ -49,8 +60,8 @@ func (d *driver) Start(c *execdriver.Container) error { if c.Network != nil { params = append(params, "-g", c.Network.Gateway, - "-i", fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network, IPPrefixLen), - "-mtu", c.Network.Mtu, + "-i", fmt.Sprintf("%s/%d", c.Network.IPAddress, c.Network.IPPrefixLen), + "-mtu", strconv.Itoa(c.Network.Mtu), ) } @@ -72,9 +83,9 @@ func (d *driver) Start(c *execdriver.Container) error { cmd := exec.Command(params[0], params[1:]...) cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} - cmd.Stdout = c.Stdout - cmd.Stderr = c.Stderr - cmd.Stdin = c.Stdin + if err := c.SetCmd(cmd); err != nil { + return err + } if err := cmd.Start(); err != nil { return err @@ -87,58 +98,135 @@ func (d *driver) Start(c *execdriver.Container) error { return nil } -func (d *driver) Stop(c *execdriver.Container) error { - l := d.getLock(c) - l.Lock() - defer l.Unlock() - +func (d *driver) Stop(c *execdriver.Process) error { if err := d.kill(c, 15); err != nil { if err := d.kill(c, 9); err != nil { return err } } - if err := d.wait(c, 10); err != nil { - return d.kill(c, 9) + if err := d.wait(c, 10*time.Second); err != nil { + if err := d.kill(c, 9); err != nil { + return err + } + } + exitCode := c.GetExitCode() + if err := c.State.SetStopped(exitCode); err != nil { + return err } return nil } -func (d *driver) Wait(c *execdriver.Container, seconds int) error { - l := d.getLock(c) - l.Lock() - defer l.Unlock() +func (d *driver) Kill(c *execdriver.Process, sig int) error { + c.State.Lock() + defer c.State.Unlock() + return d.kill(c, sig) +} - return d.wait(c, seconds) +func (d *driver) Wait(c *execdriver.Process, duration time.Duration) error { + return d.wait(c, duration) } // If seconds < 0 then wait forever -func (d *driver) wait(c *execdriver.Container, seconds int) error { - +func (d *driver) wait(c *execdriver.Process, duration time.Duration) error { +begin: + var ( + killer bool + done = d.waitCmd(c) + ) + if duration > 0 { + select { + case err := <-done: + if err != nil && err == execdriver.ErrCommandIsNil { + done = d.waitLxc(c, &killer) + goto begin + } + return err + case <-time.After(duration): + killer = true + return ErrWaitTimeoutReached + } + } else { + if err := <-done; err != nil { + if err == execdriver.ErrCommandIsNil { + done = d.waitLxc(c, &killer) + goto begin + } + return err + } + } + return nil } -func (d *driver) kill(c *execdriver.Container, sig int) error { +func (d *driver) kill(c *execdriver.Process, sig int) error { return exec.Command("lxc-kill", "-n", c.Name, strconv.Itoa(sig)).Run() } -func (d *driver) running(c *execdriver.Container) (bool, error) { - -} - -// Generate the lxc configuration and return the path to the file -func (d *driver) generateConfig(c *execdriver.Container) (string, error) { - -} - -func (d *driver) waitForStart(cmd *exec.Cmd, c *execdriver.Container) error { - -} - -func (d *driver) getLock(c *execdriver.Container) *sync.Mutex { - l, ok := d.containerLock[c.Name] - if !ok { - l = &sync.Mutex{} - d.containerLock[c.Name] = l +/* Generate the lxc configuration and return the path to the file +func (d *driver) generateConfig(c *execdriver.Process) (string, error) { + p := path.Join(d.root, c.Name) + f, err := os.Create(p) + if err != nil { + return "", nil } - return l + defer f.Close() + + if err := LxcTemplateCompiled.Execute(f, c.Context); err != nil { + return "", err + } + return p, nil +} +*/ + +func (d *driver) waitForStart(cmd *exec.Cmd, c *execdriver.Process) error { + // We wait for the container to be fully running. + // Timeout after 5 seconds. In case of broken pipe, just retry. + // Note: The container can run and finish correctly before + // the end of this loop + for now := time.Now(); time.Since(now) < 5*time.Second; { + // If the container dies while waiting for it, just return + if !c.State.IsRunning() { + return nil + } + output, err := exec.Command("lxc-info", "-s", "-n", c.Name).CombinedOutput() + if err != nil { + output, err = exec.Command("lxc-info", "-s", "-n", c.Name).CombinedOutput() + if err != nil { + return err + } + + } + if strings.Contains(string(output), "RUNNING") { + return nil + } + time.Sleep(50 * time.Millisecond) + } + return ErrNotRunning +} + +func (d *driver) waitCmd(c *execdriver.Process) <-chan error { + done := make(chan error) + go func() { + done <- c.Wait() + }() + return done +} + +func (d *driver) waitLxc(c *execdriver.Process, kill *bool) <-chan error { + done := make(chan error) + go func() { + for *kill { + output, err := exec.Command("lxc-info", "-n", c.Name).CombinedOutput() + if err != nil { + done <- err + return + } + if !strings.Contains(string(output), "RUNNING") { + done <- err + return + } + time.Sleep(500 * time.Millisecond) + } + }() + return done } From f2680e5a5b1fc2393dfe319fe4dd197672985411 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 9 Jan 2014 16:03:22 -0800 Subject: [PATCH 077/364] Integrate process changes in container.go Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 156 ++++++++++++--------------------------- execdriver/driver.go | 32 ++++---- execdriver/lxc/driver.go | 16 +--- runtime.go | 9 +++ 4 files changed, 78 insertions(+), 135 deletions(-) diff --git a/container.go b/container.go index 9e4495890a..0a0715890a 100644 --- a/container.go +++ b/container.go @@ -1,11 +1,11 @@ package docker import ( - "bytes" "encoding/json" "errors" "fmt" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/mount" "github.com/dotcloud/docker/pkg/term" @@ -16,7 +16,6 @@ import ( "log" "net" "os" - "os/exec" "path" "strconv" "strings" @@ -55,7 +54,7 @@ type Container struct { Name string Driver string - cmd *exec.Cmd + process *execdriver.Process stdout *utils.WriteBroadcaster stderr *utils.WriteBroadcaster stdin io.ReadCloser @@ -235,10 +234,6 @@ func (container *Container) Inject(file io.Reader, pth string) error { return nil } -func (container *Container) Cmd() *exec.Cmd { - return container.cmd -} - func (container *Container) When() time.Time { return container.Created } @@ -320,8 +315,8 @@ func (container *Container) startPty() error { return err } container.ptyMaster = ptyMaster - container.cmd.Stdout = ptySlave - container.cmd.Stderr = ptySlave + container.process.Stdout = ptySlave + container.process.Stderr = ptySlave // Copy the PTYs to our broadcasters go func() { @@ -333,8 +328,7 @@ func (container *Container) startPty() error { // stdin if container.Config.OpenStdin { - container.cmd.Stdin = ptySlave - container.cmd.SysProcAttr.Setctty = true + container.process.Stdin = ptySlave go func() { defer container.stdin.Close() utils.Debugf("startPty: begin of stdin pipe") @@ -342,7 +336,7 @@ func (container *Container) startPty() error { utils.Debugf("startPty: end of stdin pipe") }() } - if err := container.cmd.Start(); err != nil { + if err := container.runtime.execDriver.Start(container.process); err != nil { return err } ptySlave.Close() @@ -350,10 +344,10 @@ func (container *Container) startPty() error { } func (container *Container) start() error { - container.cmd.Stdout = container.stdout - container.cmd.Stderr = container.stderr + container.process.Stdout = container.stdout + container.process.Stderr = container.stderr if container.Config.OpenStdin { - stdin, err := container.cmd.StdinPipe() + stdin, err := container.process.StdinPipe() if err != nil { return err } @@ -364,7 +358,7 @@ func (container *Container) start() error { utils.Debugf("start: end of stdin pipe") }() } - return container.cmd.Start() + return container.runtime.execDriver.Start(container.process) } func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error { @@ -653,8 +647,9 @@ func (container *Container) Start() (err error) { return err } + var workingDir string if container.Config.WorkingDir != "" { - workingDir := path.Clean(container.Config.WorkingDir) + workingDir = path.Clean(container.Config.WorkingDir) utils.Debugf("[working dir] working dir is %s", workingDir) if err := os.MkdirAll(path.Join(container.RootfsPath(), workingDir), 0755); err != nil { @@ -713,7 +708,6 @@ func (container *Container) Start() (err error) { } // Mount user specified volumes - for r, v := range container.Volumes { mountAs := "ro" if container.VolumesRW[r] { @@ -725,7 +719,30 @@ func (container *Container) Start() (err error) { } } - container.cmd = exec.Command(params[0], params[1:]...) + var en *execdriver.Network + if !container.runtime.networkManager.disabled { + network := container.NetworkSettings + en = &execdriver.Network{ + Gateway: network.Gateway, + IPAddress: network.IPAddress, + IPPrefixLen: network.IPPrefixLen, + Mtu: container.runtime.config.Mtu, + } + } + + container.process = &execdriver.Process{ + Name: container.ID, + Privileged: container.hostConfig.Privileged, + Dir: root, + InitPath: "/.dockerinit", + Entrypoint: container.Path, + Args: container.Args, + WorkingDir: workingDir, + ConfigPath: container.lxcConfigPath(), + Network: en, + Tty: container.Config.Tty, + User: container.Config.User, + } // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { @@ -735,8 +752,6 @@ func (container *Container) Start() (err error) { return err } - container.cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} - if container.Config.Tty { err = container.startPty() } else { @@ -745,48 +760,13 @@ func (container *Container) Start() (err error) { if err != nil { return err } - // FIXME: save state on disk *first*, then converge - // this way disk state is used as a journal, eg. we can restore after crash etc. - container.State.SetRunning(container.cmd.Process.Pid) // Init the lock container.waitLock = make(chan struct{}) container.ToDisk() go container.monitor() - - defer utils.Debugf("Container running: %v", container.State.IsRunning()) - // We wait for the container to be fully running. - // Timeout after 5 seconds. In case of broken pipe, just retry. - // Note: The container can run and finish correctly before - // the end of this loop - for now := time.Now(); time.Since(now) < 5*time.Second; { - // If the container dies while waiting for it, just return - if !container.State.IsRunning() { - return nil - } - output, err := exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput() - if err != nil { - utils.Debugf("Error with lxc-info: %s (%s)", err, output) - - output, err = exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput() - if err != nil { - utils.Debugf("Second Error with lxc-info: %s (%s)", err, output) - return err - } - - } - if strings.Contains(string(output), "RUNNING") { - return nil - } - utils.Debugf("Waiting for the container to start (running: %v): %s", container.State.IsRunning(), bytes.TrimSpace(output)) - time.Sleep(50 * time.Millisecond) - } - - if container.State.IsRunning() { - return ErrContainerStartTimeout - } - return ErrContainerStart + return nil } func (container *Container) getBindMap() (map[string]BindMap, error) { @@ -1159,47 +1139,18 @@ func (container *Container) releaseNetwork() { container.NetworkSettings = &NetworkSettings{} } -// FIXME: replace this with a control socket within dockerinit -func (container *Container) waitLxc() error { - for { - output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput() - if err != nil { - return err - } - if !strings.Contains(string(output), "RUNNING") { - return nil - } - time.Sleep(500 * time.Millisecond) - } -} - func (container *Container) monitor() { // Wait for the program to exit - - // If the command does not exist, try to wait via lxc - // (This probably happens only for ghost containers, i.e. containers that were running when Docker started) - if container.cmd == nil { - utils.Debugf("monitor: waiting for container %s using waitLxc", container.ID) - if err := container.waitLxc(); err != nil { - utils.Errorf("monitor: while waiting for container %s, waitLxc had a problem: %s", container.ID, err) - } - } else { - utils.Debugf("monitor: waiting for container %s using cmd.Wait", container.ID) - if err := container.cmd.Wait(); err != nil { - // Since non-zero exit status and signal terminations will cause err to be non-nil, - // we have to actually discard it. Still, log it anyway, just in case. - utils.Debugf("monitor: cmd.Wait reported exit status %s for container %s", err, container.ID) - } + if container.process == nil { + panic("Container process is nil") } - utils.Debugf("monitor: container %s finished", container.ID) - - exitCode := -1 - if container.cmd != nil { - exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() - } - - if container.runtime != nil && container.runtime.srv != nil { - container.runtime.srv.LogEvent("die", container.ID, container.runtime.repositories.ImageName(container.Image)) + if err := container.runtime.execDriver.Wait(container.process, time.Duration(0)); err != nil { + // Since non-zero exit status and signal terminations will cause err to be non-nil, + // we have to actually discard it. Still, log it anyway, just in case. + utils.Debugf("monitor: cmd.Wait reported exit status %s for container %s", err, container.ID) + if container.runtime != nil && container.runtime.srv != nil { + container.runtime.srv.LogEvent("die", container.ID, container.runtime.repositories.ImageName(container.Image)) + } } // Cleanup @@ -1210,9 +1161,6 @@ func (container *Container) monitor() { container.stdin, container.stdinPipe = io.Pipe() } - // Report status back - container.State.SetStopped(exitCode) - // Release the lock close(container.waitLock) @@ -1267,13 +1215,7 @@ func (container *Container) kill(sig int) error { if !container.State.IsRunning() { return nil } - - if output, err := exec.Command("lxc-kill", "-n", container.ID, strconv.Itoa(sig)).CombinedOutput(); err != nil { - log.Printf("error killing container %s (%s, %s)", utils.TruncateID(container.ID), output, err) - return err - } - - return nil + return container.runtime.execDriver.Kill(container.process, sig) } func (container *Container) Kill() error { @@ -1288,11 +1230,11 @@ func (container *Container) Kill() error { // 2. Wait for the process to die, in last resort, try to kill the process directly if err := container.WaitTimeout(10 * time.Second); err != nil { - if container.cmd == nil { + if container.process == nil { return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", utils.TruncateID(container.ID)) } log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", utils.TruncateID(container.ID)) - if err := container.cmd.Process.Kill(); err != nil { + if err := container.runtime.execDriver.Kill(container.process, 9); err != nil { return err } } diff --git a/execdriver/driver.go b/execdriver/driver.go index d5b2073669..543c7890d1 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -3,7 +3,6 @@ package execdriver import ( "errors" "io" - "net" "os/exec" "sync" "syscall" @@ -24,7 +23,7 @@ type Driver interface { // Network settings of the container type Network struct { Gateway string - IPAddress net.IPAddr + IPAddress string IPPrefixLen int Mtu int } @@ -67,20 +66,21 @@ func (s *State) SetStopped(exitCode int) error { type Process struct { State State - Name string // unique name for the conatienr - Privileged bool - User string - Dir string // root fs of the container - InitPath string // dockerinit - Entrypoint string - Args []string - Environment map[string]string - WorkingDir string - ConfigPath string - Network *Network // if network is nil then networking is disabled - Stdin io.Reader - Stdout io.Writer - Stderr io.Writer + Name string // unique name for the conatienr + Privileged bool + User string + Dir string // root fs of the container + InitPath string // dockerinit + Entrypoint string + Args []string + // Environment map[string]string // we don't use this right now because we use an env file + WorkingDir string + ConfigPath string + Tty bool + Network *Network // if network is nil then networking is disabled + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer cmd *exec.Cmd } diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 61223ce8b7..b36e20986f 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -42,13 +42,6 @@ func (d *driver) Start(c *execdriver.Process) error { return nil } - /* - configPath, err := d.generateConfig(c) - if err != nil { - return err - } - */ - params := []string{ startPath, "-n", c.Name, @@ -82,6 +75,7 @@ func (d *driver) Start(c *execdriver.Process) error { cmd := exec.Command(params[0], params[1:]...) cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} + cmd.SysProcAttr.Setctty = true if err := c.SetCmd(cmd); err != nil { return err @@ -111,10 +105,7 @@ func (d *driver) Stop(c *execdriver.Process) error { } } exitCode := c.GetExitCode() - if err := c.State.SetStopped(exitCode); err != nil { - return err - } - return nil + return c.State.SetStopped(exitCode) } func (d *driver) Kill(c *execdriver.Process, sig int) error { @@ -155,7 +146,8 @@ begin: return err } } - return nil + exitCode := c.GetExitCode() + return c.State.SetStopped(exitCode) } func (d *driver) kill(c *execdriver.Process, sig int) error { diff --git a/runtime.go b/runtime.go index 731e3a8784..2686f51df1 100644 --- a/runtime.go +++ b/runtime.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/cgroups" + "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/execdriver/lxc" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/graphdriver/aufs" _ "github.com/dotcloud/docker/graphdriver/devmapper" @@ -56,6 +58,7 @@ type Runtime struct { config *DaemonConfig containerGraph *graphdb.Database driver graphdriver.Driver + execDriver execdriver.Driver } // List returns an array of all containers registered in the runtime. @@ -735,6 +738,11 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { sysInitPath = localCopy } + ed, err := lxc.NewDriver("") + if err != nil { + return nil, err + } + runtime := &Runtime{ repository: runtimeRepo, containers: list.New(), @@ -748,6 +756,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { containerGraph: graph, driver: driver, sysInitPath: sysInitPath, + execDriver: ed, } if err := runtime.restore(); err != nil { From 25a26977173632b5966838d910550a921ef5a853 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 9 Jan 2014 18:48:34 -0800 Subject: [PATCH 078/364] Keep state in core on container Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 5 +++++ execdriver/driver.go | 42 ++++------------------------------------ execdriver/lxc/driver.go | 20 +++++++------------ 3 files changed, 16 insertions(+), 51 deletions(-) diff --git a/container.go b/container.go index 0a0715890a..92da2c4e99 100644 --- a/container.go +++ b/container.go @@ -761,6 +761,8 @@ func (container *Container) Start() (err error) { return err } + container.State.SetRunning(container.process.Pid()) + // Init the lock container.waitLock = make(chan struct{}) @@ -1161,6 +1163,9 @@ func (container *Container) monitor() { container.stdin, container.stdinPipe = io.Pipe() } + exitCode := container.process.GetExitCode() + container.State.SetStopped(exitCode) + // Release the lock close(container.waitLock) diff --git a/execdriver/driver.go b/execdriver/driver.go index 543c7890d1..61d2b9e90c 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -4,7 +4,6 @@ import ( "errors" "io" "os/exec" - "sync" "syscall" "time" ) @@ -28,44 +27,7 @@ type Network struct { Mtu int } -type State struct { - sync.RWMutex - running bool - pid int - exitCode int - startedAt time.Time - finishedAt time.Time -} - -func (s *State) IsRunning() bool { - s.RLock() - defer s.RUnlock() - return s.running -} - -func (s *State) SetRunning() error { - s.Lock() - defer s.Unlock() - s.running = true - return nil -} - -func (s *State) SetStopped(exitCode int) error { - s.Lock() - defer s.Unlock() - s.exitCode = exitCode - s.running = false - return nil -} - -// Container / Process / Whatever, we can redefine the conatiner here -// to be what it should be and not have to carry the baggage of the -// container type in the core with backward compat. This is what a -// driver needs to execute a process inside of a conatiner. This is what -// a container is at it's core. type Process struct { - State State - Name string // unique name for the conatienr Privileged bool User string @@ -93,6 +55,10 @@ func (c *Process) SetCmd(cmd *exec.Cmd) error { return nil } +func (c *Process) Pid() int { + return c.cmd.Process.Pid +} + func (c *Process) StdinPipe() (io.WriteCloser, error) { return c.cmd.StdinPipe() } diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index b36e20986f..bed11c6809 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -38,10 +38,6 @@ func NewDriver(root string) (execdriver.Driver, error) { } func (d *driver) Start(c *execdriver.Process) error { - if c.State.IsRunning() { - return nil - } - params := []string{ startPath, "-n", c.Name, @@ -104,13 +100,10 @@ func (d *driver) Stop(c *execdriver.Process) error { return err } } - exitCode := c.GetExitCode() - return c.State.SetStopped(exitCode) + return nil } func (d *driver) Kill(c *execdriver.Process, sig int) error { - c.State.Lock() - defer c.State.Unlock() return d.kill(c, sig) } @@ -146,8 +139,7 @@ begin: return err } } - exitCode := c.GetExitCode() - return c.State.SetStopped(exitCode) + return nil } func (d *driver) kill(c *execdriver.Process, sig int) error { @@ -177,9 +169,11 @@ func (d *driver) waitForStart(cmd *exec.Cmd, c *execdriver.Process) error { // the end of this loop for now := time.Now(); time.Since(now) < 5*time.Second; { // If the container dies while waiting for it, just return - if !c.State.IsRunning() { - return nil - } + /* + if !c.State.IsRunning() { + return nil + } + */ output, err := exec.Command("lxc-info", "-s", "-n", c.Name).CombinedOutput() if err != nil { output, err = exec.Command("lxc-info", "-s", "-n", c.Name).CombinedOutput() From 5573c744e4be16f39e1491188c4d4ff43f61d549 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 10 Jan 2014 11:44:35 -0800 Subject: [PATCH 079/364] Embed exec.Cmd on Process Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 4 ++-- execdriver/driver.go | 46 ++++++---------------------------------- execdriver/lxc/driver.go | 44 +++++++++++++++----------------------- 3 files changed, 25 insertions(+), 69 deletions(-) diff --git a/container.go b/container.go index 92da2c4e99..ab77ea0479 100644 --- a/container.go +++ b/container.go @@ -733,10 +733,10 @@ func (container *Container) Start() (err error) { container.process = &execdriver.Process{ Name: container.ID, Privileged: container.hostConfig.Privileged, - Dir: root, + Rootfs: root, InitPath: "/.dockerinit", Entrypoint: container.Path, - Args: container.Args, + Arguments: container.Args, WorkingDir: workingDir, ConfigPath: container.lxcConfigPath(), Network: en, diff --git a/execdriver/driver.go b/execdriver/driver.go index 61d2b9e90c..503f7f2ae5 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -2,7 +2,6 @@ package execdriver import ( "errors" - "io" "os/exec" "syscall" "time" @@ -28,59 +27,26 @@ type Network struct { } type Process struct { + exec.Cmd + Name string // unique name for the conatienr Privileged bool User string - Dir string // root fs of the container + Rootfs string // root fs of the container InitPath string // dockerinit Entrypoint string - Args []string - // Environment map[string]string // we don't use this right now because we use an env file + Arguments []string WorkingDir string ConfigPath string Tty bool Network *Network // if network is nil then networking is disabled - Stdin io.Reader - Stdout io.Writer - Stderr io.Writer - - cmd *exec.Cmd -} - -func (c *Process) SetCmd(cmd *exec.Cmd) error { - c.cmd = cmd - cmd.Stdout = c.Stdout - cmd.Stderr = c.Stderr - cmd.Stdin = c.Stdin - return nil } func (c *Process) Pid() int { - return c.cmd.Process.Pid -} - -func (c *Process) StdinPipe() (io.WriteCloser, error) { - return c.cmd.StdinPipe() -} - -func (c *Process) StderrPipe() (io.ReadCloser, error) { - return c.cmd.StderrPipe() -} - -func (c *Process) StdoutPipe() (io.ReadCloser, error) { - return c.cmd.StdoutPipe() + return c.Process.Pid } func (c *Process) GetExitCode() int { - if c.cmd != nil { - return c.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() - } + return c.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() return -1 } - -func (c *Process) Wait() error { - if c.cmd != nil { - return c.cmd.Wait() - } - return ErrCommandIsNil -} diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index bed11c6809..1af76b4f40 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -67,22 +67,28 @@ func (d *driver) Start(c *execdriver.Process) error { } params = append(params, "--", c.Entrypoint) - params = append(params, c.Args...) + params = append(params, c.Arguments...) - cmd := exec.Command(params[0], params[1:]...) - cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} - cmd.SysProcAttr.Setctty = true - - if err := c.SetCmd(cmd); err != nil { - return err + var ( + name = params[0] + arg = params[1:] + ) + aname, err := exec.LookPath(name) + if err != nil { + aname = name } + c.Path = aname + c.Args = append([]string{name}, arg...) - if err := cmd.Start(); err != nil { + c.SysProcAttr = &syscall.SysProcAttr{Setsid: true} + c.SysProcAttr.Setctty = true + + if err := c.Start(); err != nil { return err } // Poll for running - if err := d.waitForStart(cmd, c); err != nil { + if err := d.waitForStart(c); err != nil { return err } return nil @@ -113,11 +119,11 @@ func (d *driver) Wait(c *execdriver.Process, duration time.Duration) error { // If seconds < 0 then wait forever func (d *driver) wait(c *execdriver.Process, duration time.Duration) error { -begin: var ( killer bool done = d.waitCmd(c) ) +begin: if duration > 0 { select { case err := <-done: @@ -146,23 +152,7 @@ func (d *driver) kill(c *execdriver.Process, sig int) error { return exec.Command("lxc-kill", "-n", c.Name, strconv.Itoa(sig)).Run() } -/* Generate the lxc configuration and return the path to the file -func (d *driver) generateConfig(c *execdriver.Process) (string, error) { - p := path.Join(d.root, c.Name) - f, err := os.Create(p) - if err != nil { - return "", nil - } - defer f.Close() - - if err := LxcTemplateCompiled.Execute(f, c.Context); err != nil { - return "", err - } - return p, nil -} -*/ - -func (d *driver) waitForStart(cmd *exec.Cmd, c *execdriver.Process) error { +func (d *driver) waitForStart(c *execdriver.Process) error { // We wait for the container to be fully running. // Timeout after 5 seconds. In case of broken pipe, just retry. // Note: The container can run and finish correctly before From 1c1cf54b0aa44553b714b82a1960b4f1199ac33a Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 10 Jan 2014 11:58:34 -0800 Subject: [PATCH 080/364] Fix tty set issue for ioctl error Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 1 + execdriver/lxc/driver.go | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/container.go b/container.go index ab77ea0479..c1f5577248 100644 --- a/container.go +++ b/container.go @@ -329,6 +329,7 @@ func (container *Container) startPty() error { // stdin if container.Config.OpenStdin { container.process.Stdin = ptySlave + container.process.SysProcAttr.Setctty = true go func() { defer container.stdin.Close() utils.Debugf("startPty: begin of stdin pipe") diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 1af76b4f40..e649263f85 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -81,7 +81,6 @@ func (d *driver) Start(c *execdriver.Process) error { c.Args = append([]string{name}, arg...) c.SysProcAttr = &syscall.SysProcAttr{Setsid: true} - c.SysProcAttr.Setctty = true if err := c.Start(); err != nil { return err From 5a3d9bd432fdaa95f870d4d99fad1d6fafeaad92 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 10 Jan 2014 14:26:29 -0800 Subject: [PATCH 081/364] WIP complete integration test run Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 28 +++++++++--------- execdriver/driver.go | 4 +-- execdriver/lxc/driver.go | 63 ++++++++++++++++++---------------------- runtime.go | 12 ++++++++ 4 files changed, 57 insertions(+), 50 deletions(-) diff --git a/container.go b/container.go index c1f5577248..90347a3212 100644 --- a/container.go +++ b/container.go @@ -337,7 +337,7 @@ func (container *Container) startPty() error { utils.Debugf("startPty: end of stdin pipe") }() } - if err := container.runtime.execDriver.Start(container.process); err != nil { + if err := container.runtime.Start(container); err != nil { return err } ptySlave.Close() @@ -359,7 +359,7 @@ func (container *Container) start() error { utils.Debugf("start: end of stdin pipe") }() } - return container.runtime.execDriver.Start(container.process) + return container.runtime.Start(container) } func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error { @@ -732,7 +732,7 @@ func (container *Container) Start() (err error) { } container.process = &execdriver.Process{ - Name: container.ID, + ID: container.ID, Privileged: container.hostConfig.Privileged, Rootfs: root, InitPath: "/.dockerinit", @@ -744,6 +744,7 @@ func (container *Container) Start() (err error) { Tty: container.Config.Tty, User: container.Config.User, } + container.process.SysProcAttr = &syscall.SysProcAttr{Setsid: true} // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { @@ -753,6 +754,10 @@ func (container *Container) Start() (err error) { return err } + // Init the lock + container.waitLock = make(chan struct{}) + go container.monitor() + if container.Config.Tty { err = container.startPty() } else { @@ -763,12 +768,7 @@ func (container *Container) Start() (err error) { } container.State.SetRunning(container.process.Pid()) - - // Init the lock - container.waitLock = make(chan struct{}) - container.ToDisk() - go container.monitor() return nil } @@ -1143,11 +1143,10 @@ func (container *Container) releaseNetwork() { } func (container *Container) monitor() { + time.Sleep(1 * time.Second) // Wait for the program to exit - if container.process == nil { - panic("Container process is nil") - } - if err := container.runtime.execDriver.Wait(container.process, time.Duration(0)); err != nil { + fmt.Printf("--->Before WAIT %s\n", container.ID) + if err := container.runtime.Wait(container, time.Duration(0)); err != nil { // Since non-zero exit status and signal terminations will cause err to be non-nil, // we have to actually discard it. Still, log it anyway, just in case. utils.Debugf("monitor: cmd.Wait reported exit status %s for container %s", err, container.ID) @@ -1156,6 +1155,7 @@ func (container *Container) monitor() { } } + fmt.Printf("--->After WAIT %s\n", container.ID) // Cleanup container.cleanup() @@ -1221,7 +1221,7 @@ func (container *Container) kill(sig int) error { if !container.State.IsRunning() { return nil } - return container.runtime.execDriver.Kill(container.process, sig) + return container.runtime.Kill(container, sig) } func (container *Container) Kill() error { @@ -1240,7 +1240,7 @@ func (container *Container) Kill() error { return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", utils.TruncateID(container.ID)) } log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", utils.TruncateID(container.ID)) - if err := container.runtime.execDriver.Kill(container.process, 9); err != nil { + if err := container.runtime.Kill(container, 9); err != nil { return err } } diff --git a/execdriver/driver.go b/execdriver/driver.go index 503f7f2ae5..eb468f3ef6 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -13,7 +13,6 @@ var ( type Driver interface { Start(c *Process) error - Stop(c *Process) error Kill(c *Process, sig int) error Wait(c *Process, duration time.Duration) error } @@ -26,10 +25,11 @@ type Network struct { Mtu int } +// Process wrapps an os/exec.Cmd to add more metadata type Process struct { exec.Cmd - Name string // unique name for the conatienr + ID string Privileged bool User string Rootfs string // root fs of the container diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index e649263f85..f6306b4e16 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -4,10 +4,10 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/execdriver" + "os" "os/exec" "strconv" "strings" - "syscall" "time" ) @@ -25,22 +25,20 @@ func init() { } type driver struct { - root string // root path for the driver to use - containers map[string]*execdriver.Process + root string // root path for the driver to use } func NewDriver(root string) (execdriver.Driver, error) { // setup unconfined symlink return &driver{ - root: root, - containers: make(map[string]*execdriver.Process), + root: root, }, nil } func (d *driver) Start(c *execdriver.Process) error { params := []string{ startPath, - "-n", c.Name, + "-n", c.ID, "-f", c.ConfigPath, "--", c.InitPath, @@ -80,8 +78,7 @@ func (d *driver) Start(c *execdriver.Process) error { c.Path = aname c.Args = append([]string{name}, arg...) - c.SysProcAttr = &syscall.SysProcAttr{Setsid: true} - + fmt.Printf("-->%s\n-->%v\n", name, arg) if err := c.Start(); err != nil { return err } @@ -93,21 +90,6 @@ func (d *driver) Start(c *execdriver.Process) error { return nil } -func (d *driver) Stop(c *execdriver.Process) error { - if err := d.kill(c, 15); err != nil { - if err := d.kill(c, 9); err != nil { - return err - } - } - - if err := d.wait(c, 10*time.Second); err != nil { - if err := d.kill(c, 9); err != nil { - return err - } - } - return nil -} - func (d *driver) Kill(c *execdriver.Process, sig int) error { return d.kill(c, sig) } @@ -148,40 +130,49 @@ begin: } func (d *driver) kill(c *execdriver.Process, sig int) error { - return exec.Command("lxc-kill", "-n", c.Name, strconv.Itoa(sig)).Run() + return exec.Command("lxc-kill", "-n", c.ID, strconv.Itoa(sig)).Run() } func (d *driver) waitForStart(c *execdriver.Process) error { + var ( + err error + output []byte + ) // We wait for the container to be fully running. // Timeout after 5 seconds. In case of broken pipe, just retry. // Note: The container can run and finish correctly before // the end of this loop for now := time.Now(); time.Since(now) < 5*time.Second; { - // If the container dies while waiting for it, just return - /* - if !c.State.IsRunning() { - return nil - } - */ - output, err := exec.Command("lxc-info", "-s", "-n", c.Name).CombinedOutput() + // If the process dies while waiting for it, just return + if c.ProcessState != nil && c.ProcessState.Exited() { + return nil + } + + output, err = d.getInfo(c) if err != nil { - output, err = exec.Command("lxc-info", "-s", "-n", c.Name).CombinedOutput() + output, err = d.getInfo(c) if err != nil { return err } - } if strings.Contains(string(output), "RUNNING") { return nil } time.Sleep(50 * time.Millisecond) } + fmt.Printf("-->%s\n", string(output)) + + os.Exit(1) return ErrNotRunning } func (d *driver) waitCmd(c *execdriver.Process) <-chan error { done := make(chan error) go func() { + if c == nil { + done <- execdriver.ErrCommandIsNil + return + } done <- c.Wait() }() return done @@ -191,7 +182,7 @@ func (d *driver) waitLxc(c *execdriver.Process, kill *bool) <-chan error { done := make(chan error) go func() { for *kill { - output, err := exec.Command("lxc-info", "-n", c.Name).CombinedOutput() + output, err := exec.Command("lxc-info", "-n", c.ID).CombinedOutput() if err != nil { done <- err return @@ -205,3 +196,7 @@ func (d *driver) waitLxc(c *execdriver.Process, kill *bool) <-chan error { }() return done } + +func (d *driver) getInfo(c *execdriver.Process) ([]byte, error) { + return exec.Command("lxc-info", "-s", "-n", c.ID).CombinedOutput() +} diff --git a/runtime.go b/runtime.go index 2686f51df1..d302c9c217 100644 --- a/runtime.go +++ b/runtime.go @@ -838,6 +838,18 @@ func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) { return archive.ExportChanges(cDir, changes) } +func (runtime *Runtime) Start(c *Container) error { + return runtime.execDriver.Start(c.process) +} + +func (runtime *Runtime) Kill(c *Container, sig int) error { + return runtime.execDriver.Kill(c.process, sig) +} + +func (runtime *Runtime) Wait(c *Container, duration time.Duration) error { + return runtime.execDriver.Wait(c.process, duration) +} + // Nuke kills all containers then removes all content // from the content root, including images, volumes and // container filesystems. From 8e87835968a4c6233406061759e3dca03ab86989 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 10 Jan 2014 15:06:16 -0800 Subject: [PATCH 082/364] Rework monitor and waitlock inside of driver Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 21 ++++++++++------- execdriver/driver.go | 9 +++---- execdriver/lxc/driver.go | 51 ++++++++++------------------------------ runtime.go | 2 +- 4 files changed, 29 insertions(+), 54 deletions(-) diff --git a/container.go b/container.go index 90347a3212..f801302154 100644 --- a/container.go +++ b/container.go @@ -743,6 +743,7 @@ func (container *Container) Start() (err error) { Network: en, Tty: container.Config.Tty, User: container.Config.User, + WaitLock: make(chan struct{}), } container.process.SysProcAttr = &syscall.SysProcAttr{Setsid: true} @@ -754,8 +755,6 @@ func (container *Container) Start() (err error) { return err } - // Init the lock - container.waitLock = make(chan struct{}) go container.monitor() if container.Config.Tty { @@ -1143,10 +1142,17 @@ func (container *Container) releaseNetwork() { } func (container *Container) monitor() { - time.Sleep(1 * time.Second) // Wait for the program to exit fmt.Printf("--->Before WAIT %s\n", container.ID) - if err := container.runtime.Wait(container, time.Duration(0)); err != nil { + if container.process == nil { + if err := container.runtime.Wait(container, 0); err != nil { + utils.Debugf("monitor: cmd.Wait reported exit status %s for container %s", err, container.ID) + } + } else { + <-container.process.WaitLock + } + + if err := container.process.WaitError; err != nil { // Since non-zero exit status and signal terminations will cause err to be non-nil, // we have to actually discard it. Still, log it anyway, just in case. utils.Debugf("monitor: cmd.Wait reported exit status %s for container %s", err, container.ID) @@ -1167,9 +1173,6 @@ func (container *Container) monitor() { exitCode := container.process.GetExitCode() container.State.SetStopped(exitCode) - // Release the lock - close(container.waitLock) - if err := container.ToDisk(); err != nil { // FIXME: there is a race condition here which causes this to fail during the unit tests. // If another goroutine was waiting for Wait() to return before removing the container's root @@ -1283,8 +1286,8 @@ func (container *Container) Restart(seconds int) error { // Wait blocks until the container stops running, then returns its exit code. func (container *Container) Wait() int { - <-container.waitLock - return container.State.GetExitCode() + <-container.process.WaitLock + return container.process.GetExitCode() } func (container *Container) Resize(h, w int) error { diff --git a/execdriver/driver.go b/execdriver/driver.go index eb468f3ef6..4a0f72dd08 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -1,20 +1,15 @@ package execdriver import ( - "errors" "os/exec" "syscall" "time" ) -var ( - ErrCommandIsNil = errors.New("Process's cmd is nil") -) - type Driver interface { Start(c *Process) error Kill(c *Process, sig int) error - Wait(c *Process, duration time.Duration) error + Wait(id string, duration time.Duration) error // Wait on an out of process option - lxc ghosts } // Network settings of the container @@ -40,6 +35,8 @@ type Process struct { ConfigPath string Tty bool Network *Network // if network is nil then networking is disabled + WaitLock chan struct{} + WaitError error } func (c *Process) Pid() int { diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index f6306b4e16..64e89c5c21 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/execdriver" - "os" "os/exec" "strconv" "strings" @@ -78,11 +77,17 @@ func (d *driver) Start(c *execdriver.Process) error { c.Path = aname c.Args = append([]string{name}, arg...) - fmt.Printf("-->%s\n-->%v\n", name, arg) if err := c.Start(); err != nil { return err } + go func() { + if err := c.Wait(); err != nil { + c.WaitError = err + } + close(c.WaitLock) + }() + // Poll for running if err := d.waitForStart(c); err != nil { return err @@ -94,37 +99,22 @@ func (d *driver) Kill(c *execdriver.Process, sig int) error { return d.kill(c, sig) } -func (d *driver) Wait(c *execdriver.Process, duration time.Duration) error { - return d.wait(c, duration) -} - -// If seconds < 0 then wait forever -func (d *driver) wait(c *execdriver.Process, duration time.Duration) error { +func (d *driver) Wait(id string, duration time.Duration) error { var ( killer bool - done = d.waitCmd(c) + done = d.waitLxc(id, &killer) ) -begin: + if duration > 0 { select { case err := <-done: - if err != nil && err == execdriver.ErrCommandIsNil { - done = d.waitLxc(c, &killer) - goto begin - } return err case <-time.After(duration): killer = true return ErrWaitTimeoutReached } } else { - if err := <-done; err != nil { - if err == execdriver.ErrCommandIsNil { - done = d.waitLxc(c, &killer) - goto begin - } - return err - } + return <-done } return nil } @@ -160,29 +150,14 @@ func (d *driver) waitForStart(c *execdriver.Process) error { } time.Sleep(50 * time.Millisecond) } - fmt.Printf("-->%s\n", string(output)) - - os.Exit(1) return ErrNotRunning } -func (d *driver) waitCmd(c *execdriver.Process) <-chan error { - done := make(chan error) - go func() { - if c == nil { - done <- execdriver.ErrCommandIsNil - return - } - done <- c.Wait() - }() - return done -} - -func (d *driver) waitLxc(c *execdriver.Process, kill *bool) <-chan error { +func (d *driver) waitLxc(id string, kill *bool) <-chan error { done := make(chan error) go func() { for *kill { - output, err := exec.Command("lxc-info", "-n", c.ID).CombinedOutput() + output, err := exec.Command("lxc-info", "-n", id).CombinedOutput() if err != nil { done <- err return diff --git a/runtime.go b/runtime.go index d302c9c217..432e28ea01 100644 --- a/runtime.go +++ b/runtime.go @@ -847,7 +847,7 @@ func (runtime *Runtime) Kill(c *Container, sig int) error { } func (runtime *Runtime) Wait(c *Container, duration time.Duration) error { - return runtime.execDriver.Wait(c.process, duration) + return runtime.execDriver.Wait(c.ID, duration) } // Nuke kills all containers then removes all content From 8b60273f76723897a9831ca125500ede85e05b5d Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 10 Jan 2014 15:34:03 -0800 Subject: [PATCH 083/364] Update with container specific waitLock Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 9 ++++----- execdriver/lxc/driver.go | 8 +++++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/container.go b/container.go index f801302154..33e39815e5 100644 --- a/container.go +++ b/container.go @@ -754,7 +754,7 @@ func (container *Container) Start() (err error) { if err := container.runtime.LogToDisk(container.stderr, container.logPath("json"), "stderr"); err != nil { return err } - + container.waitLock = make(chan struct{}) go container.monitor() if container.Config.Tty { @@ -1143,7 +1143,6 @@ func (container *Container) releaseNetwork() { func (container *Container) monitor() { // Wait for the program to exit - fmt.Printf("--->Before WAIT %s\n", container.ID) if container.process == nil { if err := container.runtime.Wait(container, 0); err != nil { utils.Debugf("monitor: cmd.Wait reported exit status %s for container %s", err, container.ID) @@ -1161,7 +1160,6 @@ func (container *Container) monitor() { } } - fmt.Printf("--->After WAIT %s\n", container.ID) // Cleanup container.cleanup() @@ -1173,6 +1171,7 @@ func (container *Container) monitor() { exitCode := container.process.GetExitCode() container.State.SetStopped(exitCode) + close(container.waitLock) if err := container.ToDisk(); err != nil { // FIXME: there is a race condition here which causes this to fail during the unit tests. // If another goroutine was waiting for Wait() to return before removing the container's root @@ -1286,8 +1285,8 @@ func (container *Container) Restart(seconds int) error { // Wait blocks until the container stops running, then returns its exit code. func (container *Container) Wait() int { - <-container.process.WaitLock - return container.process.GetExitCode() + <-container.waitLock + return container.State.GetExitCode() } func (container *Container) Resize(h, w int) error { diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 64e89c5c21..e9bddf1af5 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -17,6 +17,7 @@ const ( var ( ErrNotRunning = errors.New("Process could not be started") ErrWaitTimeoutReached = errors.New("Wait timeout reached") + Debug bool ) func init() { @@ -120,7 +121,12 @@ func (d *driver) Wait(id string, duration time.Duration) error { } func (d *driver) kill(c *execdriver.Process, sig int) error { - return exec.Command("lxc-kill", "-n", c.ID, strconv.Itoa(sig)).Run() + output, err := exec.Command("lxc-kill", "-n", c.ID, strconv.Itoa(sig)).CombinedOutput() + if err != nil { + fmt.Printf("--->%s\n", output) + return fmt.Errorf("Err: %s Output: %s", err, output) + } + return nil } func (d *driver) waitForStart(c *execdriver.Process) error { From ad9710685ca3ae101c84a57c7d68a25cba4bede2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 10 Jan 2014 16:11:19 -0800 Subject: [PATCH 084/364] Fix race in TestRun Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 2 +- execdriver/driver.go | 4 +++- execdriver/lxc/driver.go | 10 +++++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/container.go b/container.go index 33e39815e5..80f9e34ac2 100644 --- a/container.go +++ b/container.go @@ -1171,7 +1171,6 @@ func (container *Container) monitor() { exitCode := container.process.GetExitCode() container.State.SetStopped(exitCode) - close(container.waitLock) if err := container.ToDisk(); err != nil { // FIXME: there is a race condition here which causes this to fail during the unit tests. // If another goroutine was waiting for Wait() to return before removing the container's root @@ -1181,6 +1180,7 @@ func (container *Container) monitor() { // FIXME: why are we serializing running state to disk in the first place? //log.Printf("%s: Failed to dump configuration to the disk: %s", container.ID, err) } + close(container.waitLock) } func (container *Container) cleanup() { diff --git a/execdriver/driver.go b/execdriver/driver.go index 4a0f72dd08..94905a7b0d 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -44,6 +44,8 @@ func (c *Process) Pid() int { } func (c *Process) GetExitCode() int { + if c.ProcessState == nil { + return -1 + } return c.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() - return -1 } diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index e9bddf1af5..0f7a4cacf8 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -139,9 +139,13 @@ func (d *driver) waitForStart(c *execdriver.Process) error { // Note: The container can run and finish correctly before // the end of this loop for now := time.Now(); time.Since(now) < 5*time.Second; { - // If the process dies while waiting for it, just return - if c.ProcessState != nil && c.ProcessState.Exited() { - return nil + select { + case <-c.WaitLock: + // If the process dies while waiting for it, just return + if c.ProcessState != nil && c.ProcessState.Exited() { + return nil + } + default: } output, err = d.getInfo(c) From 381d593d04fc46dac5b202d047981e15183c5ed1 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 10 Jan 2014 16:18:15 -0800 Subject: [PATCH 085/364] Fix race in cleanup Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/container.go b/container.go index 80f9e34ac2..856ee352d6 100644 --- a/container.go +++ b/container.go @@ -379,12 +379,14 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s if container.Config.StdinOnce && !container.Config.Tty { defer cStdin.Close() } else { - if cStdout != nil { - defer cStdout.Close() - } - if cStderr != nil { - defer cStderr.Close() - } + defer func() { + if cStdout != nil { + cStdout.Close() + } + if cStderr != nil { + cStderr.Close() + } + }() } if container.Config.Tty { _, err = utils.CopyEscapable(cStdin, stdin) @@ -480,12 +482,15 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s } return utils.Go(func() error { - if cStdout != nil { - defer cStdout.Close() - } - if cStderr != nil { - defer cStderr.Close() - } + defer func() { + if cStdout != nil { + cStdout.Close() + } + if cStderr != nil { + cStderr.Close() + } + }() + // FIXME: how to clean up the stdin goroutine without the unwanted side effect // of closing the passed stdin? Add an intermediary io.Pipe? for i := 0; i < nJobs; i += 1 { From 77936ba1a1ff404b75ae8a34c6d4e280c23d9144 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 10 Jan 2014 17:25:32 -0800 Subject: [PATCH 086/364] Fix race in set running Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 8 ++++++-- execdriver/lxc/driver.go | 1 - 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/container.go b/container.go index 856ee352d6..924727bd3a 100644 --- a/container.go +++ b/container.go @@ -760,6 +760,7 @@ func (container *Container) Start() (err error) { return err } container.waitLock = make(chan struct{}) + container.State.SetRunning(0) go container.monitor() if container.Config.Tty { @@ -771,7 +772,9 @@ func (container *Container) Start() (err error) { return err } - container.State.SetRunning(container.process.Pid()) + // TODO: @crosbymichael @creack + // find a way to update this + // container.State.SetRunning(container.process.Pid()) container.ToDisk() return nil } @@ -1176,6 +1179,8 @@ func (container *Container) monitor() { exitCode := container.process.GetExitCode() container.State.SetStopped(exitCode) + close(container.waitLock) + if err := container.ToDisk(); err != nil { // FIXME: there is a race condition here which causes this to fail during the unit tests. // If another goroutine was waiting for Wait() to return before removing the container's root @@ -1185,7 +1190,6 @@ func (container *Container) monitor() { // FIXME: why are we serializing running state to disk in the first place? //log.Printf("%s: Failed to dump configuration to the disk: %s", container.ID, err) } - close(container.waitLock) } func (container *Container) cleanup() { diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 0f7a4cacf8..62b60deb61 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -123,7 +123,6 @@ func (d *driver) Wait(id string, duration time.Duration) error { func (d *driver) kill(c *execdriver.Process, sig int) error { output, err := exec.Command("lxc-kill", "-n", c.ID, strconv.Itoa(sig)).CombinedOutput() if err != nil { - fmt.Printf("--->%s\n", output) return fmt.Errorf("Err: %s Output: %s", err, output) } return nil From 66782730b85db20600ce48fcab203a5eb43bcb02 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 10 Jan 2014 17:41:12 -0800 Subject: [PATCH 087/364] Fix network disabled for container Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container.go b/container.go index 924727bd3a..f433418432 100644 --- a/container.go +++ b/container.go @@ -726,7 +726,7 @@ func (container *Container) Start() (err error) { } var en *execdriver.Network - if !container.runtime.networkManager.disabled { + if !container.Config.NetworkDisabled { network := container.NetworkSettings en = &execdriver.Network{ Gateway: network.Gateway, From 93ead2fe789a3a94a8433b12efcca782e0a7d7de Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 10 Jan 2014 18:09:07 -0800 Subject: [PATCH 088/364] Update with lxc unconfined changes Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 69 +++++++++------------------------------ execdriver/driver.go | 1 + execdriver/lxc/driver.go | 50 +++++++++++++++++++++++----- integration/utils_test.go | 1 - runtime.go | 48 ++++++++------------------- server.go | 9 +---- 6 files changed, 73 insertions(+), 105 deletions(-) diff --git a/container.go b/container.go index f433418432..a0b4abea6f 100644 --- a/container.go +++ b/container.go @@ -17,7 +17,6 @@ import ( "net" "os" "path" - "strconv" "strings" "sync" "syscall" @@ -563,34 +562,6 @@ func (container *Container) Start() (err error) { return err } - var lxcStart string = "lxc-start" - if container.hostConfig.Privileged && container.runtime.capabilities.AppArmor { - lxcStart = path.Join(container.runtime.config.Root, "lxc-start-unconfined") - } - - params := []string{ - lxcStart, - "-n", container.ID, - "-f", container.lxcConfigPath(), - "--", - "/.dockerinit", - } - - // Networking - if !container.Config.NetworkDisabled { - network := container.NetworkSettings - params = append(params, - "-g", network.Gateway, - "-i", fmt.Sprintf("%s/%d", network.IPAddress, network.IPPrefixLen), - "-mtu", strconv.Itoa(container.runtime.config.Mtu), - ) - } - - // User - if container.Config.User != "" { - params = append(params, "-u", container.Config.User) - } - // Setup environment env := []string{ "HOME=/", @@ -602,10 +573,6 @@ func (container *Container) Start() (err error) { env = append(env, "TERM=xterm") } - if container.hostConfig.Privileged { - params = append(params, "-privileged") - } - // Init any links between the parent and children runtime := container.runtime @@ -661,31 +628,25 @@ func (container *Container) Start() (err error) { if err := os.MkdirAll(path.Join(container.RootfsPath(), workingDir), 0755); err != nil { return nil } - - params = append(params, - "-w", workingDir, - ) } - // Program - params = append(params, "--", container.Path) - params = append(params, container.Args...) + /* + if RootIsShared() { + // lxc-start really needs / to be non-shared, or all kinds of stuff break + // when lxc-start unmount things and those unmounts propagate to the main + // mount namespace. + // What we really want is to clone into a new namespace and then + // mount / MS_REC|MS_SLAVE, but since we can't really clone or fork + // without exec in go we have to do this horrible shell hack... + shellString := + "mount --make-rslave /; exec " + + utils.ShellQuoteArguments(params) - if RootIsShared() { - // lxc-start really needs / to be non-shared, or all kinds of stuff break - // when lxc-start unmount things and those unmounts propagate to the main - // mount namespace. - // What we really want is to clone into a new namespace and then - // mount / MS_REC|MS_SLAVE, but since we can't really clone or fork - // without exec in go we have to do this horrible shell hack... - shellString := - "mount --make-rslave /; exec " + - utils.ShellQuoteArguments(params) - - params = []string{ - "unshare", "-m", "--", "/bin/sh", "-c", shellString, + params = []string{ + "unshare", "-m", "--", "/bin/sh", "-c", shellString, + } } - } + */ root := container.RootfsPath() envPath, err := container.EnvConfigPath() diff --git a/execdriver/driver.go b/execdriver/driver.go index 94905a7b0d..a7d095bda3 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -10,6 +10,7 @@ type Driver interface { Start(c *Process) error Kill(c *Process, sig int) error Wait(id string, duration time.Duration) error // Wait on an out of process option - lxc ghosts + Version() string } // Network settings of the container diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 62b60deb61..8a10c77a4d 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -4,7 +4,9 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/execdriver" + "os" "os/exec" + "path" "strconv" "strings" "time" @@ -17,21 +19,21 @@ const ( var ( ErrNotRunning = errors.New("Process could not be started") ErrWaitTimeoutReached = errors.New("Wait timeout reached") - Debug bool ) -func init() { - // Register driver -} - type driver struct { - root string // root path for the driver to use + root string // root path for the driver to use + apparmor bool } -func NewDriver(root string) (execdriver.Driver, error) { +func NewDriver(root string, apparmor bool) (execdriver.Driver, error) { // setup unconfined symlink + if err := linkLxcStart(root); err != nil { + return nil, err + } return &driver{ - root: root, + apparmor: apparmor, + root: root, }, nil } @@ -57,6 +59,10 @@ func (d *driver) Start(c *execdriver.Process) error { } if c.Privileged { + if d.apparmor { + params[0] = path.Join(d.root, "lxc-start-unconfined") + + } params = append(params, "-privileged") } @@ -120,6 +126,17 @@ func (d *driver) Wait(id string, duration time.Duration) error { return nil } +func (d *driver) Version() string { + version := "" + if output, err := exec.Command("lxc-version").CombinedOutput(); err == nil { + outputStr := string(output) + if len(strings.SplitN(outputStr, ":", 2)) == 2 { + version = strings.TrimSpace(strings.SplitN(outputStr, ":", 2)[1]) + } + } + return version +} + func (d *driver) kill(c *execdriver.Process, sig int) error { output, err := exec.Command("lxc-kill", "-n", c.ID, strconv.Itoa(sig)).CombinedOutput() if err != nil { @@ -184,3 +201,20 @@ func (d *driver) waitLxc(id string, kill *bool) <-chan error { func (d *driver) getInfo(c *execdriver.Process) ([]byte, error) { return exec.Command("lxc-info", "-s", "-n", c.ID).CombinedOutput() } + +func linkLxcStart(root string) error { + sourcePath, err := exec.LookPath("lxc-start") + if err != nil { + return err + } + targetPath := path.Join(root, "lxc-start-unconfined") + + if _, err := os.Lstat(targetPath); err != nil && !os.IsNotExist(err) { + return err + } else if err == nil { + if err := os.Remove(targetPath); err != nil { + return err + } + } + return os.Symlink(sourcePath, targetPath) +} diff --git a/integration/utils_test.go b/integration/utils_test.go index 4ab1c96cca..63ac3a44b9 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -38,7 +38,6 @@ func mkRuntime(f utils.Fataler) *docker.Runtime { if err != nil { f.Fatal(err) } - r.UpdateCapabilities(true) return r } diff --git a/runtime.go b/runtime.go index 432e28ea01..b1644b985f 100644 --- a/runtime.go +++ b/runtime.go @@ -334,8 +334,8 @@ func (runtime *Runtime) restore() error { return nil } -// FIXME: comment please! -func (runtime *Runtime) UpdateCapabilities(quiet bool) { +func NewRuntimeCapabilities(quiet bool) *Capabilities { + capabilities := &Capabilities{} if cgroupMemoryMountpoint, err := cgroups.FindCgroupMountpoint("memory"); err != nil { if !quiet { log.Printf("WARNING: %s\n", err) @@ -343,32 +343,33 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) { } else { _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes")) _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) - runtime.capabilities.MemoryLimit = err1 == nil && err2 == nil - if !runtime.capabilities.MemoryLimit && !quiet { + capabilities.MemoryLimit = err1 == nil && err2 == nil + if !capabilities.MemoryLimit && !quiet { log.Printf("WARNING: Your kernel does not support cgroup memory limit.") } _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) - runtime.capabilities.SwapLimit = err == nil - if !runtime.capabilities.SwapLimit && !quiet { + capabilities.SwapLimit = err == nil + if !capabilities.SwapLimit && !quiet { log.Printf("WARNING: Your kernel does not support cgroup swap limit.") } } content, err3 := ioutil.ReadFile("/proc/sys/net/ipv4/ip_forward") - runtime.capabilities.IPv4ForwardingDisabled = err3 != nil || len(content) == 0 || content[0] != '1' - if runtime.capabilities.IPv4ForwardingDisabled && !quiet { + capabilities.IPv4ForwardingDisabled = err3 != nil || len(content) == 0 || content[0] != '1' + if capabilities.IPv4ForwardingDisabled && !quiet { log.Printf("WARNING: IPv4 forwarding is disabled.") } // Check if AppArmor seems to be enabled on this system. if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) { utils.Debugf("/sys/kernel/security/apparmor not found; assuming AppArmor is not enabled.") - runtime.capabilities.AppArmor = false + capabilities.AppArmor = false } else { utils.Debugf("/sys/kernel/security/apparmor found; assuming AppArmor is enabled.") - runtime.capabilities.AppArmor = true + capabilities.AppArmor = true } + return capabilities } // Create creates a new container from the given configuration with a given name. @@ -649,7 +650,6 @@ func NewRuntime(config *DaemonConfig) (*Runtime, error) { if err != nil { return nil, err } - runtime.UpdateCapabilities(false) return runtime, nil } @@ -678,10 +678,6 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { } } - utils.Debugf("Escaping AppArmor confinement") - if err := linkLxcStart(config.Root); err != nil { - return nil, err - } utils.Debugf("Creating images graph") g, err := NewGraph(path.Join(config.Root, "graph"), driver) if err != nil { @@ -738,7 +734,8 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { sysInitPath = localCopy } - ed, err := lxc.NewDriver("") + capabilities := NewRuntimeCapabilities(false) + ed, err := lxc.NewDriver(config.Root, capabilities.AppArmor) if err != nil { return nil, err } @@ -750,7 +747,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { graph: g, repositories: repositories, idIndex: utils.NewTruncIndex(), - capabilities: &Capabilities{}, + capabilities: capabilities, volumes: volumes, config: config, containerGraph: graph, @@ -869,23 +866,6 @@ func (runtime *Runtime) Nuke() error { return os.RemoveAll(runtime.config.Root) } -func linkLxcStart(root string) error { - sourcePath, err := exec.LookPath("lxc-start") - if err != nil { - return err - } - targetPath := path.Join(root, "lxc-start-unconfined") - - if _, err := os.Lstat(targetPath); err != nil && !os.IsNotExist(err) { - return err - } else if err == nil { - if err := os.Remove(targetPath); err != nil { - return err - } - } - return os.Symlink(sourcePath, targetPath) -} - // FIXME: this is a convenience function for integration tests // which need direct access to runtime.graph. // Once the tests switch to using engine and jobs, this method diff --git a/server.go b/server.go index 3892ba70ea..a9c83575e4 100644 --- a/server.go +++ b/server.go @@ -661,13 +661,6 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { } else { imgcount = len(images) } - lxcVersion := "" - if output, err := exec.Command("lxc-version").CombinedOutput(); err == nil { - outputStr := string(output) - if len(strings.SplitN(outputStr, ":", 2)) == 2 { - lxcVersion = strings.TrimSpace(strings.SplitN(string(output), ":", 2)[1]) - } - } kernelVersion := "" if kv, err := utils.GetKernelVersion(); err == nil { kernelVersion = kv.String() @@ -691,7 +684,7 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { v.SetBool("Debug", os.Getenv("DEBUG") != "") v.SetInt("NFd", utils.GetTotalUsedFds()) v.SetInt("NGoroutines", runtime.NumGoroutine()) - v.Set("LXCVersion", lxcVersion) + v.Set("LXCVersion", srv.runtime.execDriver.Version()) v.SetInt("NEventsListener", len(srv.events)) v.Set("KernelVersion", kernelVersion) v.Set("IndexServerAddress", auth.IndexServerAddress()) From 1d8455e6838f2630221eb3fcafeb382f9f7ce260 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 10 Jan 2014 18:21:41 -0800 Subject: [PATCH 089/364] Move RootIsShared to lxc driver Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 20 ------------------- execdriver/MAINTAINERS | 2 ++ execdriver/lxc/driver.go | 43 ++++++++++++++++++++++++++++++++++++---- utils.go | 15 -------------- 4 files changed, 41 insertions(+), 39 deletions(-) create mode 100644 execdriver/MAINTAINERS diff --git a/container.go b/container.go index a0b4abea6f..f0d687d984 100644 --- a/container.go +++ b/container.go @@ -623,31 +623,11 @@ func (container *Container) Start() (err error) { var workingDir string if container.Config.WorkingDir != "" { workingDir = path.Clean(container.Config.WorkingDir) - utils.Debugf("[working dir] working dir is %s", workingDir) - if err := os.MkdirAll(path.Join(container.RootfsPath(), workingDir), 0755); err != nil { return nil } } - /* - if RootIsShared() { - // lxc-start really needs / to be non-shared, or all kinds of stuff break - // when lxc-start unmount things and those unmounts propagate to the main - // mount namespace. - // What we really want is to clone into a new namespace and then - // mount / MS_REC|MS_SLAVE, but since we can't really clone or fork - // without exec in go we have to do this horrible shell hack... - shellString := - "mount --make-rslave /; exec " + - utils.ShellQuoteArguments(params) - - params = []string{ - "unshare", "-m", "--", "/bin/sh", "-c", shellString, - } - } - */ - root := container.RootfsPath() envPath, err := container.EnvConfigPath() if err != nil { diff --git a/execdriver/MAINTAINERS b/execdriver/MAINTAINERS new file mode 100644 index 0000000000..e53d933d47 --- /dev/null +++ b/execdriver/MAINTAINERS @@ -0,0 +1,2 @@ +Michael Crosby (@crosbymichael) +Guillaume Charmes (@creack) diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 8a10c77a4d..359fe4198a 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/utils" + "io/ioutil" "os" "os/exec" "path" @@ -22,8 +24,9 @@ var ( ) type driver struct { - root string // root path for the driver to use - apparmor bool + root string // root path for the driver to use + apparmor bool + sharedRoot bool } func NewDriver(root string, apparmor bool) (execdriver.Driver, error) { @@ -32,8 +35,9 @@ func NewDriver(root string, apparmor bool) (execdriver.Driver, error) { return nil, err } return &driver{ - apparmor: apparmor, - root: root, + apparmor: apparmor, + root: root, + sharedRoot: rootIsShared(), }, nil } @@ -70,6 +74,23 @@ func (d *driver) Start(c *execdriver.Process) error { params = append(params, "-w", c.WorkingDir) } + if d.sharedRoot { + // lxc-start really needs / to be non-shared, or all kinds of stuff break + // when lxc-start unmount things and those unmounts propagate to the main + // mount namespace. + // What we really want is to clone into a new namespace and then + // mount / MS_REC|MS_SLAVE, but since we can't really clone or fork + // without exec in go we have to do this horrible shell hack... + shellString := + "mount --make-rslave /; exec " + + utils.ShellQuoteArguments(params) + + params = []string{ + "unshare", "-m", "--", "/bin/sh", "-c", shellString, + } + + } + params = append(params, "--", c.Entrypoint) params = append(params, c.Arguments...) @@ -218,3 +239,17 @@ func linkLxcStart(root string) error { } return os.Symlink(sourcePath, targetPath) } + +func rootIsShared() bool { + if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { + for _, line := range strings.Split(string(data), "\n") { + cols := strings.Split(line, " ") + if len(cols) >= 6 && cols[4] == "/" { + return strings.HasPrefix(cols[6], "shared") + } + } + } + + // No idea, probably safe to assume so + return true +} diff --git a/utils.go b/utils.go index 3eb1eac045..f0591158a4 100644 --- a/utils.go +++ b/utils.go @@ -5,7 +5,6 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/pkg/namesgenerator" "github.com/dotcloud/docker/utils" - "io/ioutil" "strconv" "strings" ) @@ -328,20 +327,6 @@ func parseLink(rawLink string) (map[string]string, error) { return utils.PartParser("name:alias", rawLink) } -func RootIsShared() bool { - if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { - for _, line := range strings.Split(string(data), "\n") { - cols := strings.Split(line, " ") - if len(cols) >= 6 && cols[4] == "/" { - return strings.HasPrefix(cols[6], "shared") - } - } - } - - // No idea, probably safe to assume so - return true -} - type checker struct { runtime *Runtime } From 8e0741f5e40d107780a5be1f6ae0f4d30ba43d7d Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 10 Jan 2014 20:22:39 -0800 Subject: [PATCH 090/364] Add chroot driver for testing Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 25 +++++++------- execdriver/chroot/driver.go | 66 +++++++++++++++++++++++++++++++++++++ execdriver/driver.go | 27 +++++++-------- execdriver/lxc/driver.go | 1 - runtime.go | 8 ++++- sysinit/sysinit.go | 31 ++++++++--------- 6 files changed, 116 insertions(+), 42 deletions(-) create mode 100644 execdriver/chroot/driver.go diff --git a/container.go b/container.go index f0d687d984..d81702fd83 100644 --- a/container.go +++ b/container.go @@ -678,18 +678,19 @@ func (container *Container) Start() (err error) { } container.process = &execdriver.Process{ - ID: container.ID, - Privileged: container.hostConfig.Privileged, - Rootfs: root, - InitPath: "/.dockerinit", - Entrypoint: container.Path, - Arguments: container.Args, - WorkingDir: workingDir, - ConfigPath: container.lxcConfigPath(), - Network: en, - Tty: container.Config.Tty, - User: container.Config.User, - WaitLock: make(chan struct{}), + ID: container.ID, + Privileged: container.hostConfig.Privileged, + Rootfs: root, + InitPath: "/.dockerinit", + Entrypoint: container.Path, + Arguments: container.Args, + WorkingDir: workingDir, + ConfigPath: container.lxcConfigPath(), + Network: en, + Tty: container.Config.Tty, + User: container.Config.User, + WaitLock: make(chan struct{}), + SysInitPath: runtime.sysInitPath, } container.process.SysProcAttr = &syscall.SysProcAttr{Setsid: true} diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go new file mode 100644 index 0000000000..6a9d4784c2 --- /dev/null +++ b/execdriver/chroot/driver.go @@ -0,0 +1,66 @@ +package chroot + +import ( + "fmt" + "github.com/dotcloud/docker/execdriver" + "io/ioutil" + "os/exec" + "path" + "time" +) + +type driver struct { +} + +func NewDriver() (execdriver.Driver, error) { + return &driver{}, nil +} + +func (d *driver) Start(c *execdriver.Process) error { + data, _ := ioutil.ReadFile(c.SysInitPath) + ioutil.WriteFile(path.Join(c.Rootfs, ".dockerinit"), data, 0644) + params := []string{ + "chroot", + c.Rootfs, + "/.dockerinit", + } + // need to mount proc + params = append(params, c.Entrypoint) + params = append(params, c.Arguments...) + + var ( + name = params[0] + arg = params[1:] + ) + aname, err := exec.LookPath(name) + if err != nil { + aname = name + } + c.Path = aname + c.Args = append([]string{name}, arg...) + + if err := c.Start(); err != nil { + return err + } + + go func() { + if err := c.Wait(); err != nil { + c.WaitError = err + } + close(c.WaitLock) + }() + + return nil +} + +func (d *driver) Kill(p *execdriver.Process, sig int) error { + return p.Process.Kill() +} + +func (d *driver) Wait(id string, duration time.Duration) error { + panic("No Implemented") +} + +func (d *driver) Version() string { + return "0.1" +} diff --git a/execdriver/driver.go b/execdriver/driver.go index a7d095bda3..202d6ccdc7 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -25,19 +25,20 @@ type Network struct { type Process struct { exec.Cmd - ID string - Privileged bool - User string - Rootfs string // root fs of the container - InitPath string // dockerinit - Entrypoint string - Arguments []string - WorkingDir string - ConfigPath string - Tty bool - Network *Network // if network is nil then networking is disabled - WaitLock chan struct{} - WaitError error + ID string + Privileged bool + User string + Rootfs string // root fs of the container + InitPath string // dockerinit + Entrypoint string + Arguments []string + WorkingDir string + ConfigPath string + Tty bool + Network *Network // if network is nil then networking is disabled + SysInitPath string + WaitLock chan struct{} + WaitError error } func (c *Process) Pid() int { diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 359fe4198a..2265185899 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -88,7 +88,6 @@ func (d *driver) Start(c *execdriver.Process) error { params = []string{ "unshare", "-m", "--", "/bin/sh", "-c", shellString, } - } params = append(params, "--", c.Entrypoint) diff --git a/runtime.go b/runtime.go index b1644b985f..0ae23740c3 100644 --- a/runtime.go +++ b/runtime.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/cgroups" "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/execdriver/chroot" "github.com/dotcloud/docker/execdriver/lxc" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/graphdriver/aufs" @@ -735,7 +736,12 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { } capabilities := NewRuntimeCapabilities(false) - ed, err := lxc.NewDriver(config.Root, capabilities.AppArmor) + var ed execdriver.Driver + if driver := os.Getenv("EXEC_DRIVER"); driver == "lxc" { + ed, err = lxc.NewDriver(config.Root, capabilities.AppArmor) + } else { + ed, err = chroot.NewDriver() + } if err != nil { return nil, err } diff --git a/sysinit/sysinit.go b/sysinit/sysinit.go index ce46e06f14..72f5a3ba83 100644 --- a/sysinit/sysinit.go +++ b/sysinit/sysinit.go @@ -182,24 +182,25 @@ func getEnv(args *DockerInitArgs, key string) string { func executeProgram(args *DockerInitArgs) error { setupEnv(args) - if err := setupHostname(args); err != nil { - return err - } + if false { + if err := setupHostname(args); err != nil { + return err + } - if err := setupNetworking(args); err != nil { - return err - } + if err := setupNetworking(args); err != nil { + return err + } - if err := setupCapabilities(args); err != nil { - return err - } + if err := setupCapabilities(args); err != nil { + return err + } + if err := setupWorkingDirectory(args); err != nil { + return err + } - if err := setupWorkingDirectory(args); err != nil { - return err - } - - if err := changeUser(args); err != nil { - return err + if err := changeUser(args); err != nil { + return err + } } path, err := exec.LookPath(args.args[0]) From 92e6db7beba8ad58e425119cc9885c355a5755e7 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 13 Jan 2014 11:13:49 -0800 Subject: [PATCH 091/364] Improve chroot driver by mounting proc Add -driver flag to dockerinit Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- execdriver/chroot/driver.go | 12 ++++++------ execdriver/driver.go | 1 + execdriver/lxc/driver.go | 6 ++++++ mount/mount.go | 16 +++++++++++++--- sysinit/sysinit.go | 16 +++++++++++++++- 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index 6a9d4784c2..b404f180e5 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -1,11 +1,8 @@ package chroot import ( - "fmt" "github.com/dotcloud/docker/execdriver" - "io/ioutil" "os/exec" - "path" "time" ) @@ -16,15 +13,18 @@ func NewDriver() (execdriver.Driver, error) { return &driver{}, nil } +func (d *driver) String() string { + return "chroot" +} + func (d *driver) Start(c *execdriver.Process) error { - data, _ := ioutil.ReadFile(c.SysInitPath) - ioutil.WriteFile(path.Join(c.Rootfs, ".dockerinit"), data, 0644) params := []string{ "chroot", c.Rootfs, "/.dockerinit", + "-driver", + d.String(), } - // need to mount proc params = append(params, c.Entrypoint) params = append(params, c.Arguments...) diff --git a/execdriver/driver.go b/execdriver/driver.go index 202d6ccdc7..04c52161d4 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -11,6 +11,7 @@ type Driver interface { Kill(c *Process, sig int) error Wait(id string, duration time.Duration) error // Wait on an out of process option - lxc ghosts Version() string + String() string } // Network settings of the container diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 2265185899..7a4e754911 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -41,6 +41,10 @@ func NewDriver(root string, apparmor bool) (execdriver.Driver, error) { }, nil } +func (d *driver) String() string { + return "lxc" +} + func (d *driver) Start(c *execdriver.Process) error { params := []string{ startPath, @@ -48,6 +52,8 @@ func (d *driver) Start(c *execdriver.Process) error { "-f", c.ConfigPath, "--", c.InitPath, + "-driver", + d.String(), } if c.Network != nil { diff --git a/mount/mount.go b/mount/mount.go index b087293a9d..3860b975bd 100644 --- a/mount/mount.go +++ b/mount/mount.go @@ -25,27 +25,37 @@ func Mounted(mountpoint string) (bool, error) { return false, nil } -// Mount the specified options at the target path +// Mount the specified options at the target path only if +// the target is not mounted // Options must be specified as fstab style func Mount(device, target, mType, options string) error { if mounted, err := Mounted(target); err != nil || mounted { return err } + return ForceMount(device, target, mType, options) +} +// Mount the specified options at the target path +// reguardless if the target is mounted or not +// Options must be specified as fstab style +func ForceMount(device, target, mType, options string) error { flag, data := parseOptions(options) if err := mount(device, target, mType, uintptr(flag), data); err != nil { return err } return nil - } // Unmount the target only if it is mounted -func Unmount(target string) (err error) { +func Unmount(target string) error { if mounted, err := Mounted(target); err != nil || !mounted { return err } + return ForceUnmount(target) +} +// Unmount the target reguardless if it is mounted or not +func ForceUnmount(target string) (err error) { // Simple retry logic for unmount for i := 0; i < 10; i++ { if err = unmount(target, 0); err == nil { diff --git a/sysinit/sysinit.go b/sysinit/sysinit.go index 72f5a3ba83..73349379fb 100644 --- a/sysinit/sysinit.go +++ b/sysinit/sysinit.go @@ -4,6 +4,7 @@ import ( "encoding/json" "flag" "fmt" + "github.com/dotcloud/docker/mount" "github.com/dotcloud/docker/pkg/netlink" "github.com/dotcloud/docker/utils" "github.com/syndtr/gocapability/capability" @@ -26,6 +27,7 @@ type DockerInitArgs struct { env []string args []string mtu int + driver string } func setupHostname(args *DockerInitArgs) error { @@ -92,6 +94,10 @@ func setupWorkingDirectory(args *DockerInitArgs) error { return nil } +func setupMounts(args *DockerInitArgs) error { + return mount.ForceMount("proc", "proc", "proc", "") +} + // Takes care of dropping privileges to the desired user func changeUser(args *DockerInitArgs) error { if args.user == "" { @@ -182,7 +188,7 @@ func getEnv(args *DockerInitArgs, key string) string { func executeProgram(args *DockerInitArgs) error { setupEnv(args) - if false { + if args.driver == "lxc" { if err := setupHostname(args); err != nil { return err } @@ -201,6 +207,12 @@ func executeProgram(args *DockerInitArgs) error { if err := changeUser(args); err != nil { return err } + } else if args.driver == "chroot" { + // TODO: @crosbymichael @creack how do we unmount this after the + // process exists? + if err := setupMounts(args); err != nil { + return err + } } path, err := exec.LookPath(args.args[0]) @@ -233,6 +245,7 @@ func SysInit() { workDir := flag.String("w", "", "workdir") privileged := flag.Bool("privileged", false, "privileged mode") mtu := flag.Int("mtu", 1500, "interface mtu") + driver := flag.String("driver", "", "exec driver") flag.Parse() // Get env @@ -257,6 +270,7 @@ func SysInit() { env: env, args: flag.Args(), mtu: *mtu, + driver: *driver, } if err := executeProgram(args); err != nil { From f846ecdc77bb9ab4d44b41a2e403bcf579d6b6e9 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 13 Jan 2014 15:02:12 -0800 Subject: [PATCH 092/364] Make exec driver run a blocking command Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 69 ++++++++++++++++++++----------------- execdriver/chroot/driver.go | 20 ++++++++--- execdriver/driver.go | 9 ++--- execdriver/lxc/driver.go | 31 +++++++++++------ runtime.go | 6 ++-- 5 files changed, 82 insertions(+), 53 deletions(-) diff --git a/container.go b/container.go index d81702fd83..9fac0da587 100644 --- a/container.go +++ b/container.go @@ -308,10 +308,10 @@ func (container *Container) generateLXCConfig() error { return LxcTemplateCompiled.Execute(fo, container) } -func (container *Container) startPty() error { +func (container *Container) startPty(startCallback execdriver.StartCallback) (int, error) { ptyMaster, ptySlave, err := pty.Open() if err != nil { - return err + return -1, err } container.ptyMaster = ptyMaster container.process.Stdout = ptySlave @@ -336,20 +336,17 @@ func (container *Container) startPty() error { utils.Debugf("startPty: end of stdin pipe") }() } - if err := container.runtime.Start(container); err != nil { - return err - } - ptySlave.Close() - return nil + + return container.runtime.Run(container, startCallback) } -func (container *Container) start() error { +func (container *Container) start(startCallback execdriver.StartCallback) (int, error) { container.process.Stdout = container.stdout container.process.Stderr = container.stderr if container.Config.OpenStdin { stdin, err := container.process.StdinPipe() if err != nil { - return err + return -1, err } go func() { defer stdin.Close() @@ -358,7 +355,7 @@ func (container *Container) start() error { utils.Debugf("start: end of stdin pipe") }() } - return container.runtime.Start(container) + return container.runtime.Run(container, startCallback) } func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error { @@ -689,7 +686,6 @@ func (container *Container) Start() (err error) { Network: en, Tty: container.Config.Tty, User: container.Config.User, - WaitLock: make(chan struct{}), SysInitPath: runtime.sysInitPath, } container.process.SysProcAttr = &syscall.SysProcAttr{Setsid: true} @@ -703,21 +699,26 @@ func (container *Container) Start() (err error) { } container.waitLock = make(chan struct{}) container.State.SetRunning(0) - go container.monitor() - if container.Config.Tty { - err = container.startPty() - } else { - err = container.start() - } - if err != nil { - return err + waitLock := make(chan struct{}) + f := func(process *execdriver.Process) { + container.State.SetRunning(process.Pid()) + if process.Tty { + if c, ok := process.Stdout.(io.Closer); ok { + c.Close() + } + } + if err := container.ToDisk(); err != nil { + utils.Debugf("%s", err) + } + close(waitLock) } - // TODO: @crosbymichael @creack - // find a way to update this - // container.State.SetRunning(container.process.Pid()) - container.ToDisk() + go container.monitor(f) + + // Start should not return until the process is actually running + <-waitLock + return nil } @@ -1091,17 +1092,24 @@ func (container *Container) releaseNetwork() { container.NetworkSettings = &NetworkSettings{} } -func (container *Container) monitor() { - // Wait for the program to exit +func (container *Container) monitor(f execdriver.StartCallback) { + var ( + err error + exitCode int + ) + if container.process == nil { - if err := container.runtime.Wait(container, 0); err != nil { - utils.Debugf("monitor: cmd.Wait reported exit status %s for container %s", err, container.ID) - } + // This happends when you have a GHOST container with lxc + err = container.runtime.Wait(container, 0) } else { - <-container.process.WaitLock + if container.Config.Tty { + exitCode, err = container.startPty(f) + } else { + exitCode, err = container.start(f) + } } - if err := container.process.WaitError; err != nil { + if err != nil { //TODO: @crosbymichael @creack report error // Since non-zero exit status and signal terminations will cause err to be non-nil, // we have to actually discard it. Still, log it anyway, just in case. utils.Debugf("monitor: cmd.Wait reported exit status %s for container %s", err, container.ID) @@ -1118,7 +1126,6 @@ func (container *Container) monitor() { container.stdin, container.stdinPipe = io.Pipe() } - exitCode := container.process.GetExitCode() container.State.SetStopped(exitCode) close(container.waitLock) diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index b404f180e5..eb2525be6d 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -17,7 +17,7 @@ func (d *driver) String() string { return "chroot" } -func (d *driver) Start(c *execdriver.Process) error { +func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallback) (int, error) { params := []string{ "chroot", c.Rootfs, @@ -40,17 +40,27 @@ func (d *driver) Start(c *execdriver.Process) error { c.Args = append([]string{name}, arg...) if err := c.Start(); err != nil { - return err + return -1, err } + var ( + waitErr error + waitLock = make(chan struct{}) + ) go func() { if err := c.Wait(); err != nil { - c.WaitError = err + waitErr = err } - close(c.WaitLock) + close(waitLock) }() - return nil + if startCallback != nil { + startCallback(c) + } + + <-waitLock + + return c.GetExitCode(), waitErr } func (d *driver) Kill(p *execdriver.Process, sig int) error { diff --git a/execdriver/driver.go b/execdriver/driver.go index 04c52161d4..c38dc96a91 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -6,10 +6,13 @@ import ( "time" ) +type StartCallback func(*Process) + type Driver interface { - Start(c *Process) error + Run(c *Process, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code Kill(c *Process, sig int) error - Wait(id string, duration time.Duration) error // Wait on an out of process option - lxc ghosts + // TODO: @crosbymichael @creack wait should probably return the exit code + Wait(id string, duration time.Duration) error // Wait on an out of process...process - lxc ghosts Version() string String() string } @@ -38,8 +41,6 @@ type Process struct { Tty bool Network *Network // if network is nil then networking is disabled SysInitPath string - WaitLock chan struct{} - WaitError error } func (c *Process) Pid() int { diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 7a4e754911..5ab6f8b824 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -45,7 +45,7 @@ func (d *driver) String() string { return "lxc" } -func (d *driver) Start(c *execdriver.Process) error { +func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallback) (int, error) { params := []string{ startPath, "-n", c.ID, @@ -111,21 +111,32 @@ func (d *driver) Start(c *execdriver.Process) error { c.Args = append([]string{name}, arg...) if err := c.Start(); err != nil { - return err + return -1, err } + var ( + waitErr error + waitLock = make(chan struct{}) + ) go func() { if err := c.Wait(); err != nil { - c.WaitError = err + waitErr = err } - close(c.WaitLock) + close(waitLock) }() - // Poll for running - if err := d.waitForStart(c); err != nil { - return err + // Poll lxc for RUNNING status + if err := d.waitForStart(c, waitLock); err != nil { + return -1, err } - return nil + + if startCallback != nil { + startCallback(c) + } + + <-waitLock + + return c.GetExitCode(), waitErr } func (d *driver) Kill(c *execdriver.Process, sig int) error { @@ -171,7 +182,7 @@ func (d *driver) kill(c *execdriver.Process, sig int) error { return nil } -func (d *driver) waitForStart(c *execdriver.Process) error { +func (d *driver) waitForStart(c *execdriver.Process, waitLock chan struct{}) error { var ( err error output []byte @@ -182,7 +193,7 @@ func (d *driver) waitForStart(c *execdriver.Process) error { // the end of this loop for now := time.Now(); time.Since(now) < 5*time.Second; { select { - case <-c.WaitLock: + case <-waitLock: // If the process dies while waiting for it, just return if c.ProcessState != nil && c.ProcessState.Exited() { return nil diff --git a/runtime.go b/runtime.go index 0ae23740c3..ec32f75456 100644 --- a/runtime.go +++ b/runtime.go @@ -192,7 +192,7 @@ func (runtime *Runtime) Register(container *Container) error { } container.waitLock = make(chan struct{}) - go container.monitor() + go container.monitor(nil) } } return nil @@ -841,8 +841,8 @@ func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) { return archive.ExportChanges(cDir, changes) } -func (runtime *Runtime) Start(c *Container) error { - return runtime.execDriver.Start(c.process) +func (runtime *Runtime) Run(c *Container, startCallback execdriver.StartCallback) (int, error) { + return runtime.execDriver.Run(c.process, startCallback) } func (runtime *Runtime) Kill(c *Container, sig int) error { From 1e81387edcab600c9b8bc2a502988f7b3a2013e7 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 13 Jan 2014 15:09:41 -0800 Subject: [PATCH 093/364] Remove SysinitPath from process Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 23 +++++++++++------------ execdriver/driver.go | 23 +++++++++++------------ execdriver/lxc/driver.go | 1 + 3 files changed, 23 insertions(+), 24 deletions(-) diff --git a/container.go b/container.go index 9fac0da587..424c9d5f1f 100644 --- a/container.go +++ b/container.go @@ -675,18 +675,17 @@ func (container *Container) Start() (err error) { } container.process = &execdriver.Process{ - ID: container.ID, - Privileged: container.hostConfig.Privileged, - Rootfs: root, - InitPath: "/.dockerinit", - Entrypoint: container.Path, - Arguments: container.Args, - WorkingDir: workingDir, - ConfigPath: container.lxcConfigPath(), - Network: en, - Tty: container.Config.Tty, - User: container.Config.User, - SysInitPath: runtime.sysInitPath, + ID: container.ID, + Privileged: container.hostConfig.Privileged, + Rootfs: root, + InitPath: "/.dockerinit", + Entrypoint: container.Path, + Arguments: container.Args, + WorkingDir: workingDir, + ConfigPath: container.lxcConfigPath(), + Network: en, + Tty: container.Config.Tty, + User: container.Config.User, } container.process.SysProcAttr = &syscall.SysProcAttr{Setsid: true} diff --git a/execdriver/driver.go b/execdriver/driver.go index c38dc96a91..cb9df96e79 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -29,18 +29,17 @@ type Network struct { type Process struct { exec.Cmd - ID string - Privileged bool - User string - Rootfs string // root fs of the container - InitPath string // dockerinit - Entrypoint string - Arguments []string - WorkingDir string - ConfigPath string - Tty bool - Network *Network // if network is nil then networking is disabled - SysInitPath string + ID string + Privileged bool + User string + Rootfs string // root fs of the container + InitPath string // dockerinit + Entrypoint string + Arguments []string + WorkingDir string + ConfigPath string + Tty bool + Network *Network // if network is nil then networking is disabled } func (c *Process) Pid() int { diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 5ab6f8b824..b220ff146e 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -256,6 +256,7 @@ func linkLxcStart(root string) error { return os.Symlink(sourcePath, targetPath) } +// TODO: This can be moved to the mountinfo reader in the mount pkg func rootIsShared() bool { if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { for _, line := range strings.Split(string(data), "\n") { From 8a38ead21993dd500280ae427895363a2de8be8a Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 13 Jan 2014 15:37:17 -0800 Subject: [PATCH 094/364] Move setup pty and standard pipes to funcs Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/container.go b/container.go index 424c9d5f1f..4e66539c5c 100644 --- a/container.go +++ b/container.go @@ -308,10 +308,10 @@ func (container *Container) generateLXCConfig() error { return LxcTemplateCompiled.Execute(fo, container) } -func (container *Container) startPty(startCallback execdriver.StartCallback) (int, error) { +func (container *Container) setupPty() error { ptyMaster, ptySlave, err := pty.Open() if err != nil { - return -1, err + return err } container.ptyMaster = ptyMaster container.process.Stdout = ptySlave @@ -336,17 +336,16 @@ func (container *Container) startPty(startCallback execdriver.StartCallback) (in utils.Debugf("startPty: end of stdin pipe") }() } - - return container.runtime.Run(container, startCallback) + return nil } -func (container *Container) start(startCallback execdriver.StartCallback) (int, error) { +func (container *Container) setupStd() error { container.process.Stdout = container.stdout container.process.Stderr = container.stderr if container.Config.OpenStdin { stdin, err := container.process.StdinPipe() if err != nil { - return -1, err + return err } go func() { defer stdin.Close() @@ -355,7 +354,7 @@ func (container *Container) start(startCallback execdriver.StartCallback) (int, utils.Debugf("start: end of stdin pipe") }() } - return container.runtime.Run(container, startCallback) + return nil } func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, stdout io.Writer, stderr io.Writer) chan error { @@ -697,12 +696,24 @@ func (container *Container) Start() (err error) { return err } container.waitLock = make(chan struct{}) - container.State.SetRunning(0) + + // Setup pipes + if container.Config.Tty { + err = container.setupPty() + } else { + err = container.setupStd() + } + if err != nil { + return err + } waitLock := make(chan struct{}) f := func(process *execdriver.Process) { container.State.SetRunning(process.Pid()) if process.Tty { + // The callback is called after the process Start() + // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlace + // which we close here. if c, ok := process.Stdout.(io.Closer); ok { c.Close() } @@ -1091,7 +1102,7 @@ func (container *Container) releaseNetwork() { container.NetworkSettings = &NetworkSettings{} } -func (container *Container) monitor(f execdriver.StartCallback) { +func (container *Container) monitor(callback execdriver.StartCallback) { var ( err error exitCode int @@ -1101,11 +1112,7 @@ func (container *Container) monitor(f execdriver.StartCallback) { // This happends when you have a GHOST container with lxc err = container.runtime.Wait(container, 0) } else { - if container.Config.Tty { - exitCode, err = container.startPty(f) - } else { - exitCode, err = container.start(f) - } + exitCode, err = container.runtime.Run(container, callback) } if err != nil { //TODO: @crosbymichael @creack report error From f3f2456b0451d93a539eac63dac93a748e01317f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 13 Jan 2014 16:10:23 -0800 Subject: [PATCH 095/364] Simplify chroot wait, address code review issues Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 6 ++++-- execdriver/chroot/driver.go | 22 +++++----------------- execdriver/driver.go | 5 ++++- execdriver/lxc/driver.go | 7 ++++--- 4 files changed, 17 insertions(+), 23 deletions(-) diff --git a/container.go b/container.go index 4e66539c5c..e8277131cf 100644 --- a/container.go +++ b/container.go @@ -708,7 +708,7 @@ func (container *Container) Start() (err error) { } waitLock := make(chan struct{}) - f := func(process *execdriver.Process) { + callback := func(process *execdriver.Process) { container.State.SetRunning(process.Pid()) if process.Tty { // The callback is called after the process Start() @@ -724,7 +724,9 @@ func (container *Container) Start() (err error) { close(waitLock) } - go container.monitor(f) + // We use a callback here instead of a goroutine and an chan for + // syncronization purposes + go container.monitor(callback) // Start should not return until the process is actually running <-waitLock diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index eb2525be6d..e5dc69131c 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -9,11 +9,11 @@ import ( type driver struct { } -func NewDriver() (execdriver.Driver, error) { +func NewDriver() (*driver, error) { return &driver{}, nil } -func (d *driver) String() string { +func (d *driver) Name() string { return "chroot" } @@ -23,7 +23,7 @@ func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallba c.Rootfs, "/.dockerinit", "-driver", - d.String(), + d.Name(), } params = append(params, c.Entrypoint) params = append(params, c.Arguments...) @@ -43,24 +43,12 @@ func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallba return -1, err } - var ( - waitErr error - waitLock = make(chan struct{}) - ) - go func() { - if err := c.Wait(); err != nil { - waitErr = err - } - close(waitLock) - }() - if startCallback != nil { startCallback(c) } - <-waitLock - - return c.GetExitCode(), waitErr + err = c.Wait() + return c.GetExitCode(), err } func (d *driver) Kill(p *execdriver.Process, sig int) error { diff --git a/execdriver/driver.go b/execdriver/driver.go index cb9df96e79..fac4e27704 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -14,7 +14,7 @@ type Driver interface { // TODO: @crosbymichael @creack wait should probably return the exit code Wait(id string, duration time.Duration) error // Wait on an out of process...process - lxc ghosts Version() string - String() string + Name() string } // Network settings of the container @@ -43,6 +43,9 @@ type Process struct { } func (c *Process) Pid() int { + if c.Process == nil { + return -1 + } return c.Process.Pid } diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index b220ff146e..e37e105b5b 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -29,7 +29,7 @@ type driver struct { sharedRoot bool } -func NewDriver(root string, apparmor bool) (execdriver.Driver, error) { +func NewDriver(root string, apparmor bool) (*driver, error) { // setup unconfined symlink if err := linkLxcStart(root); err != nil { return nil, err @@ -41,7 +41,7 @@ func NewDriver(root string, apparmor bool) (execdriver.Driver, error) { }, nil } -func (d *driver) String() string { +func (d *driver) Name() string { return "lxc" } @@ -53,7 +53,7 @@ func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallba "--", c.InitPath, "-driver", - d.String(), + d.Name(), } if c.Network != nil { @@ -195,6 +195,7 @@ func (d *driver) waitForStart(c *execdriver.Process, waitLock chan struct{}) err select { case <-waitLock: // If the process dies while waiting for it, just return + return nil if c.ProcessState != nil && c.ProcessState.Exited() { return nil } From e765c67b477308643c8b1b4f84bee3572bf5ec98 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 13 Jan 2014 16:18:46 -0800 Subject: [PATCH 096/364] Add json tags and comments to exedriver types Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- execdriver/driver.go | 40 +++++++++++++++++++++++++--------------- execdriver/lxc/driver.go | 16 +++------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/execdriver/driver.go b/execdriver/driver.go index fac4e27704..8f566324ae 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -1,11 +1,17 @@ package execdriver import ( + "errors" "os/exec" "syscall" "time" ) +var ( + ErrNotRunning = errors.New("Process could not be started") + ErrWaitTimeoutReached = errors.New("Wait timeout reached") +) + type StartCallback func(*Process) type Driver interface { @@ -19,29 +25,31 @@ type Driver interface { // Network settings of the container type Network struct { - Gateway string - IPAddress string - IPPrefixLen int - Mtu int + Gateway string `json:"gateway"` + IPAddress string `json:"ip"` + IPPrefixLen int `json:"ip_prefix_len"` + Mtu int `json:"mtu"` } // Process wrapps an os/exec.Cmd to add more metadata type Process struct { exec.Cmd - ID string - Privileged bool - User string - Rootfs string // root fs of the container - InitPath string // dockerinit - Entrypoint string - Arguments []string - WorkingDir string - ConfigPath string - Tty bool - Network *Network // if network is nil then networking is disabled + ID string `json:"id"` + Privileged bool `json:"privileged"` + User string `json:"user"` + Rootfs string `json:"rootfs"` // root fs of the container + InitPath string `json:"initpath"` // dockerinit + Entrypoint string `json:"entrypoint"` + Arguments []string `json:"arguments"` + WorkingDir string `json:"working_dir"` + ConfigPath string `json:"config_path"` // This should be able to be removed when the lxc template is moved into the driver + Tty bool `json:"tty"` + Network *Network `json:"network"` // if network is nil then networking is disabled } +// Return the pid of the process +// If the process is nil -1 will be returned func (c *Process) Pid() int { if c.Process == nil { return -1 @@ -49,6 +57,8 @@ func (c *Process) Pid() int { return c.Process.Pid } +// Return the exit code of the process +// if the process has not exited -1 will be returned func (c *Process) GetExitCode() int { if c.ProcessState == nil { return -1 diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index e37e105b5b..3b4d385cac 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -1,7 +1,6 @@ package lxc import ( - "errors" "fmt" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/utils" @@ -14,15 +13,6 @@ import ( "time" ) -const ( - startPath = "lxc-start" -) - -var ( - ErrNotRunning = errors.New("Process could not be started") - ErrWaitTimeoutReached = errors.New("Wait timeout reached") -) - type driver struct { root string // root path for the driver to use apparmor bool @@ -47,7 +37,7 @@ func (d *driver) Name() string { func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallback) (int, error) { params := []string{ - startPath, + "lxc-start", "-n", c.ID, "-f", c.ConfigPath, "--", @@ -155,7 +145,7 @@ func (d *driver) Wait(id string, duration time.Duration) error { return err case <-time.After(duration): killer = true - return ErrWaitTimeoutReached + return execdriver.ErrWaitTimeoutReached } } else { return <-done @@ -214,7 +204,7 @@ func (d *driver) waitForStart(c *execdriver.Process, waitLock chan struct{}) err } time.Sleep(50 * time.Millisecond) } - return ErrNotRunning + return execdriver.ErrNotRunning } func (d *driver) waitLxc(id string, kill *bool) <-chan error { From c2b602b2ac7b5b3a67dbb7945e97a9f48b1a625e Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 13 Jan 2014 16:41:35 -0800 Subject: [PATCH 097/364] Use utils.Go to get error from monitor Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/container.go b/container.go index e8277131cf..d8ff8488d0 100644 --- a/container.go +++ b/container.go @@ -726,11 +726,14 @@ func (container *Container) Start() (err error) { // We use a callback here instead of a goroutine and an chan for // syncronization purposes - go container.monitor(callback) + cErr := utils.Go(func() error { return container.monitor(callback) }) // Start should not return until the process is actually running - <-waitLock - + select { + case <-waitLock: + case err := <-cErr: + return err + } return nil } @@ -1104,7 +1107,7 @@ func (container *Container) releaseNetwork() { container.NetworkSettings = &NetworkSettings{} } -func (container *Container) monitor(callback execdriver.StartCallback) { +func (container *Container) monitor(callback execdriver.StartCallback) error { var ( err error exitCode int @@ -1117,10 +1120,7 @@ func (container *Container) monitor(callback execdriver.StartCallback) { exitCode, err = container.runtime.Run(container, callback) } - if err != nil { //TODO: @crosbymichael @creack report error - // Since non-zero exit status and signal terminations will cause err to be non-nil, - // we have to actually discard it. Still, log it anyway, just in case. - utils.Debugf("monitor: cmd.Wait reported exit status %s for container %s", err, container.ID) + if err != nil { if container.runtime != nil && container.runtime.srv != nil { container.runtime.srv.LogEvent("die", container.ID, container.runtime.repositories.ImageName(container.Image)) } @@ -1138,15 +1138,16 @@ func (container *Container) monitor(callback execdriver.StartCallback) { close(container.waitLock) - if err := container.ToDisk(); err != nil { - // FIXME: there is a race condition here which causes this to fail during the unit tests. - // If another goroutine was waiting for Wait() to return before removing the container's root - // from the filesystem... At this point it may already have done so. - // This is because State.setStopped() has already been called, and has caused Wait() - // to return. - // FIXME: why are we serializing running state to disk in the first place? - //log.Printf("%s: Failed to dump configuration to the disk: %s", container.ID, err) - } + // FIXME: there is a race condition here which causes this to fail during the unit tests. + // If another goroutine was waiting for Wait() to return before removing the container's root + // from the filesystem... At this point it may already have done so. + // This is because State.setStopped() has already been called, and has caused Wait() + // to return. + // FIXME: why are we serializing running state to disk in the first place? + //log.Printf("%s: Failed to dump configuration to the disk: %s", container.ID, err) + container.ToDisk() + + return err } func (container *Container) cleanup() { From 8c9f62d037a1bc82742ea316adaaf658af56b7c3 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 13 Jan 2014 17:17:59 -0800 Subject: [PATCH 098/364] Improve wait for lxc and driver interface Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 2 +- execdriver/chroot/driver.go | 5 ++--- execdriver/driver.go | 3 +-- execdriver/lxc/driver.go | 42 +++++++------------------------------ runtime.go | 4 ++-- 5 files changed, 14 insertions(+), 42 deletions(-) diff --git a/container.go b/container.go index d8ff8488d0..702d94e391 100644 --- a/container.go +++ b/container.go @@ -1115,7 +1115,7 @@ func (container *Container) monitor(callback execdriver.StartCallback) error { if container.process == nil { // This happends when you have a GHOST container with lxc - err = container.runtime.Wait(container, 0) + err = container.runtime.WaitGhost(container) } else { exitCode, err = container.runtime.Run(container, callback) } diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index e5dc69131c..6244d2a93f 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -3,7 +3,6 @@ package chroot import ( "github.com/dotcloud/docker/execdriver" "os/exec" - "time" ) type driver struct { @@ -55,8 +54,8 @@ func (d *driver) Kill(p *execdriver.Process, sig int) error { return p.Process.Kill() } -func (d *driver) Wait(id string, duration time.Duration) error { - panic("No Implemented") +func (d *driver) Wait(id string) error { + panic("Not Implemented") } func (d *driver) Version() string { diff --git a/execdriver/driver.go b/execdriver/driver.go index 8f566324ae..d68123d9ba 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -4,7 +4,6 @@ import ( "errors" "os/exec" "syscall" - "time" ) var ( @@ -18,7 +17,7 @@ type Driver interface { Run(c *Process, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code Kill(c *Process, sig int) error // TODO: @crosbymichael @creack wait should probably return the exit code - Wait(id string, duration time.Duration) error // Wait on an out of process...process - lxc ghosts + Wait(id string) error // Wait on an out of process...process - lxc ghosts Version() string Name() string } diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 3b4d385cac..3939841320 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -133,24 +133,17 @@ func (d *driver) Kill(c *execdriver.Process, sig int) error { return d.kill(c, sig) } -func (d *driver) Wait(id string, duration time.Duration) error { - var ( - killer bool - done = d.waitLxc(id, &killer) - ) - - if duration > 0 { - select { - case err := <-done: +func (d *driver) Wait(id string) error { + for { + output, err := exec.Command("lxc-info", "-n", id).CombinedOutput() + if err != nil { return err - case <-time.After(duration): - killer = true - return execdriver.ErrWaitTimeoutReached } - } else { - return <-done + if !strings.Contains(string(output), "RUNNING") { + return nil + } + time.Sleep(500 * time.Millisecond) } - return nil } func (d *driver) Version() string { @@ -207,25 +200,6 @@ func (d *driver) waitForStart(c *execdriver.Process, waitLock chan struct{}) err return execdriver.ErrNotRunning } -func (d *driver) waitLxc(id string, kill *bool) <-chan error { - done := make(chan error) - go func() { - for *kill { - output, err := exec.Command("lxc-info", "-n", id).CombinedOutput() - if err != nil { - done <- err - return - } - if !strings.Contains(string(output), "RUNNING") { - done <- err - return - } - time.Sleep(500 * time.Millisecond) - } - }() - return done -} - func (d *driver) getInfo(c *execdriver.Process) ([]byte, error) { return exec.Command("lxc-info", "-s", "-n", c.ID).CombinedOutput() } diff --git a/runtime.go b/runtime.go index ec32f75456..8b7d5d3494 100644 --- a/runtime.go +++ b/runtime.go @@ -849,8 +849,8 @@ func (runtime *Runtime) Kill(c *Container, sig int) error { return runtime.execDriver.Kill(c.process, sig) } -func (runtime *Runtime) Wait(c *Container, duration time.Duration) error { - return runtime.execDriver.Wait(c.ID, duration) +func (runtime *Runtime) WaitGhost(c *Container) error { + return runtime.execDriver.Wait(c.ID) } // Nuke kills all containers then removes all content From d3bae131d6fa10dd1c25077204bf2c3274a3b8cc Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 13 Jan 2014 17:55:16 -0800 Subject: [PATCH 099/364] Make sure proc is umounted for chroot driver within init Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- sysinit/sysinit.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/sysinit/sysinit.go b/sysinit/sysinit.go index 73349379fb..6701717fe3 100644 --- a/sysinit/sysinit.go +++ b/sysinit/sysinit.go @@ -208,11 +208,10 @@ func executeProgram(args *DockerInitArgs) error { return err } } else if args.driver == "chroot" { - // TODO: @crosbymichael @creack how do we unmount this after the - // process exists? if err := setupMounts(args); err != nil { return err } + defer mount.ForceUnmount("proc") } path, err := exec.LookPath(args.args[0]) @@ -221,11 +220,22 @@ func executeProgram(args *DockerInitArgs) error { os.Exit(127) } - if err := syscall.Exec(path, args.args, os.Environ()); err != nil { - return fmt.Errorf("dockerinit unable to execute %s - %s", path, err) - } + if args.driver == "lxc" { + if err := syscall.Exec(path, args.args, os.Environ()); err != nil { + panic(err) + } + // Will never reach + } else if args.driver == "chroot" { + cmd := exec.Command(path, args.args[1:]...) + + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + + return cmd.Run() + } + panic("Should not be here") - // Will never reach here return nil } From f7684ea7f61c0c69033c27605e9ad9a0a76e74cd Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 13 Jan 2014 18:36:59 -0800 Subject: [PATCH 100/364] Move docker init into drivers functions Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: crosbymichael) --- execdriver/chroot/driver.go | 22 +- execdriver/driver.go | 44 +++- execdriver/lxc/driver.go | 38 ++- execdriver/lxc/init.go | 153 +++++++++++ .../lxc/lxc_init_darwin.go | 2 +- .../lxc/lxc_init_linux.go | 2 +- sysinit/sysinit.go | 238 ++---------------- 7 files changed, 273 insertions(+), 226 deletions(-) create mode 100644 execdriver/lxc/init.go rename sysinit/sysinit_darwin.go => execdriver/lxc/lxc_init_darwin.go (83%) rename sysinit/sysinit_linux.go => execdriver/lxc/lxc_init_linux.go (87%) diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index 6244d2a93f..1f98bba0ac 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -2,9 +2,29 @@ package chroot import ( "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/mount" + "os" "os/exec" ) +const DriverName = "chroot" + +func init() { + execdriver.RegisterDockerInitFct(DriverName, func(args *execdriver.DockerInitArgs) error { + if err := mount.ForceMount("proc", "proc", "proc", ""); err != nil { + return err + } + defer mount.ForceUnmount("proc") + cmd := exec.Command(args.Args[0], args.Args[1:]...) + + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + + return cmd.Run() + }) +} + type driver struct { } @@ -13,7 +33,7 @@ func NewDriver() (*driver, error) { } func (d *driver) Name() string { - return "chroot" + return DriverName } func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallback) (int, error) { diff --git a/execdriver/driver.go b/execdriver/driver.go index d68123d9ba..ca0a2991f5 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -7,11 +7,49 @@ import ( ) var ( - ErrNotRunning = errors.New("Process could not be started") - ErrWaitTimeoutReached = errors.New("Wait timeout reached") + ErrNotRunning = errors.New("Process could not be started") + ErrWaitTimeoutReached = errors.New("Wait timeout reached") + ErrDriverAlreadyRegistered = errors.New("A driver already registered this docker init function") + ErrDriverNotFound = errors.New("The requested docker init has not been found") ) -type StartCallback func(*Process) +var dockerInitFcts map[string]DockerInitFct + +type ( + StartCallback func(*Process) + DockerInitFct func(i *DockerInitArgs) error +) + +func RegisterDockerInitFct(name string, fct DockerInitFct) error { + if dockerInitFcts == nil { + dockerInitFcts = make(map[string]DockerInitFct) + } + if _, ok := dockerInitFcts[name]; ok { + return ErrDriverAlreadyRegistered + } + dockerInitFcts[name] = fct + return nil +} + +func GetDockerInitFct(name string) (DockerInitFct, error) { + fct, ok := dockerInitFcts[name] + if !ok { + return nil, ErrDriverNotFound + } + return fct, nil +} + +type DockerInitArgs struct { + User string + Gateway string + Ip string + WorkDir string + Privileged bool + Env []string + Args []string + Mtu int + Driver string +} type Driver interface { Run(c *Process, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 3939841320..61fc99fae0 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -5,14 +5,50 @@ import ( "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/utils" "io/ioutil" + "log" "os" "os/exec" "path" "strconv" "strings" + "syscall" "time" ) +const DriverName = "lxc" + +func init() { + execdriver.RegisterDockerInitFct(DriverName, func(args *execdriver.DockerInitArgs) error { + if err := setupHostname(args); err != nil { + return err + } + + if err := setupNetworking(args); err != nil { + return err + } + + if err := setupCapabilities(args); err != nil { + return err + } + if err := setupWorkingDirectory(args); err != nil { + return err + } + + if err := changeUser(args); err != nil { + return err + } + path, err := exec.LookPath(args.Args[0]) + if err != nil { + log.Printf("Unable to locate %v", args.Args[0]) + os.Exit(127) + } + if err := syscall.Exec(path, args.Args, os.Environ()); err != nil { + panic(err) + } + panic("Unreachable") + }) +} + type driver struct { root string // root path for the driver to use apparmor bool @@ -32,7 +68,7 @@ func NewDriver(root string, apparmor bool) (*driver, error) { } func (d *driver) Name() string { - return "lxc" + return DriverName } func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallback) (int, error) { diff --git a/execdriver/lxc/init.go b/execdriver/lxc/init.go new file mode 100644 index 0000000000..dec33e3ad6 --- /dev/null +++ b/execdriver/lxc/init.go @@ -0,0 +1,153 @@ +package lxc + +import ( + "fmt" + "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/pkg/netlink" + "github.com/dotcloud/docker/utils" + "github.com/syndtr/gocapability/capability" + "net" + "os" + "strconv" + "strings" + "syscall" +) + +func setupHostname(args *execdriver.DockerInitArgs) error { + hostname := getEnv(args, "HOSTNAME") + if hostname == "" { + return nil + } + return setHostname(hostname) +} + +// Setup networking +func setupNetworking(args *execdriver.DockerInitArgs) error { + if args.Ip != "" { + // eth0 + iface, err := net.InterfaceByName("eth0") + if err != nil { + return fmt.Errorf("Unable to set up networking: %v", err) + } + ip, ipNet, err := net.ParseCIDR(args.Ip) + if err != nil { + return fmt.Errorf("Unable to set up networking: %v", err) + } + if err := netlink.NetworkLinkAddIp(iface, ip, ipNet); err != nil { + return fmt.Errorf("Unable to set up networking: %v", err) + } + if err := netlink.NetworkSetMTU(iface, args.Mtu); err != nil { + return fmt.Errorf("Unable to set MTU: %v", err) + } + if err := netlink.NetworkLinkUp(iface); err != nil { + return fmt.Errorf("Unable to set up networking: %v", err) + } + + // loopback + iface, err = net.InterfaceByName("lo") + if err != nil { + return fmt.Errorf("Unable to set up networking: %v", err) + } + if err := netlink.NetworkLinkUp(iface); err != nil { + return fmt.Errorf("Unable to set up networking: %v", err) + } + } + if args.Gateway != "" { + gw := net.ParseIP(args.Gateway) + if gw == nil { + return fmt.Errorf("Unable to set up networking, %s is not a valid gateway IP", args.Gateway) + } + + if err := netlink.AddDefaultGw(gw); err != nil { + return fmt.Errorf("Unable to set up networking: %v", err) + } + } + + return nil +} + +// Setup working directory +func setupWorkingDirectory(args *execdriver.DockerInitArgs) error { + if args.WorkDir == "" { + return nil + } + if err := syscall.Chdir(args.WorkDir); err != nil { + return fmt.Errorf("Unable to change dir to %v: %v", args.WorkDir, err) + } + return nil +} + +// Takes care of dropping privileges to the desired user +func changeUser(args *execdriver.DockerInitArgs) error { + if args.User == "" { + return nil + } + userent, err := utils.UserLookup(args.User) + if err != nil { + return fmt.Errorf("Unable to find user %v: %v", args.User, err) + } + + uid, err := strconv.Atoi(userent.Uid) + if err != nil { + return fmt.Errorf("Invalid uid: %v", userent.Uid) + } + gid, err := strconv.Atoi(userent.Gid) + if err != nil { + return fmt.Errorf("Invalid gid: %v", userent.Gid) + } + + if err := syscall.Setgid(gid); err != nil { + return fmt.Errorf("setgid failed: %v", err) + } + if err := syscall.Setuid(uid); err != nil { + return fmt.Errorf("setuid failed: %v", err) + } + + return nil +} + +func setupCapabilities(args *execdriver.DockerInitArgs) error { + + if args.Privileged { + return nil + } + + drop := []capability.Cap{ + capability.CAP_SETPCAP, + capability.CAP_SYS_MODULE, + capability.CAP_SYS_RAWIO, + capability.CAP_SYS_PACCT, + capability.CAP_SYS_ADMIN, + capability.CAP_SYS_NICE, + capability.CAP_SYS_RESOURCE, + capability.CAP_SYS_TIME, + capability.CAP_SYS_TTY_CONFIG, + capability.CAP_MKNOD, + capability.CAP_AUDIT_WRITE, + capability.CAP_AUDIT_CONTROL, + capability.CAP_MAC_OVERRIDE, + capability.CAP_MAC_ADMIN, + } + + c, err := capability.NewPid(os.Getpid()) + if err != nil { + return err + } + + c.Unset(capability.CAPS|capability.BOUNDS, drop...) + + if err := c.Apply(capability.CAPS | capability.BOUNDS); err != nil { + return err + } + return nil +} + +func getEnv(args *execdriver.DockerInitArgs, key string) string { + for _, kv := range args.Env { + parts := strings.SplitN(kv, "=", 2) + if parts[0] == key && len(parts) == 2 { + return parts[1] + } + } + return "" +} diff --git a/sysinit/sysinit_darwin.go b/execdriver/lxc/lxc_init_darwin.go similarity index 83% rename from sysinit/sysinit_darwin.go rename to execdriver/lxc/lxc_init_darwin.go index 64566afb3c..c066fead93 100644 --- a/sysinit/sysinit_darwin.go +++ b/execdriver/lxc/lxc_init_darwin.go @@ -1,4 +1,4 @@ -package sysinit +package lxc func setHostname(hostname string) error { panic("Not supported on darwin") diff --git a/sysinit/sysinit_linux.go b/execdriver/lxc/lxc_init_linux.go similarity index 87% rename from sysinit/sysinit_linux.go rename to execdriver/lxc/lxc_init_linux.go index d18d2fab8b..b0055c3668 100644 --- a/sysinit/sysinit_linux.go +++ b/execdriver/lxc/lxc_init_linux.go @@ -1,4 +1,4 @@ -package sysinit +package lxc import ( "syscall" diff --git a/sysinit/sysinit.go b/sysinit/sysinit.go index 6701717fe3..96339018ce 100644 --- a/sysinit/sysinit.go +++ b/sysinit/sysinit.go @@ -4,169 +4,17 @@ import ( "encoding/json" "flag" "fmt" - "github.com/dotcloud/docker/mount" - "github.com/dotcloud/docker/pkg/netlink" - "github.com/dotcloud/docker/utils" - "github.com/syndtr/gocapability/capability" + "github.com/dotcloud/docker/execdriver" "io/ioutil" "log" - "net" "os" - "os/exec" - "strconv" "strings" - "syscall" ) -type DockerInitArgs struct { - user string - gateway string - ip string - workDir string - privileged bool - env []string - args []string - mtu int - driver string -} - -func setupHostname(args *DockerInitArgs) error { - hostname := getEnv(args, "HOSTNAME") - if hostname == "" { - return nil - } - return setHostname(hostname) -} - -// Setup networking -func setupNetworking(args *DockerInitArgs) error { - if args.ip != "" { - // eth0 - iface, err := net.InterfaceByName("eth0") - if err != nil { - return fmt.Errorf("Unable to set up networking: %v", err) - } - ip, ipNet, err := net.ParseCIDR(args.ip) - if err != nil { - return fmt.Errorf("Unable to set up networking: %v", err) - } - if err := netlink.NetworkLinkAddIp(iface, ip, ipNet); err != nil { - return fmt.Errorf("Unable to set up networking: %v", err) - } - if err := netlink.NetworkSetMTU(iface, args.mtu); err != nil { - return fmt.Errorf("Unable to set MTU: %v", err) - } - if err := netlink.NetworkLinkUp(iface); err != nil { - return fmt.Errorf("Unable to set up networking: %v", err) - } - - // loopback - iface, err = net.InterfaceByName("lo") - if err != nil { - return fmt.Errorf("Unable to set up networking: %v", err) - } - if err := netlink.NetworkLinkUp(iface); err != nil { - return fmt.Errorf("Unable to set up networking: %v", err) - } - } - if args.gateway != "" { - gw := net.ParseIP(args.gateway) - if gw == nil { - return fmt.Errorf("Unable to set up networking, %s is not a valid gateway IP", args.gateway) - } - - if err := netlink.AddDefaultGw(gw); err != nil { - return fmt.Errorf("Unable to set up networking: %v", err) - } - } - - return nil -} - -// Setup working directory -func setupWorkingDirectory(args *DockerInitArgs) error { - if args.workDir == "" { - return nil - } - if err := syscall.Chdir(args.workDir); err != nil { - return fmt.Errorf("Unable to change dir to %v: %v", args.workDir, err) - } - return nil -} - -func setupMounts(args *DockerInitArgs) error { - return mount.ForceMount("proc", "proc", "proc", "") -} - -// Takes care of dropping privileges to the desired user -func changeUser(args *DockerInitArgs) error { - if args.user == "" { - return nil - } - userent, err := utils.UserLookup(args.user) - if err != nil { - return fmt.Errorf("Unable to find user %v: %v", args.user, err) - } - - uid, err := strconv.Atoi(userent.Uid) - if err != nil { - return fmt.Errorf("Invalid uid: %v", userent.Uid) - } - gid, err := strconv.Atoi(userent.Gid) - if err != nil { - return fmt.Errorf("Invalid gid: %v", userent.Gid) - } - - if err := syscall.Setgid(gid); err != nil { - return fmt.Errorf("setgid failed: %v", err) - } - if err := syscall.Setuid(uid); err != nil { - return fmt.Errorf("setuid failed: %v", err) - } - - return nil -} - -func setupCapabilities(args *DockerInitArgs) error { - - if args.privileged { - return nil - } - - drop := []capability.Cap{ - capability.CAP_SETPCAP, - capability.CAP_SYS_MODULE, - capability.CAP_SYS_RAWIO, - capability.CAP_SYS_PACCT, - capability.CAP_SYS_ADMIN, - capability.CAP_SYS_NICE, - capability.CAP_SYS_RESOURCE, - capability.CAP_SYS_TIME, - capability.CAP_SYS_TTY_CONFIG, - capability.CAP_MKNOD, - capability.CAP_AUDIT_WRITE, - capability.CAP_AUDIT_CONTROL, - capability.CAP_MAC_OVERRIDE, - capability.CAP_MAC_ADMIN, - } - - c, err := capability.NewPid(os.Getpid()) - if err != nil { - return err - } - - c.Unset(capability.CAPS|capability.BOUNDS, drop...) - - if err := c.Apply(capability.CAPS | capability.BOUNDS); err != nil { - return err - } - return nil -} - // Clear environment pollution introduced by lxc-start -func setupEnv(args *DockerInitArgs) { +func setupEnv(args *execdriver.DockerInitArgs) { os.Clearenv() - for _, kv := range args.env { + for _, kv := range args.Env { parts := strings.SplitN(kv, "=", 2) if len(parts) == 1 { parts = append(parts, "") @@ -175,66 +23,18 @@ func setupEnv(args *DockerInitArgs) { } } -func getEnv(args *DockerInitArgs, key string) string { - for _, kv := range args.env { - parts := strings.SplitN(kv, "=", 2) - if parts[0] == key && len(parts) == 2 { - return parts[1] - } - } - return "" -} - -func executeProgram(args *DockerInitArgs) error { +func executeProgram(args *execdriver.DockerInitArgs) error { setupEnv(args) - - if args.driver == "lxc" { - if err := setupHostname(args); err != nil { - return err - } - - if err := setupNetworking(args); err != nil { - return err - } - - if err := setupCapabilities(args); err != nil { - return err - } - if err := setupWorkingDirectory(args); err != nil { - return err - } - - if err := changeUser(args); err != nil { - return err - } - } else if args.driver == "chroot" { - if err := setupMounts(args); err != nil { - return err - } - defer mount.ForceUnmount("proc") - } - - path, err := exec.LookPath(args.args[0]) + dockerInitFct, err := execdriver.GetDockerInitFct(args.Driver) if err != nil { - log.Printf("Unable to locate %v", args.args[0]) - os.Exit(127) + panic(err) } + return dockerInitFct(args) - if args.driver == "lxc" { - if err := syscall.Exec(path, args.args, os.Environ()); err != nil { - panic(err) - } + if args.Driver == "lxc" { // Will never reach - } else if args.driver == "chroot" { - cmd := exec.Command(path, args.args[1:]...) - - cmd.Stderr = os.Stderr - cmd.Stdout = os.Stdout - cmd.Stdin = os.Stdin - - return cmd.Run() + } else if args.Driver == "chroot" { } - panic("Should not be here") return nil } @@ -271,16 +71,16 @@ func SysInit() { // Propagate the plugin-specific container env variable env = append(env, "container="+os.Getenv("container")) - args := &DockerInitArgs{ - user: *user, - gateway: *gateway, - ip: *ip, - workDir: *workDir, - privileged: *privileged, - env: env, - args: flag.Args(), - mtu: *mtu, - driver: *driver, + args := &execdriver.DockerInitArgs{ + User: *user, + Gateway: *gateway, + Ip: *ip, + WorkDir: *workDir, + Privileged: *privileged, + Env: env, + Args: flag.Args(), + Mtu: *mtu, + Driver: *driver, } if err := executeProgram(args); err != nil { From 889b4b10ae3ec1d6e7879c30860aafd7674cb576 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 15 Jan 2014 11:46:25 -0800 Subject: [PATCH 101/364] Cleanup + add Info to driver in order to have specific IsRunning() Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: crosbymichael) --- container.go | 15 ++++++++------- execdriver/chroot/driver.go | 21 ++++++++++++++------- execdriver/driver.go | 6 ++++++ execdriver/lxc/driver.go | 34 ++++++++++++++++++++++++++++++---- runtime.go | 28 +++++++++++++++++----------- 5 files changed, 75 insertions(+), 29 deletions(-) diff --git a/container.go b/container.go index 702d94e391..257925fdda 100644 --- a/container.go +++ b/container.go @@ -697,17 +697,18 @@ func (container *Container) Start() (err error) { } container.waitLock = make(chan struct{}) - // Setup pipes + // Setuping pipes and/or Pty + var setup func() error if container.Config.Tty { - err = container.setupPty() + setup = container.setupPty } else { - err = container.setupStd() + setup = container.setupStd } - if err != nil { + if err := setup(); err != nil { return err } - waitLock := make(chan struct{}) + callbackLock := make(chan struct{}) callback := func(process *execdriver.Process) { container.State.SetRunning(process.Pid()) if process.Tty { @@ -721,7 +722,7 @@ func (container *Container) Start() (err error) { if err := container.ToDisk(); err != nil { utils.Debugf("%s", err) } - close(waitLock) + close(callbackLock) } // We use a callback here instead of a goroutine and an chan for @@ -730,7 +731,7 @@ func (container *Container) Start() (err error) { // Start should not return until the process is actually running select { - case <-waitLock: + case <-callbackLock: case err := <-cErr: return err } diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index 1f98bba0ac..be8eb58a1a 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -7,7 +7,10 @@ import ( "os/exec" ) -const DriverName = "chroot" +const ( + DriverName = "chroot" + Version = "0.1" +) func init() { execdriver.RegisterDockerInitFct(DriverName, func(args *execdriver.DockerInitArgs) error { @@ -32,10 +35,6 @@ func NewDriver() (*driver, error) { return &driver{}, nil } -func (d *driver) Name() string { - return DriverName -} - func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallback) (int, error) { params := []string{ "chroot", @@ -78,6 +77,14 @@ func (d *driver) Wait(id string) error { panic("Not Implemented") } -func (d *driver) Version() string { - return "0.1" +func (d *driver) Info(id string) execdriver.Info { + panic("Not implemented") +} + +func (d *driver) Name() string { + return DriverName +} + +func (d *driver) Version() string { + return Version } diff --git a/execdriver/driver.go b/execdriver/driver.go index ca0a2991f5..3de3369dab 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -51,6 +51,10 @@ type DockerInitArgs struct { Driver string } +type Info interface { + IsRunning() bool +} + type Driver interface { Run(c *Process, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code Kill(c *Process, sig int) error @@ -58,6 +62,8 @@ type Driver interface { Wait(id string) error // Wait on an out of process...process - lxc ghosts Version() string Name() string + + Info(id string) Info // "temporary" hack (until we move state from core to plugins) } // Network settings of the container diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 61fc99fae0..1ae136b512 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -221,9 +221,9 @@ func (d *driver) waitForStart(c *execdriver.Process, waitLock chan struct{}) err default: } - output, err = d.getInfo(c) + output, err = d.getInfo(c.ID) if err != nil { - output, err = d.getInfo(c) + output, err = d.getInfo(c.ID) if err != nil { return err } @@ -236,8 +236,34 @@ func (d *driver) waitForStart(c *execdriver.Process, waitLock chan struct{}) err return execdriver.ErrNotRunning } -func (d *driver) getInfo(c *execdriver.Process) ([]byte, error) { - return exec.Command("lxc-info", "-s", "-n", c.ID).CombinedOutput() +func (d *driver) getInfo(id string) ([]byte, error) { + return exec.Command("lxc-info", "-s", "-n", id).CombinedOutput() +} + +type info struct { + ID string + driver *driver +} + +func (i *info) IsRunning() bool { + var running bool + + output, err := i.driver.getInfo(i.ID) + if err != nil { + panic(err) + } + if strings.Contains(string(output), "RUNNING") { + running = true + } + return running +} + +func (d *driver) Info(id string) execdriver.Info { + + return &info{ + ID: id, + driver: d, + } } func linkLxcStart(root string) error { diff --git a/runtime.go b/runtime.go index 8b7d5d3494..5a11faec9f 100644 --- a/runtime.go +++ b/runtime.go @@ -18,7 +18,6 @@ import ( "io/ioutil" "log" "os" - "os/exec" "path" "regexp" "sort" @@ -164,11 +163,9 @@ func (runtime *Runtime) Register(container *Container) error { // if so, then we need to restart monitor and init a new lock // If the container is supposed to be running, make sure of it if container.State.IsRunning() { - output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput() - if err != nil { - return err - } - if !strings.Contains(string(output), "RUNNING") { + info := runtime.execDriver.Info(container.ID) + + if !info.IsRunning() { utils.Debugf("Container %s was supposed to be running but is not.", container.ID) if runtime.config.AutoRestart { utils.Debugf("Restarting") @@ -736,12 +733,21 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { } capabilities := NewRuntimeCapabilities(false) - var ed execdriver.Driver - if driver := os.Getenv("EXEC_DRIVER"); driver == "lxc" { - ed, err = lxc.NewDriver(config.Root, capabilities.AppArmor) - } else { - ed, err = chroot.NewDriver() + + /* + temporarilly disabled. + */ + if false { + var ed execdriver.Driver + if driver := os.Getenv("EXEC_DRIVER"); driver == "lxc" { + ed, err = lxc.NewDriver(config.Root, capabilities.AppArmor) + } else { + ed, err = chroot.NewDriver() + } + if ed != nil { + } } + ed, err := lxc.NewDriver(config.Root, capabilities.AppArmor) if err != nil { return nil, err } From ca8dd73fbfa7aca0768278ff1ff9671f80c48138 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 15 Jan 2014 13:57:07 -0800 Subject: [PATCH 102/364] Small fixes to type names Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- execdriver/chroot/driver.go | 2 +- execdriver/driver.go | 23 ++++++++++++----------- execdriver/lxc/driver.go | 2 +- execdriver/lxc/init.go | 12 ++++++------ sysinit/sysinit.go | 8 ++++---- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index be8eb58a1a..2aa23c9866 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -13,7 +13,7 @@ const ( ) func init() { - execdriver.RegisterDockerInitFct(DriverName, func(args *execdriver.DockerInitArgs) error { + execdriver.RegisterInitFunc(DriverName, func(args *execdriver.InitArgs) error { if err := mount.ForceMount("proc", "proc", "proc", ""); err != nil { return err } diff --git a/execdriver/driver.go b/execdriver/driver.go index 3de3369dab..e4d512f7bc 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -13,16 +13,16 @@ var ( ErrDriverNotFound = errors.New("The requested docker init has not been found") ) -var dockerInitFcts map[string]DockerInitFct +var dockerInitFcts map[string]InitFunc type ( StartCallback func(*Process) - DockerInitFct func(i *DockerInitArgs) error + InitFunc func(i *InitArgs) error ) -func RegisterDockerInitFct(name string, fct DockerInitFct) error { +func RegisterInitFunc(name string, fct InitFunc) error { if dockerInitFcts == nil { - dockerInitFcts = make(map[string]DockerInitFct) + dockerInitFcts = make(map[string]InitFunc) } if _, ok := dockerInitFcts[name]; ok { return ErrDriverAlreadyRegistered @@ -31,7 +31,7 @@ func RegisterDockerInitFct(name string, fct DockerInitFct) error { return nil } -func GetDockerInitFct(name string) (DockerInitFct, error) { +func GetInitFunc(name string) (InitFunc, error) { fct, ok := dockerInitFcts[name] if !ok { return nil, ErrDriverNotFound @@ -39,7 +39,8 @@ func GetDockerInitFct(name string) (DockerInitFct, error) { return fct, nil } -type DockerInitArgs struct { +// Args provided to the init function for a driver +type InitArgs struct { User string Gateway string Ip string @@ -51,6 +52,8 @@ type DockerInitArgs struct { Driver string } +// Driver specific information based on +// processes registered with the driver type Info interface { IsRunning() bool } @@ -58,12 +61,10 @@ type Info interface { type Driver interface { Run(c *Process, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code Kill(c *Process, sig int) error - // TODO: @crosbymichael @creack wait should probably return the exit code Wait(id string) error // Wait on an out of process...process - lxc ghosts - Version() string - Name() string - - Info(id string) Info // "temporary" hack (until we move state from core to plugins) + Version() string // Driver version number + Name() string // Driver name + Info(id string) Info // "temporary" hack (until we move state from core to plugins) } // Network settings of the container diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 1ae136b512..3b4572af96 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -18,7 +18,7 @@ import ( const DriverName = "lxc" func init() { - execdriver.RegisterDockerInitFct(DriverName, func(args *execdriver.DockerInitArgs) error { + execdriver.RegisterInitFunc(DriverName, func(args *execdriver.InitArgs) error { if err := setupHostname(args); err != nil { return err } diff --git a/execdriver/lxc/init.go b/execdriver/lxc/init.go index dec33e3ad6..7c2b039c50 100644 --- a/execdriver/lxc/init.go +++ b/execdriver/lxc/init.go @@ -13,7 +13,7 @@ import ( "syscall" ) -func setupHostname(args *execdriver.DockerInitArgs) error { +func setupHostname(args *execdriver.InitArgs) error { hostname := getEnv(args, "HOSTNAME") if hostname == "" { return nil @@ -22,7 +22,7 @@ func setupHostname(args *execdriver.DockerInitArgs) error { } // Setup networking -func setupNetworking(args *execdriver.DockerInitArgs) error { +func setupNetworking(args *execdriver.InitArgs) error { if args.Ip != "" { // eth0 iface, err := net.InterfaceByName("eth0") @@ -67,7 +67,7 @@ func setupNetworking(args *execdriver.DockerInitArgs) error { } // Setup working directory -func setupWorkingDirectory(args *execdriver.DockerInitArgs) error { +func setupWorkingDirectory(args *execdriver.InitArgs) error { if args.WorkDir == "" { return nil } @@ -78,7 +78,7 @@ func setupWorkingDirectory(args *execdriver.DockerInitArgs) error { } // Takes care of dropping privileges to the desired user -func changeUser(args *execdriver.DockerInitArgs) error { +func changeUser(args *execdriver.InitArgs) error { if args.User == "" { return nil } @@ -106,7 +106,7 @@ func changeUser(args *execdriver.DockerInitArgs) error { return nil } -func setupCapabilities(args *execdriver.DockerInitArgs) error { +func setupCapabilities(args *execdriver.InitArgs) error { if args.Privileged { return nil @@ -142,7 +142,7 @@ func setupCapabilities(args *execdriver.DockerInitArgs) error { return nil } -func getEnv(args *execdriver.DockerInitArgs, key string) string { +func getEnv(args *execdriver.InitArgs, key string) string { for _, kv := range args.Env { parts := strings.SplitN(kv, "=", 2) if parts[0] == key && len(parts) == 2 { diff --git a/sysinit/sysinit.go b/sysinit/sysinit.go index 96339018ce..7e1ac519cd 100644 --- a/sysinit/sysinit.go +++ b/sysinit/sysinit.go @@ -12,7 +12,7 @@ import ( ) // Clear environment pollution introduced by lxc-start -func setupEnv(args *execdriver.DockerInitArgs) { +func setupEnv(args *execdriver.InitArgs) { os.Clearenv() for _, kv := range args.Env { parts := strings.SplitN(kv, "=", 2) @@ -23,9 +23,9 @@ func setupEnv(args *execdriver.DockerInitArgs) { } } -func executeProgram(args *execdriver.DockerInitArgs) error { +func executeProgram(args *execdriver.InitArgs) error { setupEnv(args) - dockerInitFct, err := execdriver.GetDockerInitFct(args.Driver) + dockerInitFct, err := execdriver.GetInitFunc(args.Driver) if err != nil { panic(err) } @@ -71,7 +71,7 @@ func SysInit() { // Propagate the plugin-specific container env variable env = append(env, "container="+os.Getenv("container")) - args := &execdriver.DockerInitArgs{ + args := &execdriver.InitArgs{ User: *user, Gateway: *gateway, Ip: *ip, From 9e9f4b925b4b6a58a03bbff09150f1314d5fe3b2 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 15 Jan 2014 14:36:13 -0800 Subject: [PATCH 103/364] Rename Capabilities in sysinfo and move it to its own subpackage Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: crosbymichael) --- api.go | 6 ++--- commands.go | 11 ++++---- container.go | 6 ++--- lxc_template.go | 11 ++++---- pkg/sysinfo/sysinfo.go | 55 +++++++++++++++++++++++++++++++++++++++ runtime.go | 58 +++++------------------------------------- server.go | 12 ++++----- 7 files changed, 85 insertions(+), 74 deletions(-) create mode 100644 pkg/sysinfo/sysinfo.go diff --git a/api.go b/api.go index cfef7a50ce..83b749b217 100644 --- a/api.go +++ b/api.go @@ -657,16 +657,16 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r for scanner.Scan() { out.Warnings = append(out.Warnings, scanner.Text()) } - if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.MemoryLimit { + if job.GetenvInt("Memory") > 0 && !srv.runtime.sysInfo.MemoryLimit { log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.") out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.") } - if job.GetenvInt("Memory") > 0 && !srv.runtime.capabilities.SwapLimit { + if job.GetenvInt("Memory") > 0 && !srv.runtime.sysInfo.SwapLimit { log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.") out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.") } - if !job.GetenvBool("NetworkDisabled") && srv.runtime.capabilities.IPv4ForwardingDisabled { + if !job.GetenvBool("NetworkDisabled") && srv.runtime.sysInfo.IPv4ForwardingDisabled { log.Println("Warning: IPv4 forwarding is disabled.") out.Warnings = append(out.Warnings, "IPv4 forwarding is disabled.") } diff --git a/commands.go b/commands.go index 09b65ad163..d23c7a9da5 100644 --- a/commands.go +++ b/commands.go @@ -12,6 +12,7 @@ import ( "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/engine" flag "github.com/dotcloud/docker/pkg/mflag" + "github.com/dotcloud/docker/pkg/sysinfo" "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" @@ -1745,14 +1746,14 @@ func (cli *DockerCli) CmdTag(args ...string) error { } //FIXME Only used in tests -func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) { +func ParseRun(args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) { cmd := flag.NewFlagSet("run", flag.ContinueOnError) cmd.SetOutput(ioutil.Discard) cmd.Usage = nil - return parseRun(cmd, args, capabilities) + return parseRun(cmd, args, sysInfo) } -func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) { +func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) { var ( // FIXME: use utils.ListOpts for attach and volumes? flAttach = NewListOpts(ValidateAttach) @@ -1802,7 +1803,7 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co } // Check if the kernel supports memory limit cgroup. - if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit { + if sysInfo != nil && *flMemoryString != "" && !sysInfo.MemoryLimit { *flMemoryString = "" } @@ -1934,7 +1935,7 @@ func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Co PublishAllPorts: *flPublishAll, } - if capabilities != nil && flMemory > 0 && !capabilities.SwapLimit { + if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit { //fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") config.MemorySwap = -1 } diff --git a/container.go b/container.go index 257925fdda..23a3c05335 100644 --- a/container.go +++ b/container.go @@ -527,16 +527,16 @@ func (container *Container) Start() (err error) { } // Make sure the config is compatible with the current kernel - if container.Config.Memory > 0 && !container.runtime.capabilities.MemoryLimit { + if container.Config.Memory > 0 && !container.runtime.sysInfo.MemoryLimit { log.Printf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") container.Config.Memory = 0 } - if container.Config.Memory > 0 && !container.runtime.capabilities.SwapLimit { + if container.Config.Memory > 0 && !container.runtime.sysInfo.SwapLimit { log.Printf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") container.Config.MemorySwap = -1 } - if container.runtime.capabilities.IPv4ForwardingDisabled { + if container.runtime.sysInfo.IPv4ForwardingDisabled { log.Printf("WARNING: IPv4 forwarding is disabled. Networking will not work") } diff --git a/lxc_template.go b/lxc_template.go index f96323b5ea..4e1aece94b 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -1,6 +1,7 @@ package docker import ( + "github.com/dotcloud/docker/pkg/sysinfo" "strings" "text/template" ) @@ -31,7 +32,7 @@ lxc.console = none lxc.tty = 1 {{if (getHostConfig .).Privileged}} -lxc.cgroup.devices.allow = a +lxc.cgroup.devices.allow = a {{else}} # no implicit access to devices lxc.cgroup.devices.deny = a @@ -82,7 +83,7 @@ lxc.mount.entry = devpts {{escapeFstabSpaces $ROOTFS}}/dev/pts devpts newinstanc lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0 {{if (getHostConfig .).Privileged}} -{{if (getCapabilities .).AppArmor}} +{{if (getSysInfo .).AppArmor}} lxc.aa_profile = unconfined {{else}} #lxc.aa_profile = unconfined @@ -129,8 +130,8 @@ func getHostConfig(container *Container) *HostConfig { return container.hostConfig } -func getCapabilities(container *Container) *Capabilities { - return container.runtime.capabilities +func getSysInfo(container *Container) *sysinfo.SysInfo { + return container.runtime.sysInfo } func init() { @@ -138,7 +139,7 @@ func init() { funcMap := template.FuncMap{ "getMemorySwap": getMemorySwap, "getHostConfig": getHostConfig, - "getCapabilities": getCapabilities, + "getSysInfo": getSysInfo, "escapeFstabSpaces": escapeFstabSpaces, } LxcTemplateCompiled, err = template.New("lxc").Funcs(funcMap).Parse(LxcTemplate) diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go new file mode 100644 index 0000000000..884fcbde7c --- /dev/null +++ b/pkg/sysinfo/sysinfo.go @@ -0,0 +1,55 @@ +package sysinfo + +import ( + "github.com/dotcloud/docker/cgroups" + "github.com/dotcloud/docker/utils" + "io/ioutil" + "log" + "os" + "path" +) + +type SysInfo struct { + MemoryLimit bool + SwapLimit bool + IPv4ForwardingDisabled bool + AppArmor bool +} + +func New(quiet bool) *SysInfo { + sysInfo := &SysInfo{} + if cgroupMemoryMountpoint, err := cgroups.FindCgroupMountpoint("memory"); err != nil { + if !quiet { + log.Printf("WARNING: %s\n", err) + } + } else { + _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes")) + _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) + sysInfo.MemoryLimit = err1 == nil && err2 == nil + if !sysInfo.MemoryLimit && !quiet { + log.Printf("WARNING: Your kernel does not support cgroup memory limit.") + } + + _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) + sysInfo.SwapLimit = err == nil + if !sysInfo.SwapLimit && !quiet { + log.Printf("WARNING: Your kernel does not support cgroup swap limit.") + } + } + + content, err3 := ioutil.ReadFile("/proc/sys/net/ipv4/ip_forward") + sysInfo.IPv4ForwardingDisabled = err3 != nil || len(content) == 0 || content[0] != '1' + if sysInfo.IPv4ForwardingDisabled && !quiet { + log.Printf("WARNING: IPv4 forwarding is disabled.") + } + + // Check if AppArmor seems to be enabled on this system. + if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) { + utils.Debugf("/sys/kernel/security/apparmor not found; assuming AppArmor is not enabled.") + sysInfo.AppArmor = false + } else { + utils.Debugf("/sys/kernel/security/apparmor found; assuming AppArmor is enabled.") + sysInfo.AppArmor = true + } + return sysInfo +} diff --git a/runtime.go b/runtime.go index 5a11faec9f..52f03f84be 100644 --- a/runtime.go +++ b/runtime.go @@ -4,7 +4,6 @@ import ( "container/list" "fmt" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/cgroups" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/execdriver/chroot" "github.com/dotcloud/docker/execdriver/lxc" @@ -13,10 +12,10 @@ import ( _ "github.com/dotcloud/docker/graphdriver/devmapper" _ "github.com/dotcloud/docker/graphdriver/vfs" "github.com/dotcloud/docker/pkg/graphdb" + "github.com/dotcloud/docker/pkg/sysinfo" "github.com/dotcloud/docker/utils" "io" "io/ioutil" - "log" "os" "path" "regexp" @@ -37,13 +36,6 @@ var ( validContainerNamePattern = regexp.MustCompile(`^/?` + validContainerNameChars + `+$`) ) -type Capabilities struct { - MemoryLimit bool - SwapLimit bool - IPv4ForwardingDisabled bool - AppArmor bool -} - type Runtime struct { repository string sysInitPath string @@ -52,7 +44,7 @@ type Runtime struct { graph *Graph repositories *TagStore idIndex *utils.TruncIndex - capabilities *Capabilities + sysInfo *sysinfo.SysInfo volumes *Graph srv *Server config *DaemonConfig @@ -332,44 +324,6 @@ func (runtime *Runtime) restore() error { return nil } -func NewRuntimeCapabilities(quiet bool) *Capabilities { - capabilities := &Capabilities{} - if cgroupMemoryMountpoint, err := cgroups.FindCgroupMountpoint("memory"); err != nil { - if !quiet { - log.Printf("WARNING: %s\n", err) - } - } else { - _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes")) - _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) - capabilities.MemoryLimit = err1 == nil && err2 == nil - if !capabilities.MemoryLimit && !quiet { - log.Printf("WARNING: Your kernel does not support cgroup memory limit.") - } - - _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) - capabilities.SwapLimit = err == nil - if !capabilities.SwapLimit && !quiet { - log.Printf("WARNING: Your kernel does not support cgroup swap limit.") - } - } - - content, err3 := ioutil.ReadFile("/proc/sys/net/ipv4/ip_forward") - capabilities.IPv4ForwardingDisabled = err3 != nil || len(content) == 0 || content[0] != '1' - if capabilities.IPv4ForwardingDisabled && !quiet { - log.Printf("WARNING: IPv4 forwarding is disabled.") - } - - // Check if AppArmor seems to be enabled on this system. - if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) { - utils.Debugf("/sys/kernel/security/apparmor not found; assuming AppArmor is not enabled.") - capabilities.AppArmor = false - } else { - utils.Debugf("/sys/kernel/security/apparmor found; assuming AppArmor is enabled.") - capabilities.AppArmor = true - } - return capabilities -} - // Create creates a new container from the given configuration with a given name. func (runtime *Runtime) Create(config *Config, name string) (*Container, []string, error) { // Lookup image @@ -732,7 +686,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { sysInitPath = localCopy } - capabilities := NewRuntimeCapabilities(false) + sysInfo := sysinfo.New(false) /* temporarilly disabled. @@ -740,14 +694,14 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { if false { var ed execdriver.Driver if driver := os.Getenv("EXEC_DRIVER"); driver == "lxc" { - ed, err = lxc.NewDriver(config.Root, capabilities.AppArmor) + ed, err = lxc.NewDriver(config.Root, sysInfo.AppArmor) } else { ed, err = chroot.NewDriver() } if ed != nil { } } - ed, err := lxc.NewDriver(config.Root, capabilities.AppArmor) + ed, err := lxc.NewDriver(config.Root, sysInfo.AppArmor) if err != nil { return nil, err } @@ -759,7 +713,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { graph: g, repositories: repositories, idIndex: utils.NewTruncIndex(), - capabilities: capabilities, + sysInfo: sysInfo, volumes: volumes, config: config, containerGraph: graph, diff --git a/server.go b/server.go index a9c83575e4..bc8a86ef2f 100644 --- a/server.go +++ b/server.go @@ -516,7 +516,7 @@ func (srv *Server) ImageInsert(job *engine.Job) engine.Status { } defer file.Body.Close() - config, _, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.capabilities) + config, _, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.sysInfo) if err != nil { job.Error(err) return engine.StatusErr @@ -678,9 +678,9 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { v.SetInt("Images", imgcount) v.Set("Driver", srv.runtime.driver.String()) v.SetJson("DriverStatus", srv.runtime.driver.Status()) - v.SetBool("MemoryLimit", srv.runtime.capabilities.MemoryLimit) - v.SetBool("SwapLimit", srv.runtime.capabilities.SwapLimit) - v.SetBool("IPv4Forwarding", !srv.runtime.capabilities.IPv4ForwardingDisabled) + v.SetBool("MemoryLimit", srv.runtime.sysInfo.MemoryLimit) + v.SetBool("SwapLimit", srv.runtime.sysInfo.SwapLimit) + v.SetBool("IPv4Forwarding", !srv.runtime.sysInfo.IPv4ForwardingDisabled) v.SetBool("Debug", os.Getenv("DEBUG") != "") v.SetInt("NFd", utils.GetTotalUsedFds()) v.SetInt("NGoroutines", runtime.NumGoroutine()) @@ -1470,10 +1470,10 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { job.Errorf("Minimum memory limit allowed is 512k") return engine.StatusErr } - if config.Memory > 0 && !srv.runtime.capabilities.MemoryLimit { + if config.Memory > 0 && !srv.runtime.sysInfo.MemoryLimit { config.Memory = 0 } - if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit { + if config.Memory > 0 && !srv.runtime.sysInfo.SwapLimit { config.MemorySwap = -1 } container, buildWarnings, err := srv.runtime.Create(&config, name) From 70a5cb95b31c7596886b7f94d292444654b9af8d Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 15 Jan 2014 17:26:04 -0800 Subject: [PATCH 104/364] Move lxc template into lxc driver Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- cgroups/cgroups.go | 7 +- container.go | 39 ++++---- execdriver/driver.go | 26 ++--- execdriver/lxc/driver.go | 27 ++++- .../lxc/lxc_template.go | 58 +++++------ .../lxc/lxc_template_unit_test.go | 98 ++++++++++--------- 6 files changed, 144 insertions(+), 111 deletions(-) rename lxc_template.go => execdriver/lxc/lxc_template.go (75%) rename lxc_template_unit_test.go => execdriver/lxc/lxc_template_unit_test.go (60%) diff --git a/cgroups/cgroups.go b/cgroups/cgroups.go index 30de8d4d1e..fdcab8c3da 100644 --- a/cgroups/cgroups.go +++ b/cgroups/cgroups.go @@ -12,8 +12,13 @@ import ( "strings" ) -// https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt +type Values struct { + Memory int64 `json:"memory"` + MemorySwap int64 `json:"memory_swap"` + CpuShares int64 `json:"cpu_shares"` +} +// https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt func FindCgroupMountpoint(subsystem string) (string, error) { mounts, err := mount.GetMounts() if err != nil { diff --git a/container.go b/container.go index 23a3c05335..79d9511d7b 100644 --- a/container.go +++ b/container.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/cgroups" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/mount" @@ -299,15 +300,6 @@ func (container *Container) generateEnvConfig(env []string) error { return nil } -func (container *Container) generateLXCConfig() error { - fo, err := os.Create(container.lxcConfigPath()) - if err != nil { - return err - } - defer fo.Close() - return LxcTemplateCompiled.Execute(fo, container) -} - func (container *Container) setupPty() error { ptyMaster, ptySlave, err := pty.Open() if err != nil { @@ -554,10 +546,6 @@ func (container *Container) Start() (err error) { return err } - if err := container.generateLXCConfig(); err != nil { - return err - } - // Setup environment env := []string{ "HOME=/", @@ -662,17 +650,33 @@ func (container *Container) Start() (err error) { } } - var en *execdriver.Network + var ( + en *execdriver.Network + driverConfig []string + ) + if !container.Config.NetworkDisabled { network := container.NetworkSettings en = &execdriver.Network{ Gateway: network.Gateway, + Bridge: network.Bridge, IPAddress: network.IPAddress, IPPrefixLen: network.IPPrefixLen, Mtu: container.runtime.config.Mtu, } } + if lxcConf := container.hostConfig.LxcConf; lxcConf != nil { + for _, pair := range lxcConf { + driverConfig = append(driverConfig, fmt.Sprintf("%s = %s", pair.Key, pair.Value)) + } + } + cgroupValues := &cgroups.Values{ + Memory: container.Config.Memory, + MemorySwap: container.Config.MemorySwap, + CpuShares: container.Config.CpuShares, + } + container.process = &execdriver.Process{ ID: container.ID, Privileged: container.hostConfig.Privileged, @@ -681,10 +685,11 @@ func (container *Container) Start() (err error) { Entrypoint: container.Path, Arguments: container.Args, WorkingDir: workingDir, - ConfigPath: container.lxcConfigPath(), Network: en, Tty: container.Config.Tty, User: container.Config.User, + Config: driverConfig, + Cgroups: cgroupValues, } container.process.SysProcAttr = &syscall.SysProcAttr{Setsid: true} @@ -1381,10 +1386,6 @@ func (container *Container) EnvConfigPath() (string, error) { return p, nil } -func (container *Container) lxcConfigPath() string { - return path.Join(container.root, "config.lxc") -} - // This method must be exported to be used from the lxc template func (container *Container) RootfsPath() string { return container.rootfs diff --git a/execdriver/driver.go b/execdriver/driver.go index e4d512f7bc..0141eef18d 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -2,6 +2,7 @@ package execdriver import ( "errors" + "github.com/dotcloud/docker/cgroups" "os/exec" "syscall" ) @@ -71,6 +72,7 @@ type Driver interface { type Network struct { Gateway string `json:"gateway"` IPAddress string `json:"ip"` + Bridge string `json:"bridge"` IPPrefixLen int `json:"ip_prefix_len"` Mtu int `json:"mtu"` } @@ -79,17 +81,19 @@ type Network struct { type Process struct { exec.Cmd - ID string `json:"id"` - Privileged bool `json:"privileged"` - User string `json:"user"` - Rootfs string `json:"rootfs"` // root fs of the container - InitPath string `json:"initpath"` // dockerinit - Entrypoint string `json:"entrypoint"` - Arguments []string `json:"arguments"` - WorkingDir string `json:"working_dir"` - ConfigPath string `json:"config_path"` // This should be able to be removed when the lxc template is moved into the driver - Tty bool `json:"tty"` - Network *Network `json:"network"` // if network is nil then networking is disabled + ID string `json:"id"` + Privileged bool `json:"privileged"` + User string `json:"user"` + Rootfs string `json:"rootfs"` // root fs of the container + InitPath string `json:"initpath"` // dockerinit + Entrypoint string `json:"entrypoint"` + Arguments []string `json:"arguments"` + WorkingDir string `json:"working_dir"` + ConfigPath string `json:"config_path"` // This should be able to be removed when the lxc template is moved into the driver + Tty bool `json:"tty"` + Network *Network `json:"network"` // if network is nil then networking is disabled + Config []string `json:"config"` + Cgroups *cgroups.Values `json:"cgroups"` } // Return the pid of the process diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 3b4572af96..09203dfbf1 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -72,10 +72,14 @@ func (d *driver) Name() string { } func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallback) (int, error) { + configPath, err := d.generateLXCConfig(c) + if err != nil { + return -1, err + } params := []string{ "lxc-start", "-n", c.ID, - "-f", c.ConfigPath, + "-f", configPath, "--", c.InitPath, "-driver", @@ -259,7 +263,6 @@ func (i *info) IsRunning() bool { } func (d *driver) Info(id string) execdriver.Info { - return &info{ ID: id, driver: d, @@ -297,3 +300,23 @@ func rootIsShared() bool { // No idea, probably safe to assume so return true } + +func (d *driver) generateLXCConfig(p *execdriver.Process) (string, error) { + root := path.Join(d.root, "containers", p.ID, "config.lxc") + fo, err := os.Create(root) + if err != nil { + return "", err + } + defer fo.Close() + + if err := LxcTemplateCompiled.Execute(fo, struct { + *execdriver.Process + AppArmor bool + }{ + Process: p, + AppArmor: d.apparmor, + }); err != nil { + return "", err + } + return root, nil +} diff --git a/lxc_template.go b/execdriver/lxc/lxc_template.go similarity index 75% rename from lxc_template.go rename to execdriver/lxc/lxc_template.go index 4e1aece94b..deaa7a66ae 100644 --- a/lxc_template.go +++ b/execdriver/lxc/lxc_template.go @@ -1,24 +1,24 @@ -package docker +package lxc import ( - "github.com/dotcloud/docker/pkg/sysinfo" + "github.com/dotcloud/docker/cgroups" "strings" "text/template" ) const LxcTemplate = ` -{{if .Config.NetworkDisabled}} -# network is disabled (-n=false) -lxc.network.type = empty -{{else}} +{{if .Network}} # network configuration lxc.network.type = veth -lxc.network.link = {{.NetworkSettings.Bridge}} +lxc.network.link = {{.Network.Bridge}} lxc.network.name = eth0 +{{else}} +# network is disabled (-n=false) +lxc.network.type = empty {{end}} # root filesystem -{{$ROOTFS := .RootfsPath}} +{{$ROOTFS := .Rootfs}} lxc.rootfs = {{$ROOTFS}} # use a dedicated pts for the container (and limit the number of pseudo terminal @@ -31,7 +31,7 @@ lxc.console = none # no controlling tty at all lxc.tty = 1 -{{if (getHostConfig .).Privileged}} +{{if .Privileged}} lxc.cgroup.devices.allow = a {{else}} # no implicit access to devices @@ -82,8 +82,8 @@ lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noe lxc.mount.entry = devpts {{escapeFstabSpaces $ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0 lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0 -{{if (getHostConfig .).Privileged}} -{{if (getSysInfo .).AppArmor}} +{{if .Privileged}} +{{if .AppArmor}} lxc.aa_profile = unconfined {{else}} #lxc.aa_profile = unconfined @@ -91,20 +91,22 @@ lxc.aa_profile = unconfined {{end}} # limits -{{if .Config.Memory}} -lxc.cgroup.memory.limit_in_bytes = {{.Config.Memory}} -lxc.cgroup.memory.soft_limit_in_bytes = {{.Config.Memory}} -{{with $memSwap := getMemorySwap .Config}} +{{if .Cgroups}} +{{if .Cgroups.Memory}} +lxc.cgroup.memory.limit_in_bytes = {{.Cgroups.Memory}} +lxc.cgroup.memory.soft_limit_in_bytes = {{.Cgroups.Memory}} +{{with $memSwap := getMemorySwap .Cgroups}} lxc.cgroup.memory.memsw.limit_in_bytes = {{$memSwap}} {{end}} {{end}} -{{if .Config.CpuShares}} -lxc.cgroup.cpu.shares = {{.Config.CpuShares}} +{{if .Cgroups.CpuShares}} +lxc.cgroup.cpu.shares = {{.Cgroups.CpuShares}} +{{end}} {{end}} -{{if (getHostConfig .).LxcConf}} -{{range $pair := (getHostConfig .).LxcConf}} -{{$pair.Key}} = {{$pair.Value}} +{{if .Config}} +{{range $value := .Config}} +{{$value}} {{end}} {{end}} ` @@ -117,29 +119,19 @@ func escapeFstabSpaces(field string) string { return strings.Replace(field, " ", "\\040", -1) } -func getMemorySwap(config *Config) int64 { +func getMemorySwap(v *cgroups.Values) int64 { // By default, MemorySwap is set to twice the size of RAM. // If you want to omit MemorySwap, set it to `-1'. - if config.MemorySwap < 0 { + if v.MemorySwap < 0 { return 0 } - return config.Memory * 2 -} - -func getHostConfig(container *Container) *HostConfig { - return container.hostConfig -} - -func getSysInfo(container *Container) *sysinfo.SysInfo { - return container.runtime.sysInfo + return v.Memory * 2 } func init() { var err error funcMap := template.FuncMap{ "getMemorySwap": getMemorySwap, - "getHostConfig": getHostConfig, - "getSysInfo": getSysInfo, "escapeFstabSpaces": escapeFstabSpaces, } LxcTemplateCompiled, err = template.New("lxc").Funcs(funcMap).Parse(LxcTemplate) diff --git a/lxc_template_unit_test.go b/execdriver/lxc/lxc_template_unit_test.go similarity index 60% rename from lxc_template_unit_test.go rename to execdriver/lxc/lxc_template_unit_test.go index f71f1dd6f5..fe65fe5cbc 100644 --- a/lxc_template_unit_test.go +++ b/execdriver/lxc/lxc_template_unit_test.go @@ -1,11 +1,14 @@ -package docker +package lxc import ( "bufio" "fmt" + "github.com/dotcloud/docker/cgroups" + "github.com/dotcloud/docker/execdriver" "io/ioutil" "math/rand" "os" + "path" "strings" "testing" "time" @@ -17,32 +20,39 @@ func TestLXCConfig(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(root) + + os.MkdirAll(path.Join(root, "containers", "1"), 0777) + // Memory is allocated randomly for testing rand.Seed(time.Now().UTC().UnixNano()) - memMin := 33554432 - memMax := 536870912 - mem := memMin + rand.Intn(memMax-memMin) - // CPU shares as well - cpuMin := 100 - cpuMax := 10000 - cpu := cpuMin + rand.Intn(cpuMax-cpuMin) - container := &Container{ - root: root, - Config: &Config{ - Memory: int64(mem), - CpuShares: int64(cpu), - NetworkDisabled: true, - }, - hostConfig: &HostConfig{ - Privileged: false, - }, - } - if err := container.generateLXCConfig(); err != nil { + var ( + memMin = 33554432 + memMax = 536870912 + mem = memMin + rand.Intn(memMax-memMin) + cpuMin = 100 + cpuMax = 10000 + cpu = cpuMin + rand.Intn(cpuMax-cpuMin) + ) + + driver, err := NewDriver(root, false) + if err != nil { t.Fatal(err) } - grepFile(t, container.lxcConfigPath(), + process := &execdriver.Process{ + ID: "1", + Cgroups: &cgroups.Values{ + Memory: int64(mem), + CpuShares: int64(cpu), + }, + } + p, err := driver.generateLXCConfig(process) + if err != nil { + t.Fatal(err) + } + grepFile(t, p, fmt.Sprintf("lxc.cgroup.memory.limit_in_bytes = %d", mem)) - grepFile(t, container.lxcConfigPath(), + + grepFile(t, p, fmt.Sprintf("lxc.cgroup.memory.memsw.limit_in_bytes = %d", mem*2)) } @@ -52,31 +62,29 @@ func TestCustomLxcConfig(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(root) - container := &Container{ - root: root, - Config: &Config{ - Hostname: "foobar", - NetworkDisabled: true, - }, - hostConfig: &HostConfig{ - Privileged: false, - LxcConf: []KeyValuePair{ - { - Key: "lxc.utsname", - Value: "docker", - }, - { - Key: "lxc.cgroup.cpuset.cpus", - Value: "0,1", - }, - }, - }, - } - if err := container.generateLXCConfig(); err != nil { + + os.MkdirAll(path.Join(root, "containers", "1"), 0777) + + driver, err := NewDriver(root, false) + if err != nil { t.Fatal(err) } - grepFile(t, container.lxcConfigPath(), "lxc.utsname = docker") - grepFile(t, container.lxcConfigPath(), "lxc.cgroup.cpuset.cpus = 0,1") + process := &execdriver.Process{ + ID: "1", + Privileged: false, + Config: []string{ + "lxc.utsname = docker", + "lxc.cgroup.cpuset.cpus = 0,1", + }, + } + + p, err := driver.generateLXCConfig(process) + if err != nil { + t.Fatal(err) + } + + grepFile(t, p, "lxc.utsname = docker") + grepFile(t, p, "lxc.cgroup.cpuset.cpus = 0,1") } func grepFile(t *testing.T, path string, pattern string) { From cdfebc2a20e76d4ea81eb154b748e0547a7e7ab5 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 16 Jan 2014 11:59:46 -0800 Subject: [PATCH 105/364] Change drvier name to append version Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- execdriver/chroot/driver.go | 7 ++----- execdriver/driver.go | 5 ++--- execdriver/lxc/driver.go | 5 +++-- server.go | 2 +- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index 2aa23c9866..f3eba8304d 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -1,6 +1,7 @@ package chroot import ( + "fmt" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/mount" "os" @@ -82,9 +83,5 @@ func (d *driver) Info(id string) execdriver.Info { } func (d *driver) Name() string { - return DriverName -} - -func (d *driver) Version() string { - return Version + return fmt.Sprintf("%s-%s", DriverName, Version) } diff --git a/execdriver/driver.go b/execdriver/driver.go index 0141eef18d..7fc066ee29 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -63,7 +63,6 @@ type Driver interface { Run(c *Process, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code Kill(c *Process, sig int) error Wait(id string) error // Wait on an out of process...process - lxc ghosts - Version() string // Driver version number Name() string // Driver name Info(id string) Info // "temporary" hack (until we move state from core to plugins) } @@ -89,10 +88,10 @@ type Process struct { Entrypoint string `json:"entrypoint"` Arguments []string `json:"arguments"` WorkingDir string `json:"working_dir"` - ConfigPath string `json:"config_path"` // This should be able to be removed when the lxc template is moved into the driver + ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver Tty bool `json:"tty"` Network *Network `json:"network"` // if network is nil then networking is disabled - Config []string `json:"config"` + Config []string `json:"config"` // generic values that specific drivers can consume Cgroups *cgroups.Values `json:"cgroups"` } diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 09203dfbf1..6d1e1d29dc 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -68,7 +68,8 @@ func NewDriver(root string, apparmor bool) (*driver, error) { } func (d *driver) Name() string { - return DriverName + version := d.version() + return fmt.Sprintf("%s-%s", DriverName, version) } func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallback) (int, error) { @@ -186,7 +187,7 @@ func (d *driver) Wait(id string) error { } } -func (d *driver) Version() string { +func (d *driver) version() string { version := "" if output, err := exec.Command("lxc-version").CombinedOutput(); err == nil { outputStr := string(output) diff --git a/server.go b/server.go index bc8a86ef2f..a25ba74a06 100644 --- a/server.go +++ b/server.go @@ -684,7 +684,7 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { v.SetBool("Debug", os.Getenv("DEBUG") != "") v.SetInt("NFd", utils.GetTotalUsedFds()) v.SetInt("NGoroutines", runtime.NumGoroutine()) - v.Set("LXCVersion", srv.runtime.execDriver.Version()) + v.Set("ExecutionDriver", srv.runtime.execDriver.Name()) v.SetInt("NEventsListener", len(srv.events)) v.Set("KernelVersion", kernelVersion) v.Set("IndexServerAddress", auth.IndexServerAddress()) From 97c84507054a7379c8f8b461773b9c8d4972902b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 16 Jan 2014 15:09:00 -0800 Subject: [PATCH 106/364] Make sure drivers are registerd within sysinit Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- commands.go | 2 +- execdriver/chroot/driver.go | 2 +- execdriver/lxc/driver.go | 2 +- sysinit/sysinit.go | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index d23c7a9da5..b93f0b9686 100644 --- a/commands.go +++ b/commands.go @@ -471,7 +471,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "") fmt.Fprintf(cli.out, "Fds: %d\n", remoteInfo.GetInt("NFd")) fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines")) - fmt.Fprintf(cli.out, "LXC Version: %s\n", remoteInfo.Get("LXCVersion")) + fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver")) fmt.Fprintf(cli.out, "EventsListeners: %d\n", remoteInfo.GetInt("NEventsListener")) fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion")) diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index f3eba8304d..b9f3f04016 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -42,7 +42,7 @@ func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallba c.Rootfs, "/.dockerinit", "-driver", - d.Name(), + DriverName, } params = append(params, c.Entrypoint) params = append(params, c.Arguments...) diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 6d1e1d29dc..eeef1f5370 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -84,7 +84,7 @@ func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallba "--", c.InitPath, "-driver", - d.Name(), + DriverName, } if c.Network != nil { diff --git a/sysinit/sysinit.go b/sysinit/sysinit.go index 7e1ac519cd..af69795cb6 100644 --- a/sysinit/sysinit.go +++ b/sysinit/sysinit.go @@ -5,6 +5,8 @@ import ( "flag" "fmt" "github.com/dotcloud/docker/execdriver" + _ "github.com/dotcloud/docker/execdriver/chroot" + _ "github.com/dotcloud/docker/execdriver/lxc" "io/ioutil" "log" "os" From 5c30c4379af20b3cbd2d20cc9f0ccb6f04ac63ab Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 17 Jan 2014 16:46:39 -0800 Subject: [PATCH 107/364] Add todos for driver changes Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- execdriver/driver.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/execdriver/driver.go b/execdriver/driver.go index 7fc066ee29..5f3d25a566 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -62,7 +62,7 @@ type Info interface { type Driver interface { Run(c *Process, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code Kill(c *Process, sig int) error - Wait(id string) error // Wait on an out of process...process - lxc ghosts + Wait(id string) error // Wait on an out of process...process - lxc ghosts TODO: Rename to reattach, reconnect Name() string // Driver name Info(id string) Info // "temporary" hack (until we move state from core to plugins) } @@ -77,6 +77,7 @@ type Network struct { } // Process wrapps an os/exec.Cmd to add more metadata +// TODO: Rename to Command type Process struct { exec.Cmd From d5112ffce60625e470515578ccd446a191dd9e88 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 17 Jan 2014 16:53:13 -0800 Subject: [PATCH 108/364] Fix rebase for init error Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- execdriver/lxc/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index eeef1f5370..2a6663117a 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -43,7 +43,7 @@ func init() { os.Exit(127) } if err := syscall.Exec(path, args.Args, os.Environ()); err != nil { - panic(err) + return fmt.Errorf("dockerinit unable to execute %s - %s", path, err) } panic("Unreachable") }) From 455495f7caf1bf2ff9ea1364bbf2362d9737b54c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 17 Jan 2014 18:54:02 -0800 Subject: [PATCH 109/364] refactor, remove useless buffers Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 74 +++++++++++++++++++++++----------------------------------- 1 file changed, 29 insertions(+), 45 deletions(-) diff --git a/api.go b/api.go index 83b749b217..4d874bc847 100644 --- a/api.go +++ b/api.go @@ -184,8 +184,9 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http. } var ( - buffer *bytes.Buffer - job = srv.Eng.Job("images") + err error + outs *engine.Table + job = srv.Eng.Job("images") ) job.Setenv("filter", r.Form.Get("filter")) @@ -193,20 +194,15 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http. if version >= 1.9 { job.Stdout.Add(w) - } else { - buffer = bytes.NewBuffer(nil) - job.Stdout.Add(buffer) + } else if outs, err = job.Stdout.AddTable(); err != nil { + return err } - if err := job.Run(); err != nil { + if err = job.Run(); err != nil { return err } if version < 1.9 { // Send as a valid JSON array - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadFrom(buffer); err != nil { - return err - } if version < 1.8 { // Convert to legacy format outsLegacy := engine.NewTable("Created", 0) for _, out := range outs.Data { @@ -222,10 +218,10 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http. outsLegacy.Add(outLegacy) } } - if _, err := outsLegacy.WriteListTo(w); err != nil { + if _, err = outsLegacy.WriteListTo(w); err != nil { return err } - } else if _, err := outs.WriteListTo(w); err != nil { + } else if _, err = outs.WriteListTo(w); err != nil { return err } } @@ -312,25 +308,21 @@ func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *ht } var ( - buffer *bytes.Buffer - job = srv.Eng.Job("history", vars["name"]) + err error + outs *engine.Table + job = srv.Eng.Job("history", vars["name"]) ) if version >= 1.9 { job.Stdout.Add(w) - } else { - buffer = bytes.NewBuffer(nil) - job.Stdout.Add(buffer) + } else if outs, err = job.Stdout.AddTable(); err != nil { + return err } - if err := job.Run(); err != nil { + if err = job.Run(); err != nil { return err } if version < 1.9 { // Send as a valid JSON array - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadFrom(buffer); err != nil { - return err - } - if _, err := outs.WriteListTo(w); err != nil { + if _, err = outs.WriteListTo(w); err != nil { return err } } @@ -342,25 +334,21 @@ func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r return fmt.Errorf("Missing parameter") } var ( - buffer *bytes.Buffer - job = srv.Eng.Job("changes", vars["name"]) + err error + outs *engine.Table + job = srv.Eng.Job("changes", vars["name"]) ) if version >= 1.9 { job.Stdout.Add(w) - } else { - buffer = bytes.NewBuffer(nil) - job.Stdout.Add(buffer) + } else if outs, err = job.Stdout.AddTable(); err != nil { + return err } - if err := job.Run(); err != nil { + if err = job.Run(); err != nil { return err } if version < 1.9 { // Send as a valid JSON array - outs := engine.NewTable("", 0) - if _, err := outs.ReadFrom(buffer); err != nil { - return err - } - if _, err := outs.WriteListTo(w); err != nil { + if _, err = outs.WriteListTo(w); err != nil { return err } } @@ -514,24 +502,20 @@ func getImagesSearch(srv *Server, version float64, w http.ResponseWriter, r *htt } var ( - buffer *bytes.Buffer - job = srv.Eng.Job("search", r.Form.Get("term")) + err error + outs *engine.Table + job = srv.Eng.Job("search", r.Form.Get("term")) ) if version >= 1.9 { job.Stdout.Add(w) - } else { - buffer = bytes.NewBuffer(nil) - job.Stdout.Add(buffer) + } else if outs, err = job.Stdout.AddTable(); err != nil { + return err } - if err := job.Run(); err != nil { + if err = job.Run(); err != nil { return err } if version < 1.9 { // Send as a valid JSON array - outs := engine.NewTable("", 0) - if _, err := outs.ReadFrom(buffer); err != nil { - return err - } - if _, err := outs.WriteListTo(w); err != nil { + if _, err = outs.WriteListTo(w); err != nil { return err } } From 980e0bf52c15bdf179c58b2d4f1085e26f8a3e2a Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sat, 18 Jan 2014 10:37:27 -0700 Subject: [PATCH 110/364] Fix Travis for PRs against non-master (like release, for example) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- .travis.yml | 7 ++++++- hack/travis/env.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index f6c83997aa..0650c368d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,8 +13,13 @@ before_script: - sudo apt-get update -qq - sudo apt-get install -qq python-yaml - git remote add upstream git://github.com/dotcloud/docker.git - - git fetch --append --no-tags upstream refs/heads/master:refs/remotes/upstream/master + - upstream=master; + if [ "$TRAVIS_PULL_REQUEST" != false ]; then + upstream=$TRAVIS_BRANCH; + fi; + git fetch --append --no-tags upstream refs/heads/$upstream:refs/remotes/upstream/$upstream # sometimes we have upstream master already as origin/master (PRs), but other times we don't, so let's just make sure we have a completely unambiguous way to specify "upstream master" from here out +# but if it's a PR against non-master, we need that upstream branch instead :) script: - hack/travis/dco.py diff --git a/hack/travis/env.py b/hack/travis/env.py index 86d90f1567..9830b8df34 100644 --- a/hack/travis/env.py +++ b/hack/travis/env.py @@ -6,7 +6,7 @@ if 'TRAVIS' not in os.environ: exit(127) if os.environ['TRAVIS_PULL_REQUEST'] != 'false': - commit_range = [os.environ['TRAVIS_BRANCH'], 'FETCH_HEAD'] + commit_range = ['upstream/' + os.environ['TRAVIS_BRANCH'], 'FETCH_HEAD'] else: try: subprocess.check_call([ From 595b8f29869455432e14e58fcdd9c64bc74dd3fd Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sun, 19 Jan 2014 14:07:18 -0700 Subject: [PATCH 111/364] Add udev rules file to our generated deb package Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/make/ubuntu | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hack/make/ubuntu b/hack/make/ubuntu index 0ec0716ba4..1d309d2b5c 100644 --- a/hack/make/ubuntu +++ b/hack/make/ubuntu @@ -24,6 +24,10 @@ PACKAGE_LICENSE="Apache-2.0" bundle_ubuntu() { DIR=$DEST/build + # Include our udev rules + mkdir -p $DIR/etc/udev/rules.d + cp contrib/udev/80-docker.rules $DIR/etc/udev/rules.d/ + # Include our init scripts mkdir -p $DIR/etc cp -R contrib/init/upstart $DIR/etc/init @@ -122,6 +126,7 @@ EOF --replaces lxc-docker-virtual-package \ --url "$PACKAGE_URL" \ --license "$PACKAGE_LICENSE" \ + --config-files /etc/udev/rules.d/80-docker.rules \ --config-files /etc/init/docker.conf \ --config-files /etc/init.d/docker \ --config-files /etc/default/docker \ From b8cca8138b9aa10fd087e6ebb62ca03d1e820f2f Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 15 Jan 2014 14:59:55 +1000 Subject: [PATCH 112/364] add a little info about ghosts Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/reference/commandline/cli.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index a6ae75076b..fed1772ce3 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -857,7 +857,10 @@ Running ``docker ps`` showing 2 linked containers. $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4c01db0b339c ubuntu:12.04 bash 17 seconds ago Up 16 seconds webapp - d7886598dbe2 crosbymichael/redis:latest /redis-server --dir 33 minutes ago Up 33 minutes 6379/tcp redis,webapp/db + d7886598dbe2 crosbymichael/redis:latest /redis-server --dir 33 minutes ago Up 33 minutes 6379/tcp redis,webapp/db + fd2645e2e2b5 busybox:latest top 10 days ago Ghost insane_ptolemy + +The last container is marked as a ``Ghost`` container. It is a container that was running when the docker daemon was restarted (upgraded, or ``-H`` settings changed). The container is still running, but as this docker daemon process is not able to manage it, you can't attach to it. To bring them out of ``Ghost`` Status, you need to use ``docker kill`` or ``docker restart``. .. _cli_pull: From e45b87031c2b68ebbf98fdc02bace53ebe932148 Mon Sep 17 00:00:00 2001 From: vgeta Date: Sun, 19 Jan 2014 17:48:54 -0800 Subject: [PATCH 113/364] Fixes Issue# 3450 Docker-DCO-1.1-Signed-off-by: Gopikannan Venugopalsamy (github: vgeta) --- api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.go b/api.go index 1128c7a2b7..d9e325612d 100644 --- a/api.go +++ b/api.go @@ -1054,7 +1054,7 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s } if version == 0 || version > APIVERSION { - w.WriteHeader(http.StatusNotFound) + http.Error(w, fmt.Errorf("client and server don't have same version (client : %g, server: %g)", version, APIVERSION).Error(), http.StatusNotFound) return } From c6e43154f186c9fac8501016473b459db3130222 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sat, 11 Jan 2014 01:00:03 -0700 Subject: [PATCH 114/364] Resync bash completion to include all flags defined in the code, and to autocomplete properly on all parameters that are reasonably possible to do so on today This also includes several new minor features that are interesting, so do explore a little. :) Finally, this also fixes a few bugs where commands would complete parameters that they won't necessarily accept. We still have a few of these cases, but they're reduced to a minimum now. Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- contrib/completion/bash/docker | 417 ++++++++++++++++++++++----------- 1 file changed, 279 insertions(+), 138 deletions(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 4ebe45e083..f38962cf7e 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -23,62 +23,82 @@ __docker_containers_all() { - local containers - containers="$( docker ps -a -q )" - names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" + local containers="$( docker ps -a -q )" + local names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) ) } __docker_containers_running() { - local containers - containers="$( docker ps -q )" - names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" + local containers="$( docker ps -q )" + local names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) ) } __docker_containers_stopped() { - local containers - containers="$( comm -13 <(docker ps -q | sort -u) <(docker ps -a -q | sort -u) )" - names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" + local containers="$( { docker ps -a -q; docker ps -q; } | sort | uniq -u )" + local names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) ) } __docker_image_repos() { - local repos - repos="$( docker images | awk 'NR>1{print $1}' )" + local repos="$( docker images | awk 'NR>1{print $1}' | grep -v '^$' )" COMPREPLY=( $( compgen -W "$repos" -- "$cur" ) ) } -__docker_images() -{ - local images - images="$( docker images | awk 'NR>1{print $1":"$2}' )" - COMPREPLY=( $( compgen -W "$images" -- "$cur" ) ) - __ltrim_colon_completions "$cur" -} - __docker_image_repos_and_tags() { - local repos images - repos="$( docker images | awk 'NR>1{print $1}' )" - images="$( docker images | awk 'NR>1{print $1":"$2}' )" + local repos="$( docker images | awk 'NR>1{print $1}' | grep -v '^$' )" + local images="$( docker images | awk 'NR>1{print $1":"$2}' | grep -v '^:' )" COMPREPLY=( $( compgen -W "$repos $images" -- "$cur" ) ) __ltrim_colon_completions "$cur" } +__docker_image_repos_and_tags_and_ids() +{ + local repos="$( docker images | awk 'NR>1{print $1}' | grep -v '^$' )" + local images="$( docker images | awk 'NR>1{print $1":"$2}' | grep -v '^:' )" + local ids="$( docker images -a -q )" + COMPREPLY=( $( compgen -W "$repos $images $ids" -- "$cur" ) ) + __ltrim_colon_completions "$cur" +} + __docker_containers_and_images() { - local containers images - containers="$( docker ps -a -q )" - names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" - images="$( docker images | awk 'NR>1{print $1":"$2}' )" - COMPREPLY=( $( compgen -W "$images $names $containers" -- "$cur" ) ) + local containers="$( docker ps -a -q )" + local names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" + local repos="$( docker images | awk 'NR>1{print $1}' | grep -v '^$' )" + local images="$( docker images | awk 'NR>1{print $1":"$2}' | grep -v '^:' )" + local ids="$( docker images -a -q )" + COMPREPLY=( $( compgen -W "$containers $names $repos $images $ids" -- "$cur" ) ) __ltrim_colon_completions "$cur" } +__docker_pos_first_nonflag() +{ + local argument_flags=$1 + + local counter=$cpos + while [ $counter -le $cword ]; do + if [ -n "$argument_flags" ] && eval "case '${words[$counter]}' in $argument_flags) true ;; *) false ;; esac"; then + (( counter++ )) + else + case "${words[$counter]}" in + -*) + ;; + *) + break + ;; + esac + fi + (( counter++ )) + done + + echo $counter +} + _docker_docker() { case "$prev" in @@ -101,15 +121,24 @@ _docker_docker() _docker_attach() { - if [ $cpos -eq $cword ]; then - __docker_containers_running - fi + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "--no-stdin --sig-proxy" -- "$cur" ) ) + ;; + *) + local counter="$(__docker_pos_first_nonflag)" + if [ $cword -eq $counter ]; then + __docker_containers_running + fi + ;; + esac } _docker_build() { case "$prev" in - -t) + -t|--tag) + __docker_image_repos_and_tags return ;; *) @@ -118,10 +147,13 @@ _docker_build() case "$cur" in -*) - COMPREPLY=( $( compgen -W "--no-cache -t -q --rm" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "-t --tag -q --quiet --no-cache --rm" -- "$cur" ) ) ;; *) - _filedir + local counter="$(__docker_pos_first_nonflag '-t|--tag')" + if [ $cword -eq $counter ]; then + _filedir + fi ;; esac } @@ -129,7 +161,7 @@ _docker_build() _docker_commit() { case "$prev" in - -author|-m|-run) + -m|--message|-a|--author|--run) return ;; *) @@ -138,26 +170,20 @@ _docker_commit() case "$cur" in -*) - COMPREPLY=( $( compgen -W "--author -m --run" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "-m --message -a --author --run" -- "$cur" ) ) ;; *) - local counter=$cpos - while [ $counter -le $cword ]; do - case "${words[$counter]}" in - -author|-m|-run) - (( counter++ )) - ;; - -*) - ;; - *) - break - ;; - esac - (( counter++ )) - done + local counter=$(__docker_pos_first_nonflag '-m|--message|-a|--author|--run') - if [ $counter -eq $cword ]; then + if [ $cword -eq $counter ]; then __docker_containers_all + return + fi + (( counter++ )) + + if [ $cword -eq $counter ]; then + __docker_image_repos_and_tags + return fi ;; esac @@ -165,16 +191,32 @@ _docker_commit() _docker_cp() { - if [ $cpos -eq $cword ]; then - __docker_containers_all - else + local counter=$(__docker_pos_first_nonflag) + if [ $cword -eq $counter ]; then + case "$cur" in + *:) + return + ;; + *) + __docker_containers_all + COMPREPLY=( $( compgen -W "${COMPREPLY[*]}" -S ':' ) ) + compopt -o nospace + return + ;; + esac + fi + (( counter++ )) + + if [ $cword -eq $counter ]; then _filedir + return fi } _docker_diff() { - if [ $cpos -eq $cword ]; then + local counter=$(__docker_pos_first_nonflag) + if [ $cword -eq $counter ]; then __docker_containers_all fi } @@ -182,7 +224,7 @@ _docker_diff() _docker_events() { case "$prev" in - -since) + --since) return ;; *) @@ -200,45 +242,44 @@ _docker_events() _docker_export() { - if [ $cpos -eq $cword ]; then + local counter=$(__docker_pos_first_nonflag) + if [ $cword -eq $counter ]; then __docker_containers_all fi } _docker_help() { - if [ $cpos -eq $cword ]; then + local counter=$(__docker_pos_first_nonflag) + if [ $cword -eq $counter ]; then COMPREPLY=( $( compgen -W "$commands" -- "$cur" ) ) fi } _docker_history() { - if [ $cpos -eq $cword ]; then - __docker_image_repos_and_tags - fi + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "-q --quiet --no-trunc" -- "$cur" ) ) + ;; + *) + local counter=$(__docker_pos_first_nonflag) + if [ $cword -eq $counter ]; then + __docker_image_repos_and_tags_and_ids + fi + ;; + esac } _docker_images() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "-a --no-trunc -q --viz" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "-q --quiet -a --all --no-trunc -v --viz -t --tree" -- "$cur" ) ) ;; *) - local counter=$cpos - while [ $counter -le $cword ]; do - case "${words[$counter]}" in - -*) - ;; - *) - break - ;; - esac - (( counter++ )) - done - - if [ $counter -eq $cword ]; then + local counter=$(__docker_pos_first_nonflag) + if [ $cword -eq $counter ]; then __docker_image_repos fi ;; @@ -247,7 +288,16 @@ _docker_images() _docker_import() { - return + local counter=$(__docker_pos_first_nonflag) + if [ $cword -eq $counter ]; then + return + fi + (( counter++ )) + + if [ $cword -eq $counter ]; then + __docker_image_repos_and_tags + return + fi } _docker_info() @@ -257,25 +307,16 @@ _docker_info() _docker_insert() { - if [ $cpos -eq $cword ]; then - __docker_image_repos_and_tags + local counter=$(__docker_pos_first_nonflag) + if [ $cword -eq $counter ]; then + __docker_image_repos_and_tags_and_ids fi } _docker_inspect() -{ - __docker_containers_and_images -} - -_docker_kill() -{ - __docker_containers_running -} - -_docker_login() { case "$prev" in - -e|-p|-u) + -f|--format) return ;; *) @@ -284,7 +325,37 @@ _docker_login() case "$cur" in -*) - COMPREPLY=( $( compgen -W "-e -p -u" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "-f --format" -- "$cur" ) ) + ;; + *) + __docker_containers_and_images + ;; + esac +} + +_docker_kill() +{ + __docker_containers_running +} + +_docker_load() +{ + return +} + +_docker_login() +{ + case "$prev" in + -u|--username|-p|--password|-e|--email) + return + ;; + *) + ;; + esac + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "-u --username -p --password -e --email" -- "$cur" ) ) ;; *) ;; @@ -293,14 +364,23 @@ _docker_login() _docker_logs() { - if [ $cpos -eq $cword ]; then - __docker_containers_all - fi + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "-f --follow" -- "$cur" ) ) + ;; + *) + local counter=$(__docker_pos_first_nonflag) + if [ $cword -eq $counter ]; then + __docker_containers_all + fi + ;; + esac } _docker_port() { - if [ $cpos -eq $cword ]; then + local counter=$(__docker_pos_first_nonflag) + if [ $cword -eq $counter ]; then __docker_containers_all fi } @@ -308,7 +388,13 @@ _docker_port() _docker_ps() { case "$prev" in - -before-id|-n|-since-id) + --since-id|--before-id) + COMPREPLY=( $( compgen -W "$(docker ps -a -q)" -- "$cur" ) ) + # TODO replace this with __docker_containers_all + # see https://github.com/dotcloud/docker/issues/3565 + return + ;; + -n) return ;; *) @@ -317,7 +403,7 @@ _docker_ps() case "$cur" in -*) - COMPREPLY=( $( compgen -W "-a --before-id -l -n --no-trunc -q -s --since-id" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "-q --quiet -s --size -a --all --no-trunc -l --latest --since-id --before-id -n" -- "$cur" ) ) ;; *) ;; @@ -327,7 +413,7 @@ _docker_ps() _docker_pull() { case "$prev" in - -t) + -t|--tag) return ;; *) @@ -336,22 +422,31 @@ _docker_pull() case "$cur" in -*) - COMPREPLY=( $( compgen -W "-t" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "-t --tag" -- "$cur" ) ) ;; *) + local counter=$(__docker_pos_first_nonflag '-t|--tag') + if [ $cword -eq $counter ]; then + __docker_image_repos_and_tags + fi ;; esac } _docker_push() { - __docker_image_repos + local counter=$(__docker_pos_first_nonflag) + if [ $cword -eq $counter ]; then + __docker_image_repos + # TODO replace this with __docker_image_repos_and_tags + # see https://github.com/dotcloud/docker/issues/3411 + fi } _docker_restart() { case "$prev" in - -t) + -t|--time) return ;; *) @@ -360,7 +455,7 @@ _docker_restart() case "$cur" in -*) - COMPREPLY=( $( compgen -W "-t" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "-t --time" -- "$cur" ) ) ;; *) __docker_containers_all @@ -372,7 +467,7 @@ _docker_rm() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "-v" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "-v --volumes -l --link" -- "$cur" ) ) ;; *) __docker_containers_stopped @@ -382,7 +477,7 @@ _docker_rm() _docker_rmi() { - __docker_image_repos_and_tags + __docker_image_repos_and_tags_and_ids } _docker_run() @@ -394,7 +489,15 @@ _docker_run() --volumes-from) __docker_containers_all ;; - -a|-c|--dns|-e|--entrypoint|-h|--lxc-conf|-m|-p|-u|-v|-w) + -v|--volume) + # TODO something magical with colons and _filedir ? + return + ;; + -e|--env) + COMPREPLY=( $( compgen -e -- "$cur" ) ) + return + ;; + --entrypoint|-h|--hostname|-m|--memory|-u|--username|-w|--workdir|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf) return ;; *) @@ -403,45 +506,30 @@ _docker_run() case "$cur" in -*) - COMPREPLY=( $( compgen -W "-a -c --cidfile -d --dns -e --entrypoint -h -i --lxc-conf -m -n -p --privileged -t -u -v --volumes-from -w" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--rm -d --detach -n --networking --privileged -P --publish-all -i --interactive -t --tty --cidfile --entrypoint -h --hostname -m --memory -u --username -w --workdir -c --cpu-shares --sig-proxy --name -a --attach -v --volume --link -e --env -p --publish --expose --dns --volumes-from --lxc-conf" -- "$cur" ) ) ;; *) - local counter=$cpos - while [ $counter -le $cword ]; do - case "${words[$counter]}" in - -a|-c|--cidfile|--dns|-e|--entrypoint|-h|--lxc-conf|-m|-p|-u|-v|--volumes-from|-w) - (( counter++ )) - ;; - -*) - ;; - *) - break - ;; - esac - (( counter++ )) - done + local counter=$(__docker_pos_first_nonflag '--cidfile|--volumes-from|-v|--volume|-e|--env|--entrypoint|-h|--hostname|-m|--memory|-u|--username|-w|--workdir|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf') - if [ $counter -eq $cword ]; then - __docker_image_repos_and_tags + if [ $cword -eq $counter ]; then + __docker_image_repos_and_tags_and_ids fi ;; esac } +_docker_save() +{ + local counter=$(__docker_pos_first_nonflag) + if [ $cword -eq $counter ]; then + __docker_image_repos_and_tags_and_ids + fi +} + _docker_search() -{ - COMPREPLY=( $( compgen -W "--no-trunc" "--stars" "--trusted" -- "$cur" ) ) -} - -_docker_start() -{ - __docker_containers_stopped -} - -_docker_stop() { case "$prev" in - -t) + -s|--stars) return ;; *) @@ -450,7 +538,38 @@ _docker_stop() case "$cur" in -*) - COMPREPLY=( $( compgen -W "-t" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--no-trunc -t --trusted -s --stars" -- "$cur" ) ) + ;; + *) + ;; + esac +} + +_docker_start() +{ + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "-a --attach -i --interactive" -- "$cur" ) ) + ;; + *) + __docker_containers_stopped + ;; + esac +} + +_docker_stop() +{ + case "$prev" in + -t|--time) + return + ;; + *) + ;; + esac + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "-t --time" -- "$cur" ) ) ;; *) __docker_containers_running @@ -460,12 +579,31 @@ _docker_stop() _docker_tag() { - COMPREPLY=( $( compgen -W "-f" -- "$cur" ) ) + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "-f --force" -- "$cur" ) ) + ;; + *) + local counter=$(__docker_pos_first_nonflag) + + if [ $cword -eq $counter ]; then + __docker_image_repos_and_tags + return + fi + (( counter++ )) + + if [ $cword -eq $counter ]; then + __docker_image_repos_and_tags + return + fi + ;; + esac } _docker_top() { - if [ $cpos -eq $cword ]; then + local counter=$(__docker_pos_first_nonflag) + if [ $cword -eq $counter ]; then __docker_containers_running fi } @@ -482,7 +620,6 @@ _docker_wait() _docker() { - local cur prev words cword command="docker" counter=1 word cpos local commands=" attach build @@ -498,6 +635,7 @@ _docker() insert inspect kill + load login logs port @@ -508,6 +646,7 @@ _docker() rm rmi run + save search start stop @@ -518,18 +657,20 @@ _docker() " COMPREPLY=() + local cur prev words cword _get_comp_words_by_ref -n : cur prev words cword + local command='docker' + local counter=1 while [ $counter -lt $cword ]; do - word="${words[$counter]}" - case "$word" in + case "${words[$counter]}" in -H) (( counter++ )) ;; -*) ;; *) - command="$word" + command="${words[$counter]}" cpos=$counter (( cpos++ )) break From fb981bdace66e5b34d41125beb16e450998fd1e7 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 13 Jan 2014 13:16:12 -0700 Subject: [PATCH 115/364] Switch `docker run --username` to `docker run --user` which is more accurate (while we can still do so without breaking anyone's scripts) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- commands.go | 2 +- contrib/completion/bash/docker | 6 +++--- docs/sources/reference/commandline/cli.rst | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/commands.go b/commands.go index b93f0b9686..a864180fef 100644 --- a/commands.go +++ b/commands.go @@ -1778,7 +1778,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf flEntrypoint = cmd.String([]string{"#entrypoint", "-entrypoint"}, "", "Overwrite the default entrypoint of the image") flHostname = cmd.String([]string{"h", "-hostname"}, "", "Container host name") flMemoryString = cmd.String([]string{"m", "-memory"}, "", "Memory limit (format: , where unit = b, k, m or g)") - flUser = cmd.String([]string{"u", "-username"}, "", "Username or UID") + flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID") flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container") flCpuShares = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index f38962cf7e..fb1786c335 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -497,7 +497,7 @@ _docker_run() COMPREPLY=( $( compgen -e -- "$cur" ) ) return ;; - --entrypoint|-h|--hostname|-m|--memory|-u|--username|-w|--workdir|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf) + --entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf) return ;; *) @@ -506,10 +506,10 @@ _docker_run() case "$cur" in -*) - COMPREPLY=( $( compgen -W "--rm -d --detach -n --networking --privileged -P --publish-all -i --interactive -t --tty --cidfile --entrypoint -h --hostname -m --memory -u --username -w --workdir -c --cpu-shares --sig-proxy --name -a --attach -v --volume --link -e --env -p --publish --expose --dns --volumes-from --lxc-conf" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--rm -d --detach -n --networking --privileged -P --publish-all -i --interactive -t --tty --cidfile --entrypoint -h --hostname -m --memory -u --user -w --workdir -c --cpu-shares --sig-proxy --name -a --attach -v --volume --link -e --env -p --publish --expose --dns --volumes-from --lxc-conf" -- "$cur" ) ) ;; *) - local counter=$(__docker_pos_first_nonflag '--cidfile|--volumes-from|-v|--volume|-e|--env|--entrypoint|-h|--hostname|-m|--memory|-u|--username|-w|--workdir|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf') + local counter=$(__docker_pos_first_nonflag '--cidfile|--volumes-from|-v|--volume|-e|--env|--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf') if [ $cword -eq $counter ]; then __docker_image_repos_and_tags_and_ids diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index a6ae75076b..b8cf35cbe1 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1010,7 +1010,7 @@ image is removed. -p, --publish=[]: Map a network port to the container --rm=false: Automatically remove the container when it exits (incompatible with -d) -t, --tty=false: Allocate a pseudo-tty - -u, --username="": Username or UID + -u, --user="": Username or UID --dns=[]: Set custom dns servers for the container -v, --volume=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "container-dir" is missing, then docker creates a new volume. --volumes-from="": Mount all volumes from the given container(s) From 7379a22a8d1687fc004a129ec86fd6eb01c39ede Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sat, 11 Jan 2014 03:31:01 -0700 Subject: [PATCH 116/364] Fix symlink mounting issues by evaluating symlinks directly on the LHS of a bind-mount -v and by FollowSymlinkInScope on the RHS just before mounting Tested successfully with variations around mounting /var/run and /var/run/docker.sock inside a "debian" container directly at /var/run/docker.sock where /var/run is a symlink to "/run" on both the host and in the container. Fixes #3262 Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- container.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/container.go b/container.go index 79d9511d7b..5c947df001 100644 --- a/container.go +++ b/container.go @@ -18,6 +18,7 @@ import ( "net" "os" "path" + "path/filepath" "strings" "sync" "syscall" @@ -645,7 +646,14 @@ func (container *Container) Start() (err error) { mountAs = "rw" } - if err := mount.Mount(v, path.Join(root, r), "none", fmt.Sprintf("bind,%s", mountAs)); err != nil { + r = path.Join(root, r) + if p, err := utils.FollowSymlinkInScope(r, root); err != nil { + return err + } else { + r = p + } + + if err := mount.Mount(v, r, "none", fmt.Sprintf("bind,%s", mountAs)); err != nil { return err } } @@ -806,7 +814,7 @@ func (container *Container) createVolumes() error { if strings.ToLower(bindMap.Mode) == "rw" { srcRW = true } - if stat, err := os.Lstat(bindMap.SrcPath); err != nil { + if stat, err := os.Stat(bindMap.SrcPath); err != nil { return err } else { volIsDir = stat.IsDir() @@ -827,6 +835,13 @@ func (container *Container) createVolumes() error { } srcRW = true // RW by default } + + if p, err := filepath.EvalSymlinks(srcPath); err != nil { + return err + } else { + srcPath = p + } + container.Volumes[volPath] = srcPath container.VolumesRW[volPath] = srcRW From 9152aab653294f8f8b2f206a2ccc3c8603f33d0c Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 20 Jan 2014 01:26:01 -0700 Subject: [PATCH 117/364] Update bash completion to ignore stderr output of docker binary Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- contrib/completion/bash/docker | 40 +++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index fb1786c335..1449330986 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -21,57 +21,61 @@ # If the docker daemon is using a unix socket for communication your user # must have access to the socket for the completions to function correctly +__docker_q() { + docker 2>/dev/null "$@" +} + __docker_containers_all() { - local containers="$( docker ps -a -q )" - local names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" + local containers="$( __docker_q ps -a -q )" + local names="$( __docker_q inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) ) } __docker_containers_running() { - local containers="$( docker ps -q )" - local names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" + local containers="$( __docker_q ps -q )" + local names="$( __docker_q inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) ) } __docker_containers_stopped() { - local containers="$( { docker ps -a -q; docker ps -q; } | sort | uniq -u )" - local names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" + local containers="$( { __docker_q ps -a -q; __docker_q ps -q; } | sort | uniq -u )" + local names="$( __docker_q inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" COMPREPLY=( $( compgen -W "$names $containers" -- "$cur" ) ) } __docker_image_repos() { - local repos="$( docker images | awk 'NR>1{print $1}' | grep -v '^$' )" + local repos="$( __docker_q images | awk 'NR>1{print $1}' | grep -v '^$' )" COMPREPLY=( $( compgen -W "$repos" -- "$cur" ) ) } __docker_image_repos_and_tags() { - local repos="$( docker images | awk 'NR>1{print $1}' | grep -v '^$' )" - local images="$( docker images | awk 'NR>1{print $1":"$2}' | grep -v '^:' )" + local repos="$( __docker_q images | awk 'NR>1{print $1}' | grep -v '^$' )" + local images="$( __docker_q images | awk 'NR>1{print $1":"$2}' | grep -v '^:' )" COMPREPLY=( $( compgen -W "$repos $images" -- "$cur" ) ) __ltrim_colon_completions "$cur" } __docker_image_repos_and_tags_and_ids() { - local repos="$( docker images | awk 'NR>1{print $1}' | grep -v '^$' )" - local images="$( docker images | awk 'NR>1{print $1":"$2}' | grep -v '^:' )" - local ids="$( docker images -a -q )" + local repos="$( __docker_q images | awk 'NR>1{print $1}' | grep -v '^$' )" + local images="$( __docker_q images | awk 'NR>1{print $1":"$2}' | grep -v '^:' )" + local ids="$( __docker_q images -a -q )" COMPREPLY=( $( compgen -W "$repos $images $ids" -- "$cur" ) ) __ltrim_colon_completions "$cur" } __docker_containers_and_images() { - local containers="$( docker ps -a -q )" - local names="$( docker inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" - local repos="$( docker images | awk 'NR>1{print $1}' | grep -v '^$' )" - local images="$( docker images | awk 'NR>1{print $1":"$2}' | grep -v '^:' )" - local ids="$( docker images -a -q )" + local containers="$( __docker_q ps -a -q )" + local names="$( __docker_q inspect --format '{{.Name}}' $containers | sed 's,^/,,' )" + local repos="$( __docker_q images | awk 'NR>1{print $1}' | grep -v '^$' )" + local images="$( __docker_q images | awk 'NR>1{print $1":"$2}' | grep -v '^:' )" + local ids="$( __docker_q images -a -q )" COMPREPLY=( $( compgen -W "$containers $names $repos $images $ids" -- "$cur" ) ) __ltrim_colon_completions "$cur" } @@ -389,7 +393,7 @@ _docker_ps() { case "$prev" in --since-id|--before-id) - COMPREPLY=( $( compgen -W "$(docker ps -a -q)" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "$( __docker_q ps -a -q )" -- "$cur" ) ) # TODO replace this with __docker_containers_all # see https://github.com/dotcloud/docker/issues/3565 return From ff662446f774d573442baa526263544df2570ebf Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 20 Jan 2014 10:20:29 -0800 Subject: [PATCH 118/364] Add MAINTAINER and remove docker/utils dep from pkg/sysinfo Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/sysinfo/MAINTAINERS | 2 ++ pkg/sysinfo/sysinfo.go | 5 +---- 2 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 pkg/sysinfo/MAINTAINERS diff --git a/pkg/sysinfo/MAINTAINERS b/pkg/sysinfo/MAINTAINERS new file mode 100644 index 0000000000..dcc038e7f3 --- /dev/null +++ b/pkg/sysinfo/MAINTAINERS @@ -0,0 +1,2 @@ +Michael Crosby (@crosbymichael) +Guillaume J. Charmes (@creack) diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 884fcbde7c..f9bd6b8a90 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -1,8 +1,7 @@ package sysinfo import ( - "github.com/dotcloud/docker/cgroups" - "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/cgroups" // FIXME: move cgroups in pkg "io/ioutil" "log" "os" @@ -45,10 +44,8 @@ func New(quiet bool) *SysInfo { // Check if AppArmor seems to be enabled on this system. if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) { - utils.Debugf("/sys/kernel/security/apparmor not found; assuming AppArmor is not enabled.") sysInfo.AppArmor = false } else { - utils.Debugf("/sys/kernel/security/apparmor found; assuming AppArmor is enabled.") sysInfo.AppArmor = true } return sysInfo From fe02d589272e61f75c9e794afb82641e78dfe8a3 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sat, 18 Jan 2014 11:22:05 -0700 Subject: [PATCH 119/364] Update Travis to also test compiling the docs Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 0650c368d2..513fe70c18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,9 +20,11 @@ before_script: git fetch --append --no-tags upstream refs/heads/$upstream:refs/remotes/upstream/$upstream # sometimes we have upstream master already as origin/master (PRs), but other times we don't, so let's just make sure we have a completely unambiguous way to specify "upstream master" from here out # but if it's a PR against non-master, we need that upstream branch instead :) + - sudo pip install -r docs/requirements.txt script: - hack/travis/dco.py - hack/travis/gofmt.py + - make -sC docs SPHINXOPTS=-q docs # man # vim:set sw=2 ts=2: From 10fc518fba8bf0f4a32cb1824664abfbacf5d884 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sat, 18 Jan 2014 10:07:02 -0700 Subject: [PATCH 120/364] Fix docs manpage generation Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- docs/sources/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/conf.py b/docs/sources/conf.py index c1480eb0c2..add50ce69c 100644 --- a/docs/sources/conf.py +++ b/docs/sources/conf.py @@ -235,8 +235,8 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('commandline/cli', 'docker', u'Docker Documentation', - [u'Team Docker'], 1) + ('reference/commandline/cli', 'docker', u'Docker CLI Documentation', + [u'Team Docker'], 1), ] # If true, show URL addresses after external links. From 1c152f443df254e18d44790cd0dbfb6f6361e018 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sat, 18 Jan 2014 10:07:32 -0700 Subject: [PATCH 121/364] Add Dockerfile man page generation from our Dockerfile reference page Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- docs/sources/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/conf.py b/docs/sources/conf.py index add50ce69c..12f5b57841 100644 --- a/docs/sources/conf.py +++ b/docs/sources/conf.py @@ -237,6 +237,8 @@ latex_documents = [ man_pages = [ ('reference/commandline/cli', 'docker', u'Docker CLI Documentation', [u'Team Docker'], 1), + ('reference/builder', 'Dockerfile', u'Dockerfile Documentation', + [u'Team Docker'], 5), ] # If true, show URL addresses after external links. From 274863f221b55f2eb60fde7457e30998438a3f72 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 20 Jan 2014 11:29:24 -0700 Subject: [PATCH 122/364] Update Travis to also compile the man page(s) now that they compile properly again Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 513fe70c18..8a43d9a462 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,6 @@ before_script: script: - hack/travis/dco.py - hack/travis/gofmt.py - - make -sC docs SPHINXOPTS=-q docs # man + - make -sC docs SPHINXOPTS=-q docs man # vim:set sw=2 ts=2: From 2e094db6390dde57263c07665290b1477bb6df7e Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Mon, 20 Jan 2014 13:52:26 -0500 Subject: [PATCH 123/364] Extract mount into pkg. Mount is self contained and generic, it should be in pkg, to allow other pkg modules to use it. Docker-DCO-1.1-Signed-off-by: Paul Nasrat (github: pnasrat) --- cgroups/cgroups.go | 2 +- container.go | 2 +- execdriver/chroot/driver.go | 2 +- graphdriver/aufs/aufs.go | 2 +- {mount => pkg/mount}/MAINTAINERS | 0 {mount => pkg/mount}/flags_darwin.go | 0 {mount => pkg/mount}/flags_linux.go | 0 {mount => pkg/mount}/mount.go | 0 {mount => pkg/mount}/mount_test.go | 0 {mount => pkg/mount}/mounter_darwin.go | 0 {mount => pkg/mount}/mounter_linux.go | 0 {mount => pkg/mount}/mountinfo.go | 0 {mount => pkg/mount}/mountinfo_test.go | 0 13 files changed, 4 insertions(+), 4 deletions(-) rename {mount => pkg/mount}/MAINTAINERS (100%) rename {mount => pkg/mount}/flags_darwin.go (100%) rename {mount => pkg/mount}/flags_linux.go (100%) rename {mount => pkg/mount}/mount.go (100%) rename {mount => pkg/mount}/mount_test.go (100%) rename {mount => pkg/mount}/mounter_darwin.go (100%) rename {mount => pkg/mount}/mounter_linux.go (100%) rename {mount => pkg/mount}/mountinfo.go (100%) rename {mount => pkg/mount}/mountinfo_test.go (100%) diff --git a/cgroups/cgroups.go b/cgroups/cgroups.go index fdcab8c3da..07867a6d58 100644 --- a/cgroups/cgroups.go +++ b/cgroups/cgroups.go @@ -3,7 +3,7 @@ package cgroups import ( "bufio" "fmt" - "github.com/dotcloud/docker/mount" + "github.com/dotcloud/docker/pkg/mount" "io" "io/ioutil" "os" diff --git a/container.go b/container.go index 79d9511d7b..f81c374254 100644 --- a/container.go +++ b/container.go @@ -8,7 +8,7 @@ import ( "github.com/dotcloud/docker/cgroups" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/graphdriver" - "github.com/dotcloud/docker/mount" + "github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/utils" "github.com/kr/pty" diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index b9f3f04016..749926edf7 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -3,7 +3,7 @@ package chroot import ( "fmt" "github.com/dotcloud/docker/execdriver" - "github.com/dotcloud/docker/mount" + "github.com/dotcloud/docker/pkg/mount" "os" "os/exec" ) diff --git a/graphdriver/aufs/aufs.go b/graphdriver/aufs/aufs.go index 8875cac6d9..2e9fa8298a 100644 --- a/graphdriver/aufs/aufs.go +++ b/graphdriver/aufs/aufs.go @@ -25,7 +25,7 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/graphdriver" - mountpk "github.com/dotcloud/docker/mount" + mountpk "github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/utils" "os" "os/exec" diff --git a/mount/MAINTAINERS b/pkg/mount/MAINTAINERS similarity index 100% rename from mount/MAINTAINERS rename to pkg/mount/MAINTAINERS diff --git a/mount/flags_darwin.go b/pkg/mount/flags_darwin.go similarity index 100% rename from mount/flags_darwin.go rename to pkg/mount/flags_darwin.go diff --git a/mount/flags_linux.go b/pkg/mount/flags_linux.go similarity index 100% rename from mount/flags_linux.go rename to pkg/mount/flags_linux.go diff --git a/mount/mount.go b/pkg/mount/mount.go similarity index 100% rename from mount/mount.go rename to pkg/mount/mount.go diff --git a/mount/mount_test.go b/pkg/mount/mount_test.go similarity index 100% rename from mount/mount_test.go rename to pkg/mount/mount_test.go diff --git a/mount/mounter_darwin.go b/pkg/mount/mounter_darwin.go similarity index 100% rename from mount/mounter_darwin.go rename to pkg/mount/mounter_darwin.go diff --git a/mount/mounter_linux.go b/pkg/mount/mounter_linux.go similarity index 100% rename from mount/mounter_linux.go rename to pkg/mount/mounter_linux.go diff --git a/mount/mountinfo.go b/pkg/mount/mountinfo.go similarity index 100% rename from mount/mountinfo.go rename to pkg/mount/mountinfo.go diff --git a/mount/mountinfo_test.go b/pkg/mount/mountinfo_test.go similarity index 100% rename from mount/mountinfo_test.go rename to pkg/mount/mountinfo_test.go From 2553029959929184696e25ebdba4631ba7aeb8a6 Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Mon, 20 Jan 2014 14:15:44 -0500 Subject: [PATCH 124/364] Extract cgroups pkg. Initial move before enhancing cgroups package. Docker-DCO-1.1-Signed-off-by: Paul Nasrat (github: pnasrat) --- container.go | 2 +- execdriver/driver.go | 2 +- execdriver/lxc/lxc_template.go | 2 +- execdriver/lxc/lxc_template_unit_test.go | 2 +- {cgroups => pkg/cgroups}/MAINTAINERS | 0 {cgroups => pkg/cgroups}/cgroups.go | 0 {cgroups => pkg/cgroups}/cgroups_test.go | 0 pkg/sysinfo/sysinfo.go | 2 +- server.go | 2 +- 9 files changed, 6 insertions(+), 6 deletions(-) rename {cgroups => pkg/cgroups}/MAINTAINERS (100%) rename {cgroups => pkg/cgroups}/cgroups.go (100%) rename {cgroups => pkg/cgroups}/cgroups_test.go (100%) diff --git a/container.go b/container.go index f81c374254..fc92c26444 100644 --- a/container.go +++ b/container.go @@ -5,9 +5,9 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/cgroups" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/utils" diff --git a/execdriver/driver.go b/execdriver/driver.go index 5f3d25a566..3e0210bf3e 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -2,7 +2,7 @@ package execdriver import ( "errors" - "github.com/dotcloud/docker/cgroups" + "github.com/dotcloud/docker/pkg/cgroups" "os/exec" "syscall" ) diff --git a/execdriver/lxc/lxc_template.go b/execdriver/lxc/lxc_template.go index deaa7a66ae..ac3481836d 100644 --- a/execdriver/lxc/lxc_template.go +++ b/execdriver/lxc/lxc_template.go @@ -1,7 +1,7 @@ package lxc import ( - "github.com/dotcloud/docker/cgroups" + "github.com/dotcloud/docker/pkg/cgroups" "strings" "text/template" ) diff --git a/execdriver/lxc/lxc_template_unit_test.go b/execdriver/lxc/lxc_template_unit_test.go index fe65fe5cbc..8ea27646ec 100644 --- a/execdriver/lxc/lxc_template_unit_test.go +++ b/execdriver/lxc/lxc_template_unit_test.go @@ -3,8 +3,8 @@ package lxc import ( "bufio" "fmt" - "github.com/dotcloud/docker/cgroups" "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/pkg/cgroups" "io/ioutil" "math/rand" "os" diff --git a/cgroups/MAINTAINERS b/pkg/cgroups/MAINTAINERS similarity index 100% rename from cgroups/MAINTAINERS rename to pkg/cgroups/MAINTAINERS diff --git a/cgroups/cgroups.go b/pkg/cgroups/cgroups.go similarity index 100% rename from cgroups/cgroups.go rename to pkg/cgroups/cgroups.go diff --git a/cgroups/cgroups_test.go b/pkg/cgroups/cgroups_test.go similarity index 100% rename from cgroups/cgroups_test.go rename to pkg/cgroups/cgroups_test.go diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index f9bd6b8a90..b3463f8cfe 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -1,7 +1,7 @@ package sysinfo import ( - "github.com/dotcloud/docker/cgroups" // FIXME: move cgroups in pkg + "github.com/dotcloud/docker/pkg/cgroups" "io/ioutil" "log" "os" diff --git a/server.go b/server.go index a25ba74a06..b3e858ba2d 100644 --- a/server.go +++ b/server.go @@ -6,8 +6,8 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" - "github.com/dotcloud/docker/cgroups" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" From 4fb1db7f742fb34a7a06621d0698063de87a572c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 17 Jan 2014 11:09:39 +0100 Subject: [PATCH 125/364] archive: Remove unused features This simplifies that code that calls out to tar by removing support for now unused features. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- archive/archive.go | 82 ++++------------------------------------ archive/archive_test.go | 6 +-- container.go | 1 - graphdriver/aufs/aufs.go | 1 - 4 files changed, 11 insertions(+), 79 deletions(-) diff --git a/archive/archive.go b/archive/archive.go index 89a360c906..d02767c7d9 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -23,10 +23,7 @@ type Compression int type TarOptions struct { Includes []string - Excludes []string - Recursive bool Compression Compression - CreateFiles []string } const ( @@ -66,7 +63,7 @@ func DetectCompression(source []byte) Compression { func xzDecompress(archive io.Reader) (io.Reader, error) { args := []string{"xz", "-d", "-c", "-q"} - return CmdStream(exec.Command(args[0], args[1:]...), archive, nil) + return CmdStream(exec.Command(args[0], args[1:]...), archive) } func DecompressStream(archive io.Reader) (io.Reader, error) { @@ -207,7 +204,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader *tar.Reader) // Tar creates an archive from the directory at `path`, and returns it as a // stream of bytes. func Tar(path string, compression Compression) (io.Reader, error) { - return TarFilter(path, &TarOptions{Recursive: true, Compression: compression}) + return TarFilter(path, &TarOptions{Compression: compression}) } func escapeName(name string) string { @@ -235,50 +232,12 @@ func TarFilter(path string, options *TarOptions) (io.Reader, error) { } args = append(args, "-c"+options.Compression.Flag()) - for _, exclude := range options.Excludes { - args = append(args, fmt.Sprintf("--exclude=%s", exclude)) - } - - if !options.Recursive { - args = append(args, "--no-recursion") - } - files := "" for _, f := range options.Includes { files = files + escapeName(f) + "\n" } - tmpDir := "" - - if options.CreateFiles != nil { - var err error // Can't use := here or we override the outer tmpDir - tmpDir, err = ioutil.TempDir("", "docker-tar") - if err != nil { - return nil, err - } - - files = files + "-C" + tmpDir + "\n" - for _, f := range options.CreateFiles { - path := filepath.Join(tmpDir, f) - err := os.MkdirAll(filepath.Dir(path), 0600) - if err != nil { - return nil, err - } - - if file, err := os.OpenFile(path, os.O_CREATE, 0600); err != nil { - return nil, err - } else { - file.Close() - } - files = files + escapeName(f) + "\n" - } - } - - return CmdStream(exec.Command(args[0], args[1:]...), bytes.NewBufferString(files), func() { - if tmpDir != "" { - _ = os.RemoveAll(tmpDir) - } - }) + return CmdStream(exec.Command(args[0], args[1:]...), bytes.NewBufferString(files)) } // Untar reads a stream of bytes from `archive`, parses it as a tar archive, @@ -311,19 +270,6 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error { return err } - if options != nil { - excludeFile := false - for _, exclude := range options.Excludes { - if strings.HasPrefix(hdr.Name, exclude) { - excludeFile = true - break - } - } - if excludeFile { - continue - } - } - // Normalize name, for safety and for a simple is-root check hdr.Name = filepath.Clean(hdr.Name) @@ -378,9 +324,9 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error { // TarUntar is a convenience function which calls Tar and Untar, with // the output of one piped into the other. If either Tar or Untar fails, // TarUntar aborts and returns the error. -func TarUntar(src string, filter []string, dst string) error { - utils.Debugf("TarUntar(%s %s %s)", src, filter, dst) - archive, err := TarFilter(src, &TarOptions{Compression: Uncompressed, Includes: filter, Recursive: true}) +func TarUntar(src string, dst string) error { + utils.Debugf("TarUntar(%s %s)", src, dst) + archive, err := TarFilter(src, &TarOptions{Compression: Uncompressed}) if err != nil { return err } @@ -417,7 +363,7 @@ func CopyWithTar(src, dst string) error { return err } utils.Debugf("Calling TarUntar(%s, %s)", src, dst) - return TarUntar(src, nil, dst) + return TarUntar(src, dst) } // CopyFileWithTar emulates the behavior of the 'cp' command-line @@ -480,13 +426,10 @@ func CopyFileWithTar(src, dst string) (err error) { // CmdStream executes a command, and returns its stdout as a stream. // If the command fails to run or doesn't complete successfully, an error // will be returned, including anything written on stderr. -func CmdStream(cmd *exec.Cmd, input io.Reader, atEnd func()) (io.Reader, error) { +func CmdStream(cmd *exec.Cmd, input io.Reader) (io.Reader, error) { if input != nil { stdin, err := cmd.StdinPipe() if err != nil { - if atEnd != nil { - atEnd() - } return nil, err } // Write stdin if any @@ -497,16 +440,10 @@ func CmdStream(cmd *exec.Cmd, input io.Reader, atEnd func()) (io.Reader, error) } stdout, err := cmd.StdoutPipe() if err != nil { - if atEnd != nil { - atEnd() - } return nil, err } stderr, err := cmd.StderrPipe() if err != nil { - if atEnd != nil { - atEnd() - } return nil, err } pipeR, pipeW := io.Pipe() @@ -531,9 +468,6 @@ func CmdStream(cmd *exec.Cmd, input io.Reader, atEnd func()) (io.Reader, error) } else { pipeW.Close() } - if atEnd != nil { - atEnd() - } }() // Run the command and return the pipe if err := cmd.Start(); err != nil { diff --git a/archive/archive_test.go b/archive/archive_test.go index 684d99dc14..de7b2f263f 100644 --- a/archive/archive_test.go +++ b/archive/archive_test.go @@ -14,7 +14,7 @@ import ( func TestCmdStreamLargeStderr(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") - out, err := CmdStream(cmd, nil, nil) + out, err := CmdStream(cmd, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -35,7 +35,7 @@ func TestCmdStreamLargeStderr(t *testing.T) { func TestCmdStreamBad(t *testing.T) { badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1") - out, err := CmdStream(badCmd, nil, nil) + out, err := CmdStream(badCmd, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -50,7 +50,7 @@ func TestCmdStreamBad(t *testing.T) { func TestCmdStreamGood(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0") - out, err := CmdStream(cmd, nil, nil) + out, err := CmdStream(cmd, nil) if err != nil { t.Fatal(err) } diff --git a/container.go b/container.go index f81c374254..45704d274a 100644 --- a/container.go +++ b/container.go @@ -1457,7 +1457,6 @@ func (container *Container) Copy(resource string) (archive.Archive, error) { return archive.TarFilter(basePath, &archive.TarOptions{ Compression: archive.Uncompressed, Includes: filter, - Recursive: true, }) } diff --git a/graphdriver/aufs/aufs.go b/graphdriver/aufs/aufs.go index 2e9fa8298a..81cdd88134 100644 --- a/graphdriver/aufs/aufs.go +++ b/graphdriver/aufs/aufs.go @@ -225,7 +225,6 @@ func (a *Driver) Get(id string) (string, error) { // Returns an archive of the contents for the id func (a *Driver) Diff(id string) (archive.Archive, error) { return archive.TarFilter(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{ - Recursive: true, Compression: archive.Uncompressed, }) } From 5b77e51e0a15eddefcb40380673df8c0c24f95d1 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 17 Jan 2014 11:21:36 +0100 Subject: [PATCH 126/364] archive: Extract code from ExportChanges to addTarFile() This is the code that takes a normal file and adds it to a TarWriter. We extract it so that we can share it with Tar(). Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- archive/archive.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++ archive/changes.go | 64 +++------------------------------------------ 2 files changed, 68 insertions(+), 61 deletions(-) diff --git a/archive/archive.go b/archive/archive.go index d02767c7d9..cae5833039 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -15,6 +15,7 @@ import ( "path/filepath" "strings" "syscall" + "time" ) type Archive io.Reader @@ -123,6 +124,70 @@ func (compression *Compression) Extension() string { return "" } +func addTarFile(path, name string, tw *tar.Writer) error { + var stat syscall.Stat_t + if err := syscall.Lstat(path, &stat); err != nil { + return err + } + + mtim := getLastModification(&stat) + atim := getLastAccess(&stat) + hdr := &tar.Header{ + Name: name, + Mode: int64(stat.Mode & 07777), + Uid: int(stat.Uid), + Gid: int(stat.Gid), + ModTime: time.Unix(int64(mtim.Sec), int64(mtim.Nsec)), + AccessTime: time.Unix(int64(atim.Sec), int64(atim.Nsec)), + } + + if stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR { + hdr.Typeflag = tar.TypeDir + } else if stat.Mode&syscall.S_IFLNK == syscall.S_IFLNK { + hdr.Typeflag = tar.TypeSymlink + if link, err := os.Readlink(path); err != nil { + return err + } else { + hdr.Linkname = link + } + } else if stat.Mode&syscall.S_IFBLK == syscall.S_IFBLK || + stat.Mode&syscall.S_IFCHR == syscall.S_IFCHR { + if stat.Mode&syscall.S_IFBLK == syscall.S_IFBLK { + hdr.Typeflag = tar.TypeBlock + } else { + hdr.Typeflag = tar.TypeChar + } + hdr.Devmajor = int64(major(uint64(stat.Rdev))) + hdr.Devminor = int64(minor(uint64(stat.Rdev))) + } else if stat.Mode&syscall.S_IFIFO == syscall.S_IFIFO || + stat.Mode&syscall.S_IFSOCK == syscall.S_IFSOCK { + hdr.Typeflag = tar.TypeFifo + } else if stat.Mode&syscall.S_IFREG == syscall.S_IFREG { + hdr.Typeflag = tar.TypeReg + hdr.Size = stat.Size + } else { + return fmt.Errorf("Unknown file type: %s\n", path) + } + + if err := tw.WriteHeader(hdr); err != nil { + return err + } + + if hdr.Typeflag == tar.TypeReg { + if file, err := os.Open(path); err != nil { + return err + } else { + _, err := io.Copy(tw, file) + if err != nil { + return err + } + file.Close() + } + } + + return nil +} + func createTarFile(path, extractDir string, hdr *tar.Header, reader *tar.Reader) error { switch hdr.Typeflag { case tar.TypeDir: diff --git a/archive/changes.go b/archive/changes.go index c67bec8ce2..25406f5cec 100644 --- a/archive/changes.go +++ b/archive/changes.go @@ -347,70 +347,12 @@ func ExportChanges(dir string, changes []Change) (Archive, error) { } } else { path := filepath.Join(dir, change.Path) - - var stat syscall.Stat_t - if err := syscall.Lstat(path, &stat); err != nil { - utils.Debugf("Can't stat source file: %s\n", err) - continue - } - - mtim := getLastModification(&stat) - atim := getLastAccess(&stat) - hdr := &tar.Header{ - Name: change.Path[1:], - Mode: int64(stat.Mode & 07777), - Uid: int(stat.Uid), - Gid: int(stat.Gid), - ModTime: time.Unix(int64(mtim.Sec), int64(mtim.Nsec)), - AccessTime: time.Unix(int64(atim.Sec), int64(atim.Nsec)), - } - - if stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR { - hdr.Typeflag = tar.TypeDir - } else if stat.Mode&syscall.S_IFLNK == syscall.S_IFLNK { - hdr.Typeflag = tar.TypeSymlink - if link, err := os.Readlink(path); err != nil { - utils.Debugf("Can't readlink source file: %s\n", err) - continue - } else { - hdr.Linkname = link - } - } else if stat.Mode&syscall.S_IFBLK == syscall.S_IFBLK || - stat.Mode&syscall.S_IFCHR == syscall.S_IFCHR { - if stat.Mode&syscall.S_IFBLK == syscall.S_IFBLK { - hdr.Typeflag = tar.TypeBlock - } else { - hdr.Typeflag = tar.TypeChar - } - hdr.Devmajor = int64(major(uint64(stat.Rdev))) - hdr.Devminor = int64(minor(uint64(stat.Rdev))) - } else if stat.Mode&syscall.S_IFIFO == syscall.S_IFIFO || - stat.Mode&syscall.S_IFSOCK == syscall.S_IFSOCK { - hdr.Typeflag = tar.TypeFifo - } else if stat.Mode&syscall.S_IFREG == syscall.S_IFREG { - hdr.Typeflag = tar.TypeReg - hdr.Size = stat.Size - } else { - utils.Debugf("Unknown file type: %s\n", path) - continue - } - - if err := tw.WriteHeader(hdr); err != nil { - utils.Debugf("Can't write tar header: %s\n", err) - } - if hdr.Typeflag == tar.TypeReg { - if file, err := os.Open(path); err != nil { - utils.Debugf("Can't open file: %s\n", err) - } else { - _, err := io.Copy(tw, file) - if err != nil { - utils.Debugf("Can't copy file: %s\n", err) - } - file.Close() - } + if err := addTarFile(path, change.Path[1:], tw); err != nil { + utils.Debugf("Can't add file %s to tar: %s\n", path, err) } } } + // Make sure to check the error on Close. if err := tw.Close(); err != nil { utils.Debugf("Can't close layer: %s\n", err) From bab8efbf050e1bc2d5c2ff64c6161ef4d323bc3f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 20 Jan 2014 11:48:47 +0100 Subject: [PATCH 127/364] Simplify addTarFile We can use tar.FileInfoHeader to do much of the work. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- archive/archive.go | 58 +++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/archive/archive.go b/archive/archive.go index cae5833039..ee8c419781 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -15,7 +15,6 @@ import ( "path/filepath" "strings" "syscall" - "time" ) type Archive io.Reader @@ -125,48 +124,33 @@ func (compression *Compression) Extension() string { } func addTarFile(path, name string, tw *tar.Writer) error { - var stat syscall.Stat_t - if err := syscall.Lstat(path, &stat); err != nil { + fi, err := os.Lstat(path) + if err != nil { return err } - mtim := getLastModification(&stat) - atim := getLastAccess(&stat) - hdr := &tar.Header{ - Name: name, - Mode: int64(stat.Mode & 07777), - Uid: int(stat.Uid), - Gid: int(stat.Gid), - ModTime: time.Unix(int64(mtim.Sec), int64(mtim.Nsec)), - AccessTime: time.Unix(int64(atim.Sec), int64(atim.Nsec)), + link := "" + if fi.Mode() & os.ModeSymlink != 0 { + if link, err = os.Readlink(path); err != nil { + return err + } } - if stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR { - hdr.Typeflag = tar.TypeDir - } else if stat.Mode&syscall.S_IFLNK == syscall.S_IFLNK { - hdr.Typeflag = tar.TypeSymlink - if link, err := os.Readlink(path); err != nil { - return err - } else { - hdr.Linkname = link + hdr, err := tar.FileInfoHeader(fi, link) + if err != nil { + return err + } + + hdr.Name = name + + stat, ok := fi.Sys().(*syscall.Stat_t) + if ok { + // Currently go does not fill in the major/minors + if stat.Mode&syscall.S_IFBLK == syscall.S_IFBLK || + stat.Mode&syscall.S_IFCHR == syscall.S_IFCHR { + hdr.Devmajor = int64(major(uint64(stat.Rdev))) + hdr.Devminor = int64(minor(uint64(stat.Rdev))) } - } else if stat.Mode&syscall.S_IFBLK == syscall.S_IFBLK || - stat.Mode&syscall.S_IFCHR == syscall.S_IFCHR { - if stat.Mode&syscall.S_IFBLK == syscall.S_IFBLK { - hdr.Typeflag = tar.TypeBlock - } else { - hdr.Typeflag = tar.TypeChar - } - hdr.Devmajor = int64(major(uint64(stat.Rdev))) - hdr.Devminor = int64(minor(uint64(stat.Rdev))) - } else if stat.Mode&syscall.S_IFIFO == syscall.S_IFIFO || - stat.Mode&syscall.S_IFSOCK == syscall.S_IFSOCK { - hdr.Typeflag = tar.TypeFifo - } else if stat.Mode&syscall.S_IFREG == syscall.S_IFREG { - hdr.Typeflag = tar.TypeReg - hdr.Size = stat.Size - } else { - return fmt.Errorf("Unknown file type: %s\n", path) } if err := tw.WriteHeader(hdr); err != nil { From 5ea48aa7f8d3839877b869fdfcea2d5de0972fbf Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 20 Jan 2014 12:07:34 +0100 Subject: [PATCH 128/364] Implement TarFilter in go, rather than calling out to tar This uses a plain filepath.Walk + addTarFile to create a tar file, optionially compressing it with gzip. Unfortunately go only has gzip compression support, not bzip2 or xz. However, this is not a regression, as docker currently uses *no* compression for TarFilter(). The only compression of tarfiles currently happens in utils/tarsum.go, and that manually does gzip compression. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- archive/archive.go | 78 +++++++++++++++++++++++++++++++---------- archive/archive_test.go | 12 +++++-- 2 files changed, 69 insertions(+), 21 deletions(-) diff --git a/archive/archive.go b/archive/archive.go index ee8c419781..1d21018474 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -97,16 +97,20 @@ func DecompressStream(archive io.Reader) (io.Reader, error) { } } -func (compression *Compression) Flag() string { - switch *compression { - case Bzip2: - return "j" +func CompressStream(dest io.WriteCloser, compression Compression) (io.WriteCloser, error) { + + switch compression { + case Uncompressed: + return dest, nil case Gzip: - return "z" - case Xz: - return "J" + return gzip.NewWriter(dest), nil + case Bzip2, Xz: + // archive/bzip2 does not support writing, and there is no xz support at all + // However, this is not a problem as docker only currently generates gzipped tars + return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) + default: + return nil, fmt.Errorf("Unsupported compression format %s", (&compression).Extension()) } - return "" } func (compression *Compression) Extension() string { @@ -130,7 +134,7 @@ func addTarFile(path, name string, tw *tar.Writer) error { } link := "" - if fi.Mode() & os.ModeSymlink != 0 { + if fi.Mode()&os.ModeSymlink != 0 { if link, err = os.Readlink(path); err != nil { return err } @@ -274,19 +278,55 @@ func escapeName(name string) string { // Tar creates an archive from the directory at `path`, only including files whose relative // paths are included in `filter`. If `filter` is nil, then all files are included. -func TarFilter(path string, options *TarOptions) (io.Reader, error) { - args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-"} - if options.Includes == nil { - options.Includes = []string{"."} - } - args = append(args, "-c"+options.Compression.Flag()) +func TarFilter(srcPath string, options *TarOptions) (io.Reader, error) { + pipeReader, pipeWriter := io.Pipe() - files := "" - for _, f := range options.Includes { - files = files + escapeName(f) + "\n" + compressWriter, err := CompressStream(pipeWriter, options.Compression) + if err != nil { + return nil, err } - return CmdStream(exec.Command(args[0], args[1:]...), bytes.NewBufferString(files)) + tw := tar.NewWriter(compressWriter) + + go func() { + // In general we log errors here but ignore them because + // during e.g. a diff operation the container can continue + // mutating the filesystem and we can see transient errors + // from this + + if options.Includes == nil { + options.Includes = []string{"."} + } + + for _, include := range options.Includes { + filepath.Walk(filepath.Join(srcPath, include), func(filePath string, f os.FileInfo, err error) error { + if err != nil { + utils.Debugf("Tar: Can't stat file %s to tar: %s\n", srcPath, err) + return nil + } + + relFilePath, err := filepath.Rel(srcPath, filePath) + if err != nil { + return nil + } + + if err := addTarFile(filePath, relFilePath, tw); err != nil { + utils.Debugf("Can't add file %s to tar: %s\n", srcPath, err) + } + return nil + }) + } + + // Make sure to check the error on Close. + if err := tw.Close(); err != nil { + utils.Debugf("Can't close tar writer: %s\n", err) + } + if err := compressWriter.Close(); err != nil { + utils.Debugf("Can't close compress writer: %s\n", err) + } + }() + + return pipeReader, nil } // Untar reads a stream of bytes from `archive`, parses it as a tar archive, diff --git a/archive/archive_test.go b/archive/archive_test.go index de7b2f263f..a5629deff1 100644 --- a/archive/archive_test.go +++ b/archive/archive_test.go @@ -89,6 +89,16 @@ func tarUntar(t *testing.T, origin string, compression Compression) error { if _, err := os.Stat(tmp); err != nil { return err } + + changes, err := ChangesDirs(origin, tmp) + if err != nil { + return err + } + + if len(changes) != 0 { + t.Fatalf("Unexpected differences after tarUntar: %v", changes) + } + return nil } @@ -108,8 +118,6 @@ func TestTarUntar(t *testing.T) { for _, c := range []Compression{ Uncompressed, Gzip, - Bzip2, - Xz, } { if err := tarUntar(t, origin, c); err != nil { t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err) From 6ed9b06e072888d1e7bd60106f17371a89b668a4 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 20 Jan 2014 12:29:55 -0700 Subject: [PATCH 129/364] Fix Travis build errors by bumping our python module versions to be newer See also https://bitbucket.org/birkenfeld/sphinx/issue/1281/sphinx-113-not-build-proper-manpages Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- docs/Dockerfile | 4 ++-- docs/requirements.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index ae0fa85e0d..0d703c8c84 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -8,8 +8,8 @@ MAINTAINER Nick Stinemates RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq make python-pip python-setuptools # pip installs from docs/requirements.txt, but here to increase cacheability -RUN pip install Sphinx==1.1.3 -RUN pip install sphinxcontrib-httpdomain==1.1.9 +RUN pip install Sphinx==1.2.1 +RUN pip install sphinxcontrib-httpdomain==1.2.0 ADD . /docs RUN make -C /docs clean docs diff --git a/docs/requirements.txt b/docs/requirements.txt index 095f7600cd..6f41142a84 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -Sphinx==1.1.3 -sphinxcontrib-httpdomain==1.1.9 +Sphinx==1.2.1 +sphinxcontrib-httpdomain==1.2.0 From 8a4824d1ad2fafd158fbaad52a658c5fd3f7de30 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 20 Jan 2014 20:35:16 +0100 Subject: [PATCH 130/364] hack: Remove GNU Tar requirement We no longer call out to tar, so this is not needed. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- hack/PACKAGERS.md | 1 - 1 file changed, 1 deletion(-) diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index 1dd039c3e3..2d869b6eda 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -120,7 +120,6 @@ The test suite will also download a small test container, so you will need inter To run properly, docker needs the following software to be installed at runtime: -* GNU Tar version 1.26 or later * iproute2 version 3.5 or later (build after 2012-05-21), and specifically the "ip" utility * iptables version 1.4 or later * The LXC utility scripts (http://lxc.sourceforge.net) version 0.8 or later From a2aab7757e236a895abf7b06836d8e3b84236429 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 20 Jan 2014 13:39:35 -0800 Subject: [PATCH 131/364] Make sure new repositories can be pushed with multiple tags Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- registry/registry.go | 3 +++ server.go | 25 ++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index a0da733ed9..b2d26a2dba 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -205,15 +205,18 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s } // Check if an image exists in the Registry +// TODO: This method should return the errors instead of masking them and returning false func (r *Registry) LookupRemoteImage(imgID, registry string, token []string) bool { req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil) if err != nil { + utils.Errorf("Error in LookupRemoteImage %s", err) return false } setTokenAuth(req, token) res, err := doWithCookies(r.client, req) if err != nil { + utils.Errorf("Error in LookupRemoteImage %s", err) return false } res.Body.Close() diff --git a/server.go b/server.go index 74604570f6..4e05f83e3a 100644 --- a/server.go +++ b/server.go @@ -1263,15 +1263,34 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName var repoData *registry.RepositoryData var imageIndex []*registry.ImgData - for imgId, tags := range tagsByImage { - for _, tag := range tags { + for _, imgId := range imgList { + if tags, exists := tagsByImage[imgId]; exists { + // If an image has tags you must add an entry in the image index + // for each tag + for _, tag := range tags { + imageIndex = append(imageIndex, ®istry.ImgData{ + ID: imgId, + Tag: tag, + }) + } + } else { + // If the image does not have a tag it still needs to be sent to the + // registry with an empty tag so that it is accociated with the repository imageIndex = append(imageIndex, ®istry.ImgData{ ID: imgId, - Tag: tag, + Tag: "", }) + } } + utils.Debugf("Preparing to push %s with the following images and tags\n", localRepo) + for _, data := range imageIndex { + utils.Debugf("Pushing ID: %s with Tag: %s\n", data.ID, data.Tag) + } + + // Register all the images in a repository with the registry + // If an image is not in this list it will not be associated with the repository repoData, err = r.PushImageJSONIndex(remoteName, imageIndex, false, nil) if err != nil { return err From 71c1646ba33da9b2567ad7a2f4cda0433c9718dd Mon Sep 17 00:00:00 2001 From: Paul Nasrat Date: Mon, 20 Jan 2014 16:23:02 -0500 Subject: [PATCH 132/364] Don't expose cgroups via the execdriver API. Use Resources to represent container limits rather than a cgroup specific field. Docker-DCO-1.1-Signed-off-by: Paul Nasrat (github: pnasrat) --- container.go | 5 ++-- execdriver/driver.go | 33 ++++++++++++++---------- execdriver/lxc/lxc_template.go | 18 ++++++------- execdriver/lxc/lxc_template_unit_test.go | 3 +-- pkg/cgroups/cgroups.go | 6 ----- 5 files changed, 31 insertions(+), 34 deletions(-) diff --git a/container.go b/container.go index fc92c26444..3eebb641a1 100644 --- a/container.go +++ b/container.go @@ -7,7 +7,6 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/graphdriver" - "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/utils" @@ -671,7 +670,7 @@ func (container *Container) Start() (err error) { driverConfig = append(driverConfig, fmt.Sprintf("%s = %s", pair.Key, pair.Value)) } } - cgroupValues := &cgroups.Values{ + resources := &execdriver.Resources{ Memory: container.Config.Memory, MemorySwap: container.Config.MemorySwap, CpuShares: container.Config.CpuShares, @@ -689,7 +688,7 @@ func (container *Container) Start() (err error) { Tty: container.Config.Tty, User: container.Config.User, Config: driverConfig, - Cgroups: cgroupValues, + Resources: resources, } container.process.SysProcAttr = &syscall.SysProcAttr{Setsid: true} diff --git a/execdriver/driver.go b/execdriver/driver.go index 3e0210bf3e..b56404ee41 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -2,7 +2,6 @@ package execdriver import ( "errors" - "github.com/dotcloud/docker/pkg/cgroups" "os/exec" "syscall" ) @@ -76,24 +75,30 @@ type Network struct { Mtu int `json:"mtu"` } +type Resources struct { + Memory int64 `json:"memory"` + MemorySwap int64 `json:"memory_swap"` + CpuShares int64 `json:"cpu_shares"` +} + // Process wrapps an os/exec.Cmd to add more metadata // TODO: Rename to Command type Process struct { exec.Cmd - ID string `json:"id"` - Privileged bool `json:"privileged"` - User string `json:"user"` - Rootfs string `json:"rootfs"` // root fs of the container - InitPath string `json:"initpath"` // dockerinit - Entrypoint string `json:"entrypoint"` - Arguments []string `json:"arguments"` - WorkingDir string `json:"working_dir"` - ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver - Tty bool `json:"tty"` - Network *Network `json:"network"` // if network is nil then networking is disabled - Config []string `json:"config"` // generic values that specific drivers can consume - Cgroups *cgroups.Values `json:"cgroups"` + ID string `json:"id"` + Privileged bool `json:"privileged"` + User string `json:"user"` + Rootfs string `json:"rootfs"` // root fs of the container + InitPath string `json:"initpath"` // dockerinit + Entrypoint string `json:"entrypoint"` + Arguments []string `json:"arguments"` + WorkingDir string `json:"working_dir"` + ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver + Tty bool `json:"tty"` + Network *Network `json:"network"` // if network is nil then networking is disabled + Config []string `json:"config"` // generic values that specific drivers can consume + Resources *Resources `json:"resources"` } // Return the pid of the process diff --git a/execdriver/lxc/lxc_template.go b/execdriver/lxc/lxc_template.go index ac3481836d..705bdf5363 100644 --- a/execdriver/lxc/lxc_template.go +++ b/execdriver/lxc/lxc_template.go @@ -1,7 +1,7 @@ package lxc import ( - "github.com/dotcloud/docker/pkg/cgroups" + "github.com/dotcloud/docker/execdriver" "strings" "text/template" ) @@ -91,16 +91,16 @@ lxc.aa_profile = unconfined {{end}} # limits -{{if .Cgroups}} -{{if .Cgroups.Memory}} -lxc.cgroup.memory.limit_in_bytes = {{.Cgroups.Memory}} -lxc.cgroup.memory.soft_limit_in_bytes = {{.Cgroups.Memory}} -{{with $memSwap := getMemorySwap .Cgroups}} +{{if .Resources}} +{{if .Resources.Memory}} +lxc.cgroup.memory.limit_in_bytes = {{.Resources.Memory}} +lxc.cgroup.memory.soft_limit_in_bytes = {{.Resources.Memory}} +{{with $memSwap := getMemorySwap .Resources}} lxc.cgroup.memory.memsw.limit_in_bytes = {{$memSwap}} {{end}} {{end}} -{{if .Cgroups.CpuShares}} -lxc.cgroup.cpu.shares = {{.Cgroups.CpuShares}} +{{if .Resources.CpuShares}} +lxc.cgroup.cpu.shares = {{.Resources.CpuShares}} {{end}} {{end}} @@ -119,7 +119,7 @@ func escapeFstabSpaces(field string) string { return strings.Replace(field, " ", "\\040", -1) } -func getMemorySwap(v *cgroups.Values) int64 { +func getMemorySwap(v *execdriver.Resources) int64 { // By default, MemorySwap is set to twice the size of RAM. // If you want to omit MemorySwap, set it to `-1'. if v.MemorySwap < 0 { diff --git a/execdriver/lxc/lxc_template_unit_test.go b/execdriver/lxc/lxc_template_unit_test.go index 8ea27646ec..0acf9645d2 100644 --- a/execdriver/lxc/lxc_template_unit_test.go +++ b/execdriver/lxc/lxc_template_unit_test.go @@ -4,7 +4,6 @@ import ( "bufio" "fmt" "github.com/dotcloud/docker/execdriver" - "github.com/dotcloud/docker/pkg/cgroups" "io/ioutil" "math/rand" "os" @@ -40,7 +39,7 @@ func TestLXCConfig(t *testing.T) { } process := &execdriver.Process{ ID: "1", - Cgroups: &cgroups.Values{ + Resources: &execdriver.Resources{ Memory: int64(mem), CpuShares: int64(cpu), }, diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index 07867a6d58..de7b079dc2 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -12,12 +12,6 @@ import ( "strings" ) -type Values struct { - Memory int64 `json:"memory"` - MemorySwap int64 `json:"memory_swap"` - CpuShares int64 `json:"cpu_shares"` -} - // https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt func FindCgroupMountpoint(subsystem string) (string, error) { mounts, err := mount.GetMounts() From d8a71635d4eb6b3dbe0102ab6e177bff8ff5dbec Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 16 Jan 2014 00:04:36 +0000 Subject: [PATCH 133/364] docs: move security article to a new 'articles' section Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- docs/sources/articles/index.rst | 12 ++++++++++++ docs/sources/{installation => articles}/security.rst | 0 docs/sources/installation/index.rst | 1 - docs/sources/toctree.rst | 2 ++ 4 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 docs/sources/articles/index.rst rename docs/sources/{installation => articles}/security.rst (100%) diff --git a/docs/sources/articles/index.rst b/docs/sources/articles/index.rst new file mode 100644 index 0000000000..be01a7af32 --- /dev/null +++ b/docs/sources/articles/index.rst @@ -0,0 +1,12 @@ +:title: Docker articles +:description: various articles related to Docker +:keywords: docker, articles + +.. _articles_list: + +Contents: + +.. toctree:: + :maxdepth: 1 + + security diff --git a/docs/sources/installation/security.rst b/docs/sources/articles/security.rst similarity index 100% rename from docs/sources/installation/security.rst rename to docs/sources/articles/security.rst diff --git a/docs/sources/installation/index.rst b/docs/sources/installation/index.rst index 9ebe484f6e..3df0e611c4 100644 --- a/docs/sources/installation/index.rst +++ b/docs/sources/installation/index.rst @@ -31,5 +31,4 @@ Contents: google kernel binaries - security upgrading diff --git a/docs/sources/toctree.rst b/docs/sources/toctree.rst index 2807ea5932..89d63b933f 100644 --- a/docs/sources/toctree.rst +++ b/docs/sources/toctree.rst @@ -15,6 +15,8 @@ This documentation has the following resources: use/index examples/index reference/index + commandline/index contributing/index terms/index faq + articles/index From c56e3b8569caa8db6fb7372c7a5f5153eb5d63b7 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 16 Jan 2014 00:13:00 +0000 Subject: [PATCH 134/364] docs: move 'vagrant' install docs to the more accurate 'mac' install docs Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- docs/sources/installation/index.rst | 2 +- docs/sources/installation/{vagrant.rst => mac.rst} | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) rename docs/sources/installation/{vagrant.rst => mac.rst} (73%) diff --git a/docs/sources/installation/index.rst b/docs/sources/installation/index.rst index 3df0e611c4..0d8782bf31 100644 --- a/docs/sources/installation/index.rst +++ b/docs/sources/installation/index.rst @@ -24,7 +24,7 @@ Contents: gentoolinux openSUSE frugalware - vagrant + mac windows amazon rackspace diff --git a/docs/sources/installation/vagrant.rst b/docs/sources/installation/mac.rst similarity index 73% rename from docs/sources/installation/vagrant.rst rename to docs/sources/installation/mac.rst index 81acf76f5e..69c57380b0 100644 --- a/docs/sources/installation/vagrant.rst +++ b/docs/sources/installation/mac.rst @@ -1,16 +1,14 @@ -:title: Using Vagrant (Mac, Linux) -:description: This guide will setup a new virtualbox virtual machine with docker installed on your computer. -:keywords: Docker, Docker documentation, virtualbox, vagrant, git, ssh, putty, cygwin +:title: Installing Docker on a Mac +:description: Installing Docker on a Mac +:keywords: Docker, Docker documentation, virtualbox, git, ssh .. _install_using_vagrant: -Using Vagrant (Mac, Linux) +Installing Docker on a Mac ========================== -This guide will setup a new virtualbox virtual machine with docker -installed on your computer. This works on most operating systems, -including MacOSX, Windows, Linux, FreeBSD and others. If you can -install these and have at least 400MB RAM to spare you should be good. +This guide explains how to install a full Docker setup on your Mac, +using Virtualbox and Vagrant. Install Vagrant and Virtualbox ------------------------------ From 29e6868cc36356393730ff2a5ae4506ac2d162e1 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 16 Jan 2014 00:14:28 +0000 Subject: [PATCH 135/364] docs: change topic of windows install docs to 'install docker on windows' Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- docs/sources/installation/windows.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/windows.rst b/docs/sources/installation/windows.rst index b487606a48..c980a32df9 100644 --- a/docs/sources/installation/windows.rst +++ b/docs/sources/installation/windows.rst @@ -4,8 +4,8 @@ .. _windows: -Using Vagrant (Windows) -======================= +Installing Docker on Windows +============================ Docker can run on Windows using a VM like VirtualBox. You then run Linux within the VM. From dd786eefbbf286ca57b52374a6905c1ac8b8bd60 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 16 Jan 2014 00:17:23 +0000 Subject: [PATCH 136/364] docs: instead of a top-level 'upgrading docker' doc, add an 'upgrades' paragraph to each installation target Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- docs/sources/installation/binaries.rst | 11 ++++ docs/sources/installation/mac.rst | 22 ++++++- docs/sources/installation/ubuntulinux.rst | 15 +++++ docs/sources/installation/upgrading.rst | 73 ----------------------- 4 files changed, 47 insertions(+), 74 deletions(-) delete mode 100644 docs/sources/installation/upgrading.rst diff --git a/docs/sources/installation/binaries.rst b/docs/sources/installation/binaries.rst index f06a8d6c5f..4da511b92c 100644 --- a/docs/sources/installation/binaries.rst +++ b/docs/sources/installation/binaries.rst @@ -43,6 +43,17 @@ Run the docker daemon # start the docker in daemon mode from the directory you unpacked sudo ./docker -d & +Upgrades +-------- + +To upgrade your manual installation of Docker, first kill the docker daemon: + +.. code-block:: bash + + killall docker + +Then follow the regular installation steps. + Run your first container! ------------------------- diff --git a/docs/sources/installation/mac.rst b/docs/sources/installation/mac.rst index 69c57380b0..569fa1f838 100644 --- a/docs/sources/installation/mac.rst +++ b/docs/sources/installation/mac.rst @@ -65,8 +65,28 @@ To access the VM and use Docker, Run ``vagrant ssh`` from the same directory as vagrant ssh + +Upgrades +-------- + +Since your local VM is based on Ubuntu, you can upgrade docker by logging in to the +VM and calling ``apt-get``: + + +.. code-block:: bash + + # Log into the VM + vagrant ssh + + # update your sources list + sudo apt-get update + + # install the latest + sudo apt-get install lxc-docker + + Run ------ +--- Now you are in the VM, run docker diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 8480979099..17eee15e9a 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -167,6 +167,21 @@ Type ``exit`` to exit **Done!**, now continue with the :ref:`hello_world` example. +Upgrades +-------- + +To install the latest version of docker, use the standard ``apt-get`` method: + + +.. code-block:: bash + + # update your sources list + sudo apt-get update + + # install the latest + sudo apt-get install lxc-docker + + .. _ufw: Docker and UFW diff --git a/docs/sources/installation/upgrading.rst b/docs/sources/installation/upgrading.rst deleted file mode 100644 index c760115545..0000000000 --- a/docs/sources/installation/upgrading.rst +++ /dev/null @@ -1,73 +0,0 @@ -:title: Upgrading -:description: These instructions are for upgrading Docker -:keywords: Docker, Docker documentation, upgrading docker, upgrade - -.. _upgrading: - -Upgrading -========= - -The technique for upgrading ``docker`` to a newer version depends on -how you installed ``docker``. - -.. versionadded:: 0.5.3 - You may wish to add a ``docker`` group to your system to avoid using sudo with ``docker``. (see :ref:`dockergroup`) - - -After ``apt-get`` ------------------ - -If you installed Docker using ``apt-get`` or Vagrant, then you should -use ``apt-get`` to upgrade. - -.. versionadded:: 0.6 - Add Docker repository information to your system first. - -.. code-block:: bash - - # Add the Docker repository key to your local keychain - sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9 - - # Add the Docker repository to your apt sources list. - sudo sh -c "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list" - - # update your sources list - sudo apt-get update - - # install the latest - sudo apt-get install lxc-docker - - -After manual installation -------------------------- - -If you installed the Docker :ref:`binaries` then follow these steps: - - -.. code-block:: bash - - # kill the running docker daemon - killall docker - - -.. code-block:: bash - - # get the latest binary - wget http://get.docker.io/builds/Linux/x86_64/docker-latest -O docker - - # make it executable - chmod +x docker - - -Start docker in daemon mode (``-d``) and disconnect, running the -daemon in the background (``&``). Starting as ``./docker`` guarantees -to run the version in your current directory rather than a version -which might reside in your path. - -.. code-block:: bash - - # start the new version - sudo ./docker -d & - - -Alternatively you can replace the docker binary in ``/usr/local/bin``. From 4e54dd245a5e20b86f305ccb8f493d7f17317c50 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 16 Jan 2014 00:21:22 +0000 Subject: [PATCH 137/364] docs: remove out-of-date kernel requirements document. Requirements differ from platform to platform depending on the plugins used, and so should be documented separately for each target platform Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- docs/sources/installation/index.rst | 2 - docs/sources/installation/kernel.rst | 151 --------------------------- 2 files changed, 153 deletions(-) delete mode 100644 docs/sources/installation/kernel.rst diff --git a/docs/sources/installation/index.rst b/docs/sources/installation/index.rst index 0d8782bf31..04c155d885 100644 --- a/docs/sources/installation/index.rst +++ b/docs/sources/installation/index.rst @@ -29,6 +29,4 @@ Contents: amazon rackspace google - kernel binaries - upgrading diff --git a/docs/sources/installation/kernel.rst b/docs/sources/installation/kernel.rst deleted file mode 100644 index 88822ad37d..0000000000 --- a/docs/sources/installation/kernel.rst +++ /dev/null @@ -1,151 +0,0 @@ -:title: Kernel Requirements -:description: Kernel supports -:keywords: kernel requirements, kernel support, docker, installation, cgroups, namespaces - -.. _kernel: - -Kernel Requirements -=================== - -In short, Docker has the following kernel requirements: - -- Linux version 3.8 or above. - -- Cgroups and namespaces must be enabled. - -*Note: as of 0.7 docker no longer requires aufs. AUFS support is still available as an optional driver.* - -The officially supported kernel is the one recommended by the -:ref:`ubuntu_linux` installation path. It is the one that most developers -will use, and the one that receives the most attention from the core -contributors. If you decide to go with a different kernel and hit a bug, -please try to reproduce it with the official kernels first. - -If you cannot or do not want to use the "official" kernels, -here is some technical background about the features (both optional and -mandatory) that docker needs to run successfully. - - -Linux version 3.8 or above --------------------------- - -Kernel versions 3.2 to 3.5 are not stable when used with docker. -In some circumstances, you will experience kernel "oopses", or even crashes. -The symptoms include: - -- a container being killed in the middle of an operation (e.g. an ``apt-get`` - command doesn't complete); -- kernel messages including mentioning calls to ``mntput`` or - ``d_hash_and_lookup``; -- kernel crash causing the machine to freeze for a few minutes, or even - completely. - -Additionally, kernels prior 3.4 did not implement ``reboot_pid_ns``, -which means that the ``reboot()`` syscall could reboot the host machine, -instead of terminating the container. To work around that problem, -LXC userland tools (since version 0.8) automatically drop the ``SYS_BOOT`` -capability when necessary. Still, if you run a pre-3.4 kernel with pre-0.8 -LXC tools, be aware that containers can reboot the whole host! This is -not something that Docker wants to address in the short term, since you -shouldn't use kernels prior 3.8 with Docker anyway. - -While it is still possible to use older kernels for development, it is -really not advised to do so. - -Docker checks the kernel version when it starts, and emits a warning if it -detects something older than 3.8. - -See issue `#407 `_ for details. - - -Cgroups and namespaces ----------------------- - -You need to enable namespaces and cgroups, to the extent of what is needed -to run LXC containers. Technically, while namespaces have been introduced -in the early 2.6 kernels, we do not advise to try any kernel before 2.6.32 -to run LXC containers. Note that 2.6.32 has some documented issues regarding -network namespace setup and teardown; those issues are not a risk if you -run containers in a private environment, but can lead to denial-of-service -attacks if you want to run untrusted code in your containers. For more details, -see `LP#720095 `_. - -Kernels 2.6.38, and every version since 3.2, have been deployed successfully -to run containerized production workloads. Feature-wise, there is no huge -improvement between 2.6.38 and up to 3.6 (as far as docker is concerned!). - - - - -Extra Cgroup Controllers ------------------------- - -Most control groups can be enabled or disabled individually. For instance, -you can decide that you do not want to compile support for the CPU or memory -controller. In some cases, the feature can be enabled or disabled at boot -time. It is worth mentioning that some distributions (like Debian) disable -"expensive" features, like the memory controller, because they can have -a significant performance impact. - -In the specific case of the memory cgroup, docker will detect if the cgroup -is available or not. If it's not, it will print a warning, and it won't -use the feature. If you want to enable that feature -- read on! - - -Memory and Swap Accounting on Debian/Ubuntu -------------------------------------------- - -If you use Debian or Ubuntu kernels, and want to enable memory and swap -accounting, you must add the following command-line parameters to your kernel:: - - cgroup_enable=memory swapaccount=1 - -On Debian or Ubuntu systems, if you use the default GRUB bootloader, you can -add those parameters by editing ``/etc/default/grub`` and extending -``GRUB_CMDLINE_LINUX``. Look for the following line:: - - GRUB_CMDLINE_LINUX="" - -And replace it by the following one:: - - GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1" - -Then run ``update-grub``, and reboot. - -Details -------- - -To automatically check some of the requirements below, you can run `lxc-checkconfig`. - -Networking: - -- CONFIG_BRIDGE -- CONFIG_NETFILTER_XT_MATCH_ADDRTYPE -- CONFIG_NF_NAT -- CONFIG_NF_NAT_IPV4 -- CONFIG_NF_NAT_NEEDED - -LVM: - -- CONFIG_BLK_DEV_DM -- CONFIG_DM_THIN_PROVISIONING -- CONFIG_EXT4_FS - -Namespaces: - -- CONFIG_NAMESPACES -- CONFIG_UTS_NS -- CONFIG_IPC_NS -- CONFIG_PID_NS -- CONFIG_NET_NS - -Cgroups: - -- CONFIG_CGROUPS - -Cgroup controllers (optional but highly recommended): - -- CONFIG_CGROUP_CPUACCT -- CONFIG_BLK_CGROUP -- CONFIG_MEMCG -- CONFIG_MEMCG_SWAP From 45e8d1f4c0366c5372229db20b95b70badc92119 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 20 Jan 2014 23:17:40 +0000 Subject: [PATCH 138/364] docs: reuse official dependencies list from packager's guide for custom binary install docs --- docs/sources/index.rst | 3 +-- docs/sources/installation/binaries.rst | 26 ++++++++++++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/docs/sources/index.rst b/docs/sources/index.rst index 1fb82f3bec..c10ed2abf9 100644 --- a/docs/sources/index.rst +++ b/docs/sources/index.rst @@ -12,8 +12,7 @@ dependencies. ``docker`` runs three ways: -* as a daemon to manage LXC containers on your :ref:`Linux host - ` (``sudo docker -d``) +* as a daemon to manage LXC containers on your host machine (``sudo docker -d``) * as a :ref:`CLI ` which talks to the daemon's `REST API `_ (``docker run ...``) * as a client of :ref:`Repositories ` diff --git a/docs/sources/installation/binaries.rst b/docs/sources/installation/binaries.rst index 4da511b92c..611429f84a 100644 --- a/docs/sources/installation/binaries.rst +++ b/docs/sources/installation/binaries.rst @@ -16,15 +16,29 @@ Before following these directions, you should really check if a packaged version of Docker is already available for your distribution. We have packages for many distributions, and more keep showing up all the time! -Check Your Kernel ------------------ -Your host's Linux kernel must meet the Docker :ref:`kernel` - -Check for User Space Tools +Check runtime dependencies -------------------------- -You must have a working installation of the `lxc `_ utilities and library. +To run properly, docker needs the following software to be installed at runtime: + +- GNU Tar version 1.26 or later +- iproute2 version 3.5 or later (build after 2012-05-21), and specifically the "ip" utility +- iptables version 1.4 or later +- The LXC utility scripts (http://lxc.sourceforge.net) version 0.8 or later +- Git version 1.7 or later +- XZ Utils 4.9 or later + + +Check kernel dependencies +------------------------- + +Docker in daemon mode has specific kernel requirements. For details, see +http://docs.docker.io/en/latest/articles/kernel/ + +Note that Docker also has a client mode, which can run on virtually any linux kernel (it even builds +on OSX!). + Get the docker binary: ---------------------- From 6e6ff85362dfa823c392392cb9e70d620e308911 Mon Sep 17 00:00:00 2001 From: Jake Moshenko Date: Fri, 3 Jan 2014 15:13:32 -0500 Subject: [PATCH 139/364] Fix support for registry auth with Dockerfile build. Docker-DCO-1.1-Signed-off-by: Jake Moshenko (github: jakedt) --- api.go | 20 +++++++++++++++++-- buildfile.go | 17 ++++++++++++++-- commands.go | 4 +++- .../reference/api/docker_remote_api.rst | 7 +++++++ .../reference/api/docker_remote_api_v1.9.rst | 2 +- integration/buildfile_test.go | 6 +++--- 6 files changed, 47 insertions(+), 9 deletions(-) diff --git a/api.go b/api.go index 5ac39e216a..61df3e7fe6 100644 --- a/api.go +++ b/api.go @@ -925,10 +925,17 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ rawRm = r.FormValue("rm") authEncoded = r.Header.Get("X-Registry-Auth") authConfig = &auth.AuthConfig{} + configFileEncoded = r.Header.Get("X-Registry-Config") + configFile = &auth.ConfigFile{} tag string ) repoName, tag = utils.ParseRepositoryTag(repoName) - if authEncoded != "" { + + // This block can be removed when API versions prior to 1.9 are deprecated. + // Both headers will be parsed and sent along to the daemon, but if a non-empty + // ConfigFile is present, any value provided as an AuthConfig directly will + // be overridden. See BuildFile::CmdFrom for details. + if version < 1.9 && authEncoded != "" { authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given @@ -937,6 +944,15 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ } } + if configFileEncoded != "" { + configFileJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(configFileEncoded)) + if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil { + // for a pull it is not an error if no auth was given + // to increase compatibility with the existing api it is defaulting to be empty + configFile = &auth.ConfigFile{} + } + } + var context io.Reader if remoteURL == "" { @@ -1003,7 +1019,7 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ Writer: utils.NewWriteFlusher(w), StreamFormatter: sf, }, - !suppressOutput, !noCache, rm, utils.NewWriteFlusher(w), sf, authConfig) + !suppressOutput, !noCache, rm, utils.NewWriteFlusher(w), sf, authConfig, configFile) id, err := b.Build(context) if err != nil { if sf.Used() { diff --git a/buildfile.go b/buildfile.go index 19249b04c4..97cf35c406 100644 --- a/buildfile.go +++ b/buildfile.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" + "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -47,6 +48,7 @@ type buildFile struct { rm bool authConfig *auth.AuthConfig + configFile *auth.ConfigFile tmpContainers map[string]struct{} tmpImages map[string]struct{} @@ -72,7 +74,17 @@ func (b *buildFile) CmdFrom(name string) error { if err != nil { if b.runtime.graph.IsNotExist(err) { remote, tag := utils.ParseRepositoryTag(name) - if err := b.srv.ImagePull(remote, tag, b.outOld, b.sf, b.authConfig, nil, true); err != nil { + pullRegistryAuth := b.authConfig + if len(b.configFile.Configs) > 0 { + // The request came with a full auth config file, we prefer to use that + endpoint, _, err := registry.ResolveRepositoryName(remote) + if err != nil { + return err + } + resolvedAuth := b.configFile.ResolveAuthConfig(endpoint) + pullRegistryAuth = &resolvedAuth + } + if err := b.srv.ImagePull(remote, tag, b.outOld, b.sf, pullRegistryAuth, nil, true); err != nil { return err } image, err = b.runtime.repositories.LookupImage(name) @@ -696,7 +708,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) { return "", fmt.Errorf("No image was generated. This may be because the Dockerfile does not, like, do anything.\n") } -func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *auth.AuthConfig) BuildFile { +func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *auth.AuthConfig, authConfigFile *auth.ConfigFile) BuildFile { return &buildFile{ runtime: srv.runtime, srv: srv, @@ -710,6 +722,7 @@ func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeC rm: rm, sf: sf, authConfig: auth, + configFile: authConfigFile, outOld: outOld, } } diff --git a/commands.go b/commands.go index a864180fef..a5d7c9100a 100644 --- a/commands.go +++ b/commands.go @@ -227,12 +227,14 @@ func (cli *DockerCli) CmdBuild(args ...string) error { v.Set("rm", "1") } + cli.LoadConfigFile() + headers := http.Header(make(map[string][]string)) buf, err := json.Marshal(cli.configFile) if err != nil { return err } - headers.Add("X-Registry-Auth", base64.URLEncoding.EncodeToString(buf)) + headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf)) if context != nil { headers.Set("Content-Type", "application/tar") diff --git a/docs/sources/reference/api/docker_remote_api.rst b/docs/sources/reference/api/docker_remote_api.rst index bcac603717..b616e10a8f 100644 --- a/docs/sources/reference/api/docker_remote_api.rst +++ b/docs/sources/reference/api/docker_remote_api.rst @@ -54,6 +54,13 @@ What's new **New!** This endpoint now returns a list of json message, like the events endpoint +.. http:post:: /build + + **New!** This endpoint now takes a serialized ConfigFile which it uses to + resolve the proper registry auth credentials for pulling the base image. + Clients which previously implemented the version accepting an AuthConfig + object must be updated. + v1.8 **** diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.rst b/docs/sources/reference/api/docker_remote_api_v1.9.rst index 6546035a57..65e501234f 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.9.rst @@ -1025,7 +1025,7 @@ Build an image from Dockerfile via stdin :query q: suppress verbose build output :query nocache: do not use the cache when building the image :reqheader Content-type: should be set to ``"application/tar"``. - :reqheader X-Registry-Auth: base64-encoded AuthConfig object + :reqheader X-Registry-Config: base64-encoded ConfigFile object :statuscode 200: no error :statuscode 500: server error diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 5dd403274e..6c1acc0088 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -296,7 +296,7 @@ func buildImage(context testContextTemplate, t *testing.T, eng *engine.Engine, u } dockerfile := constructDockerfile(context.dockerfile, ip, port) - buildfile := docker.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, useCache, false, ioutil.Discard, utils.NewStreamFormatter(false), nil) + buildfile := docker.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, useCache, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) id, err := buildfile.Build(mkTestContext(dockerfile, context.files, t)) if err != nil { return nil, err @@ -700,7 +700,7 @@ func TestForbiddenContextPath(t *testing.T) { } dockerfile := constructDockerfile(context.dockerfile, ip, port) - buildfile := docker.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil) + buildfile := docker.NewBuildFile(srv, ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) _, err = buildfile.Build(mkTestContext(dockerfile, context.files, t)) if err == nil { @@ -746,7 +746,7 @@ func TestBuildADDFileNotFound(t *testing.T) { } dockerfile := constructDockerfile(context.dockerfile, ip, port) - buildfile := docker.NewBuildFile(mkServerFromEngine(eng, t), ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil) + buildfile := docker.NewBuildFile(mkServerFromEngine(eng, t), ioutil.Discard, ioutil.Discard, false, true, false, ioutil.Discard, utils.NewStreamFormatter(false), nil, nil) _, err = buildfile.Build(mkTestContext(dockerfile, context.files, t)) if err == nil { From 12468f2bc8a72a6bedfd681084863ea95b48713b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 20 Jan 2014 16:05:07 -0800 Subject: [PATCH 140/364] Rename Process to Command Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- container.go | 32 ++++++++++++------------ execdriver/chroot/driver.go | 4 +-- execdriver/driver.go | 14 +++++------ execdriver/lxc/driver.go | 18 +++++++------ execdriver/lxc/lxc_template_unit_test.go | 8 +++--- runtime.go | 4 +-- sysinit/sysinit.go | 18 +++++++------ 7 files changed, 51 insertions(+), 47 deletions(-) diff --git a/container.go b/container.go index 3eebb641a1..e288636a36 100644 --- a/container.go +++ b/container.go @@ -53,7 +53,7 @@ type Container struct { Name string Driver string - process *execdriver.Process + command *execdriver.Command stdout *utils.WriteBroadcaster stderr *utils.WriteBroadcaster stdin io.ReadCloser @@ -305,8 +305,8 @@ func (container *Container) setupPty() error { return err } container.ptyMaster = ptyMaster - container.process.Stdout = ptySlave - container.process.Stderr = ptySlave + container.command.Stdout = ptySlave + container.command.Stderr = ptySlave // Copy the PTYs to our broadcasters go func() { @@ -318,8 +318,8 @@ func (container *Container) setupPty() error { // stdin if container.Config.OpenStdin { - container.process.Stdin = ptySlave - container.process.SysProcAttr.Setctty = true + container.command.Stdin = ptySlave + container.command.SysProcAttr.Setctty = true go func() { defer container.stdin.Close() utils.Debugf("startPty: begin of stdin pipe") @@ -331,10 +331,10 @@ func (container *Container) setupPty() error { } func (container *Container) setupStd() error { - container.process.Stdout = container.stdout - container.process.Stderr = container.stderr + container.command.Stdout = container.stdout + container.command.Stderr = container.stderr if container.Config.OpenStdin { - stdin, err := container.process.StdinPipe() + stdin, err := container.command.StdinPipe() if err != nil { return err } @@ -676,7 +676,7 @@ func (container *Container) Start() (err error) { CpuShares: container.Config.CpuShares, } - container.process = &execdriver.Process{ + container.command = &execdriver.Command{ ID: container.ID, Privileged: container.hostConfig.Privileged, Rootfs: root, @@ -690,7 +690,7 @@ func (container *Container) Start() (err error) { Config: driverConfig, Resources: resources, } - container.process.SysProcAttr = &syscall.SysProcAttr{Setsid: true} + container.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true} // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { @@ -713,13 +713,13 @@ func (container *Container) Start() (err error) { } callbackLock := make(chan struct{}) - callback := func(process *execdriver.Process) { - container.State.SetRunning(process.Pid()) - if process.Tty { + callback := func(command *execdriver.Command) { + container.State.SetRunning(command.Pid()) + if command.Tty { // The callback is called after the process Start() // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlace // which we close here. - if c, ok := process.Stdout.(io.Closer); ok { + if c, ok := command.Stdout.(io.Closer); ok { c.Close() } } @@ -1118,7 +1118,7 @@ func (container *Container) monitor(callback execdriver.StartCallback) error { exitCode int ) - if container.process == nil { + if container.command == nil { // This happends when you have a GHOST container with lxc err = container.runtime.WaitGhost(container) } else { @@ -1210,7 +1210,7 @@ func (container *Container) Kill() error { // 2. Wait for the process to die, in last resort, try to kill the process directly if err := container.WaitTimeout(10 * time.Second); err != nil { - if container.process == nil { + if container.command == nil { return fmt.Errorf("lxc-kill failed, impossible to kill the container %s", utils.TruncateID(container.ID)) } log.Printf("Container %s failed to exit within 10 seconds of lxc-kill %s - trying direct SIGKILL", "SIGKILL", utils.TruncateID(container.ID)) diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index 749926edf7..509a62dd65 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -36,7 +36,7 @@ func NewDriver() (*driver, error) { return &driver{}, nil } -func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallback) (int, error) { +func (d *driver) Run(c *execdriver.Command, startCallback execdriver.StartCallback) (int, error) { params := []string{ "chroot", c.Rootfs, @@ -70,7 +70,7 @@ func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallba return c.GetExitCode(), err } -func (d *driver) Kill(p *execdriver.Process, sig int) error { +func (d *driver) Kill(p *execdriver.Command, sig int) error { return p.Process.Kill() } diff --git a/execdriver/driver.go b/execdriver/driver.go index b56404ee41..41124245e2 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -16,7 +16,7 @@ var ( var dockerInitFcts map[string]InitFunc type ( - StartCallback func(*Process) + StartCallback func(*Command) InitFunc func(i *InitArgs) error ) @@ -59,8 +59,8 @@ type Info interface { } type Driver interface { - Run(c *Process, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code - Kill(c *Process, sig int) error + Run(c *Command, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code + Kill(c *Command, sig int) error Wait(id string) error // Wait on an out of process...process - lxc ghosts TODO: Rename to reattach, reconnect Name() string // Driver name Info(id string) Info // "temporary" hack (until we move state from core to plugins) @@ -83,8 +83,8 @@ type Resources struct { // Process wrapps an os/exec.Cmd to add more metadata // TODO: Rename to Command -type Process struct { - exec.Cmd +type Command struct { + exec.Cmd `json:"-"` ID string `json:"id"` Privileged bool `json:"privileged"` @@ -103,7 +103,7 @@ type Process struct { // Return the pid of the process // If the process is nil -1 will be returned -func (c *Process) Pid() int { +func (c *Command) Pid() int { if c.Process == nil { return -1 } @@ -112,7 +112,7 @@ func (c *Process) Pid() int { // Return the exit code of the process // if the process has not exited -1 will be returned -func (c *Process) GetExitCode() int { +func (c *Command) GetExitCode() int { if c.ProcessState == nil { return -1 } diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 2a6663117a..97c8a4b026 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -30,6 +30,7 @@ func init() { if err := setupCapabilities(args); err != nil { return err } + if err := setupWorkingDirectory(args); err != nil { return err } @@ -37,6 +38,7 @@ func init() { if err := changeUser(args); err != nil { return err } + path, err := exec.LookPath(args.Args[0]) if err != nil { log.Printf("Unable to locate %v", args.Args[0]) @@ -72,7 +74,7 @@ func (d *driver) Name() string { return fmt.Sprintf("%s-%s", DriverName, version) } -func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallback) (int, error) { +func (d *driver) Run(c *execdriver.Command, startCallback execdriver.StartCallback) (int, error) { configPath, err := d.generateLXCConfig(c) if err != nil { return -1, err @@ -170,7 +172,7 @@ func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallba return c.GetExitCode(), waitErr } -func (d *driver) Kill(c *execdriver.Process, sig int) error { +func (d *driver) Kill(c *execdriver.Command, sig int) error { return d.kill(c, sig) } @@ -198,7 +200,7 @@ func (d *driver) version() string { return version } -func (d *driver) kill(c *execdriver.Process, sig int) error { +func (d *driver) kill(c *execdriver.Command, sig int) error { output, err := exec.Command("lxc-kill", "-n", c.ID, strconv.Itoa(sig)).CombinedOutput() if err != nil { return fmt.Errorf("Err: %s Output: %s", err, output) @@ -206,7 +208,7 @@ func (d *driver) kill(c *execdriver.Process, sig int) error { return nil } -func (d *driver) waitForStart(c *execdriver.Process, waitLock chan struct{}) error { +func (d *driver) waitForStart(c *execdriver.Command, waitLock chan struct{}) error { var ( err error output []byte @@ -302,8 +304,8 @@ func rootIsShared() bool { return true } -func (d *driver) generateLXCConfig(p *execdriver.Process) (string, error) { - root := path.Join(d.root, "containers", p.ID, "config.lxc") +func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) { + root := path.Join(d.root, "containers", c.ID, "config.lxc") fo, err := os.Create(root) if err != nil { return "", err @@ -311,10 +313,10 @@ func (d *driver) generateLXCConfig(p *execdriver.Process) (string, error) { defer fo.Close() if err := LxcTemplateCompiled.Execute(fo, struct { - *execdriver.Process + *execdriver.Command AppArmor bool }{ - Process: p, + Command: c, AppArmor: d.apparmor, }); err != nil { return "", err diff --git a/execdriver/lxc/lxc_template_unit_test.go b/execdriver/lxc/lxc_template_unit_test.go index 0acf9645d2..99d6e636f5 100644 --- a/execdriver/lxc/lxc_template_unit_test.go +++ b/execdriver/lxc/lxc_template_unit_test.go @@ -37,14 +37,14 @@ func TestLXCConfig(t *testing.T) { if err != nil { t.Fatal(err) } - process := &execdriver.Process{ + command := &execdriver.Command{ ID: "1", Resources: &execdriver.Resources{ Memory: int64(mem), CpuShares: int64(cpu), }, } - p, err := driver.generateLXCConfig(process) + p, err := driver.generateLXCConfig(command) if err != nil { t.Fatal(err) } @@ -68,7 +68,7 @@ func TestCustomLxcConfig(t *testing.T) { if err != nil { t.Fatal(err) } - process := &execdriver.Process{ + command := &execdriver.Command{ ID: "1", Privileged: false, Config: []string{ @@ -77,7 +77,7 @@ func TestCustomLxcConfig(t *testing.T) { }, } - p, err := driver.generateLXCConfig(process) + p, err := driver.generateLXCConfig(command) if err != nil { t.Fatal(err) } diff --git a/runtime.go b/runtime.go index 52f03f84be..d35a5c7b04 100644 --- a/runtime.go +++ b/runtime.go @@ -802,11 +802,11 @@ func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) { } func (runtime *Runtime) Run(c *Container, startCallback execdriver.StartCallback) (int, error) { - return runtime.execDriver.Run(c.process, startCallback) + return runtime.execDriver.Run(c.command, startCallback) } func (runtime *Runtime) Kill(c *Container, sig int) error { - return runtime.execDriver.Kill(c.process, sig) + return runtime.execDriver.Kill(c.command, sig) } func (runtime *Runtime) WaitGhost(c *Container) error { diff --git a/sysinit/sysinit.go b/sysinit/sysinit.go index af69795cb6..dcf0eddf56 100644 --- a/sysinit/sysinit.go +++ b/sysinit/sysinit.go @@ -50,14 +50,16 @@ func SysInit() { os.Exit(1) } - // Get cmdline arguments - user := flag.String("u", "", "username or uid") - gateway := flag.String("g", "", "gateway address") - ip := flag.String("i", "", "ip address") - workDir := flag.String("w", "", "workdir") - privileged := flag.Bool("privileged", false, "privileged mode") - mtu := flag.Int("mtu", 1500, "interface mtu") - driver := flag.String("driver", "", "exec driver") + var ( + // Get cmdline arguments + user = flag.String("u", "", "username or uid") + gateway = flag.String("g", "", "gateway address") + ip = flag.String("i", "", "ip address") + workDir = flag.String("w", "", "workdir") + privileged = flag.Bool("privileged", false, "privileged mode") + mtu = flag.Int("mtu", 1500, "interface mtu") + driver = flag.String("driver", "", "exec driver") + ) flag.Parse() // Get env From 497e7d651a21fb1fa0d78221c274871a757a1dc0 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 20 Jan 2014 16:09:07 -0800 Subject: [PATCH 141/364] update todo Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- REMOTE_TODO.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/REMOTE_TODO.md b/REMOTE_TODO.md index bef1d4a673..367e1259af 100644 --- a/REMOTE_TODO.md +++ b/REMOTE_TODO.md @@ -9,13 +9,13 @@ ok "/images/viz": getImagesViz, 0 ok "/images/search": getImagesSearch, N ok "/images/{name:.*}/get": getImagesGet, 0 ok "/images/{name:.*}/history": getImagesHistory, N -... "/images/{name:.*}/json": getImagesByName, 1 +#3621 "/images/{name:.*}/json": getImagesByName, 1 TODO "/containers/ps": getContainersJSON, N TODO "/containers/json": getContainersJSON, 1 ok "/containers/{name:.*}/export": getContainersExport, 0 -#3616 "/containers/{name:.*}/changes": getContainersChanges, N -... "/containers/{name:.*}/json": getContainersByName, 1 -TODO "/containers/{name:.*}/top": getContainersTop, N +ok "/containers/{name:.*}/changes": getContainersChanges, N +#3621 "/containers/{name:.*}/json": getContainersByName, 1 +ok "/containers/{name:.*}/top": getContainersTop, N ok "/containers/{name:.*}/attach/ws": wsContainersAttach, 0 yes **POST** @@ -24,7 +24,7 @@ ok "/commit": postCommit, 0 TODO "/build": postBuild, 0 yes TODO "/images/create": postImagesCreate, N yes yes (pull) ok "/images/{name:.*}/insert": postImagesInsert, N yes yes -TODO "/images/load": postImagesLoad, 1 yes (stdin) +... "/images/load": postImagesLoad, 1 yes (stdin) TODO "/images/{name:.*}/push": postImagesPush, N yes ok "/images/{name:.*}/tag": postImagesTag, 0 ok "/containers/create": postContainersCreate, 0 @@ -39,7 +39,7 @@ ok "/containers/{name:.*}/copy": postContainersCopy, 0 **DELETE** ok "/containers/{name:.*}": deleteContainers, 0 -TODO "/images/{name:.*}": deleteImages, N +#3645 "/images/{name:.*}": deleteImages, N **OPTIONS** ok "": optionsHandler, 0 From 90b52b7e0623271ec0351ffb6dc7a7586bbe6e4d Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Mon, 20 Jan 2014 17:11:03 -0800 Subject: [PATCH 142/364] Reordering to make FAQ last --- docs/sources/toctree.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/toctree.rst b/docs/sources/toctree.rst index 89d63b933f..b43a4d827b 100644 --- a/docs/sources/toctree.rst +++ b/docs/sources/toctree.rst @@ -18,5 +18,6 @@ This documentation has the following resources: commandline/index contributing/index terms/index - faq articles/index + faq + From 230554695ad083048268cbf0fd1364cabdaddce8 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 16 Jan 2014 10:23:30 -0800 Subject: [PATCH 143/364] Remove Ken from docs maintainers (he's busy on other things) Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- docs/MAINTAINERS | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/MAINTAINERS b/docs/MAINTAINERS index d782e43c5e..e816670419 100644 --- a/docs/MAINTAINERS +++ b/docs/MAINTAINERS @@ -1,4 +1,3 @@ Andy Rothfusz (@metalivedev) -Ken Cochrane (@kencochrane) James Turnbull (@jamtur01) Sven Dowideit (@SvenDowideit) From 1fe426641d30b5cde4adc826b3112468a4b10559 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 20 Jan 2014 18:46:38 -0800 Subject: [PATCH 144/364] Remove myself as sole maintainer of api reference docs. Please still ping me before changing APIs :) Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- docs/sources/reference/api/MAINTAINERS | 1 - 1 file changed, 1 deletion(-) delete mode 100644 docs/sources/reference/api/MAINTAINERS diff --git a/docs/sources/reference/api/MAINTAINERS b/docs/sources/reference/api/MAINTAINERS deleted file mode 100644 index 1887dfc232..0000000000 --- a/docs/sources/reference/api/MAINTAINERS +++ /dev/null @@ -1 +0,0 @@ -Solomon Hykes (@shykes) From cf1781608326e9e3ce090383259bbc26d43c49d6 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 21 Jan 2014 01:48:48 +0000 Subject: [PATCH 145/364] docs: simplify basic usage doc, step 1: check your docker install docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- docs/sources/use/basics.rst | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index 7d3d8e42af..6ad3851309 100644 --- a/docs/sources/use/basics.rst +++ b/docs/sources/use/basics.rst @@ -6,21 +6,22 @@ Learn Basic Commands ==================== -Starting Docker ---------------- +Check your docker install +------------------------- -If you have used one of the quick install paths, Docker may have been -installed with upstart, Ubuntu's system for starting processes at boot -time. You should be able to run ``sudo docker help`` and get output. - -If you get ``docker: command not found`` or something like -``/var/lib/docker/repositories: permission denied`` you will need to -specify the path to it and manually start it. +This guide assumes you have a working installation of Docker. To check +your docker install, run the following command: .. code-block:: bash - # Run docker in daemon mode - sudo /docker -d & + # Check that you have a working install + docker info + +If you get ``docker: command not found`` or something like +``/var/lib/docker/repositories: permission denied`` you have an incomplete +docker installation. Please refer to :ref:`installation_list` for installation +instructions. + Download a pre-built image -------------------------- From cf5504eed50f01797646079ab849260ec16cd5a2 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 21 Jan 2014 02:10:32 +0000 Subject: [PATCH 146/364] docs: move a sysadmin-focused topic out of the basic usage manual and into installation docs (setting up a user group for non-root access) docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- docs/sources/installation/binaries.rst | 22 ++++++++++++ docs/sources/installation/ubuntulinux.rst | 38 +++++++++++++++++++- docs/sources/use/basics.rst | 42 ++--------------------- 3 files changed, 62 insertions(+), 40 deletions(-) diff --git a/docs/sources/installation/binaries.rst b/docs/sources/installation/binaries.rst index 611429f84a..9b741d6918 100644 --- a/docs/sources/installation/binaries.rst +++ b/docs/sources/installation/binaries.rst @@ -57,6 +57,28 @@ Run the docker daemon # start the docker in daemon mode from the directory you unpacked sudo ./docker -d & + +.. _dockergroup: + +Giving non-root access +---------------------- + +The ``docker`` daemon always runs as the root user, and since Docker version +0.5.2, the ``docker`` daemon binds to a Unix socket instead of a TCP port. By +default that Unix socket is owned by the user *root*, and so, by default, you +can access it with ``sudo``. + +Starting in version 0.5.3, if you (or your Docker installer) create a +Unix group called *docker* and add users to it, then the ``docker`` +daemon will make the ownership of the Unix socket read/writable by the +*docker* group when the daemon starts. The ``docker`` daemon must +always run as the root user, but if you run the ``docker`` client as a user in +the *docker* group then you don't need to add ``sudo`` to all the +client commands. + +.. warning:: The *docker* group is root-equivalent. + + Upgrades -------- diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 17eee15e9a..9019f76c45 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -167,7 +167,43 @@ Type ``exit`` to exit **Done!**, now continue with the :ref:`hello_world` example. -Upgrades + +Giving non-root access +---------------------- + +The ``docker`` daemon always runs as the root user, and since Docker version +0.5.2, the ``docker`` daemon binds to a Unix socket instead of a TCP port. By +default that Unix socket is owned by the user *root*, and so, by default, you +can access it with ``sudo``. + +Starting in version 0.5.3, if you (or your Docker installer) create a +Unix group called *docker* and add users to it, then the ``docker`` +daemon will make the ownership of the Unix socket read/writable by the +*docker* group when the daemon starts. The ``docker`` daemon must +always run as the root user, but if you run the ``docker`` client as a user in +the *docker* group then you don't need to add ``sudo`` to all the +client commands. + +.. warning:: The *docker* group is root-equivalent. + +**Example:** + +.. code-block:: bash + + # Add the docker group if it doesn't already exist. + sudo groupadd docker + + # Add the connected user "${USER}" to the docker group. + # Change the user name to match your preferred user. + # You may have to logout and log back in again for + # this to take effect. + sudo gpasswd -a ${USER} docker + + # Restart the docker daemon. + sudo service docker restart + + +Upgrade -------- To install the latest version of docker, use the standard ``apt-get`` method: diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index 6ad3851309..ed9dafe786 100644 --- a/docs/sources/use/basics.rst +++ b/docs/sources/use/basics.rst @@ -18,10 +18,10 @@ your docker install, run the following command: docker info If you get ``docker: command not found`` or something like -``/var/lib/docker/repositories: permission denied`` you have an incomplete -docker installation. Please refer to :ref:`installation_list` for installation -instructions. +``/var/lib/docker/repositories: permission denied`` you may have an incomplete +docker installation or insufficient privileges to access docker on your machine. +Please refer to :ref:`installation_list` for installation instructions. Download a pre-built image -------------------------- @@ -52,42 +52,6 @@ Running an interactive shell # use the escape sequence Ctrl-p + Ctrl-q sudo docker run -i -t ubuntu /bin/bash -.. _dockergroup: - -The sudo command and the docker Group -------------------------------------- - -The ``docker`` daemon always runs as the root user, and since Docker version -0.5.2, the ``docker`` daemon binds to a Unix socket instead of a TCP port. By -default that Unix socket is owned by the user *root*, and so, by default, you -can access it with ``sudo``. - -Starting in version 0.5.3, if you (or your Docker installer) create a -Unix group called *docker* and add users to it, then the ``docker`` -daemon will make the ownership of the Unix socket read/writable by the -*docker* group when the daemon starts. The ``docker`` daemon must -always run as the root user, but if you run the ``docker`` client as a user in -the *docker* group then you don't need to add ``sudo`` to all the -client commands. - -.. warning:: The *docker* group is root-equivalent. - -**Example:** - -.. code-block:: bash - - # Add the docker group if it doesn't already exist. - sudo groupadd docker - - # Add the connected user "${USER}" to the docker group. - # Change the user name to match your preferred user. - # You may have to logout and log back in again for - # this to take effect. - sudo gpasswd -a ${USER} docker - - # Restart the docker daemon. - sudo service docker restart - .. _bind_docker: Bind Docker to another host/port or a Unix socket From 76eee50a67732ee18a678ebf54720b78370254df Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 21 Jan 2014 02:58:04 +0000 Subject: [PATCH 147/364] docs: add standard 'check your docker install' paragraph to the 'hello world' examples. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- docs/sources/examples/hello_world.rst | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/docs/sources/examples/hello_world.rst b/docs/sources/examples/hello_world.rst index aeb95881d2..57954a98ca 100644 --- a/docs/sources/examples/hello_world.rst +++ b/docs/sources/examples/hello_world.rst @@ -9,25 +9,23 @@ Hello World .. _running_examples: -Running the Examples -==================== +Check your docker install +------------------------- -All the examples assume your machine is running the ``docker`` daemon. To -run the ``docker`` daemon in the background, simply type: +This guide assumes you have a working installation of Docker. To check +your docker install, run the following command: .. code-block:: bash - sudo docker -d & + # Check that you have a working install + docker info -Now you can run Docker in client mode: by default all commands will be -forwarded to the ``docker`` daemon via a protected Unix socket, so you -must run as the ``root`` or via the ``sudo`` command. +If you get ``docker: command not found`` or something like +``/var/lib/docker/repositories: permission denied`` you may have an incomplete +docker installation or insufficient privileges to access docker on your machine. -.. code-block:: bash +Please refer to :ref:`installation_list` for installation instructions. - sudo docker help - ----- .. _hello_world: @@ -88,9 +86,7 @@ Hello World Daemon And now for the most boring daemon ever written! -This example assumes you have Docker installed and the Ubuntu -image already imported with ``docker pull ubuntu``. We will use the Ubuntu -image to run a simple hello world daemon that will just print hello +We will use the Ubuntu image to run a simple hello world daemon that will just print hello world to standard out every second. It will continue to do this until we stop it. From 589e1a9bdce47d87870c64c5449002bca2b315a4 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 21 Jan 2014 03:31:23 +0000 Subject: [PATCH 148/364] docs: "first steps with docker" is a better title than "learn basic commands" Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- docs/sources/use/basics.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index ed9dafe786..cfc5ba4cb3 100644 --- a/docs/sources/use/basics.rst +++ b/docs/sources/use/basics.rst @@ -1,10 +1,10 @@ -:title: Learn Basic Commands +:title: First steps with Docker :description: Common usage and commands :keywords: Examples, Usage, basic commands, docker, documentation, examples -Learn Basic Commands -==================== +First steps with Docker +======================= Check your docker install ------------------------- From c24d128bb545e292ffb0790d8ad3652f56d62b44 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 21 Jan 2014 03:39:33 +0000 Subject: [PATCH 149/364] Docs: move advanced topic "create your own base image" out of basic user guide Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- docs/sources/{use => articles}/baseimages.rst | 0 docs/sources/articles/index.rst | 1 + docs/sources/use/index.rst | 1 - 3 files changed, 1 insertion(+), 1 deletion(-) rename docs/sources/{use => articles}/baseimages.rst (100%) diff --git a/docs/sources/use/baseimages.rst b/docs/sources/articles/baseimages.rst similarity index 100% rename from docs/sources/use/baseimages.rst rename to docs/sources/articles/baseimages.rst diff --git a/docs/sources/articles/index.rst b/docs/sources/articles/index.rst index be01a7af32..708ca35326 100644 --- a/docs/sources/articles/index.rst +++ b/docs/sources/articles/index.rst @@ -10,3 +10,4 @@ Contents: :maxdepth: 1 security + baseimages diff --git a/docs/sources/use/index.rst b/docs/sources/use/index.rst index 23428226a7..c1b7691cca 100644 --- a/docs/sources/use/index.rst +++ b/docs/sources/use/index.rst @@ -14,7 +14,6 @@ Contents: basics workingwithrepository - baseimages port_redirection networking host_integration From fc30346086a890687d145c33aa8fb3d0ad6a4b7e Mon Sep 17 00:00:00 2001 From: Charles Lindsay Date: Mon, 20 Jan 2014 18:59:31 -0800 Subject: [PATCH 150/364] Add failing test for odd kernel version Docker-DCO-1.1-Signed-off-by: Charles Lindsay (github: chazomaticus) --- utils/utils_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/utils_test.go b/utils/utils_test.go index c8be7b1928..e95e108225 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -414,6 +414,7 @@ func TestParseRelease(t *testing.T) { assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54}, 0) assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54, Flavor: "1"}, 0) assertParseRelease(t, "3.8.0-19-generic", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "19-generic"}, 0) + assertParseRelease(t, "3.12.8tag", &KernelVersionInfo{Kernel: 3, Major: 12, Minor: 8, Flavor: "tag"}, 0) } func TestParsePortMapping(t *testing.T) { From d2c9c1036b94b0c3e7fa3f591fa83f9c95f49406 Mon Sep 17 00:00:00 2001 From: Charles Lindsay Date: Mon, 20 Jan 2014 19:20:33 -0800 Subject: [PATCH 151/364] Remove Flavor from KernelVersionInfo Also change to parsing it with regexp to keep things simple. Docker-DCO-1.1-Signed-off-by: Charles Lindsay (github: chazomaticus) --- utils/utils.go | 54 +++++++++++++-------------------------------- utils/utils_test.go | 14 ++++++------ 2 files changed, 22 insertions(+), 46 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index 08a1994c1a..13c69c5ffc 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -6,6 +6,7 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" + "errors" "fmt" "index/suffixarray" "io" @@ -552,15 +553,10 @@ type KernelVersionInfo struct { Kernel int Major int Minor int - Flavor string } func (k *KernelVersionInfo) String() string { - flavor := "" - if len(k.Flavor) > 0 { - flavor = fmt.Sprintf("-%s", k.Flavor) - } - return fmt.Sprintf("%d.%d.%d%s", k.Kernel, k.Major, k.Minor, flavor) + return fmt.Sprintf("%d.%d.%d", k.Kernel, k.Major, k.Minor) } // Compare two KernelVersionInfo struct. @@ -613,48 +609,28 @@ func GetKernelVersion() (*KernelVersionInfo, error) { func ParseRelease(release string) (*KernelVersionInfo, error) { var ( - flavor string - kernel, major, minor int - err error + parts [3]int + err error ) - tmp := strings.SplitN(release, "-", 2) - tmp2 := strings.Split(tmp[0], ".") + re := regexp.MustCompile(`^([0-9]+)\.([0-9]+)\.([0-9]+)`) + subs := re.FindStringSubmatch(release) - if len(tmp2) > 0 { - kernel, err = strconv.Atoi(tmp2[0]) + if len(subs) < 4 { + return nil, errors.New("Can't parse kernel version " + release) + } + + for i := 0; i < 3; i++ { + parts[i], err = strconv.Atoi(subs[i+1]) if err != nil { return nil, err } } - if len(tmp2) > 1 { - major, err = strconv.Atoi(tmp2[1]) - if err != nil { - return nil, err - } - } - - if len(tmp2) > 2 { - // Removes "+" because git kernels might set it - minorUnparsed := strings.Trim(tmp2[2], "+") - minor, err = strconv.Atoi(minorUnparsed) - if err != nil { - return nil, err - } - } - - if len(tmp) == 2 { - flavor = tmp[1] - } else { - flavor = "" - } - return &KernelVersionInfo{ - Kernel: kernel, - Major: major, - Minor: minor, - Flavor: flavor, + Kernel: parts[0], + Major: parts[1], + Minor: parts[2], }, nil } diff --git a/utils/utils_test.go b/utils/utils_test.go index e95e108225..8acc27e15a 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -237,16 +237,16 @@ func TestCompareKernelVersion(t *testing.T) { &KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0}, 1) assertKernelVersion(t, - &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"}, - &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "16"}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, 0) assertKernelVersion(t, &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 5}, &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, 1) assertKernelVersion(t, - &KernelVersionInfo{Kernel: 3, Major: 0, Minor: 20, Flavor: "25"}, - &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"}, + &KernelVersionInfo{Kernel: 3, Major: 0, Minor: 20}, + &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, -1) } @@ -412,9 +412,9 @@ func assertParseRelease(t *testing.T, release string, b *KernelVersionInfo, resu func TestParseRelease(t *testing.T) { assertParseRelease(t, "3.8.0", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, 0) assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54}, 0) - assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54, Flavor: "1"}, 0) - assertParseRelease(t, "3.8.0-19-generic", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "19-generic"}, 0) - assertParseRelease(t, "3.12.8tag", &KernelVersionInfo{Kernel: 3, Major: 12, Minor: 8, Flavor: "tag"}, 0) + assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54}, 0) + assertParseRelease(t, "3.8.0-19-generic", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, 0) + assertParseRelease(t, "3.12.8tag", &KernelVersionInfo{Kernel: 3, Major: 12, Minor: 8}, 0) } func TestParsePortMapping(t *testing.T) { From 14b2b2b7c2db83ef1413a7469608f655cd15958e Mon Sep 17 00:00:00 2001 From: Charles Lindsay Date: Mon, 20 Jan 2014 19:21:25 -0800 Subject: [PATCH 152/364] Fix apparent typo Docker-DCO-1.1-Signed-off-by: Charles Lindsay (github: chazomaticus) --- utils/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/utils.go b/utils/utils.go index 13c69c5ffc..e5757ae36c 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -560,7 +560,7 @@ func (k *KernelVersionInfo) String() string { } // Compare two KernelVersionInfo struct. -// Returns -1 if a < b, = if a == b, 1 it a > b +// Returns -1 if a < b, 0 if a == b, 1 it a > b func CompareKernelVersion(a, b *KernelVersionInfo) int { if a.Kernel < b.Kernel { return -1 From 2cfd696b9bacda67468a0a2ef93d61258781e8bc Mon Sep 17 00:00:00 2001 From: "Roberto G. Hashioka" Date: Tue, 21 Jan 2014 04:06:19 +0000 Subject: [PATCH 153/364] Added missing attributes to api search calls: - Added an argument to the call() method in order to control the auth sharing - Enabled it only for search. Pulls and pushes were enabled already. - Grouped a few variable declarations Docker-DCO-1.1-Signed-off-by: Roberto Hashioka (github: rogaha) --- api.go | 21 ++++++++++ commands.go | 91 ++++++++++++++++++++++++++------------------ registry/registry.go | 4 ++ server.go | 10 ++++- 4 files changed, 88 insertions(+), 38 deletions(-) diff --git a/api.go b/api.go index 61df3e7fe6..f48b3ea261 100644 --- a/api.go +++ b/api.go @@ -500,12 +500,33 @@ func getImagesSearch(srv *Server, version float64, w http.ResponseWriter, r *htt if err := parseForm(r); err != nil { return err } + var ( + authEncoded = r.Header.Get("X-Registry-Auth") + authConfig = &auth.AuthConfig{} + metaHeaders = map[string][]string{} + ) + + if authEncoded != "" { + authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) + if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { + // for a search it is not an error if no auth was given + // to increase compatibility with the existing api it is defaulting to be empty + authConfig = &auth.AuthConfig{} + } + } + for k, v := range r.Header { + if strings.HasPrefix(k, "X-Meta-") { + metaHeaders[k] = v + } + } var ( err error outs *engine.Table job = srv.Eng.Job("search", r.Form.Get("term")) ) + job.SetenvJson("metaHeaders", metaHeaders) + job.SetenvJson("authConfig", authConfig) if version >= 1.9 { job.Stdout.Add(w) } else if outs, err = job.Stdout.AddTable(); err != nil { diff --git a/commands.go b/commands.go index a5d7c9100a..17d3f0ede1 100644 --- a/commands.go +++ b/commands.go @@ -335,7 +335,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig.ServerAddress = serverAddress cli.configFile.Configs[serverAddress] = authconfig - body, statusCode, err := readBody(cli.call("POST", "/auth", cli.configFile.Configs[serverAddress])) + body, statusCode, err := readBody(cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false)) if statusCode == 401 { delete(cli.configFile.Configs, serverAddress) auth.SaveConfig(cli.configFile) @@ -400,7 +400,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error { fmt.Fprintf(cli.out, "Git commit (client): %s\n", GITCOMMIT) } - body, _, err := readBody(cli.call("GET", "/version", nil)) + body, _, err := readBody(cli.call("GET", "/version", nil, false)) if err != nil { return err } @@ -441,7 +441,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { return nil } - body, _, err := readBody(cli.call("GET", "/info", nil)) + body, _, err := readBody(cli.call("GET", "/info", nil, false)) if err != nil { return err } @@ -521,7 +521,7 @@ func (cli *DockerCli) CmdStop(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - _, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil)) + _, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, false)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to stop one or more containers") @@ -548,7 +548,7 @@ func (cli *DockerCli) CmdRestart(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - _, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil)) + _, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, false)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to restart one or more containers") @@ -567,7 +567,7 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { if s == syscall.SIGCHLD { continue } - if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%d", cid, s), nil)); err != nil { + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%d", cid, s), nil, false)); err != nil { utils.Debugf("Error sending signal: %s", err) } } @@ -594,7 +594,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { return fmt.Errorf("Impossible to start and attach multiple containers at once.") } - body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)) + body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)) if err != nil { return err } @@ -630,7 +630,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - _, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil)) + _, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, false)) if err != nil { if !*attach || !*openStdin { fmt.Fprintf(cli.err, "%s\n", err) @@ -687,9 +687,9 @@ func (cli *DockerCli) CmdInspect(args ...string) error { status := 0 for _, name := range cmd.Args() { - obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil)) + obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false)) if err != nil { - obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil)) + obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, false)) if err != nil { if strings.Contains(err.Error(), "No such") { fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name) @@ -755,7 +755,7 @@ func (cli *DockerCli) CmdTop(args ...string) error { val.Set("ps_args", strings.Join(cmd.Args()[1:], " ")) } - body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil)) + body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false)) if err != nil { return err } @@ -790,7 +790,7 @@ func (cli *DockerCli) CmdPort(args ...string) error { port = parts[0] proto = parts[1] } - body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil)) + body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false)) if err != nil { return err } @@ -823,7 +823,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - body, _, err := readBody(cli.call("DELETE", "/images/"+name, nil)) + body, _, err := readBody(cli.call("DELETE", "/images/"+name, nil, false)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") @@ -860,7 +860,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { return nil } - stream, _, err := cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil) + stream, _, err := cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false) if stream != nil { defer stream.Close() } @@ -929,7 +929,7 @@ func (cli *DockerCli) CmdRm(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil)) + _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, false)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more containers") @@ -953,7 +953,7 @@ func (cli *DockerCli) CmdKill(args ...string) error { var encounteredError error for _, name := range args { - if _, _, err := readBody(cli.call("POST", "/containers/"+name+"/kill", nil)); err != nil { + if _, _, err := readBody(cli.call("POST", "/containers/"+name+"/kill", nil, false)); err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to kill one or more containers") } else { @@ -1138,7 +1138,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { filter := cmd.Arg(0) if *flViz || *flTree { - stream, _, err := cli.call("GET", "/images/json?all=1", nil) + stream, _, err := cli.call("GET", "/images/json?all=1", nil, false) if stream != nil { defer stream.Close() } @@ -1210,7 +1210,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { v.Set("all", "1") } - stream, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil) + stream, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil, false) if stream != nil { defer stream.Close() } @@ -1364,7 +1364,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { v.Set("size", "1") } - body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil)) + body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, false)) if err != nil { return err } @@ -1456,7 +1456,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error { return err } } - body, _, err := readBody(cli.call("POST", "/commit?"+v.Encode(), config)) + body, _, err := readBody(cli.call("POST", "/commit?"+v.Encode(), config, false)) if err != nil { return err } @@ -1531,7 +1531,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error { return nil } - stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil) + stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false) if stream != nil { defer stream.Close() } @@ -1569,7 +1569,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error { return nil } name := cmd.Arg(0) - body, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil)) + body, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false)) if err != nil { return err } @@ -1606,7 +1606,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { return nil } name := cmd.Arg(0) - body, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil)) + body, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false)) if err != nil { return err } @@ -1673,7 +1673,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { v := url.Values{} v.Set("term", cmd.Arg(0)) - stream, _, err := cli.call("GET", "/images/search?"+v.Encode(), nil) + stream, _, err := cli.call("GET", "/images/search?"+v.Encode(), nil, true) if stream != nil { defer stream.Close() } @@ -1741,7 +1741,7 @@ func (cli *DockerCli) CmdTag(args ...string) error { v.Set("force", "1") } - if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil)); err != nil { + if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false)); err != nil { return err } return nil @@ -1990,7 +1990,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } //create the container - body, statusCode, err := readBody(cli.call("POST", "/containers/create?"+containerValues.Encode(), config)) + body, statusCode, err := readBody(cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false)) //if image not found try to pull it if statusCode == 404 { _, tag := utils.ParseRepositoryTag(config.Image) @@ -2027,7 +2027,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil { return err } - if body, _, err = readBody(cli.call("POST", "/containers/create?"+containerValues.Encode(), config)); err != nil { + if body, _, err = readBody(cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false)); err != nil { return err } } else if err != nil { @@ -2128,7 +2128,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } //start the container - if _, _, err = readBody(cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig)); err != nil { + if _, _, err = readBody(cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig, false)); err != nil { return err } @@ -2158,13 +2158,13 @@ func (cli *DockerCli) CmdRun(args ...string) error { if autoRemove { // Autoremove: wait for the container to finish, retrieve // the exit code and remove the container - if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.ID+"/wait", nil)); err != nil { + if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.ID+"/wait", nil, false)); err != nil { return err } if _, status, err = getExitCode(cli, runResult.ID); err != nil { return err } - if _, _, err := readBody(cli.call("DELETE", "/containers/"+runResult.ID+"?v=1", nil)); err != nil { + if _, _, err := readBody(cli.call("DELETE", "/containers/"+runResult.ID+"?v=1", nil, false)); err != nil { return err } } else { @@ -2200,7 +2200,7 @@ func (cli *DockerCli) CmdCp(args ...string) error { copyData.Resource = info[1] copyData.HostPath = cmd.Arg(1) - stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData) + stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, false) if stream != nil { defer stream.Close() } @@ -2251,7 +2251,7 @@ func (cli *DockerCli) CmdLoad(args ...string) error { return nil } -func (cli *DockerCli) call(method, path string, data interface{}) (io.ReadCloser, int, error) { +func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) { var params io.Reader if data != nil { buf, err := json.Marshal(data) @@ -2260,7 +2260,6 @@ func (cli *DockerCli) call(method, path string, data interface{}) (io.ReadCloser } params = bytes.NewBuffer(buf) } - // fixme: refactor client to support redirect re := regexp.MustCompile("/+") path = re.ReplaceAllString(path, "/") @@ -2269,6 +2268,26 @@ func (cli *DockerCli) call(method, path string, data interface{}) (io.ReadCloser if err != nil { return nil, -1, err } + if passAuthInfo { + cli.LoadConfigFile() + // Resolve the Auth config relevant for this server + authConfig := cli.configFile.ResolveAuthConfig(auth.IndexServerAddress()) + getHeaders := func(authConfig auth.AuthConfig) (map[string][]string, error) { + buf, err := json.Marshal(authConfig) + if err != nil { + return nil, err + } + registryAuthHeader := []string{ + base64.URLEncoding.EncodeToString(buf), + } + return map[string][]string{"X-Registry-Auth": registryAuthHeader}, nil + } + if headers, err := getHeaders(authConfig); err == nil && headers != nil { + for k, v := range headers { + req.Header[k] = v + } + } + } req.Header.Set("User-Agent", "Docker-Client/"+VERSION) req.Host = cli.addr if data != nil { @@ -2493,7 +2512,7 @@ func (cli *DockerCli) resizeTty(id string) { v := url.Values{} v.Set("h", strconv.Itoa(height)) v.Set("w", strconv.Itoa(width)) - if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil)); err != nil { + if _, _, err := readBody(cli.call("POST", "/containers/"+id+"/resize?"+v.Encode(), nil, false)); err != nil { utils.Errorf("Error resize: %s", err) } } @@ -2530,7 +2549,7 @@ func (cli *DockerCli) LoadConfigFile() (err error) { } func waitForExit(cli *DockerCli, containerId string) (int, error) { - body, _, err := readBody(cli.call("POST", "/containers/"+containerId+"/wait", nil)) + body, _, err := readBody(cli.call("POST", "/containers/"+containerId+"/wait", nil, false)) if err != nil { return -1, err } @@ -2545,7 +2564,7 @@ func waitForExit(cli *DockerCli, containerId string) (int, error) { // getExitCode perform an inspect on the container. It returns // the running state and the exit code. func getExitCode(cli *DockerCli, containerId string) (bool, int, error) { - body, _, err := readBody(cli.call("GET", "/containers/"+containerId+"/json", nil)) + body, _, err := readBody(cli.call("GET", "/containers/"+containerId+"/json", nil, false)) if err != nil { // If we can't connect, then the daemon probably died. if err != ErrConnectionRefused { diff --git a/registry/registry.go b/registry/registry.go index b2d26a2dba..c0d8414de0 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -617,6 +617,10 @@ func (r *Registry) SearchRepositories(term string) (*SearchResults, error) { if err != nil { return nil, err } + if r.authConfig != nil && len(r.authConfig.Username) > 0 { + req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password) + } + req.Header.Set("X-Docker-Token", "true") res, err := r.client.Do(req) if err != nil { return nil, err diff --git a/server.go b/server.go index c99b689069..6617bab7e4 100644 --- a/server.go +++ b/server.go @@ -462,9 +462,15 @@ func (srv *Server) ImagesSearch(job *engine.Job) engine.Status { job.Errorf("Usage: %s TERM", job.Name) return engine.StatusErr } - term := job.Args[0] + var ( + term = job.Args[0] + metaHeaders = map[string][]string{} + authConfig = &auth.AuthConfig{} + ) + job.GetenvJson("authConfig", authConfig) + job.GetenvJson("metaHeaders", metaHeaders) - r, err := registry.NewRegistry(nil, srv.HTTPRequestFactory(nil), auth.IndexServerAddress()) + r, err := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), auth.IndexServerAddress()) if err != nil { job.Error(err) return engine.StatusErr From d16691e3ad520675752ff319be054e688c50c475 Mon Sep 17 00:00:00 2001 From: "Roberto G. Hashioka" Date: Tue, 21 Jan 2014 04:11:40 +0000 Subject: [PATCH 154/364] - Ajusted server.go with gofmt Docker-DCO-1.1-Signed-off-by: Roberto Hashioka (github: rogaha) --- server.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/server.go b/server.go index 6617bab7e4..a3e15307be 100644 --- a/server.go +++ b/server.go @@ -462,13 +462,13 @@ func (srv *Server) ImagesSearch(job *engine.Job) engine.Status { job.Errorf("Usage: %s TERM", job.Name) return engine.StatusErr } - var ( - term = job.Args[0] - metaHeaders = map[string][]string{} - authConfig = &auth.AuthConfig{} - ) - job.GetenvJson("authConfig", authConfig) - job.GetenvJson("metaHeaders", metaHeaders) + var ( + term = job.Args[0] + metaHeaders = map[string][]string{} + authConfig = &auth.AuthConfig{} + ) + job.GetenvJson("authConfig", authConfig) + job.GetenvJson("metaHeaders", metaHeaders) r, err := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), auth.IndexServerAddress()) if err != nil { From 5b97e00438b094db608f350732f7e4d426c50999 Mon Sep 17 00:00:00 2001 From: Charles Lindsay Date: Mon, 20 Jan 2014 21:02:37 -0800 Subject: [PATCH 155/364] Use Sscanf instead of regexp Docker-DCO-1.1-Signed-off-by: Charles Lindsay (github: chazomaticus) --- utils/utils.go | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index e5757ae36c..0f05a24279 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -609,28 +609,22 @@ func GetKernelVersion() (*KernelVersionInfo, error) { func ParseRelease(release string) (*KernelVersionInfo, error) { var ( - parts [3]int - err error + kernel, major, minor, parsed int + err error ) - re := regexp.MustCompile(`^([0-9]+)\.([0-9]+)\.([0-9]+)`) - subs := re.FindStringSubmatch(release) - - if len(subs) < 4 { + parsed, err = fmt.Sscanf(release, "%d.%d.%d", &kernel, &major, &minor) + if err != nil { + return nil, err + } + if parsed < 3 { return nil, errors.New("Can't parse kernel version " + release) } - for i := 0; i < 3; i++ { - parts[i], err = strconv.Atoi(subs[i+1]) - if err != nil { - return nil, err - } - } - return &KernelVersionInfo{ - Kernel: parts[0], - Major: parts[1], - Minor: parts[2], + Kernel: kernel, + Major: major, + Minor: minor, }, nil } From b78ae3b652d609a895cf36886e053124d2f8ae80 Mon Sep 17 00:00:00 2001 From: Charles Lindsay Date: Mon, 20 Jan 2014 21:03:09 -0800 Subject: [PATCH 156/364] Re-add Flavor to KernelVersionInfo Docker-DCO-1.1-Signed-off-by: Charles Lindsay (github: chazomaticus) --- utils/utils.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index 0f05a24279..ae39bcac36 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -553,10 +553,11 @@ type KernelVersionInfo struct { Kernel int Major int Minor int + Flavor string } func (k *KernelVersionInfo) String() string { - return fmt.Sprintf("%d.%d.%d", k.Kernel, k.Major, k.Minor) + return fmt.Sprintf("%d.%d.%d%s", k.Kernel, k.Major, k.Minor, k.Flavor) } // Compare two KernelVersionInfo struct. @@ -610,13 +611,10 @@ func GetKernelVersion() (*KernelVersionInfo, error) { func ParseRelease(release string) (*KernelVersionInfo, error) { var ( kernel, major, minor, parsed int - err error + flavor string ) - parsed, err = fmt.Sscanf(release, "%d.%d.%d", &kernel, &major, &minor) - if err != nil { - return nil, err - } + parsed, _ = fmt.Sscanf(release, "%d.%d.%d%s", &kernel, &major, &minor, &flavor) if parsed < 3 { return nil, errors.New("Can't parse kernel version " + release) } @@ -625,6 +623,7 @@ func ParseRelease(release string) (*KernelVersionInfo, error) { Kernel: kernel, Major: major, Minor: minor, + Flavor: flavor, }, nil } From ae2af201f340420f70ca05d54d150764bd88e680 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 21 Jan 2014 07:26:45 +0000 Subject: [PATCH 157/364] Docs: fix capitalization of "Docker" Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- docs/sources/examples/hello_world.rst | 6 +++--- docs/sources/installation/ubuntulinux.rst | 4 ++-- docs/sources/use/basics.rst | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/sources/examples/hello_world.rst b/docs/sources/examples/hello_world.rst index 57954a98ca..3a6a7b8587 100644 --- a/docs/sources/examples/hello_world.rst +++ b/docs/sources/examples/hello_world.rst @@ -9,11 +9,11 @@ Hello World .. _running_examples: -Check your docker install +Check your Docker install ------------------------- This guide assumes you have a working installation of Docker. To check -your docker install, run the following command: +your Docker install, run the following command: .. code-block:: bash @@ -22,7 +22,7 @@ your docker install, run the following command: If you get ``docker: command not found`` or something like ``/var/lib/docker/repositories: permission denied`` you may have an incomplete -docker installation or insufficient privileges to access docker on your machine. +Docker installation or insufficient privileges to access docker on your machine. Please refer to :ref:`installation_list` for installation instructions. diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 9019f76c45..d5e4a248ba 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -35,7 +35,7 @@ Dependencies **Linux kernel 3.8** -Due to a bug in LXC, docker works best on the 3.8 kernel. Precise +Due to a bug in LXC, Docker works best on the 3.8 kernel. Precise comes with a 3.2 kernel, so we need to upgrade it. The kernel you'll install when following these steps comes with AUFS built in. We also include the generic headers to enable packages that depend on them, @@ -199,7 +199,7 @@ client commands. # this to take effect. sudo gpasswd -a ${USER} docker - # Restart the docker daemon. + # Restart the Docker daemon. sudo service docker restart diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index cfc5ba4cb3..6bd1f0b7a0 100644 --- a/docs/sources/use/basics.rst +++ b/docs/sources/use/basics.rst @@ -6,11 +6,11 @@ First steps with Docker ======================= -Check your docker install +Check your Docker install ------------------------- This guide assumes you have a working installation of Docker. To check -your docker install, run the following command: +your Docker install, run the following command: .. code-block:: bash @@ -19,7 +19,7 @@ your docker install, run the following command: If you get ``docker: command not found`` or something like ``/var/lib/docker/repositories: permission denied`` you may have an incomplete -docker installation or insufficient privileges to access docker on your machine. +docker installation or insufficient privileges to access Docker on your machine. Please refer to :ref:`installation_list` for installation instructions. From fdf43f5de14e1ddf9e806e78f51e98721b920b5d Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 21 Jan 2014 01:36:38 +0000 Subject: [PATCH 158/364] docs: harmonize intro paragraph with website Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- docs/sources/index.rst | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/docs/sources/index.rst b/docs/sources/index.rst index c10ed2abf9..346a6619c5 100644 --- a/docs/sources/index.rst +++ b/docs/sources/index.rst @@ -5,25 +5,21 @@ Introduction ------------ -``docker``, the Linux Container Runtime, runs Unix processes with -strong guarantees of isolation across servers. Your software runs -repeatably everywhere because its :ref:`container_def` includes any -dependencies. +Docker is an open-source engine to easily create lightweight, portable, +self-sufficient containers from any application. The same container that a +developer builds and tests on a laptop can run at scale, in production, on +VMs, bare metal, OpenStack clusters, or any major infrastructure provider. -``docker`` runs three ways: +Common use cases for Docker include: -* as a daemon to manage LXC containers on your host machine (``sudo docker -d``) -* as a :ref:`CLI ` which talks to the daemon's `REST API - `_ (``docker run ...``) -* as a client of :ref:`Repositories ` - that let you share what you've built (``docker pull, docker - commit``). +- Automating the packaging and deployment of web applications. +- Automated testing and continuous integration/deployment. +- Deploying and scaling databases and backend services in a service-oriented environment. +- Building custom PaaS environments, either from scratch or as an extension of off-the-shelf platforms like OpenShift or Cloud Foundry. -Each use of ``docker`` is documented here. The features of Docker are -currently in active development, so this documentation will change -frequently. +Please note Docker is currently under heavy developement. It should not be used in production (yet). -For an overview of Docker, please see the `Introduction +For a high-level overview of Docker, please see the `Introduction `_. When you're ready to start working with Docker, we have a `quick start `_ and a more in-depth guide to :ref:`ubuntu_linux` and other From 08ab55419549374742bc879261f1d55b19af7265 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 21 Jan 2014 10:19:12 +0100 Subject: [PATCH 159/364] Fix handling of shared roots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If rootIsShared() is detected we apply the shell stuff to early, before the real command and arguments are added to the parameters. This means they get passed on to unshare rather than docker-init, breaking docker on e.g. fedora like: goroutine 1 [running]: runtime.panic(0x678340, 0x9b3fd7) /usr/lib64/golang/src/pkg/runtime/panic.c:266 +0xb6 github.com/dotcloud/docker/execdriver/lxc.func·001(0xc21000a1b0, 0xc21001eab0, 0x7fff24715faf) /home/alex/vcs/go/src/github.com/dotcloud/docker/execdriver/lxc/driver.go:41 +0x525 github.com/dotcloud/docker/sysinit.executeProgram(0xc21000a1b0, 0xc21000a1b0, 0xa) /home/alex/vcs/go/src/github.com/dotcloud/docker/sysinit/sysinit.go:34 +0xca github.com/dotcloud/docker/sysinit.SysInit() /home/alex/vcs/go/src/github.com/dotcloud/docker/sysinit/sysinit.go:88 +0x791 main.main() /home/alex/vcs/go/src/github.com/dotcloud/docker/dockerinit/dockerinit.go:14 +0x1a The fix is to construct the full params array before escaping it. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- execdriver/lxc/driver.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 2a6663117a..5999d5bab5 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -111,6 +111,9 @@ func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallba params = append(params, "-w", c.WorkingDir) } + params = append(params, "--", c.Entrypoint) + params = append(params, c.Arguments...) + if d.sharedRoot { // lxc-start really needs / to be non-shared, or all kinds of stuff break // when lxc-start unmount things and those unmounts propagate to the main @@ -127,9 +130,6 @@ func (d *driver) Run(c *execdriver.Process, startCallback execdriver.StartCallba } } - params = append(params, "--", c.Entrypoint) - params = append(params, c.Arguments...) - var ( name = params[0] arg = params[1:] From bde2d3aef7715814920a07d2c5e08d14eabe3e53 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 21 Jan 2014 10:39:50 +0100 Subject: [PATCH 160/364] integration: Fix TestGetContainersExport The tar changes made the name of the file in the tar archive change from "./test" to "test", update the test to the new name. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- integration/api_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/api_test.go b/integration/api_test.go index 30409c907b..00b10c22d2 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -374,7 +374,7 @@ func TestGetContainersExport(t *testing.T) { } t.Fatal(err) } - if h.Name == "./test" { + if h.Name == "test" { found = true break } From bcaf6c2359d83acd5da54f499e21f4a148f491c5 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Dec 2013 22:18:02 +0100 Subject: [PATCH 161/364] Add Put() to graphdriver API and use it This makes all users of Put() have a corresponding call to Get() which means we will be able to track whether any particular ID is in use and if not unmount it. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- container.go | 18 +++++++++++++++-- graph.go | 2 ++ graphdriver/aufs/aufs.go | 3 +++ graphdriver/devmapper/driver.go | 3 +++ graphdriver/driver.go | 1 + graphdriver/vfs/driver.go | 5 +++++ image.go | 35 ++++++++++++++++++++++++--------- integration/utils_test.go | 1 + runtime.go | 15 +++++++++++--- utils.go | 27 +++++++++++++++++++++++++ 10 files changed, 96 insertions(+), 14 deletions(-) diff --git a/container.go b/container.go index 3eebb641a1..63afad0ced 100644 --- a/container.go +++ b/container.go @@ -203,6 +203,7 @@ func (container *Container) Inject(file io.Reader, pth string) error { if err := container.EnsureMounted(); err != nil { return fmt.Errorf("inject: error mounting container %s: %s", container.ID, err) } + defer container.Unmount() // Return error if path exists destPath := path.Join(container.RootfsPath(), pth) @@ -1276,6 +1277,7 @@ func (container *Container) ExportRw() (archive.Archive, error) { if container.runtime == nil { return nil, fmt.Errorf("Can't load storage driver for unregistered container %s", container.ID) } + defer container.Unmount() return container.runtime.Diff(container) } @@ -1284,7 +1286,12 @@ func (container *Container) Export() (archive.Archive, error) { if err := container.EnsureMounted(); err != nil { return nil, err } - return archive.Tar(container.RootfsPath(), archive.Uncompressed) + + archive, err := archive.Tar(container.RootfsPath(), archive.Uncompressed) + if err != nil { + return nil, err + } + return EofReader(archive, func() { container.Unmount() }), nil } func (container *Container) WaitTimeout(timeout time.Duration) error { @@ -1409,6 +1416,7 @@ func (container *Container) GetSize() (int64, int64) { utils.Errorf("Warning: failed to compute size of container rootfs %s: %s", container.ID, err) return sizeRw, sizeRootfs } + defer container.Unmount() if differ, ok := container.runtime.driver.(graphdriver.Differ); ok { sizeRw, err = differ.DiffSize(container.ID) @@ -1443,6 +1451,7 @@ func (container *Container) Copy(resource string) (archive.Archive, error) { basePath := path.Join(container.RootfsPath(), resource) stat, err := os.Stat(basePath) if err != nil { + container.Unmount() return nil, err } if !stat.IsDir() { @@ -1453,11 +1462,16 @@ func (container *Container) Copy(resource string) (archive.Archive, error) { filter = []string{path.Base(basePath)} basePath = path.Dir(basePath) } - return archive.TarFilter(basePath, &archive.TarOptions{ + + archive, err := archive.TarFilter(basePath, &archive.TarOptions{ Compression: archive.Uncompressed, Includes: filter, Recursive: true, }) + if err != nil { + return nil, err + } + return EofReader(archive, func() { container.Unmount() }), nil } // Returns true if the container exposes a certain port diff --git a/graph.go b/graph.go index 176626d60a..42da42c8af 100644 --- a/graph.go +++ b/graph.go @@ -97,6 +97,7 @@ func (graph *Graph) Get(name string) (*Image, error) { if err != nil { return nil, fmt.Errorf("Driver %s failed to get image rootfs %s: %s", graph.driver, img.ID, err) } + defer graph.driver.Put(img.ID) var size int64 if img.Parent == "" { @@ -193,6 +194,7 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Im if err != nil { return fmt.Errorf("Driver %s failed to get image rootfs %s: %s", graph.driver, img.ID, err) } + defer graph.driver.Put(img.ID) img.graph = graph if err := StoreImage(img, jsonData, layerData, tmp, rootfs); err != nil { return err diff --git a/graphdriver/aufs/aufs.go b/graphdriver/aufs/aufs.go index 2e9fa8298a..3070200042 100644 --- a/graphdriver/aufs/aufs.go +++ b/graphdriver/aufs/aufs.go @@ -222,6 +222,9 @@ func (a *Driver) Get(id string) (string, error) { return out, nil } +func (a *Driver) Put(id string) { +} + // Returns an archive of the contents for the id func (a *Driver) Diff(id string) (archive.Archive, error) { return archive.TarFilter(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{ diff --git a/graphdriver/devmapper/driver.go b/graphdriver/devmapper/driver.go index 10ac172562..0bf5fb24e1 100644 --- a/graphdriver/devmapper/driver.go +++ b/graphdriver/devmapper/driver.go @@ -97,6 +97,9 @@ func (d *Driver) Get(id string) (string, error) { return path.Join(mp, "rootfs"), nil } +func (d *Driver) Put(id string) { +} + func (d *Driver) mount(id, mountPoint string) error { // Create the target directories if they don't exist if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) { diff --git a/graphdriver/driver.go b/graphdriver/driver.go index 1d5995dffc..2be3f05f3a 100644 --- a/graphdriver/driver.go +++ b/graphdriver/driver.go @@ -17,6 +17,7 @@ type Driver interface { Remove(id string) error Get(id string) (dir string, err error) + Put(id string) Exists(id string) bool Status() [][2]string diff --git a/graphdriver/vfs/driver.go b/graphdriver/vfs/driver.go index 12230f463a..21da63878a 100644 --- a/graphdriver/vfs/driver.go +++ b/graphdriver/vfs/driver.go @@ -84,6 +84,11 @@ func (d *Driver) Get(id string) (string, error) { return dir, nil } +func (d *Driver) Put(id string) { + // The vfs driver has no runtime resources (e.g. mounts) + // to clean up, so we don't need anything here +} + func (d *Driver) Exists(id string) bool { _, err := os.Stat(d.dir(id)) return err == nil diff --git a/image.go b/image.go index f062910ef8..7652824d49 100644 --- a/image.go +++ b/image.go @@ -104,6 +104,7 @@ func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, la if err != nil { return err } + defer driver.Put(img.Parent) changes, err := archive.ChangesDirs(layer, parent) if err != nil { return err @@ -147,7 +148,7 @@ func jsonPath(root string) string { } // TarLayer returns a tar archive of the image's filesystem layer. -func (img *Image) TarLayer() (archive.Archive, error) { +func (img *Image) TarLayer() (arch archive.Archive, err error) { if img.graph == nil { return nil, fmt.Errorf("Can't load storage driver for unregistered image %s", img.ID) } @@ -160,19 +161,35 @@ func (img *Image) TarLayer() (archive.Archive, error) { if err != nil { return nil, err } + + defer func() { + if err == nil { + driver.Put(img.ID) + } + }() + if img.Parent == "" { - return archive.Tar(imgFs, archive.Uncompressed) - } else { - parentFs, err := driver.Get(img.Parent) + archive, err := archive.Tar(imgFs, archive.Uncompressed) if err != nil { return nil, err } - changes, err := archive.ChangesDirs(imgFs, parentFs) - if err != nil { - return nil, err - } - return archive.ExportChanges(imgFs, changes) + return EofReader(archive, func() { driver.Put(img.ID) }), nil } + + parentFs, err := driver.Get(img.Parent) + if err != nil { + return nil, err + } + defer driver.Put(img.Parent) + changes, err := archive.ChangesDirs(imgFs, parentFs) + if err != nil { + return nil, err + } + archive, err := archive.ExportChanges(imgFs, changes) + if err != nil { + return nil, err + } + return EofReader(archive, func() { driver.Put(img.ID) }), nil } func ValidateID(id string) error { diff --git a/integration/utils_test.go b/integration/utils_test.go index 63ac3a44b9..69686ba69e 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -74,6 +74,7 @@ func containerFileExists(eng *engine.Engine, id, dir string, t utils.Fataler) bo if err := c.EnsureMounted(); err != nil { t.Fatal(err) } + defer c.Unmount() if _, err := os.Stat(path.Join(c.RootfsPath(), dir)); err != nil { if os.IsNotExist(err) { return false diff --git a/runtime.go b/runtime.go index 52f03f84be..5576cb2aee 100644 --- a/runtime.go +++ b/runtime.go @@ -134,6 +134,7 @@ func (runtime *Runtime) Register(container *Container) error { if err != nil { return fmt.Errorf("Error getting container filesystem %s from driver %s: %s", container.ID, runtime.driver, err) } + defer runtime.driver.Put(container.ID) container.rootfs = rootfs container.runtime = runtime @@ -460,6 +461,8 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin if err != nil { return nil, nil, err } + defer runtime.driver.Put(initID) + if err := setupInitLayer(initPath); err != nil { return nil, nil, err } @@ -520,6 +523,7 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a if err := container.EnsureMounted(); err != nil { return nil, err } + defer container.Unmount() rwTar, err := container.ExportRw() if err != nil { @@ -763,8 +767,7 @@ func (runtime *Runtime) Mount(container *Container) error { } func (runtime *Runtime) Unmount(container *Container) error { - // FIXME: Unmount is deprecated because drivers are responsible for mounting - // and unmounting when necessary. Use driver.Remove() instead. + runtime.driver.Put(container.ID) return nil } @@ -776,10 +779,12 @@ func (runtime *Runtime) Changes(container *Container) ([]archive.Change, error) if err != nil { return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err) } + defer runtime.driver.Put(container.ID) initDir, err := runtime.driver.Get(container.ID + "-init") if err != nil { return nil, fmt.Errorf("Error getting container init rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err) } + defer runtime.driver.Put(container.ID + "-init") return archive.ChangesDirs(cDir, initDir) } @@ -798,7 +803,11 @@ func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) { return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err) } - return archive.ExportChanges(cDir, changes) + archive, err := archive.ExportChanges(cDir, changes) + if err != nil { + return nil, err + } + return EofReader(archive, func() { runtime.driver.Put(container.ID) }), nil } func (runtime *Runtime) Run(c *Container, startCallback execdriver.StartCallback) (int, error) { diff --git a/utils.go b/utils.go index f0591158a4..e3ba08d51c 100644 --- a/utils.go +++ b/utils.go @@ -5,8 +5,10 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/pkg/namesgenerator" "github.com/dotcloud/docker/utils" + "io" "strconv" "strings" + "sync/atomic" ) type Change struct { @@ -339,3 +341,28 @@ func (c *checker) Exists(name string) bool { func generateRandomName(runtime *Runtime) (string, error) { return namesgenerator.GenerateRandomName(&checker{runtime}) } + +// Read an io.Reader and call a function when it returns EOF +func EofReader(r io.Reader, callback func()) *eofReader { + return &eofReader{ + Reader: r, + callback: callback, + } +} + +type eofReader struct { + io.Reader + gotEOF int32 + callback func() +} + +func (r *eofReader) Read(p []byte) (n int, err error) { + n, err = r.Reader.Read(p) + if err == io.EOF { + // Use atomics to make the gotEOF check threadsafe + if atomic.CompareAndSwapInt32(&r.gotEOF, 0, 1) { + r.callback() + } + } + return +} From 191aa17d16c0935a7deda03b4c3bc879f7a316eb Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Dec 2013 12:15:14 +0100 Subject: [PATCH 162/364] Remove container.EnsureMounted This was deprecated already and all it did was call Mount(). The use of this was a bit confusing since we need to pair Mount/Unmount calls which wasn't obvious with "EnsureMounted". Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- buildfile.go | 4 ++-- container.go | 18 ++++++------------ integration/utils_test.go | 2 +- runtime.go | 2 +- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/buildfile.go b/buildfile.go index 97cf35c406..a2b6da7347 100644 --- a/buildfile.go +++ b/buildfile.go @@ -488,7 +488,7 @@ func (b *buildFile) CmdAdd(args string) error { } b.tmpContainers[container.ID] = struct{}{} - if err := container.EnsureMounted(); err != nil { + if err := container.Mount(); err != nil { return err } defer container.Unmount() @@ -610,7 +610,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { b.tmpContainers[container.ID] = struct{}{} fmt.Fprintf(b.outStream, " ---> Running in %s\n", utils.TruncateID(container.ID)) id = container.ID - if err := container.EnsureMounted(); err != nil { + if err := container.Mount(); err != nil { return err } defer container.Unmount() diff --git a/container.go b/container.go index 63afad0ced..db08a41b51 100644 --- a/container.go +++ b/container.go @@ -200,7 +200,7 @@ func (settings *NetworkSettings) PortMappingAPI() []APIPort { // Inject the io.Reader at the given path. Note: do not close the reader func (container *Container) Inject(file io.Reader, pth string) error { - if err := container.EnsureMounted(); err != nil { + if err := container.Mount(); err != nil { return fmt.Errorf("inject: error mounting container %s: %s", container.ID, err) } defer container.Unmount() @@ -505,7 +505,7 @@ func (container *Container) Start() (err error) { container.cleanup() } }() - if err := container.EnsureMounted(); err != nil { + if err := container.Mount(); err != nil { return err } if container.runtime.networkManager.disabled { @@ -1271,7 +1271,7 @@ func (container *Container) Resize(h, w int) error { } func (container *Container) ExportRw() (archive.Archive, error) { - if err := container.EnsureMounted(); err != nil { + if err := container.Mount(); err != nil { return nil, err } if container.runtime == nil { @@ -1283,7 +1283,7 @@ func (container *Container) ExportRw() (archive.Archive, error) { } func (container *Container) Export() (archive.Archive, error) { - if err := container.EnsureMounted(); err != nil { + if err := container.Mount(); err != nil { return nil, err } @@ -1309,12 +1309,6 @@ func (container *Container) WaitTimeout(timeout time.Duration) error { } } -func (container *Container) EnsureMounted() error { - // FIXME: EnsureMounted is deprecated because drivers are now responsible - // for re-entrant mounting in their Get() method. - return container.Mount() -} - func (container *Container) Mount() error { return container.runtime.Mount(container) } @@ -1412,7 +1406,7 @@ func (container *Container) GetSize() (int64, int64) { driver = container.runtime.driver ) - if err := container.EnsureMounted(); err != nil { + if err := container.Mount(); err != nil { utils.Errorf("Warning: failed to compute size of container rootfs %s: %s", container.ID, err) return sizeRw, sizeRootfs } @@ -1444,7 +1438,7 @@ func (container *Container) GetSize() (int64, int64) { } func (container *Container) Copy(resource string) (archive.Archive, error) { - if err := container.EnsureMounted(); err != nil { + if err := container.Mount(); err != nil { return nil, err } var filter []string diff --git a/integration/utils_test.go b/integration/utils_test.go index 69686ba69e..64297217de 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -71,7 +71,7 @@ func containerRun(eng *engine.Engine, id string, t utils.Fataler) { func containerFileExists(eng *engine.Engine, id, dir string, t utils.Fataler) bool { c := getContainer(eng, id, t) - if err := c.EnsureMounted(); err != nil { + if err := c.Mount(); err != nil { t.Fatal(err) } defer c.Unmount() diff --git a/runtime.go b/runtime.go index 5576cb2aee..b4567c15c0 100644 --- a/runtime.go +++ b/runtime.go @@ -520,7 +520,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin func (runtime *Runtime) Commit(container *Container, repository, tag, comment, author string, config *Config) (*Image, error) { // FIXME: freeze the container before copying it to avoid data corruption? // FIXME: this shouldn't be in commands. - if err := container.EnsureMounted(); err != nil { + if err := container.Mount(); err != nil { return nil, err } defer container.Unmount() From 886f650d9b2133fb61e7cc5b0bb7795246852026 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Dec 2013 22:22:55 +0100 Subject: [PATCH 163/364] devicemapper: Unmount inactive devices This implements the new Put() operation such that Get()/Put() maintains a refcount for each ID, mounting only on first Get() and unmounting on the last Get(). This means we avoid littering the system with lots of mounts and active devicemapper devices and free resources related to them. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) asdfljk --- graphdriver/devmapper/driver.go | 41 ++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/graphdriver/devmapper/driver.go b/graphdriver/devmapper/driver.go index 0bf5fb24e1..dae712b9b5 100644 --- a/graphdriver/devmapper/driver.go +++ b/graphdriver/devmapper/driver.go @@ -5,8 +5,10 @@ package devmapper import ( "fmt" "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/utils" "io/ioutil" "path" + "sync" ) func init() { @@ -20,7 +22,9 @@ func init() { type Driver struct { *DeviceSet - home string + home string + sync.Mutex // Protects concurrent modification to active + active map[string]int } var Init = func(home string) (graphdriver.Driver, error) { @@ -31,6 +35,7 @@ var Init = func(home string) (graphdriver.Driver, error) { d := &Driver{ DeviceSet: deviceSet, home: home, + active: make(map[string]int), } return d, nil } @@ -82,6 +87,14 @@ func (d *Driver) Create(id, parent string) error { } func (d *Driver) Remove(id string) error { + // Protect the d.active from concurrent access + d.Lock() + defer d.Unlock() + + if d.active[id] != 0 { + utils.Errorf("Warning: removing active id %s\n", id) + } + mp := path.Join(d.home, "mnt", id) if err := d.unmount(id, mp); err != nil { return err @@ -90,14 +103,36 @@ func (d *Driver) Remove(id string) error { } func (d *Driver) Get(id string) (string, error) { + // Protect the d.active from concurrent access + d.Lock() + defer d.Unlock() + + count := d.active[id] + mp := path.Join(d.home, "mnt", id) - if err := d.mount(id, mp); err != nil { - return "", err + if count == 0 { + if err := d.mount(id, mp); err != nil { + return "", err + } } + + d.active[id] = count + 1 + return path.Join(mp, "rootfs"), nil } func (d *Driver) Put(id string) { + // Protect the d.active from concurrent access + d.Lock() + defer d.Unlock() + + if count := d.active[id]; count > 1 { + d.active[id] = count - 1 + } else { + mp := path.Join(d.home, "mnt", id) + d.unmount(id, mp) + delete(d.active, id) + } } func (d *Driver) mount(id, mountPoint string) error { From 5fe26ee426b9d748605c538829f30885462ad932 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 14 Jan 2014 12:23:20 +0100 Subject: [PATCH 164/364] aufs: Unmount inactive devices This implements the new Put() operation such that Get()/Put() maintains a refcount for each ID, mounting only on first Get() and unmounting on the last Get(). This means we avoid littering the system with lots of mounts and free resources related to them. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/aufs/aufs.go | 52 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/graphdriver/aufs/aufs.go b/graphdriver/aufs/aufs.go index 3070200042..2f22d3923c 100644 --- a/graphdriver/aufs/aufs.go +++ b/graphdriver/aufs/aufs.go @@ -31,6 +31,7 @@ import ( "os/exec" "path" "strings" + "sync" ) func init() { @@ -38,7 +39,9 @@ func init() { } type Driver struct { - root string + root string + sync.Mutex // Protects concurrent modification to active + active map[string]int } // New returns a new AUFS driver. @@ -54,12 +57,17 @@ func Init(root string) (graphdriver.Driver, error) { "layers", } + a := &Driver{ + root: root, + active: make(map[string]int), + } + // Create the root aufs driver dir and return // if it already exists // If not populate the dir structure if err := os.MkdirAll(root, 0755); err != nil { if os.IsExist(err) { - return &Driver{root}, nil + return a, nil } return nil, err } @@ -69,7 +77,7 @@ func Init(root string) (graphdriver.Driver, error) { return nil, err } } - return &Driver{root}, nil + return a, nil } // Return a nil error if the kernel supports aufs @@ -167,6 +175,14 @@ func (a *Driver) createDirsFor(id string) error { // Unmount and remove the dir information func (a *Driver) Remove(id string) error { + // Protect the a.active from concurrent access + a.Lock() + defer a.Unlock() + + if a.active[id] != 0 { + utils.Errorf("Warning: removing active id %s\n", id) + } + // Make sure the dir is umounted first if err := a.unmount(id); err != nil { return err @@ -210,19 +226,45 @@ func (a *Driver) Get(id string) (string, error) { ids = []string{} } + // Protect the a.active from concurrent access + a.Lock() + defer a.Unlock() + + count := a.active[id] + // If a dir does not have a parent ( no layers )do not try to mount // just return the diff path to the data out := path.Join(a.rootPath(), "diff", id) if len(ids) > 0 { out = path.Join(a.rootPath(), "mnt", id) - if err := a.mount(id); err != nil { - return "", err + + if count == 0 { + if err := a.mount(id); err != nil { + return "", err + } } } + + a.active[id] = count + 1 + return out, nil } func (a *Driver) Put(id string) { + // Protect the a.active from concurrent access + a.Lock() + defer a.Unlock() + + if count := a.active[id]; count > 1 { + a.active[id] = count - 1 + } else { + ids, _ := getParentIds(a.rootPath(), id) + // We only mounted if there are any parents + if ids != nil && len(ids) > 0 { + a.unmount(id) + } + delete(a.active, id) + } } // Returns an archive of the contents for the id From f918fca3bf77ebe5845593281382f68e5b166ab3 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 21 Jan 2014 12:52:05 +0100 Subject: [PATCH 165/364] archive: Always end directory filenames in tars with / This matches what tar does, and without it the tarsum created by the registry will not match the docker one. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- archive/archive.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/archive/archive.go b/archive/archive.go index 1d21018474..727e9289fa 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -145,6 +145,10 @@ func addTarFile(path, name string, tw *tar.Writer) error { return err } + if fi.IsDir() && !strings.HasSuffix(name, "/") { + name = name + "/" + } + hdr.Name = name stat, ok := fi.Sys().(*syscall.Stat_t) From c138c335a5d867d81231fdca6f418a9cc11ca244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Piotrowski?= Date: Mon, 6 Jan 2014 13:08:55 +0100 Subject: [PATCH 166/364] mkimage-arch: use mktemp to create ROOTFS directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Bartłomiej Piotrowski (github: Barthalion) --- contrib/mkimage-arch.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/mkimage-arch.sh b/contrib/mkimage-arch.sh index db14e8674e..d9e911474e 100755 --- a/contrib/mkimage-arch.sh +++ b/contrib/mkimage-arch.sh @@ -4,19 +4,19 @@ # requires root set -e -PACSTRAP=$(which pacstrap) +PACSTRAP=$(hash pacstrap &>/dev/null) [ "$PACSTRAP" ] || { echo "Could not find pacstrap. Run pacman -S arch-install-scripts" exit 1 } -EXPECT=$(which expect) -[ "$EXPECT" ] || { + +EXPECT=$(hash expect &>/dev/null) +[[ "$EXPECT" ]] || { echo "Could not find expect. Run pacman -S expect" exit 1 } -ROOTFS=~/rootfs-arch-$$-$RANDOM -mkdir $ROOTFS +ROOTFS=$(mktemp -d /tmp/rootfs-archlinux-XXXXXXXXXX) #packages to ignore for space savings PKGIGNORE=linux,jfsutils,lvm2,cryptsetup,groff,man-db,man-pages,mdadm,pciutils,pcmciautils,reiserfsprogs,s-nail,xfsprogs From a4e6e9bd41ee0f80e7e9bce05156ddd7d63554aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Piotrowski?= Date: Mon, 6 Jan 2014 13:10:16 +0100 Subject: [PATCH 167/364] mkimage-arch: remove $DEV instead of renaming it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Bartłomiej Piotrowski (github: Barthalion) --- contrib/mkimage-arch.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/mkimage-arch.sh b/contrib/mkimage-arch.sh index d9e911474e..14fb765587 100755 --- a/contrib/mkimage-arch.sh +++ b/contrib/mkimage-arch.sh @@ -47,7 +47,7 @@ arch-chroot $ROOTFS /bin/sh -c 'echo "Server = http://mirrors.kernel.org/archlin # udev doesn't work in containers, rebuild /dev DEV=${ROOTFS}/dev -mv ${DEV} ${DEV}.old +rm -rf ${DEV} mkdir -p ${DEV} mknod -m 666 ${DEV}/null c 1 3 mknod -m 666 ${DEV}/zero c 1 5 From c6535d272c0d1a07ea51639be66e1959a0fc996f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Piotrowski?= Date: Mon, 6 Jan 2014 13:11:16 +0100 Subject: [PATCH 168/364] mkimage-arch: unify usage of brackets around variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Bartłomiej Piotrowski (github: Barthalion) --- contrib/mkimage-arch.sh | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/contrib/mkimage-arch.sh b/contrib/mkimage-arch.sh index 14fb765587..5f48557f78 100755 --- a/contrib/mkimage-arch.sh +++ b/contrib/mkimage-arch.sh @@ -18,7 +18,7 @@ EXPECT=$(hash expect &>/dev/null) ROOTFS=$(mktemp -d /tmp/rootfs-archlinux-XXXXXXXXXX) -#packages to ignore for space savings +# packages to ignore for space savings PKGIGNORE=linux,jfsutils,lvm2,cryptsetup,groff,man-db,man-pages,mdadm,pciutils,pcmciautils,reiserfsprogs,s-nail,xfsprogs expect < /etc/pacman.d/mirrorlist' # udev doesn't work in containers, rebuild /dev -DEV=${ROOTFS}/dev -rm -rf ${DEV} -mkdir -p ${DEV} -mknod -m 666 ${DEV}/null c 1 3 -mknod -m 666 ${DEV}/zero c 1 5 -mknod -m 666 ${DEV}/random c 1 8 -mknod -m 666 ${DEV}/urandom c 1 9 -mkdir -m 755 ${DEV}/pts -mkdir -m 1777 ${DEV}/shm -mknod -m 666 ${DEV}/tty c 5 0 -mknod -m 600 ${DEV}/console c 5 1 -mknod -m 666 ${DEV}/tty0 c 4 0 -mknod -m 666 ${DEV}/full c 1 7 -mknod -m 600 ${DEV}/initctl p -mknod -m 666 ${DEV}/ptmx c 5 2 +DEV=$ROOTFS/dev +rm -rf $DEV +mkdir -p $DEV +mknod -m 666 $DEV/null c 1 3 +mknod -m 666 $DEV/zero c 1 5 +mknod -m 666 $DEV/random c 1 8 +mknod -m 666 $DEV/urandom c 1 9 +mkdir -m 755 $DEV/pts +mkdir -m 1777 $DEV/shm +mknod -m 666 $DEV/tty c 5 0 +mknod -m 600 $DEV/console c 5 1 +mknod -m 666 $DEV/tty0 c 4 0 +mknod -m 666 $DEV/full c 1 7 +mknod -m 600 $DEV/initctl p +mknod -m 666 $DEV/ptmx c 5 2 tar --numeric-owner -C $ROOTFS -c . | docker import - archlinux docker run -i -t archlinux echo Success. From da4ba80733ff4826ea17d253677a58fdedf4cc00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Piotrowski?= Date: Mon, 6 Jan 2014 13:13:19 +0100 Subject: [PATCH 169/364] mkimage-arch: don't generate ISO-8859-1 locale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Bartłomiej Piotrowski (github: Barthalion) --- contrib/mkimage-arch.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/contrib/mkimage-arch.sh b/contrib/mkimage-arch.sh index 5f48557f78..41070aedb1 100755 --- a/contrib/mkimage-arch.sh +++ b/contrib/mkimage-arch.sh @@ -20,7 +20,7 @@ ROOTFS=$(mktemp -d /tmp/rootfs-archlinux-XXXXXXXXXX) # packages to ignore for space savings PKGIGNORE=linux,jfsutils,lvm2,cryptsetup,groff,man-db,man-pages,mdadm,pciutils,pcmciautils,reiserfsprogs,s-nail,xfsprogs - + expect < $ROOTFS/etc/locale.gen < $ROOTFS/etc/locale.gen arch-chroot $ROOTFS locale-gen arch-chroot $ROOTFS /bin/sh -c 'echo "Server = http://mirrors.kernel.org/archlinux/\$repo/os/\$arch" > /etc/pacman.d/mirrorlist' From 787ff55283067ef3d8da3238e65ca204bc63b3f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Piotrowski?= Date: Mon, 6 Jan 2014 13:36:04 +0100 Subject: [PATCH 170/364] mkimage-arch: use hash to check if command is available MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apart from having more predictable return codes on various operating systems, it additionally caches the path to application. Docker-DCO-1.1-Signed-off-by: Bartłomiej Piotrowski (github: Barthalion) --- contrib/mkimage-arch.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contrib/mkimage-arch.sh b/contrib/mkimage-arch.sh index 41070aedb1..da11750c03 100755 --- a/contrib/mkimage-arch.sh +++ b/contrib/mkimage-arch.sh @@ -4,14 +4,12 @@ # requires root set -e -PACSTRAP=$(hash pacstrap &>/dev/null) -[ "$PACSTRAP" ] || { +hash pacstrap &>/dev/null || { echo "Could not find pacstrap. Run pacman -S arch-install-scripts" exit 1 } -EXPECT=$(hash expect &>/dev/null) -[[ "$EXPECT" ]] || { +hash expect &>/dev/null || { echo "Could not find expect. Run pacman -S expect" exit 1 } From fabf96b8ff5560523d60269d0052b65037394dcc Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 21 Jan 2014 08:22:51 -0500 Subject: [PATCH 171/364] Fixed commandline/index warning in TOC Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/sources/toctree.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/sources/toctree.rst b/docs/sources/toctree.rst index b43a4d827b..d1f98b6a5d 100644 --- a/docs/sources/toctree.rst +++ b/docs/sources/toctree.rst @@ -15,7 +15,6 @@ This documentation has the following resources: use/index examples/index reference/index - commandline/index contributing/index terms/index articles/index From 4137a0ea327ea1775c1b57892fd684da2c738f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Piotrowski?= Date: Mon, 6 Jan 2014 14:25:36 +0100 Subject: [PATCH 172/364] mkimage-arch: provide and use own pacman.conf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Bartłomiej Piotrowski (github: Barthalion) --- contrib/mkimage-arch-pacman.conf | 92 ++++++++++++++++++++++++++++++++ contrib/mkimage-arch.sh | 2 +- 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 contrib/mkimage-arch-pacman.conf diff --git a/contrib/mkimage-arch-pacman.conf b/contrib/mkimage-arch-pacman.conf new file mode 100644 index 0000000000..45fe03dc96 --- /dev/null +++ b/contrib/mkimage-arch-pacman.conf @@ -0,0 +1,92 @@ +# +# /etc/pacman.conf +# +# See the pacman.conf(5) manpage for option and repository directives + +# +# GENERAL OPTIONS +# +[options] +# The following paths are commented out with their default values listed. +# If you wish to use different paths, uncomment and update the paths. +#RootDir = / +#DBPath = /var/lib/pacman/ +#CacheDir = /var/cache/pacman/pkg/ +#LogFile = /var/log/pacman.log +#GPGDir = /etc/pacman.d/gnupg/ +HoldPkg = pacman glibc +#XferCommand = /usr/bin/curl -C - -f %u > %o +#XferCommand = /usr/bin/wget --passive-ftp -c -O %o %u +#CleanMethod = KeepInstalled +#UseDelta = 0.7 +Architecture = auto + +# Pacman won't upgrade packages listed in IgnorePkg and members of IgnoreGroup +#IgnorePkg = +#IgnoreGroup = + +#NoUpgrade = +#NoExtract = + +# Misc options +#UseSyslog +#Color +#TotalDownload +# We cannot check disk space from within a chroot environment +#CheckSpace +#VerbosePkgLists + +# By default, pacman accepts packages signed by keys that its local keyring +# trusts (see pacman-key and its man page), as well as unsigned packages. +SigLevel = Required DatabaseOptional +LocalFileSigLevel = Optional +#RemoteFileSigLevel = Required + +# NOTE: You must run `pacman-key --init` before first using pacman; the local +# keyring can then be populated with the keys of all official Arch Linux +# packagers with `pacman-key --populate archlinux`. + +# +# REPOSITORIES +# - can be defined here or included from another file +# - pacman will search repositories in the order defined here +# - local/custom mirrors can be added here or in separate files +# - repositories listed first will take precedence when packages +# have identical names, regardless of version number +# - URLs will have $repo replaced by the name of the current repo +# - URLs will have $arch replaced by the name of the architecture +# +# Repository entries are of the format: +# [repo-name] +# Server = ServerName +# Include = IncludePath +# +# The header [repo-name] is crucial - it must be present and +# uncommented to enable the repo. +# + +# The testing repositories are disabled by default. To enable, uncomment the +# repo name header and Include lines. You can add preferred servers immediately +# after the header, and they will be used before the default mirrors. + +#[testing] +#Include = /etc/pacman.d/mirrorlist + +[core] +Include = /etc/pacman.d/mirrorlist + +[extra] +Include = /etc/pacman.d/mirrorlist + +#[community-testing] +#Include = /etc/pacman.d/mirrorlist + +[community] +Include = /etc/pacman.d/mirrorlist + +# An example of a custom package repository. See the pacman manpage for +# tips on creating your own repositories. +#[custom] +#SigLevel = Optional TrustAll +#Server = file:///home/custompkgs + diff --git a/contrib/mkimage-arch.sh b/contrib/mkimage-arch.sh index da11750c03..f06d14be4c 100755 --- a/contrib/mkimage-arch.sh +++ b/contrib/mkimage-arch.sh @@ -22,7 +22,7 @@ PKGIGNORE=linux,jfsutils,lvm2,cryptsetup,groff,man-db,man-pages,mdadm,pciutils,p expect < Date: Tue, 21 Jan 2014 09:56:53 -0800 Subject: [PATCH 173/364] Re-add kernel Flavor tests Docker-DCO-1.1-Signed-off-by: Charles Lindsay (github: chazomaticus) --- utils/utils_test.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/utils/utils_test.go b/utils/utils_test.go index 8acc27e15a..b0a5acb170 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -407,14 +407,17 @@ func assertParseRelease(t *testing.T, release string, b *KernelVersionInfo, resu if r := CompareKernelVersion(a, b); r != result { t.Fatalf("Unexpected kernel version comparison result. Found %d, expected %d", r, result) } + if a.Flavor != b.Flavor { + t.Fatalf("Unexpected parsed kernel flavor. Found %s, expected %s", a.Flavor, b.Flavor) + } } func TestParseRelease(t *testing.T) { assertParseRelease(t, "3.8.0", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, 0) - assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54}, 0) - assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54}, 0) - assertParseRelease(t, "3.8.0-19-generic", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, 0) - assertParseRelease(t, "3.12.8tag", &KernelVersionInfo{Kernel: 3, Major: 12, Minor: 8}, 0) + assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54, Flavor: ".longterm-1"}, 0) + assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54, Flavor: ".longterm-1"}, 0) + assertParseRelease(t, "3.8.0-19-generic", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "-19-generic"}, 0) + assertParseRelease(t, "3.12.8tag", &KernelVersionInfo{Kernel: 3, Major: 12, Minor: 8, Flavor: "tag"}, 0) } func TestParsePortMapping(t *testing.T) { From f2f44b91a8ebbcea9c62f0c1b63e5aabaa3110b4 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 20 Jan 2014 16:09:17 -0800 Subject: [PATCH 174/364] move load to a job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- REMOTE_TODO.md | 2 +- api.go | 4 +++- server.go | 40 ++++++++++++++++++++++++++-------------- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/REMOTE_TODO.md b/REMOTE_TODO.md index 367e1259af..8a16f0ae0a 100644 --- a/REMOTE_TODO.md +++ b/REMOTE_TODO.md @@ -24,7 +24,7 @@ ok "/commit": postCommit, 0 TODO "/build": postBuild, 0 yes TODO "/images/create": postImagesCreate, N yes yes (pull) ok "/images/{name:.*}/insert": postImagesInsert, N yes yes -... "/images/load": postImagesLoad, 1 yes (stdin) +ok "/images/load": postImagesLoad, 1 yes (stdin) TODO "/images/{name:.*}/push": postImagesPush, N yes ok "/images/{name:.*}/tag": postImagesTag, 0 ok "/containers/create": postContainersCreate, 0 diff --git a/api.go b/api.go index 61df3e7fe6..1435762cd8 100644 --- a/api.go +++ b/api.go @@ -608,7 +608,9 @@ func getImagesGet(srv *Server, version float64, w http.ResponseWriter, r *http.R } func postImagesLoad(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - return srv.ImageLoad(r.Body) + job := srv.Eng.Job("load") + job.Stdin.Add(r.Body) + return job.Run() } func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/server.go b/server.go index c99b689069..e0ba2cca08 100644 --- a/server.go +++ b/server.go @@ -95,6 +95,7 @@ func jobInitApi(job *engine.Job) engine.Status { "search": srv.ImagesSearch, "changes": srv.ContainerChanges, "top": srv.ContainerTop, + "load": srv.ImageLoad, } { if err := job.Eng.Register(name, handler); err != nil { job.Error(err) @@ -355,10 +356,11 @@ func (srv *Server) exportImage(image *Image, tempdir string) error { // Loads a set of images into the repository. This is the complementary of ImageExport. // The input stream is an uncompressed tar ball containing images and metadata. -func (srv *Server) ImageLoad(in io.Reader) error { +func (srv *Server) ImageLoad(job *engine.Job) engine.Status { tmpImageDir, err := ioutil.TempDir("", "docker-import-") if err != nil { - return err + job.Error(err) + return engine.StatusErr } defer os.RemoveAll(tmpImageDir) @@ -369,33 +371,40 @@ func (srv *Server) ImageLoad(in io.Reader) error { tarFile, err := os.Create(repoTarFile) if err != nil { - return err + job.Error(err) + return engine.StatusErr } - if _, err := io.Copy(tarFile, in); err != nil { - return err + if _, err := io.Copy(tarFile, job.Stdin); err != nil { + job.Error(err) + return engine.StatusErr } tarFile.Close() repoFile, err := os.Open(repoTarFile) if err != nil { - return err + job.Error(err) + return engine.StatusErr } if err := os.Mkdir(repoDir, os.ModeDir); err != nil { - return err + job.Error(err) + return engine.StatusErr } if err := archive.Untar(repoFile, repoDir, nil); err != nil { - return err + job.Error(err) + return engine.StatusErr } dirs, err := ioutil.ReadDir(repoDir) if err != nil { - return err + job.Error(err) + return engine.StatusErr } for _, d := range dirs { if d.IsDir() { if err := srv.recursiveLoad(d.Name(), tmpImageDir); err != nil { - return err + job.Error(err) + return engine.StatusErr } } } @@ -404,21 +413,24 @@ func (srv *Server) ImageLoad(in io.Reader) error { if err == nil { repositories := map[string]Repository{} if err := json.Unmarshal(repositoriesJson, &repositories); err != nil { - return err + job.Error(err) + return engine.StatusErr } for imageName, tagMap := range repositories { for tag, address := range tagMap { if err := srv.runtime.repositories.Set(imageName, tag, address, true); err != nil { - return err + job.Error(err) + return engine.StatusErr } } } } else if !os.IsNotExist(err) { - return err + job.Error(err) + return engine.StatusErr } - return nil + return engine.StatusOK } func (srv *Server) recursiveLoad(address, tmpImageDir string) error { From e3461bc8d694fa4b104a9cdd08e11de26a04d923 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 21 Jan 2014 15:06:23 -0800 Subject: [PATCH 175/364] switch back to the valid json format Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 105 +++++------------- commands.go | 10 +- .../reference/api/docker_remote_api.rst | 8 -- .../reference/api/docker_remote_api_v1.9.rst | 48 ++++---- engine/env.go | 26 +++++ engine/streams.go | 16 +++ integration/api_test.go | 14 +-- integration/utils_test.go | 2 +- server.go | 8 +- 9 files changed, 115 insertions(+), 122 deletions(-) diff --git a/api.go b/api.go index 1435762cd8..1e0e12e8bf 100644 --- a/api.go +++ b/api.go @@ -192,40 +192,35 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http. job.Setenv("filter", r.Form.Get("filter")) job.Setenv("all", r.Form.Get("all")) - if version >= 1.9 { + if version > 1.8 { job.Stdout.Add(w) - } else if outs, err = job.Stdout.AddTable(); err != nil { + } else if outs, err = job.Stdout.AddListTable(); err != nil { return err } - if err = job.Run(); err != nil { + if err := job.Run(); err != nil { return err } - if version < 1.9 { // Send as a valid JSON array - if version < 1.8 { // Convert to legacy format - outsLegacy := engine.NewTable("Created", 0) - for _, out := range outs.Data { - for _, repoTag := range out.GetList("RepoTags") { - parts := strings.Split(repoTag, ":") - outLegacy := &engine.Env{} - outLegacy.Set("Repository", parts[0]) - outLegacy.Set("Tag", parts[1]) - outLegacy.Set("ID", out.Get("ID")) - outLegacy.SetInt64("Created", out.GetInt64("Created")) - outLegacy.SetInt64("Size", out.GetInt64("Size")) - outLegacy.SetInt64("VirtualSize", out.GetInt64("VirtualSize")) - outsLegacy.Add(outLegacy) - } + if version < 1.8 && outs != nil { // Convert to legacy format + outsLegacy := engine.NewTable("Created", 0) + for _, out := range outs.Data { + for _, repoTag := range out.GetList("RepoTags") { + parts := strings.Split(repoTag, ":") + outLegacy := &engine.Env{} + outLegacy.Set("Repository", parts[0]) + outLegacy.Set("Tag", parts[1]) + outLegacy.Set("ID", out.Get("ID")) + outLegacy.SetInt64("Created", out.GetInt64("Created")) + outLegacy.SetInt64("Size", out.GetInt64("Size")) + outLegacy.SetInt64("VirtualSize", out.GetInt64("VirtualSize")) + outsLegacy.Add(outLegacy) } - if _, err = outsLegacy.WriteListTo(w); err != nil { - return err - } - } else if _, err = outs.WriteListTo(w); err != nil { + } + if _, err := outsLegacy.WriteListTo(w); err != nil { return err } } - return nil } @@ -307,25 +302,12 @@ func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *ht return fmt.Errorf("Missing parameter") } - var ( - err error - outs *engine.Table - job = srv.Eng.Job("history", vars["name"]) - ) + var job = srv.Eng.Job("history", vars["name"]) + job.Stdout.Add(w) - if version >= 1.9 { - job.Stdout.Add(w) - } else if outs, err = job.Stdout.AddTable(); err != nil { + if err := job.Run(); err != nil { return err } - if err = job.Run(); err != nil { - return err - } - if version < 1.9 { // Send as a valid JSON array - if _, err = outs.WriteListTo(w); err != nil { - return err - } - } return nil } @@ -333,26 +315,10 @@ func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r if vars == nil { return fmt.Errorf("Missing parameter") } - var ( - err error - outs *engine.Table - job = srv.Eng.Job("changes", vars["name"]) - ) + var job = srv.Eng.Job("changes", vars["name"]) + job.Stdout.Add(w) - if version >= 1.9 { - job.Stdout.Add(w) - } else if outs, err = job.Stdout.AddTable(); err != nil { - return err - } - if err = job.Run(); err != nil { - return err - } - if version < 1.9 { // Send as a valid JSON array - if _, err = outs.WriteListTo(w); err != nil { - return err - } - } - return nil + return job.Run() } func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { @@ -501,25 +467,10 @@ func getImagesSearch(srv *Server, version float64, w http.ResponseWriter, r *htt return err } - var ( - err error - outs *engine.Table - job = srv.Eng.Job("search", r.Form.Get("term")) - ) - if version >= 1.9 { - job.Stdout.Add(w) - } else if outs, err = job.Stdout.AddTable(); err != nil { - return err - } - if err = job.Run(); err != nil { - return err - } - if version < 1.9 { // Send as a valid JSON array - if _, err = outs.WriteListTo(w); err != nil { - return err - } - } - return nil + var job = srv.Eng.Job("search", r.Form.Get("term")) + job.Stdout.Add(w) + + return job.Run() } func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/commands.go b/commands.go index a5d7c9100a..2267774fb5 100644 --- a/commands.go +++ b/commands.go @@ -869,7 +869,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { } outs := engine.NewTable("Created", 0) - if _, err := outs.ReadFrom(stream); err != nil { + if _, err := outs.ReadListFrom(stream); err != nil { return err } @@ -1147,7 +1147,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { } outs := engine.NewTable("Created", 0) - if _, err := outs.ReadFrom(stream); err != nil { + if _, err := outs.ReadListFrom(stream); err != nil { return err } @@ -1219,7 +1219,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { } outs := engine.NewTable("Created", 0) - if _, err := outs.ReadFrom(stream); err != nil { + if _, err := outs.ReadListFrom(stream); err != nil { return err } @@ -1540,7 +1540,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error { } outs := engine.NewTable("", 0) - if _, err := outs.ReadFrom(stream); err != nil { + if _, err := outs.ReadListFrom(stream); err != nil { return err } for _, change := range outs.Data { @@ -1681,7 +1681,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { return err } outs := engine.NewTable("star_count", 0) - if _, err := outs.ReadFrom(stream); err != nil { + if _, err := outs.ReadListFrom(stream); err != nil { return err } w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) diff --git a/docs/sources/reference/api/docker_remote_api.rst b/docs/sources/reference/api/docker_remote_api.rst index b616e10a8f..f7cd7faf4f 100644 --- a/docs/sources/reference/api/docker_remote_api.rst +++ b/docs/sources/reference/api/docker_remote_api.rst @@ -46,14 +46,6 @@ Full Documentation What's new ---------- -.. http:get:: /images/json - - **New!** This endpoint now returns a list of json message, like the events endpoint - -.. http:get:: /images/(name)/history - - **New!** This endpoint now returns a list of json message, like the events endpoint - .. http:post:: /build **New!** This endpoint now takes a serialized ConfigFile which it uses to diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.rst b/docs/sources/reference/api/docker_remote_api_v1.9.rst index 65e501234f..cb406da82b 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.9.rst @@ -317,18 +317,20 @@ Inspect changes on a container's filesystem HTTP/1.1 200 OK Content-Type: application/json - { + [ + { "Path":"/dev", "Kind":0 - } - { + }, + { "Path":"/dev/kmsg", "Kind":1 - } - { + }, + { "Path":"/test", "Kind":1 - } + } + ] :statuscode 200: no error :statuscode 404: no such container @@ -654,7 +656,8 @@ List Images HTTP/1.1 200 OK Content-Type: application/json - { + [ + { "RepoTags": [ "ubuntu:12.04", "ubuntu:precise", @@ -664,8 +667,8 @@ List Images "Created": 1365714795, "Size": 131506275, "VirtualSize": 131506275 - } - { + }, + { "RepoTags": [ "ubuntu:12.10", "ubuntu:quantal" @@ -675,7 +678,8 @@ List Images "Created": 1364102658, "Size": 24653, "VirtualSize": 180116135 - } + } + ] Create an image @@ -821,16 +825,18 @@ Get the history of an image HTTP/1.1 200 OK Content-Type: application/json - { + [ + { "Id":"b750fe79269d", "Created":1364102658, "CreatedBy":"/bin/bash" - } - { + }, + { "Id":"27cf78414709", "Created":1364068391, "CreatedBy":"" - } + } + ] :statuscode 200: no error :statuscode 404: no such image @@ -954,28 +960,30 @@ Search images HTTP/1.1 200 OK Content-Type: application/json - { + [ + { "description": "", "is_official": false, "is_trusted": false, "name": "wma55/u1210sshd", "star_count": 0 - } - { + }, + { "description": "", "is_official": false, "is_trusted": false, "name": "jdswinbank/sshd", "star_count": 0 - } - { + }, + { "description": "", "is_official": false, "is_trusted": false, "name": "vgauthier/sshd", "star_count": 0 - } + } ... + ] :query term: term to search :statuscode 200: no error diff --git a/engine/env.go b/engine/env.go index 0593bfa012..37ba2ddb4c 100644 --- a/engine/env.go +++ b/engine/env.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "sort" "strconv" "strings" @@ -324,6 +325,31 @@ func (t *Table) WriteTo(dst io.Writer) (n int64, err error) { return n, nil } +func (t *Table) ReadListFrom(src io.Reader) (n int64, err error) { + var array []interface{} + + content, err := ioutil.ReadAll(src) + if err != nil { + return -1, err + } + + if err := json.Unmarshal(content, &array); err != nil { + return -1, err + } + + for _, item := range array { + if m, ok := item.(map[string]interface{}); ok { + env := &Env{} + for key, value := range m { + env.SetAuto(key, value) + } + t.Add(env) + } + } + + return int64(len(content)), nil +} + func (t *Table) ReadFrom(src io.Reader) (n int64, err error) { decoder := NewDecoder(src) for { diff --git a/engine/streams.go b/engine/streams.go index 75b3c768b2..703a56256d 100644 --- a/engine/streams.go +++ b/engine/streams.go @@ -211,6 +211,22 @@ func (o *Output) AddEnv() (dst *Env, err error) { return dst, nil } +func (o *Output) AddListTable() (dst *Table, err error) { + src, err := o.AddPipe() + if err != nil { + return nil, err + } + dst = NewTable("", 0) + o.tasks.Add(1) + go func() { + defer o.tasks.Done() + if _, err := dst.ReadListFrom(src); err != nil { + return + } + }() + return dst, nil +} + func (o *Output) AddTable() (dst *Table, err error) { src, err := o.AddPipe() if err != nil { diff --git a/integration/api_test.go b/integration/api_test.go index 30409c907b..8a9d64684f 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -61,7 +61,7 @@ func TestGetInfo(t *testing.T) { srv := mkServerFromEngine(eng, t) job := eng.Job("images") - initialImages, err := job.Stdout.AddTable() + initialImages, err := job.Stdout.AddListTable() if err != nil { t.Fatal(err) } @@ -149,7 +149,7 @@ func TestGetImagesJSON(t *testing.T) { srv := mkServerFromEngine(eng, t) job := eng.Job("images") - initialImages, err := job.Stdout.AddTable() + initialImages, err := job.Stdout.AddListTable() if err != nil { t.Fatal(err) } @@ -170,7 +170,7 @@ func TestGetImagesJSON(t *testing.T) { assertHttpNotError(r, t) images := engine.NewTable("Created", 0) - if _, err := images.ReadFrom(r.Body); err != nil { + if _, err := images.ReadListFrom(r.Body); err != nil { t.Fatal(err) } @@ -205,7 +205,7 @@ func TestGetImagesJSON(t *testing.T) { assertHttpNotError(r2, t) images2 := engine.NewTable("ID", 0) - if _, err := images2.ReadFrom(r2.Body); err != nil { + if _, err := images2.ReadListFrom(r2.Body); err != nil { t.Fatal(err) } @@ -238,7 +238,7 @@ func TestGetImagesJSON(t *testing.T) { assertHttpNotError(r3, t) images3 := engine.NewTable("ID", 0) - if _, err := images3.ReadFrom(r3.Body); err != nil { + if _, err := images3.ReadListFrom(r3.Body); err != nil { t.Fatal(err) } @@ -264,7 +264,7 @@ func TestGetImagesHistory(t *testing.T) { assertHttpNotError(r, t) outs := engine.NewTable("Created", 0) - if _, err := outs.ReadFrom(r.Body); err != nil { + if _, err := outs.ReadListFrom(r.Body); err != nil { t.Fatal(err) } if len(outs.Data) != 1 { @@ -409,7 +409,7 @@ func TestGetContainersChanges(t *testing.T) { } assertHttpNotError(r, t) outs := engine.NewTable("", 0) - if _, err := outs.ReadFrom(r.Body); err != nil { + if _, err := outs.ReadListFrom(r.Body); err != nil { t.Fatal(err) } diff --git a/integration/utils_test.go b/integration/utils_test.go index 63ac3a44b9..a7734fb257 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -335,7 +335,7 @@ func getImages(eng *engine.Engine, t *testing.T, all bool, filter string) *engin job := eng.Job("images") job.SetenvBool("all", all) job.Setenv("filter", filter) - images, err := job.Stdout.AddTable() + images, err := job.Stdout.AddListTable() if err != nil { t.Fatal(err) } diff --git a/server.go b/server.go index e0ba2cca08..331c596411 100644 --- a/server.go +++ b/server.go @@ -493,7 +493,7 @@ func (srv *Server) ImagesSearch(job *engine.Job) engine.Status { outs.Add(out) } outs.ReverseSort() - if _, err := outs.WriteTo(job.Stdout); err != nil { + if _, err := outs.WriteListTo(job.Stdout); err != nil { job.Error(err) return engine.StatusErr } @@ -658,7 +658,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { } outs.ReverseSort() - if _, err := outs.WriteTo(job.Stdout); err != nil { + if _, err := outs.WriteListTo(job.Stdout); err != nil { job.Error(err) return engine.StatusErr } @@ -744,7 +744,7 @@ func (srv *Server) ImageHistory(job *engine.Job) engine.Status { return nil }) outs.ReverseSort() - if _, err := outs.WriteTo(job.Stdout); err != nil { + if _, err := outs.WriteListTo(job.Stdout); err != nil { job.Error(err) return engine.StatusErr } @@ -849,7 +849,7 @@ func (srv *Server) ContainerChanges(job *engine.Job) engine.Status { } outs.Add(out) } - if _, err := outs.WriteTo(job.Stdout); err != nil { + if _, err := outs.WriteListTo(job.Stdout); err != nil { job.Error(err) return engine.StatusErr } From 3e96f46b30340f8468a7bac8c159b610a2309821 Mon Sep 17 00:00:00 2001 From: Maxime Petazzoni Date: Mon, 11 Nov 2013 13:29:56 -0800 Subject: [PATCH 176/364] Automatically create non-existent bind-mounted directories on the host Fixes #1279. Docker-DCO-1.1-Signed-off-by: Maxime Petazzoni (github: mpetazzoni) --- server.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index 331c596411..db26441bc1 100644 --- a/server.go +++ b/server.go @@ -1909,8 +1909,11 @@ func (srv *Server) ContainerStart(job *engine.Job) engine.Status { // ensure the source exists on the host _, err := os.Stat(source) if err != nil && os.IsNotExist(err) { - job.Errorf("Invalid bind mount '%s' : source doesn't exist", bind) - return engine.StatusErr + err = os.MkdirAll(source, 0755) + if err != nil { + job.Errorf("Could not create local directory '%s' for bind mount: %s!", source, err.Error()) + return engine.StatusErr + } } } // Register any links from the host config before starting the container From 47d1413d7a0850edc64c62f1473b4c4a3f417d7c Mon Sep 17 00:00:00 2001 From: Maxime Petazzoni Date: Fri, 6 Dec 2013 10:43:54 -0800 Subject: [PATCH 177/364] Documentation about automatic bind-mount dir creation Docker-DCO-1.1-Signed-off-by: Maxime Petazzoni (github: mpetazzoni) --- docs/sources/reference/commandline/cli.rst | 11 ++++++++++- docs/sources/use/working_with_volumes.rst | 12 ++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index f6318326e2..c00a97d5c4 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1100,7 +1100,16 @@ using the container, but inside the current working directory. .. code-block:: bash - $ sudo docker run -p 127.0.0.1:80:8080 ubuntu bash + $ sudo docker run -v /dont/exist:/foo -w /foo -i -t ubuntu bash + +When the host directory of a bind-mounted volume doesn't exist, Docker +will automatically create this directory on the host for you. In the +example above, Docker will create the ``/dont/exist`` folder before +starting your container. + +.. code-block:: bash + + $ sudo docker run -p 127.0.0.1:80:8080 ubuntu bash This binds port ``8080`` of the container to port ``80`` on ``127.0.0.1`` of the host machine. :ref:`port_redirection` explains in detail how to manipulate ports diff --git a/docs/sources/use/working_with_volumes.rst b/docs/sources/use/working_with_volumes.rst index d908157473..82d5806954 100644 --- a/docs/sources/use/working_with_volumes.rst +++ b/docs/sources/use/working_with_volumes.rst @@ -89,11 +89,15 @@ Mount a Host Directory as a Container Volume: :: -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. - If "host-dir" is missing, then docker creates a new volume. -This is not available from a Dockerfile as it makes the built image less portable -or shareable. [host-dir] volumes are 100% host dependent and will break on any -other machine. +If ``host-dir`` is missing from the command, then docker creates a new volume. +If ``host-dir`` is present but points to a non-existent directory on the host, +Docker will automatically create this directory and use it as the source of the +bind-mount. + +Note that this is not available from a Dockerfile due the portability and +sharing purpose of it. The ``host-dir`` volumes are entirely host-dependent and +might not work on any other machine. For example:: From 80f128a6ea2b71e2ed6838c6a913086859ed50ff Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 21 Jan 2014 16:18:57 -0800 Subject: [PATCH 178/364] Fix die command when monitor returns Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/container.go b/container.go index 4f7f03d454..a3fb5d6b39 100644 --- a/container.go +++ b/container.go @@ -1141,9 +1141,7 @@ func (container *Container) monitor(callback execdriver.StartCallback) error { } if err != nil { - if container.runtime != nil && container.runtime.srv != nil { - container.runtime.srv.LogEvent("die", container.ID, container.runtime.repositories.ImageName(container.Image)) - } + utils.Errorf("Error running container: %s", err) } // Cleanup @@ -1156,6 +1154,10 @@ func (container *Container) monitor(callback execdriver.StartCallback) error { container.State.SetStopped(exitCode) + if container.runtime != nil && container.runtime.srv != nil { + container.runtime.srv.LogEvent("die", container.ID, container.runtime.repositories.ImageName(container.Image)) + } + close(container.waitLock) // FIXME: there is a race condition here which causes this to fail during the unit tests. From 0376a69cb1cc839f7e1a147623b399147a5976c9 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 21 Jan 2014 16:38:17 -0800 Subject: [PATCH 179/364] Fix wait on stopped container (after docker restart) + add Unit test for that case Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- integration/server_test.go | 61 ++++++++++++++++++++++++++++++++++++++ runtime.go | 6 ++++ 2 files changed, 67 insertions(+) diff --git a/integration/server_test.go b/integration/server_test.go index 6c26f56174..1347d54b3a 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -2,8 +2,10 @@ package docker import ( "github.com/dotcloud/docker" + "github.com/dotcloud/docker/engine" "strings" "testing" + "time" ) func TestImageTagImageDelete(t *testing.T) { @@ -154,6 +156,65 @@ func TestCommit(t *testing.T) { } } +func TestRestartKillWait(t *testing.T) { + eng := NewTestEngine(t) + srv := mkServerFromEngine(eng, t) + runtime := mkRuntimeFromEngine(eng, t) + defer runtime.Nuke() + + config, hostConfig, _, err := docker.ParseRun([]string{"-i", unitTestImageID, "/bin/cat"}, nil) + if err != nil { + t.Fatal(err) + } + + 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("start", id) + if err := job.ImportEnv(hostConfig); err != nil { + t.Fatal(err) + } + if err := job.Run(); err != nil { + t.Fatal(err) + } + job = eng.Job("kill", id) + if err := job.Run(); err != nil { + t.Fatal(err) + } + + eng, err = engine.New(eng.Root()) + if err != nil { + t.Fatal(err) + } + + job = eng.Job("initapi") + job.Setenv("Root", eng.Root()) + job.SetenvBool("AutoRestart", false) + // TestGetEnabledCors and TestOptionsRoute require EnableCors=true + job.SetenvBool("EnableCors", true) + if err := job.Run(); err != nil { + t.Fatal(err) + } + + srv = mkServerFromEngine(eng, t) + c := srv.Containers(true, false, -1, "", "") + if len(c) != 1 { + t.Errorf("Expected 1 container, %v found", len(c)) + } + + setTimeout(t, "Waiting on stopped container timedout", 5*time.Second, func() { + job = srv.Eng.Job("wait", c[0].ID) + var statusStr string + job.Stdout.AddString(&statusStr) + if err := job.Run(); err != nil { + t.Fatal(err) + } + }) +} + func TestCreateStartRestartStopStartKillRm(t *testing.T) { eng := NewTestEngine(t) srv := mkServerFromEngine(eng, t) diff --git a/runtime.go b/runtime.go index 52f03f84be..d95fc11b45 100644 --- a/runtime.go +++ b/runtime.go @@ -183,6 +183,12 @@ func (runtime *Runtime) Register(container *Container) error { container.waitLock = make(chan struct{}) go container.monitor(nil) } + } else { + // When the container is not running, we still initialize the waitLock + // chan and close it. Receiving on nil chan blocks whereas receiving on a + // closed chan does not. In this case we do not want to block. + container.waitLock = make(chan struct{}) + close(container.waitLock) } return nil } From b1953baba28362c928d962f0729325d3293c4753 Mon Sep 17 00:00:00 2001 From: Kim BKC Carlbacker Date: Wed, 22 Jan 2014 02:21:56 +0100 Subject: [PATCH 180/364] This should make all bash-scripts run on pretty much any posix-system (with bash installed ofc...) Docker-DCO-1.1-Signed-off-by: Kim BKC Carlbacker (github: bkcsoft) --- contrib/mkimage-arch.sh | 2 +- contrib/mkimage-busybox.sh | 2 +- contrib/mkimage-debootstrap.sh | 2 +- contrib/mkimage-rinse.sh | 2 +- contrib/mkimage-unittest.sh | 2 +- hack/infrastructure/docker-ci/buildbot/setup.sh | 2 +- .../infrastructure/docker-ci/docker-coverage/coverage-docker.sh | 2 +- hack/infrastructure/docker-ci/docker-test/test_docker.sh | 2 +- hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh | 2 +- .../docker-ci/registry-coverage/registry_coverage.sh | 2 +- hack/make.sh | 2 +- hack/release.sh | 2 +- hack/stats.sh | 2 +- hack/vendor.sh | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/contrib/mkimage-arch.sh b/contrib/mkimage-arch.sh index db14e8674e..55c6fe8e25 100755 --- a/contrib/mkimage-arch.sh +++ b/contrib/mkimage-arch.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Generate a minimal filesystem for archlinux and load it into the local # docker as "archlinux" # requires root diff --git a/contrib/mkimage-busybox.sh b/contrib/mkimage-busybox.sh index 7eed0c800c..c1bb88c350 100755 --- a/contrib/mkimage-busybox.sh +++ b/contrib/mkimage-busybox.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Generate a very minimal filesystem based on busybox-static, # and load it into the local docker under the name "busybox". diff --git a/contrib/mkimage-debootstrap.sh b/contrib/mkimage-debootstrap.sh index 3f268b52da..8564aaee2f 100755 --- a/contrib/mkimage-debootstrap.sh +++ b/contrib/mkimage-debootstrap.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e variant='minbase' diff --git a/contrib/mkimage-rinse.sh b/contrib/mkimage-rinse.sh index ff8f173f98..de9265d48c 100755 --- a/contrib/mkimage-rinse.sh +++ b/contrib/mkimage-rinse.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e repo="$1" diff --git a/contrib/mkimage-unittest.sh b/contrib/mkimage-unittest.sh index af6488e9b7..a33f238845 100755 --- a/contrib/mkimage-unittest.sh +++ b/contrib/mkimage-unittest.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Generate a very minimal filesystem based on busybox-static, # and load it into the local docker under the name "docker-ut". diff --git a/hack/infrastructure/docker-ci/buildbot/setup.sh b/hack/infrastructure/docker-ci/buildbot/setup.sh index c7e89c44b2..c5d9cb988e 100755 --- a/hack/infrastructure/docker-ci/buildbot/setup.sh +++ b/hack/infrastructure/docker-ci/buildbot/setup.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Setup of buildbot configuration. Package installation is being done by # Vagrantfile diff --git a/hack/infrastructure/docker-ci/docker-coverage/coverage-docker.sh b/hack/infrastructure/docker-ci/docker-coverage/coverage-docker.sh index f03243cf8f..c29ede5b81 100755 --- a/hack/infrastructure/docker-ci/docker-coverage/coverage-docker.sh +++ b/hack/infrastructure/docker-ci/docker-coverage/coverage-docker.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -x # Generate a random string of $1 characters diff --git a/hack/infrastructure/docker-ci/docker-test/test_docker.sh b/hack/infrastructure/docker-ci/docker-test/test_docker.sh index cf8fdb90bb..14816706ed 100755 --- a/hack/infrastructure/docker-ci/docker-test/test_docker.sh +++ b/hack/infrastructure/docker-ci/docker-test/test_docker.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -x COMMIT=${1-HEAD} diff --git a/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh b/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh index 80caaec25e..d5e58da7e1 100644 --- a/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh +++ b/hack/infrastructure/docker-ci/nightlyrelease/dockerbuild.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Variables AWS_ACCESS_KEY, AWS_SECRET_KEY and PG_PASSPHRASE are decoded # from /root/release_credentials.json diff --git a/hack/infrastructure/docker-ci/registry-coverage/registry_coverage.sh b/hack/infrastructure/docker-ci/registry-coverage/registry_coverage.sh index e16cea8e3c..c67b17eba0 100755 --- a/hack/infrastructure/docker-ci/registry-coverage/registry_coverage.sh +++ b/hack/infrastructure/docker-ci/registry-coverage/registry_coverage.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -x diff --git a/hack/make.sh b/hack/make.sh index fe0f9c175f..6029c9ec10 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e # This script builds various binary artifacts from a checkout of the docker diff --git a/hack/release.sh b/hack/release.sh index 729f1b5eff..9a38d2b6e9 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e # This script looks for bundles built by make.sh, and releases them on a diff --git a/hack/stats.sh b/hack/stats.sh index 2053e583a2..985a77f22d 100755 --- a/hack/stats.sh +++ b/hack/stats.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash ## Run this script from the root of the docker repository ## to query project stats useful to the maintainers. diff --git a/hack/vendor.sh b/hack/vendor.sh index 3326d5b004..d3e7ea9f43 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e cd "$(dirname "$BASH_SOURCE")/.." From fd38de2818d959a9f5591537515e923a3ac56632 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 21 Jan 2014 18:05:03 -0800 Subject: [PATCH 181/364] Fix issue with exit code in non-tty mode. Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- commands.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index a5d7c9100a..3724a0aaed 100644 --- a/commands.go +++ b/commands.go @@ -2168,9 +2168,18 @@ func (cli *DockerCli) CmdRun(args ...string) error { return err } } else { - // No Autoremove: Simply retrieve the exit code - if _, status, err = getExitCode(cli, runResult.ID); err != nil { - return err + if !config.Tty { + // In non-tty mode, we can't dettach, so we know we need to wait. + if status, err = waitForExit(cli, runResult.ID); err != nil { + return err + } + } else { + // In TTY mode, there is a race. If the process dies too slowly, the state can be update after the getExitCode call + // and result in a wrong exit code. + // No Autoremove: Simply retrieve the exit code + if _, status, err = getExitCode(cli, runResult.ID); err != nil { + return err + } } } if status != 0 { From 1f75a0bf435fe9a0f118b027e0387ba41e201c66 Mon Sep 17 00:00:00 2001 From: Paul Lietar Date: Wed, 22 Jan 2014 04:42:36 +0000 Subject: [PATCH 182/364] Add a --signal option to the kill command to specify a signal. Docker-DCO-1.1-Signed-off-by: Paul Lietar (github: plietar) --- commands.go | 8 ++- docs/sources/reference/commandline/cli.rst | 8 ++- integration/commands_test.go | 82 ++++++++++++++++++++-- server.go | 53 +++++++++++--- 4 files changed, 132 insertions(+), 19 deletions(-) diff --git a/commands.go b/commands.go index 4201df004f..84466c204a 100644 --- a/commands.go +++ b/commands.go @@ -942,7 +942,9 @@ func (cli *DockerCli) CmdRm(args ...string) error { // 'docker kill NAME' kills a running container func (cli *DockerCli) CmdKill(args ...string) error { - cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL)") + cmd := cli.Subcmd("kill", "[OPTIONS] CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL, or specified signal)") + signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container") + if err := cmd.Parse(args); err != nil { return nil } @@ -952,8 +954,8 @@ func (cli *DockerCli) CmdKill(args ...string) error { } var encounteredError error - for _, name := range args { - if _, _, err := readBody(cli.call("POST", "/containers/"+name+"/kill", nil, false)); err != nil { + for _, name := range cmd.Args() { + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, false)); err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to kill one or more containers") } else { diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index c00a97d5c4..1d8fa32a2f 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -754,11 +754,13 @@ we ask for the ``HostPort`` field to get the public address. :: - Usage: docker kill CONTAINER [CONTAINER...] + Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...] - Kill a running container (Send SIGKILL) + Kill a running container (send SIGKILL, or specified signal) -The main process inside the container will be sent SIGKILL. + -s, --signal="KILL": Signal to send to the container + +The main process inside the container will be sent SIGKILL, or any signal specified with option ``--signal``. Known Issues (kill) ~~~~~~~~~~~~~~~~~~~ diff --git a/integration/commands_test.go b/integration/commands_test.go index 9a2168d966..48819670e7 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -12,7 +12,9 @@ import ( "os" "path" "regexp" + "strconv" "strings" + "syscall" "testing" "time" ) @@ -90,18 +92,25 @@ func setTimeout(t *testing.T, msg string, d time.Duration, f func()) { } } +func expectPipe(expected string, r io.Reader) error { + o, err := bufio.NewReader(r).ReadString('\n') + if err != nil { + return err + } + if strings.Trim(o, " \r\n") != expected { + return fmt.Errorf("Unexpected output. Expected [%s], received [%s]", expected, o) + } + return nil +} + func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error { for i := 0; i < count; i++ { if _, err := w.Write([]byte(input)); err != nil { return err } - o, err := bufio.NewReader(r).ReadString('\n') - if err != nil { + if err := expectPipe(output, r); err != nil { return err } - if strings.Trim(o, " \r\n") != output { - return fmt.Errorf("Unexpected output. Expected [%s], received [%s]", output, o) - } } return nil } @@ -1031,3 +1040,66 @@ func TestContainerOrphaning(t *testing.T) { } } + +func TestCmdKill(t *testing.T) { + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + + cli := docker.NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + cli2 := docker.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalEngine, t) + + ch := make(chan struct{}) + go func() { + defer close(ch) + cli.CmdRun("-i", "-t", unitTestImageID, "sh", "-c", "trap 'echo SIGUSR1' USR1; trap 'echo SIGUSR2' USR2; echo Ready; while true; do read; done") + }() + + container := waitContainerStart(t, 10*time.Second) + + setTimeout(t, "Read Ready timed out", 3*time.Second, func() { + if err := expectPipe("Ready", stdout); err != nil { + t.Fatal(err) + } + }) + + setTimeout(t, "SIGUSR1 timed out", 2*time.Second, func() { + for i := 0; i < 10; i++ { + if err := cli2.CmdKill("-s", strconv.Itoa(int(syscall.SIGUSR1)), container.ID); err != nil { + t.Fatal(err) + } + if err := expectPipe("SIGUSR1", stdout); err != nil { + t.Fatal(err) + } + } + }) + + setTimeout(t, "SIGUSR2 timed out", 2*time.Second, func() { + for i := 0; i < 10; i++ { + if err := cli2.CmdKill("--signal=USR2", container.ID); err != nil { + t.Fatal(err) + } + if err := expectPipe("SIGUSR2", stdout); err != nil { + t.Fatal(err) + } + } + }) + + time.Sleep(500 * time.Millisecond) + if !container.State.IsRunning() { + t.Fatal("The container should be still running") + } + + setTimeout(t, "Waiting for container timedout", 5*time.Second, func() { + if err := cli2.CmdKill(container.ID); err != nil { + t.Fatal(err) + } + + <-ch + if err := cli2.CmdWait(container.ID); err != nil { + t.Fatal(err) + } + }) + + closeWrap(stdin, stdinPipe, stdout, stdoutPipe) +} diff --git a/server.go b/server.go index 9b86311ccd..60fefc524f 100644 --- a/server.go +++ b/server.go @@ -161,6 +161,40 @@ func (v *simpleVersionInfo) Version() string { // for the container to exit. // If a signal is given, then just send it to the container and return. func (srv *Server) ContainerKill(job *engine.Job) engine.Status { + signalMap := map[string]syscall.Signal{ + "HUP": syscall.SIGHUP, + "INT": syscall.SIGINT, + "QUIT": syscall.SIGQUIT, + "ILL": syscall.SIGILL, + "TRAP": syscall.SIGTRAP, + "ABRT": syscall.SIGABRT, + "BUS": syscall.SIGBUS, + "FPE": syscall.SIGFPE, + "KILL": syscall.SIGKILL, + "USR1": syscall.SIGUSR1, + "SEGV": syscall.SIGSEGV, + "USR2": syscall.SIGUSR2, + "PIPE": syscall.SIGPIPE, + "ALRM": syscall.SIGALRM, + "TERM": syscall.SIGTERM, + //"STKFLT": syscall.SIGSTKFLT, + "CHLD": syscall.SIGCHLD, + "CONT": syscall.SIGCONT, + "STOP": syscall.SIGSTOP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "URG": syscall.SIGURG, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, + "VTALRM": syscall.SIGVTALRM, + "PROF": syscall.SIGPROF, + "WINCH": syscall.SIGWINCH, + "IO": syscall.SIGIO, + //"PWR": syscall.SIGPWR, + "SYS": syscall.SIGSYS, + } + if n := len(job.Args); n < 1 || n > 2 { job.Errorf("Usage: %s CONTAINER [SIGNAL]", job.Name) return engine.StatusErr @@ -168,17 +202,20 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { name := job.Args[0] var sig uint64 if len(job.Args) == 2 && job.Args[1] != "" { - var err error - // The largest legal signal is 31, so let's parse on 5 bits - sig, err = strconv.ParseUint(job.Args[1], 10, 5) - if err != nil { - job.Errorf("Invalid signal: %s", job.Args[1]) - return engine.StatusErr + sig = uint64(signalMap[job.Args[1]]) + if sig == 0 { + var err error + // The largest legal signal is 31, so let's parse on 5 bits + sig, err = strconv.ParseUint(job.Args[1], 10, 5) + if err != nil { + job.Errorf("Invalid signal: %s", job.Args[1]) + return engine.StatusErr + } } } if container := srv.runtime.Get(name); container != nil { - // If no signal is passed, perform regular Kill (SIGKILL + wait()) - if sig == 0 { + // If no signal is passed, or SIGKILL, perform regular Kill (SIGKILL + wait()) + if sig == 0 || syscall.Signal(sig) == syscall.SIGKILL { if err := container.Kill(); err != nil { job.Errorf("Cannot kill container %s: %s", name, err) return engine.StatusErr From f3cb808e9d7d870dc71510bdeecec15745bb44b5 Mon Sep 17 00:00:00 2001 From: John Gardiner Myers Date: Tue, 21 Jan 2014 22:08:11 -0800 Subject: [PATCH 183/364] Grammar and punctuation fixes Docker-DCO-1.1-Signed-off-by: John Gardiner Myers (github: johngmyers) --- docs/sources/use/networking.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/use/networking.rst b/docs/sources/use/networking.rst index 4e75fbc20d..3b1606e2cd 100644 --- a/docs/sources/use/networking.rst +++ b/docs/sources/use/networking.rst @@ -31,11 +31,11 @@ itself for this purpose. Thus, when the Docker daemon starts it : At runtime, a :ref:`specific kind of virtual -interface` is given to each containers which is then -bonded to the ``docker0`` bridge. Each containers also receives a +interface` is given to each container which is then +bonded to the ``docker0`` bridge. Each container also receives a dedicated IP address from the same range as ``docker0``. The ``docker0`` IP address is then used as the default gateway for the -containers. +container. .. code-block:: bash @@ -135,7 +135,7 @@ What's about the vethXXXX device? Well. Things get complicated here. The ``vethXXXX`` interface is the host side of a point-to-point link -between the host and the corresponding container, the other side of +between the host and the corresponding container; the other side of the link being materialized by the container's ``eth0`` interface. This pair (host ``vethXXX`` and container ``eth0``) are connected like a tube. Everything that comes in one side will come out From 1c06a9196451a595c71a47ba9fb4e9f4bc270746 Mon Sep 17 00:00:00 2001 From: John Gardiner Myers Date: Tue, 21 Jan 2014 22:24:04 -0800 Subject: [PATCH 184/364] Wording improvements Docker-DCO-1.1-Signed-off-by: John Gardiner Myers (github: johngmyers) --- docs/sources/use/networking.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/sources/use/networking.rst b/docs/sources/use/networking.rst index 3b1606e2cd..431158cc39 100644 --- a/docs/sources/use/networking.rst +++ b/docs/sources/use/networking.rst @@ -8,7 +8,7 @@ Configure Networking Docker uses Linux bridge capabilities to provide network connectivity to containers. The ``docker0`` bridge interface is managed by Docker -itself for this purpose. Thus, when the Docker daemon starts it : +for this purpose. When the Docker daemon starts it : - creates the ``docker0`` bridge if not present - searches for an IP address range which doesn't overlap with an existing route @@ -34,7 +34,7 @@ At runtime, a :ref:`specific kind of virtual interface` is given to each container which is then bonded to the ``docker0`` bridge. Each container also receives a dedicated IP address from the same range as ``docker0``. The -``docker0`` IP address is then used as the default gateway for the +``docker0`` IP address is used as the default gateway for the container. .. code-block:: bash @@ -55,8 +55,8 @@ which is dedicated to the 52f811c5d3d6 container. How to use a specific IP address range --------------------------------------- -Docker will try hard to find an IP range which is not used by the -host. Even if it works for most cases, it's not bullet-proof and +Docker will try hard to find an IP range that is not used by the +host. Even though it works for most cases, it's not bullet-proof and sometimes you need to have more control over the IP addressing scheme. For this purpose, Docker allows you to manage the ``docker0`` bridge @@ -118,25 +118,25 @@ In this scenario: Container intercommunication ------------------------------- -Containers can communicate with each other according to the ``icc`` -parameter value of the Docker daemon. +The value of the Docker daemon's ``icc`` parameter determines whether +containers can communicate with each other over the bridge network. - The default, ``-icc=true`` allows containers to communicate with each other. - ``-icc=false`` means containers are isolated from each other. -Under the hood, ``iptables`` is used by Docker to either accept or +Docker uses ``iptables`` under the hood to either accept or drop communication between containers. .. _vethxxxx-device: -What's about the vethXXXX device? +What is the vethXXXX device? ----------------------------------- Well. Things get complicated here. The ``vethXXXX`` interface is the host side of a point-to-point link between the host and the corresponding container; the other side of -the link being materialized by the container's ``eth0`` +the link is the container's ``eth0`` interface. This pair (host ``vethXXX`` and container ``eth0``) are connected like a tube. Everything that comes in one side will come out the other side. From 0f5ad430c4a37f6efe7f27428ba54cf719936b68 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 22 Jan 2014 14:12:01 +1000 Subject: [PATCH 185/364] change the documentation only - It looks like we have a number of cmdline params to completely update from the code version Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/reference/commandline/cli.rst | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index c00a97d5c4..fa8bd505f2 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1015,7 +1015,7 @@ image is removed. -t, --tty=false: Allocate a pseudo-tty -u, --user="": Username or UID --dns=[]: Set custom dns servers for the container - -v, --volume=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. If "container-dir" is missing, then docker creates a new volume. + -v, --volume=[]: Create a bind mount to a directory or file with: [host-path]:[container-path]:[rw|ro]. If a directory "container-path" is missing, then docker creates a new volume. --volumes-from="": Mount all volumes from the given container(s) --entrypoint="": Overwrite the default entrypoint set by the image -w, --workdir="": Working directory inside the container @@ -1100,13 +1100,21 @@ using the container, but inside the current working directory. .. code-block:: bash - $ sudo docker run -v /dont/exist:/foo -w /foo -i -t ubuntu bash + $ sudo docker run -v /doesnt/exist:/foo -w /foo -i -t ubuntu bash When the host directory of a bind-mounted volume doesn't exist, Docker will automatically create this directory on the host for you. In the -example above, Docker will create the ``/dont/exist`` folder before +example above, Docker will create the ``/doesnt/exist`` folder before starting your container. +.. code-block:: bash + + $ sudo docker run -t -i -v /var/run/docker.sock:/var/run/docker.sock -v ./static-docker:/usr/bin/docker busybox sh + +By bind-mounting the docker unix socket and statically linked docker binary +(such as that provided by https://get.docker.io), you give the container +the full access to create and manipulate the host's docker daemon. + .. code-block:: bash $ sudo docker run -p 127.0.0.1:80:8080 ubuntu bash From 7c55cbd4c9da88ffb4b1a325ba2e5435e337a3af Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 10 Jan 2014 12:56:05 +1000 Subject: [PATCH 186/364] move the contributing hook into hack, and use curl in the same way as the gofmt above and remove the fmt-check one we don't document tianon tells me they're called GitHub, not Github :) Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- CONTRIBUTING.md | 18 ++++------- contrib/prepare-commit-msg.hook | 7 +++++ docs/sources/articles/baseimages.rst | 2 +- docs/sources/faq.rst | 2 +- hack/fmt-check.hook | 46 ---------------------------- 5 files changed, 15 insertions(+), 60 deletions(-) create mode 100644 contrib/prepare-commit-msg.hook delete mode 100644 hack/fmt-check.hook diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5a5393ba27..f4acb3e716 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ feels wrong or incomplete. ## Reporting Issues When reporting [issues](https://github.com/dotcloud/docker/issues) -on Github please include your host OS ( Ubuntu 12.04, Fedora 19, etc... ) +on GitHub please include your host OS ( Ubuntu 12.04, Fedora 19, etc... ) and the output of `docker version` along with the output of `docker info` if possible. This information will help us review and fix your issue faster. @@ -45,7 +45,7 @@ else is working on the same thing. ### Create issues... -Any significant improvement should be documented as [a github +Any significant improvement should be documented as [a GitHub issue](https://github.com/dotcloud/docker/issues) before anybody starts working on it. @@ -146,19 +146,13 @@ then you just add a line to every git commit message: using your real name (sorry, no pseudonyms or anonymous contributions.) One way to automate this, is customise your get ``commit.template`` by adding -the following to your ``.git/hooks/prepare-commit-msg`` script (needs -``chmod 755 .git/hooks/prepare-commit-msg`` ) in the docker checkout: +a ``prepare-commit-msg`` hook to your docker checkout: ``` - #!/bin/sh - # Auto sign all commits to allow them to be used by the Docker project. - # see https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md#sign-your-work - # - GH_USER=$(git config --get github.user) - SOB=$(git var GIT_AUTHOR_IDENT | sed -n "s/^\(.*>\).*$/Docker-DCO-1.1-Signed-off-by: \1 \(github: $GH_USER\)/p") - grep -qs "^$SOB" "$1" || echo "\n$SOB" >> "$1" +curl -o .git/hooks/prepare-commit-msg https://raw.github.com/dotcloud/docker/master/contrib/prepare-commit-msg.hook && chmod +x .git/hooks/prepare-commit-msg +`` -``` +* Note: the above script expects to find your GitHub user name in ``git config --get github.user`` If you have any questions, please refer to the FAQ in the [docs](http://docs.docker.io) diff --git a/contrib/prepare-commit-msg.hook b/contrib/prepare-commit-msg.hook new file mode 100644 index 0000000000..595f7a783b --- /dev/null +++ b/contrib/prepare-commit-msg.hook @@ -0,0 +1,7 @@ +#!/bin/sh +# Auto sign all commits to allow them to be used by the Docker project. +# see https://github.com/dotcloud/docker/blob/master/CONTRIBUTING.md#sign-your-work +# +GH_USER=$(git config --get github.user) +SOB=$(git var GIT_AUTHOR_IDENT | sed -n "s/^\(.*>\).*$/Docker-DCO-1.1-Signed-off-by: \1 \(github: $GH_USER\)/p") +grep -qs "^$SOB" "$1" || echo "\n$SOB" >> "$1" diff --git a/docs/sources/articles/baseimages.rst b/docs/sources/articles/baseimages.rst index 51a51e2f93..68251bedd3 100644 --- a/docs/sources/articles/baseimages.rst +++ b/docs/sources/articles/baseimages.rst @@ -34,7 +34,7 @@ It can be as simple as this to create an Ubuntu base image:: DISTRIB_DESCRIPTION="Ubuntu 13.04" There are more example scripts for creating base images in the -Docker Github Repo: +Docker GitHub Repo: * `BusyBox `_ * `CentOS / Scientific Linux CERN (SLC) diff --git a/docs/sources/faq.rst b/docs/sources/faq.rst index 5c5a74afef..8de1d45c48 100644 --- a/docs/sources/faq.rst +++ b/docs/sources/faq.rst @@ -196,7 +196,7 @@ Where can I find more answers? * `Docker user mailinglist`_ * `Docker developer mailinglist`_ * `IRC, docker on freenode`_ - * `Github`_ + * `GitHub`_ * `Ask questions on Stackoverflow`_ * `Join the conversation on Twitter`_ diff --git a/hack/fmt-check.hook b/hack/fmt-check.hook deleted file mode 100644 index cd18a18bcb..0000000000 --- a/hack/fmt-check.hook +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/sh - -# This pre-commit hook will abort if a committed file doesn't pass gofmt. -# By Even Shaw -# http://github.com/edsrzf/gofmt-git-hook - -test_fmt() { - hash gofmt 2>&- || { echo >&2 "gofmt not in PATH."; exit 1; } - IFS=' -' - for file in `git diff --cached --name-only --diff-filter=ACM | grep '\.go$'` - do - output=`git cat-file -p :$file | gofmt -l 2>&1` - if test $? -ne 0 - then - output=`echo "$output" | sed "s,,$file,"` - syntaxerrors="${list}${output}\n" - elif test -n "$output" - then - list="${list}${file}\n" - fi - done - exitcode=0 - if test -n "$syntaxerrors" - then - echo >&2 "gofmt found syntax errors:" - printf "$syntaxerrors" - exitcode=1 - fi - if test -n "$list" - then - echo >&2 "gofmt needs to format these files (run gofmt -w and git add):" - printf "$list" - exitcode=1 - fi - exit $exitcode -} - -case "$1" in - --about ) - echo "Check Go code formatting" - ;; - * ) - test_fmt - ;; -esac From dea3c2655dc02c03cf7dc03d058068cf9f0d0970 Mon Sep 17 00:00:00 2001 From: Charles Lindsay Date: Wed, 22 Jan 2014 09:32:50 -0800 Subject: [PATCH 187/364] Add comment about ignoring Sscanf error Docker-DCO-1.1-Signed-off-by: Charles Lindsay (github: chazomaticus) --- utils/utils.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils/utils.go b/utils/utils.go index ae39bcac36..2a11397212 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -614,6 +614,8 @@ func ParseRelease(release string) (*KernelVersionInfo, error) { flavor string ) + // Ignore error from Sscanf to allow an empty flavor. Instead, just + // make sure we got all the version numbers. parsed, _ = fmt.Sscanf(release, "%d.%d.%d%s", &kernel, &major, &minor, &flavor) if parsed < 3 { return nil, errors.New("Can't parse kernel version " + release) From e51af36a85126aca6bf6da5291eaf960fd82aa56 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 21 Nov 2013 21:45:48 +0100 Subject: [PATCH 188/364] Add experimenta btrfs driver This is an experimental btrfs driver. To use it you must have /var/lib/docker mounted on a btrfs filesystem and explicitly specify DOCKER_DRIVER=btrfs in the docker daemon environment. It works by using subvolumes for the docker image/container layers. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/btrfs/btrfs.go | 215 +++++++++++++++++++++++++ graphdriver/btrfs/dummy_unsupported.go | 3 + graphdriver/driver.go | 2 + runtime.go | 1 + 4 files changed, 221 insertions(+) create mode 100644 graphdriver/btrfs/btrfs.go create mode 100644 graphdriver/btrfs/dummy_unsupported.go diff --git a/graphdriver/btrfs/btrfs.go b/graphdriver/btrfs/btrfs.go new file mode 100644 index 0000000000..52ed66b174 --- /dev/null +++ b/graphdriver/btrfs/btrfs.go @@ -0,0 +1,215 @@ +// +build linux + +package btrfs + +/* +#include +#include +#include +#include +#include +#include +#include + +*/ +import "C" +import ( + "fmt" + "github.com/dotcloud/docker/graphdriver" + "os" + "path" + "syscall" + "unsafe" +) + +func init() { + graphdriver.Register("btrfs", Init) +} + +func Init(home string) (graphdriver.Driver, error) { + rootdir := path.Dir(home) + + var buf syscall.Statfs_t + if err := syscall.Statfs(rootdir, &buf); err != nil { + return nil, err + } + + if buf.Type != 0x9123683E { + return nil, fmt.Errorf("%s is not a btrfs filesystem", rootdir) + } + + return &Driver{ + home: home, + }, nil +} + +type Driver struct { + home string +} + +func (d *Driver) String() string { + return "btrfs" +} + +func (d *Driver) Status() [][2]string { + return nil +} + +func (d *Driver) Cleanup() error { + return nil +} + +func free(p *C.char) { + C.free(unsafe.Pointer(p)) +} + +func openDir(path string) (*C.DIR, error) { + Cpath := C.CString(path) + defer free(Cpath) + + dir := C.opendir(Cpath) + if dir == nil { + return nil, fmt.Errorf("Can't open dir") + } + return dir, nil +} + +func closeDir(dir *C.DIR) { + if dir != nil { + C.closedir(dir) + } +} + +func getDirFd(dir *C.DIR) uintptr { + return uintptr(C.dirfd(dir)) +} + +func subvolCreate(path, name string) error { + dir, err := openDir(path) + if err != nil { + return err + } + defer closeDir(dir) + + var args C.struct_btrfs_ioctl_vol_args + for i, c := range []byte(name) { + args.name[i] = C.char(c) + } + + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SUBVOL_CREATE, + uintptr(unsafe.Pointer(&args))) + if errno != 0 { + return fmt.Errorf("Can't create subvolume") + } + return nil +} + +func subvolSnapshot(src, dest, name string) error { + srcDir, err := openDir(src) + if err != nil { + return err + } + defer closeDir(srcDir) + + destDir, err := openDir(dest) + if err != nil { + return err + } + defer closeDir(destDir) + + var args C.struct_btrfs_ioctl_vol_args_v2 + args.fd = C.__s64(getDirFd(srcDir)) + for i, c := range []byte(name) { + args.name[i] = C.char(c) + } + + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(destDir), C.BTRFS_IOC_SNAP_CREATE_V2, + uintptr(unsafe.Pointer(&args))) + if errno != 0 { + return fmt.Errorf("Can't create subvolume") + } + return nil +} + +func subvolDelete(path, name string) error { + dir, err := openDir(path) + if err != nil { + return err + } + defer closeDir(dir) + + var args C.struct_btrfs_ioctl_vol_args + for i, c := range []byte(name) { + args.name[i] = C.char(c) + } + + _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SNAP_DESTROY, + uintptr(unsafe.Pointer(&args))) + if errno != 0 { + return fmt.Errorf("Can't create subvolume") + } + return nil +} + +func (d *Driver) subvolumesDir() string { + return path.Join(d.home, "subvolumes") +} + +func (d *Driver) subvolumesDirId(id string) string { + return path.Join(d.subvolumesDir(), id) +} + +func (d *Driver) Create(id string, parent string) error { + subvolumes := path.Join(d.home, "subvolumes") + if err := os.MkdirAll(subvolumes, 0700); err != nil { + return err + } + if parent == "" { + if err := subvolCreate(subvolumes, id); err != nil { + return err + } + } else { + parentDir, err := d.Get(parent) + if err != nil { + return err + } + if err := subvolSnapshot(parentDir, subvolumes, id); err != nil { + return err + } + } + return nil +} + +func (d *Driver) Remove(id string) error { + dir := d.subvolumesDirId(id) + if _, err := os.Stat(dir); err != nil { + return err + } + if err := subvolDelete(d.subvolumesDir(), id); err != nil { + return err + } + return os.RemoveAll(dir) +} + +func (d *Driver) Get(id string) (string, error) { + dir := d.subvolumesDirId(id) + st, err := os.Stat(dir) + if err != nil { + return "", err + } + + if !st.IsDir() { + return "", fmt.Errorf("%s: not a directory", dir) + } + + return dir, nil +} + +func (d *Driver) Put(id string) { +} + +func (d *Driver) Exists(id string) bool { + dir := d.subvolumesDirId(id) + _, err := os.Stat(dir) + return err == nil +} diff --git a/graphdriver/btrfs/dummy_unsupported.go b/graphdriver/btrfs/dummy_unsupported.go new file mode 100644 index 0000000000..5efd18081f --- /dev/null +++ b/graphdriver/btrfs/dummy_unsupported.go @@ -0,0 +1,3 @@ +// +build !linux + +package btrfs diff --git a/graphdriver/driver.go b/graphdriver/driver.go index 2be3f05f3a..c0ed00b0ad 100644 --- a/graphdriver/driver.go +++ b/graphdriver/driver.go @@ -41,6 +41,8 @@ var ( "aufs", "devicemapper", "vfs", + // experimental, has to be enabled manually for now + "btrfs", } ) diff --git a/runtime.go b/runtime.go index 1b17bac973..c33220b3a6 100644 --- a/runtime.go +++ b/runtime.go @@ -9,6 +9,7 @@ import ( "github.com/dotcloud/docker/execdriver/lxc" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/graphdriver/aufs" + _ "github.com/dotcloud/docker/graphdriver/btrfs" _ "github.com/dotcloud/docker/graphdriver/devmapper" _ "github.com/dotcloud/docker/graphdriver/vfs" "github.com/dotcloud/docker/pkg/graphdb" From 75e0357d691499b20dc903b702bc44c444f85005 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 20 Jan 2014 18:07:18 -0800 Subject: [PATCH 189/364] Populate Command self cointainer (toward Restore()) Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- container.go | 94 ++++++++++++++++++++++---------------------- execdriver/driver.go | 6 +-- runtime.go | 2 +- 3 files changed, 52 insertions(+), 50 deletions(-) diff --git a/container.go b/container.go index e288636a36..87df0d46b4 100644 --- a/container.go +++ b/container.go @@ -492,6 +492,49 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s }) } +func populateCommand(c *Container) { + var ( + en *execdriver.Network + driverConfig []string + ) + if !c.Config.NetworkDisabled { + network := c.NetworkSettings + en = &execdriver.Network{ + Gateway: network.Gateway, + Bridge: network.Bridge, + IPAddress: network.IPAddress, + IPPrefixLen: network.IPPrefixLen, + Mtu: c.runtime.config.Mtu, + } + } + + if lxcConf := c.hostConfig.LxcConf; lxcConf != nil { + for _, pair := range lxcConf { + driverConfig = append(driverConfig, fmt.Sprintf("%s = %s", pair.Key, pair.Value)) + } + } + resources := &execdriver.Resources{ + Memory: c.Config.Memory, + MemorySwap: c.Config.MemorySwap, + CpuShares: c.Config.CpuShares, + } + c.command = &execdriver.Command{ + ID: c.ID, + Privileged: c.hostConfig.Privileged, + Rootfs: c.RootfsPath(), + InitPath: "/.dockerinit", + Entrypoint: c.Path, + Arguments: c.Args, + WorkingDir: c.Config.WorkingDir, + Network: en, + Tty: c.Config.Tty, + User: c.Config.User, + Config: driverConfig, + Resources: resources, + } + c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true} +} + func (container *Container) Start() (err error) { container.Lock() defer container.Unlock() @@ -603,15 +646,15 @@ func (container *Container) Start() (err error) { return err } - var workingDir string + root := container.RootfsPath() + if container.Config.WorkingDir != "" { - workingDir = path.Clean(container.Config.WorkingDir) - if err := os.MkdirAll(path.Join(container.RootfsPath(), workingDir), 0755); err != nil { + container.Config.WorkingDir = path.Clean(container.Config.WorkingDir) + if err := os.MkdirAll(path.Join(root, container.Config.WorkingDir), 0755); err != nil { return nil } } - root := container.RootfsPath() envPath, err := container.EnvConfigPath() if err != nil { return err @@ -649,48 +692,7 @@ func (container *Container) Start() (err error) { } } - var ( - en *execdriver.Network - driverConfig []string - ) - - if !container.Config.NetworkDisabled { - network := container.NetworkSettings - en = &execdriver.Network{ - Gateway: network.Gateway, - Bridge: network.Bridge, - IPAddress: network.IPAddress, - IPPrefixLen: network.IPPrefixLen, - Mtu: container.runtime.config.Mtu, - } - } - - if lxcConf := container.hostConfig.LxcConf; lxcConf != nil { - for _, pair := range lxcConf { - driverConfig = append(driverConfig, fmt.Sprintf("%s = %s", pair.Key, pair.Value)) - } - } - resources := &execdriver.Resources{ - Memory: container.Config.Memory, - MemorySwap: container.Config.MemorySwap, - CpuShares: container.Config.CpuShares, - } - - container.command = &execdriver.Command{ - ID: container.ID, - Privileged: container.hostConfig.Privileged, - Rootfs: root, - InitPath: "/.dockerinit", - Entrypoint: container.Path, - Arguments: container.Args, - WorkingDir: workingDir, - Network: en, - Tty: container.Config.Tty, - User: container.Config.User, - Config: driverConfig, - Resources: resources, - } - container.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true} + populateCommand(container) // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { diff --git a/execdriver/driver.go b/execdriver/driver.go index 41124245e2..c2fd526ed4 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -61,9 +61,9 @@ type Info interface { type Driver interface { Run(c *Command, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code Kill(c *Command, sig int) error - Wait(id string) error // Wait on an out of process...process - lxc ghosts TODO: Rename to reattach, reconnect - Name() string // Driver name - Info(id string) Info // "temporary" hack (until we move state from core to plugins) + Restore(id string) (*Command, error) // Wait and try to re-attach on an out of process...process (lxc ghosts) + Name() string // Driver name + Info(id string) Info // "temporary" hack (until we move state from core to plugins) } // Network settings of the container diff --git a/runtime.go b/runtime.go index d35a5c7b04..f838e77ffe 100644 --- a/runtime.go +++ b/runtime.go @@ -810,7 +810,7 @@ func (runtime *Runtime) Kill(c *Container, sig int) error { } func (runtime *Runtime) WaitGhost(c *Container) error { - return runtime.execDriver.Wait(c.ID) + return runtime.execDriver.Restore(c.command) } // Nuke kills all containers then removes all content From ba8ca598625215499713a9b65001164b672e69ab Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 20 Jan 2014 18:27:36 -0800 Subject: [PATCH 190/364] Compile driver interface changes Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- execdriver/chroot/driver.go | 2 +- execdriver/driver.go | 6 +++--- execdriver/lxc/driver.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index 509a62dd65..4b2e02904e 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -74,7 +74,7 @@ func (d *driver) Kill(p *execdriver.Command, sig int) error { return p.Process.Kill() } -func (d *driver) Wait(id string) error { +func (d *driver) Restore(c *execdriver.Command) error { panic("Not Implemented") } diff --git a/execdriver/driver.go b/execdriver/driver.go index c2fd526ed4..511e9e80c6 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -61,9 +61,9 @@ type Info interface { type Driver interface { Run(c *Command, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code Kill(c *Command, sig int) error - Restore(id string) (*Command, error) // Wait and try to re-attach on an out of process...process (lxc ghosts) - Name() string // Driver name - Info(id string) Info // "temporary" hack (until we move state from core to plugins) + Restore(c *Command) error // Wait and try to re-attach on an out of process command + Name() string // Driver name + Info(id string) Info // "temporary" hack (until we move state from core to plugins) } // Network settings of the container diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 97c8a4b026..fbef616230 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -176,9 +176,9 @@ func (d *driver) Kill(c *execdriver.Command, sig int) error { return d.kill(c, sig) } -func (d *driver) Wait(id string) error { +func (d *driver) Restore(c *execdriver.Command) error { for { - output, err := exec.Command("lxc-info", "-n", id).CombinedOutput() + output, err := exec.Command("lxc-info", "-n", c.ID).CombinedOutput() if err != nil { return err } From 8723a8a89ec7d5b2a37e0536d7aadd78e5329773 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 20 Jan 2014 18:43:54 -0800 Subject: [PATCH 191/364] Populate command in ghost state to pass to RestoreCommand Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 3 ++- runtime.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/container.go b/container.go index 87df0d46b4..cb697e825e 100644 --- a/container.go +++ b/container.go @@ -1122,7 +1122,8 @@ func (container *Container) monitor(callback execdriver.StartCallback) error { if container.command == nil { // This happends when you have a GHOST container with lxc - err = container.runtime.WaitGhost(container) + populateCommand(container) + err = container.runtime.RestoreCommand(container) } else { exitCode, err = container.runtime.Run(container, callback) } diff --git a/runtime.go b/runtime.go index f838e77ffe..52690dcb7f 100644 --- a/runtime.go +++ b/runtime.go @@ -809,7 +809,7 @@ func (runtime *Runtime) Kill(c *Container, sig int) error { return runtime.execDriver.Kill(c.command, sig) } -func (runtime *Runtime) WaitGhost(c *Container) error { +func (runtime *Runtime) RestoreCommand(c *Container) error { return runtime.execDriver.Restore(c.command) } From 9e2e8a923c28833dd631a7206d3664a6a90cc24c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 22 Jan 2014 15:52:39 -0800 Subject: [PATCH 192/364] WIP for ip allocator Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- network.go | 122 +----------- networkdriver/portallocator/allocator.go | 229 +++++++++++++++++++++++ 2 files changed, 230 insertions(+), 121 deletions(-) create mode 100644 networkdriver/portallocator/allocator.go diff --git a/network.go b/network.go index 1139f6ad37..473df0ffdb 100644 --- a/network.go +++ b/network.go @@ -1,7 +1,6 @@ package docker import ( - "encoding/binary" "errors" "fmt" "github.com/dotcloud/docker/pkg/iptables" @@ -25,61 +24,6 @@ const ( siocBRADDBR = 0x89a0 ) -// Calculates the first and last IP addresses in an IPNet -func networkRange(network *net.IPNet) (net.IP, net.IP) { - netIP := network.IP.To4() - firstIP := netIP.Mask(network.Mask) - lastIP := net.IPv4(0, 0, 0, 0).To4() - for i := 0; i < len(lastIP); i++ { - lastIP[i] = netIP[i] | ^network.Mask[i] - } - return firstIP, lastIP -} - -// Detects overlap between one IPNet and another -func networkOverlaps(netX *net.IPNet, netY *net.IPNet) bool { - firstIP, _ := networkRange(netX) - if netY.Contains(firstIP) { - return true - } - firstIP, _ = networkRange(netY) - if netX.Contains(firstIP) { - return true - } - return false -} - -// Converts a 4 bytes IP into a 32 bit integer -func ipToInt(ip net.IP) int32 { - return int32(binary.BigEndian.Uint32(ip.To4())) -} - -// Converts 32 bit integer into a 4 bytes IP address -func intToIP(n int32) net.IP { - b := make([]byte, 4) - binary.BigEndian.PutUint32(b, uint32(n)) - return net.IP(b) -} - -// Given a netmask, calculates the number of available hosts -func networkSize(mask net.IPMask) int32 { - m := net.IPv4Mask(0, 0, 0, 0) - for i := 0; i < net.IPv4len; i++ { - m[i] = ^mask[i] - } - - return int32(binary.BigEndian.Uint32(m)) + 1 -} - -func checkRouteOverlaps(networks []netlink.Route, dockerNetwork *net.IPNet) error { - for _, network := range networks { - if network.IPNet != nil && networkOverlaps(dockerNetwork, network.IPNet) { - return fmt.Errorf("Network %s is already routed: '%s'", dockerNetwork, network) - } - } - return nil -} - func checkNameserverOverlaps(nameservers []string, dockerNetwork *net.IPNet) error { if len(nameservers) > 0 { for _, ns := range nameservers { @@ -142,10 +86,7 @@ func CreateBridgeIface(config *DaemonConfig) error { if err != nil { return err } - routes, err := netlink.NetworkGetRoutes() - if err != nil { - return err - } + // TODO: @crosbymichael register route if err := checkRouteOverlaps(routes, dockerNetwork); err == nil { if err := checkNameserverOverlaps(nameservers, dockerNetwork); err == nil { ifaceAddr = addr @@ -441,67 +382,6 @@ type allocatedIP struct { } func (alloc *IPAllocator) run() { - firstIP, _ := networkRange(alloc.network) - ipNum := ipToInt(firstIP) - ownIP := ipToInt(alloc.network.IP) - size := networkSize(alloc.network.Mask) - - pos := int32(1) - max := size - 2 // -1 for the broadcast address, -1 for the gateway address - for { - var ( - newNum int32 - inUse bool - ) - - // Find first unused IP, give up after one whole round - for attempt := int32(0); attempt < max; attempt++ { - newNum = ipNum + pos - - pos = pos%max + 1 - - // The network's IP is never okay to use - if newNum == ownIP { - continue - } - - if _, inUse = alloc.inUse[newNum]; !inUse { - // We found an unused IP - break - } - } - - ip := allocatedIP{ip: intToIP(newNum)} - if inUse { - ip.err = errors.New("No unallocated IP available") - } - - select { - case quit := <-alloc.quit: - if quit { - return - } - case alloc.queueAlloc <- ip: - alloc.inUse[newNum] = struct{}{} - case released := <-alloc.queueReleased: - r := ipToInt(released) - delete(alloc.inUse, r) - - if inUse { - // If we couldn't allocate a new IP, the released one - // will be the only free one now, so instantly use it - // next time - pos = r - ipNum - } else { - // Use same IP as last time - if pos == 1 { - pos = max - } else { - pos-- - } - } - } - } } func (alloc *IPAllocator) Acquire() (net.IP, error) { diff --git a/networkdriver/portallocator/allocator.go b/networkdriver/portallocator/allocator.go new file mode 100644 index 0000000000..5d8e812309 --- /dev/null +++ b/networkdriver/portallocator/allocator.go @@ -0,0 +1,229 @@ +package ipallocator + +import ( + "encoding/binary" + "errors" + "github.com/dotcloud/docker/pkg/netlink" + "net" + "sync" +) + +type networkSet map[iPNet]iPSet +type iPSet map[string]struct{} + +type iPNet struct { + IP string + Mask string +} + +var ( + ErrNetworkAlreadyAllocated = errors.New("requested network overlaps with existing network") + ErrNetworkAlreadyRegisterd = errors.New("requested network is already registered") + lock = sync.Mutex{} + allocatedIPs = networkSet{} + availableIPS = networkSet{} +) + +func RegisterNetwork(network *net.IPNet) error { + lock.Lock() + defer lock.Unlock() + + routes, err := netlink.NetworkGetRoutes() + if err != nil { + return err + } + + if err := checkRouteOverlaps(routes, network); err != nil { + return err + } + + if err := checkExistingNetworkOverlaps(network); err != nil { + return err + } + + allocatedIPs[newIPNet(network)] = iPSet{} + + return nil +} + +func RequestIP(ip *net.IPAddr) (*net.IPAddr, error) { + lock.Lock() + defer lock.Unlock() + + if ip == nil { + next, err := getNextIp() + if err != nil { + return nil, err + } + return next, nil + } + + if err := validateIP(ip); err != nil { + return nil, err + } + return ip, nil +} + +func ReleaseIP(ip *net.IPAddr) error { + lock.Lock() + defer lock.Unlock() + +} + +func getNextIp(network iPNet) (net.IPAddr, error) { + if available, exists := availableIPS[network]; exists { + } + + var ( + netNetwork = newNetIPNet(network) + firstIP, _ = networkRange(netNetwork) + ipNum = ipToInt(firstIP) + ownIP = ipToInt(netNetwork.IP) + size = networkSize(netNetwork.Mask) + + pos = int32(1) + max = size - 2 // -1 for the broadcast address, -1 for the gateway address + ) + + for { + var ( + newNum int32 + inUse bool + ) + + // Find first unused IP, give up after one whole round + for attempt := int32(0); attempt < max; attempt++ { + newNum = ipNum + pos + + pos = pos%max + 1 + + // The network's IP is never okay to use + if newNum == ownIP { + continue + } + + if _, inUse = alloc.inUse[newNum]; !inUse { + // We found an unused IP + break + } + } + + ip := allocatedIP{ip: intToIP(newNum)} + if inUse { + ip.err = errors.New("No unallocated IP available") + } + + select { + case quit := <-alloc.quit: + if quit { + return + } + case alloc.queueAlloc <- ip: + alloc.inUse[newNum] = struct{}{} + case released := <-alloc.queueReleased: + r := ipToInt(released) + delete(alloc.inUse, r) + + if inUse { + // If we couldn't allocate a new IP, the released one + // will be the only free one now, so instantly use it + // next time + pos = r - ipNum + } else { + // Use same IP as last time + if pos == 1 { + pos = max + } else { + pos-- + } + } + } + } + +} + +func validateIP(ip *net.IPAddr) error { + +} + +func checkRouteOverlaps(networks []netlink.Route, toCheck *net.IPNet) error { + for _, network := range networks { + if network.IPNet != nil && networkOverlaps(toCheck, network.IPNet) { + return ErrNetworkAlreadyAllocated + } + } + return nil +} + +// Detects overlap between one IPNet and another +func networkOverlaps(netX *net.IPNet, netY *net.IPNet) bool { + if firstIP, _ := networkRange(netX); netY.Contains(firstIP) { + return true + } + if firstIP, _ := networkRange(netY); netX.Contains(firstIP) { + return true + } + return false +} + +func checkExistingNetworkOverlaps(network *net.IPNet) error { + for existing := range allocatedIPs { + if newIPNet(network) == existing { + return ErrNetworkAlreadyRegisterd + } + if networkOverlaps(network, existing) { + return ErrNetworkAlreadyAllocated + } + } + return nil +} + +// Calculates the first and last IP addresses in an IPNet +func networkRange(network *net.IPNet) (net.IP, net.IP) { + var ( + netIP = network.IP.To4() + firstIP = netIP.Mask(network.Mask) + lastIP = net.IPv4(0, 0, 0, 0).To4() + ) + + for i := 0; i < len(lastIP); i++ { + lastIP[i] = netIP[i] | ^network.Mask[i] + } + return firstIP, lastIP +} + +func newIPNet(network *net.IPNet) iPNet { + return iPNet{ + IP: string(network.IP), + Mask: string(network.Mask), + } +} + +func newNetIPNet(network iPNet) *net.IPNet { + return &net.IPNet{ + IP: []byte(network.IP), + Mask: []byte(network.Mask), + } +} + +// Converts a 4 bytes IP into a 32 bit integer +func ipToInt(ip net.IP) int32 { + return int32(binary.BigEndian.Uint32(ip.To4())) +} + +// Converts 32 bit integer into a 4 bytes IP address +func intToIP(n int32) net.IP { + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, uint32(n)) + return net.IP(b) +} + +// Given a netmask, calculates the number of available hosts +func networkSize(mask net.IPMask) int32 { + m := net.IPv4Mask(0, 0, 0, 0) + for i := 0; i < net.IPv4len; i++ { + m[i] = ^mask[i] + } + + return int32(binary.BigEndian.Uint32(m)) + 1 +} From e7a9d236402363b402df43f25624a4e7e049f40f Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 22 Jan 2014 15:54:22 -0800 Subject: [PATCH 193/364] don't user os.Stderr in attach Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 12 ++++-------- engine/streams.go | 14 +++++++++++--- engine/streams_test.go | 4 +--- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/api.go b/api.go index b45f1a35d1..127ad77603 100644 --- a/api.go +++ b/api.go @@ -169,9 +169,7 @@ func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r return fmt.Errorf("Missing parameter") } job := srv.Eng.Job("export", vars["name"]) - if err := job.Stdout.Add(w); err != nil { - return err - } + job.Stdout.Add(w) if err := job.Run(); err != nil { return err } @@ -573,9 +571,7 @@ func getImagesGet(srv *Server, version float64, w http.ResponseWriter, r *http.R w.Header().Set("Content-Type", "application/x-tar") } job := srv.Eng.Job("image_export", vars["name"]) - if err := job.Stdout.Add(w); err != nil { - return err - } + job.Stdout.Add(w) return job.Run() } @@ -806,7 +802,7 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r job.Setenv("stderr", r.Form.Get("stderr")) job.Stdin.Add(inStream) job.Stdout.Add(outStream) - job.Stderr.Add(errStream) + job.Stderr.Set(errStream) if err := job.Run(); err != nil { fmt.Fprintf(outStream, "Error: %s\n", err) @@ -836,7 +832,7 @@ func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r * job.Setenv("stderr", r.Form.Get("stderr")) job.Stdin.Add(ws) job.Stdout.Add(ws) - job.Stderr.Add(ws) + job.Stderr.Set(ws) if err := job.Run(); err != nil { utils.Errorf("Error: %s", err) } diff --git a/engine/streams.go b/engine/streams.go index 703a56256d..935334c261 100644 --- a/engine/streams.go +++ b/engine/streams.go @@ -31,12 +31,20 @@ func (o *Output) Used() bool { // Add attaches a new destination to the Output. Any data subsequently written // to the output will be written to the new destination in addition to all the others. // This method is thread-safe. -// FIXME: Add cannot fail -func (o *Output) Add(dst io.Writer) error { +func (o *Output) Add(dst io.Writer) { o.Mutex.Lock() defer o.Mutex.Unlock() o.dests = append(o.dests, dst) - return nil +} + +// Set closes and remove existing destination and then attaches a new destination to +// the Output. Any data subsequently written to the output will be written to the new +// destination in addition to all the others. This method is thread-safe. +func (o *Output) Set(dst io.Writer) { + o.Close() + o.Mutex.Lock() + defer o.Mutex.Unlock() + o.dests = []io.Writer{dst} } // AddPipe creates an in-memory pipe with io.Pipe(), adds its writing end as a destination, diff --git a/engine/streams_test.go b/engine/streams_test.go index 37720c61ea..30d31d2952 100644 --- a/engine/streams_test.go +++ b/engine/streams_test.go @@ -95,9 +95,7 @@ func TestOutputAddEnv(t *testing.T) { func TestOutputAddClose(t *testing.T) { o := NewOutput() var s sentinelWriteCloser - if err := o.Add(&s); err != nil { - t.Fatal(err) - } + o.Add(&s) if err := o.Close(); err != nil { t.Fatal(err) } From 63d18d875b8a136d59c98b7b571366b10b5d4f76 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 22 Jan 2014 16:00:53 -0800 Subject: [PATCH 194/364] Implement basic stack for ips Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- networkdriver/portallocator/allocator.go | 2 +- networkdriver/portallocator/ipset.go | 62 ++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 networkdriver/portallocator/ipset.go diff --git a/networkdriver/portallocator/allocator.go b/networkdriver/portallocator/allocator.go index 5d8e812309..3704fae485 100644 --- a/networkdriver/portallocator/allocator.go +++ b/networkdriver/portallocator/allocator.go @@ -5,11 +5,11 @@ import ( "errors" "github.com/dotcloud/docker/pkg/netlink" "net" + "sort" "sync" ) type networkSet map[iPNet]iPSet -type iPSet map[string]struct{} type iPNet struct { IP string diff --git a/networkdriver/portallocator/ipset.go b/networkdriver/portallocator/ipset.go new file mode 100644 index 0000000000..b7737bdb73 --- /dev/null +++ b/networkdriver/portallocator/ipset.go @@ -0,0 +1,62 @@ +package ipallocator + +import ( + "sync" +) + +// iPSet is a thread-safe sorted set and a stack. +type iPSet struct { + sync.RWMutex + set []string +} + +// Push takes a string and adds it to the set. If the elem aready exists, it has no effect. +func (s *iPSet) Push(elem string) { + s.RLock() + for i, e := range s.set { + if e == elem { + s.RUnlock() + return + } + } + s.RUnlock() + + s.Lock() + s.set = append(s.set, elem) + // Make sure the list is always sorted + sort.Strings(s.set) + s.Unlock() +} + +// Pop returns the first elemen from the list and removes it. +// If the list is empty, it returns an empty string +func (s *iPSet) Pop() string { + s.RLock() + + for i, e := range s.set { + ret := e + s.RUnlock() + s.Lock() + s.set = append(s.set[:i], s.set[i+1:]...) + s.Unlock() + return e + } + s.RUnlock() + return "" +} + +// Remove removes an element from the list. +// If the element is not found, it has no effect. +func (s *iPSet) Remove(elem string) { + for i, e := range s.set { + if e == elem { + s.set = append(s.set[:i], s.set[i+1:]...) + return + } + } +} + +// Len returns the length of the list. +func (s *iPSet) Len() int { + return len(s.set) +} From d32777f6b1f3a5dff6df0e285b9cd68b2c0d00ad Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 22 Jan 2014 16:13:22 -0800 Subject: [PATCH 195/364] Implement get next ip Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- networkdriver/portallocator/allocator.go | 105 +++++++++-------------- 1 file changed, 42 insertions(+), 63 deletions(-) diff --git a/networkdriver/portallocator/allocator.go b/networkdriver/portallocator/allocator.go index 5d8e812309..a148dc4138 100644 --- a/networkdriver/portallocator/allocator.go +++ b/networkdriver/portallocator/allocator.go @@ -19,6 +19,7 @@ type iPNet struct { var ( ErrNetworkAlreadyAllocated = errors.New("requested network overlaps with existing network") ErrNetworkAlreadyRegisterd = errors.New("requested network is already registered") + ErrNoAvailableIps = errors.New("no available ips on network") lock = sync.Mutex{} allocatedIPs = networkSet{} availableIPS = networkSet{} @@ -40,109 +41,87 @@ func RegisterNetwork(network *net.IPNet) error { if err := checkExistingNetworkOverlaps(network); err != nil { return err } - allocatedIPs[newIPNet(network)] = iPSet{} return nil } -func RequestIP(ip *net.IPAddr) (*net.IPAddr, error) { +func RequestIP(network *net.IPNet, ip *net.IPAddr) (*net.IPAddr, error) { lock.Lock() defer lock.Unlock() if ip == nil { - next, err := getNextIp() + next, err := getNextIp(network) if err != nil { return nil, err } return next, nil } - if err := validateIP(ip); err != nil { + if err := validateIP(network, ip); err != nil { return nil, err } return ip, nil } -func ReleaseIP(ip *net.IPAddr) error { +func ReleaseIP(network *net.IPNet, ip *net.IPAddr) error { lock.Lock() defer lock.Unlock() + n := newIPNet(network) + existing := allocatedIPs[n] + + delete(existing, ip.String()) + availableIPS[n][ip.String()] = struct{}{} + + return nil } -func getNextIp(network iPNet) (net.IPAddr, error) { +func getNextIp(network *net.IPNet) (net.IPAddr, error) { if available, exists := availableIPS[network]; exists { + return nil, nil } var ( - netNetwork = newNetIPNet(network) - firstIP, _ = networkRange(netNetwork) + firstIP, _ = networkRange(network) ipNum = ipToInt(firstIP) - ownIP = ipToInt(netNetwork.IP) - size = networkSize(netNetwork.Mask) + ownIP = ipToInt(network.IP) + size = networkSize(network.Mask) + n = newIPNet(network) + allocated = allocatedIPs[n] - pos = int32(1) - max = size - 2 // -1 for the broadcast address, -1 for the gateway address + pos = int32(1) + max = size - 2 // -1 for the broadcast address, -1 for the gateway address + ip *net.IP + newNum int32 + inUse bool ) - for { - var ( - newNum int32 - inUse bool - ) - - // Find first unused IP, give up after one whole round - for attempt := int32(0); attempt < max; attempt++ { - newNum = ipNum + pos - - pos = pos%max + 1 - - // The network's IP is never okay to use - if newNum == ownIP { - continue - } - - if _, inUse = alloc.inUse[newNum]; !inUse { - // We found an unused IP - break - } + // Find first unused IP, give up after one whole round + for attempt := int32(0); attempt < max; attempt++ { + newNum = ipNum + pos + pos = pos%max + 1 + // The network's IP is never okay to use + if newNum == ownIP { + continue } - ip := allocatedIP{ip: intToIP(newNum)} - if inUse { - ip.err = errors.New("No unallocated IP available") - } - - select { - case quit := <-alloc.quit: - if quit { - return - } - case alloc.queueAlloc <- ip: - alloc.inUse[newNum] = struct{}{} - case released := <-alloc.queueReleased: - r := ipToInt(released) - delete(alloc.inUse, r) - - if inUse { - // If we couldn't allocate a new IP, the released one - // will be the only free one now, so instantly use it - // next time - pos = r - ipNum - } else { - // Use same IP as last time - if pos == 1 { - pos = max - } else { - pos-- - } - } + ip = intToIP(newNum) + if _, inUse = allocated[ip.String()]; !inUse { + // We found an unused IP + break } } + if ip == nil { + return nil, ErrNoAvailableIps + } + allocated[ip.String()] = struct{}{} + + return ip, nil } -func validateIP(ip *net.IPAddr) error { +func validateIP(network *net.IPNet, ip *net.IPAddr) error { } From 43bcbf06a663c5d8cac63f2af8fefef7edc5513a Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 22 Jan 2014 16:13:28 -0800 Subject: [PATCH 196/364] Implement Containers in set Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- networkdriver/portallocator/ipset.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/networkdriver/portallocator/ipset.go b/networkdriver/portallocator/ipset.go index b7737bdb73..83203a1688 100644 --- a/networkdriver/portallocator/ipset.go +++ b/networkdriver/portallocator/ipset.go @@ -45,6 +45,16 @@ func (s *iPSet) Pop() string { return "" } +// Exists checks if the given element present in the list. +func (s *iPSet) Exists(elem string) bool { + for _, e := range s.set { + if e == elem { + return true + } + } + return false +} + // Remove removes an element from the list. // If the element is not found, it has no effect. func (s *iPSet) Remove(elem string) { From 18df387bf8a4a1419b6d3267a4e0551ff72b0f88 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 22 Jan 2014 17:12:28 -0800 Subject: [PATCH 197/364] Implement PullBack() Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- networkdriver/portallocator/ipset.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/networkdriver/portallocator/ipset.go b/networkdriver/portallocator/ipset.go index 83203a1688..282279f29d 100644 --- a/networkdriver/portallocator/ipset.go +++ b/networkdriver/portallocator/ipset.go @@ -28,9 +28,14 @@ func (s *iPSet) Push(elem string) { s.Unlock() } +// Pop is an alias to PopFront() +func (s *iPSet) Pop() string { + return s.PopFront() +} + // Pop returns the first elemen from the list and removes it. // If the list is empty, it returns an empty string -func (s *iPSet) Pop() string { +func (s *iPSet) PopFront() string { s.RLock() for i, e := range s.set { @@ -45,6 +50,16 @@ func (s *iPSet) Pop() string { return "" } +// PullBack retrieve the last element of the list. +// The element is not removed. +// If the list is empty, an empty element is returned. +func (s *iPSet) PullBack() string { + if len(s.set) == 0 { + return "" + } + return s.set[len(s.set)-1] +} + // Exists checks if the given element present in the list. func (s *iPSet) Exists(elem string) bool { for _, e := range s.set { From 3e3abdd770bdc23c409a9e49619a1897ffbf2354 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 22 Jan 2014 17:20:19 -0800 Subject: [PATCH 198/364] Use int32 instead of string for ip set Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- networkdriver/portallocator/ipset.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/networkdriver/portallocator/ipset.go b/networkdriver/portallocator/ipset.go index 282279f29d..c54e64a120 100644 --- a/networkdriver/portallocator/ipset.go +++ b/networkdriver/portallocator/ipset.go @@ -1,17 +1,18 @@ package ipallocator import ( + "sort" "sync" ) // iPSet is a thread-safe sorted set and a stack. type iPSet struct { sync.RWMutex - set []string + set []int32 } // Push takes a string and adds it to the set. If the elem aready exists, it has no effect. -func (s *iPSet) Push(elem string) { +func (s *iPSet) Push(elem int32) { s.RLock() for i, e := range s.set { if e == elem { @@ -24,18 +25,18 @@ func (s *iPSet) Push(elem string) { s.Lock() s.set = append(s.set, elem) // Make sure the list is always sorted - sort.Strings(s.set) + sort.Ints(s.set) s.Unlock() } // Pop is an alias to PopFront() -func (s *iPSet) Pop() string { +func (s *iPSet) Pop() int32 { return s.PopFront() } // Pop returns the first elemen from the list and removes it. -// If the list is empty, it returns an empty string -func (s *iPSet) PopFront() string { +// If the list is empty, it returns 0 +func (s *iPSet) PopFront() int32 { s.RLock() for i, e := range s.set { @@ -53,7 +54,7 @@ func (s *iPSet) PopFront() string { // PullBack retrieve the last element of the list. // The element is not removed. // If the list is empty, an empty element is returned. -func (s *iPSet) PullBack() string { +func (s *iPSet) PullBack() int32 { if len(s.set) == 0 { return "" } @@ -61,7 +62,7 @@ func (s *iPSet) PullBack() string { } // Exists checks if the given element present in the list. -func (s *iPSet) Exists(elem string) bool { +func (s *iPSet) Exists(elem int32) bool { for _, e := range s.set { if e == elem { return true @@ -72,7 +73,7 @@ func (s *iPSet) Exists(elem string) bool { // Remove removes an element from the list. // If the element is not found, it has no effect. -func (s *iPSet) Remove(elem string) { +func (s *iPSet) Remove(elem int32) { for i, e := range s.set { if e == elem { s.set = append(s.set[:i], s.set[i+1:]...) From 6bc05899aa5023fdda0441b76a829d0e9a6f6dea Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 22 Jan 2014 18:05:20 -0800 Subject: [PATCH 199/364] Finish implementation and begin working on tests Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- networkdriver/portallocator/allocator.go | 101 +++++++++--------- networkdriver/portallocator/allocator_test.go | 26 +++++ networkdriver/portallocator/ipset.go | 23 ++-- 3 files changed, 89 insertions(+), 61 deletions(-) create mode 100644 networkdriver/portallocator/allocator_test.go diff --git a/networkdriver/portallocator/allocator.go b/networkdriver/portallocator/allocator.go index 40a1c001ce..91036de199 100644 --- a/networkdriver/portallocator/allocator.go +++ b/networkdriver/portallocator/allocator.go @@ -5,7 +5,6 @@ import ( "errors" "github.com/dotcloud/docker/pkg/netlink" "net" - "sort" "sync" ) @@ -20,9 +19,11 @@ var ( ErrNetworkAlreadyAllocated = errors.New("requested network overlaps with existing network") ErrNetworkAlreadyRegisterd = errors.New("requested network is already registered") ErrNoAvailableIps = errors.New("no available ips on network") - lock = sync.Mutex{} - allocatedIPs = networkSet{} - availableIPS = networkSet{} + ErrIPAlreadyAllocated = errors.New("ip already allocated") + + lock = sync.Mutex{} + allocatedIPs = networkSet{} + availableIPS = networkSet{} ) func RegisterNetwork(network *net.IPNet) error { @@ -41,12 +42,15 @@ func RegisterNetwork(network *net.IPNet) error { if err := checkExistingNetworkOverlaps(network); err != nil { return err } - allocatedIPs[newIPNet(network)] = iPSet{} + n := newIPNet(network) + + allocatedIPs[n] = iPSet{} + availableIPS[n] = iPSet{} return nil } -func RequestIP(network *net.IPNet, ip *net.IPAddr) (*net.IPAddr, error) { +func RequestIP(network *net.IPNet, ip *net.IP) (*net.IP, error) { lock.Lock() defer lock.Unlock() @@ -58,71 +62,65 @@ func RequestIP(network *net.IPNet, ip *net.IPAddr) (*net.IPAddr, error) { return next, nil } - if err := validateIP(network, ip); err != nil { + if err := registerIP(network, ip); err != nil { return nil, err } return ip, nil } -func ReleaseIP(network *net.IPNet, ip *net.IPAddr) error { +func ReleaseIP(network *net.IPNet, ip *net.IP) error { lock.Lock() defer lock.Unlock() n := newIPNet(network) existing := allocatedIPs[n] - delete(existing, ip.String()) - availableIPS[n][ip.String()] = struct{}{} + i := ipToInt(ip) + existing.Remove(int(i)) + available := availableIPS[n] + available.Push(int(i)) return nil } -func getNextIp(network *net.IPNet) (net.IPAddr, error) { - if available, exists := availableIPS[network]; exists { - return nil, nil - } - +func getNextIp(network *net.IPNet) (*net.IP, error) { var ( - firstIP, _ = networkRange(network) - ipNum = ipToInt(firstIP) - ownIP = ipToInt(network.IP) - size = networkSize(network.Mask) - n = newIPNet(network) - allocated = allocatedIPs[n] - - pos = int32(1) - max = size - 2 // -1 for the broadcast address, -1 for the gateway address - ip *net.IP - newNum int32 - inUse bool + n = newIPNet(network) + available = availableIPS[n] + next = available.Pop() + allocated = allocatedIPs[n] + ownIP = int(ipToInt(&network.IP)) ) - // Find first unused IP, give up after one whole round - for attempt := int32(0); attempt < max; attempt++ { - newNum = ipNum + pos - pos = pos%max + 1 - // The network's IP is never okay to use - if newNum == ownIP { + if next != 0 { + ip := intToIP(int32(next)) + allocated.Push(int(next)) + return ip, nil + } + size := int(networkSize(network.Mask)) + next = allocated.PullBack() + 1 + + // size -1 for the broadcast address, -1 for the gateway address + for i := 0; i < size-2; i++ { + if next == ownIP { + next++ continue } - ip = intToIP(newNum) - if _, inUse = allocated[ip.String()]; !inUse { - // We found an unused IP - break - } - } + ip := intToIP(int32(next)) + allocated.Push(next) - if ip == nil { - return nil, ErrNoAvailableIps + return ip, nil } - allocated[ip.String()] = struct{}{} - - return ip, nil + return nil, ErrNoAvailableIps } -func validateIP(network *net.IPNet, ip *net.IPAddr) error { - +func registerIP(network *net.IPNet, ip *net.IP) error { + existing := allocatedIPs[newIPNet(network)] + if existing.Exists(int(ipToInt(ip))) { + return ErrIPAlreadyAllocated + } + return nil } func checkRouteOverlaps(networks []netlink.Route, toCheck *net.IPNet) error { @@ -150,7 +148,9 @@ func checkExistingNetworkOverlaps(network *net.IPNet) error { if newIPNet(network) == existing { return ErrNetworkAlreadyRegisterd } - if networkOverlaps(network, existing) { + + ex := newNetIPNet(existing) + if networkOverlaps(network, ex) { return ErrNetworkAlreadyAllocated } } @@ -186,15 +186,16 @@ func newNetIPNet(network iPNet) *net.IPNet { } // Converts a 4 bytes IP into a 32 bit integer -func ipToInt(ip net.IP) int32 { +func ipToInt(ip *net.IP) int32 { return int32(binary.BigEndian.Uint32(ip.To4())) } // Converts 32 bit integer into a 4 bytes IP address -func intToIP(n int32) net.IP { +func intToIP(n int32) *net.IP { b := make([]byte, 4) binary.BigEndian.PutUint32(b, uint32(n)) - return net.IP(b) + ip := net.IP(b) + return &ip } // Given a netmask, calculates the number of available hosts diff --git a/networkdriver/portallocator/allocator_test.go b/networkdriver/portallocator/allocator_test.go new file mode 100644 index 0000000000..570f415780 --- /dev/null +++ b/networkdriver/portallocator/allocator_test.go @@ -0,0 +1,26 @@ +package ipallocator + +import ( + "net" + "testing" +) + +func TestRegisterNetwork(t *testing.T) { + network := &net.IPNet{ + IP: []byte{192, 168, 0, 1}, + Mask: []byte{255, 255, 255, 0}, + } + + if err := RegisterNetwork(network); err != nil { + t.Fatal(err) + } + + n := newIPNet(network) + if _, exists := allocatedIPs[n]; !exists { + t.Fatal("IPNet should exist in allocated IPs") + } + + if _, exists := availableIPS[n]; !exists { + t.Fatal("IPNet should exist in available IPs") + } +} diff --git a/networkdriver/portallocator/ipset.go b/networkdriver/portallocator/ipset.go index c54e64a120..42b545b2d7 100644 --- a/networkdriver/portallocator/ipset.go +++ b/networkdriver/portallocator/ipset.go @@ -8,13 +8,13 @@ import ( // iPSet is a thread-safe sorted set and a stack. type iPSet struct { sync.RWMutex - set []int32 + set []int } // Push takes a string and adds it to the set. If the elem aready exists, it has no effect. -func (s *iPSet) Push(elem int32) { +func (s *iPSet) Push(elem int) { s.RLock() - for i, e := range s.set { + for _, e := range s.set { if e == elem { s.RUnlock() return @@ -30,13 +30,13 @@ func (s *iPSet) Push(elem int32) { } // Pop is an alias to PopFront() -func (s *iPSet) Pop() int32 { +func (s *iPSet) Pop() int { return s.PopFront() } // Pop returns the first elemen from the list and removes it. // If the list is empty, it returns 0 -func (s *iPSet) PopFront() int32 { +func (s *iPSet) PopFront() int { s.RLock() for i, e := range s.set { @@ -45,24 +45,25 @@ func (s *iPSet) PopFront() int32 { s.Lock() s.set = append(s.set[:i], s.set[i+1:]...) s.Unlock() - return e + return ret } s.RUnlock() - return "" + + return 0 } // PullBack retrieve the last element of the list. // The element is not removed. // If the list is empty, an empty element is returned. -func (s *iPSet) PullBack() int32 { +func (s *iPSet) PullBack() int { if len(s.set) == 0 { - return "" + return 0 } return s.set[len(s.set)-1] } // Exists checks if the given element present in the list. -func (s *iPSet) Exists(elem int32) bool { +func (s *iPSet) Exists(elem int) bool { for _, e := range s.set { if e == elem { return true @@ -73,7 +74,7 @@ func (s *iPSet) Exists(elem int32) bool { // Remove removes an element from the list. // If the element is not found, it has no effect. -func (s *iPSet) Remove(elem int32) { +func (s *iPSet) Remove(elem int) { for i, e := range s.set { if e == elem { s.set = append(s.set[:i], s.set[i+1:]...) From 79bb8212e05cc9c14b8edda2b8a924fef63ea2e0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 22 Jan 2014 19:34:47 -0800 Subject: [PATCH 200/364] Implement pos for set and add unit tests Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- networkdriver/portallocator/allocator.go | 62 +++++--- networkdriver/portallocator/allocator_test.go | 137 ++++++++++++++++++ networkdriver/portallocator/ipset.go | 5 - 3 files changed, 178 insertions(+), 26 deletions(-) diff --git a/networkdriver/portallocator/allocator.go b/networkdriver/portallocator/allocator.go index 91036de199..5b6befd1da 100644 --- a/networkdriver/portallocator/allocator.go +++ b/networkdriver/portallocator/allocator.go @@ -8,7 +8,7 @@ import ( "sync" ) -type networkSet map[iPNet]iPSet +type networkSet map[iPNet]*iPSet type iPNet struct { IP string @@ -44,8 +44,8 @@ func RegisterNetwork(network *net.IPNet) error { } n := newIPNet(network) - allocatedIPs[n] = iPSet{} - availableIPS[n] = iPSet{} + allocatedIPs[n] = &iPSet{} + availableIPS[n] = &iPSet{} return nil } @@ -72,13 +72,18 @@ func ReleaseIP(network *net.IPNet, ip *net.IP) error { lock.Lock() defer lock.Unlock() - n := newIPNet(network) - existing := allocatedIPs[n] + var ( + first, _ = networkRange(network) + base = ipToInt(&first) + n = newIPNet(network) + existing = allocatedIPs[n] + available = availableIPS[n] + i = ipToInt(ip) + pos = i - base + ) - i := ipToInt(ip) - existing.Remove(int(i)) - available := availableIPS[n] - available.Push(int(i)) + existing.Remove(int(pos)) + available.Push(int(pos)) return nil } @@ -86,29 +91,43 @@ func ReleaseIP(network *net.IPNet, ip *net.IP) error { func getNextIp(network *net.IPNet) (*net.IP, error) { var ( n = newIPNet(network) + ownIP = ipToInt(&network.IP) available = availableIPS[n] - next = available.Pop() allocated = allocatedIPs[n] - ownIP = int(ipToInt(&network.IP)) + + first, _ = networkRange(network) + base = ipToInt(&first) + + pos = int32(available.Pop()) ) - if next != 0 { - ip := intToIP(int32(next)) - allocated.Push(int(next)) + // We pop and push the position not the ip + if pos != 0 { + ip := intToIP(int32(base + pos)) + allocated.Push(int(pos)) + return ip, nil } - size := int(networkSize(network.Mask)) - next = allocated.PullBack() + 1 - // size -1 for the broadcast address, -1 for the gateway address - for i := 0; i < size-2; i++ { + var ( + size = int(networkSize(network.Mask)) + max = int32(size - 2) // size -1 for the broadcast address, -1 for the gateway address + ) + + if pos = int32(allocated.PullBack()); pos == 0 { + pos = 1 + } + + for i := int32(0); i < max; i++ { + next := int32(base + pos) + pos = pos%max + 1 + if next == ownIP { - next++ continue } - ip := intToIP(int32(next)) - allocated.Push(next) + ip := intToIP(next) + allocated.Push(int(pos)) return ip, nil } @@ -117,6 +136,7 @@ func getNextIp(network *net.IPNet) (*net.IP, error) { func registerIP(network *net.IPNet, ip *net.IP) error { existing := allocatedIPs[newIPNet(network)] + // checking position not ip if existing.Exists(int(ipToInt(ip))) { return ErrIPAlreadyAllocated } diff --git a/networkdriver/portallocator/allocator_test.go b/networkdriver/portallocator/allocator_test.go index 570f415780..bcdcfa66b8 100644 --- a/networkdriver/portallocator/allocator_test.go +++ b/networkdriver/portallocator/allocator_test.go @@ -1,11 +1,18 @@ package ipallocator import ( + "fmt" "net" "testing" ) +func reset() { + allocatedIPs = networkSet{} + availableIPS = networkSet{} +} + func TestRegisterNetwork(t *testing.T) { + defer reset() network := &net.IPNet{ IP: []byte{192, 168, 0, 1}, Mask: []byte{255, 255, 255, 0}, @@ -24,3 +31,133 @@ func TestRegisterNetwork(t *testing.T) { t.Fatal("IPNet should exist in available IPs") } } + +func TestRegisterTwoNetworks(t *testing.T) { + defer reset() + network := &net.IPNet{ + IP: []byte{192, 168, 0, 1}, + Mask: []byte{255, 255, 255, 0}, + } + + if err := RegisterNetwork(network); err != nil { + t.Fatal(err) + } + + network2 := &net.IPNet{ + IP: []byte{10, 1, 42, 1}, + Mask: []byte{255, 255, 255, 0}, + } + + if err := RegisterNetwork(network2); err != nil { + t.Fatal(err) + } +} + +func TestRegisterNetworkThatExists(t *testing.T) { + defer reset() + network := &net.IPNet{ + IP: []byte{192, 168, 0, 1}, + Mask: []byte{255, 255, 255, 0}, + } + + if err := RegisterNetwork(network); err != nil { + t.Fatal(err) + } + + if err := RegisterNetwork(network); err != ErrNetworkAlreadyRegisterd { + t.Fatalf("Expected error of %s got %s", ErrNetworkAlreadyRegisterd, err) + } +} + +func TestRequestNewIps(t *testing.T) { + defer reset() + network := &net.IPNet{ + IP: []byte{192, 168, 0, 1}, + Mask: []byte{255, 255, 255, 0}, + } + + if err := RegisterNetwork(network); err != nil { + t.Fatal(err) + } + + for i := 2; i < 10; i++ { + ip, err := RequestIP(network, nil) + if err != nil { + t.Fatal(err) + } + + if expected := fmt.Sprintf("192.168.0.%d", i); ip.String() != expected { + t.Fatalf("Expected ip %s got %s", expected, ip.String()) + } + } +} + +func TestReleaseIp(t *testing.T) { + defer reset() + network := &net.IPNet{ + IP: []byte{192, 168, 0, 1}, + Mask: []byte{255, 255, 255, 0}, + } + + if err := RegisterNetwork(network); err != nil { + t.Fatal(err) + } + + ip, err := RequestIP(network, nil) + if err != nil { + t.Fatal(err) + } + + if err := ReleaseIP(network, ip); err != nil { + t.Fatal(err) + } +} + +func TestGetReleasedIp(t *testing.T) { + defer reset() + network := &net.IPNet{ + IP: []byte{192, 168, 0, 1}, + Mask: []byte{255, 255, 255, 0}, + } + + if err := RegisterNetwork(network); err != nil { + t.Fatal(err) + } + + ip, err := RequestIP(network, nil) + if err != nil { + t.Fatal(err) + } + + value := ip.String() + if err := ReleaseIP(network, ip); err != nil { + t.Fatal(err) + } + + ip, err = RequestIP(network, nil) + if err != nil { + t.Fatal(err) + } + + if ip.String() != value { + t.Fatalf("Expected to receive same ip %s got %s", value, ip.String()) + } +} + +func TestRequesetSpecificIp(t *testing.T) { + defer reset() + network := &net.IPNet{ + IP: []byte{192, 168, 0, 1}, + Mask: []byte{255, 255, 255, 0}, + } + + if err := RegisterNetwork(network); err != nil { + t.Fatal(err) + } + + ip := net.ParseIP("192.168.1.5") + + if _, err := RequestIP(network, &ip); err != nil { + t.Fatal(err) + } +} diff --git a/networkdriver/portallocator/ipset.go b/networkdriver/portallocator/ipset.go index 42b545b2d7..43d54691d1 100644 --- a/networkdriver/portallocator/ipset.go +++ b/networkdriver/portallocator/ipset.go @@ -82,8 +82,3 @@ func (s *iPSet) Remove(elem int) { } } } - -// Len returns the length of the list. -func (s *iPSet) Len() int { - return len(s.set) -} From d247b7d42613e3435e332aa05351d22e1fbea82c Mon Sep 17 00:00:00 2001 From: James Mills Date: Thu, 23 Jan 2014 00:48:47 +1000 Subject: [PATCH 201/364] Added script for building Base Docker CRUX images Docker-DCO-1.1-Signed-off-by: James Mills (github: therealprologic) --- contrib/mkimage-crux.sh | 75 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100755 contrib/mkimage-crux.sh diff --git a/contrib/mkimage-crux.sh b/contrib/mkimage-crux.sh new file mode 100755 index 0000000000..074c334bba --- /dev/null +++ b/contrib/mkimage-crux.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Generate a minimal filesystem for CRUX/Linux and load it into the local +# docker as "cruxlinux" +# requires root and the crux iso (http://crux.nu) + +set -e + +die () { + echo >&2 "$@" + exit 1 +} + +[ "$#" -eq 1 ] || die "1 argument(s) required, $# provided. Usage: ./mkimage-crux.sh /path/to/iso" + +ISO=${1} + +ROOTFS=$(mktemp -d /tmp/rootfs-crux-XXXXXXXXXX) +CRUX=$(mktemp -d /tmp/crux-XXXXXXXXXX) +TMP=$(mktemp -d /tmp/XXXXXXXXXX) + +VERSION=$(basename --suffix=.iso $ISO | sed 's/[^0-9.]*\([0-9.]*\).*/\1/') + +# Mount the ISO +mount -o ro,loop $ISO $CRUX + +# Extract pkgutils +tar -C $TMP -xf $CRUX/tools/pkgutils#*.pkg.tar.gz + +# Put pkgadd in the $PATH +export PATH="$TMP/usr/bin:$PATH" + +# Install core packages +mkdir -p $ROOTFS/var/lib/pkg +touch $ROOTFS/var/lib/pkg/db +for pkg in $CRUX/crux/core/*; do + pkgadd -r $ROOTFS $pkg +done + +# Remove agetty and inittab config +if (grep agetty ${ROOTFS}/etc/inittab 2>&1 > /dev/null); then + echo "Removing agetty from /etc/inittab ..." + chroot ${ROOTFS} sed -i -e "/agetty/d" /etc/inittab + chroot ${ROOTFS} sed -i -e "/shutdown/d" /etc/inittab + chroot ${ROOTFS} sed -i -e "/^$/N;/^\n$/d" /etc/inittab +fi + +# Remove kernel source +rm -rf $ROOTFS/usr/src/* + +# udev doesn't work in containers, rebuild /dev +DEV=$ROOTFS/dev +rm -rf $DEV +mkdir -p $DEV +mknod -m 666 $DEV/null c 1 3 +mknod -m 666 $DEV/zero c 1 5 +mknod -m 666 $DEV/random c 1 8 +mknod -m 666 $DEV/urandom c 1 9 +mkdir -m 755 $DEV/pts +mkdir -m 1777 $DEV/shm +mknod -m 666 $DEV/tty c 5 0 +mknod -m 600 $DEV/console c 5 1 +mknod -m 666 $DEV/tty0 c 4 0 +mknod -m 666 $DEV/full c 1 7 +mknod -m 600 $DEV/initctl p +mknod -m 666 $DEV/ptmx c 5 2 + +IMAGE_ID=$(tar --numeric-owner -C $ROOTFS -c . | docker import - crux:$VERSION) +docker tag $IMAGE_ID crux:latest +docker run -i -t crux echo Success. + +# Cleanup +umount $CRUX +rm -rf $ROOTFS +rm -rf $CRUX +rm -rf $TMP From bfc1043f36dcce7f6232be55148f52327e356933 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Thu, 23 Jan 2014 14:00:16 +1000 Subject: [PATCH 202/364] talk about the new exec form of RUN (Closes #3723) implemented by #3558 Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/reference/builder.rst | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/sources/reference/builder.rst b/docs/sources/reference/builder.rst index e61db62689..45cb2ab86e 100644 --- a/docs/sources/reference/builder.rst +++ b/docs/sources/reference/builder.rst @@ -147,17 +147,23 @@ the generated images. 3.3 RUN ------- - ``RUN `` +RUN has 2 forms: -The ``RUN`` instruction will execute any commands on the current image -and commit the results. The resulting committed image will be used for -the next step in the Dockerfile. +* ``RUN `` (the command is run in a shell - ``/bin/sh -c``) +* ``RUN ["executable", "param1", "param2"]`` (*exec* form) + +The ``RUN`` instruction will execute any commands in a new layer on top +of the current image and commit the results. The resulting committed image +will be used for the next step in the Dockerfile. Layering ``RUN`` instructions and generating commits conforms to the core concepts of Docker where commits are cheap and containers can be created from any point in an image's history, much like source control. +The *exec* form makes it possible to avoid shell string munging, and to ``RUN`` +commands using a base image that does not contain ``/bin/sh``. + Known Issues (RUN) .................. From 648357ffdf6670810383be6437b8a5f8a38d4e42 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 23 Jan 2014 00:28:24 -0800 Subject: [PATCH 203/364] Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- networkdriver/{portallocator => ipallocator}/allocator.go | 0 networkdriver/{portallocator => ipallocator}/allocator_test.go | 0 networkdriver/{portallocator => ipallocator}/ipset.go | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename networkdriver/{portallocator => ipallocator}/allocator.go (100%) rename networkdriver/{portallocator => ipallocator}/allocator_test.go (100%) rename networkdriver/{portallocator => ipallocator}/ipset.go (100%) diff --git a/networkdriver/portallocator/allocator.go b/networkdriver/ipallocator/allocator.go similarity index 100% rename from networkdriver/portallocator/allocator.go rename to networkdriver/ipallocator/allocator.go diff --git a/networkdriver/portallocator/allocator_test.go b/networkdriver/ipallocator/allocator_test.go similarity index 100% rename from networkdriver/portallocator/allocator_test.go rename to networkdriver/ipallocator/allocator_test.go diff --git a/networkdriver/portallocator/ipset.go b/networkdriver/ipallocator/ipset.go similarity index 100% rename from networkdriver/portallocator/ipset.go rename to networkdriver/ipallocator/ipset.go From fccca3542b91513dd0da73db21ffa3ced4dd9b7f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 23 Jan 2014 01:31:38 -0800 Subject: [PATCH 204/364] Move tests from core into ipallocator Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 6 +- network.go | 115 ++----- network_test.go | 273 ---------------- networkdriver/ipallocator/allocator.go | 33 +- networkdriver/ipallocator/allocator_test.go | 329 +++++++++++++++++++- networkdriver/network.go | 1 + 6 files changed, 378 insertions(+), 379 deletions(-) create mode 100644 networkdriver/network.go diff --git a/container.go b/container.go index 7ebfb3e397..f7bcec99d8 100644 --- a/container.go +++ b/container.go @@ -7,6 +7,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/networkdriver/ipallocator" "github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/utils" @@ -1039,8 +1040,9 @@ func (container *Container) allocateNetwork() error { manager: manager, } if iface != nil && iface.IPNet.IP != nil { - ipNum := ipToInt(iface.IPNet.IP) - manager.ipAllocator.inUse[ipNum] = struct{}{} + if _, err := ipallocator.RequestIP(manager.bridgeNetwork, &iface.IPNet.IP); err != nil { + return err + } } else { iface, err = container.runtime.networkManager.Allocate() if err != nil { diff --git a/network.go b/network.go index 473df0ffdb..4987140402 100644 --- a/network.go +++ b/network.go @@ -1,8 +1,8 @@ package docker import ( - "errors" "fmt" + "github.com/dotcloud/docker/networkdriver/ipallocator" "github.com/dotcloud/docker/pkg/iptables" "github.com/dotcloud/docker/pkg/netlink" "github.com/dotcloud/docker/proxy" @@ -24,21 +24,6 @@ const ( siocBRADDBR = 0x89a0 ) -func checkNameserverOverlaps(nameservers []string, dockerNetwork *net.IPNet) error { - if len(nameservers) > 0 { - for _, ns := range nameservers { - _, nsNetwork, err := net.ParseCIDR(ns) - if err != nil { - return err - } - if networkOverlaps(dockerNetwork, nsNetwork) { - return fmt.Errorf("%s overlaps nameserver %s", dockerNetwork, nsNetwork) - } - } - } - return nil -} - // CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`, // and attempts to configure it with an address which doesn't conflict with any other interface on the host. // If it can't find an address which doesn't conflict, it will return an error. @@ -86,17 +71,16 @@ func CreateBridgeIface(config *DaemonConfig) error { if err != nil { return err } - // TODO: @crosbymichael register route - if err := checkRouteOverlaps(routes, dockerNetwork); err == nil { - if err := checkNameserverOverlaps(nameservers, dockerNetwork); err == nil { - ifaceAddr = addr - break - } + + if err := ipallocator.RegisterNetwork(dockerNetwork, nameservers); err == nil { + ifaceAddr = addr + break } else { utils.Debugf("%s: %s", addr, err) } } } + if ifaceAddr == "" { return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", config.BridgeIface, config.BridgeIface) } @@ -367,54 +351,6 @@ func newPortAllocator() (*PortAllocator, error) { return allocator, nil } -// IP allocator: Automatically allocate and release networking ports -type IPAllocator struct { - network *net.IPNet - queueAlloc chan allocatedIP - queueReleased chan net.IP - inUse map[int32]struct{} - quit chan bool -} - -type allocatedIP struct { - ip net.IP - err error -} - -func (alloc *IPAllocator) run() { -} - -func (alloc *IPAllocator) Acquire() (net.IP, error) { - ip := <-alloc.queueAlloc - return ip.ip, ip.err -} - -func (alloc *IPAllocator) Release(ip net.IP) { - alloc.queueReleased <- ip -} - -func (alloc *IPAllocator) Close() error { - alloc.quit <- true - close(alloc.quit) - close(alloc.queueAlloc) - close(alloc.queueReleased) - return nil -} - -func newIPAllocator(network *net.IPNet) *IPAllocator { - alloc := &IPAllocator{ - network: network, - queueAlloc: make(chan allocatedIP), - queueReleased: make(chan net.IP), - inUse: make(map[int32]struct{}), - quit: make(chan bool), - } - - go alloc.run() - - return alloc -} - // Network interface represents the networking stack of a container type NetworkInterface struct { IPNet net.IPNet @@ -519,7 +455,9 @@ func (iface *NetworkInterface) Release() { } } - iface.manager.ipAllocator.Release(iface.IPNet.IP) + if err := ipallocator.ReleaseIP(iface.manager.bridgeNetwork, &iface.IPNet.IP); err != nil { + log.Printf("Unable to release ip %s\n", err) + } } // Network Manager manages a set of network interfaces @@ -528,7 +466,6 @@ type NetworkManager struct { bridgeIface string bridgeNetwork *net.IPNet - ipAllocator *IPAllocator tcpPortAllocator *PortAllocator udpPortAllocator *PortAllocator portMapper *PortMapper @@ -543,27 +480,31 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) { return &NetworkInterface{disabled: true}, nil } - var ip net.IP + var ip *net.IP var err error - ip, err = manager.ipAllocator.Acquire() + ip, err = ipallocator.RequestIP(manager.bridgeNetwork, nil) if err != nil { return nil, err } - // avoid duplicate IP - ipNum := ipToInt(ip) - firstIP := manager.ipAllocator.network.IP.To4().Mask(manager.ipAllocator.network.Mask) - firstIPNum := ipToInt(firstIP) + 1 - if firstIPNum == ipNum { - ip, err = manager.ipAllocator.Acquire() - if err != nil { - return nil, err + // TODO: @crosbymichael why are we doing this ? + /* + // avoid duplicate IP + ipNum := ipToInt(ip) + firstIP := manager.ipAllocator.network.IP.To4().Mask(manager.ipAllocator.network.Mask) + firstIPNum := ipToInt(firstIP) + 1 + + if firstIPNum == ipNum { + ip, err = manager.ipAllocator.Acquire() + if err != nil { + return nil, err + } } - } + */ iface := &NetworkInterface{ - IPNet: net.IPNet{IP: ip, Mask: manager.bridgeNetwork.Mask}, + IPNet: net.IPNet{IP: *ip, Mask: manager.bridgeNetwork.Mask}, Gateway: manager.bridgeNetwork.IP, manager: manager, } @@ -576,14 +517,13 @@ func (manager *NetworkManager) Close() error { } err1 := manager.tcpPortAllocator.Close() err2 := manager.udpPortAllocator.Close() - err3 := manager.ipAllocator.Close() if err1 != nil { return err1 } if err2 != nil { return err2 } - return err3 + return nil } func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { @@ -670,8 +610,6 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { } } - ipAllocator := newIPAllocator(network) - tcpPortAllocator, err := newPortAllocator() if err != nil { return nil, err @@ -690,7 +628,6 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { manager := &NetworkManager{ bridgeIface: config.BridgeIface, bridgeNetwork: network, - ipAllocator: ipAllocator, tcpPortAllocator: tcpPortAllocator, udpPortAllocator: udpPortAllocator, portMapper: portMapper, diff --git a/network_test.go b/network_test.go index 0b6857ba76..0d25ccb158 100644 --- a/network_test.go +++ b/network_test.go @@ -2,9 +2,7 @@ package docker import ( "github.com/dotcloud/docker/pkg/iptables" - "github.com/dotcloud/docker/pkg/netlink" "github.com/dotcloud/docker/proxy" - "net" "testing" ) @@ -53,277 +51,6 @@ func TestPortAllocation(t *testing.T) { } } -func TestNetworkRange(t *testing.T) { - // Simple class C test - _, network, _ := net.ParseCIDR("192.168.0.1/24") - first, last := networkRange(network) - if !first.Equal(net.ParseIP("192.168.0.0")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("192.168.0.255")) { - t.Error(last.String()) - } - if size := networkSize(network.Mask); size != 256 { - t.Error(size) - } - - // Class A test - _, network, _ = net.ParseCIDR("10.0.0.1/8") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("10.0.0.0")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("10.255.255.255")) { - t.Error(last.String()) - } - if size := networkSize(network.Mask); size != 16777216 { - t.Error(size) - } - - // Class A, random IP address - _, network, _ = net.ParseCIDR("10.1.2.3/8") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("10.0.0.0")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("10.255.255.255")) { - t.Error(last.String()) - } - - // 32bit mask - _, network, _ = net.ParseCIDR("10.1.2.3/32") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("10.1.2.3")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("10.1.2.3")) { - t.Error(last.String()) - } - if size := networkSize(network.Mask); size != 1 { - t.Error(size) - } - - // 31bit mask - _, network, _ = net.ParseCIDR("10.1.2.3/31") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("10.1.2.2")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("10.1.2.3")) { - t.Error(last.String()) - } - if size := networkSize(network.Mask); size != 2 { - t.Error(size) - } - - // 26bit mask - _, network, _ = net.ParseCIDR("10.1.2.3/26") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("10.1.2.0")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("10.1.2.63")) { - t.Error(last.String()) - } - if size := networkSize(network.Mask); size != 64 { - t.Error(size) - } -} - -func TestConversion(t *testing.T) { - ip := net.ParseIP("127.0.0.1") - i := ipToInt(ip) - if i == 0 { - t.Fatal("converted to zero") - } - conv := intToIP(i) - if !ip.Equal(conv) { - t.Error(conv.String()) - } -} - -func TestIPAllocator(t *testing.T) { - expectedIPs := []net.IP{ - 0: net.IPv4(127, 0, 0, 2), - 1: net.IPv4(127, 0, 0, 3), - 2: net.IPv4(127, 0, 0, 4), - 3: net.IPv4(127, 0, 0, 5), - 4: net.IPv4(127, 0, 0, 6), - } - - gwIP, n, _ := net.ParseCIDR("127.0.0.1/29") - alloc := newIPAllocator(&net.IPNet{IP: gwIP, Mask: n.Mask}) - // Pool after initialisation (f = free, u = used) - // 2(f) - 3(f) - 4(f) - 5(f) - 6(f) - // ↑ - - // Check that we get 5 IPs, from 127.0.0.2–127.0.0.6, in that - // order. - for i := 0; i < 5; i++ { - ip, err := alloc.Acquire() - if err != nil { - t.Fatal(err) - } - - assertIPEquals(t, expectedIPs[i], ip) - } - // Before loop begin - // 2(f) - 3(f) - 4(f) - 5(f) - 6(f) - // ↑ - - // After i = 0 - // 2(u) - 3(f) - 4(f) - 5(f) - 6(f) - // ↑ - - // After i = 1 - // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) - // ↑ - - // After i = 2 - // 2(u) - 3(u) - 4(u) - 5(f) - 6(f) - // ↑ - - // After i = 3 - // 2(u) - 3(u) - 4(u) - 5(u) - 6(f) - // ↑ - - // After i = 4 - // 2(u) - 3(u) - 4(u) - 5(u) - 6(u) - // ↑ - - // Check that there are no more IPs - _, err := alloc.Acquire() - if err == nil { - t.Fatal("There shouldn't be any IP addresses at this point") - } - - // Release some IPs in non-sequential order - alloc.Release(expectedIPs[3]) - // 2(u) - 3(u) - 4(u) - 5(f) - 6(u) - // ↑ - - alloc.Release(expectedIPs[2]) - // 2(u) - 3(u) - 4(f) - 5(f) - 6(u) - // ↑ - - alloc.Release(expectedIPs[4]) - // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) - // ↑ - - // Make sure that IPs are reused in sequential order, starting - // with the first released IP - newIPs := make([]net.IP, 3) - for i := 0; i < 3; i++ { - ip, err := alloc.Acquire() - if err != nil { - t.Fatal(err) - } - - newIPs[i] = ip - } - // Before loop begin - // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) - // ↑ - - // After i = 0 - // 2(u) - 3(u) - 4(f) - 5(u) - 6(f) - // ↑ - - // After i = 1 - // 2(u) - 3(u) - 4(f) - 5(u) - 6(u) - // ↑ - - // After i = 2 - // 2(u) - 3(u) - 4(u) - 5(u) - 6(u) - // ↑ - - assertIPEquals(t, expectedIPs[3], newIPs[0]) - assertIPEquals(t, expectedIPs[4], newIPs[1]) - assertIPEquals(t, expectedIPs[2], newIPs[2]) - - _, err = alloc.Acquire() - if err == nil { - t.Fatal("There shouldn't be any IP addresses at this point") - } -} - -func assertIPEquals(t *testing.T, ip1, ip2 net.IP) { - if !ip1.Equal(ip2) { - t.Fatalf("Expected IP %s, got %s", ip1, ip2) - } -} - -func AssertOverlap(CIDRx string, CIDRy string, t *testing.T) { - _, netX, _ := net.ParseCIDR(CIDRx) - _, netY, _ := net.ParseCIDR(CIDRy) - if !networkOverlaps(netX, netY) { - t.Errorf("%v and %v should overlap", netX, netY) - } -} - -func AssertNoOverlap(CIDRx string, CIDRy string, t *testing.T) { - _, netX, _ := net.ParseCIDR(CIDRx) - _, netY, _ := net.ParseCIDR(CIDRy) - if networkOverlaps(netX, netY) { - t.Errorf("%v and %v should not overlap", netX, netY) - } -} - -func TestNetworkOverlaps(t *testing.T) { - //netY starts at same IP and ends within netX - AssertOverlap("172.16.0.1/24", "172.16.0.1/25", t) - //netY starts within netX and ends at same IP - AssertOverlap("172.16.0.1/24", "172.16.0.128/25", t) - //netY starts and ends within netX - AssertOverlap("172.16.0.1/24", "172.16.0.64/25", t) - //netY starts at same IP and ends outside of netX - AssertOverlap("172.16.0.1/24", "172.16.0.1/23", t) - //netY starts before and ends at same IP of netX - AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t) - //netY starts before and ends outside of netX - AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t) - //netY starts and ends before netX - AssertNoOverlap("172.16.1.1/25", "172.16.0.1/24", t) - //netX starts and ends before netY - AssertNoOverlap("172.16.1.1/25", "172.16.2.1/24", t) -} - -func TestCheckRouteOverlaps(t *testing.T) { - routesData := []string{"10.0.2.0/32", "10.0.3.0/24", "10.0.42.0/24", "172.16.42.0/24", "192.168.142.0/24"} - - routes := []netlink.Route{} - for _, addr := range routesData { - _, netX, _ := net.ParseCIDR(addr) - routes = append(routes, netlink.Route{IPNet: netX}) - } - - _, netX, _ := net.ParseCIDR("172.16.0.1/24") - if err := checkRouteOverlaps(routes, netX); err != nil { - t.Fatal(err) - } - - _, netX, _ = net.ParseCIDR("10.0.2.0/24") - if err := checkRouteOverlaps(routes, netX); err == nil { - t.Fatalf("10.0.2.0/24 and 10.0.2.0 should overlap but it doesn't") - } -} - -func TestCheckNameserverOverlaps(t *testing.T) { - nameservers := []string{"10.0.2.3/32", "192.168.102.1/32"} - - _, netX, _ := net.ParseCIDR("10.0.2.3/32") - - if err := checkNameserverOverlaps(nameservers, netX); err == nil { - t.Fatalf("%s should overlap 10.0.2.3/32 but doesn't", netX) - } - - _, netX, _ = net.ParseCIDR("192.168.102.2/32") - - if err := checkNameserverOverlaps(nameservers, netX); err != nil { - t.Fatalf("%s should not overlap %v but it does", netX, nameservers) - } -} - type StubProxy struct { frontendAddr *net.Addr backendAddr *net.Addr diff --git a/networkdriver/ipallocator/allocator.go b/networkdriver/ipallocator/allocator.go index 5b6befd1da..e6d39446df 100644 --- a/networkdriver/ipallocator/allocator.go +++ b/networkdriver/ipallocator/allocator.go @@ -16,20 +16,25 @@ type iPNet struct { } var ( - ErrNetworkAlreadyAllocated = errors.New("requested network overlaps with existing network") - ErrNetworkAlreadyRegisterd = errors.New("requested network is already registered") - ErrNoAvailableIps = errors.New("no available ips on network") - ErrIPAlreadyAllocated = errors.New("ip already allocated") + ErrNetworkAlreadyAllocated = errors.New("requested network overlaps with existing network") + ErrNetworkAlreadyRegisterd = errors.New("requested network is already registered") + ErrNetworkOverlapsWithNameservers = errors.New("requested network overlaps with nameserver") + ErrNoAvailableIps = errors.New("no available ips on network") + ErrIPAlreadyAllocated = errors.New("ip already allocated") lock = sync.Mutex{} allocatedIPs = networkSet{} availableIPS = networkSet{} ) -func RegisterNetwork(network *net.IPNet) error { +func RegisterNetwork(network *net.IPNet, nameservers []string) error { lock.Lock() defer lock.Unlock() + if err := checkExistingNetworkOverlaps(network); err != nil { + return err + } + routes, err := netlink.NetworkGetRoutes() if err != nil { return err @@ -39,9 +44,10 @@ func RegisterNetwork(network *net.IPNet) error { return err } - if err := checkExistingNetworkOverlaps(network); err != nil { + if err := checkNameserverOverlaps(nameservers, network); err != nil { return err } + n := newIPNet(network) allocatedIPs[n] = &iPSet{} @@ -227,3 +233,18 @@ func networkSize(mask net.IPMask) int32 { return int32(binary.BigEndian.Uint32(m)) + 1 } + +func checkNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error { + if len(nameservers) > 0 { + for _, ns := range nameservers { + _, nsNetwork, err := net.ParseCIDR(ns) + if err != nil { + return err + } + if networkOverlaps(toCheck, nsNetwork) { + return ErrNetworkOverlapsWithNameservers + } + } + } + return nil +} diff --git a/networkdriver/ipallocator/allocator_test.go b/networkdriver/ipallocator/allocator_test.go index bcdcfa66b8..d1a58a021d 100644 --- a/networkdriver/ipallocator/allocator_test.go +++ b/networkdriver/ipallocator/allocator_test.go @@ -2,6 +2,7 @@ package ipallocator import ( "fmt" + "github.com/dotcloud/docker/pkg/netlink" "net" "testing" ) @@ -18,7 +19,7 @@ func TestRegisterNetwork(t *testing.T) { Mask: []byte{255, 255, 255, 0}, } - if err := RegisterNetwork(network); err != nil { + if err := RegisterNetwork(network, nil); err != nil { t.Fatal(err) } @@ -39,7 +40,7 @@ func TestRegisterTwoNetworks(t *testing.T) { Mask: []byte{255, 255, 255, 0}, } - if err := RegisterNetwork(network); err != nil { + if err := RegisterNetwork(network, nil); err != nil { t.Fatal(err) } @@ -48,7 +49,7 @@ func TestRegisterTwoNetworks(t *testing.T) { Mask: []byte{255, 255, 255, 0}, } - if err := RegisterNetwork(network2); err != nil { + if err := RegisterNetwork(network2, nil); err != nil { t.Fatal(err) } } @@ -60,11 +61,11 @@ func TestRegisterNetworkThatExists(t *testing.T) { Mask: []byte{255, 255, 255, 0}, } - if err := RegisterNetwork(network); err != nil { + if err := RegisterNetwork(network, nil); err != nil { t.Fatal(err) } - if err := RegisterNetwork(network); err != ErrNetworkAlreadyRegisterd { + if err := RegisterNetwork(network, nil); err != ErrNetworkAlreadyRegisterd { t.Fatalf("Expected error of %s got %s", ErrNetworkAlreadyRegisterd, err) } } @@ -76,7 +77,7 @@ func TestRequestNewIps(t *testing.T) { Mask: []byte{255, 255, 255, 0}, } - if err := RegisterNetwork(network); err != nil { + if err := RegisterNetwork(network, nil); err != nil { t.Fatal(err) } @@ -99,7 +100,7 @@ func TestReleaseIp(t *testing.T) { Mask: []byte{255, 255, 255, 0}, } - if err := RegisterNetwork(network); err != nil { + if err := RegisterNetwork(network, nil); err != nil { t.Fatal(err) } @@ -120,7 +121,7 @@ func TestGetReleasedIp(t *testing.T) { Mask: []byte{255, 255, 255, 0}, } - if err := RegisterNetwork(network); err != nil { + if err := RegisterNetwork(network, nil); err != nil { t.Fatal(err) } @@ -151,7 +152,7 @@ func TestRequesetSpecificIp(t *testing.T) { Mask: []byte{255, 255, 255, 0}, } - if err := RegisterNetwork(network); err != nil { + if err := RegisterNetwork(network, nil); err != nil { t.Fatal(err) } @@ -161,3 +162,313 @@ func TestRequesetSpecificIp(t *testing.T) { t.Fatal(err) } } + +func TestNonOverlapingNameservers(t *testing.T) { + defer reset() + network := &net.IPNet{ + IP: []byte{192, 168, 0, 1}, + Mask: []byte{255, 255, 255, 0}, + } + nameservers := []string{ + "127.0.0.1/32", + } + + if err := RegisterNetwork(network, nameservers); err != nil { + t.Fatal(err) + } +} + +func TestOverlapingNameservers(t *testing.T) { + defer reset() + network := &net.IPNet{ + IP: []byte{192, 168, 0, 1}, + Mask: []byte{255, 255, 255, 0}, + } + nameservers := []string{ + "192.168.0.1/32", + } + + if err := RegisterNetwork(network, nameservers); err != ErrNetworkOverlapsWithNameservers { + t.Fatalf("Expectecd error of %s got %s", ErrNetworkOverlapsWithNameservers, err) + } +} + +func TestNetworkRange(t *testing.T) { + // Simple class C test + _, network, _ := net.ParseCIDR("192.168.0.1/24") + first, last := networkRange(network) + if !first.Equal(net.ParseIP("192.168.0.0")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("192.168.0.255")) { + t.Error(last.String()) + } + if size := networkSize(network.Mask); size != 256 { + t.Error(size) + } + + // Class A test + _, network, _ = net.ParseCIDR("10.0.0.1/8") + first, last = networkRange(network) + if !first.Equal(net.ParseIP("10.0.0.0")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("10.255.255.255")) { + t.Error(last.String()) + } + if size := networkSize(network.Mask); size != 16777216 { + t.Error(size) + } + + // Class A, random IP address + _, network, _ = net.ParseCIDR("10.1.2.3/8") + first, last = networkRange(network) + if !first.Equal(net.ParseIP("10.0.0.0")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("10.255.255.255")) { + t.Error(last.String()) + } + + // 32bit mask + _, network, _ = net.ParseCIDR("10.1.2.3/32") + first, last = networkRange(network) + if !first.Equal(net.ParseIP("10.1.2.3")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("10.1.2.3")) { + t.Error(last.String()) + } + if size := networkSize(network.Mask); size != 1 { + t.Error(size) + } + + // 31bit mask + _, network, _ = net.ParseCIDR("10.1.2.3/31") + first, last = networkRange(network) + if !first.Equal(net.ParseIP("10.1.2.2")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("10.1.2.3")) { + t.Error(last.String()) + } + if size := networkSize(network.Mask); size != 2 { + t.Error(size) + } + + // 26bit mask + _, network, _ = net.ParseCIDR("10.1.2.3/26") + first, last = networkRange(network) + if !first.Equal(net.ParseIP("10.1.2.0")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("10.1.2.63")) { + t.Error(last.String()) + } + if size := networkSize(network.Mask); size != 64 { + t.Error(size) + } +} + +func TestConversion(t *testing.T) { + ip := net.ParseIP("127.0.0.1") + i := ipToInt(&ip) + if i == 0 { + t.Fatal("converted to zero") + } + conv := intToIP(i) + if !ip.Equal(*conv) { + t.Error(conv.String()) + } +} + +func TestIPAllocator(t *testing.T) { + expectedIPs := []net.IP{ + 0: net.IPv4(127, 0, 0, 2), + 1: net.IPv4(127, 0, 0, 3), + 2: net.IPv4(127, 0, 0, 4), + 3: net.IPv4(127, 0, 0, 5), + 4: net.IPv4(127, 0, 0, 6), + } + + gwIP, n, _ := net.ParseCIDR("127.0.0.1/29") + network := &net.IPNet{IP: gwIP, Mask: n.Mask} + if err := RegisterNetwork(network, nil); err != nil { + t.Fatal(err) + } + // Pool after initialisation (f = free, u = used) + // 2(f) - 3(f) - 4(f) - 5(f) - 6(f) + // ↑ + + // Check that we get 5 IPs, from 127.0.0.2–127.0.0.6, in that + // order. + for i := 0; i < 5; i++ { + ip, err := RequestIP(network, nil) + if err != nil { + t.Fatal(err) + } + + assertIPEquals(t, &expectedIPs[i], ip) + } + // Before loop begin + // 2(f) - 3(f) - 4(f) - 5(f) - 6(f) + // ↑ + + // After i = 0 + // 2(u) - 3(f) - 4(f) - 5(f) - 6(f) + // ↑ + + // After i = 1 + // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) + // ↑ + + // After i = 2 + // 2(u) - 3(u) - 4(u) - 5(f) - 6(f) + // ↑ + + // After i = 3 + // 2(u) - 3(u) - 4(u) - 5(u) - 6(f) + // ↑ + + // After i = 4 + // 2(u) - 3(u) - 4(u) - 5(u) - 6(u) + // ↑ + + // Check that there are no more IPs + ip, err := RequestIP(network, nil) + if err == nil { + t.Fatalf("There shouldn't be any IP addresses at this point, got %s\n", ip) + } + + // Release some IPs in non-sequential order + if err := ReleaseIP(network, &expectedIPs[3]); err != nil { + t.Fatal(err) + } + // 2(u) - 3(u) - 4(u) - 5(f) - 6(u) + // ↑ + + if err := ReleaseIP(network, &expectedIPs[2]); err != nil { + t.Fatal(err) + } + // 2(u) - 3(u) - 4(f) - 5(f) - 6(u) + // ↑ + + if err := ReleaseIP(network, &expectedIPs[4]); err != nil { + t.Fatal(err) + } + // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) + // ↑ + + // Make sure that IPs are reused in sequential order, starting + // with the first released IP + newIPs := make([]*net.IP, 3) + for i := 0; i < 3; i++ { + ip, err := RequestIP(network, nil) + if err != nil { + t.Fatal(err) + } + + newIPs[i] = ip + } + // Before loop begin + // 2(u) - 3(u) - 4(f) - 5(f) - 6(f) + // ↑ + + // After i = 0 + // 2(u) - 3(u) - 4(f) - 5(u) - 6(f) + // ↑ + + // After i = 1 + // 2(u) - 3(u) - 4(f) - 5(u) - 6(u) + // ↑ + + // After i = 2 + // 2(u) - 3(u) - 4(u) - 5(u) - 6(u) + // ↑ + + assertIPEquals(t, &expectedIPs[3], newIPs[0]) + assertIPEquals(t, &expectedIPs[4], newIPs[1]) + assertIPEquals(t, &expectedIPs[2], newIPs[2]) + + _, err = RequestIP(network, nil) + if err == nil { + t.Fatal("There shouldn't be any IP addresses at this point") + } +} + +func assertIPEquals(t *testing.T, ip1, ip2 *net.IP) { + if !ip1.Equal(*ip2) { + t.Fatalf("Expected IP %s, got %s", ip1, ip2) + } +} + +func AssertOverlap(CIDRx string, CIDRy string, t *testing.T) { + _, netX, _ := net.ParseCIDR(CIDRx) + _, netY, _ := net.ParseCIDR(CIDRy) + if !networkOverlaps(netX, netY) { + t.Errorf("%v and %v should overlap", netX, netY) + } +} + +func AssertNoOverlap(CIDRx string, CIDRy string, t *testing.T) { + _, netX, _ := net.ParseCIDR(CIDRx) + _, netY, _ := net.ParseCIDR(CIDRy) + if networkOverlaps(netX, netY) { + t.Errorf("%v and %v should not overlap", netX, netY) + } +} + +func TestNetworkOverlaps(t *testing.T) { + //netY starts at same IP and ends within netX + AssertOverlap("172.16.0.1/24", "172.16.0.1/25", t) + //netY starts within netX and ends at same IP + AssertOverlap("172.16.0.1/24", "172.16.0.128/25", t) + //netY starts and ends within netX + AssertOverlap("172.16.0.1/24", "172.16.0.64/25", t) + //netY starts at same IP and ends outside of netX + AssertOverlap("172.16.0.1/24", "172.16.0.1/23", t) + //netY starts before and ends at same IP of netX + AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t) + //netY starts before and ends outside of netX + AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t) + //netY starts and ends before netX + AssertNoOverlap("172.16.1.1/25", "172.16.0.1/24", t) + //netX starts and ends before netY + AssertNoOverlap("172.16.1.1/25", "172.16.2.1/24", t) +} + +func TestCheckRouteOverlaps(t *testing.T) { + routesData := []string{"10.0.2.0/32", "10.0.3.0/24", "10.0.42.0/24", "172.16.42.0/24", "192.168.142.0/24"} + + routes := []netlink.Route{} + for _, addr := range routesData { + _, netX, _ := net.ParseCIDR(addr) + routes = append(routes, netlink.Route{IPNet: netX}) + } + + _, netX, _ := net.ParseCIDR("172.16.0.1/24") + if err := checkRouteOverlaps(routes, netX); err != nil { + t.Fatal(err) + } + + _, netX, _ = net.ParseCIDR("10.0.2.0/24") + if err := checkRouteOverlaps(routes, netX); err == nil { + t.Fatalf("10.0.2.0/24 and 10.0.2.0 should overlap but it doesn't") + } +} + +func TestCheckNameserverOverlaps(t *testing.T) { + nameservers := []string{"10.0.2.3/32", "192.168.102.1/32"} + + _, netX, _ := net.ParseCIDR("10.0.2.3/32") + + if err := checkNameserverOverlaps(nameservers, netX); err == nil { + t.Fatalf("%s should overlap 10.0.2.3/32 but doesn't", netX) + } + + _, netX, _ = net.ParseCIDR("192.168.102.2/32") + + if err := checkNameserverOverlaps(nameservers, netX); err != nil { + t.Fatalf("%s should not overlap %v but it does", netX, nameservers) + } +} diff --git a/networkdriver/network.go b/networkdriver/network.go new file mode 100644 index 0000000000..e74734d55f --- /dev/null +++ b/networkdriver/network.go @@ -0,0 +1 @@ +package networkdriver From 9d11db0f8c2be614f18eec856f11edbff5da17fe Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 23 Jan 2014 02:22:13 -0800 Subject: [PATCH 205/364] Update code for new test cases Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- networkdriver/ipallocator/allocator.go | 35 +++++++++------------ networkdriver/ipallocator/allocator_test.go | 6 ++-- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/networkdriver/ipallocator/allocator.go b/networkdriver/ipallocator/allocator.go index e6d39446df..8b7c986461 100644 --- a/networkdriver/ipallocator/allocator.go +++ b/networkdriver/ipallocator/allocator.go @@ -19,7 +19,7 @@ var ( ErrNetworkAlreadyAllocated = errors.New("requested network overlaps with existing network") ErrNetworkAlreadyRegisterd = errors.New("requested network is already registered") ErrNetworkOverlapsWithNameservers = errors.New("requested network overlaps with nameserver") - ErrNoAvailableIps = errors.New("no available ips on network") + ErrNoAvailableIPs = errors.New("no available ip addresses on network") ErrIPAlreadyAllocated = errors.New("ip already allocated") lock = sync.Mutex{} @@ -100,11 +100,11 @@ func getNextIp(network *net.IPNet) (*net.IP, error) { ownIP = ipToInt(&network.IP) available = availableIPS[n] allocated = allocatedIPs[n] - - first, _ = networkRange(network) - base = ipToInt(&first) - - pos = int32(available.Pop()) + first, _ = networkRange(network) + base = ipToInt(&first) + size = int(networkSize(network.Mask)) + max = int32(size - 2) // size -1 for the broadcast address, -1 for the gateway address + pos = int32(available.Pop()) ) // We pop and push the position not the ip @@ -115,29 +115,22 @@ func getNextIp(network *net.IPNet) (*net.IP, error) { return ip, nil } - var ( - size = int(networkSize(network.Mask)) - max = int32(size - 2) // size -1 for the broadcast address, -1 for the gateway address - ) - - if pos = int32(allocated.PullBack()); pos == 0 { - pos = 1 - } - + pos = int32(allocated.PullBack()) for i := int32(0); i < max; i++ { - next := int32(base + pos) pos = pos%max + 1 + next := int32(base + pos) if next == ownIP { continue } - ip := intToIP(next) - allocated.Push(int(pos)) - - return ip, nil + if !allocated.Exists(int(pos)) { + ip := intToIP(next) + allocated.Push(int(pos)) + return ip, nil + } } - return nil, ErrNoAvailableIps + return nil, ErrNoAvailableIPs } func registerIP(network *net.IPNet, ip *net.IP) error { diff --git a/networkdriver/ipallocator/allocator_test.go b/networkdriver/ipallocator/allocator_test.go index d1a58a021d..2e2d463638 100644 --- a/networkdriver/ipallocator/allocator_test.go +++ b/networkdriver/ipallocator/allocator_test.go @@ -386,9 +386,9 @@ func TestIPAllocator(t *testing.T) { // 2(u) - 3(u) - 4(u) - 5(u) - 6(u) // ↑ - assertIPEquals(t, &expectedIPs[3], newIPs[0]) - assertIPEquals(t, &expectedIPs[4], newIPs[1]) - assertIPEquals(t, &expectedIPs[2], newIPs[2]) + assertIPEquals(t, &expectedIPs[2], newIPs[0]) + assertIPEquals(t, &expectedIPs[3], newIPs[1]) + assertIPEquals(t, &expectedIPs[4], newIPs[2]) _, err = RequestIP(network, nil) if err == nil { From d419da7227826e84e9375ece4fd9d4978a42cbf7 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Fri, 17 Jan 2014 08:46:11 -0500 Subject: [PATCH 206/364] Added new mkimage-yum.sh script to create CentOS base images mkimage-rinse.sh requires rinse, which is not readily available on CentOS or Fedora. Plus, creating a base image is trivial with yum alone. Docker-DCO-1.1-Signed-off-by: Chris St. Pierre (github: stpierre) --- contrib/mkimage-rinse.sh | 7 +++ contrib/mkimage-yum.sh | 90 ++++++++++++++++++++++++++++ docs/sources/articles/baseimages.rst | 5 +- 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100755 contrib/mkimage-yum.sh diff --git a/contrib/mkimage-rinse.sh b/contrib/mkimage-rinse.sh index de9265d48c..dfe9999d92 100755 --- a/contrib/mkimage-rinse.sh +++ b/contrib/mkimage-rinse.sh @@ -1,4 +1,11 @@ #!/usr/bin/env bash +# +# Create a base CentOS Docker image. + +# This script is useful on systems with rinse available (e.g., +# building a CentOS image on Debian). See contrib/mkimage-yum.sh for +# a way to build CentOS images on systems with yum installed. + set -e repo="$1" diff --git a/contrib/mkimage-yum.sh b/contrib/mkimage-yum.sh new file mode 100755 index 0000000000..594eb96fec --- /dev/null +++ b/contrib/mkimage-yum.sh @@ -0,0 +1,90 @@ +#!/bin/bash +# +# Create a base CentOS Docker image. +# +# This script is useful on systems with yum installed (e.g., building +# a CentOS image on CentOS). See contrib/mkimage-rinse.sh for a way +# to build CentOS images on other systems. + +usage() { + cat < +OPTIONS: + -y The path to the yum config to install packages from. The + default is /etc/yum.conf. +EOOPTS + exit 1 +} + +# option defaults +yum_config=/etc/yum.conf +while getopts ":y:h" opt; do + case $opt in + y) + yum_config=$OPTARG + ;; + h) + usage + ;; + \?) + echo "Invalid option: -$OPTARG" + usage + ;; + esac +done +shift $((OPTIND - 1)) +name=$1 + +if [[ -z $name ]]; then + usage +fi + +#-------------------- + +target=$(mktemp -d --tmpdir $(basename $0).XXXXXX) + +set -x + +for dev in console null zero urandom; do + /sbin/MAKEDEV -d "$target"/dev -x $dev +done + +yum -c "$yum_config" --installroot="$target" --setopt=tsflags=nodocs \ + --setopt=group_package_types=mandatory -y groupinstall Core +yum -c "$yum_config" --installroot="$mount" -y clean all + +cat > "$target"/etc/sysconfig/network <&2 "warning: cannot autodetect OS version, using '$name' as tag" + version=$name +fi + +tar --numeric-owner -c -C "$target" . | docker import - $name:$version +docker run -i -t $name:$version echo success + +rm -rf "$target" diff --git a/docs/sources/articles/baseimages.rst b/docs/sources/articles/baseimages.rst index 51a51e2f93..0523434e0b 100644 --- a/docs/sources/articles/baseimages.rst +++ b/docs/sources/articles/baseimages.rst @@ -37,7 +37,10 @@ There are more example scripts for creating base images in the Docker Github Repo: * `BusyBox `_ -* `CentOS / Scientific Linux CERN (SLC) +* CentOS / Scientific Linux CERN (SLC) `on Debian/Ubuntu `_ + or + `on CentOS/RHEL/SLC/etc. + `_ * `Debian / Ubuntu `_ From ea5b3e193ba40b65c38c774e9abb85340c52e8c6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 23 Jan 2014 05:22:32 -0800 Subject: [PATCH 207/364] Finalize core changes with new package Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- network.go | 13 +++- networkdriver/ipallocator/allocator.go | 68 ++++++++++++++++++--- networkdriver/ipallocator/allocator_test.go | 2 + 3 files changed, 72 insertions(+), 11 deletions(-) diff --git a/network.go b/network.go index 4987140402..7414b348a2 100644 --- a/network.go +++ b/network.go @@ -60,10 +60,13 @@ func CreateBridgeIface(config *DaemonConfig) error { var ifaceAddr string if len(config.BridgeIp) != 0 { - _, _, err := net.ParseCIDR(config.BridgeIp) + _, dockerNetwork, err := net.ParseCIDR(config.BridgeIp) if err != nil { return err } + if err := ipallocator.RegisterNetwork(dockerNetwork, nameservers); err != nil { + return err + } ifaceAddr = config.BridgeIp } else { for _, addr := range addrs { @@ -534,6 +537,7 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { return manager, nil } + var network *net.IPNet addr, err := getIfaceAddr(config.BridgeIface) if err != nil { // If the iface is not found, try to create it @@ -544,8 +548,13 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { if err != nil { return nil, err } + network = addr.(*net.IPNet) + } else { + network = addr.(*net.IPNet) + if err := ipallocator.RegisterExistingNetwork(network); err != nil { + return nil, err + } } - network := addr.(*net.IPNet) // Configure iptables for link support if config.EnableIptables { diff --git a/networkdriver/ipallocator/allocator.go b/networkdriver/ipallocator/allocator.go index 8b7c986461..cca8cdb05a 100644 --- a/networkdriver/ipallocator/allocator.go +++ b/networkdriver/ipallocator/allocator.go @@ -21,12 +21,16 @@ var ( ErrNetworkOverlapsWithNameservers = errors.New("requested network overlaps with nameserver") ErrNoAvailableIPs = errors.New("no available ip addresses on network") ErrIPAlreadyAllocated = errors.New("ip already allocated") + ErrNetworkNotRegistered = errors.New("network not registered") lock = sync.Mutex{} allocatedIPs = networkSet{} availableIPS = networkSet{} ) +// RegisterNetwork registers a new network with the allocator +// and validates that it contains a valid ip that does not overlap +// with existing routes and nameservers func RegisterNetwork(network *net.IPNet, nameservers []string) error { lock.Lock() defer lock.Unlock() @@ -47,19 +51,36 @@ func RegisterNetwork(network *net.IPNet, nameservers []string) error { if err := checkNameserverOverlaps(nameservers, network); err != nil { return err } + return RegisterExistingNetwork(network) +} +// RegisterExistingNetwork registers an exising network created +// for use with the allocator but does not perform any validation +func RegisterExistingNetwork(network *net.IPNet) error { n := newIPNet(network) - allocatedIPs[n] = &iPSet{} - availableIPS[n] = &iPSet{} + if _, exists := allocatedIPs[n]; !exists { + allocatedIPs[n] = &iPSet{} + } + if _, exists := availableIPS[n]; !exists { + availableIPS[n] = &iPSet{} + } return nil } +// RequestIP requests an available ip from the given network. It +// will return the next available ip if the ip provided is nil. If the +// ip provided is not nil it will validate that the provided ip is available +// for use or return an error func RequestIP(network *net.IPNet, ip *net.IP) (*net.IP, error) { lock.Lock() defer lock.Unlock() + if !networkExists(network) { + return nil, ErrNetworkNotRegistered + } + if ip == nil { next, err := getNextIp(network) if err != nil { @@ -74,18 +95,21 @@ func RequestIP(network *net.IPNet, ip *net.IP) (*net.IP, error) { return ip, nil } +// ReleaseIP adds the provided ip back into the pool of +// available ips to be returned for use. func ReleaseIP(network *net.IPNet, ip *net.IP) error { lock.Lock() defer lock.Unlock() + if !networkExists(network) { + return ErrNetworkNotRegistered + } + var ( - first, _ = networkRange(network) - base = ipToInt(&first) n = newIPNet(network) existing = allocatedIPs[n] available = availableIPS[n] - i = ipToInt(ip) - pos = i - base + pos = getPosition(network, ip) ) existing.Remove(int(pos)) @@ -94,6 +118,19 @@ func ReleaseIP(network *net.IPNet, ip *net.IP) error { return nil } +// convert the ip into the position in the subnet. Only +// position are saved in the set +func getPosition(network *net.IPNet, ip *net.IP) int32 { + var ( + first, _ = networkRange(network) + base = ipToInt(&first) + i = ipToInt(ip) + ) + return i - base +} + +// return an available ip if one is currently available. If not, +// return the next available ip for the nextwork func getNextIp(network *net.IPNet) (*net.IP, error) { var ( n = newIPNet(network) @@ -134,11 +171,18 @@ func getNextIp(network *net.IPNet) (*net.IP, error) { } func registerIP(network *net.IPNet, ip *net.IP) error { - existing := allocatedIPs[newIPNet(network)] - // checking position not ip - if existing.Exists(int(ipToInt(ip))) { + var ( + n = newIPNet(network) + existing = allocatedIPs[n] + available = availableIPS[n] + pos = getPosition(network, ip) + ) + + if existing.Exists(int(pos)) { return ErrIPAlreadyAllocated } + available.Remove(int(pos)) + return nil } @@ -241,3 +285,9 @@ func checkNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error { } return nil } + +func networkExists(network *net.IPNet) bool { + n := newIPNet(network) + _, exists := allocatedIPs[n] + return exists +} diff --git a/networkdriver/ipallocator/allocator_test.go b/networkdriver/ipallocator/allocator_test.go index 2e2d463638..f574dfda70 100644 --- a/networkdriver/ipallocator/allocator_test.go +++ b/networkdriver/ipallocator/allocator_test.go @@ -386,6 +386,8 @@ func TestIPAllocator(t *testing.T) { // 2(u) - 3(u) - 4(u) - 5(u) - 6(u) // ↑ + // Reordered these because the new set will always return the + // lowest ips first and not in the order that they were released assertIPEquals(t, &expectedIPs[2], newIPs[0]) assertIPEquals(t, &expectedIPs[3], newIPs[1]) assertIPEquals(t, &expectedIPs[4], newIPs[2]) From fd2403b8303f7ea9152dd08021d2c9c7070aefb4 Mon Sep 17 00:00:00 2001 From: "Chris St. Pierre" Date: Thu, 23 Jan 2014 12:01:21 -0500 Subject: [PATCH 208/364] fix shebang in mkimage-yum.sh Docker-DCO-1.1-Signed-off-by: Chris St. Pierre (github: stpierre) --- contrib/mkimage-yum.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/mkimage-yum.sh b/contrib/mkimage-yum.sh index 594eb96fec..54e99f1f04 100755 --- a/contrib/mkimage-yum.sh +++ b/contrib/mkimage-yum.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # # Create a base CentOS Docker image. # From 4d4339c6deb44eb5aee7578a0efd185f349ef68e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 23 Jan 2014 10:50:11 -0800 Subject: [PATCH 209/364] update REMOTE_TODO.md Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- REMOTE_TODO.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/REMOTE_TODO.md b/REMOTE_TODO.md index 8a16f0ae0a..2c0cf70b6f 100644 --- a/REMOTE_TODO.md +++ b/REMOTE_TODO.md @@ -10,8 +10,8 @@ ok "/images/search": getImagesSearch, N ok "/images/{name:.*}/get": getImagesGet, 0 ok "/images/{name:.*}/history": getImagesHistory, N #3621 "/images/{name:.*}/json": getImagesByName, 1 -TODO "/containers/ps": getContainersJSON, N -TODO "/containers/json": getContainersJSON, 1 +#3728 "/containers/ps": getContainersJSON, N +#3728 "/containers/json": getContainersJSON, 1 ok "/containers/{name:.*}/export": getContainersExport, 0 ok "/containers/{name:.*}/changes": getContainersChanges, N #3621 "/containers/{name:.*}/json": getContainersByName, 1 @@ -22,10 +22,10 @@ ok "/containers/{name:.*}/attach/ws": wsContainersAttach, 0 TODO "/auth": postAuth, 0 yes ok "/commit": postCommit, 0 TODO "/build": postBuild, 0 yes -TODO "/images/create": postImagesCreate, N yes yes (pull) +#3725 "/images/create": postImagesCreate, N yes yes (pull) ok "/images/{name:.*}/insert": postImagesInsert, N yes yes ok "/images/load": postImagesLoad, 1 yes (stdin) -TODO "/images/{name:.*}/push": postImagesPush, N yes +#3727 "/images/{name:.*}/push": postImagesPush, N yes ok "/images/{name:.*}/tag": postImagesTag, 0 ok "/containers/create": postContainersCreate, 0 ok "/containers/{name:.*}/kill": postContainersKill, 0 From f60eee4894cfa07e8a969425c72d291f80f3053a Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 23 Jan 2014 13:00:23 -0700 Subject: [PATCH 210/364] Fix mflag test issue with "ResetForTesting" (which only showed up under dyntest for some odd reason) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- pkg/mflag/export_test.go | 17 ----------------- pkg/mflag/flag_test.go | 7 +++++++ 2 files changed, 7 insertions(+), 17 deletions(-) delete mode 100644 pkg/mflag/export_test.go diff --git a/pkg/mflag/export_test.go b/pkg/mflag/export_test.go deleted file mode 100644 index 7c1cea0bd7..0000000000 --- a/pkg/mflag/export_test.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2014 The Docker & Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package mflag - -import "os" - -// Additional routines compiled into the package only during testing. - -// ResetForTesting clears all flag state and sets the usage function as directed. -// After calling ResetForTesting, parse errors in flag handling will not -// exit the program. -func ResetForTesting(usage func()) { - CommandLine = NewFlagSet(os.Args[0], ContinueOnError) - Usage = usage -} diff --git a/pkg/mflag/flag_test.go b/pkg/mflag/flag_test.go index aecbbd0fbb..631febca47 100644 --- a/pkg/mflag/flag_test.go +++ b/pkg/mflag/flag_test.go @@ -15,6 +15,13 @@ import ( "time" ) +// ResetForTesting clears all flag state and sets the usage function as directed. +// After calling ResetForTesting, parse errors in flag handling will not +// exit the program. +func ResetForTesting(usage func()) { + CommandLine = NewFlagSet(os.Args[0], ContinueOnError) + Usage = usage +} func boolString(s string) string { if s == "0" { return "false" From 5d71ad8b192e4e4b41c58ff3eae10c0d8ccdec71 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 23 Jan 2014 12:55:58 -0800 Subject: [PATCH 211/364] no more TODO Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- REMOTE_TODO.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/REMOTE_TODO.md b/REMOTE_TODO.md index 2c0cf70b6f..b21ba68bf8 100644 --- a/REMOTE_TODO.md +++ b/REMOTE_TODO.md @@ -19,9 +19,9 @@ ok "/containers/{name:.*}/top": getContainersTop, N ok "/containers/{name:.*}/attach/ws": wsContainersAttach, 0 yes **POST** -TODO "/auth": postAuth, 0 yes +N/A "/auth": postAuth, 0 yes ok "/commit": postCommit, 0 -TODO "/build": postBuild, 0 yes +#3741 "/build": postBuild, 0 yes #3725 "/images/create": postImagesCreate, N yes yes (pull) ok "/images/{name:.*}/insert": postImagesInsert, N yes yes ok "/images/load": postImagesLoad, 1 yes (stdin) @@ -42,5 +42,5 @@ ok "/containers/{name:.*}": deleteContainers, 0 #3645 "/images/{name:.*}": deleteImages, N **OPTIONS** -ok "": optionsHandler, 0 +N/A "": optionsHandler, 0 ``` From ec73c232318a3dae803a27a6922f0af7117eec6e Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 23 Jan 2014 14:39:10 -0800 Subject: [PATCH 212/364] Refactor and fix register interface when bridge does not exist Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- network.go | 37 +-- networkdriver/ipallocator/allocator.go | 204 +++------------- networkdriver/ipallocator/allocator_test.go | 256 -------------------- networkdriver/network.go | 75 ++++++ networkdriver/network_test.go | 183 ++++++++++++++ 5 files changed, 299 insertions(+), 456 deletions(-) create mode 100644 networkdriver/network_test.go diff --git a/network.go b/network.go index 7414b348a2..3ec1f6fb73 100644 --- a/network.go +++ b/network.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "github.com/dotcloud/docker/networkdriver" "github.com/dotcloud/docker/networkdriver/ipallocator" "github.com/dotcloud/docker/pkg/iptables" "github.com/dotcloud/docker/pkg/netlink" @@ -60,13 +61,10 @@ func CreateBridgeIface(config *DaemonConfig) error { var ifaceAddr string if len(config.BridgeIp) != 0 { - _, dockerNetwork, err := net.ParseCIDR(config.BridgeIp) + _, _, err := net.ParseCIDR(config.BridgeIp) if err != nil { return err } - if err := ipallocator.RegisterNetwork(dockerNetwork, nameservers); err != nil { - return err - } ifaceAddr = config.BridgeIp } else { for _, addr := range addrs { @@ -74,12 +72,13 @@ func CreateBridgeIface(config *DaemonConfig) error { if err != nil { return err } - - if err := ipallocator.RegisterNetwork(dockerNetwork, nameservers); err == nil { - ifaceAddr = addr - break - } else { - utils.Debugf("%s: %s", addr, err) + if err := networkdriver.CheckNameserverOverlaps(nameservers, dockerNetwork); err == nil { + if err := networkdriver.CheckRouteOverlaps(dockerNetwork); err == nil { + ifaceAddr = addr + break + } else { + utils.Debugf("%s %s", addr, err) + } } } } @@ -491,21 +490,6 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) { return nil, err } - // TODO: @crosbymichael why are we doing this ? - /* - // avoid duplicate IP - ipNum := ipToInt(ip) - firstIP := manager.ipAllocator.network.IP.To4().Mask(manager.ipAllocator.network.Mask) - firstIPNum := ipToInt(firstIP) + 1 - - if firstIPNum == ipNum { - ip, err = manager.ipAllocator.Acquire() - if err != nil { - return nil, err - } - } - */ - iface := &NetworkInterface{ IPNet: net.IPNet{IP: *ip, Mask: manager.bridgeNetwork.Mask}, Gateway: manager.bridgeNetwork.IP, @@ -551,9 +535,6 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { network = addr.(*net.IPNet) } else { network = addr.(*net.IPNet) - if err := ipallocator.RegisterExistingNetwork(network); err != nil { - return nil, err - } } // Configure iptables for link support diff --git a/networkdriver/ipallocator/allocator.go b/networkdriver/ipallocator/allocator.go index cca8cdb05a..09319d1332 100644 --- a/networkdriver/ipallocator/allocator.go +++ b/networkdriver/ipallocator/allocator.go @@ -3,93 +3,43 @@ package ipallocator import ( "encoding/binary" "errors" - "github.com/dotcloud/docker/pkg/netlink" + "github.com/dotcloud/docker/networkdriver" "net" "sync" ) -type networkSet map[iPNet]*iPSet - -type iPNet struct { - IP string - Mask string -} +type networkSet map[string]*iPSet var ( - ErrNetworkAlreadyAllocated = errors.New("requested network overlaps with existing network") - ErrNetworkAlreadyRegisterd = errors.New("requested network is already registered") - ErrNetworkOverlapsWithNameservers = errors.New("requested network overlaps with nameserver") - ErrNoAvailableIPs = errors.New("no available ip addresses on network") - ErrIPAlreadyAllocated = errors.New("ip already allocated") - ErrNetworkNotRegistered = errors.New("network not registered") + ErrNoAvailableIPs = errors.New("no available ip addresses on network") + ErrIPAlreadyAllocated = errors.New("ip already allocated") +) +var ( lock = sync.Mutex{} allocatedIPs = networkSet{} availableIPS = networkSet{} ) -// RegisterNetwork registers a new network with the allocator -// and validates that it contains a valid ip that does not overlap -// with existing routes and nameservers -func RegisterNetwork(network *net.IPNet, nameservers []string) error { - lock.Lock() - defer lock.Unlock() - - if err := checkExistingNetworkOverlaps(network); err != nil { - return err - } - - routes, err := netlink.NetworkGetRoutes() - if err != nil { - return err - } - - if err := checkRouteOverlaps(routes, network); err != nil { - return err - } - - if err := checkNameserverOverlaps(nameservers, network); err != nil { - return err - } - return RegisterExistingNetwork(network) -} - -// RegisterExistingNetwork registers an exising network created -// for use with the allocator but does not perform any validation -func RegisterExistingNetwork(network *net.IPNet) error { - n := newIPNet(network) - - if _, exists := allocatedIPs[n]; !exists { - allocatedIPs[n] = &iPSet{} - } - if _, exists := availableIPS[n]; !exists { - availableIPS[n] = &iPSet{} - } - - return nil -} - // RequestIP requests an available ip from the given network. It // will return the next available ip if the ip provided is nil. If the // ip provided is not nil it will validate that the provided ip is available // for use or return an error -func RequestIP(network *net.IPNet, ip *net.IP) (*net.IP, error) { +func RequestIP(address *net.IPNet, ip *net.IP) (*net.IP, error) { lock.Lock() defer lock.Unlock() - if !networkExists(network) { - return nil, ErrNetworkNotRegistered - } + checkAddress(address) if ip == nil { - next, err := getNextIp(network) + next, err := getNextIp(address) if err != nil { return nil, err } return next, nil } - if err := registerIP(network, ip); err != nil { + if err := registerIP(address, ip); err != nil { return nil, err } return ip, nil @@ -97,19 +47,16 @@ func RequestIP(network *net.IPNet, ip *net.IP) (*net.IP, error) { // ReleaseIP adds the provided ip back into the pool of // available ips to be returned for use. -func ReleaseIP(network *net.IPNet, ip *net.IP) error { +func ReleaseIP(address *net.IPNet, ip *net.IP) error { lock.Lock() defer lock.Unlock() - if !networkExists(network) { - return ErrNetworkNotRegistered - } + checkAddress(address) var ( - n = newIPNet(network) - existing = allocatedIPs[n] - available = availableIPS[n] - pos = getPosition(network, ip) + existing = allocatedIPs[address.String()] + available = availableIPS[address.String()] + pos = getPosition(address, ip) ) existing.Remove(int(pos)) @@ -120,9 +67,9 @@ func ReleaseIP(network *net.IPNet, ip *net.IP) error { // convert the ip into the position in the subnet. Only // position are saved in the set -func getPosition(network *net.IPNet, ip *net.IP) int32 { +func getPosition(address *net.IPNet, ip *net.IP) int32 { var ( - first, _ = networkRange(network) + first, _ = networkdriver.NetworkRange(address) base = ipToInt(&first) i = ipToInt(ip) ) @@ -131,15 +78,14 @@ func getPosition(network *net.IPNet, ip *net.IP) int32 { // return an available ip if one is currently available. If not, // return the next available ip for the nextwork -func getNextIp(network *net.IPNet) (*net.IP, error) { +func getNextIp(address *net.IPNet) (*net.IP, error) { var ( - n = newIPNet(network) - ownIP = ipToInt(&network.IP) - available = availableIPS[n] - allocated = allocatedIPs[n] - first, _ = networkRange(network) + ownIP = ipToInt(&address.IP) + available = availableIPS[address.String()] + allocated = allocatedIPs[address.String()] + first, _ = networkdriver.NetworkRange(address) base = ipToInt(&first) - size = int(networkSize(network.Mask)) + size = int(networkdriver.NetworkSize(address.Mask)) max = int32(size - 2) // size -1 for the broadcast address, -1 for the gateway address pos = int32(available.Pop()) ) @@ -170,12 +116,11 @@ func getNextIp(network *net.IPNet) (*net.IP, error) { return nil, ErrNoAvailableIPs } -func registerIP(network *net.IPNet, ip *net.IP) error { +func registerIP(address *net.IPNet, ip *net.IP) error { var ( - n = newIPNet(network) - existing = allocatedIPs[n] - available = availableIPS[n] - pos = getPosition(network, ip) + existing = allocatedIPs[address.String()] + available = availableIPS[address.String()] + pos = getPosition(address, ip) ) if existing.Exists(int(pos)) { @@ -186,68 +131,6 @@ func registerIP(network *net.IPNet, ip *net.IP) error { return nil } -func checkRouteOverlaps(networks []netlink.Route, toCheck *net.IPNet) error { - for _, network := range networks { - if network.IPNet != nil && networkOverlaps(toCheck, network.IPNet) { - return ErrNetworkAlreadyAllocated - } - } - return nil -} - -// Detects overlap between one IPNet and another -func networkOverlaps(netX *net.IPNet, netY *net.IPNet) bool { - if firstIP, _ := networkRange(netX); netY.Contains(firstIP) { - return true - } - if firstIP, _ := networkRange(netY); netX.Contains(firstIP) { - return true - } - return false -} - -func checkExistingNetworkOverlaps(network *net.IPNet) error { - for existing := range allocatedIPs { - if newIPNet(network) == existing { - return ErrNetworkAlreadyRegisterd - } - - ex := newNetIPNet(existing) - if networkOverlaps(network, ex) { - return ErrNetworkAlreadyAllocated - } - } - return nil -} - -// Calculates the first and last IP addresses in an IPNet -func networkRange(network *net.IPNet) (net.IP, net.IP) { - var ( - netIP = network.IP.To4() - firstIP = netIP.Mask(network.Mask) - lastIP = net.IPv4(0, 0, 0, 0).To4() - ) - - for i := 0; i < len(lastIP); i++ { - lastIP[i] = netIP[i] | ^network.Mask[i] - } - return firstIP, lastIP -} - -func newIPNet(network *net.IPNet) iPNet { - return iPNet{ - IP: string(network.IP), - Mask: string(network.Mask), - } -} - -func newNetIPNet(network iPNet) *net.IPNet { - return &net.IPNet{ - IP: []byte(network.IP), - Mask: []byte(network.Mask), - } -} - // Converts a 4 bytes IP into a 32 bit integer func ipToInt(ip *net.IP) int32 { return int32(binary.BigEndian.Uint32(ip.To4())) @@ -261,33 +144,10 @@ func intToIP(n int32) *net.IP { return &ip } -// Given a netmask, calculates the number of available hosts -func networkSize(mask net.IPMask) int32 { - m := net.IPv4Mask(0, 0, 0, 0) - for i := 0; i < net.IPv4len; i++ { - m[i] = ^mask[i] +func checkAddress(address *net.IPNet) { + key := address.String() + if _, exists := allocatedIPs[key]; !exists { + allocatedIPs[key] = &iPSet{} + availableIPS[key] = &iPSet{} } - - return int32(binary.BigEndian.Uint32(m)) + 1 -} - -func checkNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error { - if len(nameservers) > 0 { - for _, ns := range nameservers { - _, nsNetwork, err := net.ParseCIDR(ns) - if err != nil { - return err - } - if networkOverlaps(toCheck, nsNetwork) { - return ErrNetworkOverlapsWithNameservers - } - } - } - return nil -} - -func networkExists(network *net.IPNet) bool { - n := newIPNet(network) - _, exists := allocatedIPs[n] - return exists } diff --git a/networkdriver/ipallocator/allocator_test.go b/networkdriver/ipallocator/allocator_test.go index f574dfda70..871f143521 100644 --- a/networkdriver/ipallocator/allocator_test.go +++ b/networkdriver/ipallocator/allocator_test.go @@ -2,7 +2,6 @@ package ipallocator import ( "fmt" - "github.com/dotcloud/docker/pkg/netlink" "net" "testing" ) @@ -12,64 +11,6 @@ func reset() { availableIPS = networkSet{} } -func TestRegisterNetwork(t *testing.T) { - defer reset() - network := &net.IPNet{ - IP: []byte{192, 168, 0, 1}, - Mask: []byte{255, 255, 255, 0}, - } - - if err := RegisterNetwork(network, nil); err != nil { - t.Fatal(err) - } - - n := newIPNet(network) - if _, exists := allocatedIPs[n]; !exists { - t.Fatal("IPNet should exist in allocated IPs") - } - - if _, exists := availableIPS[n]; !exists { - t.Fatal("IPNet should exist in available IPs") - } -} - -func TestRegisterTwoNetworks(t *testing.T) { - defer reset() - network := &net.IPNet{ - IP: []byte{192, 168, 0, 1}, - Mask: []byte{255, 255, 255, 0}, - } - - if err := RegisterNetwork(network, nil); err != nil { - t.Fatal(err) - } - - network2 := &net.IPNet{ - IP: []byte{10, 1, 42, 1}, - Mask: []byte{255, 255, 255, 0}, - } - - if err := RegisterNetwork(network2, nil); err != nil { - t.Fatal(err) - } -} - -func TestRegisterNetworkThatExists(t *testing.T) { - defer reset() - network := &net.IPNet{ - IP: []byte{192, 168, 0, 1}, - Mask: []byte{255, 255, 255, 0}, - } - - if err := RegisterNetwork(network, nil); err != nil { - t.Fatal(err) - } - - if err := RegisterNetwork(network, nil); err != ErrNetworkAlreadyRegisterd { - t.Fatalf("Expected error of %s got %s", ErrNetworkAlreadyRegisterd, err) - } -} - func TestRequestNewIps(t *testing.T) { defer reset() network := &net.IPNet{ @@ -77,10 +18,6 @@ func TestRequestNewIps(t *testing.T) { Mask: []byte{255, 255, 255, 0}, } - if err := RegisterNetwork(network, nil); err != nil { - t.Fatal(err) - } - for i := 2; i < 10; i++ { ip, err := RequestIP(network, nil) if err != nil { @@ -100,10 +37,6 @@ func TestReleaseIp(t *testing.T) { Mask: []byte{255, 255, 255, 0}, } - if err := RegisterNetwork(network, nil); err != nil { - t.Fatal(err) - } - ip, err := RequestIP(network, nil) if err != nil { t.Fatal(err) @@ -121,10 +54,6 @@ func TestGetReleasedIp(t *testing.T) { Mask: []byte{255, 255, 255, 0}, } - if err := RegisterNetwork(network, nil); err != nil { - t.Fatal(err) - } - ip, err := RequestIP(network, nil) if err != nil { t.Fatal(err) @@ -152,10 +81,6 @@ func TestRequesetSpecificIp(t *testing.T) { Mask: []byte{255, 255, 255, 0}, } - if err := RegisterNetwork(network, nil); err != nil { - t.Fatal(err) - } - ip := net.ParseIP("192.168.1.5") if _, err := RequestIP(network, &ip); err != nil { @@ -163,113 +88,6 @@ func TestRequesetSpecificIp(t *testing.T) { } } -func TestNonOverlapingNameservers(t *testing.T) { - defer reset() - network := &net.IPNet{ - IP: []byte{192, 168, 0, 1}, - Mask: []byte{255, 255, 255, 0}, - } - nameservers := []string{ - "127.0.0.1/32", - } - - if err := RegisterNetwork(network, nameservers); err != nil { - t.Fatal(err) - } -} - -func TestOverlapingNameservers(t *testing.T) { - defer reset() - network := &net.IPNet{ - IP: []byte{192, 168, 0, 1}, - Mask: []byte{255, 255, 255, 0}, - } - nameservers := []string{ - "192.168.0.1/32", - } - - if err := RegisterNetwork(network, nameservers); err != ErrNetworkOverlapsWithNameservers { - t.Fatalf("Expectecd error of %s got %s", ErrNetworkOverlapsWithNameservers, err) - } -} - -func TestNetworkRange(t *testing.T) { - // Simple class C test - _, network, _ := net.ParseCIDR("192.168.0.1/24") - first, last := networkRange(network) - if !first.Equal(net.ParseIP("192.168.0.0")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("192.168.0.255")) { - t.Error(last.String()) - } - if size := networkSize(network.Mask); size != 256 { - t.Error(size) - } - - // Class A test - _, network, _ = net.ParseCIDR("10.0.0.1/8") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("10.0.0.0")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("10.255.255.255")) { - t.Error(last.String()) - } - if size := networkSize(network.Mask); size != 16777216 { - t.Error(size) - } - - // Class A, random IP address - _, network, _ = net.ParseCIDR("10.1.2.3/8") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("10.0.0.0")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("10.255.255.255")) { - t.Error(last.String()) - } - - // 32bit mask - _, network, _ = net.ParseCIDR("10.1.2.3/32") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("10.1.2.3")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("10.1.2.3")) { - t.Error(last.String()) - } - if size := networkSize(network.Mask); size != 1 { - t.Error(size) - } - - // 31bit mask - _, network, _ = net.ParseCIDR("10.1.2.3/31") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("10.1.2.2")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("10.1.2.3")) { - t.Error(last.String()) - } - if size := networkSize(network.Mask); size != 2 { - t.Error(size) - } - - // 26bit mask - _, network, _ = net.ParseCIDR("10.1.2.3/26") - first, last = networkRange(network) - if !first.Equal(net.ParseIP("10.1.2.0")) { - t.Error(first.String()) - } - if !last.Equal(net.ParseIP("10.1.2.63")) { - t.Error(last.String()) - } - if size := networkSize(network.Mask); size != 64 { - t.Error(size) - } -} - func TestConversion(t *testing.T) { ip := net.ParseIP("127.0.0.1") i := ipToInt(&ip) @@ -293,9 +111,6 @@ func TestIPAllocator(t *testing.T) { gwIP, n, _ := net.ParseCIDR("127.0.0.1/29") network := &net.IPNet{IP: gwIP, Mask: n.Mask} - if err := RegisterNetwork(network, nil); err != nil { - t.Fatal(err) - } // Pool after initialisation (f = free, u = used) // 2(f) - 3(f) - 4(f) - 5(f) - 6(f) // ↑ @@ -403,74 +218,3 @@ func assertIPEquals(t *testing.T, ip1, ip2 *net.IP) { t.Fatalf("Expected IP %s, got %s", ip1, ip2) } } - -func AssertOverlap(CIDRx string, CIDRy string, t *testing.T) { - _, netX, _ := net.ParseCIDR(CIDRx) - _, netY, _ := net.ParseCIDR(CIDRy) - if !networkOverlaps(netX, netY) { - t.Errorf("%v and %v should overlap", netX, netY) - } -} - -func AssertNoOverlap(CIDRx string, CIDRy string, t *testing.T) { - _, netX, _ := net.ParseCIDR(CIDRx) - _, netY, _ := net.ParseCIDR(CIDRy) - if networkOverlaps(netX, netY) { - t.Errorf("%v and %v should not overlap", netX, netY) - } -} - -func TestNetworkOverlaps(t *testing.T) { - //netY starts at same IP and ends within netX - AssertOverlap("172.16.0.1/24", "172.16.0.1/25", t) - //netY starts within netX and ends at same IP - AssertOverlap("172.16.0.1/24", "172.16.0.128/25", t) - //netY starts and ends within netX - AssertOverlap("172.16.0.1/24", "172.16.0.64/25", t) - //netY starts at same IP and ends outside of netX - AssertOverlap("172.16.0.1/24", "172.16.0.1/23", t) - //netY starts before and ends at same IP of netX - AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t) - //netY starts before and ends outside of netX - AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t) - //netY starts and ends before netX - AssertNoOverlap("172.16.1.1/25", "172.16.0.1/24", t) - //netX starts and ends before netY - AssertNoOverlap("172.16.1.1/25", "172.16.2.1/24", t) -} - -func TestCheckRouteOverlaps(t *testing.T) { - routesData := []string{"10.0.2.0/32", "10.0.3.0/24", "10.0.42.0/24", "172.16.42.0/24", "192.168.142.0/24"} - - routes := []netlink.Route{} - for _, addr := range routesData { - _, netX, _ := net.ParseCIDR(addr) - routes = append(routes, netlink.Route{IPNet: netX}) - } - - _, netX, _ := net.ParseCIDR("172.16.0.1/24") - if err := checkRouteOverlaps(routes, netX); err != nil { - t.Fatal(err) - } - - _, netX, _ = net.ParseCIDR("10.0.2.0/24") - if err := checkRouteOverlaps(routes, netX); err == nil { - t.Fatalf("10.0.2.0/24 and 10.0.2.0 should overlap but it doesn't") - } -} - -func TestCheckNameserverOverlaps(t *testing.T) { - nameservers := []string{"10.0.2.3/32", "192.168.102.1/32"} - - _, netX, _ := net.ParseCIDR("10.0.2.3/32") - - if err := checkNameserverOverlaps(nameservers, netX); err == nil { - t.Fatalf("%s should overlap 10.0.2.3/32 but doesn't", netX) - } - - _, netX, _ = net.ParseCIDR("192.168.102.2/32") - - if err := checkNameserverOverlaps(nameservers, netX); err != nil { - t.Fatalf("%s should not overlap %v but it does", netX, nameservers) - } -} diff --git a/networkdriver/network.go b/networkdriver/network.go index e74734d55f..cce91d7a55 100644 --- a/networkdriver/network.go +++ b/networkdriver/network.go @@ -1 +1,76 @@ package networkdriver + +import ( + "encoding/binary" + "errors" + "github.com/dotcloud/docker/pkg/netlink" + "net" +) + +var ( + ErrNetworkOverlapsWithNameservers = errors.New("requested network overlaps with nameserver") + ErrNetworkOverlaps = errors.New("requested network overlaps with existing network") +) + +func CheckNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error { + if len(nameservers) > 0 { + for _, ns := range nameservers { + _, nsNetwork, err := net.ParseCIDR(ns) + if err != nil { + return err + } + if NetworkOverlaps(toCheck, nsNetwork) { + return ErrNetworkOverlapsWithNameservers + } + } + } + return nil +} + +func CheckRouteOverlaps(toCheck *net.IPNet) error { + networks, err := netlink.NetworkGetRoutes() + if err != nil { + return err + } + + for _, network := range networks { + if network.IPNet != nil && NetworkOverlaps(toCheck, network.IPNet) { + return ErrNetworkOverlaps + } + } + return nil +} + +// Detects overlap between one IPNet and another +func NetworkOverlaps(netX *net.IPNet, netY *net.IPNet) bool { + if firstIP, _ := NetworkRange(netX); netY.Contains(firstIP) { + return true + } + if firstIP, _ := NetworkRange(netY); netX.Contains(firstIP) { + return true + } + return false +} + +// Calculates the first and last IP addresses in an IPNet +func NetworkRange(network *net.IPNet) (net.IP, net.IP) { + var ( + netIP = network.IP.To4() + firstIP = netIP.Mask(network.Mask) + lastIP = net.IPv4(0, 0, 0, 0).To4() + ) + + for i := 0; i < len(lastIP); i++ { + lastIP[i] = netIP[i] | ^network.Mask[i] + } + return firstIP, lastIP +} + +// Given a netmask, calculates the number of available hosts +func NetworkSize(mask net.IPMask) int32 { + m := net.IPv4Mask(0, 0, 0, 0) + for i := 0; i < net.IPv4len; i++ { + m[i] = ^mask[i] + } + return int32(binary.BigEndian.Uint32(m)) + 1 +} diff --git a/networkdriver/network_test.go b/networkdriver/network_test.go new file mode 100644 index 0000000000..8905dad053 --- /dev/null +++ b/networkdriver/network_test.go @@ -0,0 +1,183 @@ +package networkdriver + +import ( + "github.com/dotcloud/docker/pkg/netlink" + "net" + "testing" +) + +func TestNonOverlapingNameservers(t *testing.T) { + network := &net.IPNet{ + IP: []byte{192, 168, 0, 1}, + Mask: []byte{255, 255, 255, 0}, + } + nameservers := []string{ + "127.0.0.1/32", + } + + if err := CheckNameserverOverlaps(nameservers, network); err != nil { + t.Fatal(err) + } +} + +func TestOverlapingNameservers(t *testing.T) { + network := &net.IPNet{ + IP: []byte{192, 168, 0, 1}, + Mask: []byte{255, 255, 255, 0}, + } + nameservers := []string{ + "192.168.0.1/32", + } + + if err := CheckNameserverOverlaps(nameservers, network); err == nil { + t.Fatalf("Expected error %s got %s", ErrNetworkOverlapsWithNameservers, err) + } +} + +func TestCheckRouteOverlaps(t *testing.T) { + routesData := []string{"10.0.2.0/32", "10.0.3.0/24", "10.0.42.0/24", "172.16.42.0/24", "192.168.142.0/24"} + + routes := []netlink.Route{} + for _, addr := range routesData { + _, netX, _ := net.ParseCIDR(addr) + routes = append(routes, netlink.Route{IPNet: netX}) + } + + _, netX, _ := net.ParseCIDR("172.16.0.1/24") + if err := CheckRouteOverlaps(netX); err != nil { + t.Fatal(err) + } + + _, netX, _ = net.ParseCIDR("10.0.2.0/24") + if err := CheckRouteOverlaps(netX); err == nil { + t.Fatalf("10.0.2.0/24 and 10.0.2.0 should overlap but it doesn't") + } +} + +func TestCheckNameserverOverlaps(t *testing.T) { + nameservers := []string{"10.0.2.3/32", "192.168.102.1/32"} + + _, netX, _ := net.ParseCIDR("10.0.2.3/32") + + if err := CheckNameserverOverlaps(nameservers, netX); err == nil { + t.Fatalf("%s should overlap 10.0.2.3/32 but doesn't", netX) + } + + _, netX, _ = net.ParseCIDR("192.168.102.2/32") + + if err := CheckNameserverOverlaps(nameservers, netX); err != nil { + t.Fatalf("%s should not overlap %v but it does", netX, nameservers) + } +} + +func AssertOverlap(CIDRx string, CIDRy string, t *testing.T) { + _, netX, _ := net.ParseCIDR(CIDRx) + _, netY, _ := net.ParseCIDR(CIDRy) + if !NetworkOverlaps(netX, netY) { + t.Errorf("%v and %v should overlap", netX, netY) + } +} + +func AssertNoOverlap(CIDRx string, CIDRy string, t *testing.T) { + _, netX, _ := net.ParseCIDR(CIDRx) + _, netY, _ := net.ParseCIDR(CIDRy) + if NetworkOverlaps(netX, netY) { + t.Errorf("%v and %v should not overlap", netX, netY) + } +} + +func TestNetworkOverlaps(t *testing.T) { + //netY starts at same IP and ends within netX + AssertOverlap("172.16.0.1/24", "172.16.0.1/25", t) + //netY starts within netX and ends at same IP + AssertOverlap("172.16.0.1/24", "172.16.0.128/25", t) + //netY starts and ends within netX + AssertOverlap("172.16.0.1/24", "172.16.0.64/25", t) + //netY starts at same IP and ends outside of netX + AssertOverlap("172.16.0.1/24", "172.16.0.1/23", t) + //netY starts before and ends at same IP of netX + AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t) + //netY starts before and ends outside of netX + AssertOverlap("172.16.1.1/24", "172.16.0.1/23", t) + //netY starts and ends before netX + AssertNoOverlap("172.16.1.1/25", "172.16.0.1/24", t) + //netX starts and ends before netY + AssertNoOverlap("172.16.1.1/25", "172.16.2.1/24", t) +} + +func TestNetworkRange(t *testing.T) { + // Simple class C test + _, network, _ := net.ParseCIDR("192.168.0.1/24") + first, last := NetworkRange(network) + if !first.Equal(net.ParseIP("192.168.0.0")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("192.168.0.255")) { + t.Error(last.String()) + } + if size := NetworkSize(network.Mask); size != 256 { + t.Error(size) + } + + // Class A test + _, network, _ = net.ParseCIDR("10.0.0.1/8") + first, last = NetworkRange(network) + if !first.Equal(net.ParseIP("10.0.0.0")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("10.255.255.255")) { + t.Error(last.String()) + } + if size := NetworkSize(network.Mask); size != 16777216 { + t.Error(size) + } + + // Class A, random IP address + _, network, _ = net.ParseCIDR("10.1.2.3/8") + first, last = NetworkRange(network) + if !first.Equal(net.ParseIP("10.0.0.0")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("10.255.255.255")) { + t.Error(last.String()) + } + + // 32bit mask + _, network, _ = net.ParseCIDR("10.1.2.3/32") + first, last = NetworkRange(network) + if !first.Equal(net.ParseIP("10.1.2.3")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("10.1.2.3")) { + t.Error(last.String()) + } + if size := NetworkSize(network.Mask); size != 1 { + t.Error(size) + } + + // 31bit mask + _, network, _ = net.ParseCIDR("10.1.2.3/31") + first, last = NetworkRange(network) + if !first.Equal(net.ParseIP("10.1.2.2")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("10.1.2.3")) { + t.Error(last.String()) + } + if size := NetworkSize(network.Mask); size != 2 { + t.Error(size) + } + + // 26bit mask + _, network, _ = net.ParseCIDR("10.1.2.3/26") + first, last = NetworkRange(network) + if !first.Equal(net.ParseIP("10.1.2.0")) { + t.Error(first.String()) + } + if !last.Equal(net.ParseIP("10.1.2.63")) { + t.Error(last.String()) + } + if size := NetworkSize(network.Mask); size != 64 { + t.Error(size) + } +} From 2f57eb04102c2ef08e478d3977fc3682672473af Mon Sep 17 00:00:00 2001 From: Paul Morie Date: Thu, 23 Jan 2014 16:41:18 -0500 Subject: [PATCH 213/364] Fix typo in container.go Docker-DCO-1.1-Signed-off-by: Paul Morie (github: pmorie) --- container.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/container.go b/container.go index 7ebfb3e397..8c9863c0a4 100644 --- a/container.go +++ b/container.go @@ -897,7 +897,7 @@ func (container *Container) createVolumes() error { return err } // Change the source volume's ownership if it differs from the root - // files that where just copied + // files that were just copied if stat.Uid != srcStat.Uid || stat.Gid != srcStat.Gid { if err := os.Chown(srcPath, int(stat.Uid), int(stat.Gid)); err != nil { return err @@ -925,7 +925,7 @@ func (container *Container) applyExternalVolumes() error { mountRW = false case "rw": // mountRW is already true default: - return fmt.Errorf("Malformed volumes-from speficication: %s", containerSpec) + return fmt.Errorf("Malformed volumes-from specification: %s", containerSpec) } } c := container.runtime.Get(specParts[0]) From 415379e45dadb32385771ceae701d8b9f204f2b8 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 23 Jan 2014 12:19:52 -0800 Subject: [PATCH 214/364] move build to a job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 98 +++++++++---------------------------------------------- server.go | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 82 deletions(-) diff --git a/api.go b/api.go index b45f1a35d1..06d9fde77b 100644 --- a/api.go +++ b/api.go @@ -8,7 +8,6 @@ import ( "encoding/json" "expvar" "fmt" - "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/pkg/systemd" @@ -22,7 +21,6 @@ import ( "net/http" "net/http/pprof" "os" - "os/exec" "regexp" "strconv" "strings" @@ -892,18 +890,12 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.") } var ( - remoteURL = r.FormValue("remote") - repoName = r.FormValue("t") - rawSuppressOutput = r.FormValue("q") - rawNoCache = r.FormValue("nocache") - rawRm = r.FormValue("rm") authEncoded = r.Header.Get("X-Registry-Auth") authConfig = &auth.AuthConfig{} configFileEncoded = r.Header.Get("X-Registry-Config") configFile = &auth.ConfigFile{} - tag string + job = srv.Eng.Job("build") ) - repoName, tag = utils.ParseRepositoryTag(repoName) // This block can be removed when API versions prior to 1.9 are deprecated. // Both headers will be parsed and sent along to the daemon, but if a non-empty @@ -927,83 +919,25 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ } } - var context io.Reader - - if remoteURL == "" { - context = r.Body - } else if utils.IsGIT(remoteURL) { - if !strings.HasPrefix(remoteURL, "git://") { - remoteURL = "https://" + remoteURL - } - root, err := ioutil.TempDir("", "docker-build-git") - if err != nil { - return err - } - defer os.RemoveAll(root) - - if output, err := exec.Command("git", "clone", remoteURL, root).CombinedOutput(); err != nil { - return fmt.Errorf("Error trying to use git: %s (%s)", err, output) - } - - c, err := archive.Tar(root, archive.Uncompressed) - if err != nil { - return err - } - context = c - } else if utils.IsURL(remoteURL) { - f, err := utils.Download(remoteURL) - if err != nil { - return err - } - defer f.Body.Close() - dockerFile, err := ioutil.ReadAll(f.Body) - if err != nil { - return err - } - c, err := MkBuildContext(string(dockerFile), nil) - if err != nil { - return err - } - context = c - } - - suppressOutput, err := getBoolParam(rawSuppressOutput) - if err != nil { - return err - } - noCache, err := getBoolParam(rawNoCache) - if err != nil { - return err - } - rm, err := getBoolParam(rawRm) - if err != nil { - return err - } - if version >= 1.8 { w.Header().Set("Content-Type", "application/json") + job.SetenvBool("json", true) } - sf := utils.NewStreamFormatter(version >= 1.8) - b := NewBuildFile(srv, - &StdoutFormater{ - Writer: utils.NewWriteFlusher(w), - StreamFormatter: sf, - }, - &StderrFormater{ - Writer: utils.NewWriteFlusher(w), - StreamFormatter: sf, - }, - !suppressOutput, !noCache, rm, utils.NewWriteFlusher(w), sf, authConfig, configFile) - id, err := b.Build(context) - if err != nil { - if sf.Used() { - w.Write(sf.FormatError(err)) - return nil + + job.Stdout.Add(utils.NewWriteFlusher(w)) + job.Stdin.Add(r.Body) + job.Setenv("remote", r.FormValue("remote")) + job.Setenv("t", r.FormValue("t")) + job.Setenv("q", r.FormValue("q")) + job.Setenv("nocache", r.FormValue("nocache")) + job.Setenv("rm", r.FormValue("rm")) + + if err := job.Run(); err != nil { + if !job.Stdout.Used() { + return err } - return fmt.Errorf("Error build: %s", err) - } - if repoName != "" { - srv.runtime.repositories.Set(repoName, tag, id, false) + sf := utils.NewStreamFormatter(version >= 1.8) + w.Write(sf.FormatError(err)) } return nil } diff --git a/server.go b/server.go index 60fefc524f..c0b45feeb2 100644 --- a/server.go +++ b/server.go @@ -96,6 +96,7 @@ func jobInitApi(job *engine.Job) engine.Status { "changes": srv.ContainerChanges, "top": srv.ContainerTop, "load": srv.ImageLoad, + "build": srv.Build, } { if err := job.Eng.Register(name, handler); err != nil { job.Error(err) @@ -391,6 +392,92 @@ func (srv *Server) exportImage(image *Image, tempdir string) error { return nil } +func (srv *Server) Build(job *engine.Job) engine.Status { + if len(job.Args) != 0 { + job.Errorf("Usage: %s\n", job.Name) + return engine.StatusErr + } + var ( + remoteURL = job.Getenv("remote") + repoName = job.Getenv("t") + suppressOutput = job.GetenvBool("q") + noCache = job.GetenvBool("nocache") + rm = job.GetenvBool("rm") + authConfig = &auth.AuthConfig{} + configFile = &auth.ConfigFile{} + tag string + context io.Reader + ) + job.GetenvJson("authConfig", authConfig) + job.GetenvJson("configFile", configFile) + repoName, tag = utils.ParseRepositoryTag(repoName) + + if remoteURL == "" { + context = job.Stdin + } else if utils.IsGIT(remoteURL) { + if !strings.HasPrefix(remoteURL, "git://") { + remoteURL = "https://" + remoteURL + } + root, err := ioutil.TempDir("", "docker-build-git") + if err != nil { + job.Error(err) + return engine.StatusErr + } + defer os.RemoveAll(root) + + if output, err := exec.Command("git", "clone", remoteURL, root).CombinedOutput(); err != nil { + job.Errorf("Error trying to use git: %s (%s)", err, output) + return engine.StatusErr + } + + c, err := archive.Tar(root, archive.Uncompressed) + if err != nil { + job.Error(err) + return engine.StatusErr + } + context = c + } else if utils.IsURL(remoteURL) { + f, err := utils.Download(remoteURL) + if err != nil { + job.Error(err) + return engine.StatusErr + } + defer f.Body.Close() + dockerFile, err := ioutil.ReadAll(f.Body) + if err != nil { + job.Error(err) + return engine.StatusErr + } + c, err := MkBuildContext(string(dockerFile), nil) + if err != nil { + job.Error(err) + return engine.StatusErr + } + context = c + } + + sf := utils.NewStreamFormatter(job.GetenvBool("json")) + b := NewBuildFile(srv, + &StdoutFormater{ + Writer: job.Stdout, + StreamFormatter: sf, + }, + &StderrFormater{ + Writer: job.Stdout, + StreamFormatter: sf, + }, + !suppressOutput, !noCache, rm, job.Stdout, sf, authConfig, configFile) + id, err := b.Build(context) + if err != nil { + job.Error(err) + return engine.StatusErr + } + if repoName != "" { + srv.runtime.repositories.Set(repoName, tag, id, false) + } + return engine.StatusOK +} + // Loads a set of images into the repository. This is the complementary of ImageExport. // The input stream is an uncompressed tar ball containing images and metadata. func (srv *Server) ImageLoad(job *engine.Job) engine.Status { From 42e35ecff36fcb07e45c19f880af84f8532a3fac Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 23 Jan 2014 16:20:51 -0800 Subject: [PATCH 215/364] remove useless anonymous field mentions Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- engine/streams.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/engine/streams.go b/engine/streams.go index 935334c261..4b1f172b49 100644 --- a/engine/streams.go +++ b/engine/streams.go @@ -23,8 +23,8 @@ func NewOutput() *Output { // Return true if something was written on this output func (o *Output) Used() bool { - o.Mutex.Lock() - defer o.Mutex.Unlock() + o.Lock() + defer o.Unlock() return o.used } @@ -32,8 +32,8 @@ func (o *Output) Used() bool { // to the output will be written to the new destination in addition to all the others. // This method is thread-safe. func (o *Output) Add(dst io.Writer) { - o.Mutex.Lock() - defer o.Mutex.Unlock() + o.Lock() + defer o.Unlock() o.dests = append(o.dests, dst) } @@ -42,8 +42,8 @@ func (o *Output) Add(dst io.Writer) { // destination in addition to all the others. This method is thread-safe. func (o *Output) Set(dst io.Writer) { o.Close() - o.Mutex.Lock() - defer o.Mutex.Unlock() + o.Lock() + defer o.Unlock() o.dests = []io.Writer{dst} } @@ -96,8 +96,8 @@ func (o *Output) AddString(dst *string) error { // Write writes the same data to all registered destinations. // This method is thread-safe. func (o *Output) Write(p []byte) (n int, err error) { - o.Mutex.Lock() - defer o.Mutex.Unlock() + o.Lock() + defer o.Unlock() o.used = true var firstErr error for _, dst := range o.dests { @@ -113,8 +113,8 @@ func (o *Output) Write(p []byte) (n int, err error) { // AddTail and AddString tasks to complete. // The Close method of each destination is called if it exists. func (o *Output) Close() error { - o.Mutex.Lock() - defer o.Mutex.Unlock() + o.Lock() + defer o.Unlock() var firstErr error for _, dst := range o.dests { if closer, ok := dst.(io.WriteCloser); ok { From 6cae33ca0f78ce8b7f1250e1f85828cfb242b12f Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Thu, 23 Jan 2014 18:55:26 -0800 Subject: [PATCH 216/364] Fix TOC for Articles --- docs/sources/articles/index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/articles/index.rst b/docs/sources/articles/index.rst index 708ca35326..2cfc427420 100644 --- a/docs/sources/articles/index.rst +++ b/docs/sources/articles/index.rst @@ -4,7 +4,8 @@ .. _articles_list: -Contents: +Articles +======== .. toctree:: :maxdepth: 1 From 9dcbdbc4b1addb67c0fdcadab1c8f98f30e58b4c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 22 Jan 2014 13:35:35 -0800 Subject: [PATCH 217/364] move pull and import to a job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 38 +++++++++--------- buildfile.go | 7 +++- engine/engine.go | 7 +++- engine/job.go | 8 +++- integration/runtime_test.go | 4 +- integration/sorter_test.go | 7 ++-- server.go | 77 ++++++++++++++++++++++++++++--------- utils/streamformatter.go | 4 ++ 8 files changed, 107 insertions(+), 45 deletions(-) diff --git a/api.go b/api.go index 0591723ea0..eba1ec31e9 100644 --- a/api.go +++ b/api.go @@ -413,11 +413,11 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht return err } - src := r.Form.Get("fromSrc") - image := r.Form.Get("fromImage") - tag := r.Form.Get("tag") - repo := r.Form.Get("repo") - + var ( + image = r.Form.Get("fromImage") + tag = r.Form.Get("tag") + job *engine.Job + ) authEncoded := r.Header.Get("X-Registry-Auth") authConfig := &auth.AuthConfig{} if authEncoded != "" { @@ -431,7 +431,6 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht if version > 1.0 { w.Header().Set("Content-Type", "application/json") } - sf := utils.NewStreamFormatter(version > 1.0) if image != "" { //pull metaHeaders := map[string][]string{} for k, v := range r.Header { @@ -439,22 +438,25 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht metaHeaders[k] = v } } - if err := srv.ImagePull(image, tag, w, sf, authConfig, metaHeaders, version > 1.3); err != nil { - if sf.Used() { - w.Write(sf.FormatError(err)) - return nil - } - return err - } + job = srv.Eng.Job("pull", r.Form.Get("fromImage"), tag) + job.SetenvBool("parallel", version > 1.3) + job.SetenvJson("metaHeaders", metaHeaders) + job.SetenvJson("authConfig", authConfig) } else { //import - if err := srv.ImageImport(src, repo, tag, r.Body, w, sf); err != nil { - if sf.Used() { - w.Write(sf.FormatError(err)) - return nil - } + job = srv.Eng.Job("import", r.Form.Get("fromSrc"), r.Form.Get("repo"), tag) + job.Stdin.Add(r.Body) + } + + job.SetenvBool("json", version > 1.0) + job.Stdout.Add(w) + if err := job.Run(); err != nil { + if !job.Stdout.Used() { return err } + sf := utils.NewStreamFormatter(version > 1.0) + w.Write(sf.FormatError(err)) } + return nil } diff --git a/buildfile.go b/buildfile.go index fc8bfed5d3..89afccebbd 100644 --- a/buildfile.go +++ b/buildfile.go @@ -84,7 +84,12 @@ func (b *buildFile) CmdFrom(name string) error { resolvedAuth := b.configFile.ResolveAuthConfig(endpoint) pullRegistryAuth = &resolvedAuth } - if err := b.srv.ImagePull(remote, tag, b.outOld, b.sf, pullRegistryAuth, nil, true); err != nil { + job := b.srv.Eng.Job("pull", remote, tag) + job.SetenvBool("json", b.sf.Json()) + job.SetenvBool("parallel", true) + job.SetenvJson("authConfig", pullRegistryAuth) + job.Stdout.Add(b.outOld) + if err := job.Run(); err != nil { return err } image, err = b.runtime.repositories.LookupImage(name) diff --git a/engine/engine.go b/engine/engine.go index ff69dcd138..ec880b9c85 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -137,6 +137,9 @@ func (eng *Engine) Job(name string, args ...string) *Job { } func (eng *Engine) Logf(format string, args ...interface{}) (n int, err error) { - prefixedFormat := fmt.Sprintf("[%s] %s\n", eng, strings.TrimRight(format, "\n")) - return fmt.Fprintf(eng.Stderr, prefixedFormat, args...) + if os.Getenv("TEST") == "" { + prefixedFormat := fmt.Sprintf("[%s] %s\n", eng, strings.TrimRight(format, "\n")) + return fmt.Fprintf(eng.Stderr, prefixedFormat, args...) + } + return 0, nil } diff --git a/engine/job.go b/engine/job.go index 68b1715d92..179b2ebdda 100644 --- a/engine/job.go +++ b/engine/job.go @@ -3,6 +3,7 @@ package engine import ( "fmt" "io" + "os" "strings" "time" ) @@ -176,8 +177,11 @@ func (job *Job) Environ() map[string]string { } func (job *Job) Logf(format string, args ...interface{}) (n int, err error) { - prefixedFormat := fmt.Sprintf("[%s] %s\n", job, strings.TrimRight(format, "\n")) - return fmt.Fprintf(job.Stderr, prefixedFormat, args...) + if os.Getenv("TEST") == "" { + prefixedFormat := fmt.Sprintf("[%s] %s\n", job, strings.TrimRight(format, "\n")) + return fmt.Fprintf(job.Stderr, prefixedFormat, args...) + } + return 0, nil } func (job *Job) Printf(format string, args ...interface{}) (n int, err error) { diff --git a/integration/runtime_test.go b/integration/runtime_test.go index f3d8384082..008be9ef38 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -137,7 +137,9 @@ func setupBaseImage() { // If the unit test is not found, try to download it. if img, err := srv.ImageInspect(unitTestImageName); err != nil || img.ID != unitTestImageID { // Retrieve the Image - if err := srv.ImagePull(unitTestImageName, "", os.Stdout, utils.NewStreamFormatter(false), nil, nil, true); err != nil { + job = eng.Job("pull", unitTestImageName) + job.Stdout.Add(utils.NopWriteCloser(os.Stdout)) + if err := job.Run(); err != nil { log.Fatalf("Unable to pull the test image: %s", err) } } diff --git a/integration/sorter_test.go b/integration/sorter_test.go index 02d08d3409..d193fca1f0 100644 --- a/integration/sorter_test.go +++ b/integration/sorter_test.go @@ -2,8 +2,6 @@ package docker import ( "github.com/dotcloud/docker" - "github.com/dotcloud/docker/utils" - "io/ioutil" "testing" "time" ) @@ -53,5 +51,8 @@ func generateImage(name string, srv *docker.Server) error { if err != nil { return err } - return srv.ImageImport("-", "repo", name, archive, ioutil.Discard, utils.NewStreamFormatter(true)) + job := srv.Eng.Job("import", "-", "repo", name) + job.Stdin.Add(archive) + job.SetenvBool("json", true) + return job.Run() } diff --git a/server.go b/server.go index c0b45feeb2..e672f6a69a 100644 --- a/server.go +++ b/server.go @@ -97,6 +97,8 @@ func jobInitApi(job *engine.Job) engine.Status { "top": srv.ContainerTop, "load": srv.ImageLoad, "build": srv.Build, + "pull": srv.ImagePull, + "import": srv.ImageImport, } { if err := job.Eng.Register(name, handler); err != nil { job.Error(err) @@ -1312,8 +1314,25 @@ func (srv *Server) poolRemove(kind, key string) error { return nil } -func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig, metaHeaders map[string][]string, parallel bool) error { - out = utils.NewWriteFlusher(out) +func (srv *Server) ImagePull(job *engine.Job) engine.Status { + if n := len(job.Args); n != 1 && n != 2 { + job.Errorf("Usage: %s IMAGE [TAG]", job.Name) + return engine.StatusErr + } + var ( + localName = job.Args[0] + tag string + sf = utils.NewStreamFormatter(job.GetenvBool("json")) + out = utils.NewWriteFlusher(job.Stdout) + authConfig *auth.AuthConfig + metaHeaders map[string][]string + ) + if len(job.Args) > 1 { + tag = job.Args[1] + } + + job.GetenvJson("authConfig", authConfig) + job.GetenvJson("metaHeaders", metaHeaders) c, err := srv.poolAdd("pull", localName+":"+tag) if err != nil { @@ -1321,21 +1340,24 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut // Another pull of the same repository is already taking place; just wait for it to finish out.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", localName)) <-c - return nil + return engine.StatusOK } - return err + job.Error(err) + return engine.StatusErr } defer srv.poolRemove("pull", localName+":"+tag) // Resolve the Repository name from fqn to endpoint + name endpoint, remoteName, err := registry.ResolveRepositoryName(localName) if err != nil { - return err + job.Error(err) + return engine.StatusErr } r, err := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), endpoint) if err != nil { - return err + job.Error(err) + return engine.StatusErr } if endpoint == auth.IndexServerAddress() { @@ -1343,11 +1365,12 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut localName = remoteName } - if err = srv.pullRepository(r, out, localName, remoteName, tag, sf, parallel); err != nil { - return err + if err = srv.pullRepository(r, out, localName, remoteName, tag, sf, job.GetenvBool("parallel")); err != nil { + job.Error(err) + return engine.StatusErr } - return nil + return engine.StatusOK } // Retrieve the all the images to be uploaded in the correct order @@ -1551,16 +1574,31 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo return nil } -func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Writer, sf *utils.StreamFormatter) error { - var archive io.Reader - var resp *http.Response +func (srv *Server) ImageImport(job *engine.Job) engine.Status { + if n := len(job.Args); n != 2 && n != 3 { + job.Errorf("Usage: %s SRC REPO [TAG]", job.Name) + return engine.StatusErr + } + var ( + src = job.Args[0] + repo = job.Args[1] + tag string + sf = utils.NewStreamFormatter(job.GetenvBool("json")) + out = utils.NewWriteFlusher(job.Stdout) + archive io.Reader + resp *http.Response + ) + if len(job.Args) > 2 { + tag = job.Args[2] + } if src == "-" { - archive = in + archive = job.Stdin } else { u, err := url.Parse(src) if err != nil { - return err + job.Error(err) + return engine.StatusErr } if u.Scheme == "" { u.Scheme = "http" @@ -1572,22 +1610,25 @@ func (srv *Server) ImageImport(src, repo, tag string, in io.Reader, out io.Write // If curl is not available, fallback to http.Get() resp, err = utils.Download(u.String()) if err != nil { - return err + job.Error(err) + return engine.StatusErr } archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf, true, "", "Importing") } img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) if err != nil { - return err + job.Error(err) + return engine.StatusErr } // Optionally register the image at REPO/TAG if repo != "" { if err := srv.runtime.repositories.Set(repo, tag, img.ID, true); err != nil { - return err + job.Error(err) + return engine.StatusErr } } out.Write(sf.FormatStatus("", img.ID)) - return nil + return engine.StatusOK } func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { diff --git a/utils/streamformatter.go b/utils/streamformatter.go index 0c41d0bddd..9345c3cb16 100644 --- a/utils/streamformatter.go +++ b/utils/streamformatter.go @@ -82,3 +82,7 @@ func (sf *StreamFormatter) FormatProgress(id, action string, progress *JSONProgr func (sf *StreamFormatter) Used() bool { return sf.used } + +func (sf *StreamFormatter) Json() bool { + return sf.json +} From 90e9a2d85a6c981b137df0c22c31d6f32f4b6f66 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 22 Jan 2014 16:15:23 -0800 Subject: [PATCH 218/364] fix flush Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- engine/streams.go | 12 ++++++++++++ server.go | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/engine/streams.go b/engine/streams.go index 4b1f172b49..07db058b2d 100644 --- a/engine/streams.go +++ b/engine/streams.go @@ -109,6 +109,18 @@ func (o *Output) Write(p []byte) (n int, err error) { return len(p), firstErr } +func (o *Output) Flush() { + o.Mutex.Lock() + defer o.Mutex.Unlock() + for _, dst := range o.dests { + if f, ok := dst.(interface { + Flush() + }); ok { + f.Flush() + } + } +} + // Close unregisters all destinations and waits for all background // AddTail and AddString tasks to complete. // The Close method of each destination is called if it exists. diff --git a/server.go b/server.go index e672f6a69a..3e4daad77f 100644 --- a/server.go +++ b/server.go @@ -1324,7 +1324,7 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { tag string sf = utils.NewStreamFormatter(job.GetenvBool("json")) out = utils.NewWriteFlusher(job.Stdout) - authConfig *auth.AuthConfig + authConfig = &auth.AuthConfig{} metaHeaders map[string][]string ) if len(job.Args) > 1 { From 35641f0ec7ecae16f88ba9affe0aeea0ae864874 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 23 Jan 2014 16:00:07 -0800 Subject: [PATCH 219/364] remove useless flush method Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 2 +- engine/streams.go | 12 ------------ server.go | 12 +++++------- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/api.go b/api.go index eba1ec31e9..13397e6b92 100644 --- a/api.go +++ b/api.go @@ -448,7 +448,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht } job.SetenvBool("json", version > 1.0) - job.Stdout.Add(w) + job.Stdout.Add(utils.NewWriteFlusher(w)) if err := job.Run(); err != nil { if !job.Stdout.Used() { return err diff --git a/engine/streams.go b/engine/streams.go index 07db058b2d..4b1f172b49 100644 --- a/engine/streams.go +++ b/engine/streams.go @@ -109,18 +109,6 @@ func (o *Output) Write(p []byte) (n int, err error) { return len(p), firstErr } -func (o *Output) Flush() { - o.Mutex.Lock() - defer o.Mutex.Unlock() - for _, dst := range o.dests { - if f, ok := dst.(interface { - Flush() - }); ok { - f.Flush() - } - } -} - // Close unregisters all destinations and waits for all background // AddTail and AddString tasks to complete. // The Close method of each destination is called if it exists. diff --git a/server.go b/server.go index 3e4daad77f..49beeb5fb4 100644 --- a/server.go +++ b/server.go @@ -1323,7 +1323,6 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { localName = job.Args[0] tag string sf = utils.NewStreamFormatter(job.GetenvBool("json")) - out = utils.NewWriteFlusher(job.Stdout) authConfig = &auth.AuthConfig{} metaHeaders map[string][]string ) @@ -1338,7 +1337,7 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { if err != nil { if c != nil { // Another pull of the same repository is already taking place; just wait for it to finish - out.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", localName)) + job.Stdout.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", localName)) <-c return engine.StatusOK } @@ -1365,7 +1364,7 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { localName = remoteName } - if err = srv.pullRepository(r, out, localName, remoteName, tag, sf, job.GetenvBool("parallel")); err != nil { + if err = srv.pullRepository(r, job.Stdout, localName, remoteName, tag, sf, job.GetenvBool("parallel")); err != nil { job.Error(err) return engine.StatusErr } @@ -1584,7 +1583,6 @@ func (srv *Server) ImageImport(job *engine.Job) engine.Status { repo = job.Args[1] tag string sf = utils.NewStreamFormatter(job.GetenvBool("json")) - out = utils.NewWriteFlusher(job.Stdout) archive io.Reader resp *http.Response ) @@ -1605,7 +1603,7 @@ func (srv *Server) ImageImport(job *engine.Job) engine.Status { u.Host = src u.Path = "" } - out.Write(sf.FormatStatus("", "Downloading from %s", u)) + job.Stdout.Write(sf.FormatStatus("", "Downloading from %s", u)) // Download with curl (pretty progress bar) // If curl is not available, fallback to http.Get() resp, err = utils.Download(u.String()) @@ -1613,7 +1611,7 @@ func (srv *Server) ImageImport(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } - archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), out, sf, true, "", "Importing") + archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), job.Stdout, sf, true, "", "Importing") } img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) if err != nil { @@ -1627,7 +1625,7 @@ func (srv *Server) ImageImport(job *engine.Job) engine.Status { return engine.StatusErr } } - out.Write(sf.FormatStatus("", img.ID)) + job.Stdout.Write(sf.FormatStatus("", img.ID)) return engine.StatusOK } From 564e6bc7802b606d829a498eee0c2bb8ce4032e1 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 16 Jan 2014 18:40:33 -0800 Subject: [PATCH 220/364] Move docker rmi to a job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 32 ++++++++---- commands.go | 13 +++-- integration/commands_test.go | 6 +-- integration/runtime_test.go | 2 +- integration/server_test.go | 20 ++++---- server.go | 99 ++++++++++++++++++++++-------------- 6 files changed, 101 insertions(+), 71 deletions(-) diff --git a/api.go b/api.go index 13397e6b92..89d4561130 100644 --- a/api.go +++ b/api.go @@ -668,19 +668,31 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R if vars == nil { return fmt.Errorf("Missing parameter") } - name := vars["name"] - imgs, err := srv.ImageDelete(name, version > 1.1) - if err != nil { + var ( + buffer = bytes.NewBuffer(nil) + job = srv.Eng.Job("image_delete", vars["name"]) + ) + job.Stdout.Add(buffer) + job.SetenvBool("autoPrune", version > 1.1) + if err := job.Run(); err != nil { return err } - if imgs != nil { - if len(imgs) != 0 { - return writeJSON(w, http.StatusOK, imgs) - } - return fmt.Errorf("Conflict, %s wasn't deleted", name) + + outs := engine.NewTable("", 0) + if _, err := outs.ReadFrom(buffer); err != nil { + return err } - w.WriteHeader(http.StatusNoContent) - return nil + + if len(outs.Data) != 0 { + var err error + if version < 1.9 { + _, err = outs.WriteTo(w) + } else { + _, err = outs.WriteListTo(w) + } + return err + } + return fmt.Errorf("Conflict, %s wasn't deleted", vars["name"]) } func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/commands.go b/commands.go index 84466c204a..d918efdace 100644 --- a/commands.go +++ b/commands.go @@ -828,18 +828,17 @@ func (cli *DockerCli) CmdRmi(args ...string) error { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") } else { - var outs []APIRmi - err = json.Unmarshal(body, &outs) - if err != nil { + outs := engine.NewTable("Created", 0) + if _, err := outs.ReadFrom(bytes.NewReader(body)); err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") continue } - for _, out := range outs { - if out.Deleted != "" { - fmt.Fprintf(cli.out, "Deleted: %s\n", out.Deleted) + for _, out := range outs.Data { + if out.Get("Deleted") != "" { + fmt.Fprintf(cli.out, "Deleted: %s\n", out.Get("Deleted")) } else { - fmt.Fprintf(cli.out, "Untagged: %s\n", out.Untagged) + fmt.Fprintf(cli.out, "Untagged: %s\n", out.Get("Untagged")) } } } diff --git a/integration/commands_test.go b/integration/commands_test.go index 48819670e7..a0fc4b9523 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -1030,11 +1030,11 @@ func TestContainerOrphaning(t *testing.T) { buildSomething(template2, imageName) // remove the second image by name - resp, err := srv.ImageDelete(imageName, true) + resp, err := srv.DeleteImage(imageName, true) // see if we deleted the first image (and orphaned the container) - for _, i := range resp { - if img1 == i.Deleted { + for _, i := range resp.Data { + if img1 == i.Get("Deleted") { t.Fatal("Orphaned image with container") } } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 008be9ef38..e08643b36d 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -61,7 +61,7 @@ func cleanup(eng *engine.Engine, t *testing.T) error { } for _, image := range images.Data { if image.Get("ID") != unitTestImageID { - mkServerFromEngine(eng, t).ImageDelete(image.Get("ID"), false) + mkServerFromEngine(eng, t).DeleteImage(image.Get("ID"), false) } } return nil diff --git a/integration/server_test.go b/integration/server_test.go index 1347d54b3a..4efe478f9b 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -35,7 +35,7 @@ func TestImageTagImageDelete(t *testing.T) { t.Errorf("Expected %d images, %d found", nExpected, nActual) } - if _, err := srv.ImageDelete("utest/docker:tag2", true); err != nil { + if _, err := srv.DeleteImage("utest/docker:tag2", true); err != nil { t.Fatal(err) } @@ -47,7 +47,7 @@ func TestImageTagImageDelete(t *testing.T) { t.Errorf("Expected %d images, %d found", nExpected, nActual) } - if _, err := srv.ImageDelete("utest:5000/docker:tag3", true); err != nil { + if _, err := srv.DeleteImage("utest:5000/docker:tag3", true); err != nil { t.Fatal(err) } @@ -56,7 +56,7 @@ func TestImageTagImageDelete(t *testing.T) { nExpected = len(initialImages.Data[0].GetList("RepoTags")) + 1 nActual = len(images.Data[0].GetList("RepoTags")) - if _, err := srv.ImageDelete("utest:tag1", true); err != nil { + if _, err := srv.DeleteImage("utest:tag1", true); err != nil { t.Fatal(err) } @@ -358,7 +358,7 @@ func TestRmi(t *testing.T) { t.Fatalf("Expected 2 new images, found %d.", images.Len()-initialImages.Len()) } - _, err = srv.ImageDelete(imageID, true) + _, err = srv.DeleteImage(imageID, true) if err != nil { t.Fatal(err) } @@ -472,18 +472,16 @@ func TestDeleteTagWithExistingContainers(t *testing.T) { } // Try to remove the tag - imgs, err := srv.ImageDelete("utest:tag1", true) + imgs, err := srv.DeleteImage("utest:tag1", true) if err != nil { t.Fatal(err) } - if len(imgs) != 1 { - t.Fatalf("Should only have deleted one untag %d", len(imgs)) + if len(imgs.Data) != 1 { + t.Fatalf("Should only have deleted one untag %d", len(imgs.Data)) } - untag := imgs[0] - - if untag.Untagged != unitTestImageID { - t.Fatalf("Expected %s got %s", unitTestImageID, untag.Untagged) + if untag := imgs.Data[0].Get("Untagged"); untag != unitTestImageID { + t.Fatalf("Expected %s got %s", unitTestImageID, untag) } } diff --git a/server.go b/server.go index 49beeb5fb4..d88dc16e82 100644 --- a/server.go +++ b/server.go @@ -336,6 +336,10 @@ func (srv *Server) ImageExport(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + if err := job.Eng.Register("image_delete", srv.ImageDelete); err != nil { + job.Error(err) + return engine.StatusErr + } return engine.StatusOK } @@ -1813,7 +1817,7 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { var ErrImageReferenced = errors.New("Image referenced by a repository") -func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi, byParents map[string][]*Image) error { +func (srv *Server) deleteImageAndChildren(id string, imgs *engine.Table, byParents map[string][]*Image) error { // If the image is referenced by a repo, do not delete if len(srv.runtime.repositories.ByID()[id]) != 0 { return ErrImageReferenced @@ -1845,14 +1849,16 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi, byParents m if err != nil { return err } - *imgs = append(*imgs, APIRmi{Deleted: id}) + out := &engine.Env{} + out.Set("Deleted", id) + imgs.Add(out) srv.LogEvent("delete", id, "") return nil } return nil } -func (srv *Server) deleteImageParents(img *Image, imgs *[]APIRmi) error { +func (srv *Server) deleteImageParents(img *Image, imgs *engine.Table) error { if img.Parent != "" { parent, err := srv.runtime.graph.Get(img.Parent) if err != nil { @@ -1871,12 +1877,42 @@ func (srv *Server) deleteImageParents(img *Image, imgs *[]APIRmi) error { return nil } -func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, error) { +func (srv *Server) DeleteImage(name string, autoPrune bool) (*engine.Table, error) { var ( - imgs = []APIRmi{} - tags = []string{} + repoName, tag string + img, err = srv.runtime.repositories.LookupImage(name) + imgs = engine.NewTable("", 0) + tags = []string{} ) + if err != nil { + return nil, fmt.Errorf("No such image: %s", name) + } + + // FIXME: What does autoPrune mean ? + if !autoPrune { + if err := srv.runtime.graph.Delete(img.ID); err != nil { + return nil, fmt.Errorf("Cannot delete image %s: %s", name, err) + } + return nil, nil + } + + if !strings.Contains(img.ID, name) { + repoName, tag = utils.ParseRepositoryTag(name) + } + + // If we have a repo and the image is not referenced anywhere else + // then just perform an untag and do not validate. + // + // i.e. only validate if we are performing an actual delete and not + // an untag op + if repoName != "" && len(srv.runtime.repositories.ByID()[img.ID]) == 1 { + // Prevent deletion if image is used by a container + if err := srv.canDeleteImage(img.ID); err != nil { + return nil, err + } + } + //If delete by id, see if the id belong only to one repository if repoName == "" { for _, repoAndTag := range srv.runtime.repositories.ByID()[img.ID] { @@ -1903,17 +1939,19 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro return nil, err } if tagDeleted { - imgs = append(imgs, APIRmi{Untagged: img.ID}) + out := &engine.Env{} + out.Set("Untagged", img.ID) + imgs.Add(out) srv.LogEvent("untag", img.ID, "") } } if len(srv.runtime.repositories.ByID()[img.ID]) == 0 { - if err := srv.deleteImageAndChildren(img.ID, &imgs, nil); err != nil { + if err := srv.deleteImageAndChildren(img.ID, imgs, nil); err != nil { if err != ErrImageReferenced { return imgs, err } - } else if err := srv.deleteImageParents(img, &imgs); err != nil { + } else if err := srv.deleteImageParents(img, imgs); err != nil { if err != ErrImageReferenced { return imgs, err } @@ -1922,39 +1960,22 @@ func (srv *Server) deleteImage(img *Image, repoName, tag string) ([]APIRmi, erro return imgs, nil } -func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) { - var ( - repository, tag string - img, err = srv.runtime.repositories.LookupImage(name) - ) +func (srv *Server) ImageDelete(job *engine.Job) engine.Status { + if n := len(job.Args); n != 1 { + job.Errorf("Usage: %s IMAGE", job.Name) + return engine.StatusErr + } + + imgs, err := srv.DeleteImage(job.Args[0], job.GetenvBool("autoPrune")) if err != nil { - return nil, fmt.Errorf("No such image: %s", name) + job.Error(err) + return engine.StatusErr } - - // FIXME: What does autoPrune mean ? - if !autoPrune { - if err := srv.runtime.graph.Delete(img.ID); err != nil { - return nil, fmt.Errorf("Cannot delete image %s: %s", name, err) - } - return nil, nil + if _, err := imgs.WriteTo(job.Stdout); err != nil { + job.Error(err) + return engine.StatusErr } - - if !strings.Contains(img.ID, name) { - repository, tag = utils.ParseRepositoryTag(name) - } - - // If we have a repo and the image is not referenced anywhere else - // then just perform an untag and do not validate. - // - // i.e. only validate if we are performing an actual delete and not - // an untag op - if repository != "" && len(srv.runtime.repositories.ByID()[img.ID]) == 1 { - // Prevent deletion if image is used by a container - if err := srv.canDeleteImage(img.ID); err != nil { - return nil, err - } - } - return srv.deleteImage(img, repository, tag) + return engine.StatusOK } func (srv *Server) canDeleteImage(imgID string) error { From 177f6588824de5a489f0b31a2cf053c3cdf0bb0e Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 20 Jan 2014 14:10:23 -0800 Subject: [PATCH 221/364] remove buffer Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 26 +++----------------------- commands.go | 4 ++-- server.go | 11 ++++++----- 3 files changed, 11 insertions(+), 30 deletions(-) diff --git a/api.go b/api.go index 89d4561130..698e5b429a 100644 --- a/api.go +++ b/api.go @@ -668,31 +668,11 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R if vars == nil { return fmt.Errorf("Missing parameter") } - var ( - buffer = bytes.NewBuffer(nil) - job = srv.Eng.Job("image_delete", vars["name"]) - ) - job.Stdout.Add(buffer) + var job = srv.Eng.Job("image_delete", vars["name"]) + job.Stdout.Add(w) job.SetenvBool("autoPrune", version > 1.1) - if err := job.Run(); err != nil { - return err - } - outs := engine.NewTable("", 0) - if _, err := outs.ReadFrom(buffer); err != nil { - return err - } - - if len(outs.Data) != 0 { - var err error - if version < 1.9 { - _, err = outs.WriteTo(w) - } else { - _, err = outs.WriteListTo(w) - } - return err - } - return fmt.Errorf("Conflict, %s wasn't deleted", vars["name"]) + return job.Run() } func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/commands.go b/commands.go index d918efdace..5578455761 100644 --- a/commands.go +++ b/commands.go @@ -823,13 +823,13 @@ func (cli *DockerCli) CmdRmi(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - body, _, err := readBody(cli.call("DELETE", "/images/"+name, nil, false)) + stream, _, err := cli.call("DELETE", "/images/"+name, nil, false) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") } else { outs := engine.NewTable("Created", 0) - if _, err := outs.ReadFrom(bytes.NewReader(body)); err != nil { + if _, err := outs.ReadListFrom(stream); err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") continue diff --git a/server.go b/server.go index d88dc16e82..95b8433676 100644 --- a/server.go +++ b/server.go @@ -99,6 +99,7 @@ func jobInitApi(job *engine.Job) engine.Status { "build": srv.Build, "pull": srv.ImagePull, "import": srv.ImageImport, + "image_delete": srv.ImageDelete, } { if err := job.Eng.Register(name, handler); err != nil { job.Error(err) @@ -336,10 +337,6 @@ func (srv *Server) ImageExport(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } - if err := job.Eng.Register("image_delete", srv.ImageDelete); err != nil { - job.Error(err) - return engine.StatusErr - } return engine.StatusOK } @@ -1971,7 +1968,11 @@ func (srv *Server) ImageDelete(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } - if _, err := imgs.WriteTo(job.Stdout); err != nil { + if len(imgs.Data) == 0 { + job.Errorf("Conflict, %s wasn't deleted", job.Args[0]) + return engine.StatusErr + } + if _, err := imgs.WriteListTo(job.Stdout); err != nil { job.Error(err) return engine.StatusErr } From f41e0cf0485eac21d65c1af19a732b350292d200 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 21 Jan 2014 17:56:09 -0800 Subject: [PATCH 222/364] fix error handling Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- commands.go | 43 +++++++++++++++-------------------------- engine/env.go | 12 +++--------- engine/streams.go | 7 ++++++- integration/api_test.go | 10 +++++----- 4 files changed, 30 insertions(+), 42 deletions(-) diff --git a/commands.go b/commands.go index 5578455761..98be8b6edf 100644 --- a/commands.go +++ b/commands.go @@ -823,13 +823,13 @@ func (cli *DockerCli) CmdRmi(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - stream, _, err := cli.call("DELETE", "/images/"+name, nil, false) + body, _, err := readBody(cli.call("DELETE", "/images/"+name, nil, false)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") } else { outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(stream); err != nil { + if _, err := outs.ReadListFrom(body); err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") continue @@ -859,16 +859,13 @@ func (cli *DockerCli) CmdHistory(args ...string) error { return nil } - stream, _, err := cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false) - if stream != nil { - defer stream.Close() - } + body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false)) if err != nil { return err } outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(stream); err != nil { + if _, err := outs.ReadListFrom(body); err != nil { return err } @@ -1139,16 +1136,13 @@ func (cli *DockerCli) CmdImages(args ...string) error { filter := cmd.Arg(0) if *flViz || *flTree { - stream, _, err := cli.call("GET", "/images/json?all=1", nil, false) - if stream != nil { - defer stream.Close() - } + body, _, err := readBody(cli.call("GET", "/images/json?all=1", nil, false)) if err != nil { return err } outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(stream); err != nil { + if _, err := outs.ReadListFrom(body); err != nil { return err } @@ -1211,16 +1205,14 @@ func (cli *DockerCli) CmdImages(args ...string) error { v.Set("all", "1") } - stream, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil, false) - if stream != nil { - defer stream.Close() - } + body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false)) + if err != nil { return err } outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(stream); err != nil { + if _, err := outs.ReadListFrom(body); err != nil { return err } @@ -1532,16 +1524,14 @@ func (cli *DockerCli) CmdDiff(args ...string) error { return nil } - stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false) - if stream != nil { - defer stream.Close() - } + body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false)) + if err != nil { return err } outs := engine.NewTable("", 0) - if _, err := outs.ReadListFrom(stream); err != nil { + if _, err := outs.ReadListFrom(body); err != nil { return err } for _, change := range outs.Data { @@ -1674,15 +1664,14 @@ func (cli *DockerCli) CmdSearch(args ...string) error { v := url.Values{} v.Set("term", cmd.Arg(0)) - stream, _, err := cli.call("GET", "/images/search?"+v.Encode(), nil, true) - if stream != nil { - defer stream.Close() - } + + body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil, false)) + if err != nil { return err } outs := engine.NewTable("star_count", 0) - if _, err := outs.ReadListFrom(stream); err != nil { + if _, err := outs.ReadListFrom(body); err != nil { return err } w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) diff --git a/engine/env.go b/engine/env.go index 37ba2ddb4c..2df303c9e1 100644 --- a/engine/env.go +++ b/engine/env.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "sort" "strconv" "strings" @@ -325,15 +324,10 @@ func (t *Table) WriteTo(dst io.Writer) (n int64, err error) { return n, nil } -func (t *Table) ReadListFrom(src io.Reader) (n int64, err error) { +func (t *Table) ReadListFrom(src []byte) (n int64, err error) { var array []interface{} - content, err := ioutil.ReadAll(src) - if err != nil { - return -1, err - } - - if err := json.Unmarshal(content, &array); err != nil { + if err := json.Unmarshal(src, &array); err != nil { return -1, err } @@ -347,7 +341,7 @@ func (t *Table) ReadListFrom(src io.Reader) (n int64, err error) { } } - return int64(len(content)), nil + return int64(len(src)), nil } func (t *Table) ReadFrom(src io.Reader) (n int64, err error) { diff --git a/engine/streams.go b/engine/streams.go index 4b1f172b49..48f031de8f 100644 --- a/engine/streams.go +++ b/engine/streams.go @@ -5,6 +5,7 @@ import ( "container/ring" "fmt" "io" + "io/ioutil" "sync" ) @@ -228,7 +229,11 @@ func (o *Output) AddListTable() (dst *Table, err error) { o.tasks.Add(1) go func() { defer o.tasks.Done() - if _, err := dst.ReadListFrom(src); err != nil { + content, err := ioutil.ReadAll(src) + if err != nil { + return + } + if _, err := dst.ReadListFrom(content); err != nil { return } }() diff --git a/integration/api_test.go b/integration/api_test.go index 6fac656c99..ec9e4acb7c 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -170,7 +170,7 @@ func TestGetImagesJSON(t *testing.T) { assertHttpNotError(r, t) images := engine.NewTable("Created", 0) - if _, err := images.ReadListFrom(r.Body); err != nil { + if _, err := images.ReadListFrom(r.Body.Bytes()); err != nil { t.Fatal(err) } @@ -205,7 +205,7 @@ func TestGetImagesJSON(t *testing.T) { assertHttpNotError(r2, t) images2 := engine.NewTable("ID", 0) - if _, err := images2.ReadListFrom(r2.Body); err != nil { + if _, err := images2.ReadListFrom(r2.Body.Bytes()); err != nil { t.Fatal(err) } @@ -238,7 +238,7 @@ func TestGetImagesJSON(t *testing.T) { assertHttpNotError(r3, t) images3 := engine.NewTable("ID", 0) - if _, err := images3.ReadListFrom(r3.Body); err != nil { + if _, err := images3.ReadListFrom(r3.Body.Bytes()); err != nil { t.Fatal(err) } @@ -264,7 +264,7 @@ func TestGetImagesHistory(t *testing.T) { assertHttpNotError(r, t) outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(r.Body); err != nil { + if _, err := outs.ReadListFrom(r.Body.Bytes()); err != nil { t.Fatal(err) } if len(outs.Data) != 1 { @@ -409,7 +409,7 @@ func TestGetContainersChanges(t *testing.T) { } assertHttpNotError(r, t) outs := engine.NewTable("", 0) - if _, err := outs.ReadListFrom(r.Body); err != nil { + if _, err := outs.ReadListFrom(r.Body.Bytes()); err != nil { t.Fatal(err) } From 134435a79c06bd178aa6f687fcabeb2caced485a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 15 Jan 2014 17:09:05 -0800 Subject: [PATCH 223/364] move inspect to 2 jobs Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 35 +++++++++++++++++----------------- api_params.go | 4 ---- server.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 21 deletions(-) diff --git a/api.go b/api.go index 698e5b429a..ea685c68dc 100644 --- a/api.go +++ b/api.go @@ -838,41 +838,42 @@ func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r if vars == nil { return fmt.Errorf("Missing parameter") } - name := vars["name"] + var ( + buffer = bytes.NewBuffer(nil) + job = srv.Eng.Job("inspect_container", vars["name"]) + ) - container, err := srv.ContainerInspect(name) - if err != nil { + job.Stdout.Add(buffer) + if err := job.Run(); err != nil { return err } - _, err = srv.ImageInspect(name) - if err == nil { + if err := srv.Eng.Job("inspect_image", vars["name"]).Run(); err == nil { return fmt.Errorf("Conflict between containers and images") } - - container.readHostConfig() - c := APIContainer{container, container.hostConfig} - - return writeJSON(w, http.StatusOK, c) + _, err := io.Copy(w, buffer) + return err } func getImagesByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - name := vars["name"] + var ( + buffer = bytes.NewBuffer(nil) + job = srv.Eng.Job("inspect_image", vars["name"]) + ) - image, err := srv.ImageInspect(name) - if err != nil { + job.Stdout.Add(buffer) + if err := job.Run(); err != nil { return err } - _, err = srv.ContainerInspect(name) - if err == nil { + if err := srv.Eng.Job("inspect_container", vars["name"]).Run(); err == nil { return fmt.Errorf("Conflict between containers and images") } - - return writeJSON(w, http.StatusOK, image) + _, err := io.Copy(w, buffer) + return err } func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/api_params.go b/api_params.go index 92dde2906a..5de1255524 100644 --- a/api_params.go +++ b/api_params.go @@ -67,10 +67,6 @@ type ( Resource string HostPath string } - APIContainer struct { - *Container - HostConfig *HostConfig - } ) func (api APIContainers) ToLegacy() *APIContainersOld { diff --git a/server.go b/server.go index 95b8433676..a52fd20693 100644 --- a/server.go +++ b/server.go @@ -337,6 +337,14 @@ func (srv *Server) ImageExport(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + if err := job.Eng.Register("inspect_image", srv.JobImageInspect); err != nil { + job.Error(err) + return engine.StatusErr + } + if err := job.Eng.Register("inspect_container", srv.JobContainerInspect); err != nil { + job.Error(err) + return engine.StatusErr + } return engine.StatusOK } @@ -2303,6 +2311,31 @@ func (srv *Server) ContainerInspect(name string) (*Container, error) { return nil, fmt.Errorf("No such container: %s", name) } +func (srv *Server) JobContainerInspect(job *engine.Job) engine.Status { + if n := len(job.Args); n != 1 { + job.Errorf("Usage: %s CONTAINER", job.Name) + return engine.StatusErr + } + container, err := srv.ContainerInspect(job.Args[0]) + if err != nil { + job.Error(err) + return engine.StatusErr + } + + type HostConfigPacker struct { + *Container + HostConfig *HostConfig + } + + b, err := json.Marshal(&HostConfigPacker{container, container.hostConfig}) + if err != nil { + job.Error(err) + return engine.StatusErr + } + job.Stdout.Write(b) + return engine.StatusOK +} + func (srv *Server) ImageInspect(name string) (*Image, error) { if image, err := srv.runtime.repositories.LookupImage(name); err == nil && image != nil { return image, nil @@ -2310,6 +2343,25 @@ func (srv *Server) ImageInspect(name string) (*Image, error) { return nil, fmt.Errorf("No such image: %s", name) } +func (srv *Server) JobImageInspect(job *engine.Job) engine.Status { + if n := len(job.Args); n != 1 { + job.Errorf("Usage: %s IMAGE", job.Name) + return engine.StatusErr + } + image, err := srv.ImageInspect(job.Args[0]) + if err != nil { + job.Error(err) + return engine.StatusErr + } + b, err := json.Marshal(image) + if err != nil { + job.Error(err) + return engine.StatusErr + } + job.Stdout.Write(b) + return engine.StatusOK +} + func (srv *Server) ContainerCopy(job *engine.Job) engine.Status { if len(job.Args) != 2 { job.Errorf("Usage: %s CONTAINER RESOURCE\n", job.Name) From a7e9baaf89ae86dba4f93fb76a436e4c86249e4c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 15 Jan 2014 17:43:57 -0800 Subject: [PATCH 224/364] update attach to use the new job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 17 +++++++++++++---- server.go | 6 ++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/api.go b/api.go index ea685c68dc..ee3cbf9d94 100644 --- a/api.go +++ b/api.go @@ -751,8 +751,17 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r return fmt.Errorf("Missing parameter") } - c, err := srv.ContainerInspect(vars["name"]) - if err != nil { + var ( + job = srv.Eng.Job("inspect_container", vars["name"]) + buffer = bytes.NewBuffer(nil) + c Container + ) + job.Stdout.Add(buffer) + if err := job.Run(); err != nil { + return err + } + + if err := json.Unmarshal(buffer.Bytes(), &c); err != nil { return err } @@ -786,7 +795,7 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r errStream = outStream } - job := srv.Eng.Job("attach", vars["name"]) + job = srv.Eng.Job("attach", vars["name"]) job.Setenv("logs", r.Form.Get("logs")) job.Setenv("stream", r.Form.Get("stream")) job.Setenv("stdin", r.Form.Get("stdin")) @@ -810,7 +819,7 @@ func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r * return fmt.Errorf("Missing parameter") } - if _, err := srv.ContainerInspect(vars["name"]); err != nil { + if err := srv.Eng.Job("inspect_container", vars["name"]).Run(); err != nil { return err } diff --git a/server.go b/server.go index a52fd20693..2dd4c7ce23 100644 --- a/server.go +++ b/server.go @@ -2322,12 +2322,10 @@ func (srv *Server) JobContainerInspect(job *engine.Job) engine.Status { return engine.StatusErr } - type HostConfigPacker struct { + b, err := json.Marshal(&struct { *Container HostConfig *HostConfig - } - - b, err := json.Marshal(&HostConfigPacker{container, container.hostConfig}) + }{container, container.hostConfig}) if err != nil { job.Error(err) return engine.StatusErr From 5fd8aa02bae6602bfe6bff541be1e1dc5c1e0fb8 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 20 Jan 2014 15:15:00 -0800 Subject: [PATCH 225/364] merge 2 jobs, no more buffer Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 43 ++++++++----------------------- server.go | 77 ++++++++++++++++++++++++++++--------------------------- 2 files changed, 50 insertions(+), 70 deletions(-) diff --git a/api.go b/api.go index ee3cbf9d94..864a577fd1 100644 --- a/api.go +++ b/api.go @@ -751,8 +751,9 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r return fmt.Errorf("Missing parameter") } + // TODO: replace the buffer by job.AddEnv() var ( - job = srv.Eng.Job("inspect_container", vars["name"]) + job = srv.Eng.Job("inspect", vars["name"], "container") buffer = bytes.NewBuffer(nil) c Container ) @@ -819,7 +820,7 @@ func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r * return fmt.Errorf("Missing parameter") } - if err := srv.Eng.Job("inspect_container", vars["name"]).Run(); err != nil { + if err := srv.Eng.Job("inspect", vars["name"], "container").Run(); err != nil { return err } @@ -847,42 +848,20 @@ func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r if vars == nil { return fmt.Errorf("Missing parameter") } - var ( - buffer = bytes.NewBuffer(nil) - job = srv.Eng.Job("inspect_container", vars["name"]) - ) - - job.Stdout.Add(buffer) - if err := job.Run(); err != nil { - return err - } - - if err := srv.Eng.Job("inspect_image", vars["name"]).Run(); err == nil { - return fmt.Errorf("Conflict between containers and images") - } - _, err := io.Copy(w, buffer) - return err + var job = srv.Eng.Job("inspect", vars["name"], "container") + job.Stdout.Add(w) + job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job + return job.Run() } func getImagesByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - var ( - buffer = bytes.NewBuffer(nil) - job = srv.Eng.Job("inspect_image", vars["name"]) - ) - - job.Stdout.Add(buffer) - if err := job.Run(); err != nil { - return err - } - - if err := srv.Eng.Job("inspect_container", vars["name"]).Run(); err == nil { - return fmt.Errorf("Conflict between containers and images") - } - _, err := io.Copy(w, buffer) - return err + var job = srv.Eng.Job("inspect", vars["name"], "image") + job.Stdout.Add(w) + job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job + return job.Run() } func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/server.go b/server.go index 2dd4c7ce23..f7aaf90812 100644 --- a/server.go +++ b/server.go @@ -100,6 +100,7 @@ func jobInitApi(job *engine.Job) engine.Status { "pull": srv.ImagePull, "import": srv.ImageImport, "image_delete": srv.ImageDelete, + "inspect": srv.JobInspect, } { if err := job.Eng.Register(name, handler); err != nil { job.Error(err) @@ -337,14 +338,6 @@ func (srv *Server) ImageExport(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } - if err := job.Eng.Register("inspect_image", srv.JobImageInspect); err != nil { - job.Error(err) - return engine.StatusErr - } - if err := job.Eng.Register("inspect_container", srv.JobContainerInspect); err != nil { - job.Error(err) - return engine.StatusErr - } return engine.StatusOK } @@ -2311,29 +2304,6 @@ func (srv *Server) ContainerInspect(name string) (*Container, error) { return nil, fmt.Errorf("No such container: %s", name) } -func (srv *Server) JobContainerInspect(job *engine.Job) engine.Status { - if n := len(job.Args); n != 1 { - job.Errorf("Usage: %s CONTAINER", job.Name) - return engine.StatusErr - } - container, err := srv.ContainerInspect(job.Args[0]) - if err != nil { - job.Error(err) - return engine.StatusErr - } - - b, err := json.Marshal(&struct { - *Container - HostConfig *HostConfig - }{container, container.hostConfig}) - if err != nil { - job.Error(err) - return engine.StatusErr - } - job.Stdout.Write(b) - return engine.StatusOK -} - func (srv *Server) ImageInspect(name string) (*Image, error) { if image, err := srv.runtime.repositories.LookupImage(name); err == nil && image != nil { return image, nil @@ -2341,17 +2311,48 @@ func (srv *Server) ImageInspect(name string) (*Image, error) { return nil, fmt.Errorf("No such image: %s", name) } -func (srv *Server) JobImageInspect(job *engine.Job) engine.Status { - if n := len(job.Args); n != 1 { - job.Errorf("Usage: %s IMAGE", job.Name) +func (srv *Server) JobInspect(job *engine.Job) engine.Status { + // TODO: deprecate KIND/conflict + if n := len(job.Args); n != 2 { + job.Errorf("Usage: %s CONTAINER|IMAGE KIND", job.Name) return engine.StatusErr } - image, err := srv.ImageInspect(job.Args[0]) - if err != nil { - job.Error(err) + var ( + name = job.Args[0] + kind = job.Args[1] + object interface{} + conflict = job.GetenvBool("conflict") //should the job detect conflict between containers and images + image, errImage = srv.ImageInspect(name) + container, errContainer = srv.ContainerInspect(name) + ) + + if conflict && image != nil && container != nil { + job.Errorf("Conflict between containers and images") return engine.StatusErr } - b, err := json.Marshal(image) + + switch kind { + case "image": + if errImage != nil { + job.Error(errImage) + return engine.StatusErr + } + object = image + case "container": + if errContainer != nil { + job.Error(errContainer) + return engine.StatusErr + } + object = &struct { + *Container + HostConfig *HostConfig + }{container, container.hostConfig} + default: + job.Errorf("Unknown kind: %s", kind) + return engine.StatusErr + } + + b, err := json.Marshal(object) if err != nil { job.Error(err) return engine.StatusErr From 5cc6312bfc4e511784693d02b9bb8e8d9d1c04b0 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 23 Jan 2014 11:12:17 -0800 Subject: [PATCH 226/364] move events to job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 56 +++++---------------------------------------------- server.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 51 deletions(-) diff --git a/api.go b/api.go index 864a577fd1..73933e5398 100644 --- a/api.go +++ b/api.go @@ -236,61 +236,15 @@ func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Reques } func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - sendEvent := func(wf *utils.WriteFlusher, event *utils.JSONMessage) error { - b, err := json.Marshal(event) - if err != nil { - return fmt.Errorf("JSON error") - } - _, err = wf.Write(b) - if err != nil { - // On error, evict the listener - utils.Errorf("%s", err) - srv.Lock() - delete(srv.listeners, r.RemoteAddr) - srv.Unlock() - return err - } - return nil - } - if err := parseForm(r); err != nil { return err } - listener := make(chan utils.JSONMessage) - srv.Lock() - srv.listeners[r.RemoteAddr] = listener - srv.Unlock() - since, err := strconv.ParseInt(r.Form.Get("since"), 10, 0) - if err != nil { - since = 0 - } + w.Header().Set("Content-Type", "application/json") - wf := utils.NewWriteFlusher(w) - wf.Flush() - if since != 0 { - // If since, send previous events that happened after the timestamp - for _, event := range srv.GetEvents() { - if event.Time >= since { - err := sendEvent(wf, &event) - if err != nil && err.Error() == "JSON error" { - continue - } - if err != nil { - return err - } - } - } - } - for event := range listener { - err := sendEvent(wf, &event) - if err != nil && err.Error() == "JSON error" { - continue - } - if err != nil { - return err - } - } - return nil + var job = srv.Eng.Job("events", r.RemoteAddr) + job.Stdout.Add(utils.NewWriteFlusher(w)) + job.Setenv("since", r.Form.Get("since")) + return job.Run() } func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/server.go b/server.go index f7aaf90812..21ce15419f 100644 --- a/server.go +++ b/server.go @@ -101,6 +101,7 @@ func jobInitApi(job *engine.Job) engine.Status { "import": srv.ImageImport, "image_delete": srv.ImageDelete, "inspect": srv.JobInspect, + "events": srv.Events, } { if err := job.Eng.Register(name, handler); err != nil { job.Error(err) @@ -240,6 +241,65 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { } return engine.StatusOK } +func (srv *Server) Events(job *engine.Job) engine.Status { + if len(job.Args) != 1 { + job.Errorf("Usage: %s FROM", job.Name) + return engine.StatusErr + } + + var ( + from = job.Args[0] + since = job.GetenvInt64("since") + ) + sendEvent := func(event *utils.JSONMessage) error { + b, err := json.Marshal(event) + if err != nil { + return fmt.Errorf("JSON error") + } + _, err = job.Stdout.Write(b) + if err != nil { + // On error, evict the listener + utils.Errorf("%s", err) + srv.Lock() + delete(srv.listeners, from) + srv.Unlock() + return err + } + return nil + } + + listener := make(chan utils.JSONMessage) + srv.Lock() + srv.listeners[from] = listener + srv.Unlock() + job.Stdout.Write(nil) // flush + if since != 0 { + // If since, send previous events that happened after the timestamp + for _, event := range srv.GetEvents() { + if event.Time >= since { + err := sendEvent(&event) + if err != nil && err.Error() == "JSON error" { + continue + } + if err != nil { + job.Error(err) + return engine.StatusErr + } + } + } + } + for event := range listener { + err := sendEvent(&event) + if err != nil && err.Error() == "JSON error" { + continue + } + if err != nil { + job.Error(err) + return engine.StatusErr + } + } + return engine.StatusOK +} func (srv *Server) ContainerExport(job *engine.Job) engine.Status { if len(job.Args) != 1 { From e8ee18fa2993bd47069ba582889b0c83a68f3d43 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 22 Jan 2014 17:33:29 -0800 Subject: [PATCH 227/364] push job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 26 +++++++++++++++----------- server.go | 49 +++++++++++++++++++++++++++++++++---------------- 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/api.go b/api.go index 73933e5398..d2655be0ac 100644 --- a/api.go +++ b/api.go @@ -472,6 +472,10 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht } func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + if vars == nil { + return fmt.Errorf("Missing parameter") + } + metaHeaders := map[string][]string{} for k, v := range r.Header { if strings.HasPrefix(k, "X-Meta-") { @@ -496,23 +500,23 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { return err } - } - if vars == nil { - return fmt.Errorf("Missing parameter") - } - name := vars["name"] if version > 1.0 { w.Header().Set("Content-Type", "application/json") } - sf := utils.NewStreamFormatter(version > 1.0) - if err := srv.ImagePush(name, w, sf, authConfig, metaHeaders); err != nil { - if sf.Used() { - w.Write(sf.FormatError(err)) - return nil + job := srv.Eng.Job("push", vars["name"]) + job.SetenvJson("metaHeaders", metaHeaders) + job.SetenvJson("authConfig", authConfig) + job.SetenvBool("json", version > 1.0) + job.Stdout.Add(utils.NewWriteFlusher(w)) + + if err := job.Run(); err != nil { + if !job.Stdout.Used() { + return err } - return err + sf := utils.NewStreamFormatter(version > 1.0) + w.Write(sf.FormatError(err)) } return nil } diff --git a/server.go b/server.go index 21ce15419f..15b207f6ec 100644 --- a/server.go +++ b/server.go @@ -102,6 +102,7 @@ func jobInitApi(job *engine.Job) engine.Status { "image_delete": srv.ImageDelete, "inspect": srv.JobInspect, "events": srv.Events, + "push": srv.ImagePush, } { if err := job.Eng.Register(name, handler); err != nil { job.Error(err) @@ -1595,44 +1596,60 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, } // FIXME: Allow to interrupt current push when new push of same image is done. -func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig, metaHeaders map[string][]string) error { - if _, err := srv.poolAdd("push", localName); err != nil { - return err +func (srv *Server) ImagePush(job *engine.Job) engine.Status { + if n := len(job.Args); n != 1 { + job.Errorf("Usage: %s IMAGE", job.Name) + return engine.StatusErr } - defer srv.poolRemove("push", localName) + var ( + localName = job.Args[0] + sf = utils.NewStreamFormatter(job.GetenvBool("json")) + authConfig = &auth.AuthConfig{} + metaHeaders map[string][]string + ) + job.GetenvJson("authConfig", authConfig) + job.GetenvJson("metaHeaders", metaHeaders) + if _, err := srv.poolAdd("push", localName); err != nil { + job.Error(err) + return engine.StatusErr + } // Resolve the Repository name from fqn to endpoint + name endpoint, remoteName, err := registry.ResolveRepositoryName(localName) if err != nil { - return err + job.Error(err) + return engine.StatusErr } - out = utils.NewWriteFlusher(out) img, err := srv.runtime.graph.Get(localName) r, err2 := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), endpoint) if err2 != nil { - return err2 + job.Error(err2) + return engine.StatusErr } if err != nil { reposLen := len(srv.runtime.repositories.Repositories[localName]) - out.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", localName, reposLen)) + job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", localName, reposLen)) // If it fails, try to get the repository if localRepo, exists := srv.runtime.repositories.Repositories[localName]; exists { - if err := srv.pushRepository(r, out, localName, remoteName, localRepo, sf); err != nil { - return err + if err := srv.pushRepository(r, job.Stdout, localName, remoteName, localRepo, sf); err != nil { + job.Error(err) + return engine.StatusErr } - return nil + return engine.StatusOK } - return err + job.Error(err) + return engine.StatusErr } var token []string - out.Write(sf.FormatStatus("", "The push refers to an image: [%s]", localName)) - if _, err := srv.pushImage(r, out, remoteName, img.ID, endpoint, token, sf); err != nil { - return err + job.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", localName)) + if _, err := srv.pushImage(r, job.Stdout, remoteName, img.ID, endpoint, token, sf); err != nil { + job.Error(err) + return engine.StatusErr } - return nil + return engine.StatusOK } func (srv *Server) ImageImport(job *engine.Job) engine.Status { From 5ea2986ce5cfce1b86fdc92610dbc6d670691168 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 16 Jan 2014 14:00:18 -0800 Subject: [PATCH 228/364] Move containers to a job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 45 ++++++------ api_params.go | 36 ---------- commands.go | 45 +++++++----- container.go | 28 ++++---- engine/env.go | 8 +++ integration/api_test.go | 23 ++++-- integration/server_test.go | 140 ++++++++++++++++++++++++++++++------- server.go | 59 ++++++++++------ 8 files changed, 240 insertions(+), 144 deletions(-) diff --git a/api.go b/api.go index d2655be0ac..24b18da512 100644 --- a/api.go +++ b/api.go @@ -291,32 +291,37 @@ func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *h if err := parseForm(r); err != nil { 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 } - size, err := getBoolParam(r.Form.Get("size")) - if err != nil { + if err = job.Run(); err != nil { 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 { diff --git a/api_params.go b/api_params.go index 5de1255524..9688063c6f 100644 --- a/api_params.go +++ b/api_params.go @@ -11,29 +11,6 @@ type ( 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 { ID string `json:"Id"` } @@ -68,16 +45,3 @@ type ( 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, - } -} diff --git a/commands.go b/commands.go index 98be8b6edf..572de84da0 100644 --- a/commands.go +++ b/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{} - 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 { - 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) @@ -1362,9 +1362,8 @@ func (cli *DockerCli) CmdPs(args ...string) error { 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 } 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 { - out.ID = utils.TruncateID(out.ID) + outID = utils.TruncateID(outID) } // 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 { + var ( + outCommand = out.Get("Command") + ports = engine.NewTable("", 0) + ) 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 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 { - fmt.Fprintf(w, "%s\n", utils.HumanSize(out.SizeRw)) + fmt.Fprintf(w, "%s\n", utils.HumanSize(out.GetInt64("SizeRw"))) } } else { fmt.Fprint(w, "\n") } } else { - fmt.Fprintln(w, out.ID) + fmt.Fprintln(w, outID) } } diff --git a/container.go b/container.go index a0f14ed810..aafda78bc7 100644 --- a/container.go +++ b/container.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/networkdriver/ipallocator" @@ -175,29 +176,28 @@ type NetworkSettings struct { 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 { p, _ := parsePort(port.Port()) 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 } for _, binding := range bindings { - p, _ := parsePort(port.Port()) + out := &engine.Env{} 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 diff --git a/engine/env.go b/engine/env.go index 2df303c9e1..f93555a40b 100644 --- a/engine/env.go +++ b/engine/env.go @@ -313,6 +313,14 @@ func (t *Table) WriteListTo(dst io.Writer) (n int64, err error) { 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) { for _, env := range t.Data { bytes, err := env.WriteTo(dst) diff --git a/integration/api_test.go b/integration/api_test.go index ec9e4acb7c..95cae47e15 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -302,7 +302,16 @@ func TestGetContainersJSON(t *testing.T) { defer mkRuntimeFromEngine(eng, t).Nuke() 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{ Image: unitTestImageID, @@ -323,15 +332,15 @@ func TestGetContainersJSON(t *testing.T) { t.Fatal(err) } 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) } - 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) } } diff --git a/integration/server_test.go b/integration/server_test.go index 4efe478f9b..681e2e3718 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -69,7 +69,6 @@ func TestImageTagImageDelete(t *testing.T) { func TestCreateRm(t *testing.T) { eng := NewTestEngine(t) - srv := mkServerFromEngine(eng, t) defer mkRuntimeFromEngine(eng, t).Nuke() config, _, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil) @@ -79,25 +78,44 @@ func TestCreateRm(t *testing.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("container_delete", id) + if len(outs.Data) != 1 { + t.Errorf("Expected 1 container, %v found", len(outs.Data)) + } + + job = eng.Job("container_delete", id) job.SetenvBool("removeVolume", true) if err := job.Run(); err != nil { 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) { eng := NewTestEngine(t) - srv := mkServerFromEngine(eng, t) defer mkRuntimeFromEngine(eng, t).Nuke() 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) - 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 { t.Fatal(err) } @@ -131,8 +159,18 @@ func TestCreateRmVolumes(t *testing.T) { 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) - 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 { t.Fatal(err) } @@ -200,13 +248,23 @@ func TestRestartKillWait(t *testing.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() { - job = srv.Eng.Job("wait", c[0].ID) + job = srv.Eng.Job("wait", outs.Data[0].Get("ID")) var statusStr string job.Stdout.AddString(&statusStr) if err := job.Run(); err != nil { @@ -227,11 +285,21 @@ func TestCreateStartRestartStopStartKillRm(t *testing.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) } - 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 { t.Fatal(err) } @@ -270,8 +338,18 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { 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") } - 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 diff --git a/server.go b/server.go index 15b207f6ec..cacfa715fc 100644 --- a/server.go +++ b/server.go @@ -103,6 +103,7 @@ func jobInitApi(job *engine.Job) engine.Status { "inspect": srv.JobInspect, "events": srv.Events, "push": srv.ImagePush, + "containers": srv.Containers, } { if err := job.Eng.Register(name, handler); err != nil { job.Error(err) @@ -1055,10 +1056,17 @@ func (srv *Server) ContainerChanges(job *engine.Job) engine.Status { 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{} 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 } displayed++ - c := createAPIContainer(names[container.ID], container, size, srv.runtime) - out = append(out, c) + 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) } - return out + outs.ReverseSort() + if _, err := outs.WriteListTo(job.Stdout); err != nil { + job.Error(err) + return engine.StatusErr + } + return engine.StatusOK } -func createAPIContainer(names []string, container *Container, size bool, runtime *Runtime) APIContainers { - c := APIContainers{ - ID: container.ID, - } - 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() - } - return c -} func (srv *Server) ContainerCommit(job *engine.Job) engine.Status { if len(job.Args) != 1 { job.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) From 1f8b1bb84e65cfd84f938eb01e1a48c33fce5a27 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 24 Jan 2014 16:39:41 -0800 Subject: [PATCH 229/364] move auth to a job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 16 +++++++++++----- api_params.go | 4 ---- commands.go | 6 +++--- server.go | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/api.go b/api.go index 24b18da512..bf9f29b57f 100644 --- a/api.go +++ b/api.go @@ -122,17 +122,23 @@ func matchesContentType(contentType, expectedType string) bool { } func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - authConfig := &auth.AuthConfig{} - err := json.NewDecoder(r.Body).Decode(authConfig) + var ( + authConfig, err = ioutil.ReadAll(r.Body) + job = srv.Eng.Job("auth") + status string + ) if err != nil { return err } - status, err := auth.Login(authConfig, srv.HTTPRequestFactory(nil)) - if err != nil { + job.Setenv("authConfig", string(authConfig)) + job.Stdout.AddString(&status) + if err = job.Run(); err != nil { return err } if status != "" { - return writeJSON(w, http.StatusOK, &APIAuth{Status: status}) + var env engine.Env + env.Set("Status", status) + return writeJSON(w, http.StatusOK, env) } w.WriteHeader(http.StatusNoContent) return nil diff --git a/api_params.go b/api_params.go index 9688063c6f..fb5ad6f388 100644 --- a/api_params.go +++ b/api_params.go @@ -31,10 +31,6 @@ type ( StatusCode int } - APIAuth struct { - Status string - } - APIImageConfig struct { ID string `json:"Id"` *Config diff --git a/commands.go b/commands.go index 572de84da0..08b2c01139 100644 --- a/commands.go +++ b/commands.go @@ -345,15 +345,15 @@ func (cli *DockerCli) CmdLogin(args ...string) error { return err } - var out2 APIAuth + var out2 engine.Env err = json.Unmarshal(body, &out2) if err != nil { cli.configFile, _ = auth.LoadConfig(os.Getenv("HOME")) return err } auth.SaveConfig(cli.configFile) - if out2.Status != "" { - fmt.Fprintf(cli.out, "%s\n", out2.Status) + if out2.Get("Status") != "" { + fmt.Fprintf(cli.out, "%s\n", out2.Get("Status")) } return nil } diff --git a/server.go b/server.go index cacfa715fc..5fbde5865d 100644 --- a/server.go +++ b/server.go @@ -104,6 +104,7 @@ func jobInitApi(job *engine.Job) engine.Status { "events": srv.Events, "push": srv.ImagePush, "containers": srv.Containers, + "auth": srv.Auth, } { if err := job.Eng.Register(name, handler); err != nil { job.Error(err) @@ -243,6 +244,19 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { } return engine.StatusOK } + +func (srv *Server) Auth(job *engine.Job) engine.Status { + authConfig := &auth.AuthConfig{} + job.GetenvJson("authConfig", authConfig) + status, err := auth.Login(authConfig, srv.HTTPRequestFactory(nil)) + if err != nil { + job.Error(err) + return engine.StatusErr + } + job.Printf("%s\n", status) + return engine.StatusOK +} + func (srv *Server) Events(job *engine.Job) engine.Status { if len(job.Args) != 1 { job.Errorf("Usage: %s FROM", job.Name) From b1d26985d0dfcbecc8264124fa81ff843a6d574b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 24 Jan 2014 17:18:48 -0800 Subject: [PATCH 230/364] Update tests to have custom routes Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- networkdriver/network.go | 6 +++++- networkdriver/network_test.go | 17 ++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/networkdriver/network.go b/networkdriver/network.go index cce91d7a55..f8a807975b 100644 --- a/networkdriver/network.go +++ b/networkdriver/network.go @@ -12,6 +12,10 @@ var ( ErrNetworkOverlaps = errors.New("requested network overlaps with existing network") ) +var ( + networkGetRoutesFct = netlink.NetworkGetRoutes +) + func CheckNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error { if len(nameservers) > 0 { for _, ns := range nameservers { @@ -28,7 +32,7 @@ func CheckNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error { } func CheckRouteOverlaps(toCheck *net.IPNet) error { - networks, err := netlink.NetworkGetRoutes() + networks, err := networkGetRoutesFct() if err != nil { return err } diff --git a/networkdriver/network_test.go b/networkdriver/network_test.go index 8905dad053..c15f8b1cf5 100644 --- a/networkdriver/network_test.go +++ b/networkdriver/network_test.go @@ -35,12 +35,19 @@ func TestOverlapingNameservers(t *testing.T) { } func TestCheckRouteOverlaps(t *testing.T) { - routesData := []string{"10.0.2.0/32", "10.0.3.0/24", "10.0.42.0/24", "172.16.42.0/24", "192.168.142.0/24"} + orig := networkGetRoutesFct + defer func() { + networkGetRoutesFct = orig + }() + networkGetRoutesFct = func() ([]netlink.Route, error) { + routesData := []string{"10.0.2.0/32", "10.0.3.0/24", "10.0.42.0/24", "172.16.42.0/24", "192.168.142.0/24"} - routes := []netlink.Route{} - for _, addr := range routesData { - _, netX, _ := net.ParseCIDR(addr) - routes = append(routes, netlink.Route{IPNet: netX}) + routes := []netlink.Route{} + for _, addr := range routesData { + _, netX, _ := net.ParseCIDR(addr) + routes = append(routes, netlink.Route{IPNet: netX}) + } + return routes, nil } _, netX, _ := net.ParseCIDR("172.16.0.1/24") From 5c04f1bcc70e81fe3c93b1c246ce17a11304ad7f Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 24 Jan 2014 16:57:04 -0600 Subject: [PATCH 231/364] network: remove unnecessary links iptables rule for return traffic Currently there are two iptables rules per port for each link: one to allow the parent to connect to the child's port, and another one to allow return traffic from the child back to the parent. The second rule shouldn't be needed because the "ctstate RELATED,ESTABLISHED" rule can already allow all established traffic. So this patch does the following: 1. Move the RELATED,ESTABLISHED rule to be _before_ the potential inter-container communication DROP rule so it will work for inter-container traffic as well. Since we're inserting, everything is reversed chronologically so it should be inserted _after_ we insert the DROP. This also has a small performance benefit because it will be processed earlier and it's generally one of the most commonly used rules. 2. Get rid of the unnecessary return traffic rule per link. 3. Also move the other "Accept all non-intercontainer outgoing packets" rule to earlier. This gives a small performance benefit since it's also a commonly used rule, and it makes sense to logically group it next to the ctstate rule. Docker-DCO-1.1-Signed-off-by: Josh Poimboeuf (github: jpoimboe) --- links.go | 12 ------------ network.go | 45 +++++++++++++++++++++++---------------------- 2 files changed, 23 insertions(+), 34 deletions(-) diff --git a/links.go b/links.go index 55834b92d2..cd96b56629 100644 --- a/links.go +++ b/links.go @@ -131,18 +131,6 @@ func (l *Link) toggle(action string, ignoreErrors bool) error { } else if len(output) != 0 { return fmt.Errorf("Error toggle iptables forward: %s", output) } - - if output, err := iptables.Raw(action, "FORWARD", - "-i", l.BridgeInterface, "-o", l.BridgeInterface, - "-p", p.Proto(), - "-s", l.ChildIP, - "--sport", p.Port(), - "-d", l.ParentIP, - "-j", "ACCEPT"); !ignoreErrors && err != nil { - return err - } else if len(output) != 0 { - return fmt.Errorf("Error toggle iptables forward: %s", output) - } } return nil } diff --git a/network.go b/network.go index 7414b348a2..50839d7d20 100644 --- a/network.go +++ b/network.go @@ -570,28 +570,6 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { } } - // Accept incoming packets for existing connections - existingArgs := []string{"FORWARD", "-o", config.BridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"} - - if !iptables.Exists(existingArgs...) { - if output, err := iptables.Raw(append([]string{"-I"}, existingArgs...)...); err != nil { - return nil, fmt.Errorf("Unable to allow incoming packets: %s", err) - } else if len(output) != 0 { - return nil, fmt.Errorf("Error iptables allow incoming: %s", output) - } - } - - // Accept all non-intercontainer outgoing packets - outgoingArgs := []string{"FORWARD", "-i", config.BridgeIface, "!", "-o", config.BridgeIface, "-j", "ACCEPT"} - - if !iptables.Exists(outgoingArgs...) { - if output, err := iptables.Raw(append([]string{"-I"}, outgoingArgs...)...); err != nil { - return nil, fmt.Errorf("Unable to allow outgoing packets: %s", err) - } else if len(output) != 0 { - return nil, fmt.Errorf("Error iptables allow outgoing: %s", output) - } - } - args := []string{"FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j"} acceptArgs := append(args, "ACCEPT") dropArgs := append(args, "DROP") @@ -617,6 +595,29 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { } } } + + // Accept all non-intercontainer outgoing packets + outgoingArgs := []string{"FORWARD", "-i", config.BridgeIface, "!", "-o", config.BridgeIface, "-j", "ACCEPT"} + + if !iptables.Exists(outgoingArgs...) { + if output, err := iptables.Raw(append([]string{"-I"}, outgoingArgs...)...); err != nil { + return nil, fmt.Errorf("Unable to allow outgoing packets: %s", err) + } else if len(output) != 0 { + return nil, fmt.Errorf("Error iptables allow outgoing: %s", output) + } + } + + // Accept incoming packets for existing connections + existingArgs := []string{"FORWARD", "-o", config.BridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"} + + if !iptables.Exists(existingArgs...) { + if output, err := iptables.Raw(append([]string{"-I"}, existingArgs...)...); err != nil { + return nil, fmt.Errorf("Unable to allow incoming packets: %s", err) + } else if len(output) != 0 { + return nil, fmt.Errorf("Error iptables allow incoming: %s", output) + } + } + } tcpPortAllocator, err := newPortAllocator() From 33ee78004f0f2a3cd760952f724c5ea0a9d24b37 Mon Sep 17 00:00:00 2001 From: Simon Taranto Date: Sat, 25 Jan 2014 23:30:27 -0700 Subject: [PATCH 232/364] Update README.md Docker-DCO-1.1-Signed-off-by: Simon Taranto (github: srt32) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 12ffc2e8ec..b6b77d6e61 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ hundreds of thousands of applications and databases. ## Better than VMs -A common method for distributing applications and sandbox their +A common method for distributing applications and sandboxing their execution is to use virtual machines, or VMs. Typical VM formats are VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In theory these formats should allow every developer to automatically From 31e59580092bd4f144bf512e2fd13dcba426b02a Mon Sep 17 00:00:00 2001 From: Aaron Feng Date: Sat, 25 Jan 2014 20:22:59 -0500 Subject: [PATCH 233/364] move image name into correct position Docker-DCO-1.1-Signed-off-by: Aaron Feng (github: aaronfeng) --- docs/sources/use/working_with_volumes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/use/working_with_volumes.rst b/docs/sources/use/working_with_volumes.rst index 82d5806954..34728cbd3d 100644 --- a/docs/sources/use/working_with_volumes.rst +++ b/docs/sources/use/working_with_volumes.rst @@ -73,7 +73,7 @@ data volumes from multiple containers. Interestingly, you can mount the volumes that came from the ``DATA`` container in yet another container via the ``client1`` middleman container:: - $ docker run -t -i -rm -volumes-from client1 ubuntu -name client2 bash + $ docker run -t -i -rm -volumes-from client1 -name client2 ubuntu bash This allows you to abstract the actual data source from users of that data, similar to :ref:`ambassador_pattern_linking `. From 2ba2a8ae40b83ac1cc0c4a60724073fa8255b257 Mon Sep 17 00:00:00 2001 From: Mike Naberezny Date: Sun, 26 Jan 2014 11:35:45 -0800 Subject: [PATCH 234/364] Use exec to replace bash with apache2 after environment is set up Docker-DCO-1.1-Signed-off-by: Mike Naberezny (github: mnaberez) --- docs/sources/examples/using_supervisord.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/examples/using_supervisord.rst b/docs/sources/examples/using_supervisord.rst index c32ba0cc0b..eed063292d 100644 --- a/docs/sources/examples/using_supervisord.rst +++ b/docs/sources/examples/using_supervisord.rst @@ -70,7 +70,7 @@ Let's see what is inside our ``supervisord.conf`` file. command=/usr/sbin/sshd -D [program:apache2] - command=/bin/bash -c "source /etc/apache2/envvars && /usr/sbin/apache2 -DFOREGROUND" + command=/bin/bash -c "source /etc/apache2/envvars && exec /usr/sbin/apache2 -DFOREGROUND" The ``supervisord.conf`` configuration file contains directives that configure Supervisor and the processes it manages. The first block ``[supervisord]`` From b994c1315997945815e7b927b8493595b194d016 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 24 Jan 2014 23:01:12 -0700 Subject: [PATCH 235/364] Update hack/make.sh with slightly clearer output and a more precise "are we in the Docker container?" check Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/make.sh | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/hack/make.sh b/hack/make.sh index 6029c9ec10..ef13c1a283 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -25,12 +25,18 @@ set -o pipefail # We're a nice, sexy, little shell script, and people might try to run us; # but really, they shouldn't. We want to be in a container! -RESOLVCONF=$(readlink --canonicalize /etc/resolv.conf) -grep -q "$RESOLVCONF" /proc/mounts || { - echo >&2 "# WARNING! I don't seem to be running in a docker container." - echo >&2 "# The result of this command might be an incorrect build, and will not be officially supported." - echo >&2 "# Try this: 'make all'" -} +if [ "$(pwd)" != '/go/src/github.com/dotcloud/docker' ] || [ -z "$DOCKER_CROSSPLATFORMS" ]; then + { + echo "# WARNING! I don't seem to be running in the Docker container." + echo "# The result of this command might be an incorrect build, and will not be" + echo "# officially supported." + echo "#" + echo "# Try this instead: make all" + echo "#" + } >&2 +fi + +echo # List of bundles to create when no argument is passed DEFAULT_BUNDLES=( From 303ed3c8300183bab09a36b58e0c1db89d12424a Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 23 Jan 2014 06:43:50 -0800 Subject: [PATCH 236/364] Add port allocator and move ipset into orderedintset Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- networkdriver/ipallocator/allocator.go | 4 +- networkdriver/portallocator/portallocator.go | 103 ++++++++++++ .../portallocator/portallocator_test.go | 150 ++++++++++++++++++ .../collections/orderedintset.go | 23 +-- 4 files changed, 270 insertions(+), 10 deletions(-) create mode 100644 networkdriver/portallocator/portallocator.go create mode 100644 networkdriver/portallocator/portallocator_test.go rename networkdriver/ipallocator/ipset.go => pkg/collections/orderedintset.go (72%) diff --git a/networkdriver/ipallocator/allocator.go b/networkdriver/ipallocator/allocator.go index 09319d1332..602c9d220b 100644 --- a/networkdriver/ipallocator/allocator.go +++ b/networkdriver/ipallocator/allocator.go @@ -4,11 +4,13 @@ import ( "encoding/binary" "errors" "github.com/dotcloud/docker/networkdriver" + "github.com/dotcloud/docker/pkg/collections" + "github.com/dotcloud/docker/pkg/netlink" "net" "sync" ) -type networkSet map[string]*iPSet +type networkSet map[iPNet]*collections.OrderedIntSet var ( ErrNoAvailableIPs = errors.New("no available ip addresses on network") diff --git a/networkdriver/portallocator/portallocator.go b/networkdriver/portallocator/portallocator.go new file mode 100644 index 0000000000..a0a3ebd8d2 --- /dev/null +++ b/networkdriver/portallocator/portallocator.go @@ -0,0 +1,103 @@ +package portallocator + +import ( + "errors" + "github.com/dotcloud/docker/pkg/collections" + "sync" +) + +type portMappings map[string]*collections.OrderedIntSet + +const ( + BeginPortRange = 49153 + EndPortRange = 65535 +) + +var ( + ErrPortAlreadyAllocated = errors.New("port has already been allocated") + ErrPortExceedsRange = errors.New("port exceeds upper range") + ErrUnknownProtocol = errors.New("unknown protocol") +) + +var ( + lock = sync.Mutex{} + allocatedPorts = portMappings{} + availablePorts = portMappings{} +) + +func init() { + allocatedPorts["udp"] = collections.NewOrderedIntSet() + availablePorts["udp"] = collections.NewOrderedIntSet() + allocatedPorts["tcp"] = collections.NewOrderedIntSet() + availablePorts["tcp"] = collections.NewOrderedIntSet() +} + +// RequestPort returns an available port if the port is 0 +// If the provided port is not 0 then it will be checked if +// it is available for allocation +func RequestPort(proto string, port int) (int, error) { + lock.Lock() + defer lock.Unlock() + + if err := validateProtocol(proto); err != nil { + return 0, err + } + + var ( + allocated = allocatedPorts[proto] + available = availablePorts[proto] + ) + + if port != 0 { + if allocated.Exists(port) { + return 0, ErrPortAlreadyAllocated + } + available.Remove(port) + allocated.Push(port) + return port, nil + } + + next := available.Pop() + if next == 0 { + next = allocated.PullBack() + if next == 0 { + next = BeginPortRange + } else { + next++ + } + if next > EndPortRange { + return 0, ErrPortExceedsRange + } + } + + allocated.Push(next) + return next, nil +} + +// ReleasePort will return the provided port back into the +// pool for reuse +func ReleasePort(proto string, port int) error { + lock.Lock() + defer lock.Unlock() + + if err := validateProtocol(proto); err != nil { + return err + } + + var ( + allocated = allocatedPorts[proto] + available = availablePorts[proto] + ) + + allocated.Remove(port) + available.Push(port) + + return nil +} + +func validateProtocol(proto string) error { + if _, exists := allocatedPorts[proto]; !exists { + return ErrUnknownProtocol + } + return nil +} diff --git a/networkdriver/portallocator/portallocator_test.go b/networkdriver/portallocator/portallocator_test.go new file mode 100644 index 0000000000..603bf5a15a --- /dev/null +++ b/networkdriver/portallocator/portallocator_test.go @@ -0,0 +1,150 @@ +package portallocator + +import ( + "github.com/dotcloud/docker/pkg/collections" + "testing" +) + +func reset() { + lock.Lock() + defer lock.Unlock() + + allocatedPorts = portMappings{} + availablePorts = portMappings{} + + allocatedPorts["udp"] = collections.NewOrderedIntSet() + availablePorts["udp"] = collections.NewOrderedIntSet() + allocatedPorts["tcp"] = collections.NewOrderedIntSet() + availablePorts["tcp"] = collections.NewOrderedIntSet() +} + +func TestRequestNewPort(t *testing.T) { + defer reset() + + port, err := RequestPort("tcp", 0) + if err != nil { + t.Fatal(err) + } + + if expected := BeginPortRange; port != expected { + t.Fatalf("Expected port %d got %d", expected, port) + } +} + +func TestRequestSpecificPort(t *testing.T) { + defer reset() + + port, err := RequestPort("tcp", 5000) + if err != nil { + t.Fatal(err) + } + if port != 5000 { + t.Fatalf("Expected port 5000 got %d", port) + } +} + +func TestReleasePort(t *testing.T) { + defer reset() + + port, err := RequestPort("tcp", 5000) + if err != nil { + t.Fatal(err) + } + if port != 5000 { + t.Fatalf("Expected port 5000 got %d", port) + } + + if err := ReleasePort("tcp", 5000); err != nil { + t.Fatal(err) + } +} + +func TestReuseReleasedPort(t *testing.T) { + defer reset() + + port, err := RequestPort("tcp", 5000) + if err != nil { + t.Fatal(err) + } + if port != 5000 { + t.Fatalf("Expected port 5000 got %d", port) + } + + if err := ReleasePort("tcp", 5000); err != nil { + t.Fatal(err) + } + + port, err = RequestPort("tcp", 5000) + if err != nil { + t.Fatal(err) + } +} + +func TestReleaseUnreadledPort(t *testing.T) { + defer reset() + + port, err := RequestPort("tcp", 5000) + if err != nil { + t.Fatal(err) + } + if port != 5000 { + t.Fatalf("Expected port 5000 got %d", port) + } + + port, err = RequestPort("tcp", 5000) + if err != ErrPortAlreadyAllocated { + t.Fatalf("Expected error %s got %s", ErrPortAlreadyAllocated, err) + } +} + +func TestUnknowProtocol(t *testing.T) { + defer reset() + + if _, err := RequestPort("tcpp", 0); err != ErrUnknownProtocol { + t.Fatalf("Expected error %s got %s", ErrUnknownProtocol, err) + } +} + +func TestAllocateAllPorts(t *testing.T) { + defer reset() + + for i := 0; i <= EndPortRange-BeginPortRange; i++ { + port, err := RequestPort("tcp", 0) + if err != nil { + t.Fatal(err) + } + + if expected := BeginPortRange + i; port != expected { + t.Fatalf("Expected port %d got %d", expected, port) + } + } + + if _, err := RequestPort("tcp", 0); err != ErrPortExceedsRange { + t.Fatalf("Expected error %s got %s", ErrPortExceedsRange, err) + } + + _, err := RequestPort("udp", 0) + if err != nil { + t.Fatal(err) + } +} + +func BenchmarkAllocatePorts(b *testing.B) { + defer reset() + + b.StartTimer() + for i := 0; i < b.N; i++ { + for i := 0; i <= EndPortRange-BeginPortRange; i++ { + port, err := RequestPort("tcp", 0) + if err != nil { + b.Fatal(err) + } + + if expected := BeginPortRange + i; port != expected { + b.Fatalf("Expected port %d got %d", expected, port) + } + } + reset() + } + b.StopTimer() +} diff --git a/networkdriver/ipallocator/ipset.go b/pkg/collections/orderedintset.go similarity index 72% rename from networkdriver/ipallocator/ipset.go rename to pkg/collections/orderedintset.go index 43d54691d1..456975cbb0 100644 --- a/networkdriver/ipallocator/ipset.go +++ b/pkg/collections/orderedintset.go @@ -1,18 +1,23 @@ -package ipallocator +package collections import ( "sort" "sync" ) -// iPSet is a thread-safe sorted set and a stack. -type iPSet struct { +// OrderedIntSet is a thread-safe sorted set and a stack. +type OrderedIntSet struct { sync.RWMutex set []int } +// NewOrderedSet returns an initialized OrderedSet +func NewOrderedIntSet() *OrderedIntSet { + return &OrderedIntSet{} +} + // Push takes a string and adds it to the set. If the elem aready exists, it has no effect. -func (s *iPSet) Push(elem int) { +func (s *OrderedIntSet) Push(elem int) { s.RLock() for _, e := range s.set { if e == elem { @@ -30,13 +35,13 @@ func (s *iPSet) Push(elem int) { } // Pop is an alias to PopFront() -func (s *iPSet) Pop() int { +func (s *OrderedIntSet) Pop() int { return s.PopFront() } // Pop returns the first elemen from the list and removes it. // If the list is empty, it returns 0 -func (s *iPSet) PopFront() int { +func (s *OrderedIntSet) PopFront() int { s.RLock() for i, e := range s.set { @@ -55,7 +60,7 @@ func (s *iPSet) PopFront() int { // PullBack retrieve the last element of the list. // The element is not removed. // If the list is empty, an empty element is returned. -func (s *iPSet) PullBack() int { +func (s *OrderedIntSet) PullBack() int { if len(s.set) == 0 { return 0 } @@ -63,7 +68,7 @@ func (s *iPSet) PullBack() int { } // Exists checks if the given element present in the list. -func (s *iPSet) Exists(elem int) bool { +func (s *OrderedIntSet) Exists(elem int) bool { for _, e := range s.set { if e == elem { return true @@ -74,7 +79,7 @@ func (s *iPSet) Exists(elem int) bool { // Remove removes an element from the list. // If the element is not found, it has no effect. -func (s *iPSet) Remove(elem int) { +func (s *OrderedIntSet) Remove(elem int) { for i, e := range s.set { if e == elem { s.set = append(s.set[:i], s.set[i+1:]...) From da30eb7c2038c8c554e1f44c80c061c7a827a429 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 23 Jan 2014 17:35:39 -0800 Subject: [PATCH 237/364] Remove std sort and use custom sort for performances Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- pkg/collections/orderedintset.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/collections/orderedintset.go b/pkg/collections/orderedintset.go index 456975cbb0..23abab04d3 100644 --- a/pkg/collections/orderedintset.go +++ b/pkg/collections/orderedintset.go @@ -1,7 +1,6 @@ package collections import ( - "sort" "sync" ) @@ -28,9 +27,17 @@ func (s *OrderedIntSet) Push(elem int) { s.RUnlock() s.Lock() - s.set = append(s.set, elem) + // Make sure the list is always sorted - sort.Ints(s.set) + for i, e := range s.set { + if elem < e { + s.set = append(s.set[:i], append([]int{elem}, s.set[i:]...)...) + s.Unlock() + return + } + } + // If we reach here, then elem is the biggest elem of the list. + s.set = append(s.set, elem) s.Unlock() } From ac2a4e64106b28d7b05d1ee31716af365424ff83 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 23 Jan 2014 07:46:42 -0800 Subject: [PATCH 238/364] Refactor to support multiple ip addresses Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- network.go | 3 +- networkdriver/portallocator/portallocator.go | 91 ++++++++++++++++---- 2 files changed, 73 insertions(+), 21 deletions(-) diff --git a/network.go b/network.go index 3ec1f6fb73..ddb0af5bc7 100644 --- a/network.go +++ b/network.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/dotcloud/docker/networkdriver" "github.com/dotcloud/docker/networkdriver/ipallocator" + "github.com/dotcloud/docker/networkdriver/portallocator" "github.com/dotcloud/docker/pkg/iptables" "github.com/dotcloud/docker/pkg/netlink" "github.com/dotcloud/docker/proxy" @@ -20,8 +21,6 @@ const ( DefaultNetworkBridge = "docker0" DisableNetworkBridge = "none" DefaultNetworkMtu = 1500 - portRangeStart = 49153 - portRangeEnd = 65535 siocBRADDBR = 0x89a0 ) diff --git a/networkdriver/portallocator/portallocator.go b/networkdriver/portallocator/portallocator.go index a0a3ebd8d2..5c7ad04caa 100644 --- a/networkdriver/portallocator/portallocator.go +++ b/networkdriver/portallocator/portallocator.go @@ -3,11 +3,19 @@ package portallocator import ( "errors" "github.com/dotcloud/docker/pkg/collections" + "net" "sync" ) type portMappings map[string]*collections.OrderedIntSet +type ipData struct { + allocatedPorts portMappings + availablePorts portMappings +} + +type ipMapping map[net.IP]*ipData + const ( BeginPortRange = 49153 EndPortRange = 65535 @@ -20,22 +28,62 @@ var ( ) var ( - lock = sync.Mutex{} - allocatedPorts = portMappings{} - availablePorts = portMappings{} + defaultIPData *ipData + + lock = sync.Mutex{} + ips = ipMapping{} + defaultIP = net.ParseIP("0.0.0.0") ) func init() { - allocatedPorts["udp"] = collections.NewOrderedIntSet() - availablePorts["udp"] = collections.NewOrderedIntSet() - allocatedPorts["tcp"] = collections.NewOrderedIntSet() - availablePorts["tcp"] = collections.NewOrderedIntSet() + defaultIPData = newIpData() + ips[defaultIP] = defaultIP +} + +func newIpData() { + data := &ipData{ + allocatedPorts: portMappings{}, + availablePorts: portMappings{}, + } + + data.allocatedPorts["udp"] = collections.NewOrderedIntSet() + data.availablePorts["udp"] = collections.NewOrderedIntSet() + data.allocatedPorts["tcp"] = collections.NewOrderedIntSet() + data.availablePorts["tcp"] = collections.NewOrderedIntSet() + + return data +} + +func getData(ip net.IP) *ipData { + data, exists := ips[ip] + if !exists { + data = newIpData() + ips[ip] = data + } + return data +} + +func validateMapping(data *ipData, proto string, port int) error { + allocated := data.allocatedPorts[proto] + if allocated.Exists(proto) { + return ErrPortAlreadyAllocated + } + return nil +} + +func usePort(data *ipData, proto string, port int) { + allocated, available := data.allocatedPorts[proto], data.availablePorts[proto] + for i := 0; i < 2; i++ { + allocated.Push(port) + available.Remove(port) + allocated, available = defaultIPData.allocatedPorts[proto], defaultIPData.availablePorts[proto] + } } // RequestPort returns an available port if the port is 0 // If the provided port is not 0 then it will be checked if // it is available for allocation -func RequestPort(proto string, port int) (int, error) { +func RequestPort(ip net.IP, proto string, port int) (int, error) { lock.Lock() defer lock.Unlock() @@ -43,20 +91,28 @@ func RequestPort(proto string, port int) (int, error) { return 0, err } - var ( - allocated = allocatedPorts[proto] - available = availablePorts[proto] - ) + data := getData(ip) + allocated, available := data.allocatedPorts[proto], data.availablePorts[proto] + // If the user requested a specific port to be allocated if port != 0 { - if allocated.Exists(port) { - return 0, ErrPortAlreadyAllocated + if err := validateMapping(defaultIP, proto, port); err != nil { + return 0, err } + + if !defaultIP.Equal(ip) { + if err := validateMapping(data, proto, port); err != nil { + return 0, err + } + } + available.Remove(port) allocated.Push(port) + return port, nil } + // Dynamic allocation next := available.Pop() if next == 0 { next = allocated.PullBack() @@ -76,7 +132,7 @@ func RequestPort(proto string, port int) (int, error) { // ReleasePort will return the provided port back into the // pool for reuse -func ReleasePort(proto string, port int) error { +func ReleasePort(ip net.IP, proto string, port int) error { lock.Lock() defer lock.Unlock() @@ -84,10 +140,7 @@ func ReleasePort(proto string, port int) error { return err } - var ( - allocated = allocatedPorts[proto] - available = availablePorts[proto] - ) + allocated, available := getCollection(ip, proto) allocated.Remove(port) available.Push(port) From da61b99b392657343df4dc221ba5cd9ad6b1c9e1 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 23 Jan 2014 12:17:28 -0800 Subject: [PATCH 239/364] Simplify logic for registering ports Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- network.go | 158 +++------------ network_test.go | 44 ---- networkdriver/portallocator/portallocator.go | 191 +++++++++--------- .../portallocator/portallocator_test.go | 88 +++++--- runtime.go | 5 +- 5 files changed, 187 insertions(+), 299 deletions(-) diff --git a/network.go b/network.go index ddb0af5bc7..85b0a588df 100644 --- a/network.go +++ b/network.go @@ -12,7 +12,6 @@ import ( "log" "net" "strconv" - "sync" "syscall" "unsafe" ) @@ -282,76 +281,6 @@ func newPortMapper(config *DaemonConfig) (*PortMapper, error) { return mapper, nil } -// Port allocator: Automatically allocate and release networking ports -type PortAllocator struct { - sync.Mutex - inUse map[string]struct{} - fountain chan int - quit chan bool -} - -func (alloc *PortAllocator) runFountain() { - for { - for port := portRangeStart; port < portRangeEnd; port++ { - select { - case alloc.fountain <- port: - case quit := <-alloc.quit: - if quit { - return - } - } - } - } -} - -// FIXME: Release can no longer fail, change its prototype to reflect that. -func (alloc *PortAllocator) Release(addr net.IP, port int) error { - mapKey := (&net.TCPAddr{Port: port, IP: addr}).String() - utils.Debugf("Releasing %d", port) - alloc.Lock() - delete(alloc.inUse, mapKey) - alloc.Unlock() - return nil -} - -func (alloc *PortAllocator) Acquire(addr net.IP, port int) (int, error) { - mapKey := (&net.TCPAddr{Port: port, IP: addr}).String() - utils.Debugf("Acquiring %s", mapKey) - if port == 0 { - // Allocate a port from the fountain - for port := range alloc.fountain { - if _, err := alloc.Acquire(addr, port); err == nil { - return port, nil - } - } - return -1, fmt.Errorf("Port generator ended unexpectedly") - } - alloc.Lock() - defer alloc.Unlock() - if _, inUse := alloc.inUse[mapKey]; inUse { - return -1, fmt.Errorf("Port already in use: %d", port) - } - alloc.inUse[mapKey] = struct{}{} - return port, nil -} - -func (alloc *PortAllocator) Close() error { - alloc.quit <- true - close(alloc.quit) - close(alloc.fountain) - return nil -} - -func newPortAllocator() (*PortAllocator, error) { - allocator := &PortAllocator{ - inUse: make(map[string]struct{}), - fountain: make(chan int), - quit: make(chan bool), - } - go allocator.runFountain() - return allocator, nil -} - // Network interface represents the networking stack of a container type NetworkInterface struct { IPNet net.IPNet @@ -389,30 +318,24 @@ func (iface *NetworkInterface) AllocatePort(port Port, binding PortBinding) (*Na hostPort, _ := parsePort(nat.Binding.HostPort) - if nat.Port.Proto() == "tcp" { - extPort, err := iface.manager.tcpPortAllocator.Acquire(ip, hostPort) - if err != nil { - return nil, err - } - - backend := &net.TCPAddr{IP: iface.IPNet.IP, Port: containerPort} - if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil { - iface.manager.tcpPortAllocator.Release(ip, extPort) - return nil, err - } - nat.Binding.HostPort = strconv.Itoa(extPort) - } else { - extPort, err := iface.manager.udpPortAllocator.Acquire(ip, hostPort) - if err != nil { - return nil, err - } - backend := &net.UDPAddr{IP: iface.IPNet.IP, Port: containerPort} - if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil { - iface.manager.udpPortAllocator.Release(ip, extPort) - return nil, err - } - nat.Binding.HostPort = strconv.Itoa(extPort) + extPort, err := portallocator.RequestPort(ip, nat.Port.Proto(), hostPort) + if err != nil { + return nil, err } + + var backend net.Addr + if nat.Port.Proto() == "tcp" { + backend = &net.TCPAddr{IP: iface.IPNet.IP, Port: containerPort} + } else { + backend = &net.UDPAddr{IP: iface.IPNet.IP, Port: containerPort} + } + + if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil { + portallocator.ReleasePort(ip, nat.Port.Proto(), extPort) + return nil, err + } + + nat.Binding.HostPort = strconv.Itoa(extPort) iface.extPorts = append(iface.extPorts, nat) return nat, nil @@ -445,14 +368,8 @@ func (iface *NetworkInterface) Release() { log.Printf("Unable to unmap port %s: %s", nat, err) } - if nat.Port.Proto() == "tcp" { - if err := iface.manager.tcpPortAllocator.Release(ip, hostPort); err != nil { - log.Printf("Unable to release port %s", nat) - } - } else if nat.Port.Proto() == "udp" { - if err := iface.manager.udpPortAllocator.Release(ip, hostPort); err != nil { - log.Printf("Unable to release port %s: %s", nat, err) - } + if err := portallocator.ReleasePort(ip, nat.Port.Proto(), hostPort); err != nil { + log.Printf("Unable to release port %s", nat) } } @@ -467,9 +384,7 @@ type NetworkManager struct { bridgeIface string bridgeNetwork *net.IPNet - tcpPortAllocator *PortAllocator - udpPortAllocator *PortAllocator - portMapper *PortMapper + portMapper *PortMapper disabled bool } @@ -497,21 +412,6 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) { return iface, nil } -func (manager *NetworkManager) Close() error { - if manager.disabled { - return nil - } - err1 := manager.tcpPortAllocator.Close() - err2 := manager.udpPortAllocator.Close() - if err1 != nil { - return err1 - } - if err2 != nil { - return err2 - } - return nil -} - func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { if config.BridgeIface == DisableNetworkBridge { manager := &NetworkManager{ @@ -599,27 +499,15 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { } } - tcpPortAllocator, err := newPortAllocator() - if err != nil { - return nil, err - } - - udpPortAllocator, err := newPortAllocator() - if err != nil { - return nil, err - } - portMapper, err := newPortMapper(config) if err != nil { return nil, err } manager := &NetworkManager{ - bridgeIface: config.BridgeIface, - bridgeNetwork: network, - tcpPortAllocator: tcpPortAllocator, - udpPortAllocator: udpPortAllocator, - portMapper: portMapper, + bridgeIface: config.BridgeIface, + bridgeNetwork: network, + portMapper: portMapper, } return manager, nil diff --git a/network_test.go b/network_test.go index 0d25ccb158..6cdf50ab6e 100644 --- a/network_test.go +++ b/network_test.go @@ -7,50 +7,6 @@ import ( "testing" ) -func TestPortAllocation(t *testing.T) { - ip := net.ParseIP("192.168.0.1") - ip2 := net.ParseIP("192.168.0.2") - allocator, err := newPortAllocator() - if err != nil { - t.Fatal(err) - } - if port, err := allocator.Acquire(ip, 80); err != nil { - t.Fatal(err) - } else if port != 80 { - t.Fatalf("Acquire(80) should return 80, not %d", port) - } - port, err := allocator.Acquire(ip, 0) - if err != nil { - t.Fatal(err) - } - if port <= 0 { - t.Fatalf("Acquire(0) should return a non-zero port") - } - if _, err := allocator.Acquire(ip, port); err == nil { - t.Fatalf("Acquiring a port already in use should return an error") - } - if newPort, err := allocator.Acquire(ip, 0); err != nil { - t.Fatal(err) - } else if newPort == port { - t.Fatalf("Acquire(0) allocated the same port twice: %d", port) - } - if _, err := allocator.Acquire(ip, 80); err == nil { - t.Fatalf("Acquiring a port already in use should return an error") - } - if _, err := allocator.Acquire(ip2, 80); err != nil { - t.Fatalf("It should be possible to allocate the same port on a different interface") - } - if _, err := allocator.Acquire(ip2, 80); err == nil { - t.Fatalf("Acquiring a port already in use should return an error") - } - if err := allocator.Release(ip, 80); err != nil { - t.Fatal(err) - } - if _, err := allocator.Acquire(ip, 80); err != nil { - t.Fatal(err) - } -} - type StubProxy struct { frontendAddr *net.Addr backendAddr *net.Addr diff --git a/networkdriver/portallocator/portallocator.go b/networkdriver/portallocator/portallocator.go index 5c7ad04caa..2566ea8500 100644 --- a/networkdriver/portallocator/portallocator.go +++ b/networkdriver/portallocator/portallocator.go @@ -7,20 +7,16 @@ import ( "sync" ) -type portMappings map[string]*collections.OrderedIntSet - -type ipData struct { - allocatedPorts portMappings - availablePorts portMappings -} - -type ipMapping map[net.IP]*ipData - const ( BeginPortRange = 49153 EndPortRange = 65535 ) +type ( + portMappings map[string]*collections.OrderedIntSet + ipMapping map[string]portMappings +) + var ( ErrPortAlreadyAllocated = errors.New("port has already been allocated") ErrPortExceedsRange = errors.New("port exceeds upper range") @@ -28,56 +24,19 @@ var ( ) var ( - defaultIPData *ipData - - lock = sync.Mutex{} - ips = ipMapping{} - defaultIP = net.ParseIP("0.0.0.0") + currentDynamicPort = map[string]int{ + "tcp": BeginPortRange - 1, + "udp": BeginPortRange - 1, + } + defaultIP = net.ParseIP("0.0.0.0") + defaultAllocatedPorts = portMappings{} + otherAllocatedPorts = ipMapping{} + lock = sync.Mutex{} ) func init() { - defaultIPData = newIpData() - ips[defaultIP] = defaultIP -} - -func newIpData() { - data := &ipData{ - allocatedPorts: portMappings{}, - availablePorts: portMappings{}, - } - - data.allocatedPorts["udp"] = collections.NewOrderedIntSet() - data.availablePorts["udp"] = collections.NewOrderedIntSet() - data.allocatedPorts["tcp"] = collections.NewOrderedIntSet() - data.availablePorts["tcp"] = collections.NewOrderedIntSet() - - return data -} - -func getData(ip net.IP) *ipData { - data, exists := ips[ip] - if !exists { - data = newIpData() - ips[ip] = data - } - return data -} - -func validateMapping(data *ipData, proto string, port int) error { - allocated := data.allocatedPorts[proto] - if allocated.Exists(proto) { - return ErrPortAlreadyAllocated - } - return nil -} - -func usePort(data *ipData, proto string, port int) { - allocated, available := data.allocatedPorts[proto], data.availablePorts[proto] - for i := 0; i < 2; i++ { - allocated.Push(port) - available.Remove(port) - allocated, available = defaultIPData.allocatedPorts[proto], defaultIPData.availablePorts[proto] - } + defaultAllocatedPorts["tcp"] = collections.NewOrderedIntSet() + defaultAllocatedPorts["udp"] = collections.NewOrderedIntSet() } // RequestPort returns an available port if the port is 0 @@ -91,43 +50,14 @@ func RequestPort(ip net.IP, proto string, port int) (int, error) { return 0, err } - data := getData(ip) - allocated, available := data.allocatedPorts[proto], data.availablePorts[proto] - // If the user requested a specific port to be allocated if port != 0 { - if err := validateMapping(defaultIP, proto, port); err != nil { + if err := registerSetPort(ip, proto, port); err != nil { return 0, err } - - if !defaultIP.Equal(ip) { - if err := validateMapping(data, proto, port); err != nil { - return 0, err - } - } - - available.Remove(port) - allocated.Push(port) - return port, nil } - - // Dynamic allocation - next := available.Pop() - if next == 0 { - next = allocated.PullBack() - if next == 0 { - next = BeginPortRange - } else { - next++ - } - if next > EndPortRange { - return 0, ErrPortExceedsRange - } - } - - allocated.Push(next) - return next, nil + return registerDynamicPort(ip, proto) } // ReleasePort will return the provided port back into the @@ -140,16 +70,95 @@ func ReleasePort(ip net.IP, proto string, port int) error { return err } - allocated, available := getCollection(ip, proto) - + allocated := defaultAllocatedPorts[proto] allocated.Remove(port) - available.Push(port) + + if !equalsDefault(ip) { + registerIP(ip) + + // Remove the port for the specific ip address + allocated = otherAllocatedPorts[ip.String()][proto] + allocated.Remove(port) + } + return nil +} + +func ReleaseAll() error { + lock.Lock() + defer lock.Unlock() + + currentDynamicPort["tcp"] = BeginPortRange - 1 + currentDynamicPort["udp"] = BeginPortRange - 1 + + defaultAllocatedPorts = portMappings{} + defaultAllocatedPorts["tcp"] = collections.NewOrderedIntSet() + defaultAllocatedPorts["udp"] = collections.NewOrderedIntSet() + + otherAllocatedPorts = ipMapping{} return nil } +func registerDynamicPort(ip net.IP, proto string) (int, error) { + allocated := defaultAllocatedPorts[proto] + + port := nextPort(proto) + if port > EndPortRange { + return 0, ErrPortExceedsRange + } + + if !equalsDefault(ip) { + registerIP(ip) + + ipAllocated := otherAllocatedPorts[ip.String()][proto] + ipAllocated.Push(port) + } else { + allocated.Push(port) + } + return port, nil +} + +func registerSetPort(ip net.IP, proto string, port int) error { + allocated := defaultAllocatedPorts[proto] + if allocated.Exists(port) { + return ErrPortAlreadyAllocated + } + + if !equalsDefault(ip) { + registerIP(ip) + + ipAllocated := otherAllocatedPorts[ip.String()][proto] + if ipAllocated.Exists(port) { + return ErrPortAlreadyAllocated + } + ipAllocated.Push(port) + } else { + allocated.Push(port) + } + return nil +} + +func equalsDefault(ip net.IP) bool { + return ip == nil || ip.Equal(defaultIP) +} + +func nextPort(proto string) int { + c := currentDynamicPort[proto] + 1 + currentDynamicPort[proto] = c + return c +} + +func registerIP(ip net.IP) { + if _, exists := otherAllocatedPorts[ip.String()]; !exists { + otherAllocatedPorts[ip.String()] = portMappings{ + "tcp": collections.NewOrderedIntSet(), + "udp": collections.NewOrderedIntSet(), + } + } +} + func validateProtocol(proto string) error { - if _, exists := allocatedPorts[proto]; !exists { + if _, exists := defaultAllocatedPorts[proto]; !exists { return ErrUnknownProtocol } return nil diff --git a/networkdriver/portallocator/portallocator_test.go b/networkdriver/portallocator/portallocator_test.go index 603bf5a15a..603bd03bd7 100644 --- a/networkdriver/portallocator/portallocator_test.go +++ b/networkdriver/portallocator/portallocator_test.go @@ -1,27 +1,18 @@ package portallocator import ( - "github.com/dotcloud/docker/pkg/collections" + "net" "testing" ) func reset() { - lock.Lock() - defer lock.Unlock() - - allocatedPorts = portMappings{} - availablePorts = portMappings{} - - allocatedPorts["udp"] = collections.NewOrderedIntSet() - availablePorts["udp"] = collections.NewOrderedIntSet() - allocatedPorts["tcp"] = collections.NewOrderedIntSet() - availablePorts["tcp"] = collections.NewOrderedIntSet() + ReleaseAll() } func TestRequestNewPort(t *testing.T) { defer reset() - port, err := RequestPort("tcp", 0) + port, err := RequestPort(defaultIP, "tcp", 0) if err != nil { t.Fatal(err) } @@ -34,7 +25,7 @@ func TestRequestNewPort(t *testing.T) { func TestRequestSpecificPort(t *testing.T) { defer reset() - port, err := RequestPort("tcp", 5000) + port, err := RequestPort(defaultIP, "tcp", 5000) if err != nil { t.Fatal(err) } @@ -46,7 +37,7 @@ func TestRequestSpecificPort(t *testing.T) { func TestReleasePort(t *testing.T) { defer reset() - port, err := RequestPort("tcp", 5000) + port, err := RequestPort(defaultIP, "tcp", 5000) if err != nil { t.Fatal(err) } @@ -54,7 +45,7 @@ func TestReleasePort(t *testing.T) { t.Fatalf("Expected port 5000 got %d", port) } - if err := ReleasePort("tcp", 5000); err != nil { + if err := ReleasePort(defaultIP, "tcp", 5000); err != nil { t.Fatal(err) } } @@ -62,7 +53,7 @@ func TestReleasePort(t *testing.T) { func TestReuseReleasedPort(t *testing.T) { defer reset() - port, err := RequestPort("tcp", 5000) + port, err := RequestPort(defaultIP, "tcp", 5000) if err != nil { t.Fatal(err) } @@ -70,11 +61,11 @@ func TestReuseReleasedPort(t *testing.T) { t.Fatalf("Expected port 5000 got %d", port) } - if err := ReleasePort("tcp", 5000); err != nil { + if err := ReleasePort(defaultIP, "tcp", 5000); err != nil { t.Fatal(err) } - port, err = RequestPort("tcp", 5000) + port, err = RequestPort(defaultIP, "tcp", 5000) if err != nil { t.Fatal(err) } @@ -83,7 +74,7 @@ func TestReuseReleasedPort(t *testing.T) { func TestReleaseUnreadledPort(t *testing.T) { defer reset() - port, err := RequestPort("tcp", 5000) + port, err := RequestPort(defaultIP, "tcp", 5000) if err != nil { t.Fatal(err) } @@ -91,7 +82,7 @@ func TestReleaseUnreadledPort(t *testing.T) { t.Fatalf("Expected port 5000 got %d", port) } - port, err = RequestPort("tcp", 5000) + port, err = RequestPort(defaultIP, "tcp", 5000) if err != ErrPortAlreadyAllocated { t.Fatalf("Expected error %s got %s", ErrPortAlreadyAllocated, err) } @@ -100,7 +91,7 @@ func TestReleaseUnreadledPort(t *testing.T) { func TestUnknowProtocol(t *testing.T) { defer reset() - if _, err := RequestPort("tcpp", 0); err != ErrUnknownProtocol { + if _, err := RequestPort(defaultIP, "tcpp", 0); err != ErrUnknownProtocol { t.Fatalf("Expected error %s got %s", ErrUnknownProtocol, err) } } @@ -109,7 +100,7 @@ func TestAllocateAllPorts(t *testing.T) { defer reset() for i := 0; i <= EndPortRange-BeginPortRange; i++ { - port, err := RequestPort("tcp", 0) + port, err := RequestPort(defaultIP, "tcp", 0) if err != nil { t.Fatal(err) } @@ -119,11 +110,11 @@ func TestAllocateAllPorts(t *testing.T) { } } - if _, err := RequestPort("tcp", 0); err != ErrPortExceedsRange { + if _, err := RequestPort(defaultIP, "tcp", 0); err != ErrPortExceedsRange { t.Fatalf("Expected error %s got %s", ErrPortExceedsRange, err) } - _, err := RequestPort("udp", 0) + _, err := RequestPort(defaultIP, "udp", 0) if err != nil { t.Fatal(err) } @@ -132,10 +123,9 @@ func TestAllocateAllPorts(t *testing.T) { func BenchmarkAllocatePorts(b *testing.B) { defer reset() - b.StartTimer() for i := 0; i < b.N; i++ { for i := 0; i <= EndPortRange-BeginPortRange; i++ { - port, err := RequestPort("tcp", 0) + port, err := RequestPort(defaultIP, "tcp", 0) if err != nil { b.Fatal(err) } @@ -146,5 +136,49 @@ func BenchmarkAllocatePorts(b *testing.B) { } reset() } - b.StopTimer() +} + +func TestPortAllocation(t *testing.T) { + defer reset() + + ip := net.ParseIP("192.168.0.1") + ip2 := net.ParseIP("192.168.0.2") + if port, err := RequestPort(ip, "tcp", 80); err != nil { + t.Fatal(err) + } else if port != 80 { + t.Fatalf("Acquire(80) should return 80, not %d", port) + } + port, err := RequestPort(ip, "tcp", 0) + if err != nil { + t.Fatal(err) + } + if port <= 0 { + t.Fatalf("Acquire(0) should return a non-zero port") + } + + if _, err := RequestPort(ip, "tcp", port); err == nil { + t.Fatalf("Acquiring a port already in use should return an error") + } + + if newPort, err := RequestPort(ip, "tcp", 0); err != nil { + t.Fatal(err) + } else if newPort == port { + t.Fatalf("Acquire(0) allocated the same port twice: %d", port) + } + + if _, err := RequestPort(ip, "tcp", 80); err == nil { + t.Fatalf("Acquiring a port already in use should return an error") + } + if _, err := RequestPort(ip2, "tcp", 80); err != nil { + t.Fatalf("It should be possible to allocate the same port on a different interface") + } + if _, err := RequestPort(ip2, "tcp", 80); err == nil { + t.Fatalf("Acquiring a port already in use should return an error") + } + if err := ReleasePort(ip, "tcp", 80); err != nil { + t.Fatal(err) + } + if _, err := RequestPort(ip, "tcp", 80); err != nil { + t.Fatal(err) + } } diff --git a/runtime.go b/runtime.go index 4eb6f476b0..3d47a50398 100644 --- a/runtime.go +++ b/runtime.go @@ -11,6 +11,7 @@ import ( "github.com/dotcloud/docker/graphdriver/aufs" _ "github.com/dotcloud/docker/graphdriver/devmapper" _ "github.com/dotcloud/docker/graphdriver/vfs" + "github.com/dotcloud/docker/networkdriver/portallocator" "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/pkg/sysinfo" "github.com/dotcloud/docker/utils" @@ -740,8 +741,8 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { func (runtime *Runtime) Close() error { errorsStrings := []string{} - if err := runtime.networkManager.Close(); err != nil { - utils.Errorf("runtime.networkManager.Close(): %s", err.Error()) + if err := portallocator.ReleaseAll(); err != nil { + utils.Errorf("portallocator.ReleaseAll(): %s", err) errorsStrings = append(errorsStrings, err.Error()) } if err := runtime.driver.Cleanup(); err != nil { From 5e69b3837b26d1d1f7cd8b3c4f5b077ba642bd20 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Sun, 26 Jan 2014 14:07:43 -0800 Subject: [PATCH 240/364] Fix issues from rebase on master Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- networkdriver/ipallocator/allocator.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/networkdriver/ipallocator/allocator.go b/networkdriver/ipallocator/allocator.go index 602c9d220b..33401d5caf 100644 --- a/networkdriver/ipallocator/allocator.go +++ b/networkdriver/ipallocator/allocator.go @@ -5,12 +5,11 @@ import ( "errors" "github.com/dotcloud/docker/networkdriver" "github.com/dotcloud/docker/pkg/collections" - "github.com/dotcloud/docker/pkg/netlink" "net" "sync" ) -type networkSet map[iPNet]*collections.OrderedIntSet +type networkSet map[string]*collections.OrderedIntSet var ( ErrNoAvailableIPs = errors.New("no available ip addresses on network") @@ -149,7 +148,7 @@ func intToIP(n int32) *net.IP { func checkAddress(address *net.IPNet) { key := address.String() if _, exists := allocatedIPs[key]; !exists { - allocatedIPs[key] = &iPSet{} - availableIPS[key] = &iPSet{} + allocatedIPs[key] = collections.NewOrderedIntSet() + availableIPS[key] = collections.NewOrderedIntSet() } } From 1ee1dc2340ef030fc3ed132ff51d196536b46600 Mon Sep 17 00:00:00 2001 From: Fabio Falci Date: Sun, 26 Jan 2014 23:10:34 +0000 Subject: [PATCH 241/364] Improve test for container list `ps` Docker-DCO-1.1-Signed-off-by: Fabio Falci (github: fabiofalci) --- integration/server_test.go | 114 +++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/integration/server_test.go b/integration/server_test.go index 681e2e3718..2666d1d4fe 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -519,6 +519,120 @@ func TestImageInsert(t *testing.T) { } } +func TestListContainers(t *testing.T) { + eng := NewTestEngine(t) + srv := mkServerFromEngine(eng, t) + defer mkRuntimeFromEngine(eng, t).Nuke() + + config := docker.Config{ + Image: unitTestImageID, + Cmd: []string{"/bin/sh", "-c", "cat"}, + OpenStdin: true, + } + + firstID := createTestContainer(eng, &config, t) + secondID := createTestContainer(eng, &config, t) + thirdID := createTestContainer(eng, &config, t) + fourthID := createTestContainer(eng, &config, t) + defer func() { + containerKill(eng, firstID, t) + containerKill(eng, secondID, t) + containerKill(eng, fourthID, t) + containerWait(eng, firstID, t) + containerWait(eng, secondID, t) + containerWait(eng, fourthID, t) + }() + + startContainer(eng, firstID, t) + startContainer(eng, secondID, t) + startContainer(eng, fourthID, t) + + // all + if !assertContainerList(srv, true, -1, "", "", []string{fourthID, thirdID, secondID, firstID}) { + t.Error("Container list is not in the correct order") + } + + // running + if !assertContainerList(srv, false, -1, "", "", []string{fourthID, secondID, firstID}) { + t.Error("Container list is not in the correct order") + } + + // from here 'all' flag is ignored + + // limit + expected := []string{fourthID, thirdID} + if !assertContainerList(srv, true, 2, "", "", expected) || + !assertContainerList(srv, false, 2, "", "", expected) { + t.Error("Container list is not in the correct order") + } + + // since + expected = []string{fourthID, thirdID, secondID} + if !assertContainerList(srv, true, -1, firstID, "", expected) || + !assertContainerList(srv, false, -1, firstID, "", expected) { + t.Error("Container list is not in the correct order") + } + + // before + expected = []string{secondID, firstID} + if !assertContainerList(srv, true, -1, "", thirdID, expected) || + !assertContainerList(srv, false, -1, "", thirdID, expected) { + t.Error("Container list is not in the correct order") + } + + // since & before + expected = []string{thirdID, secondID} + if !assertContainerList(srv, true, -1, firstID, fourthID, expected) || + !assertContainerList(srv, false, -1, firstID, fourthID, expected) { + t.Error("Container list is not in the correct order") + } + + // since & limit + expected = []string{fourthID, thirdID} + if !assertContainerList(srv, true, 2, firstID, "", expected) || + !assertContainerList(srv, false, 2, firstID, "", expected) { + t.Error("Container list is not in the correct order") + } + + // before & limit + expected = []string{thirdID} + if !assertContainerList(srv, true, 1, "", fourthID, expected) || + !assertContainerList(srv, false, 1, "", fourthID, expected) { + t.Error("Container list is not in the correct order") + } + + // since & before & limit + expected = []string{thirdID} + if !assertContainerList(srv, true, 1, firstID, fourthID, expected) || + !assertContainerList(srv, false, 1, firstID, fourthID, expected) { + t.Error("Container list is not in the correct order") + } +} + +func assertContainerList(srv *docker.Server, all bool, limit int, since, before string, expected []string) bool { + job := srv.Eng.Job("containers") + job.SetenvBool("all", all) + job.SetenvInt("limit", limit) + job.Setenv("since", since) + job.Setenv("before", before) + outs, err := job.Stdout.AddListTable() + if err != nil { + return false + } + if err := job.Run(); err != nil { + return false + } + if len(outs.Data) != len(expected) { + return false + } + for i := 0; i < len(outs.Data); i++ { + if outs.Data[i].Get("ID") != expected[i] { + return false + } + } + return true +} + // Regression test for being able to untag an image with an existing // container func TestDeleteTagWithExistingContainers(t *testing.T) { From 03d9984f177ec1d093011bcf3693338dabecc19e Mon Sep 17 00:00:00 2001 From: mattymo Date: Mon, 27 Jan 2014 15:13:17 +0300 Subject: [PATCH 242/364] Clean up grammar on puppet.rst --- docs/sources/use/puppet.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/sources/use/puppet.rst b/docs/sources/use/puppet.rst index 94de76c30b..bbd0cd7e47 100644 --- a/docs/sources/use/puppet.rst +++ b/docs/sources/use/puppet.rst @@ -52,7 +52,7 @@ Installation Images ~~~~~~ -The next step is probably to install a docker image, for this we have a +The next step is probably to install a docker image. For this, we have a defined type which can be used like so: .. code-block:: ruby @@ -65,10 +65,11 @@ This is equivalent to running: docker pull ubuntu -Note that it will only if the image of that name does not already exist. -This is downloading a large binary so on first run can take a while. -For that reason this define turns off the default 5 minute timeout -for exec. Note that you can also remove images you no longer need with: +Note that it will only be downloaded if an image of that name does +not already exist. This is downloading a large binary so on first +run can take a while. For that reason this define turns off the +default 5 minute timeout for exec. Note that you can also remove +images you no longer need with: .. code-block:: ruby @@ -79,8 +80,8 @@ for exec. Note that you can also remove images you no longer need with: Containers ~~~~~~~~~~ -Now you have an image you can run commands within a container managed by -docker. +Now you have an image where you can run commands within a container +managed by docker. .. code-block:: ruby @@ -103,7 +104,7 @@ Run also contains a number of optional parameters: image => 'ubuntu', command => '/bin/sh -c "while true; do echo hello world; sleep 1; done"', ports => ['4444', '4555'], - volumes => ['/var/lib/counchdb', '/var/log'], + volumes => ['/var/lib/couchdb', '/var/log'], volumes_from => '6446ea52fbc9', memory_limit => 10485760, # bytes username => 'example', From 551e1a766c8aaec190972e3542fd8a1c7452b6b2 Mon Sep 17 00:00:00 2001 From: mattymo Date: Mon, 27 Jan 2014 18:21:48 +0300 Subject: [PATCH 243/364] Update puppet.rst --- docs/sources/use/puppet.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/sources/use/puppet.rst b/docs/sources/use/puppet.rst index bbd0cd7e47..4183c14f18 100644 --- a/docs/sources/use/puppet.rst +++ b/docs/sources/use/puppet.rst @@ -39,7 +39,7 @@ download the source. Usage ----- -The module provides a puppet class for installing docker and two defined types +The module provides a puppet class for installing Docker and two defined types for managing images and containers. Installation @@ -52,7 +52,7 @@ Installation Images ~~~~~~ -The next step is probably to install a docker image. For this, we have a +The next step is probably to install a Docker image. For this, we have a defined type which can be used like so: .. code-block:: ruby @@ -68,8 +68,8 @@ This is equivalent to running: Note that it will only be downloaded if an image of that name does not already exist. This is downloading a large binary so on first run can take a while. For that reason this define turns off the -default 5 minute timeout for exec. Note that you can also remove -images you no longer need with: +default 5 minute timeout for the exec type. Note that you can also +remove images you no longer need with: .. code-block:: ruby @@ -81,7 +81,7 @@ Containers ~~~~~~~~~~ Now you have an image where you can run commands within a container -managed by docker. +managed by Docker. .. code-block:: ruby From 77a34920b24901da82f9e22138ef21ffc665d678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Mui=C3=B1o?= Date: Mon, 27 Jan 2014 16:22:59 +0100 Subject: [PATCH 244/364] Release the push lock at the end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Abel Muiño (github: amuino) --- server.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server.go b/server.go index 5fbde5865d..b4add79771 100644 --- a/server.go +++ b/server.go @@ -1643,6 +1643,8 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + defer srv.poolRemove("push", localName) + // Resolve the Repository name from fqn to endpoint + name endpoint, remoteName, err := registry.ResolveRepositoryName(localName) if err != nil { From 32753c3d241c9238b2a34f61fd44399453d7b455 Mon Sep 17 00:00:00 2001 From: Peter Waller Date: Mon, 27 Jan 2014 17:19:38 +0000 Subject: [PATCH 245/364] Fix for issue #3786 This is a fix for the case that one mount is inside another mount and docker can't then delete the resulting container. Docker-DCO-1.1-Signed-off-by: Peter Waller (github: pwaller) --- container.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/container.go b/container.go index aafda78bc7..c5df1f4b58 100644 --- a/container.go +++ b/container.go @@ -1365,9 +1365,9 @@ func (container *Container) Unmount() error { mounts = append(mounts, path.Join(root, r)) } - for _, m := range mounts { - if lastError := mount.Unmount(m); lastError != nil { - err = lastError + for i := len(mounts) - 1; i >= 0; i-- { + if lastError := mount.Unmount(mounts[i]); lastError != nil { + err = fmt.Errorf("Failed to umount %v: %v", mounts[i], lastError) } } if err != nil { From 6f3d8d39085e7dc31f757c0221eca7ab5f220224 Mon Sep 17 00:00:00 2001 From: Peter Waller Date: Mon, 27 Jan 2014 18:07:30 +0000 Subject: [PATCH 246/364] Fix for #3674 Can't `rm` containers when disk full Rather than creating a new directory and moving it there before deleting that new directory, just move the directory we intend to delete. In the old way, the Mkdirall could fail, which meant that you couldn't delete containers when the disk was full. Tested. Docker-DCO-1.1-Signed-off-by: Peter Waller (github: pwaller) --- graphdriver/aufs/aufs.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/graphdriver/aufs/aufs.go b/graphdriver/aufs/aufs.go index 8ab544eeba..d1cf87d1a0 100644 --- a/graphdriver/aufs/aufs.go +++ b/graphdriver/aufs/aufs.go @@ -192,20 +192,17 @@ func (a *Driver) Remove(id string) error { "diff", } - // Remove the dirs atomically + // Atomically remove each directory in turn by first moving it out of the + // way (so that docker doesn't find it anymore) before doing removal of + // the whole tree. for _, p := range tmpDirs { - // We need to use a temp dir in the same dir as the driver so Rename - // does not fall back to the slow copy if /tmp and the driver dir - // are on different devices - tmp := path.Join(a.rootPath(), "tmp", p, id) - if err := os.MkdirAll(tmp, 0755); err != nil { - return err - } + realPath := path.Join(a.rootPath(), p, id) - if err := os.Rename(realPath, tmp); err != nil && !os.IsNotExist(err) { + tmpPath := path.Join(a.rootPath(), p, fmt.Sprintf("%s-removing", id)) + if err := os.Rename(realPath, tmpPath); err != nil && !os.IsNotExist(err) { return err } - defer os.RemoveAll(tmp) + defer os.RemoveAll(tmpPath) } // Remove the layers file for the id From c4c558d138c43c6e2a48fd3060c4b8c81ace989b Mon Sep 17 00:00:00 2001 From: Peter Waller Date: Mon, 27 Jan 2014 21:47:49 +0000 Subject: [PATCH 247/364] Add Peter Waller to AUTHORS Docker-DCO-1.1-Signed-off-by: Peter Waller (github: pwaller) --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 0985fed915..c865b49ecd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -231,6 +231,7 @@ Paul Morie Paul Nasrat Paul Peter Braden +Peter Waller Phil Spitler Pierre-Alain RIVIERE Piotr Bogdan From 07c4eda46a6a1af82b4b519d9186b9bf7881d7cc Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Thu, 23 Jan 2014 18:59:00 -0800 Subject: [PATCH 248/364] Fix 2720 -- Expanded documentation for docker run. Docker-DCO-1.1-Signed-off-by: Andy Rothfusz (github: metalivedev) --- docs/sources/articles/index.rst | 1 + docs/sources/articles/runmetrics.rst | 469 +++++++++++++++++++++ docs/sources/reference/builder.rst | 8 +- docs/sources/reference/commandline/cli.rst | 39 ++ docs/sources/reference/index.rst | 1 + docs/sources/reference/run.rst | 353 ++++++++++++++++ 6 files changed, 867 insertions(+), 4 deletions(-) create mode 100644 docs/sources/articles/runmetrics.rst create mode 100644 docs/sources/reference/run.rst diff --git a/docs/sources/articles/index.rst b/docs/sources/articles/index.rst index 2cfc427420..75c0cd3fa9 100644 --- a/docs/sources/articles/index.rst +++ b/docs/sources/articles/index.rst @@ -12,3 +12,4 @@ Articles security baseimages + runmetrics diff --git a/docs/sources/articles/runmetrics.rst b/docs/sources/articles/runmetrics.rst new file mode 100644 index 0000000000..f7406bc5ed --- /dev/null +++ b/docs/sources/articles/runmetrics.rst @@ -0,0 +1,469 @@ +:title: Runtime Metrics +:description: Measure the behavior of running containers +:keywords: docker, metrics, CPU, memory, disk, IO, run, runtime + +.. _run_metrics: + + +Runtime Metrics +=============== + +Linux Containers rely on `control groups +`_ which +not only track groups of processes, but also expose metrics about CPU, +memory, and block I/O usage. You can access those metrics and obtain +network usage metrics as well. This is relevant for "pure" LXC +containers, as well as for Docker containers. + +Control Groups +-------------- + +Control groups are exposed through a pseudo-filesystem. In recent +distros, you should find this filesystem under +``/sys/fs/cgroup``. Under that directory, you will see multiple +sub-directories, called devices, freezer, blkio, etc.; each +sub-directory actually corresponds to a different cgroup hierarchy. + +On older systems, the control groups might be mounted on ``/cgroup``, +without distinct hierarchies. In that case, instead of seeing the +sub-directories, you will see a bunch of files in that directory, and +possibly some directories corresponding to existing containers. + +To figure out where your control groups are mounted, you can run: + +:: + + grep cgroup /proc/mounts + +.. _run_findpid: + +Ennumerating Cgroups +-------------------- + +You can look into ``/proc/cgroups`` to see the different control group +subsystems known to the system, the hierarchy they belong to, and how +many groups they contain. + +You can also look at ``/proc//cgroup`` to see which control +groups a process belongs to. The control group will be shown as a path +relative to the root of the hierarchy mountpoint; e.g. ``/`` means +“this process has not been assigned into a particular group”, while +``/lxc/pumpkin`` means that the process is likely to be a member of a +container named ``pumpkin``. + +Finding the Cgroup for a Given Container +---------------------------------------- + +For each container, one cgroup will be created in each hierarchy. On +older systems with older versions of the LXC userland tools, the name +of the cgroup will be the name of the container. With more recent +versions of the LXC tools, the cgroup will be ``lxc/.`` + +For Docker containers using cgroups, the container name will be the +full ID or long ID of the container. If a container shows up as +ae836c95b4c3 in ``docker ps``, its long ID might be something like +``ae836c95b4c3c9e9179e0e91015512da89fdec91612f63cebae57df9a5444c79``. You +can look it up with ``docker inspect`` or ``docker ps -notrunc``. + +Putting everything together to look at the memory metrics for a Docker +container, take a look at ``/sys/fs/cgroup/memory/lxc//``. + +Metrics from Cgroups: Memory, CPU, Block IO +------------------------------------------- + +For each subsystem (memory, cpu, and block i/o), you will find one or +more pseudo-files containing statistics. + +Memory Metrics: ``memory.stat`` +............................... + +Memory metrics are found in the "memory" cgroup. Note that the memory +control group adds a little overhead, because it does very +fine-grained accounting of the memory usage on your system. Therefore, +many distros chose to not enable it by default. Generally, to enable +it, all you have to do is to add some kernel command-line parameters: +``cgroup_enable=memory swapaccount=1``. + +The metrics are in the pseudo-file ``memory.stat``. Here is what it +will look like: + +:: + + cache 11492564992 + rss 1930993664 + mapped_file 306728960 + pgpgin 406632648 + pgpgout 403355412 + swap 0 + pgfault 728281223 + pgmajfault 1724 + inactive_anon 46608384 + active_anon 1884520448 + inactive_file 7003344896 + active_file 4489052160 + unevictable 32768 + hierarchical_memory_limit 9223372036854775807 + hierarchical_memsw_limit 9223372036854775807 + total_cache 11492564992 + total_rss 1930993664 + total_mapped_file 306728960 + total_pgpgin 406632648 + total_pgpgout 403355412 + total_swap 0 + total_pgfault 728281223 + total_pgmajfault 1724 + total_inactive_anon 46608384 + total_active_anon 1884520448 + total_inactive_file 7003344896 + total_active_file 4489052160 + total_unevictable 32768 + +The first half (without the ``total_`` prefix) contains statistics +relevant to the processes within the cgroup, excluding +sub-cgroups. The second half (with the ``total_`` prefix) includes +sub-cgroups as well. + +Some metrics are "gauges", i.e. values that can increase or decrease +(e.g. swap, the amount of swap space used by the members of the +cgroup). Some others are "counters", i.e. values that can only go up, +because they represent occurrences of a specific event (e.g. pgfault, +which indicates the number of page faults which happened since the +creation of the cgroup; this number can never decrease). + +cache + the amount of memory used by the processes of this control group + that can be associated precisely with a block on a block + device. When you read and write files from and to disk, this amount + will increase. This will be the case if you use "conventional" I/O + (``open``, ``read``, ``write`` syscalls) as well as mapped files + (with ``mmap``). It also accounts for the memory used by ``tmpfs`` + mounts, though the reasons are unclear. + +rss + the amount of memory that *doesn't* correspond to anything on + disk: stacks, heaps, and anonymous memory maps. + +mapped_file + indicates the amount of memory mapped by the processes in the + control group. It doesn't give you information about *how much* + memory is used; it rather tells you *how* it is used. + +pgpgin and pgpgout + correspond to *charging events*. Each time a page is "charged" + (=added to the accounting) to a cgroup, pgpgin increases. When a + page is "uncharged" (=no longer "billed" to a cgroup), pgpgout + increases. + +pgfault and pgmajfault + indicate the number of times that a process of the cgroup triggered + a "page fault" and a "major fault", respectively. A page fault + happens when a process accesses a part of its virtual memory space + which is inexistent or protected. The former can happen if the + process is buggy and tries to access an invalid address (it will + then be sent a ``SIGSEGV`` signal, typically killing it with the + famous ``Segmentation fault`` message). The latter can happen when + the process reads from a memory zone which has been swapped out, or + which corresponds to a mapped file: in that case, the kernel will + load the page from disk, and let the CPU complete the memory + access. It can also happen when the process writes to a + copy-on-write memory zone: likewise, the kernel will preempt the + process, duplicate the memory page, and resume the write operation + on the process' own copy of the page. "Major" faults happen when the + kernel actually has to read the data from disk. When it just has to + duplicate an existing page, or allocate an empty page, it's a + regular (or "minor") fault. + +swap + the amount of swap currently used by the processes in this cgroup. + +active_anon and inactive_anon + the amount of *anonymous* memory that has been identified has + respectively *active* and *inactive* by the kernel. "Anonymous" + memory is the memory that is *not* linked to disk pages. In other + words, that's the equivalent of the rss counter described above. In + fact, the very definition of the rss counter is **active_anon** + + **inactive_anon** - **tmpfs** (where tmpfs is the amount of memory + used up by ``tmpfs`` filesystems mounted by this control + group). Now, what's the difference between "active" and "inactive"? + Pages are initially "active"; and at regular intervals, the kernel + sweeps over the memory, and tags some pages as "inactive". Whenever + they are accessed again, they are immediately retagged + "active". When the kernel is almost out of memory, and time comes to + swap out to disk, the kernel will swap "inactive" pages. + +active_file and inactive_file + cache memory, with *active* and *inactive* similar to the *anon* + memory above. The exact formula is cache = **active_file** + + **inactive_file** + **tmpfs**. The exact rules used by the kernel to + move memory pages between active and inactive sets are different + from the ones used for anonymous memory, but the general principle + is the same. Note that when the kernel needs to reclaim memory, it + is cheaper to reclaim a clean (=non modified) page from this pool, + since it can be reclaimed immediately (while anonymous pages and + dirty/modified pages have to be written to disk first). + +unevictable + the amount of memory that cannot be reclaimed; generally, it will + account for memory that has been "locked" with ``mlock``. It is + often used by crypto frameworks to make sure that secret keys and + other sensitive material never gets swapped out to disk. + +memory and memsw limits + These are not really metrics, but a reminder of the limits applied + to this cgroup. The first one indicates the maximum amount of + physical memory that can be used by the processes of this control + group; the second one indicates the maximum amount of RAM+swap. + +Accounting for memory in the page cache is very complex. If two +processes in different control groups both read the same file +(ultimately relying on the same blocks on disk), the corresponding +memory charge will be split between the control groups. It's nice, but +it also means that when a cgroup is terminated, it could increase the +memory usage of another cgroup, because they are not splitting the +cost anymore for those memory pages. + +CPU metrics: ``cpuacct.stat`` +............................. + +Now that we've covered memory metrics, everything else will look very +simple in comparison. CPU metrics will be found in the ``cpuacct`` +controller. + +For each container, you will find a pseudo-file ``cpuacct.stat``, +containing the CPU usage accumulated by the processes of the +container, broken down between ``user`` and ``system`` time. If you're +not familiar with the distinction, ``user`` is the time during which +the processes were in direct control of the CPU (i.e. executing +process code), and ``system`` is the time during which the CPU was +executing system calls on behalf of those processes. + +Those times are expressed in ticks of 1/100th of second. Actually, +they are expressed in "user jiffies". There are ``USER_HZ`` +*"jiffies"* per second, and on x86 systems, ``USER_HZ`` is 100. This +used to map exactly to the number of scheduler "ticks" per second; but +with the advent of higher frequency scheduling, as well as `tickless +kernels `_, the number of kernel +ticks wasn't relevant anymore. It stuck around anyway, mainly for +legacy and compatibility reasons. + +Block I/O metrics +................. + +Block I/O is accounted in the ``blkio`` controller. Different metrics +are scattered across different files. While you can find in-depth +details in the `blkio-controller +`_ +file in the kernel documentation, here is a short list of the most +relevant ones: + +blkio.sectors + contain the number of 512-bytes sectors read and written by the + processes member of the cgroup, device by device. Reads and writes + are merged in a single counter. + +blkio.io_service_bytes + indicates the number of bytes read and written by the cgroup. It has + 4 counters per device, because for each device, it differentiates + between synchronous vs. asynchronous I/O, and reads vs. writes. + +blkio.io_serviced + the number of I/O operations performed, regardless of their size. It + also has 4 counters per device. + +blkio.io_queued + indicates the number of I/O operations currently queued for this + cgroup. In other words, if the cgroup isn't doing any I/O, this will + be zero. Note that the opposite is not true. In other words, if + there is no I/O queued, it does not mean that the cgroup is idle + (I/O-wise). It could be doing purely synchronous reads on an + otherwise quiescent device, which is therefore able to handle them + immediately, without queuing. Also, while it is helpful to figure + out which cgroup is putting stress on the I/O subsystem, keep in + mind that is is a relative quantity. Even if a process group does + not perform more I/O, its queue size can increase just because the + device load increases because of other devices. + +Network Metrics +--------------- + +Network metrics are not exposed directly by control groups. There is a +good explanation for that: network interfaces exist within the context +of *network namespaces*. The kernel could probably accumulate metrics +about packets and bytes sent and received by a group of processes, but +those metrics wouldn't be very useful. You want per-interface metrics +(because traffic happening on the local ``lo`` interface doesn't +really count). But since processes in a single cgroup can belong to +multiple network namespaces, those metrics would be harder to +interpret: multiple network namespaces means multiple ``lo`` +interfaces, potentially multiple ``eth0`` interfaces, etc.; so this is +why there is no easy way to gather network metrics with control +groups. + +Instead we can gather network metrics from other sources: + +IPtables +........ + +IPtables (or rather, the netfilter framework for which iptables is +just an interface) can do some serious accounting. + +For instance, you can setup a rule to account for the outbound HTTP +traffic on a web server: + +:: + + iptables -I OUTPUT -p tcp --sport 80 + + +There is no ``-j`` or ``-g`` flag, so the rule will just count matched +packets and go to the following rule. + +Later, you can check the values of the counters, with: + +:: + + iptables -nxvL OUTPUT + +Technically, ``-n`` is not required, but it will prevent iptables from +doing DNS reverse lookups, which are probably useless in this +scenario. + +Counters include packets and bytes. If you want to setup metrics for +container traffic like this, you could execute a ``for`` loop to add +two ``iptables`` rules per container IP address (one in each +direction), in the ``FORWARD`` chain. This will only meter traffic +going through the NAT layer; you will also have to add traffic going +through the userland proxy. + +Then, you will need to check those counters on a regular basis. If you +happen to use ``collectd``, there is a nice plugin to automate +iptables counters collection. + +Interface-level counters +........................ + +Since each container has a virtual Ethernet interface, you might want +to check directly the TX and RX counters of this interface. You will +notice that each container is associated to a virtual Ethernet +interface in your host, with a name like ``vethKk8Zqi``. Figuring out +which interface corresponds to which container is, unfortunately, +difficult. + +But for now, the best way is to check the metrics *from within the +containers*. To accomplish this, you can run an executable from the +host environment within the network namespace of a container using +**ip-netns magic**. + +The ``ip-netns exec`` command will let you execute any program +(present in the host system) within any network namespace visible to +the current process. This means that your host will be able to enter +the network namespace of your containers, but your containers won't be +able to access the host, nor their sibling containers. Containers will +be able to “see” and affect their sub-containers, though. + +The exact format of the command is:: + + ip netns exec + +For example:: + + ip netns exec mycontainer netstat -i + +``ip netns`` finds the "mycontainer" container by using namespaces +pseudo-files. Each process belongs to one network namespace, one PID +namespace, one ``mnt`` namespace, etc., and those namespaces are +materialized under ``/proc//ns/``. For example, the network +namespace of PID 42 is materialized by the pseudo-file +``/proc/42/ns/net``. + +When you run ``ip netns exec mycontainer ...``, it expects +``/var/run/netns/mycontainer`` to be one of those +pseudo-files. (Symlinks are accepted.) + +In other words, to execute a command within the network namespace of a +container, we need to: + +* find out the PID of any process within the container that we want to + investigate; +* create a symlink from ``/var/run/netns/`` to + ``/proc//ns/net`` +* execute ``ip netns exec ....`` + +Please review :ref:`run_findpid` to learn how to find the cgroup of a +pprocess running in the container of which you want to measure network +usage. From there, you can examine the pseudo-file named ``tasks``, +which containes the PIDs that are in the control group (i.e. in the +container). Pick any one of them. + +Putting everything together, if the "short ID" of a container is held +in the environment variable ``$CID``, then you can do this:: + + TASKS=/sys/fs/cgroup/devices/$CID*/tasks + PID=$(head -n 1 $TASKS) + mkdir -p /var/run/netns + ln -sf /proc/$PID/ns/net /var/run/netns/$CID + ip netns exec $CID netstat -i + + +Tips for high-performance metric collection +------------------------------------------- + +Note that running a new process each time you want to update metrics +is (relatively) expensive. If you want to collect metrics at high +resolutions, and/or over a large number of containers (think 1000 +containers on a single host), you do not want to fork a new process +each time. + +Here is how to collect metrics from a single process. You will have to +write your metric collector in C (or any language that lets you do +low-level system calls). You need to use a special system call, +``setns()``, which lets the current process enter any arbitrary +namespace. It requires, however, an open file descriptor to the +namespace pseudo-file (remember: that’s the pseudo-file in +``/proc//ns/net``). + +However, there is a catch: you must not keep this file descriptor +open. If you do, when the last process of the control group exits, the +namespace will not be destroyed, and its network resources (like the +virtual interface of the container) will stay around for ever (or +until you close that file descriptor). + +The right approach would be to keep track of the first PID of each +container, and re-open the namespace pseudo-file each time. + +Collecting metrics when a container exits +----------------------------------------- + +Sometimes, you do not care about real time metric collection, but when +a container exits, you want to know how much CPU, memory, etc. it has +used. + +Docker makes this difficult because it relies on ``lxc-start``, which +carefully cleans up after itself, but it is still possible. It is +usually easier to collect metrics at regular intervals (e.g. every +minute, with the collectd LXC plugin) and rely on that instead. + +But, if you'd still like to gather the stats when a container stops, +here is how: + +For each container, start a collection process, and move it to the +control groups that you want to monitor by writing its PID to the +tasks file of the cgroup. The collection process should periodically +re-read the tasks file to check if it's the last process of the +control group. (If you also want to collect network statistics as +explained in the previous section, you should also move the process to +the appropriate network namespace.) + +When the container exits, ``lxc-start`` will try to delete the control +groups. It will fail, since the control group is still in use; but +that’s fine. You process should now detect that it is the only one +remaining in the group. Now is the right time to collect all the +metrics you need! + +Finally, your process should move itself back to the root control +group, and remove the container control group. To remove a control +group, just ``rmdir`` its directory. It's counter-intuitive to +``rmdir`` a directory as it still contains files; but remember that +this is a pseudo-filesystem, so usual rules don't apply. After the +cleanup is done, the collection process can exit safely. + diff --git a/docs/sources/reference/builder.rst b/docs/sources/reference/builder.rst index 45cb2ab86e..9889660913 100644 --- a/docs/sources/reference/builder.rst +++ b/docs/sources/reference/builder.rst @@ -1,12 +1,12 @@ -:title: Build Images (Dockerfile Reference) +:title: Dockerfile Reference :description: Dockerfiles use a simple DSL which allows you to automate the steps you would normally manually take to create an image. :keywords: builder, docker, Dockerfile, automation, image creation .. _dockerbuilder: -=================================== -Build Images (Dockerfile Reference) -=================================== +==================== +Dockerfile Reference +==================== **Docker can act as a builder** and read instructions from a text ``Dockerfile`` to automate the steps you would otherwise take manually diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index e71b691bcc..a636df6259 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -18,6 +18,45 @@ To list available commands, either run ``docker`` with no parameters or execute ... +.. _cli_options: + +Types of Options +---------------- + +Boolean +~~~~~~~ + +Boolean options look like ``-d=false``. The value you see is the +default value which gets set if you do **not** use the boolean +flag. If you do call ``run -d``, that sets the opposite boolean value, +so in this case, ``true``, and so ``docker run -d`` **will** run in +"detached" mode, in the background. Other boolean options are similar +-- specifying them will set the value to the opposite of the default +value. + +Multi +~~~~~ + +Options like ``-a=[]`` indicate they can be specified multiple times:: + + docker run -a stdin -a stdout -a stderr -i -t ubuntu /bin/bash + +Sometimes this can use a more complex value string, as for ``-v``:: + + docker run -v /host:/container example/mysql + +Strings and Integers +~~~~~~~~~~~~~~~~~~~~ + +Options like ``-name=""`` expect a string, and they can only be +specified once. Options like ``-c=0`` expect an integer, and they can +only be specified once. + +---- + +Commands +-------- + .. _cli_daemon: ``daemon`` diff --git a/docs/sources/reference/index.rst b/docs/sources/reference/index.rst index 49099d5621..d35a19b93d 100644 --- a/docs/sources/reference/index.rst +++ b/docs/sources/reference/index.rst @@ -14,4 +14,5 @@ Contents: commandline/index builder + run api/index diff --git a/docs/sources/reference/run.rst b/docs/sources/reference/run.rst new file mode 100644 index 0000000000..7505b7c02f --- /dev/null +++ b/docs/sources/reference/run.rst @@ -0,0 +1,353 @@ +:title: Docker Run Reference +:description: Configure containers at runtime +:keywords: docker, run, configure, runtime + +.. _run_docker: + +==================== +Docker Run Reference +==================== + +**Docker runs processes in isolated containers**. When an operator +executes ``docker run``, she starts a process with its own file +system, its own networking, and its own isolated process tree. The +:ref:`image_def` which starts the process may define defaults related +to the binary to run, the networking to expose, and more, but ``docker +run`` gives final control to the operator who starts the container +from the image. That's the main reason :ref:`cli_run` has more options +than any other ``docker`` command. + +Every one of the :ref:`example_list` shows running containers, and so +here we try to give more in-depth guidance. + +.. contents:: Table of Contents + +.. _run_running: + +General Form +============ + +As you've seen in the :ref:`example_list`, the basic `run` command +takes this form:: + + docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...] + +To learn how to interpret the types of ``[OPTIONS]``, see +:ref:`cli_options`. + +The list of ``[OPTIONS]`` breaks down into two groups: + +* options that define the runtime behavior or environment, and +* options that override image defaults. + +Since image defaults usually get set in :ref:`Dockerfiles +` (though they could also be set at :ref:`cli_commit` +time too), we will group the runtime options here by their related +Dockerfile commands so that it is easier to see how to override image +defaults and set new behavior. + +We'll start, though, with the options that are unique to ``docker +run``, the options which define the runtime behavior or the container +environment. + +.. note:: The runtime operator always has final control over the + behavior of a Docker container. + +Detached or Foreground +====================== + +When starting a Docker container, you must first decide if you want to +run the container in the background in a "detached" mode or in the +default foreground mode:: + + -d=false: Detached mode: Run container in the background, print new container id + +Detached (-d) +............. + +In detached mode (``-d=true`` or just ``-d``), all IO should be done +through network connections or shared volumes because the container is +no longer listening to the commandline where you executed ``docker +run``. You can reattach to a detached container with ``docker`` +:ref:`cli_attach`. If you choose to run a container in the detached +mode, then you cannot use the ``-rm`` option. + +Foreground +.......... + +In foreground mode (the default when ``-d`` is not specified), +``docker run`` can start the process in the container and attach the +console to the process's standard input, output, and standard +error. It can even pretend to be a TTY (this is what most commandline +executables expect) and pass along signals. All of that is +configurable:: + + -a=[] : Attach to stdin, stdout and/or stderr + -t=false : Allocate a pseudo-tty + -sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) + -i=false : Keep stdin open even if not attached + +If you do not specify ``-a`` then Docker will `attach everything +(stdin,stdout,stderr) +`_. You +can specify which of the three standard streams (stdin, stdout, +stderr) you'd like to connect between your instead, as in:: + + docker run -a stdin -a stdout -i -t ubuntu /bin/bash + +For interactive processes (like a shell) you will typically want a tty +as well as persistent standard in, so you'll use ``-i -t`` together in +most interactive cases. + +Clean Up (-rm) +-------------- + +By default a container's file system persists even after the container +exits. This makes debugging a lot easier (since you can inspect the +final state) and you retain all your data by default. But if you are +running short-term **foreground** processes, these container file +systems can really pile up. If instead you'd like Docker to +**automatically clean up the container and remove the file system when +the container exits**, you can add the ``-rm`` flag:: + + -rm=false: Automatically remove the container when it exits (incompatible with -d) + +Name (-name) +============ + +The operator can identify a container in three ways: + +* UUID long identifier ("f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778") +* UUID short identifier ("f78375b1c487") +* name ("evil_ptolemy") + +The UUID identifiers come from the Docker daemon, and if you do not +assign a name to the container with ``-name`` then the daemon will +also generate a random string name too. The name can become a handy +way to add meaning to a container since you can use this name when +defining :ref:`links ` (or any other place +you need to identify a container). This works for both background and +foreground Docker containers. + +PID Equivalent +============== + +And finally, to help with automation, you can have Docker write the +container id out to a file of your choosing. This is similar to how +some programs might write out their process ID to a file (you've seen +them as .pid files):: + + -cidfile="": Write the container ID to the file + +Overriding Dockerfile Image Defaults +==================================== + +When a developer builds an image from a :ref:`Dockerfile +` or when she commits it, the developer can set a +number of default parameters that take effect when the image starts up +as a container. + +Four of the Dockerfile commands cannot be overridden at runtime: +``FROM, MAINTAINER, RUN``, and ``ADD``. Everything else has a +corresponding override in ``docker run``. We'll go through what the +developer might have set in each Dockerfile instruction and how the +operator can override that setting. + + +CMD +... + +Remember the optional ``COMMAND`` in the Docker commandline:: + + docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...] + +This command is optional because the person who created the ``IMAGE`` +may have already provided a default ``COMMAND`` using the Dockerfile +``CMD``. As the operator (the person running a container from the +image), you can override that ``CMD`` just by specifying a new +``COMMAND``. + +If the image also specifies an ``ENTRYPOINT`` then the ``CMD`` or +``COMMAND`` get appended as arguments to the ``ENTRYPOINT``. + + +ENTRYPOINT +.......... + +:: + + -entrypoint="": Overwrite the default entrypoint set by the image + +The ENTRYPOINT of an image is similar to a COMMAND because it +specifies what executable to run when the container starts, but it is +(purposely) more difficult to override. The ENTRYPOINT gives a +container its default nature or behavior, so that when you set an +ENTRYPOINT you can run the container *as if it were that binary*, +complete with default options, and you can pass in more options via +the COMMAND. But, sometimes an operator may want to run something else +inside the container, so you can override the default ENTRYPOINT at +runtime by using a string to specify the new ENTRYPOINT. Here is an +example of how to run a shell in a container that has been set up to +automatically run something else (like ``/usr/bin/redis-server``):: + + docker run -i -t -entrypoint /bin/bash example/redis + +or two examples of how to pass more parameters to that ENTRYPOINT:: + + docker run -i -t -entrypoint /bin/bash example/redis -c ls -l + docker run -i -t -entrypoint /usr/bin/redis-cli example/redis --help + + +EXPOSE (``run`` Networking Options) +................................... + +The *Dockerfile* doesn't give much control over networking, only +providing the EXPOSE instruction to give a hint to the operator about +what incoming ports might provide services. At runtime, however, +Docker provides a number of ``run`` options related to networking:: + + -n=true : Enable networking for this container + -dns=[] : Set custom dns servers for the container + -expose=[]: Expose a port from the container + without publishing it to your host + -P=false : Publish all exposed ports to the host interfaces + -p=[] : Publish a container's port to the host (format: + ip:hostPort:containerPort | ip::containerPort | + hostPort:containerPort) + (use 'docker port' to see the actual mapping) + -link="" : Add link to another container (name:alias) + +By default, all containers have networking enabled and they can make +any outgoing connections. The operator can completely disable +networking with ``run -n`` which disables all incoming and outgoing +networking. In cases like this, you would perform IO through files or +stdin/stdout only. + +Your container will use the same DNS servers as the host by default, +but you can override this with ``-dns``. + +As mentioned previously, ``EXPOSE`` (and ``-expose``) make a port +available **in** a container for incoming connections. The port number +on the inside of the container (where the service listens) does not +need to be the same number as the port exposed on the outside of the +container (where clients connect), so inside the container you might +have an HTTP service listening on port 80 (and so you ``EXPOSE 80`` in +the Dockerfile), but outside the container the port might be 42800. + +To help a new client container reach the server container's internal +port operator ``-expose'd`` by the operator or ``EXPOSE'd`` by the +developer, the operator has three choices: start the server container +with ``-P`` or ``-p,`` or start the client container with ``-link``. + +If the operator uses ``-P`` or ``-p`` then Docker will make the +exposed port accessible on the host and the ports will be available to +any client that can reach the host. To find the map between the host +ports and the exposed ports, use ``docker port``) + +If the operator uses ``-link`` when starting the new client container, +then the client container can access the exposed port via a private +networking interface. Docker will set some environment variables in +the client container to help indicate which interface and port to use. + +ENV (Environment Variables) +........................... + +The operator can **set any environment variable** in the container by +using one or more ``-e``, even overriding those already defined by the +developer with a Dockefile ``ENV``:: + + $ docker run -e "deep=purple" -rm ubuntu /bin/bash -c export + declare -x HOME="/" + declare -x HOSTNAME="85bc26a0e200" + declare -x OLDPWD + declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + declare -x PWD="/" + declare -x SHLVL="1" + declare -x container="lxc" + declare -x deep="purple" + +Similarly the operator can set the **hostname** with ``-h``. + +``-link name:alias`` also sets environment variables, using the +*alias* string to define environment variables within the container +that give the IP and PORT information for connecting to the service +container. Let's imagine we have a container running Redis:: + + # Start the service container, named redis-name + $ docker run -d -name redis-name dockerfiles/redis + 4241164edf6f5aca5b0e9e4c9eccd899b0b8080c64c0cd26efe02166c73208f3 + + # The redis-name container exposed port 6379 + $ docker ps + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + 4241164edf6f dockerfiles/redis:latest /redis-stable/src/re 5 seconds ago Up 4 seconds 6379/tcp redis-name + + # Note that there are no public ports exposed since we didn't use -p or -P + $ docker port 4241164edf6f 6379 + 2014/01/25 00:55:38 Error: No public port '6379' published for 4241164edf6f + + +Yet we can get information about the redis container's exposed ports with ``-link``. Choose an alias that will form a valid environment variable! + +:: + + $ docker run -rm -link redis-name:redis_alias -entrypoint /bin/bash dockerfiles/redis -c export + declare -x HOME="/" + declare -x HOSTNAME="acda7f7b1cdc" + declare -x OLDPWD + declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + declare -x PWD="/" + declare -x REDIS_ALIAS_NAME="/distracted_wright/redis" + declare -x REDIS_ALIAS_PORT="tcp://172.17.0.32:6379" + declare -x REDIS_ALIAS_PORT_6379_TCP="tcp://172.17.0.32:6379" + declare -x REDIS_ALIAS_PORT_6379_TCP_ADDR="172.17.0.32" + declare -x REDIS_ALIAS_PORT_6379_TCP_PORT="6379" + declare -x REDIS_ALIAS_PORT_6379_TCP_PROTO="tcp" + declare -x SHLVL="1" + declare -x container="lxc" + +And we can use that information to connect from another container as a client:: + + $ docker run -i -t -rm -link redis-name:redis_alias -entrypoint /bin/bash dockerfiles/redis -c '/redis-stable/src/redis-cli -h $REDIS_ALIAS_PORT_6379_TCP_ADDR -p $REDIS_ALIAS_PORT_6379_TCP_PORT' + 172.17.0.32:6379> + +VOLUME (Shared Filesystems) +........................... + +:: + + -v=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]. + If "container-dir" is missing, then docker creates a new volume. + -volumes-from="": Mount all volumes from the given container(s) + +The volumes commands are complex enough to have their own +documentation in section :ref:`volume_def`. A developer can define one +or more VOLUMEs associated with an image, but only the operator can +give access from one container to another (or from a container to a +volume mounted on the host). + +USER +.... + +:: + + -u="": Username or UID + +WORKDIR +....... + +:: + + -w="": Working directory inside the container + +Performance +=========== + +The operator can also adjust the performance parameters of the container:: + + -c=0 : CPU shares (relative weight) + -m="": Memory limit (format: , where unit = b, k, m or g) + + -lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" + -privileged=false: Give extended privileges to this container + From c4e7b0e4d539c66f095d726856562c65f25eac0b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 27 Jan 2014 23:25:14 +0000 Subject: [PATCH 249/364] fix mflag import Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- pkg/mflag/example/example.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/mflag/example/example.go b/pkg/mflag/example/example.go index dcff4277e2..543592d23b 100644 --- a/pkg/mflag/example/example.go +++ b/pkg/mflag/example/example.go @@ -2,7 +2,7 @@ package main import ( "fmt" - "github.com/dotcloud/docker/pkg/flag" + flag "github.com/dotcloud/docker/pkg/mflag" ) var ( From abbae8f76481701ed8147aefdb6e2ce82e130f4c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 27 Jan 2014 20:51:31 +0000 Subject: [PATCH 250/364] check for errors in call, not readBody Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- commands.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/commands.go b/commands.go index 08b2c01139..083ca39bc5 100644 --- a/commands.go +++ b/commands.go @@ -2319,6 +2319,18 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b } return nil, -1, err } + + if resp.StatusCode < 200 || resp.StatusCode >= 400 { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, -1, err + } + if len(body) == 0 { + return nil, resp.StatusCode, fmt.Errorf("Error :%s", http.StatusText(resp.StatusCode)) + } + return nil, resp.StatusCode, fmt.Errorf("Error: %s", bytes.TrimSpace(body)) + } + wrapper := utils.NewReadCloserWrapper(resp.Body, func() error { if resp != nil && resp.Body != nil { resp.Body.Close() @@ -2598,12 +2610,6 @@ func readBody(stream io.ReadCloser, statusCode int, err error) ([]byte, int, err if err != nil { return nil, -1, err } - if statusCode < 200 || statusCode >= 400 { - if len(body) == 0 { - return nil, statusCode, fmt.Errorf("Error: %s", http.StatusText(statusCode)) - } - return nil, statusCode, fmt.Errorf("Error: %s", bytes.TrimSpace(body)) - } return body, statusCode, nil } From 94566b748e69a57cf88ef4acbe18ecbe76bb2f54 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 27 Jan 2014 15:34:46 -0700 Subject: [PATCH 251/364] Update Dockerfile to be based on 13.10 instead of 12.04 so that we can remove our s3cmd backports need, and so we can have the new linux-libc-dev dependency we need for compiling the btrfs driver without nasty APT hackery In addition, we've now hard-coded LXC version 0.8 compiled from source so that we can have the most stable dev environment possible. Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- Dockerfile | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3980f8713a..5cee5e67d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,23 +24,23 @@ # docker-version 0.6.1 -FROM stackbrew/ubuntu:12.04 +FROM stackbrew/ubuntu:13.10 MAINTAINER Tianon Gravi (@tianon) -# Add precise-backports to get s3cmd >= 1.1.0 (so we get ENV variable support in our .s3cfg) -RUN echo 'deb http://archive.ubuntu.com/ubuntu precise-backports main universe' > /etc/apt/sources.list.d/backports.list - # Packaged dependencies RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq \ apt-utils \ aufs-tools \ + automake \ build-essential \ curl \ dpkg-sig \ git \ iptables \ + libapparmor-dev \ + libcap-dev \ libsqlite3-dev \ - lxc \ + linux-libc-dev \ mercurial \ reprepro \ ruby1.9.1 \ @@ -48,10 +48,14 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq \ s3cmd=1.1.0* \ --no-install-recommends +# Get and compile LXC 0.8 (since it is the most stable) +RUN git clone --no-checkout https://github.com/lxc/lxc.git /usr/local/lxc && cd /usr/local/lxc && git checkout -q lxc-0.8.0 +RUN cd /usr/local/lxc && ./autogen.sh && ./configure --disable-docs && make && make install + # Get lvm2 source for compiling statically -RUN git clone https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout -q v2_02_103 +RUN git clone --no-checkout https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout -q v2_02_103 # see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags -# note: we can't use "git clone -b" above because it requires at least git 1.7.10 to be able to use that on a tag instead of a branch and we only have 1.7.9.5 +# note: we don't use "git clone -b" above because it then spews big nasty warnings about 'detached HEAD' state that we can't silence as easily as we can silence them using "git checkout" directly # Compile and install lvm2 RUN cd /usr/local/lvm2 && ./configure --enable-static_link && make device-mapper && make install_device-mapper From e996daeed08defbab8864be1e26cd1294b12af87 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Fri, 6 Dec 2013 21:18:42 -0800 Subject: [PATCH 252/364] vendor: add github.com/coreos/go-systemd/activation Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- .../src/github.com/coreos/go-systemd/LICENSE | 191 ++++++++++++++++++ .../github.com/coreos/go-systemd/README.md | 3 + .../coreos/go-systemd/activation/files.go | 38 ++++ .../go-systemd/activation/files_test.go | 68 +++++++ 4 files changed, 300 insertions(+) create mode 100644 vendor/src/github.com/coreos/go-systemd/LICENSE create mode 100644 vendor/src/github.com/coreos/go-systemd/README.md create mode 100644 vendor/src/github.com/coreos/go-systemd/activation/files.go create mode 100644 vendor/src/github.com/coreos/go-systemd/activation/files_test.go diff --git a/vendor/src/github.com/coreos/go-systemd/LICENSE b/vendor/src/github.com/coreos/go-systemd/LICENSE new file mode 100644 index 0000000000..37ec93a14f --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/src/github.com/coreos/go-systemd/README.md b/vendor/src/github.com/coreos/go-systemd/README.md new file mode 100644 index 0000000000..9b8a2f83ff --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/README.md @@ -0,0 +1,3 @@ +# go-systemd + +Go bindings to systemd socket activation, journal and D-BUS APIs. diff --git a/vendor/src/github.com/coreos/go-systemd/activation/files.go b/vendor/src/github.com/coreos/go-systemd/activation/files.go new file mode 100644 index 0000000000..3f323d8201 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/activation/files.go @@ -0,0 +1,38 @@ +// Package activation implements primitives for systemd socket activation. +package activation + +import ( + "os" + "strconv" + "syscall" +) + +// based on: https://gist.github.com/alberts/4640792 +const ( + listenFdsStart = 3 +) + +func Files(unsetEnv bool) []*os.File { + + if unsetEnv { + // there is no way to unset env in golang os package for now + // https://code.google.com/p/go/issues/detail?id=6423 + defer os.Setenv("LISTEN_PID", "") + defer os.Setenv("LISTEN_FDS", "") + } + + pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) + if err != nil || pid != os.Getpid() { + return nil + } + nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")) + if err != nil || nfds == 0 { + return nil + } + var files []*os.File + for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ { + syscall.CloseOnExec(fd) + files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd))) + } + return files +} diff --git a/vendor/src/github.com/coreos/go-systemd/activation/files_test.go b/vendor/src/github.com/coreos/go-systemd/activation/files_test.go new file mode 100644 index 0000000000..cee919e765 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/activation/files_test.go @@ -0,0 +1,68 @@ +package activation + +import ( + "bytes" + "io" + "os" + "os/exec" + "testing" +) + +// correctStringWritten fails the text if the correct string wasn't written +// to the other side of the pipe. +func correctStringWritten(t *testing.T, r *os.File, expected string) bool { + bytes := make([]byte, len(expected)) + io.ReadAtLeast(r, bytes, len(expected)) + + if string(bytes) != expected { + t.Fatalf("Unexpected string %s", string(bytes)) + } + + return true +} + +// TestActivation forks out a copy of activation.go example and reads back two +// strings from the pipes that are passed in. +func TestActivation(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/activation.go") + + r1, w1, _ := os.Pipe() + r2, w2, _ := os.Pipe() + cmd.ExtraFiles = []*os.File{ + w1, + w2, + } + + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1") + + err := cmd.Run() + if err != nil { + t.Fatalf(err.Error()) + } + + correctStringWritten(t, r1, "Hello world") + correctStringWritten(t, r2, "Goodbye world") +} + +func TestActivationNoFix(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/activation.go") + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=2") + + out, _ := cmd.CombinedOutput() + if bytes.Contains(out, []byte("No files")) == false { + t.Fatalf("Child didn't error out as expected") + } +} + +func TestActivationNoFiles(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/activation.go") + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=0", "FIX_LISTEN_PID=1") + + out, _ := cmd.CombinedOutput() + if bytes.Contains(out, []byte("No files")) == false { + t.Fatalf("Child didn't error out as expected") + } +} From 87fb2c973d8f9a8a1868ab0c2da504095d04715b Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Fri, 6 Dec 2013 21:19:30 -0800 Subject: [PATCH 253/364] server: add socket activation This adds the ability to socket activate docker by passing in `-H fd://*` along with examples systemd configuration files. The fastest way to test this is to run: ``` /usr/lib/systemd/systemd-activate -l 127.0.0.1:2001 /usr/bin/docker -d -H 'fd://*' docker -H tcp://127.0.0.1:2001 ps ``` Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- api.go | 64 +++++++++++++++++-- .../systemd/socket-activation/docker.service | 11 ++++ .../systemd/socket-activation/docker.socket | 8 +++ server.go | 26 ++++---- systemd/listendfd.go | 41 ++++++++++++ utils/utils.go | 2 + 6 files changed, 130 insertions(+), 22 deletions(-) create mode 100644 contrib/init/systemd/socket-activation/docker.service create mode 100644 contrib/init/systemd/socket-activation/docker.socket create mode 100644 systemd/listendfd.go diff --git a/api.go b/api.go index bf9f29b57f..e522583087 100644 --- a/api.go +++ b/api.go @@ -24,6 +24,7 @@ import ( "regexp" "strconv" "strings" + "syscall" ) const ( @@ -1081,16 +1082,66 @@ func ServeRequest(srv *Server, apiversion float64, w http.ResponseWriter, req *h return nil } +// ServeFD creates an http.Server and sets it up to serve given a socket activated +// argument. +func ServeFd(addr string, handle http.Handler) error { + ls, e := systemd.ListenFD(addr) + if e != nil { + return e + } + + chErrors := make(chan error, len(ls)) + + // Since ListenFD will return one or more sockets we have + // to create a go func to spawn off multiple serves + for i, _ := range(ls) { + listener := ls[i] + go func () { + httpSrv := http.Server{Handler: handle} + chErrors <- httpSrv.Serve(listener) + }() + } + + for i := 0; i < len(ls); i += 1 { + err := <-chErrors + if err != nil { + return err + } + } + + return nil +} + +// ListenAndServe sets up the required http.Server and gets it listening for +// each addr passed in and does protocol specific checking. func ListenAndServe(proto, addr string, srv *Server, logging bool) error { r, err := createRouter(srv, logging) if err != nil { return err } - l, e := net.Listen(proto, addr) - if e != nil { - return e + + if proto == "fd" { + return ServeFd(addr, r) } + if proto == "unix" { + if err := syscall.Unlink(addr); err != nil && !os.IsNotExist(err) { + return err + } + } + + l, err := net.Listen(proto, addr) + if err != nil { + return err + } + + // Basic error and sanity checking + switch proto { + case "tcp": + if !strings.HasPrefix(addr, "127.0.0.1") { + log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") + } + case "unix": if err := os.Chmod(addr, 0660); err != nil { return err } @@ -1110,11 +1161,10 @@ func ListenAndServe(proto, addr string, srv *Server, logging bool) error { return err } } + default: + return fmt.Errorf("Invalid protocol format.") } - httpSrv := http.Server{Addr: addr, Handler: r} - log.Printf("Listening for HTTP on %s (%s)\n", addr, proto) - // Tell the init daemon we are accepting requests - go systemd.SdNotify("READY=1") + httpSrv := http.Server{Addr: addr, Handler: r} return httpSrv.Serve(l) } diff --git a/contrib/init/systemd/socket-activation/docker.service b/contrib/init/systemd/socket-activation/docker.service new file mode 100644 index 0000000000..0b39c28328 --- /dev/null +++ b/contrib/init/systemd/socket-activation/docker.service @@ -0,0 +1,11 @@ +[Unit] +Description=Docker Application Container Engine +Documentation=http://docs.docker.io +After=network.target + +[Service] +ExecStartPre=/bin/mount --make-rprivate / +ExecStart=/usr/bin/docker -d -H fd://* + +[Install] +WantedBy=multi-user.target diff --git a/contrib/init/systemd/socket-activation/docker.socket b/contrib/init/systemd/socket-activation/docker.socket new file mode 100644 index 0000000000..3635c89385 --- /dev/null +++ b/contrib/init/systemd/socket-activation/docker.socket @@ -0,0 +1,8 @@ +[Unit] +Description=Docker Socket for the API + +[Socket] +ListenStream=/var/run/docker.sock + +[Install] +WantedBy=sockets.target diff --git a/server.go b/server.go index b4add79771..b87ec659f8 100644 --- a/server.go +++ b/server.go @@ -10,6 +10,7 @@ import ( "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/registry" + "github.com/dotcloud/docker/systemd" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -114,29 +115,20 @@ func jobInitApi(job *engine.Job) engine.Status { return engine.StatusOK } +// ListenAndServe loops through all of the protocols sent in to docker and spawns +// off a go routine to setup a serving http.Server for each. func (srv *Server) ListenAndServe(job *engine.Job) engine.Status { protoAddrs := job.Args chErrors := make(chan error, len(protoAddrs)) + for _, protoAddr := range protoAddrs { protoAddrParts := strings.SplitN(protoAddr, "://", 2) - switch protoAddrParts[0] { - case "unix": - if err := syscall.Unlink(protoAddrParts[1]); err != nil && !os.IsNotExist(err) { - log.Fatal(err) - } - case "tcp": - if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") { - log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") - } - default: - job.Errorf("Invalid protocol format.") - return engine.StatusErr - } - go func() { - // FIXME: merge Server.ListenAndServe with ListenAndServe + go func () { + log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1]) chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, job.GetenvBool("Logging")) }() } + for i := 0; i < len(protoAddrs); i += 1 { err := <-chErrors if err != nil { @@ -144,6 +136,10 @@ func (srv *Server) ListenAndServe(job *engine.Job) engine.Status { return engine.StatusErr } } + + // Tell the init daemon we are accepting requests + go systemd.SdNotify("READY=1") + return engine.StatusOK } diff --git a/systemd/listendfd.go b/systemd/listendfd.go new file mode 100644 index 0000000000..a8fdb09ca4 --- /dev/null +++ b/systemd/listendfd.go @@ -0,0 +1,41 @@ +package systemd + +import ( + "errors" + "fmt" + "net" + "strconv" + + "github.com/coreos/go-systemd/activation" +) + +// ListenFD returns the specified socket activated files as a slice of +// net.Listeners or all of the activated files if "*" is given. +func ListenFD(addr string) ([]net.Listener, error) { + files := activation.Files(false) + if files == nil || len(files) == 0 { + return nil, errors.New("No sockets found") + } + + fdNum, _ := strconv.Atoi(addr) + fdOffset := fdNum - 3 + if (addr != "*") && (len(files) < int(fdOffset)+1) { + return nil, errors.New("Too few socket activated files passed in") + } + + // socket activation + listeners := make([]net.Listener, len(files)) + for i, f := range files { + var err error + listeners[i], err = net.FileListener(f) + if err != nil { + return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error()) + } + } + + if addr == "*" { + return listeners, nil + } + + return []net.Listener{listeners[fdOffset]}, nil +} diff --git a/utils/utils.go b/utils/utils.go index 2a11397212..542ab49702 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -767,6 +767,8 @@ func ParseHost(defaultHost string, defaultPort int, defaultUnix, addr string) (s case strings.HasPrefix(addr, "tcp://"): proto = "tcp" addr = strings.TrimPrefix(addr, "tcp://") + case strings.HasPrefix(addr, "fd://"): + return addr, nil case addr == "": proto = "unix" addr = defaultUnix From cfeb1f0f652e11b8b6a8b4e1cbed3835fc5d9546 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 21 Jan 2014 12:47:27 -0800 Subject: [PATCH 254/364] fix(docs): fixup based on changes in master Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- docker/docker.go | 2 +- docs/sources/reference/commandline/cli.rst | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index 5f5b3c17ce..5824c78270 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -43,7 +43,7 @@ func main() { flMtu = flag.Int([]string{"#mtu", "-mtu"}, docker.DefaultNetworkMtu, "Set the containers network mtu") ) flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers") - flag.Var(&flHosts, []string{"H", "-host"}, "Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise") + flag.Var(&flHosts, []string{"H", "-host"}, "tcp://host:port, unix://path/to/socket, fd://* or fd://socketfd to use in daemon mode. Multiple sockets can be specified") flag.Parse() diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index e71b691bcc..a0b2115987 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -27,7 +27,7 @@ To list available commands, either run ``docker`` with no parameters or execute Usage of docker: -D, --debug=false: Enable debug mode - -H, --host=[]: Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise + -H, --host=[]: Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise. systemd socket activation can be used with fd://[socketfd]. --api-enable-cors=false: Enable CORS headers in the remote API -b, --bridge="": Attach containers to a pre-existing network bridge; use 'none' to disable container networking --bip="": Use this CIDR notation address for the network bridge's IP, not compatible with -b @@ -63,6 +63,8 @@ the ``-H`` flag for the client. # both are equal +To run the daemon with socket activation, use ``docker -d -H fd://*``. Individual sockets can also be specified ``docker -d -H fd://3``. + .. _cli_attach: ``attach`` From 4a90f00ab7ec9e534c97611dbbdc412ed5966602 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 10 Dec 2013 17:39:20 -0800 Subject: [PATCH 255/364] docs: improve the socket activation cli docs as suggested by SvenDowideit expand the docs to have more information on socket activation. Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- docs/sources/reference/commandline/cli.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index a0b2115987..a9fc9100a0 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -63,7 +63,10 @@ the ``-H`` flag for the client. # both are equal -To run the daemon with socket activation, use ``docker -d -H fd://*``. Individual sockets can also be specified ``docker -d -H fd://3``. +To run the daemon with `socket activation `, use ``docker -d -H fd://*``. +Using ``fd://*`` will work perfectly for most setups but you can also specify individual sockets too ``docker -d -H fd://3``. +If the specified socket activated files aren't found then docker will exit. +You can find examples of using socket activation with docker and systemd in the `docker source tree `. .. _cli_attach: From 6acc99889d97638d632a2974e04cd91ed4036c4b Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 18 Dec 2013 10:15:31 -0800 Subject: [PATCH 256/364] fix(docs): add Systemd in front of socket activation Suggested by SvenDowideit. Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- docs/sources/reference/commandline/cli.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index a9fc9100a0..e9809156d5 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -63,10 +63,10 @@ the ``-H`` flag for the client. # both are equal -To run the daemon with `socket activation `, use ``docker -d -H fd://*``. +To run the daemon with `systemd socket activation `, use ``docker -d -H fd://*``. Using ``fd://*`` will work perfectly for most setups but you can also specify individual sockets too ``docker -d -H fd://3``. If the specified socket activated files aren't found then docker will exit. -You can find examples of using socket activation with docker and systemd in the `docker source tree `. +You can find examples of using systemd socket activation with docker and systemd in the `docker source tree `. .. _cli_attach: From 566fb31c889cfa05c617f44e6c99d44578d643a5 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Fri, 3 Jan 2014 18:06:34 -0800 Subject: [PATCH 257/364] Move listenfd to utility package Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- {systemd => pkg/systemd}/listendfd.go | 0 server.go | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {systemd => pkg/systemd}/listendfd.go (100%) diff --git a/systemd/listendfd.go b/pkg/systemd/listendfd.go similarity index 100% rename from systemd/listendfd.go rename to pkg/systemd/listendfd.go diff --git a/server.go b/server.go index b87ec659f8..a9d4a20bfb 100644 --- a/server.go +++ b/server.go @@ -10,7 +10,7 @@ import ( "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/registry" - "github.com/dotcloud/docker/systemd" + "github.com/dotcloud/docker/pkg/systemd" "github.com/dotcloud/docker/utils" "io" "io/ioutil" From 7f9d3268bf0e1d4a79d07c67e22eb14d3de96f6c Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Fri, 3 Jan 2014 18:07:43 -0800 Subject: [PATCH 258/364] Allow fd:// like unix:// and tcp:// Somthing like 20605eb310f0b57bd06eea80ec63c5022fc83bde Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- contrib/init/systemd/socket-activation/docker.service | 2 +- docs/sources/reference/commandline/cli.rst | 4 ++-- pkg/systemd/listendfd.go | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/contrib/init/systemd/socket-activation/docker.service b/contrib/init/systemd/socket-activation/docker.service index 0b39c28328..405bdf5afc 100644 --- a/contrib/init/systemd/socket-activation/docker.service +++ b/contrib/init/systemd/socket-activation/docker.service @@ -5,7 +5,7 @@ After=network.target [Service] ExecStartPre=/bin/mount --make-rprivate / -ExecStart=/usr/bin/docker -d -H fd://* +ExecStart=/usr/bin/docker -d -H fd:// [Install] WantedBy=multi-user.target diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index e9809156d5..739e93975d 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -63,8 +63,8 @@ the ``-H`` flag for the client. # both are equal -To run the daemon with `systemd socket activation `, use ``docker -d -H fd://*``. -Using ``fd://*`` will work perfectly for most setups but you can also specify individual sockets too ``docker -d -H fd://3``. +To run the daemon with `systemd socket activation `, use ``docker -d -H fd://``. +Using ``fd://`` will work perfectly for most setups but you can also specify individual sockets too ``docker -d -H fd://3``. If the specified socket activated files aren't found then docker will exit. You can find examples of using systemd socket activation with docker and systemd in the `docker source tree `. diff --git a/pkg/systemd/listendfd.go b/pkg/systemd/listendfd.go index a8fdb09ca4..08070b6ece 100644 --- a/pkg/systemd/listendfd.go +++ b/pkg/systemd/listendfd.go @@ -17,6 +17,11 @@ func ListenFD(addr string) ([]net.Listener, error) { return nil, errors.New("No sockets found") } + // default to all fds just like unix:// and tcp:// + if addr == "" { + addr = "*" + } + fdNum, _ := strconv.Atoi(addr) fdOffset := fdNum - 3 if (addr != "*") && (len(files) < int(fdOffset)+1) { From 2fe7588af79bbc61e758ff066be1cf1c2d381f7b Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 7 Jan 2014 16:10:32 -0800 Subject: [PATCH 259/364] fix(cli.rst): add missing underscores As suggested by @metalivedev Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- docs/sources/reference/commandline/cli.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 739e93975d..3d215cc0b4 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -63,10 +63,10 @@ the ``-H`` flag for the client. # both are equal -To run the daemon with `systemd socket activation `, use ``docker -d -H fd://``. +To run the daemon with `systemd socket activation `_, use ``docker -d -H fd://``. Using ``fd://`` will work perfectly for most setups but you can also specify individual sockets too ``docker -d -H fd://3``. If the specified socket activated files aren't found then docker will exit. -You can find examples of using systemd socket activation with docker and systemd in the `docker source tree `. +You can find examples of using systemd socket activation with docker and systemd in the `docker source tree `_. .. _cli_attach: From 1f44921c51883e923ed82ed4e30eca6f79702cfb Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 14 Jan 2014 10:06:08 -0800 Subject: [PATCH 260/364] fix(contrib/init/systemd): remove mount rprivate Docker does this now via 157d99a72786c454dfaad8b0800914cc80879aa8 Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- contrib/init/systemd/socket-activation/docker.service | 1 - 1 file changed, 1 deletion(-) diff --git a/contrib/init/systemd/socket-activation/docker.service b/contrib/init/systemd/socket-activation/docker.service index 405bdf5afc..4ab92dfef8 100644 --- a/contrib/init/systemd/socket-activation/docker.service +++ b/contrib/init/systemd/socket-activation/docker.service @@ -4,7 +4,6 @@ Documentation=http://docs.docker.io After=network.target [Service] -ExecStartPre=/bin/mount --make-rprivate / ExecStart=/usr/bin/docker -d -H fd:// [Install] From 1603039a7150e0510853efc9a9733ef64508dbb0 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 14 Jan 2014 20:07:39 -0800 Subject: [PATCH 261/364] chore(*): go fmt I noticed that travis was failing, go fmt to make it happy. Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- api.go | 4 ++-- server.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api.go b/api.go index e522583087..9c2c1fcdba 100644 --- a/api.go +++ b/api.go @@ -1094,9 +1094,9 @@ func ServeFd(addr string, handle http.Handler) error { // Since ListenFD will return one or more sockets we have // to create a go func to spawn off multiple serves - for i, _ := range(ls) { + for i, _ := range ls { listener := ls[i] - go func () { + go func() { httpSrv := http.Server{Handler: handle} chErrors <- httpSrv.Serve(listener) }() diff --git a/server.go b/server.go index a9d4a20bfb..14974dec73 100644 --- a/server.go +++ b/server.go @@ -9,8 +9,8 @@ import ( "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/graphdb" - "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/pkg/systemd" + "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -123,7 +123,7 @@ func (srv *Server) ListenAndServe(job *engine.Job) engine.Status { for _, protoAddr := range protoAddrs { protoAddrParts := strings.SplitN(protoAddr, "://", 2) - go func () { + go func() { log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1]) chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, job.GetenvBool("Logging")) }() From f82d1291cc850b39d872d022feac6b9eb7773d64 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Fri, 17 Jan 2014 13:18:43 -0800 Subject: [PATCH 262/364] vendor: bump github.com/coreos/go-systemd/activation tests now work in the Docker tree with `go test github.com/coreos/go-systemd/activation` Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- .../coreos/go-systemd/activation/files.go | 4 +- .../coreos/go-systemd/activation/listeners.go | 22 ++++++ .../go-systemd/activation/listeners_test.go | 72 +++++++++++++++++++ .../examples/activation/activation.go | 44 ++++++++++++ .../examples/activation/httpserver/README.md | 1 + .../activation/httpserver/hello.service | 11 +++ .../activation/httpserver/hello.socket | 5 ++ .../activation/httpserver/httpserver.go | 29 ++++++++ .../go-systemd/examples/activation/listen.go | 50 +++++++++++++ 9 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 vendor/src/github.com/coreos/go-systemd/activation/listeners.go create mode 100644 vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go diff --git a/vendor/src/github.com/coreos/go-systemd/activation/files.go b/vendor/src/github.com/coreos/go-systemd/activation/files.go index 3f323d8201..d4f4cc58ea 100644 --- a/vendor/src/github.com/coreos/go-systemd/activation/files.go +++ b/vendor/src/github.com/coreos/go-systemd/activation/files.go @@ -13,7 +13,6 @@ const ( ) func Files(unsetEnv bool) []*os.File { - if unsetEnv { // there is no way to unset env in golang os package for now // https://code.google.com/p/go/issues/detail?id=6423 @@ -25,14 +24,17 @@ func Files(unsetEnv bool) []*os.File { if err != nil || pid != os.Getpid() { return nil } + nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")) if err != nil || nfds == 0 { return nil } + var files []*os.File for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ { syscall.CloseOnExec(fd) files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd))) } + return files } diff --git a/vendor/src/github.com/coreos/go-systemd/activation/listeners.go b/vendor/src/github.com/coreos/go-systemd/activation/listeners.go new file mode 100644 index 0000000000..0fbd4ead6f --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/activation/listeners.go @@ -0,0 +1,22 @@ +package activation + +import ( + "fmt" + "net" +) + +// Listeners returns net.Listeners for all socket activated fds passed to this process. +func Listeners(unsetEnv bool) ([]net.Listener, error) { + files := Files(unsetEnv) + listeners := make([]net.Listener, len(files)) + + for i, f := range files { + var err error + listeners[i], err = net.FileListener(f) + if err != nil { + return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error()) + } + } + + return listeners, nil +} diff --git a/vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go b/vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go new file mode 100644 index 0000000000..6a8852184b --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go @@ -0,0 +1,72 @@ +package activation + +import ( + "io" + "net" + "os" + "os/exec" + "testing" +) + +// correctStringWritten fails the text if the correct string wasn't written +// to the other side of the pipe. +func correctStringWrittenNet(t *testing.T, r net.Conn, expected string) bool { + bytes := make([]byte, len(expected)) + io.ReadAtLeast(r, bytes, len(expected)) + + if string(bytes) != expected { + t.Fatalf("Unexpected string %s", string(bytes)) + } + + return true +} + +// TestActivation forks out a copy of activation.go example and reads back two +// strings from the pipes that are passed in. +func TestListeners(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/listen.go") + + l1, err := net.Listen("tcp", ":9999") + if err != nil { + t.Fatalf(err.Error()) + } + l2, err := net.Listen("tcp", ":1234") + if err != nil { + t.Fatalf(err.Error()) + } + + t1 := l1.(*net.TCPListener) + t2 := l2.(*net.TCPListener) + + f1, _ := t1.File() + f2, _ := t2.File() + + cmd.ExtraFiles = []*os.File{ + f1, + f2, + } + + r1, err := net.Dial("tcp", "127.0.0.1:9999") + if err != nil { + t.Fatalf(err.Error()) + } + r1.Write([]byte("Hi")) + + r2, err := net.Dial("tcp", "127.0.0.1:1234") + if err != nil { + t.Fatalf(err.Error()) + } + r2.Write([]byte("Hi")) + + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1") + + out, err := cmd.Output() + if err != nil { + println(string(out)) + t.Fatalf(err.Error()) + } + + correctStringWrittenNet(t, r1, "Hello world") + correctStringWrittenNet(t, r2, "Goodbye world") +} diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go b/vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go new file mode 100644 index 0000000000..b3cf70ed84 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go @@ -0,0 +1,44 @@ +// Activation example used by the activation unit tests. +package main + +import ( + "fmt" + "os" + + "github.com/coreos/go-systemd/activation" +) + +func fixListenPid() { + if os.Getenv("FIX_LISTEN_PID") != "" { + // HACK: real systemd would set LISTEN_PID before exec'ing but + // this is too difficult in golang for the purpose of a test. + // Do not do this in real code. + os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid())) + } +} + +func main() { + fixListenPid() + + files := activation.Files(false) + + if len(files) == 0 { + panic("No files") + } + + if os.Getenv("LISTEN_PID") == "" || os.Getenv("LISTEN_FDS") == "" { + panic("Should not unset envs") + } + + files = activation.Files(true) + + if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" { + panic("Can not unset envs") + } + + // Write out the expected strings to the two pipes + files[0].Write([]byte("Hello world")) + files[1].Write([]byte("Goodbye world")) + + return +} diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md new file mode 100644 index 0000000000..91c7cbf130 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md @@ -0,0 +1 @@ +Example of using socket activation with systemd to serve a simple HTTP server on http://127.0.0.1:8076 diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service new file mode 100644 index 0000000000..c8dea0f6b3 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service @@ -0,0 +1,11 @@ +[Unit] +Description=Hello World HTTP +Requires=network.target +After=multi-user.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/httpserver + +[Install] +WantedBy=multi-user.target diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket new file mode 100644 index 0000000000..723ed7ed92 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket @@ -0,0 +1,5 @@ +[Socket] +ListenStream=127.0.0.1:8076 + +[Install] +WantedBy=sockets.target diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go new file mode 100644 index 0000000000..db12ecf612 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go @@ -0,0 +1,29 @@ +package main + +import ( + "io" + "net" + "net/http" + + "github.com/coreos/go-systemd/activation" +) + +func HelloServer(w http.ResponseWriter, req *http.Request) { + io.WriteString(w, "hello socket activated world!\n") +} + +func main() { + files := activation.Files(true) + + if len(files) != 1 { + panic("Unexpected number of socket activation fds") + } + + l, err := net.FileListener(files[0]) + if err != nil { + panic(err) + } + + http.HandleFunc("/", HelloServer) + http.Serve(l, nil) +} diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go b/vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go new file mode 100644 index 0000000000..5850a8b796 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go @@ -0,0 +1,50 @@ +// Activation example used by the activation unit tests. +package main + +import ( + "fmt" + "os" + + "github.com/coreos/go-systemd/activation" +) + +func fixListenPid() { + if os.Getenv("FIX_LISTEN_PID") != "" { + // HACK: real systemd would set LISTEN_PID before exec'ing but + // this is too difficult in golang for the purpose of a test. + // Do not do this in real code. + os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid())) + } +} + +func main() { + fixListenPid() + + listeners, _ := activation.Listeners(false) + + if len(listeners) == 0 { + panic("No listeners") + } + + if os.Getenv("LISTEN_PID") == "" || os.Getenv("LISTEN_FDS") == "" { + panic("Should not unset envs") + } + + listeners, err := activation.Listeners(true) + if err != nil { + panic(err) + } + + if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" { + panic("Can not unset envs") + } + + c0, _ := listeners[0].Accept() + c1, _ := listeners[1].Accept() + + // Write out the expected strings to the two pipes + c0.Write([]byte("Hello world")) + c1.Write([]byte("Goodbye world")) + + return +} From def09526066001eefe16dbc6475b93bc1a9af0a2 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Fri, 17 Jan 2014 14:07:06 -0800 Subject: [PATCH 263/364] chore(systemd): use activation.Listeners instead of Files Use this Listeners() API that was exposed to save a few more lines of boiler plate code. Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- pkg/systemd/listendfd.go | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/pkg/systemd/listendfd.go b/pkg/systemd/listendfd.go index 08070b6ece..0fbc0a6ab6 100644 --- a/pkg/systemd/listendfd.go +++ b/pkg/systemd/listendfd.go @@ -2,7 +2,6 @@ package systemd import ( "errors" - "fmt" "net" "strconv" @@ -12,8 +11,13 @@ import ( // ListenFD returns the specified socket activated files as a slice of // net.Listeners or all of the activated files if "*" is given. func ListenFD(addr string) ([]net.Listener, error) { - files := activation.Files(false) - if files == nil || len(files) == 0 { + // socket activation + listeners, err := activation.Listeners(false) + if err != nil { + return nil, err + } + + if listeners == nil || len(listeners) == 0 { return nil, errors.New("No sockets found") } @@ -24,20 +28,10 @@ func ListenFD(addr string) ([]net.Listener, error) { fdNum, _ := strconv.Atoi(addr) fdOffset := fdNum - 3 - if (addr != "*") && (len(files) < int(fdOffset)+1) { + if (addr != "*") && (len(listeners) < int(fdOffset)+1) { return nil, errors.New("Too few socket activated files passed in") } - // socket activation - listeners := make([]net.Listener, len(files)) - for i, f := range files { - var err error - listeners[i], err = net.FileListener(f) - if err != nil { - return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error()) - } - } - if addr == "*" { return listeners, nil } From 7839350dd582cc0f08b5e0de735d57baa08328ff Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Tue, 21 Jan 2014 12:43:57 -0800 Subject: [PATCH 264/364] chore(coreos/go-systemd): copy to github.com/dotcloud/docker/systemd/pkg/activation Via https://github.com/dotcloud/docker/pull/3105#issuecomment-32807547 Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- .../systemd}/activation/files.go | 15 ++ pkg/systemd/activation/listeners.go | 37 ++++ pkg/systemd/listendfd.go | 2 +- .../src/github.com/coreos/go-systemd/LICENSE | 191 ------------------ .../github.com/coreos/go-systemd/README.md | 3 - .../go-systemd/activation/files_test.go | 68 ------- .../coreos/go-systemd/activation/listeners.go | 22 -- .../go-systemd/activation/listeners_test.go | 72 ------- .../examples/activation/activation.go | 44 ---- .../examples/activation/httpserver/README.md | 1 - .../activation/httpserver/hello.service | 11 - .../activation/httpserver/hello.socket | 5 - .../activation/httpserver/httpserver.go | 29 --- .../go-systemd/examples/activation/listen.go | 50 ----- 14 files changed, 53 insertions(+), 497 deletions(-) rename {vendor/src/github.com/coreos/go-systemd => pkg/systemd}/activation/files.go (61%) create mode 100644 pkg/systemd/activation/listeners.go delete mode 100644 vendor/src/github.com/coreos/go-systemd/LICENSE delete mode 100644 vendor/src/github.com/coreos/go-systemd/README.md delete mode 100644 vendor/src/github.com/coreos/go-systemd/activation/files_test.go delete mode 100644 vendor/src/github.com/coreos/go-systemd/activation/listeners.go delete mode 100644 vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go delete mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go delete mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md delete mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service delete mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket delete mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go delete mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go diff --git a/vendor/src/github.com/coreos/go-systemd/activation/files.go b/pkg/systemd/activation/files.go similarity index 61% rename from vendor/src/github.com/coreos/go-systemd/activation/files.go rename to pkg/systemd/activation/files.go index d4f4cc58ea..0281146310 100644 --- a/vendor/src/github.com/coreos/go-systemd/activation/files.go +++ b/pkg/systemd/activation/files.go @@ -1,3 +1,18 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ // Package activation implements primitives for systemd socket activation. package activation diff --git a/pkg/systemd/activation/listeners.go b/pkg/systemd/activation/listeners.go new file mode 100644 index 0000000000..3296a08361 --- /dev/null +++ b/pkg/systemd/activation/listeners.go @@ -0,0 +1,37 @@ +/* +Copyright 2014 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package activation + +import ( + "fmt" + "net" +) + +// Listeners returns net.Listeners for all socket activated fds passed to this process. +func Listeners(unsetEnv bool) ([]net.Listener, error) { + files := Files(unsetEnv) + listeners := make([]net.Listener, len(files)) + + for i, f := range files { + var err error + listeners[i], err = net.FileListener(f) + if err != nil { + return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error()) + } + } + + return listeners, nil +} diff --git a/pkg/systemd/listendfd.go b/pkg/systemd/listendfd.go index 0fbc0a6ab6..f6044328c2 100644 --- a/pkg/systemd/listendfd.go +++ b/pkg/systemd/listendfd.go @@ -5,7 +5,7 @@ import ( "net" "strconv" - "github.com/coreos/go-systemd/activation" + "github.com/dotcloud/docker/pkg/systemd/activation" ) // ListenFD returns the specified socket activated files as a slice of diff --git a/vendor/src/github.com/coreos/go-systemd/LICENSE b/vendor/src/github.com/coreos/go-systemd/LICENSE deleted file mode 100644 index 37ec93a14f..0000000000 --- a/vendor/src/github.com/coreos/go-systemd/LICENSE +++ /dev/null @@ -1,191 +0,0 @@ -Apache License -Version 2.0, January 2004 -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright -owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, "control" means (i) the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the -outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising -permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. - -"Object" form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -"submitted" means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -2. Grant of Copyright License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable copyright license to reproduce, prepare Derivative Works of, -publicly display, publicly perform, sublicense, and distribute the Work and such -Derivative Works in Source or Object form. - -3. Grant of Patent License. - -Subject to the terms and conditions of this License, each Contributor hereby -grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, -irrevocable (except as stated in this section) patent license to make, have -made, use, offer to sell, sell, import, and otherwise transfer the Work, where -such license applies only to those patent claims licensable by such Contributor -that are necessarily infringed by their Contribution(s) alone or by combination -of their Contribution(s) with the Work to which such Contribution(s) was -submitted. If You institute patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Work or a -Contribution incorporated within the Work constitutes direct or contributory -patent infringement, then any patent licenses granted to You under this License -for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. - -You may reproduce and distribute copies of the Work or Derivative Works thereof -in any medium, with or without modifications, and in Source or Object form, -provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of -this License; and -You must cause any modified files to carry prominent notices stating that You -changed the files; and -You must retain, in the Source form of any Derivative Works that You distribute, -all copyright, patent, trademark, and attribution notices from the Source form -of the Work, excluding those notices that do not pertain to any part of the -Derivative Works; and -If the Work includes a "NOTICE" text file as part of its distribution, then any -Derivative Works that You distribute must include a readable copy of the -attribution notices contained within such NOTICE file, excluding those notices -that do not pertain to any part of the Derivative Works, in at least one of the -following places: within a NOTICE text file distributed as part of the -Derivative Works; within the Source form or documentation, if provided along -with the Derivative Works; or, within a display generated by the Derivative -Works, if and wherever such third-party notices normally appear. The contents of -the NOTICE file are for informational purposes only and do not modify the -License. You may add Your own attribution notices within Derivative Works that -You distribute, alongside or as an addendum to the NOTICE text from the Work, -provided that such additional attribution notices cannot be construed as -modifying the License. -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use, reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -5. Submission of Contributions. - -Unless You explicitly state otherwise, any Contribution intentionally submitted -for inclusion in the Work by You to the Licensor shall be under the terms and -conditions of this License, without any additional terms or conditions. -Notwithstanding the above, nothing herein shall supersede or modify the terms of -any separate license agreement you may have executed with Licensor regarding -such Contributions. - -6. Trademarks. - -This License does not grant permission to use the trade names, trademarks, -service marks, or product names of the Licensor, except as required for -reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. - -Unless required by applicable law or agreed to in writing, Licensor provides the -Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, -including, without limitation, any warranties or conditions of TITLE, -NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are -solely responsible for determining the appropriateness of using or -redistributing the Work and assume any risks associated with Your exercise of -permissions under this License. - -8. Limitation of Liability. - -In no event and under no legal theory, whether in tort (including negligence), -contract, or otherwise, unless required by applicable law (such as deliberate -and grossly negligent acts) or agreed to in writing, shall any Contributor be -liable to You for damages, including any direct, indirect, special, incidental, -or consequential damages of any character arising as a result of this License or -out of the use or inability to use the Work (including but not limited to -damages for loss of goodwill, work stoppage, computer failure or malfunction, or -any and all other commercial damages or losses), even if such Contributor has -been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. - -While redistributing the Work or Derivative Works thereof, You may choose to -offer, and charge a fee for, acceptance of support, warranty, indemnity, or -other liability obligations and/or rights consistent with this License. However, -in accepting such obligations, You may act only on Your own behalf and on Your -sole responsibility, not on behalf of any other Contributor, and only if You -agree to indemnify, defend, and hold each Contributor harmless for any liability -incurred by, or claims asserted against, such Contributor by reason of your -accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work - -To apply the Apache License to your work, attach the following boilerplate -notice, with the fields enclosed by brackets "[]" replaced with your own -identifying information. (Don't include the brackets!) The text should be -enclosed in the appropriate comment syntax for the file format. We also -recommend that a file or class name and description of purpose be included on -the same "printed page" as the copyright notice for easier identification within -third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/vendor/src/github.com/coreos/go-systemd/README.md b/vendor/src/github.com/coreos/go-systemd/README.md deleted file mode 100644 index 9b8a2f83ff..0000000000 --- a/vendor/src/github.com/coreos/go-systemd/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# go-systemd - -Go bindings to systemd socket activation, journal and D-BUS APIs. diff --git a/vendor/src/github.com/coreos/go-systemd/activation/files_test.go b/vendor/src/github.com/coreos/go-systemd/activation/files_test.go deleted file mode 100644 index cee919e765..0000000000 --- a/vendor/src/github.com/coreos/go-systemd/activation/files_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package activation - -import ( - "bytes" - "io" - "os" - "os/exec" - "testing" -) - -// correctStringWritten fails the text if the correct string wasn't written -// to the other side of the pipe. -func correctStringWritten(t *testing.T, r *os.File, expected string) bool { - bytes := make([]byte, len(expected)) - io.ReadAtLeast(r, bytes, len(expected)) - - if string(bytes) != expected { - t.Fatalf("Unexpected string %s", string(bytes)) - } - - return true -} - -// TestActivation forks out a copy of activation.go example and reads back two -// strings from the pipes that are passed in. -func TestActivation(t *testing.T) { - cmd := exec.Command("go", "run", "../examples/activation/activation.go") - - r1, w1, _ := os.Pipe() - r2, w2, _ := os.Pipe() - cmd.ExtraFiles = []*os.File{ - w1, - w2, - } - - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1") - - err := cmd.Run() - if err != nil { - t.Fatalf(err.Error()) - } - - correctStringWritten(t, r1, "Hello world") - correctStringWritten(t, r2, "Goodbye world") -} - -func TestActivationNoFix(t *testing.T) { - cmd := exec.Command("go", "run", "../examples/activation/activation.go") - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, "LISTEN_FDS=2") - - out, _ := cmd.CombinedOutput() - if bytes.Contains(out, []byte("No files")) == false { - t.Fatalf("Child didn't error out as expected") - } -} - -func TestActivationNoFiles(t *testing.T) { - cmd := exec.Command("go", "run", "../examples/activation/activation.go") - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, "LISTEN_FDS=0", "FIX_LISTEN_PID=1") - - out, _ := cmd.CombinedOutput() - if bytes.Contains(out, []byte("No files")) == false { - t.Fatalf("Child didn't error out as expected") - } -} diff --git a/vendor/src/github.com/coreos/go-systemd/activation/listeners.go b/vendor/src/github.com/coreos/go-systemd/activation/listeners.go deleted file mode 100644 index 0fbd4ead6f..0000000000 --- a/vendor/src/github.com/coreos/go-systemd/activation/listeners.go +++ /dev/null @@ -1,22 +0,0 @@ -package activation - -import ( - "fmt" - "net" -) - -// Listeners returns net.Listeners for all socket activated fds passed to this process. -func Listeners(unsetEnv bool) ([]net.Listener, error) { - files := Files(unsetEnv) - listeners := make([]net.Listener, len(files)) - - for i, f := range files { - var err error - listeners[i], err = net.FileListener(f) - if err != nil { - return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error()) - } - } - - return listeners, nil -} diff --git a/vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go b/vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go deleted file mode 100644 index 6a8852184b..0000000000 --- a/vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package activation - -import ( - "io" - "net" - "os" - "os/exec" - "testing" -) - -// correctStringWritten fails the text if the correct string wasn't written -// to the other side of the pipe. -func correctStringWrittenNet(t *testing.T, r net.Conn, expected string) bool { - bytes := make([]byte, len(expected)) - io.ReadAtLeast(r, bytes, len(expected)) - - if string(bytes) != expected { - t.Fatalf("Unexpected string %s", string(bytes)) - } - - return true -} - -// TestActivation forks out a copy of activation.go example and reads back two -// strings from the pipes that are passed in. -func TestListeners(t *testing.T) { - cmd := exec.Command("go", "run", "../examples/activation/listen.go") - - l1, err := net.Listen("tcp", ":9999") - if err != nil { - t.Fatalf(err.Error()) - } - l2, err := net.Listen("tcp", ":1234") - if err != nil { - t.Fatalf(err.Error()) - } - - t1 := l1.(*net.TCPListener) - t2 := l2.(*net.TCPListener) - - f1, _ := t1.File() - f2, _ := t2.File() - - cmd.ExtraFiles = []*os.File{ - f1, - f2, - } - - r1, err := net.Dial("tcp", "127.0.0.1:9999") - if err != nil { - t.Fatalf(err.Error()) - } - r1.Write([]byte("Hi")) - - r2, err := net.Dial("tcp", "127.0.0.1:1234") - if err != nil { - t.Fatalf(err.Error()) - } - r2.Write([]byte("Hi")) - - cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1") - - out, err := cmd.Output() - if err != nil { - println(string(out)) - t.Fatalf(err.Error()) - } - - correctStringWrittenNet(t, r1, "Hello world") - correctStringWrittenNet(t, r2, "Goodbye world") -} diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go b/vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go deleted file mode 100644 index b3cf70ed84..0000000000 --- a/vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go +++ /dev/null @@ -1,44 +0,0 @@ -// Activation example used by the activation unit tests. -package main - -import ( - "fmt" - "os" - - "github.com/coreos/go-systemd/activation" -) - -func fixListenPid() { - if os.Getenv("FIX_LISTEN_PID") != "" { - // HACK: real systemd would set LISTEN_PID before exec'ing but - // this is too difficult in golang for the purpose of a test. - // Do not do this in real code. - os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid())) - } -} - -func main() { - fixListenPid() - - files := activation.Files(false) - - if len(files) == 0 { - panic("No files") - } - - if os.Getenv("LISTEN_PID") == "" || os.Getenv("LISTEN_FDS") == "" { - panic("Should not unset envs") - } - - files = activation.Files(true) - - if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" { - panic("Can not unset envs") - } - - // Write out the expected strings to the two pipes - files[0].Write([]byte("Hello world")) - files[1].Write([]byte("Goodbye world")) - - return -} diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md deleted file mode 100644 index 91c7cbf130..0000000000 --- a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md +++ /dev/null @@ -1 +0,0 @@ -Example of using socket activation with systemd to serve a simple HTTP server on http://127.0.0.1:8076 diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service deleted file mode 100644 index c8dea0f6b3..0000000000 --- a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=Hello World HTTP -Requires=network.target -After=multi-user.target - -[Service] -Type=simple -ExecStart=/usr/local/bin/httpserver - -[Install] -WantedBy=multi-user.target diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket deleted file mode 100644 index 723ed7ed92..0000000000 --- a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket +++ /dev/null @@ -1,5 +0,0 @@ -[Socket] -ListenStream=127.0.0.1:8076 - -[Install] -WantedBy=sockets.target diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go deleted file mode 100644 index db12ecf612..0000000000 --- a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go +++ /dev/null @@ -1,29 +0,0 @@ -package main - -import ( - "io" - "net" - "net/http" - - "github.com/coreos/go-systemd/activation" -) - -func HelloServer(w http.ResponseWriter, req *http.Request) { - io.WriteString(w, "hello socket activated world!\n") -} - -func main() { - files := activation.Files(true) - - if len(files) != 1 { - panic("Unexpected number of socket activation fds") - } - - l, err := net.FileListener(files[0]) - if err != nil { - panic(err) - } - - http.HandleFunc("/", HelloServer) - http.Serve(l, nil) -} diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go b/vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go deleted file mode 100644 index 5850a8b796..0000000000 --- a/vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go +++ /dev/null @@ -1,50 +0,0 @@ -// Activation example used by the activation unit tests. -package main - -import ( - "fmt" - "os" - - "github.com/coreos/go-systemd/activation" -) - -func fixListenPid() { - if os.Getenv("FIX_LISTEN_PID") != "" { - // HACK: real systemd would set LISTEN_PID before exec'ing but - // this is too difficult in golang for the purpose of a test. - // Do not do this in real code. - os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid())) - } -} - -func main() { - fixListenPid() - - listeners, _ := activation.Listeners(false) - - if len(listeners) == 0 { - panic("No listeners") - } - - if os.Getenv("LISTEN_PID") == "" || os.Getenv("LISTEN_FDS") == "" { - panic("Should not unset envs") - } - - listeners, err := activation.Listeners(true) - if err != nil { - panic(err) - } - - if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" { - panic("Can not unset envs") - } - - c0, _ := listeners[0].Accept() - c1, _ := listeners[1].Accept() - - // Write out the expected strings to the two pipes - c0.Write([]byte("Hello world")) - c1.Write([]byte("Goodbye world")) - - return -} From 4e5859e8300e7c56f28ca07c47131d14f6cf8632 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Mon, 27 Jan 2014 17:09:36 -0800 Subject: [PATCH 265/364] fix(api): , _ removed to simplify code in range Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api.go b/api.go index 9c2c1fcdba..85c06bb26b 100644 --- a/api.go +++ b/api.go @@ -1094,7 +1094,7 @@ func ServeFd(addr string, handle http.Handler) error { // Since ListenFD will return one or more sockets we have // to create a go func to spawn off multiple serves - for i, _ := range ls { + for i := range ls { listener := ls[i] go func() { httpSrv := http.Server{Handler: handle} From cabe624c823348579ada234e3c029479683f624c Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 27 Jan 2014 21:35:05 -0700 Subject: [PATCH 266/364] Add --ip-forward flag to daemon (enabled by default) which automatically sets "net.ipv4.ip_forward" to 1 See also https://groups.google.com/d/topic/docker-dev/DCjF5Prx7HA/discussion Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- config.go | 2 ++ docker/docker.go | 2 ++ network.go | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/config.go b/config.go index 5a6de7a873..a948744f0e 100644 --- a/config.go +++ b/config.go @@ -13,6 +13,7 @@ type DaemonConfig struct { EnableCors bool Dns []string EnableIptables bool + EnableIpForward bool BridgeIface string BridgeIp string DefaultIp net.IP @@ -33,6 +34,7 @@ func ConfigFromJob(job *engine.Job) *DaemonConfig { config.Dns = dns } config.EnableIptables = job.GetenvBool("EnableIptables") + config.EnableIpForward = job.GetenvBool("EnableIpForward") if br := job.Getenv("BridgeIface"); br != "" { config.BridgeIface = br } else { diff --git a/docker/docker.go b/docker/docker.go index 5f5b3c17ce..3a9b14db5f 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -36,6 +36,7 @@ func main() { flEnableCors = flag.Bool([]string{"#api-enable-cors", "-api-enable-cors"}, false, "Enable CORS headers in the remote API") flDns = docker.NewListOpts(docker.ValidateIp4Address) flEnableIptables = flag.Bool([]string{"#iptables", "-iptables"}, true, "Disable docker's addition of iptables rules") + flEnableIpForward = flag.Bool([]string{"#ip-forward", "-ip-forward"}, true, "Disable enabling of net.ipv4.ip_forward") flDefaultIp = flag.String([]string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports") flInterContainerComm = flag.Bool([]string{"#icc", "-icc"}, true, "Enable inter-container communication") flGraphDriver = flag.String([]string{"s", "-storage-driver"}, "", "Force the docker runtime to use a specific storage driver") @@ -88,6 +89,7 @@ func main() { job.SetenvBool("EnableCors", *flEnableCors) job.SetenvList("Dns", flDns.GetAll()) job.SetenvBool("EnableIptables", *flEnableIptables) + job.SetenvBool("EnableIpForward", *flEnableIpForward) job.Setenv("BridgeIface", *bridgeName) job.Setenv("BridgeIp", *bridgeIp) job.Setenv("DefaultIp", *flDefaultIp) diff --git a/network.go b/network.go index 85b0a588df..2396c6b320 100644 --- a/network.go +++ b/network.go @@ -9,6 +9,7 @@ import ( "github.com/dotcloud/docker/pkg/netlink" "github.com/dotcloud/docker/proxy" "github.com/dotcloud/docker/utils" + "io/ioutil" "log" "net" "strconv" @@ -499,6 +500,13 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { } } + if config.EnableIpForward { + // Enable IPv4 forwarding + if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte{'1', '\n'}, 0644); err != nil { + log.Printf("WARNING: unable to enable IPv4 forwarding: %s\n", err) + } + } + portMapper, err := newPortMapper(config) if err != nil { return nil, err From a1851a6d3e69d67bfc4e6bfdec85ba58293351b9 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 28 Jan 2014 09:56:51 +0100 Subject: [PATCH 267/364] btrfs: Add comment to Put() Document why we don't need to do anything in Put(). Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/btrfs/btrfs.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graphdriver/btrfs/btrfs.go b/graphdriver/btrfs/btrfs.go index 52ed66b174..e8dc6bd0e9 100644 --- a/graphdriver/btrfs/btrfs.go +++ b/graphdriver/btrfs/btrfs.go @@ -206,6 +206,8 @@ func (d *Driver) Get(id string) (string, error) { } func (d *Driver) Put(id string) { + // Get() creates no runtime resources (like e.g. mounts) + // so this doesn't need to do anything. } func (d *Driver) Exists(id string) bool { From 70c1781e073287a0b012ce94ea1b233fd6628dfa Mon Sep 17 00:00:00 2001 From: Peter Waller Date: Tue, 28 Jan 2014 14:28:13 +0000 Subject: [PATCH 268/364] Fix unclear error message when deleting container I was getting the following error: ``` $ bundles/0.7.6-dev/binary/docker-0.7.6-dev run -rm -v=/var/run:/foo base echo hi hi 2014/01/28 14:24:46 Error: container_delete: No such id: run ``` This commit makes the true origin of the error clearer. Issue #3806 is tracking the cause of the error. Docker-DCO-1.1-Signed-off-by: Peter Waller (github: pwaller) --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index b4add79771..152e2b279e 100644 --- a/server.go +++ b/server.go @@ -1909,7 +1909,7 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { continue } if err := srv.runtime.volumes.Delete(volumeId); err != nil { - job.Error(err) + job.Errorf("Error calling volumes.Delete(%q): %v", volumeId, err) return engine.StatusErr } } From 335bc39c9a5abfdbd07914ae1b8667a9d8590a6e Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 28 Jan 2014 16:17:51 +0100 Subject: [PATCH 269/364] execdriver: Make GetPidsForContainer() a driver call The current implementation is lxc specific. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- execdriver/chroot/driver.go | 4 ++++ execdriver/driver.go | 7 +++--- execdriver/lxc/driver.go | 41 +++++++++++++++++++++++++++++++++ pkg/cgroups/cgroups.go | 45 +------------------------------------ server.go | 3 +-- 5 files changed, 51 insertions(+), 49 deletions(-) diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index 4b2e02904e..1fe8a5418f 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -85,3 +85,7 @@ func (d *driver) Info(id string) execdriver.Info { func (d *driver) Name() string { return fmt.Sprintf("%s-%s", DriverName, Version) } + +func (d *driver) GetPidsForContainer(id string) ([]int, error) { + return nil, fmt.Errorf("Not supported") +} diff --git a/execdriver/driver.go b/execdriver/driver.go index 511e9e80c6..fd6e0e1c31 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -61,9 +61,10 @@ type Info interface { type Driver interface { Run(c *Command, startCallback StartCallback) (int, error) // Run executes the process and blocks until the process exits and returns the exit code Kill(c *Command, sig int) error - Restore(c *Command) error // Wait and try to re-attach on an out of process command - Name() string // Driver name - Info(id string) Info // "temporary" hack (until we move state from core to plugins) + Restore(c *Command) error // Wait and try to re-attach on an out of process command + Name() string // Driver name + Info(id string) Info // "temporary" hack (until we move state from core to plugins) + GetPidsForContainer(id string) ([]int, error) // Returns a list of pids for the given container. } // Network settings of the container diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 0e64dfd968..d35c72dbe9 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -3,12 +3,14 @@ package lxc import ( "fmt" "github.com/dotcloud/docker/execdriver" + "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/utils" "io/ioutil" "log" "os" "os/exec" "path" + "path/filepath" "strconv" "strings" "syscall" @@ -272,6 +274,45 @@ func (d *driver) Info(id string) execdriver.Info { } } +func (d *driver) GetPidsForContainer(id string) ([]int, error) { + pids := []int{} + + // memory is chosen randomly, any cgroup used by docker works + subsystem := "memory" + + cgroupRoot, err := cgroups.FindCgroupMountpoint(subsystem) + if err != nil { + return pids, err + } + + cgroupDir, err := cgroups.GetThisCgroupDir(subsystem) + if err != nil { + return pids, err + } + + filename := filepath.Join(cgroupRoot, cgroupDir, id, "tasks") + if _, err := os.Stat(filename); os.IsNotExist(err) { + // With more recent lxc versions use, cgroup will be in lxc/ + filename = filepath.Join(cgroupRoot, cgroupDir, "lxc", id, "tasks") + } + + output, err := ioutil.ReadFile(filename) + if err != nil { + return pids, err + } + for _, p := range strings.Split(string(output), "\n") { + if len(p) == 0 { + continue + } + pid, err := strconv.Atoi(p) + if err != nil { + return pids, fmt.Errorf("Invalid pid '%s': %s", p, err) + } + pids = append(pids, pid) + } + return pids, nil +} + func linkLxcStart(root string) error { sourcePath, err := exec.LookPath("lxc-start") if err != nil { diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index de7b079dc2..91ac3842ac 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -5,10 +5,7 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/mount" "io" - "io/ioutil" "os" - "path/filepath" - "strconv" "strings" ) @@ -33,7 +30,7 @@ func FindCgroupMountpoint(subsystem string) (string, error) { } // Returns the relative path to the cgroup docker is running in. -func getThisCgroupDir(subsystem string) (string, error) { +func GetThisCgroupDir(subsystem string) (string, error) { f, err := os.Open("/proc/self/cgroup") if err != nil { return "", err @@ -58,43 +55,3 @@ func parseCgroupFile(subsystem string, r io.Reader) (string, error) { } return "", fmt.Errorf("cgroup '%s' not found in /proc/self/cgroup", subsystem) } - -// Returns a list of pids for the given container. -func GetPidsForContainer(id string) ([]int, error) { - pids := []int{} - - // memory is chosen randomly, any cgroup used by docker works - subsystem := "memory" - - cgroupRoot, err := FindCgroupMountpoint(subsystem) - if err != nil { - return pids, err - } - - cgroupDir, err := getThisCgroupDir(subsystem) - if err != nil { - return pids, err - } - - filename := filepath.Join(cgroupRoot, cgroupDir, id, "tasks") - if _, err := os.Stat(filename); os.IsNotExist(err) { - // With more recent lxc versions use, cgroup will be in lxc/ - filename = filepath.Join(cgroupRoot, cgroupDir, "lxc", id, "tasks") - } - - output, err := ioutil.ReadFile(filename) - if err != nil { - return pids, err - } - for _, p := range strings.Split(string(output), "\n") { - if len(p) == 0 { - continue - } - pid, err := strconv.Atoi(p) - if err != nil { - return pids, fmt.Errorf("Invalid pid '%s': %s", p, err) - } - pids = append(pids, pid) - } - return pids, nil -} diff --git a/server.go b/server.go index 5fbde5865d..ebccf4c850 100644 --- a/server.go +++ b/server.go @@ -7,7 +7,6 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" @@ -980,7 +979,7 @@ func (srv *Server) ContainerTop(job *engine.Job) engine.Status { job.Errorf("Container %s is not running", name) return engine.StatusErr } - pids, err := cgroups.GetPidsForContainer(container.ID) + pids, err := srv.runtime.execDriver.GetPidsForContainer(container.ID) if err != nil { job.Error(err) return engine.StatusErr From 9ad70528b723810d98e77368458408e85eebdfee Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 28 Jan 2014 11:16:02 +0100 Subject: [PATCH 270/364] exexdriver: Make Command.GetExitCode an internal call This code only works for backends that directly spawn the child via the Command. It will not work for the libvirt backend. So we move this code into the individual backends that need it. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- execdriver/chroot/driver.go | 12 +++++++++++- execdriver/driver.go | 10 ---------- execdriver/lxc/driver.go | 11 ++++++++++- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/execdriver/chroot/driver.go b/execdriver/chroot/driver.go index 4b2e02904e..7848cfc4be 100644 --- a/execdriver/chroot/driver.go +++ b/execdriver/chroot/driver.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker/pkg/mount" "os" "os/exec" + "syscall" ) const ( @@ -67,7 +68,16 @@ func (d *driver) Run(c *execdriver.Command, startCallback execdriver.StartCallba } err = c.Wait() - return c.GetExitCode(), err + return getExitCode(c), err +} + +/// Return the exit code of the process +// if the process has not exited -1 will be returned +func getExitCode(c *execdriver.Command) int { + if c.ProcessState == nil { + return -1 + } + return c.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() } func (d *driver) Kill(p *execdriver.Command, sig int) error { diff --git a/execdriver/driver.go b/execdriver/driver.go index 511e9e80c6..9b352c379f 100644 --- a/execdriver/driver.go +++ b/execdriver/driver.go @@ -3,7 +3,6 @@ package execdriver import ( "errors" "os/exec" - "syscall" ) var ( @@ -109,12 +108,3 @@ func (c *Command) Pid() int { } return c.Process.Pid } - -// Return the exit code of the process -// if the process has not exited -1 will be returned -func (c *Command) GetExitCode() int { - if c.ProcessState == nil { - return -1 - } - return c.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() -} diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 0e64dfd968..0d3454f2b7 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -169,7 +169,16 @@ func (d *driver) Run(c *execdriver.Command, startCallback execdriver.StartCallba <-waitLock - return c.GetExitCode(), waitErr + return getExitCode(c), waitErr +} + +/// Return the exit code of the process +// if the process has not exited -1 will be returned +func getExitCode(c *execdriver.Command) int { + if c.ProcessState == nil { + return -1 + } + return c.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() } func (d *driver) Kill(c *execdriver.Command, sig int) error { From db250f709ad5bcee313710d34e0b6ef02abdc326 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Fri, 24 Jan 2014 22:22:53 -0600 Subject: [PATCH 271/364] network: add publicly mapped ports to FORWARD table Allow publicly mapped ports to be made public beyond the host. This is needed for distros like Fedora and RHEL which have a reject all rule at the end of their FORWARD table. Docker-DCO-1.1-Signed-off-by: Josh Poimboeuf (github: jpoimboe) --- pkg/iptables/iptables.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index 0438bcbd88..2df93657ac 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -73,6 +73,23 @@ func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr str } else if len(output) != 0 { return fmt.Errorf("Error iptables forward: %s", output) } + + fAction := action + if fAction == Add { + fAction = "-I" + } + if output, err := Raw(string(fAction), "FORWARD", + "!", "-i", c.Bridge, + "-o", c.Bridge, + "-p", proto, + "-d", daddr, + "--dport", strconv.Itoa(port), + "-j", "ACCEPT"); err != nil { + return err + } else if len(output) != 0 { + return fmt.Errorf("Error iptables forward: %s", output) + } + return nil } From ef6c0d53410c0be6f33d049e7998b54804497350 Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Tue, 28 Jan 2014 13:24:38 -0600 Subject: [PATCH 272/364] remove ip_forward warning Now that docker sets /proc/sys/net/ipv4/ip_forward by default (unless the user manually specifies "-ip-forward=false"), there's no need to warn if its disabled. Docker-DCO-1.1-Signed-off-by: Josh Poimboeuf (github: jpoimboe) --- pkg/sysinfo/sysinfo.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index b3463f8cfe..27af37bb89 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -36,12 +36,6 @@ func New(quiet bool) *SysInfo { } } - content, err3 := ioutil.ReadFile("/proc/sys/net/ipv4/ip_forward") - sysInfo.IPv4ForwardingDisabled = err3 != nil || len(content) == 0 || content[0] != '1' - if sysInfo.IPv4ForwardingDisabled && !quiet { - log.Printf("WARNING: IPv4 forwarding is disabled.") - } - // Check if AppArmor seems to be enabled on this system. if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) { sysInfo.AppArmor = false From aeb2f479fa1c063867475a610ddbfe4f8883dfda Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Tue, 28 Jan 2014 13:29:27 -0600 Subject: [PATCH 273/364] docs: remove ip_forward enabling step Now that docker sets /proc/sys/net/ipv4/ip_forward by default, remove the step to enable it in the installation docs. Docker-DCO-1.1-Signed-off-by: Josh Poimboeuf (github: jpoimboe) --- docs/sources/installation/archlinux.rst | 18 ------------------ docs/sources/installation/frugalware.rst | 18 ------------------ docs/sources/installation/gentoolinux.rst | 16 ---------------- docs/sources/installation/google.rst | 13 +++---------- 4 files changed, 3 insertions(+), 62 deletions(-) diff --git a/docs/sources/installation/archlinux.rst b/docs/sources/installation/archlinux.rst index 3859317c44..2d823bfd46 100644 --- a/docs/sources/installation/archlinux.rst +++ b/docs/sources/installation/archlinux.rst @@ -71,21 +71,3 @@ To start on system boot: :: sudo systemctl enable docker - -Network Configuration ---------------------- - -IPv4 packet forwarding is disabled by default on Arch, so internet access from inside -the container may not work. - -To enable the forwarding, run as root on the host system: - -:: - - sysctl net.ipv4.ip_forward=1 - -And, to make it persistent across reboots, enable it on the host's **/etc/sysctl.d/docker.conf**: - -:: - - net.ipv4.ip_forward=1 diff --git a/docs/sources/installation/frugalware.rst b/docs/sources/installation/frugalware.rst index cda6c4bfc4..de2b92ae10 100644 --- a/docs/sources/installation/frugalware.rst +++ b/docs/sources/installation/frugalware.rst @@ -60,21 +60,3 @@ To start on system boot: :: sudo systemctl enable lxc-docker - -Network Configuration ---------------------- - -IPv4 packet forwarding is disabled by default on FrugalWare, so Internet access from inside -the container may not work. - -To enable packet forwarding, run the following command as the ``root`` user on the host system: - -:: - - sysctl net.ipv4.ip_forward=1 - -And, to make it persistent across reboots, add the following to a file named **/etc/sysctl.d/docker.conf**: - -:: - - net.ipv4.ip_forward=1 diff --git a/docs/sources/installation/gentoolinux.rst b/docs/sources/installation/gentoolinux.rst index 0e8809f7b5..421af0a1e7 100644 --- a/docs/sources/installation/gentoolinux.rst +++ b/docs/sources/installation/gentoolinux.rst @@ -82,19 +82,3 @@ To start on system boot: .. code-block:: bash sudo systemctl enable docker.service - -Network Configuration -^^^^^^^^^^^^^^^^^^^^^ - -IPv4 packet forwarding is disabled by default, so internet access from inside -the container will not work unless ``net.ipv4.ip_forward`` is enabled: - -.. code-block:: bash - - sudo sysctl -w net.ipv4.ip_forward=1 - -Or, to enable it more permanently: - -.. code-block:: bash - - echo net.ipv4.ip_forward = 1 | sudo tee /etc/sysctl.d/docker.conf diff --git a/docs/sources/installation/google.rst b/docs/sources/installation/google.rst index 62af581fea..88118778a2 100644 --- a/docs/sources/installation/google.rst +++ b/docs/sources/installation/google.rst @@ -43,21 +43,14 @@ $ gcutil ssh docker-playground docker-playground:~$ -5. Enable IP forwarding: - -.. code-block:: bash - - docker-playground:~$ echo net.ipv4.ip_forward=1 | sudo tee /etc/sysctl.d/99-docker.conf - docker-playground:~$ sudo sysctl --system - -6. Install the latest Docker release and configure it to start when the instance boots: +5. Install the latest Docker release and configure it to start when the instance boots: .. code-block:: bash docker-playground:~$ curl get.docker.io | bash docker-playground:~$ sudo update-rc.d docker defaults -7. If running in zones: ``us-central1-a``, ``europe-west1-1``, and ``europe-west1-b``, the docker daemon must be started with the ``-mtu`` flag. Without the flag, you may experience intermittent network pauses. +6. If running in zones: ``us-central1-a``, ``europe-west1-1``, and ``europe-west1-b``, the docker daemon must be started with the ``-mtu`` flag. Without the flag, you may experience intermittent network pauses. `See this issue `_ for more details. .. code-block:: bash @@ -65,7 +58,7 @@ docker-playground:~$ echo 'DOCKER_OPTS="$DOCKER_OPTS -mtu 1460"' | sudo tee -a /etc/default/docker docker-playground:~$ sudo service docker restart -8. Start a new container: +7. Start a new container: .. code-block:: bash From 99756ef11fc2a3ae821bb412607e7e9b322f278a Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 28 Jan 2014 15:42:46 -0800 Subject: [PATCH 274/364] Initial move of port mapper code into sub pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- network.go | 166 +++++------------------------ networkdriver/portmapper/mapper.go | 143 +++++++++++++++++++++++++ 2 files changed, 170 insertions(+), 139 deletions(-) create mode 100644 networkdriver/portmapper/mapper.go diff --git a/network.go b/network.go index 250f7b594f..05ff005ee6 100644 --- a/network.go +++ b/network.go @@ -5,9 +5,9 @@ import ( "github.com/dotcloud/docker/networkdriver" "github.com/dotcloud/docker/networkdriver/ipallocator" "github.com/dotcloud/docker/networkdriver/portallocator" + "github.com/dotcloud/docker/networkdriver/portmapper" "github.com/dotcloud/docker/pkg/iptables" "github.com/dotcloud/docker/pkg/netlink" - "github.com/dotcloud/docker/proxy" "github.com/dotcloud/docker/utils" "io/ioutil" "log" @@ -159,129 +159,6 @@ func getIfaceAddr(name string) (net.Addr, error) { return addrs4[0], nil } -// Port mapper takes care of mapping external ports to containers by setting -// up iptables rules. -// It keeps track of all mappings and is able to unmap at will -type PortMapper struct { - tcpMapping map[string]*net.TCPAddr - tcpProxies map[string]proxy.Proxy - udpMapping map[string]*net.UDPAddr - udpProxies map[string]proxy.Proxy - - iptables *iptables.Chain - defaultIp net.IP - proxyFactoryFunc func(net.Addr, net.Addr) (proxy.Proxy, error) -} - -func (mapper *PortMapper) Map(ip net.IP, port int, backendAddr net.Addr) error { - - if _, isTCP := backendAddr.(*net.TCPAddr); isTCP { - mapKey := (&net.TCPAddr{Port: port, IP: ip}).String() - if _, exists := mapper.tcpProxies[mapKey]; exists { - return fmt.Errorf("TCP Port %s is already in use", mapKey) - } - backendPort := backendAddr.(*net.TCPAddr).Port - backendIP := backendAddr.(*net.TCPAddr).IP - if mapper.iptables != nil { - if err := mapper.iptables.Forward(iptables.Add, ip, port, "tcp", backendIP.String(), backendPort); err != nil { - return err - } - } - mapper.tcpMapping[mapKey] = backendAddr.(*net.TCPAddr) - proxy, err := mapper.proxyFactoryFunc(&net.TCPAddr{IP: ip, Port: port}, backendAddr) - if err != nil { - mapper.Unmap(ip, port, "tcp") - return err - } - mapper.tcpProxies[mapKey] = proxy - go proxy.Run() - } else { - mapKey := (&net.UDPAddr{Port: port, IP: ip}).String() - if _, exists := mapper.udpProxies[mapKey]; exists { - return fmt.Errorf("UDP: Port %s is already in use", mapKey) - } - backendPort := backendAddr.(*net.UDPAddr).Port - backendIP := backendAddr.(*net.UDPAddr).IP - if mapper.iptables != nil { - if err := mapper.iptables.Forward(iptables.Add, ip, port, "udp", backendIP.String(), backendPort); err != nil { - return err - } - } - mapper.udpMapping[mapKey] = backendAddr.(*net.UDPAddr) - proxy, err := mapper.proxyFactoryFunc(&net.UDPAddr{IP: ip, Port: port}, backendAddr) - if err != nil { - mapper.Unmap(ip, port, "udp") - return err - } - mapper.udpProxies[mapKey] = proxy - go proxy.Run() - } - return nil -} - -func (mapper *PortMapper) Unmap(ip net.IP, port int, proto string) error { - if proto == "tcp" { - mapKey := (&net.TCPAddr{Port: port, IP: ip}).String() - backendAddr, ok := mapper.tcpMapping[mapKey] - if !ok { - return fmt.Errorf("Port tcp/%s is not mapped", mapKey) - } - if proxy, exists := mapper.tcpProxies[mapKey]; exists { - proxy.Close() - delete(mapper.tcpProxies, mapKey) - } - if mapper.iptables != nil { - if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { - return err - } - } - delete(mapper.tcpMapping, mapKey) - } else { - mapKey := (&net.UDPAddr{Port: port, IP: ip}).String() - backendAddr, ok := mapper.udpMapping[mapKey] - if !ok { - return fmt.Errorf("Port udp/%s is not mapped", mapKey) - } - if proxy, exists := mapper.udpProxies[mapKey]; exists { - proxy.Close() - delete(mapper.udpProxies, mapKey) - } - if mapper.iptables != nil { - if err := mapper.iptables.Forward(iptables.Delete, ip, port, proto, backendAddr.IP.String(), backendAddr.Port); err != nil { - return err - } - } - delete(mapper.udpMapping, mapKey) - } - return nil -} - -func newPortMapper(config *DaemonConfig) (*PortMapper, error) { - // We can always try removing the iptables - if err := iptables.RemoveExistingChain("DOCKER"); err != nil { - return nil, err - } - var chain *iptables.Chain - if config.EnableIptables { - var err error - chain, err = iptables.NewChain("DOCKER", config.BridgeIface) - if err != nil { - return nil, fmt.Errorf("Failed to create DOCKER chain: %s", err) - } - } - - mapper := &PortMapper{ - tcpMapping: make(map[string]*net.TCPAddr), - tcpProxies: make(map[string]proxy.Proxy), - udpMapping: make(map[string]*net.UDPAddr), - udpProxies: make(map[string]proxy.Proxy), - iptables: chain, - defaultIp: config.DefaultIp, - proxyFactoryFunc: proxy.NewProxy, - } - return mapper, nil -} - // Network interface represents the networking stack of a container type NetworkInterface struct { IPNet net.IPNet @@ -299,7 +176,7 @@ func (iface *NetworkInterface) AllocatePort(port Port, binding PortBinding) (*Na return nil, fmt.Errorf("Trying to allocate port for interface %v, which is disabled", iface) // FIXME } - ip := iface.manager.portMapper.defaultIp + ip := iface.manager.defaultBindingIP if binding.HostIp != "" { ip = net.ParseIP(binding.HostIp) @@ -331,7 +208,7 @@ func (iface *NetworkInterface) AllocatePort(port Port, binding PortBinding) (*Na backend = &net.UDPAddr{IP: iface.IPNet.IP, Port: containerPort} } - if err := iface.manager.portMapper.Map(ip, extPort, backend); err != nil { + if err := portmapper.Map(backend, ip, extPort); err != nil { portallocator.ReleasePort(ip, nat.Port.Proto(), extPort) return nil, err } @@ -365,7 +242,15 @@ func (iface *NetworkInterface) Release() { } ip := net.ParseIP(nat.Binding.HostIp) utils.Debugf("Unmaping %s/%s:%s", nat.Port.Proto, ip.String(), nat.Binding.HostPort) - if err := iface.manager.portMapper.Unmap(ip, hostPort, nat.Port.Proto()); err != nil { + + var host net.Addr + if nat.Port.Proto() == "tcp" { + host = &net.TCPAddr{IP: ip, Port: hostPort} + } else { + host = &net.UDPAddr{IP: ip, Port: hostPort} + } + + if err := portmapper.Unmap(host); err != nil { log.Printf("Unable to unmap port %s: %s", nat, err) } @@ -382,12 +267,10 @@ func (iface *NetworkInterface) Release() { // Network Manager manages a set of network interfaces // Only *one* manager per host machine should be used type NetworkManager struct { - bridgeIface string - bridgeNetwork *net.IPNet - - portMapper *PortMapper - - disabled bool + bridgeIface string + bridgeNetwork *net.IPNet + defaultBindingIP net.IP + disabled bool } // Allocate a network interface @@ -508,16 +391,21 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { } } - portMapper, err := newPortMapper(config) - if err != nil { + // We can always try removing the iptables + if err := portmapper.RemoveIpTablesChain("DOCKER"); err != nil { return nil, err } - manager := &NetworkManager{ - bridgeIface: config.BridgeIface, - bridgeNetwork: network, - portMapper: portMapper, + if config.EnableIptables { + if err := portmapper.RegisterIpTablesChain("DOCKER", config.BridgeIface); err != nil { + return nil, err + } } + manager := &NetworkManager{ + bridgeIface: config.BridgeIface, + bridgeNetwork: network, + defaultBindingIP: config.DefaultIp, + } return manager, nil } diff --git a/networkdriver/portmapper/mapper.go b/networkdriver/portmapper/mapper.go new file mode 100644 index 0000000000..74e2728ab6 --- /dev/null +++ b/networkdriver/portmapper/mapper.go @@ -0,0 +1,143 @@ +package portmapper + +import ( + "errors" + "fmt" + "github.com/dotcloud/docker/pkg/iptables" + "github.com/dotcloud/docker/proxy" + "net" + "sync" +) + +type mapping struct { + proto string + userlandProxy proxy.Proxy + host net.Addr + container net.Addr +} + +var ( + chain *iptables.Chain + lock sync.Mutex + + // udp:ip:port + currentMappings = make(map[string]*mapping) +) + +var ( + ErrUnknownBackendAddressType = errors.New("unknown container address type not supported") + ErrPortMappedForIP = errors.New("port is already mapped to ip") + ErrPortNotMapped = errors.New("port is not mapped") +) + +func RegisterIpTablesChain(name, bridge string) error { + c, err := iptables.NewChain(name, bridge) + if err != nil { + return fmt.Errorf("failed to create %s chain: %s", name, err) + } + chain = c + return nil +} + +func RemoveIpTablesChain(name string) error { + if err := iptables.RemoveExistingChain(name); err != nil { + return err + } + chain = nil + return nil +} + +func Map(container net.Addr, hostIP net.IP, hostPort int) error { + lock.Lock() + defer lock.Unlock() + + var m *mapping + switch container.(type) { + case *net.TCPAddr: + m = &mapping{ + proto: "tcp", + host: &net.TCPAddr{IP: hostIP, Port: hostPort}, + container: container, + } + case *net.UDPAddr: + m = &mapping{ + proto: "udp", + host: &net.UDPAddr{IP: hostIP, Port: hostPort}, + container: container, + } + default: + return ErrUnknownBackendAddressType + } + + key := getKey(m.host) + if _, exists := currentMappings[key]; exists { + return ErrPortMappedForIP + } + + containerIP, containerPort := getIPAndPort(m.container) + if err := forward(iptables.Add, m.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { + return err + } + + p, err := proxy.NewProxy(m.host, m.container) + if err != nil { + // need to undo the iptables rules before we reutrn + forward(iptables.Delete, m.proto, hostIP, hostPort, containerIP.String(), containerPort) + return err + } + + m.userlandProxy = p + currentMappings[key] = m + + go p.Run() + + return nil +} + +func Unmap(host net.Addr) error { + lock.Lock() + defer lock.Unlock() + + key := getKey(host) + data, exists := currentMappings[key] + if !exists { + return ErrPortNotMapped + } + + data.userlandProxy.Close() + delete(currentMappings, key) + + containerIP, containerPort := getIPAndPort(data.container) + hostIP, hostPort := getIPAndPort(data.host) + if err := forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { + return err + } + return nil +} + +func getKey(a net.Addr) string { + switch t := a.(type) { + case *net.TCPAddr: + return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp") + case *net.UDPAddr: + return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp") + } + return "" +} + +func getIPAndPort(a net.Addr) (net.IP, int) { + switch t := a.(type) { + case *net.TCPAddr: + return t.IP, t.Port + case *net.UDPAddr: + return t.IP, t.Port + } + return nil, 0 +} + +func forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error { + if chain == nil { + return nil + } + return chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort) +} From d41844ed2d946fc963f9977991233b5d9daf86fd Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 29 Jan 2014 00:20:51 +0000 Subject: [PATCH 275/364] remove enableCors from the config and move it as a job arg Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 14 +++++++------- config.go | 2 -- docker/docker.go | 2 +- server.go | 2 +- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/api.go b/api.go index 85c06bb26b..24b6b7e4bf 100644 --- a/api.go +++ b/api.go @@ -931,7 +931,7 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request) { w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") } -func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc) http.HandlerFunc { +func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, enableCors bool) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // log the request utils.Debugf("Calling %s %s", localMethod, localRoute) @@ -950,7 +950,7 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s if err != nil { version = APIVERSION } - if srv.runtime.config.EnableCors { + if enableCors { writeCorsHeaders(w, r) } @@ -992,7 +992,7 @@ func AttachProfiler(router *mux.Router) { router.HandleFunc("/debug/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP) } -func createRouter(srv *Server, logging bool) (*mux.Router, error) { +func createRouter(srv *Server, logging, enableCors bool) (*mux.Router, error) { r := mux.NewRouter() if os.Getenv("DEBUG") != "" { AttachProfiler(r) @@ -1053,7 +1053,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { localMethod := method // build the handler function - f := makeHttpHandler(srv, logging, localMethod, localRoute, localFct) + f := makeHttpHandler(srv, logging, localMethod, localRoute, localFct, enableCors) // add the new route if localRoute == "" { @@ -1072,7 +1072,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { // FIXME: refactor this to be part of Server and not require re-creating a new // router each time. This requires first moving ListenAndServe into Server. func ServeRequest(srv *Server, apiversion float64, w http.ResponseWriter, req *http.Request) error { - router, err := createRouter(srv, false) + router, err := createRouter(srv, false, true) if err != nil { return err } @@ -1114,8 +1114,8 @@ func ServeFd(addr string, handle http.Handler) error { // ListenAndServe sets up the required http.Server and gets it listening for // each addr passed in and does protocol specific checking. -func ListenAndServe(proto, addr string, srv *Server, logging bool) error { - r, err := createRouter(srv, logging) +func ListenAndServe(proto, addr string, srv *Server, logging, enableCors bool) error { + r, err := createRouter(srv, logging, enableCors) if err != nil { return err } diff --git a/config.go b/config.go index a948744f0e..aad5d50fc0 100644 --- a/config.go +++ b/config.go @@ -10,7 +10,6 @@ type DaemonConfig struct { Pidfile string Root string AutoRestart bool - EnableCors bool Dns []string EnableIptables bool EnableIpForward bool @@ -29,7 +28,6 @@ func ConfigFromJob(job *engine.Job) *DaemonConfig { config.Pidfile = job.Getenv("Pidfile") config.Root = job.Getenv("Root") config.AutoRestart = job.GetenvBool("AutoRestart") - config.EnableCors = job.GetenvBool("EnableCors") if dns := job.GetenvList("Dns"); dns != nil { config.Dns = dns } diff --git a/docker/docker.go b/docker/docker.go index 6fb88fc7c6..e6c8076f36 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -86,7 +86,6 @@ func main() { job.Setenv("Pidfile", *pidfile) job.Setenv("Root", *flRoot) job.SetenvBool("AutoRestart", *flAutoRestart) - job.SetenvBool("EnableCors", *flEnableCors) job.SetenvList("Dns", flDns.GetAll()) job.SetenvBool("EnableIptables", *flEnableIptables) job.SetenvBool("EnableIpForward", *flEnableIpForward) @@ -102,6 +101,7 @@ func main() { // Serve api job = eng.Job("serveapi", flHosts.GetAll()...) job.SetenvBool("Logging", true) + job.SetenvBool("EnableCors", *flEnableCors) if err := job.Run(); err != nil { log.Fatal(err) } diff --git a/server.go b/server.go index 383284f259..87bab9ec5c 100644 --- a/server.go +++ b/server.go @@ -125,7 +125,7 @@ func (srv *Server) ListenAndServe(job *engine.Job) engine.Status { protoAddrParts := strings.SplitN(protoAddr, "://", 2) go func() { log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1]) - chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, job.GetenvBool("Logging")) + chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, job.GetenvBool("Logging"), job.GetenvBool("EnableCors")) }() } From b3b12f00593d96c6d0e93f9e333ee368278e871d Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 28 Jan 2014 16:28:32 -0800 Subject: [PATCH 276/364] Move port mapper tests out of core and into portmapper Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- network.go | 6 +- network_test.go | 72 ---------------- networkdriver/portmapper/mapper.go | 18 +--- networkdriver/portmapper/mapper_test.go | 107 ++++++++++++++++++++++++ proxy/stub_proxy.go | 22 +++++ 5 files changed, 136 insertions(+), 89 deletions(-) delete mode 100644 network_test.go create mode 100644 networkdriver/portmapper/mapper_test.go create mode 100644 proxy/stub_proxy.go diff --git a/network.go b/network.go index 05ff005ee6..c72ea12055 100644 --- a/network.go +++ b/network.go @@ -392,14 +392,16 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { } // We can always try removing the iptables - if err := portmapper.RemoveIpTablesChain("DOCKER"); err != nil { + if err := iptables.RemoveExistingChain("DOCKER"); err != nil { return nil, err } if config.EnableIptables { - if err := portmapper.RegisterIpTablesChain("DOCKER", config.BridgeIface); err != nil { + chain, err := iptables.NewChain("DOCKER", config.BridgeIface) + if err != nil { return nil, err } + portmapper.SetIptablesChain(chain) } manager := &NetworkManager{ diff --git a/network_test.go b/network_test.go deleted file mode 100644 index 6cdf50ab6e..0000000000 --- a/network_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package docker - -import ( - "github.com/dotcloud/docker/pkg/iptables" - "github.com/dotcloud/docker/proxy" - "net" - "testing" -) - -type StubProxy struct { - frontendAddr *net.Addr - backendAddr *net.Addr -} - -func (proxy *StubProxy) Run() {} -func (proxy *StubProxy) Close() {} -func (proxy *StubProxy) FrontendAddr() net.Addr { return *proxy.frontendAddr } -func (proxy *StubProxy) BackendAddr() net.Addr { return *proxy.backendAddr } - -func NewStubProxy(frontendAddr, backendAddr net.Addr) (proxy.Proxy, error) { - return &StubProxy{ - frontendAddr: &frontendAddr, - backendAddr: &backendAddr, - }, nil -} - -func TestPortMapper(t *testing.T) { - // FIXME: is this iptables chain still used anywhere? - var chain *iptables.Chain - mapper := &PortMapper{ - tcpMapping: make(map[string]*net.TCPAddr), - tcpProxies: make(map[string]proxy.Proxy), - udpMapping: make(map[string]*net.UDPAddr), - udpProxies: make(map[string]proxy.Proxy), - iptables: chain, - defaultIp: net.IP("0.0.0.0"), - proxyFactoryFunc: NewStubProxy, - } - - dstIp1 := net.ParseIP("192.168.0.1") - dstIp2 := net.ParseIP("192.168.0.2") - srcAddr1 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")} - srcAddr2 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.2")} - - if err := mapper.Map(dstIp1, 80, srcAddr1); err != nil { - t.Fatalf("Failed to allocate port: %s", err) - } - - if mapper.Map(dstIp1, 80, srcAddr1) == nil { - t.Fatalf("Port is in use - mapping should have failed") - } - - if mapper.Map(dstIp1, 80, srcAddr2) == nil { - t.Fatalf("Port is in use - mapping should have failed") - } - - if err := mapper.Map(dstIp2, 80, srcAddr2); err != nil { - t.Fatalf("Failed to allocate port: %s", err) - } - - if mapper.Unmap(dstIp1, 80, "tcp") != nil { - t.Fatalf("Failed to release port") - } - - if mapper.Unmap(dstIp2, 80, "tcp") != nil { - t.Fatalf("Failed to release port") - } - - if mapper.Unmap(dstIp2, 80, "tcp") == nil { - t.Fatalf("Port already released, but no error reported") - } -} diff --git a/networkdriver/portmapper/mapper.go b/networkdriver/portmapper/mapper.go index 74e2728ab6..f052c48143 100644 --- a/networkdriver/portmapper/mapper.go +++ b/networkdriver/portmapper/mapper.go @@ -22,6 +22,7 @@ var ( // udp:ip:port currentMappings = make(map[string]*mapping) + newProxy = proxy.NewProxy ) var ( @@ -30,21 +31,8 @@ var ( ErrPortNotMapped = errors.New("port is not mapped") ) -func RegisterIpTablesChain(name, bridge string) error { - c, err := iptables.NewChain(name, bridge) - if err != nil { - return fmt.Errorf("failed to create %s chain: %s", name, err) - } +func SetIptablesChain(c *iptables.Chain) { chain = c - return nil -} - -func RemoveIpTablesChain(name string) error { - if err := iptables.RemoveExistingChain(name); err != nil { - return err - } - chain = nil - return nil } func Map(container net.Addr, hostIP net.IP, hostPort int) error { @@ -79,7 +67,7 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) error { return err } - p, err := proxy.NewProxy(m.host, m.container) + p, err := newProxy(m.host, m.container) if err != nil { // need to undo the iptables rules before we reutrn forward(iptables.Delete, m.proto, hostIP, hostPort, containerIP.String(), containerPort) diff --git a/networkdriver/portmapper/mapper_test.go b/networkdriver/portmapper/mapper_test.go new file mode 100644 index 0000000000..05718063e3 --- /dev/null +++ b/networkdriver/portmapper/mapper_test.go @@ -0,0 +1,107 @@ +package portmapper + +import ( + "github.com/dotcloud/docker/pkg/iptables" + "github.com/dotcloud/docker/proxy" + "net" + "testing" +) + +func init() { + // override this func to mock out the proxy server + newProxy = proxy.NewStubProxy +} + +func reset() { + chain = nil + currentMappings = make(map[string]*mapping) +} + +func TestSetIptablesChain(t *testing.T) { + defer reset() + + c := &iptables.Chain{ + Name: "TEST", + Bridge: "192.168.1.1", + } + + if chain != nil { + t.Fatal("chain should be nil at init") + } + + SetIptablesChain(c) + if chain == nil { + t.Fatal("chain should not be nil after set") + } +} + +func TestMapPorts(t *testing.T) { + dstIp1 := net.ParseIP("192.168.0.1") + dstIp2 := net.ParseIP("192.168.0.2") + dstAddr1 := &net.TCPAddr{IP: dstIp1, Port: 80} + dstAddr2 := &net.TCPAddr{IP: dstIp2, Port: 80} + + srcAddr1 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")} + srcAddr2 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.2")} + + if err := Map(srcAddr1, dstIp1, 80); err != nil { + t.Fatalf("Failed to allocate port: %s", err) + } + + if Map(srcAddr1, dstIp1, 80) == nil { + t.Fatalf("Port is in use - mapping should have failed") + } + + if Map(srcAddr2, dstIp1, 80) == nil { + t.Fatalf("Port is in use - mapping should have failed") + } + + if err := Map(srcAddr2, dstIp2, 80); err != nil { + t.Fatalf("Failed to allocate port: %s", err) + } + + if Unmap(dstAddr1) != nil { + t.Fatalf("Failed to release port") + } + + if Unmap(dstAddr2) != nil { + t.Fatalf("Failed to release port") + } + + if Unmap(dstAddr2) == nil { + t.Fatalf("Port already released, but no error reported") + } +} + +func TestGetUDPKey(t *testing.T) { + addr := &net.UDPAddr{IP: net.ParseIP("192.168.1.5"), Port: 53} + + key := getKey(addr) + + if expected := "192.168.1.5:53/udp"; key != expected { + t.Fatalf("expected key %s got %s", expected, key) + } +} + +func TestGetTCPKey(t *testing.T) { + addr := &net.TCPAddr{IP: net.ParseIP("192.168.1.5"), Port: 80} + + key := getKey(addr) + + if expected := "192.168.1.5:80/tcp"; key != expected { + t.Fatalf("expected key %s got %s", expected, key) + } +} + +func TestGetUDPIPAndPort(t *testing.T) { + addr := &net.UDPAddr{IP: net.ParseIP("192.168.1.5"), Port: 53} + + ip, port := getIPAndPort(addr) + if expected := "192.168.1.5"; ip.String() != expected { + t.Fatalf("expected ip %s got %s", expected, ip) + } + + if ep := 53; port != ep { + t.Fatalf("expected port %d got %d", ep, port) + } +} diff --git a/proxy/stub_proxy.go b/proxy/stub_proxy.go new file mode 100644 index 0000000000..7684427058 --- /dev/null +++ b/proxy/stub_proxy.go @@ -0,0 +1,22 @@ +package proxy + +import ( + "net" +) + +type StubProxy struct { + frontendAddr net.Addr + backendAddr net.Addr +} + +func (p *StubProxy) Run() {} +func (p *StubProxy) Close() {} +func (p *StubProxy) FrontendAddr() net.Addr { return p.frontendAddr } +func (p *StubProxy) BackendAddr() net.Addr { return p.backendAddr } + +func NewStubProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) { + return &StubProxy{ + frontendAddr: frontendAddr, + backendAddr: backendAddr, + }, nil +} From abd64a2b00a57dda2f24326fc49b7ca10e950dff Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 29 Jan 2014 01:17:55 +0000 Subject: [PATCH 277/364] move warnings to the job Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 22 ---------------------- server.go | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/api.go b/api.go index 24b6b7e4bf..0a7f7abea7 100644 --- a/api.go +++ b/api.go @@ -560,14 +560,6 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r if err := job.DecodeEnv(r.Body); err != nil { return err } - resolvConf, err := utils.GetResolvConf() - if err != nil { - return err - } - if !job.GetenvBool("NetworkDisabled") && len(job.Getenv("Dns")) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { - out.Warnings = append(out.Warnings, fmt.Sprintf("Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns)) - job.SetenvList("Dns", defaultDns) - } // Read container ID from the first line of stdout job.Stdout.AddString(&out.ID) // Read warnings from stderr @@ -581,20 +573,6 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r for scanner.Scan() { out.Warnings = append(out.Warnings, scanner.Text()) } - if job.GetenvInt("Memory") > 0 && !srv.runtime.sysInfo.MemoryLimit { - log.Println("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.") - out.Warnings = append(out.Warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.") - } - if job.GetenvInt("Memory") > 0 && !srv.runtime.sysInfo.SwapLimit { - log.Println("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.") - out.Warnings = append(out.Warnings, "Your kernel does not support memory swap capabilities. Limitation discarded.") - } - - if !job.GetenvBool("NetworkDisabled") && srv.runtime.sysInfo.IPv4ForwardingDisabled { - log.Println("Warning: IPv4 forwarding is disabled.") - out.Warnings = append(out.Warnings, "IPv4 forwarding is disabled.") - } - return writeJSON(w, http.StatusCreated, out) } diff --git a/server.go b/server.go index 87bab9ec5c..53cd4acc25 100644 --- a/server.go +++ b/server.go @@ -1753,11 +1753,23 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { return engine.StatusErr } if config.Memory > 0 && !srv.runtime.sysInfo.MemoryLimit { + job.Errorf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") config.Memory = 0 } if config.Memory > 0 && !srv.runtime.sysInfo.SwapLimit { + job.Errorf("WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") config.MemorySwap = -1 } + resolvConf, err := utils.GetResolvConf() + if err != nil { + job.Error(err) + return engine.StatusErr + } + if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { + job.Errorf("WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v\n", defaultDns) + config.Dns = defaultDns + } + container, buildWarnings, err := srv.runtime.Create(&config, name) if err != nil { if srv.runtime.graph.IsNotExist(err) { @@ -1771,6 +1783,9 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { job.Error(err) return engine.StatusErr } + if !container.Config.NetworkDisabled && srv.runtime.sysInfo.IPv4ForwardingDisabled { + job.Errorf("WARNING: IPv4 forwarding is disabled.\n") + } srv.LogEvent("create", container.ID, srv.runtime.repositories.ImageName(container.Image)) // FIXME: this is necessary because runtime.Create might return a nil container // with a non-nil error. This should not happen! Once it's fixed we From f3a032f27b80e8194cb7f14df848f063a56c5f26 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Tue, 28 Jan 2014 17:32:05 -0800 Subject: [PATCH 278/364] Address feedback from @jamtur01. Docker-DCO-1.1-Signed-off-by: Andy Rothfusz (github: metalivedev) --- docs/sources/articles/runmetrics.rst | 26 ++- docs/sources/reference/run.rst | 260 +++++++++++++++++---------- 2 files changed, 173 insertions(+), 113 deletions(-) diff --git a/docs/sources/articles/runmetrics.rst b/docs/sources/articles/runmetrics.rst index f7406bc5ed..afb7f82e39 100644 --- a/docs/sources/articles/runmetrics.rst +++ b/docs/sources/articles/runmetrics.rst @@ -37,8 +37,8 @@ To figure out where your control groups are mounted, you can run: .. _run_findpid: -Ennumerating Cgroups --------------------- +Enumerating Cgroups +------------------- You can look into ``/proc/cgroups`` to see the different control group subsystems known to the system, the hierarchy they belong to, and how @@ -71,7 +71,7 @@ container, take a look at ``/sys/fs/cgroup/memory/lxc//``. Metrics from Cgroups: Memory, CPU, Block IO ------------------------------------------- -For each subsystem (memory, cpu, and block i/o), you will find one or +For each subsystem (memory, CPU, and block I/O), you will find one or more pseudo-files containing statistics. Memory Metrics: ``memory.stat`` @@ -79,7 +79,7 @@ Memory Metrics: ``memory.stat`` Memory metrics are found in the "memory" cgroup. Note that the memory control group adds a little overhead, because it does very -fine-grained accounting of the memory usage on your system. Therefore, +fine-grained accounting of the memory usage on your host. Therefore, many distros chose to not enable it by default. Generally, to enable it, all you have to do is to add some kernel command-line parameters: ``cgroup_enable=memory swapaccount=1``. @@ -133,7 +133,7 @@ creation of the cgroup; this number can never decrease). cache the amount of memory used by the processes of this control group that can be associated precisely with a block on a block - device. When you read and write files from and to disk, this amount + device. When you read from and write to files on disk, this amount will increase. This will be the case if you use "conventional" I/O (``open``, ``read``, ``write`` syscalls) as well as mapped files (with ``mmap``). It also accounts for the memory used by ``tmpfs`` @@ -148,17 +148,11 @@ mapped_file control group. It doesn't give you information about *how much* memory is used; it rather tells you *how* it is used. -pgpgin and pgpgout - correspond to *charging events*. Each time a page is "charged" - (=added to the accounting) to a cgroup, pgpgin increases. When a - page is "uncharged" (=no longer "billed" to a cgroup), pgpgout - increases. - pgfault and pgmajfault indicate the number of times that a process of the cgroup triggered a "page fault" and a "major fault", respectively. A page fault happens when a process accesses a part of its virtual memory space - which is inexistent or protected. The former can happen if the + which is nonexistent or protected. The former can happen if the process is buggy and tries to access an invalid address (it will then be sent a ``SIGSEGV`` signal, typically killing it with the famous ``Segmentation fault`` message). The latter can happen when @@ -237,7 +231,7 @@ the processes were in direct control of the CPU (i.e. executing process code), and ``system`` is the time during which the CPU was executing system calls on behalf of those processes. -Those times are expressed in ticks of 1/100th of second. Actually, +Those times are expressed in ticks of 1/100th of a second. Actually, they are expressed in "user jiffies". There are ``USER_HZ`` *"jiffies"* per second, and on x86 systems, ``USER_HZ`` is 100. This used to map exactly to the number of scheduler "ticks" per second; but @@ -383,11 +377,11 @@ pseudo-files. (Symlinks are accepted.) In other words, to execute a command within the network namespace of a container, we need to: -* find out the PID of any process within the container that we want to +* Find out the PID of any process within the container that we want to investigate; -* create a symlink from ``/var/run/netns/`` to +* Create a symlink from ``/var/run/netns/`` to ``/proc//ns/net`` -* execute ``ip netns exec ....`` +* Execute ``ip netns exec ....`` Please review :ref:`run_findpid` to learn how to find the cgroup of a pprocess running in the container of which you want to measure network diff --git a/docs/sources/reference/run.rst b/docs/sources/reference/run.rst index 7505b7c02f..307edace00 100644 --- a/docs/sources/reference/run.rst +++ b/docs/sources/reference/run.rst @@ -21,6 +21,7 @@ Every one of the :ref:`example_list` shows running containers, and so here we try to give more in-depth guidance. .. contents:: Table of Contents + :depth: 2 .. _run_running: @@ -37,24 +38,33 @@ To learn how to interpret the types of ``[OPTIONS]``, see The list of ``[OPTIONS]`` breaks down into two groups: -* options that define the runtime behavior or environment, and -* options that override image defaults. +1. Settings exclusive to operators, including: -Since image defaults usually get set in :ref:`Dockerfiles -` (though they could also be set at :ref:`cli_commit` -time too), we will group the runtime options here by their related -Dockerfile commands so that it is easier to see how to override image -defaults and set new behavior. + * Detached or Foreground running, + * Container Identification, + * Network settings, and + * Runtime Constraints on CPU and Memory + * Privileges and LXC Configuration -We'll start, though, with the options that are unique to ``docker -run``, the options which define the runtime behavior or the container -environment. +2. Setting shared between operators and developers, where operators + can override defaults developers set in images at build time. -.. note:: The runtime operator always has final control over the - behavior of a Docker container. +Together, the ``docker run [OPTIONS]`` give complete control over +runtime behavior to the operator, allowing them to override all +defaults set by the developer during ``docker build`` and nearly all +the defaults set by the Docker runtime itself. -Detached or Foreground -====================== +Operator Exclusive Options +========================== + +Only the operator (the person executing ``docker run``) can set the +following options. + +.. contents:: + :local: + +Detached vs Foreground +---------------------- When starting a Docker container, you must first decide if you want to run the container in the background in a "detached" mode or in the @@ -65,7 +75,7 @@ default foreground mode:: Detached (-d) ............. -In detached mode (``-d=true`` or just ``-d``), all IO should be done +In detached mode (``-d=true`` or just ``-d``), all I/O should be done through network connections or shared volumes because the container is no longer listening to the commandline where you executed ``docker run``. You can reattach to a detached container with ``docker`` @@ -82,22 +92,68 @@ error. It can even pretend to be a TTY (this is what most commandline executables expect) and pass along signals. All of that is configurable:: - -a=[] : Attach to stdin, stdout and/or stderr + -a=[] : Attach to ``stdin``, ``stdout`` and/or ``stderr`` -t=false : Allocate a pseudo-tty -sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) - -i=false : Keep stdin open even if not attached + -i=false : Keep STDIN open even if not attached If you do not specify ``-a`` then Docker will `attach everything (stdin,stdout,stderr) -`_. You -can specify which of the three standard streams (stdin, stdout, -stderr) you'd like to connect between your instead, as in:: +`_. You +can specify to which of the three standard streams (``stdin``, ``stdout``, +``stderr``) you'd like to connect instead, as in:: docker run -a stdin -a stdout -i -t ubuntu /bin/bash For interactive processes (like a shell) you will typically want a tty -as well as persistent standard in, so you'll use ``-i -t`` together in -most interactive cases. +as well as persistent standard input (``stdin``), so you'll use ``-i +-t`` together in most interactive cases. + +Container Identification +------------------------ + +Name (-name) +............ + +The operator can identify a container in three ways: + +* UUID long identifier ("f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778") +* UUID short identifier ("f78375b1c487") +* Name ("evil_ptolemy") + +The UUID identifiers come from the Docker daemon, and if you do not +assign a name to the container with ``-name`` then the daemon will +also generate a random string name too. The name can become a handy +way to add meaning to a container since you can use this name when +defining :ref:`links ` (or any other place +you need to identify a container). This works for both background and +foreground Docker containers. + +PID Equivalent +.............. + +And finally, to help with automation, you can have Docker write the +container ID out to a file of your choosing. This is similar to how +some programs might write out their process ID to a file (you've seen +them as PID files):: + + -cidfile="": Write the container ID to the file + +Network Settings +---------------- + +:: + -n=true : Enable networking for this container + -dns=[] : Set custom dns servers for the container + +By default, all containers have networking enabled and they can make +any outgoing connections. The operator can completely disable +networking with ``docker run -n`` which disables all incoming and outgoing +networking. In cases like this, you would perform I/O through files or +STDIN/STDOUT only. + +Your container will use the same DNS servers as the host by default, +but you can override this with ``-dns``. Clean Up (-rm) -------------- @@ -112,57 +168,84 @@ the container exits**, you can add the ``-rm`` flag:: -rm=false: Automatically remove the container when it exits (incompatible with -d) -Name (-name) -============ -The operator can identify a container in three ways: +Runtime Constraints on CPU and Memory +------------------------------------- -* UUID long identifier ("f78375b1c487e03c9438c729345e54db9d20cfa2ac1fc3494b6eb60872e74778") -* UUID short identifier ("f78375b1c487") -* name ("evil_ptolemy") +The operator can also adjust the performance parameters of the container:: -The UUID identifiers come from the Docker daemon, and if you do not -assign a name to the container with ``-name`` then the daemon will -also generate a random string name too. The name can become a handy -way to add meaning to a container since you can use this name when -defining :ref:`links ` (or any other place -you need to identify a container). This works for both background and -foreground Docker containers. + -m="": Memory limit (format: , where unit = b, k, m or g) + -c=0 : CPU shares (relative weight) -PID Equivalent -============== +The operator can constrain the memory available to a container easily +with ``docker run -m``. If the host supports swap memory, then the +``-m`` memory setting can be larger than physical RAM. -And finally, to help with automation, you can have Docker write the -container id out to a file of your choosing. This is similar to how -some programs might write out their process ID to a file (you've seen -them as .pid files):: +Similarly the operator can increase the priority of this container +with the ``-c`` option. By default, all containers run at the same +priority and get the same proportion of CPU cycles, but you can tell +the kernel to give more shares of CPU time to one or more containers +when you start them via Docker. - -cidfile="": Write the container ID to the file +Runtime Privilege and LXC Configuration +--------------------------------------- -Overriding Dockerfile Image Defaults -==================================== +:: + + -privileged=false: Give extended privileges to this container + -lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" + +By default, Docker containers are "unprivileged" and cannot, for +example, run a Docker daemon inside a Docker container. This is +because by default a container is not allowed to access any devices, +but a "privileged" container is given access to all devices (see +lxc-template.go_ and documentation on `cgroups devices +`_). + +When the operator executes ``docker run -privileged``, Docker will +enable to access to all devices on the host as well as set some +configuration in AppArmor to allow the container nearly all the same +access to the host as processes running outside containers on the +host. Additional information about running with ``-privileged`` is +available on the `Docker Blog +`_. + +An operator can also specify LXC options using one or more +``-lxc-conf`` parameters. These can be new parameters or override +existing parameters from the lxc-template.go_. Note that in the +future, a given host's Docker daemon may not use LXC, so this is an +implementation-specific configuration meant for operators already +familiar with using LXC directly. + +.. _lxc-template.go: https://github.com/dotcloud/docker/blob/master/execdriver/lxc/lxc_template.go + + +Overriding ``Dockerfile`` Image Defaults +======================================== When a developer builds an image from a :ref:`Dockerfile ` or when she commits it, the developer can set a number of default parameters that take effect when the image starts up as a container. -Four of the Dockerfile commands cannot be overridden at runtime: +Four of the ``Dockerfile`` commands cannot be overridden at runtime: ``FROM, MAINTAINER, RUN``, and ``ADD``. Everything else has a corresponding override in ``docker run``. We'll go through what the -developer might have set in each Dockerfile instruction and how the +developer might have set in each ``Dockerfile`` instruction and how the operator can override that setting. +.. contents:: + :local: -CMD -... +CMD (Default Command or Options) +-------------------------------- -Remember the optional ``COMMAND`` in the Docker commandline:: +Recall the optional ``COMMAND`` in the Docker commandline:: docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...] This command is optional because the person who created the ``IMAGE`` -may have already provided a default ``COMMAND`` using the Dockerfile +may have already provided a default ``COMMAND`` using the ``Dockerfile`` ``CMD``. As the operator (the person running a container from the image), you can override that ``CMD`` just by specifying a new ``COMMAND``. @@ -171,22 +254,22 @@ If the image also specifies an ``ENTRYPOINT`` then the ``CMD`` or ``COMMAND`` get appended as arguments to the ``ENTRYPOINT``. -ENTRYPOINT -.......... +ENTRYPOINT (Default Command to Execute at Runtime +------------------------------------------------- :: -entrypoint="": Overwrite the default entrypoint set by the image -The ENTRYPOINT of an image is similar to a COMMAND because it +The ENTRYPOINT of an image is similar to a ``COMMAND`` because it specifies what executable to run when the container starts, but it is -(purposely) more difficult to override. The ENTRYPOINT gives a +(purposely) more difficult to override. The ``ENTRYPOINT`` gives a container its default nature or behavior, so that when you set an -ENTRYPOINT you can run the container *as if it were that binary*, +``ENTRYPOINT`` you can run the container *as if it were that binary*, complete with default options, and you can pass in more options via -the COMMAND. But, sometimes an operator may want to run something else -inside the container, so you can override the default ENTRYPOINT at -runtime by using a string to specify the new ENTRYPOINT. Here is an +the ``COMMAND``. But, sometimes an operator may want to run something else +inside the container, so you can override the default ``ENTRYPOINT`` at +runtime by using a string to specify the new ``ENTRYPOINT``. Here is an example of how to run a shell in a container that has been set up to automatically run something else (like ``/usr/bin/redis-server``):: @@ -198,16 +281,14 @@ or two examples of how to pass more parameters to that ENTRYPOINT:: docker run -i -t -entrypoint /usr/bin/redis-cli example/redis --help -EXPOSE (``run`` Networking Options) -................................... +EXPOSE (Incoming Ports) +----------------------- -The *Dockerfile* doesn't give much control over networking, only -providing the EXPOSE instruction to give a hint to the operator about -what incoming ports might provide services. At runtime, however, -Docker provides a number of ``run`` options related to networking:: +The ``Dockerfile`` doesn't give much control over networking, only +providing the ``EXPOSE`` instruction to give a hint to the operator +about what incoming ports might provide services. The following +options work with or override the ``Dockerfile``'s exposed defaults:: - -n=true : Enable networking for this container - -dns=[] : Set custom dns servers for the container -expose=[]: Expose a port from the container without publishing it to your host -P=false : Publish all exposed ports to the host interfaces @@ -217,25 +298,16 @@ Docker provides a number of ``run`` options related to networking:: (use 'docker port' to see the actual mapping) -link="" : Add link to another container (name:alias) -By default, all containers have networking enabled and they can make -any outgoing connections. The operator can completely disable -networking with ``run -n`` which disables all incoming and outgoing -networking. In cases like this, you would perform IO through files or -stdin/stdout only. - -Your container will use the same DNS servers as the host by default, -but you can override this with ``-dns``. - As mentioned previously, ``EXPOSE`` (and ``-expose``) make a port available **in** a container for incoming connections. The port number on the inside of the container (where the service listens) does not need to be the same number as the port exposed on the outside of the container (where clients connect), so inside the container you might have an HTTP service listening on port 80 (and so you ``EXPOSE 80`` in -the Dockerfile), but outside the container the port might be 42800. +the ``Dockerfile``), but outside the container the port might be 42800. To help a new client container reach the server container's internal -port operator ``-expose'd`` by the operator or ``EXPOSE'd`` by the +port operator ``-expose``'d by the operator or ``EXPOSE``'d by the developer, the operator has three choices: start the server container with ``-P`` or ``-p,`` or start the client container with ``-link``. @@ -250,10 +322,10 @@ networking interface. Docker will set some environment variables in the client container to help indicate which interface and port to use. ENV (Environment Variables) -........................... +--------------------------- The operator can **set any environment variable** in the container by -using one or more ``-e``, even overriding those already defined by the +using one or more ``-e`` flags, even overriding those already defined by the developer with a Dockefile ``ENV``:: $ docker run -e "deep=purple" -rm ubuntu /bin/bash -c export @@ -287,7 +359,9 @@ container. Let's imagine we have a container running Redis:: 2014/01/25 00:55:38 Error: No public port '6379' published for 4241164edf6f -Yet we can get information about the redis container's exposed ports with ``-link``. Choose an alias that will form a valid environment variable! +Yet we can get information about the Redis container's exposed ports +with ``-link``. Choose an alias that will form a valid environment +variable! :: @@ -312,7 +386,7 @@ And we can use that information to connect from another container as a client:: 172.17.0.32:6379> VOLUME (Shared Filesystems) -........................... +--------------------------- :: @@ -322,32 +396,24 @@ VOLUME (Shared Filesystems) The volumes commands are complex enough to have their own documentation in section :ref:`volume_def`. A developer can define one -or more VOLUMEs associated with an image, but only the operator can +or more ``VOLUME``\s associated with an image, but only the operator can give access from one container to another (or from a container to a volume mounted on the host). USER -.... +---- -:: +The default user within a container is ``root`` (id = 0), but if the +developer created additional users, those are accessible too. The +developer can set a default user to run the first process with the +``Dockerfile USER`` command, but the operator can override it :: -u="": Username or UID WORKDIR -....... +------- -:: +The default working directory for running binaries within a container is the root directory (``/``), but the developer can set a different default with the ``Dockerfile WORKDIR`` command. The operator can override this with:: -w="": Working directory inside the container -Performance -=========== - -The operator can also adjust the performance parameters of the container:: - - -c=0 : CPU shares (relative weight) - -m="": Memory limit (format: , where unit = b, k, m or g) - - -lxc-conf=[]: Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" - -privileged=false: Give extended privileges to this container - From 6cfe778c2cb6359b47cf646dc12824b58cc8d887 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 29 Jan 2014 00:10:39 -0700 Subject: [PATCH 279/364] Make get.docker.io install directions slightly better by telling user exactly how to add themselves to the "docker" group As a bonus, if the user has run this script the way we recommend (ie, without "sudo" or "su", run as their user), we can actually give them the exact command they'll need with their correct username substituted appropriately. Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/install.sh | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/hack/install.sh b/hack/install.sh index 02d812f388..65e34f9659 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -37,8 +37,10 @@ if command_exists docker || command_exists lxc-docker; then ( set -x; sleep 20 ) fi +user="$(id -un 2>/dev/null || true)" + sh_c='sh -c' -if [ "$(whoami 2>/dev/null || true)" != 'root' ]; then +if [ "$user" != 'root' ]; then if command_exists sudo; then sh_c='sudo sh -c' elif command_exists su; then @@ -124,6 +126,16 @@ case "$lsb_dist" in $sh_c 'docker run busybox echo "Docker has been successfully installed!"' ) || true fi + your_user=your-user + [ "$user" != 'root' ] && your_user="$user" + echo + echo 'If you would like to use Docker as a non-root user, you should now consider' + echo 'adding your user to the "docker" group with something like:' + echo + echo ' sudo usermod -aG docker' $your_user + echo + echo 'Remember that you will have to log out and back in for this to take effect!' + echo exit 0 ;; From f09a78cd219b24d4308034c8dd13410cfe5fbec7 Mon Sep 17 00:00:00 2001 From: Lokesh Mandvekar Date: Thu, 9 Jan 2014 12:09:25 -0500 Subject: [PATCH 280/364] ExecStartPre commands updated Docker-DCO-1.1-Signed-off-by: Lokesh Mandvekar (github: lsm5) systemd service no longer does '/bin/mount/ --make-rprivate /'. Core issue fixed by Alex Larsson (commit 157d99a). ip forwarding enabled. --- contrib/init/systemd/docker.service | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contrib/init/systemd/docker.service b/contrib/init/systemd/docker.service index aae7b6daf9..dc7cc450dd 100644 --- a/contrib/init/systemd/docker.service +++ b/contrib/init/systemd/docker.service @@ -1,11 +1,13 @@ [Unit] -Description=Docker Application Container Engine +Description=Docker Application Container Engine Documentation=http://docs.docker.io After=network.target [Service] -ExecStartPre=/bin/mount --make-rprivate / +Type=simple +ExecStartPre=/usr/sbin/sysctl -w net.ipv4.ip_forward=1 net.ipv6.conf.all.forwarding=1 ExecStart=/usr/bin/docker -d +Restart=on-failure [Install] WantedBy=multi-user.target From a9d0bbcfc6e45817f39f15a7c9b11305b4c04bad Mon Sep 17 00:00:00 2001 From: Lokesh Mandvekar Date: Wed, 29 Jan 2014 11:05:11 -0500 Subject: [PATCH 281/364] remove ip fowarding from systemd (fixed: commit #3801) Docker-DCO-1.1-Signed-off-by: Lokesh Mandvekar (github: lsm5) --- contrib/init/systemd/docker.service | 2 -- 1 file changed, 2 deletions(-) diff --git a/contrib/init/systemd/docker.service b/contrib/init/systemd/docker.service index dc7cc450dd..387be2eb1c 100644 --- a/contrib/init/systemd/docker.service +++ b/contrib/init/systemd/docker.service @@ -4,8 +4,6 @@ Documentation=http://docs.docker.io After=network.target [Service] -Type=simple -ExecStartPre=/usr/sbin/sysctl -w net.ipv4.ip_forward=1 net.ipv6.conf.all.forwarding=1 ExecStart=/usr/bin/docker -d Restart=on-failure From 217ad5e5e673aa09471e95f4726ddab2422fe5d1 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 24 Jan 2014 23:15:40 -0800 Subject: [PATCH 282/364] Remove api_params.go Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 69 ++++++++++++++++-------------- api_params.go | 43 ------------------- commands.go | 95 +++++++++++++++++++++++------------------ integration/api_test.go | 68 +++++++++++++++-------------- 4 files changed, 127 insertions(+), 148 deletions(-) delete mode 100644 api_params.go diff --git a/api.go b/api.go index 0a7f7abea7..ba8646599d 100644 --- a/api.go +++ b/api.go @@ -89,18 +89,10 @@ func httpError(w http.ResponseWriter, err error) { } } -func writeJSON(w http.ResponseWriter, code int, v interface{}) error { - b, err := json.Marshal(v) - - if err != nil { - return err - } - +func writeJSON(w http.ResponseWriter, code int, v engine.Env) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) - w.Write(b) - - return nil + return v.Encode(w) } func getBoolParam(value string) (bool, error) { @@ -352,12 +344,15 @@ func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Req if err := parseForm(r); err != nil { return err } - config := &Config{} + var ( + config = &Config{} + env engine.Env + job = srv.Eng.Job("commit", r.Form.Get("container")) + ) if err := json.NewDecoder(r.Body).Decode(config); err != nil && err != io.EOF { utils.Errorf("%s", err) } - job := srv.Eng.Job("commit", r.Form.Get("container")) job.Setenv("repo", r.Form.Get("repo")) job.Setenv("tag", r.Form.Get("tag")) job.Setenv("author", r.Form.Get("author")) @@ -369,8 +364,8 @@ func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Req if err := job.Run(); err != nil { return err } - - return writeJSON(w, http.StatusCreated, &APIID{id}) + env.Set("Id", id) + return writeJSON(w, http.StatusCreated, env) } // Creates an image from Pull or from Import @@ -555,15 +550,19 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r if err := parseForm(r); err != nil { return nil } - out := &APIRun{} - job := srv.Eng.Job("create", r.Form.Get("name")) + var ( + out engine.Env + job = srv.Eng.Job("create", r.Form.Get("name")) + outWarnings []string + outId string + warnings = bytes.NewBuffer(nil) + ) if err := job.DecodeEnv(r.Body); err != nil { return err } // Read container ID from the first line of stdout - job.Stdout.AddString(&out.ID) + job.Stdout.AddString(&outId) // Read warnings from stderr - warnings := &bytes.Buffer{} job.Stderr.Add(warnings) if err := job.Run(); err != nil { return err @@ -571,8 +570,10 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r // Parse warnings from stderr scanner := bufio.NewScanner(warnings) for scanner.Scan() { - out.Warnings = append(out.Warnings, scanner.Text()) + outWarnings = append(outWarnings, scanner.Text()) } + out.Set("Id", outId) + out.SetList("Warnings", outWarnings) return writeJSON(w, http.StatusCreated, out) } @@ -664,18 +665,22 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r * if vars == nil { return fmt.Errorf("Missing parameter") } - job := srv.Eng.Job("wait", vars["name"]) - var statusStr string - job.Stdout.AddString(&statusStr) + var ( + env engine.Env + status string + job = srv.Eng.Job("wait", vars["name"]) + ) + job.Stdout.AddString(&status) if err := job.Run(); err != nil { return err } // Parse a 16-bit encoded integer to map typical unix exit status. - status, err := strconv.ParseInt(statusStr, 10, 16) + _, err := strconv.ParseInt(status, 10, 16) if err != nil { return err } - return writeJSON(w, http.StatusOK, &APIWait{StatusCode: int(status)}) + env.Set("StatusCode", status) + return writeJSON(w, http.StatusOK, env) } func postContainersResize(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { @@ -874,24 +879,24 @@ func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r * return fmt.Errorf("Missing parameter") } - copyData := &APICopy{} - contentType := r.Header.Get("Content-Type") - if contentType == "application/json" { - if err := json.NewDecoder(r.Body).Decode(copyData); err != nil { + var copyData engine.Env + + if contentType := r.Header.Get("Content-Type"); contentType == "application/json" { + if err := copyData.Decode(r.Body); err != nil { return err } } else { return fmt.Errorf("Content-Type not supported: %s", contentType) } - if copyData.Resource == "" { + if copyData.Get("Resource") == "" { return fmt.Errorf("Path cannot be empty") } - if copyData.Resource[0] == '/' { - copyData.Resource = copyData.Resource[1:] + if copyData.Get("Resource")[0] == '/' { + copyData.Set("Resource", copyData.Get("Resource")[1:]) } - job := srv.Eng.Job("container_copy", vars["name"], copyData.Resource) + job := srv.Eng.Job("container_copy", vars["name"], copyData.Get("Resource")) job.Stdout.Add(w) if err := job.Run(); err != nil { utils.Errorf("%s", err.Error()) diff --git a/api_params.go b/api_params.go deleted file mode 100644 index fb5ad6f388..0000000000 --- a/api_params.go +++ /dev/null @@ -1,43 +0,0 @@ -package docker - -type ( - APITop struct { - Titles []string - Processes [][]string - } - - APIRmi struct { - Deleted string `json:",omitempty"` - Untagged string `json:",omitempty"` - } - - APIID struct { - ID string `json:"Id"` - } - - APIRun struct { - ID string `json:"Id"` - Warnings []string `json:",omitempty"` - } - - APIPort struct { - PrivatePort int64 - PublicPort int64 - Type string - IP string - } - - APIWait struct { - StatusCode int - } - - APIImageConfig struct { - ID string `json:"Id"` - *Config - } - - APICopy struct { - Resource string - HostPath string - } -) diff --git a/commands.go b/commands.go index 083ca39bc5..5af02046a3 100644 --- a/commands.go +++ b/commands.go @@ -755,18 +755,21 @@ func (cli *DockerCli) CmdTop(args ...string) error { val.Set("ps_args", strings.Join(cmd.Args()[1:], " ")) } - body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false)) + stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false) if err != nil { return err } - procs := APITop{} - err = json.Unmarshal(body, &procs) - if err != nil { + var procs engine.Env + if err := procs.Decode(stream); err != nil { return err } w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) - fmt.Fprintln(w, strings.Join(procs.Titles, "\t")) - for _, proc := range procs.Processes { + fmt.Fprintln(w, strings.Join(procs.GetList("Titles"), "\t")) + processes := [][]string{} + if err := procs.GetJson("Processes", &processes); err != nil { + return err + } + for _, proc := range processes { fmt.Fprintln(w, strings.Join(proc, "\t")) } w.Flush() @@ -1451,25 +1454,25 @@ func (cli *DockerCli) CmdCommit(args ...string) error { v.Set("tag", tag) v.Set("comment", *flComment) v.Set("author", *flAuthor) - var config *Config + var ( + config *Config + env engine.Env + ) if *flConfig != "" { config = &Config{} if err := json.Unmarshal([]byte(*flConfig), config); err != nil { return err } } - body, _, err := readBody(cli.call("POST", "/commit?"+v.Encode(), config, false)) + stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, false) if err != nil { return err } - - apiID := &APIID{} - err = json.Unmarshal(body, apiID) - if err != nil { + if err := env.Decode(stream); err != nil { return err } - fmt.Fprintf(cli.out, "%s\n", apiID.ID) + fmt.Fprintf(cli.out, "%s\n", env.Get("ID")) return nil } @@ -1989,7 +1992,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } //create the container - body, statusCode, err := readBody(cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false)) + stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false) //if image not found try to pull it if statusCode == 404 { _, tag := utils.ParseRepositoryTag(config.Image) @@ -2026,30 +2029,30 @@ func (cli *DockerCli) CmdRun(args ...string) error { if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil { return err } - if body, _, err = readBody(cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false)); err != nil { + if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false); err != nil { return err } } else if err != nil { return err } - var runResult APIRun - if err := json.Unmarshal(body, &runResult); err != nil { + var runResult engine.Env + if err := runResult.Decode(stream); err != nil { return err } - for _, warning := range runResult.Warnings { + for _, warning := range runResult.GetList("Warnings") { fmt.Fprintf(cli.err, "WARNING: %s\n", warning) } if len(hostConfig.ContainerIDFile) > 0 { - if _, err = containerIDFile.Write([]byte(runResult.ID)); err != nil { + if _, err = containerIDFile.Write([]byte(runResult.Get("Id"))); err != nil { return fmt.Errorf("failed to write the container ID to the file: %s", err) } } if sigProxy { - sigc := cli.forwardAllSignals(runResult.ID) + sigc := cli.forwardAllSignals(runResult.Get("Id")) defer utils.StopCatch(sigc) } @@ -2063,7 +2066,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { waitDisplayId = make(chan struct{}) go func() { defer close(waitDisplayId) - fmt.Fprintf(cli.out, "%s\n", runResult.ID) + fmt.Fprintf(cli.out, "%s\n", runResult.Get("Id")) }() } @@ -2105,7 +2108,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } errCh = utils.Go(func() error { - return cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked) + return cli.hijack("POST", "/containers/"+runResult.Get("Id")+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked) }) } else { close(hijacked) @@ -2127,12 +2130,12 @@ func (cli *DockerCli) CmdRun(args ...string) error { } //start the container - if _, _, err = readBody(cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig, false)); err != nil { + if _, _, err = readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/start", hostConfig, false)); err != nil { return err } if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminal { - if err := cli.monitorTtySize(runResult.ID); err != nil { + if err := cli.monitorTtySize(runResult.Get("Id")); err != nil { utils.Errorf("Error monitoring TTY size: %s\n", err) } } @@ -2157,26 +2160,26 @@ func (cli *DockerCli) CmdRun(args ...string) error { if autoRemove { // Autoremove: wait for the container to finish, retrieve // the exit code and remove the container - if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.ID+"/wait", nil, false)); err != nil { + if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/wait", nil, false)); err != nil { return err } - if _, status, err = getExitCode(cli, runResult.ID); err != nil { + if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil { return err } - if _, _, err := readBody(cli.call("DELETE", "/containers/"+runResult.ID+"?v=1", nil, false)); err != nil { + if _, _, err := readBody(cli.call("DELETE", "/containers/"+runResult.Get("Id")+"?v=1", nil, false)); err != nil { return err } } else { if !config.Tty { // In non-tty mode, we can't dettach, so we know we need to wait. - if status, err = waitForExit(cli, runResult.ID); err != nil { + if status, err = waitForExit(cli, runResult.Get("Id")); err != nil { return err } } else { // In TTY mode, there is a race. If the process dies too slowly, the state can be update after the getExitCode call // and result in a wrong exit code. // No Autoremove: Simply retrieve the exit code - if _, status, err = getExitCode(cli, runResult.ID); err != nil { + if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil { return err } } @@ -2198,15 +2201,15 @@ func (cli *DockerCli) CmdCp(args ...string) error { return nil } - var copyData APICopy + var copyData engine.Env info := strings.Split(cmd.Arg(0), ":") if len(info) != 2 { return fmt.Errorf("Error: Path not specified") } - copyData.Resource = info[1] - copyData.HostPath = cmd.Arg(1) + copyData.Set("Resource", info[1]) + copyData.Set("HostPath", cmd.Arg(1)) stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, false) if stream != nil { @@ -2217,7 +2220,7 @@ func (cli *DockerCli) CmdCp(args ...string) error { } if statusCode == 200 { - if err := archive.Untar(stream, copyData.HostPath, nil); err != nil { + if err := archive.Untar(stream, copyData.Get("HostPath"), nil); err != nil { return err } } @@ -2260,13 +2263,21 @@ func (cli *DockerCli) CmdLoad(args ...string) error { } func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) { - var params io.Reader + params := bytes.NewBuffer(nil) if data != nil { - buf, err := json.Marshal(data) - if err != nil { - return nil, -1, err + if env, ok := data.(engine.Env); ok { + if err := env.Encode(params); err != nil { + return nil, -1, err + } + } else { + buf, err := json.Marshal(data) + if err != nil { + return nil, -1, err + } + if _, err := params.Write(buf); err != nil { + return nil, -1, err + } } - params = bytes.NewBuffer(buf) } // fixme: refactor client to support redirect re := regexp.MustCompile("/+") @@ -2569,16 +2580,16 @@ func (cli *DockerCli) LoadConfigFile() (err error) { } func waitForExit(cli *DockerCli, containerId string) (int, error) { - body, _, err := readBody(cli.call("POST", "/containers/"+containerId+"/wait", nil, false)) + stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false) if err != nil { return -1, err } - var out APIWait - if err := json.Unmarshal(body, &out); err != nil { + var out engine.Env + if err := out.Decode(stream); err != nil { return -1, err } - return out.StatusCode, nil + return out.GetInt("StatusCode"), nil } // getExitCode perform an inspect on the container. It returns diff --git a/integration/api_test.go b/integration/api_test.go index 95cae47e15..b9ff079cb1 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -485,26 +485,29 @@ func TestGetContainersTop(t *testing.T) { t.Fatal(err) } assertHttpNotError(r, t) - procs := docker.APITop{} - if err := json.Unmarshal(r.Body.Bytes(), &procs); err != nil { + var procs engine.Env + if err := procs.Decode(r.Body); err != nil { t.Fatal(err) } - if len(procs.Titles) != 11 { - t.Fatalf("Expected 11 titles, found %d.", len(procs.Titles)) + if len(procs.GetList("Titles")) != 11 { + t.Fatalf("Expected 11 titles, found %d.", len(procs.GetList("Titles"))) } - if procs.Titles[0] != "USER" || procs.Titles[10] != "COMMAND" { - t.Fatalf("Expected Titles[0] to be USER and Titles[10] to be COMMAND, found %s and %s.", procs.Titles[0], procs.Titles[10]) + if procs.GetList("Titles")[0] != "USER" || procs.GetList("Titles")[10] != "COMMAND" { + t.Fatalf("Expected Titles[0] to be USER and Titles[10] to be COMMAND, found %s and %s.", procs.GetList("Titles")[0], procs.GetList("Titles")[10]) } - - if len(procs.Processes) != 2 { - t.Fatalf("Expected 2 processes, found %d.", len(procs.Processes)) + processes := [][]string{} + if err := procs.GetJson("Processes", &processes); err != nil { + t.Fatal(err) } - if procs.Processes[0][10] != "/bin/sh -c cat" { - t.Fatalf("Expected `/bin/sh -c cat`, found %s.", procs.Processes[0][10]) + if len(processes) != 2 { + t.Fatalf("Expected 2 processes, found %d.", len(processes)) } - if procs.Processes[1][10] != "/bin/sh -c cat" { - t.Fatalf("Expected `/bin/sh -c cat`, found %s.", procs.Processes[1][10]) + if processes[0][10] != "/bin/sh -c cat" { + t.Fatalf("Expected `/bin/sh -c cat`, found %s.", processes[0][10]) + } + if processes[1][10] != "/bin/sh -c cat" { + t.Fatalf("Expected `/bin/sh -c cat`, found %s.", processes[1][10]) } } @@ -570,11 +573,11 @@ func TestPostCommit(t *testing.T) { t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) } - apiID := &docker.APIID{} - if err := json.Unmarshal(r.Body.Bytes(), apiID); err != nil { + var env engine.Env + if err := env.Decode(r.Body); err != nil { t.Fatal(err) } - if _, err := srv.ImageInspect(apiID.ID); err != nil { + if _, err := srv.ImageInspect(env.Get("Id")); err != nil { t.Fatalf("The image has not been committed") } } @@ -607,11 +610,11 @@ func TestPostContainersCreate(t *testing.T) { t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) } - apiRun := &docker.APIRun{} - if err := json.Unmarshal(r.Body.Bytes(), apiRun); err != nil { + var apiRun engine.Env + if err := apiRun.Decode(r.Body); err != nil { t.Fatal(err) } - containerID := apiRun.ID + containerID := apiRun.Get("Id") containerAssertExists(eng, containerID, t) containerRun(eng, containerID, t) @@ -863,12 +866,12 @@ func TestPostContainersWait(t *testing.T) { t.Fatal(err) } assertHttpNotError(r, t) - apiWait := &docker.APIWait{} - if err := json.Unmarshal(r.Body.Bytes(), apiWait); err != nil { + var apiWait engine.Env + if err := apiWait.Decode(r.Body); err != nil { t.Fatal(err) } - if apiWait.StatusCode != 0 { - t.Fatalf("Non zero exit code for sleep: %d\n", apiWait.StatusCode) + if apiWait.GetInt("StatusCode") != 0 { + t.Fatalf("Non zero exit code for sleep: %d\n", apiWait.GetInt("StatusCode")) } }) @@ -1160,12 +1163,12 @@ func TestDeleteImages(t *testing.T) { t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) } - var outs []docker.APIRmi - if err := json.Unmarshal(r2.Body.Bytes(), &outs); err != nil { + outs := engine.NewTable("Created", 0) + if _, err := outs.ReadListFrom(r2.Body.Bytes()); err != nil { t.Fatal(err) } - if len(outs) != 1 { - t.Fatalf("Expected %d event (untagged), got %d", 1, len(outs)) + if len(outs.Data) != 1 { + t.Fatalf("Expected %d event (untagged), got %d", 1, len(outs.Data)) } images = getImages(eng, t, false, "") @@ -1190,14 +1193,17 @@ func TestPostContainersCopy(t *testing.T) { containerRun(eng, containerID, t) r := httptest.NewRecorder() - copyData := docker.APICopy{HostPath: ".", Resource: "/test.txt"} - jsonData, err := json.Marshal(copyData) - if err != nil { + var copyData engine.Env + copyData.Set("Resource", "/test.txt") + copyData.Set("HostPath", ".") + + jsonData := bytes.NewBuffer(nil) + if err := copyData.Encode(jsonData); err != nil { t.Fatal(err) } - req, err := http.NewRequest("POST", "/containers/"+containerID+"/copy", bytes.NewReader(jsonData)) + req, err := http.NewRequest("POST", "/containers/"+containerID+"/copy", jsonData) if err != nil { t.Fatal(err) } From 55d7aa1b49a49a27d77cd4011d921e340ec99f9f Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 28 Jan 2014 00:27:02 +0000 Subject: [PATCH 283/364] job.error\* now return engine.StatusErr Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- engine/job.go | 10 +- server.go | 452 +++++++++++++++++--------------------------------- 2 files changed, 156 insertions(+), 306 deletions(-) diff --git a/engine/job.go b/engine/job.go index 179b2ebdda..34393fcab5 100644 --- a/engine/job.go +++ b/engine/job.go @@ -188,10 +188,12 @@ func (job *Job) Printf(format string, args ...interface{}) (n int, err error) { return fmt.Fprintf(job.Stdout, format, args...) } -func (job *Job) Errorf(format string, args ...interface{}) (n int, err error) { - return fmt.Fprintf(job.Stderr, format, args...) +func (job *Job) Errorf(format string, args ...interface{}) Status { + fmt.Fprintf(job.Stderr, format, args...) + return StatusErr } -func (job *Job) Error(err error) (int, error) { - return fmt.Fprintf(job.Stderr, "%s", err) +func (job *Job) Error(err error) Status { + fmt.Fprintf(job.Stderr, "%s", err) + return StatusErr } diff --git a/server.go b/server.go index a6731842cc..a6f869f977 100644 --- a/server.go +++ b/server.go @@ -45,8 +45,7 @@ func jobInitApi(job *engine.Job) engine.Status { // FIXME: ImportEnv deprecates ConfigFromJob srv, err := NewServer(job.Eng, ConfigFromJob(job)) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if srv.runtime.config.Pidfile != "" { job.Logf("Creating pidfile") @@ -107,8 +106,7 @@ func jobInitApi(job *engine.Job) engine.Status { "auth": srv.Auth, } { if err := job.Eng.Register(name, handler); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } return engine.StatusOK @@ -131,8 +129,7 @@ func (srv *Server) ListenAndServe(job *engine.Job) engine.Status { for i := 0; i < len(protoAddrs); i += 1 { err := <-chErrors if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } @@ -200,8 +197,7 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { } if n := len(job.Args); n < 1 || n > 2 { - job.Errorf("Usage: %s CONTAINER [SIGNAL]", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER [SIGNAL]", job.Name) } name := job.Args[0] var sig uint64 @@ -212,8 +208,7 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { // The largest legal signal is 31, so let's parse on 5 bits sig, err = strconv.ParseUint(job.Args[1], 10, 5) if err != nil { - job.Errorf("Invalid signal: %s", job.Args[1]) - return engine.StatusErr + return job.Errorf("Invalid signal: %s", job.Args[1]) } } } @@ -221,21 +216,18 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { // If no signal is passed, or SIGKILL, perform regular Kill (SIGKILL + wait()) if sig == 0 || syscall.Signal(sig) == syscall.SIGKILL { if err := container.Kill(); err != nil { - job.Errorf("Cannot kill container %s: %s", name, err) - return engine.StatusErr + return job.Errorf("Cannot kill container %s: %s", name, err) } srv.LogEvent("kill", container.ID, srv.runtime.repositories.ImageName(container.Image)) } else { // Otherwise, just send the requested signal if err := container.kill(int(sig)); err != nil { - job.Errorf("Cannot kill container %s: %s", name, err) - return engine.StatusErr + return job.Errorf("Cannot kill container %s: %s", name, err) } // FIXME: Add event for signals } } else { - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } return engine.StatusOK } @@ -245,8 +237,7 @@ func (srv *Server) Auth(job *engine.Job) engine.Status { job.GetenvJson("authConfig", authConfig) status, err := auth.Login(authConfig, srv.HTTPRequestFactory(nil)) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } job.Printf("%s\n", status) return engine.StatusOK @@ -254,8 +245,7 @@ func (srv *Server) Auth(job *engine.Job) engine.Status { func (srv *Server) Events(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Usage: %s FROM", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s FROM", job.Name) } var ( @@ -305,8 +295,7 @@ func (srv *Server) Events(job *engine.Job) engine.Status { continue } if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } return engine.StatusOK @@ -314,28 +303,24 @@ func (srv *Server) Events(job *engine.Job) engine.Status { func (srv *Server) ContainerExport(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Usage: %s container_id", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s container_id", job.Name) } name := job.Args[0] if container := srv.runtime.Get(name); container != nil { data, err := container.Export() if err != nil { - job.Errorf("%s: %s", name, err) - return engine.StatusErr + return job.Errorf("%s: %s", name, err) } // Stream the entire contents of the container (basically a volatile snapshot) if _, err := io.Copy(job.Stdout, data); err != nil { - job.Errorf("%s: %s", name, err) - return engine.StatusErr + return job.Errorf("%s: %s", name, err) } // FIXME: factor job-specific LogEvent to engine.Job.Run() srv.LogEvent("export", container.ID, srv.runtime.repositories.ImageName(container.Image)) return engine.StatusOK } - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } // ImageExport exports all images with the given tag. All versions @@ -345,15 +330,13 @@ func (srv *Server) ContainerExport(job *engine.Job) engine.Status { // out is the writer where the images are written to. func (srv *Server) ImageExport(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Usage: %s CONTAINER\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER\n", job.Name) } name := job.Args[0] // get image json tempdir, err := ioutil.TempDir("", "docker-export-") if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } defer os.RemoveAll(tempdir) @@ -361,20 +344,17 @@ func (srv *Server) ImageExport(job *engine.Job) engine.Status { rootRepo, err := srv.runtime.repositories.Get(name) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if rootRepo != nil { for _, id := range rootRepo { image, err := srv.ImageInspect(id) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if err := srv.exportImage(image, tempdir); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } @@ -384,30 +364,25 @@ func (srv *Server) ImageExport(job *engine.Job) engine.Status { rootRepoJson, _ := json.Marshal(rootRepoMap) if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.ModeAppend); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } else { image, err := srv.ImageInspect(name) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if err := srv.exportImage(image, tempdir); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } fs, err := archive.Tar(tempdir, archive.Uncompressed) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if _, err := io.Copy(job.Stdout, fs); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } @@ -469,8 +444,7 @@ func (srv *Server) exportImage(image *Image, tempdir string) error { func (srv *Server) Build(job *engine.Job) engine.Status { if len(job.Args) != 0 { - job.Errorf("Usage: %s\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s\n", job.Name) } var ( remoteURL = job.Getenv("remote") @@ -495,38 +469,32 @@ func (srv *Server) Build(job *engine.Job) engine.Status { } root, err := ioutil.TempDir("", "docker-build-git") if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } defer os.RemoveAll(root) if output, err := exec.Command("git", "clone", remoteURL, root).CombinedOutput(); err != nil { - job.Errorf("Error trying to use git: %s (%s)", err, output) - return engine.StatusErr + return job.Errorf("Error trying to use git: %s (%s)", err, output) } c, err := archive.Tar(root, archive.Uncompressed) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } context = c } else if utils.IsURL(remoteURL) { f, err := utils.Download(remoteURL) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } defer f.Body.Close() dockerFile, err := ioutil.ReadAll(f.Body) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } c, err := MkBuildContext(string(dockerFile), nil) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } context = c } @@ -544,8 +512,7 @@ func (srv *Server) Build(job *engine.Job) engine.Status { !suppressOutput, !noCache, rm, job.Stdout, sf, authConfig, configFile) id, err := b.Build(context) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if repoName != "" { srv.runtime.repositories.Set(repoName, tag, id, false) @@ -558,8 +525,7 @@ func (srv *Server) Build(job *engine.Job) engine.Status { func (srv *Server) ImageLoad(job *engine.Job) engine.Status { tmpImageDir, err := ioutil.TempDir("", "docker-import-") if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } defer os.RemoveAll(tmpImageDir) @@ -570,40 +536,33 @@ func (srv *Server) ImageLoad(job *engine.Job) engine.Status { tarFile, err := os.Create(repoTarFile) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if _, err := io.Copy(tarFile, job.Stdin); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } tarFile.Close() repoFile, err := os.Open(repoTarFile) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if err := os.Mkdir(repoDir, os.ModeDir); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if err := archive.Untar(repoFile, repoDir, nil); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } dirs, err := ioutil.ReadDir(repoDir) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } for _, d := range dirs { if d.IsDir() { if err := srv.recursiveLoad(d.Name(), tmpImageDir); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } } @@ -612,21 +571,18 @@ func (srv *Server) ImageLoad(job *engine.Job) engine.Status { if err == nil { repositories := map[string]Repository{} if err := json.Unmarshal(repositoriesJson, &repositories); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } for imageName, tagMap := range repositories { for tag, address := range tagMap { if err := srv.runtime.repositories.Set(imageName, tag, address, true); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } } } else if !os.IsNotExist(err) { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK @@ -670,8 +626,7 @@ func (srv *Server) recursiveLoad(address, tmpImageDir string) error { func (srv *Server) ImagesSearch(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { - job.Errorf("Usage: %s TERM", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s TERM", job.Name) } var ( term = job.Args[0] @@ -683,13 +638,11 @@ func (srv *Server) ImagesSearch(job *engine.Job) engine.Status { r, err := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), auth.IndexServerAddress()) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } results, err := r.SearchRepositories(term) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } outs := engine.NewTable("star_count", 0) for _, result := range results.Results { @@ -699,16 +652,14 @@ func (srv *Server) ImagesSearch(job *engine.Job) engine.Status { } outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } func (srv *Server) ImageInsert(job *engine.Job) engine.Status { if len(job.Args) != 3 { - job.Errorf("Usage: %s IMAGE URL PATH\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s IMAGE URL PATH\n", job.Name) } var ( @@ -722,32 +673,27 @@ func (srv *Server) ImageInsert(job *engine.Job) engine.Status { out := utils.NewWriteFlusher(job.Stdout) img, err := srv.runtime.repositories.LookupImage(name) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } file, err := utils.Download(url) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } defer file.Body.Close() config, _, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.sysInfo) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } c, _, err := srv.runtime.Create(config, "") if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf, false, utils.TruncateID(img.ID), "Downloading"), path); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } // FIXME: Handle custom repo, tag comment, author img, err = srv.runtime.Commit(c, "", "", img.Comment, img.Author, nil) @@ -773,8 +719,7 @@ func (srv *Server) ImagesViz(job *engine.Job) engine.Status { for _, image := range images { parentImage, err = image.GetParent() if err != nil { - job.Errorf("Error while getting parent image: %v", err) - return engine.StatusErr + return job.Errorf("Error while getting parent image: %v", err) } if parentImage != nil { job.Stdout.Write([]byte(" \"" + parentImage.ID + "\" -> \"" + image.ID + "\"\n")) @@ -809,8 +754,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { allImages, err = srv.runtime.graph.Heads() } if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } lookup := make(map[string]*engine.Env) for name, repository := range srv.runtime.repositories.Repositories { @@ -864,8 +808,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } @@ -908,22 +851,19 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { v.Set("InitSha1", utils.INITSHA1) v.Set("InitPath", initPath) if _, err := v.WriteTo(job.Stdout); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } func (srv *Server) ImageHistory(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { - job.Errorf("Usage: %s IMAGE", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s IMAGE", job.Name) } name := job.Args[0] image, err := srv.runtime.repositories.LookupImage(name) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } lookupMap := make(map[string][]string) @@ -950,16 +890,14 @@ func (srv *Server) ImageHistory(job *engine.Job) engine.Status { }) outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } func (srv *Server) ContainerTop(job *engine.Job) engine.Status { if len(job.Args) != 1 && len(job.Args) != 2 { - job.Errorf("Not enough arguments. Usage: %s CONTAINER [PS_ARGS]\n", job.Name) - return engine.StatusErr + return job.Errorf("Not enough arguments. Usage: %s CONTAINER [PS_ARGS]\n", job.Name) } var ( name = job.Args[0] @@ -972,18 +910,15 @@ func (srv *Server) ContainerTop(job *engine.Job) engine.Status { if container := srv.runtime.Get(name); container != nil { if !container.State.IsRunning() { - job.Errorf("Container %s is not running", name) - return engine.StatusErr + return job.Errorf("Container %s is not running", name) } pids, err := srv.runtime.execDriver.GetPidsForContainer(container.ID) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } output, err := exec.Command("ps", psArgs).Output() if err != nil { - job.Errorf("Error running ps: %s", err) - return engine.StatusErr + return job.Errorf("Error running ps: %s", err) } lines := strings.Split(string(output), "\n") @@ -998,8 +933,7 @@ func (srv *Server) ContainerTop(job *engine.Job) engine.Status { } } if pidIndex == -1 { - job.Errorf("Couldn't find PID field in ps output") - return engine.StatusErr + return job.Errorf("Couldn't find PID field in ps output") } processes := [][]string{} @@ -1010,8 +944,7 @@ func (srv *Server) ContainerTop(job *engine.Job) engine.Status { fields := strings.Fields(line) p, err := strconv.Atoi(fields[pidIndex]) if err != nil { - job.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) - return engine.StatusErr + return job.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) } for _, pid := range pids { @@ -1029,38 +962,32 @@ func (srv *Server) ContainerTop(job *engine.Job) engine.Status { return engine.StatusOK } - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } func (srv *Server) ContainerChanges(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { - job.Errorf("Usage: %s CONTAINER", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER", job.Name) } name := job.Args[0] if container := srv.runtime.Get(name); container != nil { outs := engine.NewTable("", 0) changes, err := container.Changes() if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } for _, change := range changes { out := &engine.Env{} if err := out.Import(change); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } outs.Add(out) } if _, err := outs.WriteListTo(job.Stdout); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } else { - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } return engine.StatusOK } @@ -1109,8 +1036,7 @@ func (srv *Server) Containers(job *engine.Job) engine.Status { out.Set("Status", container.State.String()) str, err := container.NetworkSettings.PortMappingAPI().ToListString() if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } out.Set("Ports", str) if size { @@ -1122,34 +1048,29 @@ func (srv *Server) Containers(job *engine.Job) engine.Status { } outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } func (srv *Server) ContainerCommit(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) - return engine.StatusErr + return job.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) } name := job.Args[0] container := srv.runtime.Get(name) if container == nil { - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } var config Config if err := job.GetenvJson("config", &config); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } img, err := srv.runtime.Commit(container, job.Getenv("repo"), job.Getenv("tag"), job.Getenv("comment"), job.Getenv("author"), &config) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } job.Printf("%s\n", img.ID) return engine.StatusOK @@ -1157,16 +1078,14 @@ func (srv *Server) ContainerCommit(job *engine.Job) engine.Status { func (srv *Server) ImageTag(job *engine.Job) engine.Status { if len(job.Args) != 2 && len(job.Args) != 3 { - job.Errorf("Usage: %s IMAGE REPOSITORY [TAG]\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s IMAGE REPOSITORY [TAG]\n", job.Name) } var tag string if len(job.Args) == 3 { tag = job.Args[2] } if err := srv.runtime.repositories.Set(job.Args[1], tag, job.Args[0], job.GetenvBool("force")); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } @@ -1403,8 +1322,7 @@ func (srv *Server) poolRemove(kind, key string) error { func (srv *Server) ImagePull(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 && n != 2 { - job.Errorf("Usage: %s IMAGE [TAG]", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s IMAGE [TAG]", job.Name) } var ( localName = job.Args[0] @@ -1428,22 +1346,19 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { <-c return engine.StatusOK } - job.Error(err) - return engine.StatusErr + return job.Error(err) } defer srv.poolRemove("pull", localName+":"+tag) // Resolve the Repository name from fqn to endpoint + name endpoint, remoteName, err := registry.ResolveRepositoryName(localName) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } r, err := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), endpoint) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if endpoint == auth.IndexServerAddress() { @@ -1452,8 +1367,7 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { } if err = srv.pullRepository(r, job.Stdout, localName, remoteName, tag, sf, job.GetenvBool("parallel")); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK @@ -1622,8 +1536,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, // FIXME: Allow to interrupt current push when new push of same image is done. func (srv *Server) ImagePush(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { - job.Errorf("Usage: %s IMAGE", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s IMAGE", job.Name) } var ( localName = job.Args[0] @@ -1635,23 +1548,20 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status { job.GetenvJson("authConfig", authConfig) job.GetenvJson("metaHeaders", metaHeaders) if _, err := srv.poolAdd("push", localName); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } defer srv.poolRemove("push", localName) // Resolve the Repository name from fqn to endpoint + name endpoint, remoteName, err := registry.ResolveRepositoryName(localName) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } img, err := srv.runtime.graph.Get(localName) r, err2 := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), endpoint) if err2 != nil { - job.Error(err2) - return engine.StatusErr + return job.Error(err2) } if err != nil { @@ -1660,28 +1570,24 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status { // If it fails, try to get the repository if localRepo, exists := srv.runtime.repositories.Repositories[localName]; exists { if err := srv.pushRepository(r, job.Stdout, localName, remoteName, localRepo, sf); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } - job.Error(err) - return engine.StatusErr + return job.Error(err) } var token []string job.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", localName)) if _, err := srv.pushImage(r, job.Stdout, remoteName, img.ID, endpoint, token, sf); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } func (srv *Server) ImageImport(job *engine.Job) engine.Status { if n := len(job.Args); n != 2 && n != 3 { - job.Errorf("Usage: %s SRC REPO [TAG]", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s SRC REPO [TAG]", job.Name) } var ( src = job.Args[0] @@ -1700,8 +1606,7 @@ func (srv *Server) ImageImport(job *engine.Job) engine.Status { } else { u, err := url.Parse(src) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if u.Scheme == "" { u.Scheme = "http" @@ -1713,21 +1618,18 @@ func (srv *Server) ImageImport(job *engine.Job) engine.Status { // If curl is not available, fallback to http.Get() resp, err = utils.Download(u.String()) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), job.Stdout, sf, true, "", "Importing") } img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } // Optionally register the image at REPO/TAG if repo != "" { if err := srv.runtime.repositories.Set(repo, tag, img.ID, true); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } job.Stdout.Write(sf.FormatStatus("", img.ID)) @@ -1739,17 +1641,14 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { if len(job.Args) == 1 { name = job.Args[0] } else if len(job.Args) > 1 { - job.Printf("Usage: %s", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s", job.Name) } var config Config if err := job.ExportEnv(&config); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if config.Memory != 0 && config.Memory < 524288 { - job.Errorf("Minimum memory limit allowed is 512k") - return engine.StatusErr + return job.Errorf("Minimum memory limit allowed is 512k") } if config.Memory > 0 && !srv.runtime.sysInfo.MemoryLimit { job.Errorf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") @@ -1776,11 +1675,9 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { if tag == "" { tag = DEFAULTTAG } - job.Errorf("No such image: %s (tag: %s)", config.Image, tag) - return engine.StatusErr + return job.Errorf("No such image: %s (tag: %s)", config.Image, tag) } - job.Error(err) - return engine.StatusErr + return job.Error(err) } if !container.Config.NetworkDisabled && srv.runtime.sysInfo.IPv4ForwardingDisabled { job.Errorf("WARNING: IPv4 forwarding is disabled.\n") @@ -1793,15 +1690,14 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { job.Printf("%s\n", container.ID) } for _, warning := range buildWarnings { - job.Errorf("%s\n", warning) + return job.Errorf("%s\n", warning) } return engine.StatusOK } func (srv *Server) ContainerRestart(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Usage: %s CONTAINER\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER\n", job.Name) } name := job.Args[0] t := job.GetenvInt("t") @@ -1810,22 +1706,18 @@ func (srv *Server) ContainerRestart(job *engine.Job) engine.Status { } if container := srv.runtime.Get(name); container != nil { if err := container.Restart(int(t)); err != nil { - job.Errorf("Cannot restart container %s: %s\n", name, err) - return engine.StatusErr + return job.Errorf("Cannot restart container %s: %s\n", name, err) } srv.LogEvent("restart", container.ID, srv.runtime.repositories.ImageName(container.Image)) } else { - job.Errorf("No such container: %s\n", name) - return engine.StatusErr + return job.Errorf("No such container: %s\n", name) } return engine.StatusOK - } func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) - return engine.StatusErr + return job.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) } name := job.Args[0] removeVolume := job.GetenvBool("removeVolume") @@ -1835,23 +1727,19 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { if removeLink { if container == nil { - job.Errorf("No such link: %s", name) - return engine.StatusErr + return job.Errorf("No such link: %s", name) } name, err := getFullName(name) if err != nil { job.Error(err) - return engine.StatusErr } parent, n := path.Split(name) if parent == "/" { - job.Errorf("Conflict, cannot remove the default name of the container") - return engine.StatusErr + return job.Errorf("Conflict, cannot remove the default name of the container") } pe := srv.runtime.containerGraph.Get(parent) if pe == nil { - job.Errorf("Cannot get parent %s for name %s", parent, name) - return engine.StatusErr + return job.Errorf("Cannot get parent %s for name %s", parent, name) } parentContainer := srv.runtime.Get(pe.ID()) @@ -1864,16 +1752,14 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { } if err := srv.runtime.containerGraph.Delete(name); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } if container != nil { if container.State.IsRunning() { - job.Errorf("Impossible to remove a running container, please stop it first") - return engine.StatusErr + return job.Errorf("Impossible to remove a running container, please stop it first") } volumes := make(map[string]struct{}) @@ -1898,8 +1784,7 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { volumes[volumeId] = struct{}{} } if err := srv.runtime.Destroy(container); err != nil { - job.Errorf("Cannot destroy container %s: %s", name, err) - return engine.StatusErr + return job.Errorf("Cannot destroy container %s: %s", name, err) } srv.LogEvent("destroy", container.ID, srv.runtime.repositories.ImageName(container.Image)) @@ -1919,14 +1804,12 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { continue } if err := srv.runtime.volumes.Delete(volumeId); err != nil { - job.Errorf("Error calling volumes.Delete(%q): %v", volumeId, err) - return engine.StatusErr + return job.Errorf("Error calling volumes.Delete(%q): %v", volumeId, err) } } } } else { - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } return engine.StatusOK } @@ -2078,22 +1961,18 @@ func (srv *Server) DeleteImage(name string, autoPrune bool) (*engine.Table, erro func (srv *Server) ImageDelete(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { - job.Errorf("Usage: %s IMAGE", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s IMAGE", job.Name) } imgs, err := srv.DeleteImage(job.Args[0], job.GetenvBool("autoPrune")) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if len(imgs.Data) == 0 { - job.Errorf("Conflict, %s wasn't deleted", job.Args[0]) - return engine.StatusErr + return job.Errorf("Conflict, %s wasn't deleted", job.Args[0]) } if _, err := imgs.WriteListTo(job.Stdout); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } @@ -2183,23 +2062,20 @@ func (srv *Server) RegisterLinks(container *Container, hostConfig *HostConfig) e func (srv *Server) ContainerStart(job *engine.Job) engine.Status { if len(job.Args) < 1 { - job.Errorf("Usage: %s container_id", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s container_id", job.Name) } name := job.Args[0] runtime := srv.runtime container := runtime.Get(name) if container == nil { - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } // If no environment was set, then no hostconfig was passed. if len(job.Environ()) > 0 { var hostConfig HostConfig if err := job.ExportEnv(&hostConfig); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } // Validate the HostConfig binds. Make sure that: // 1) the source of a bind mount isn't / @@ -2212,8 +2088,7 @@ func (srv *Server) ContainerStart(job *engine.Job) engine.Status { // refuse to bind mount "/" to the container if source == "/" { - job.Errorf("Invalid bind mount '%s' : source can't be '/'", bind) - return engine.StatusErr + return job.Errorf("Invalid bind mount '%s' : source can't be '/'", bind) } // ensure the source exists on the host @@ -2221,22 +2096,19 @@ func (srv *Server) ContainerStart(job *engine.Job) engine.Status { if err != nil && os.IsNotExist(err) { err = os.MkdirAll(source, 0755) if err != nil { - job.Errorf("Could not create local directory '%s' for bind mount: %s!", source, err.Error()) - return engine.StatusErr + return job.Errorf("Could not create local directory '%s' for bind mount: %s!", source, err.Error()) } } } // Register any links from the host config before starting the container if err := srv.RegisterLinks(container, &hostConfig); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } container.hostConfig = &hostConfig container.ToDisk() } if err := container.Start(); err != nil { - job.Errorf("Cannot start container %s: %s", name, err) - return engine.StatusErr + return job.Errorf("Cannot start container %s: %s", name, err) } srv.LogEvent("start", container.ID, runtime.repositories.ImageName(container.Image)) @@ -2245,8 +2117,7 @@ func (srv *Server) ContainerStart(job *engine.Job) engine.Status { func (srv *Server) ContainerStop(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Usage: %s CONTAINER\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER\n", job.Name) } name := job.Args[0] t := job.GetenvInt("t") @@ -2255,21 +2126,18 @@ func (srv *Server) ContainerStop(job *engine.Job) engine.Status { } if container := srv.runtime.Get(name); container != nil { if err := container.Stop(int(t)); err != nil { - job.Errorf("Cannot stop container %s: %s\n", name, err) - return engine.StatusErr + return job.Errorf("Cannot stop container %s: %s\n", name, err) } srv.LogEvent("stop", container.ID, srv.runtime.repositories.ImageName(container.Image)) } else { - job.Errorf("No such container: %s\n", name) - return engine.StatusErr + return job.Errorf("No such container: %s\n", name) } return engine.StatusOK } func (srv *Server) ContainerWait(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Usage: %s", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s", job.Name) } name := job.Args[0] if container := srv.runtime.Get(name); container != nil { @@ -2277,41 +2145,34 @@ func (srv *Server) ContainerWait(job *engine.Job) engine.Status { job.Printf("%d\n", status) return engine.StatusOK } - job.Errorf("%s: no such container: %s", job.Name, name) - return engine.StatusErr + return job.Errorf("%s: no such container: %s", job.Name, name) } func (srv *Server) ContainerResize(job *engine.Job) engine.Status { if len(job.Args) != 3 { - job.Errorf("Not enough arguments. Usage: %s CONTAINER HEIGHT WIDTH\n", job.Name) - return engine.StatusErr + return job.Errorf("Not enough arguments. Usage: %s CONTAINER HEIGHT WIDTH\n", job.Name) } name := job.Args[0] height, err := strconv.Atoi(job.Args[1]) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } width, err := strconv.Atoi(job.Args[2]) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if container := srv.runtime.Get(name); container != nil { if err := container.Resize(height, width); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } func (srv *Server) ContainerAttach(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Usage: %s CONTAINER\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER\n", job.Name) } var ( @@ -2325,8 +2186,7 @@ func (srv *Server) ContainerAttach(job *engine.Job) engine.Status { container := srv.runtime.Get(name) if container == nil { - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } //logs @@ -2377,8 +2237,7 @@ func (srv *Server) ContainerAttach(job *engine.Job) engine.Status { //stream if stream { if container.State.IsGhost() { - job.Errorf("Impossible to attach to a ghost container") - return engine.StatusErr + return job.Errorf("Impossible to attach to a ghost container") } var ( @@ -2432,8 +2291,7 @@ func (srv *Server) ImageInspect(name string) (*Image, error) { func (srv *Server) JobInspect(job *engine.Job) engine.Status { // TODO: deprecate KIND/conflict if n := len(job.Args); n != 2 { - job.Errorf("Usage: %s CONTAINER|IMAGE KIND", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER|IMAGE KIND", job.Name) } var ( name = job.Args[0] @@ -2445,35 +2303,30 @@ func (srv *Server) JobInspect(job *engine.Job) engine.Status { ) if conflict && image != nil && container != nil { - job.Errorf("Conflict between containers and images") - return engine.StatusErr + return job.Errorf("Conflict between containers and images") } switch kind { case "image": if errImage != nil { - job.Error(errImage) - return engine.StatusErr + return job.Error(errImage) } object = image case "container": if errContainer != nil { - job.Error(errContainer) - return engine.StatusErr + return job.Error(errContainer) } object = &struct { *Container HostConfig *HostConfig }{container, container.hostConfig} default: - job.Errorf("Unknown kind: %s", kind) - return engine.StatusErr + return job.Errorf("Unknown kind: %s", kind) } b, err := json.Marshal(object) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } job.Stdout.Write(b) return engine.StatusOK @@ -2481,8 +2334,7 @@ func (srv *Server) JobInspect(job *engine.Job) engine.Status { func (srv *Server) ContainerCopy(job *engine.Job) engine.Status { if len(job.Args) != 2 { - job.Errorf("Usage: %s CONTAINER RESOURCE\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER RESOURCE\n", job.Name) } var ( @@ -2494,19 +2346,15 @@ func (srv *Server) ContainerCopy(job *engine.Job) engine.Status { data, err := container.Copy(resource) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if _, err := io.Copy(job.Stdout, data); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } - job.Errorf("No such container: %s", name) - return engine.StatusErr - + return job.Errorf("No such container: %s", name) } func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) { From 7b7f7e443637d8303f03316ee437012b71936c12 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 28 Jan 2014 03:26:24 +0000 Subject: [PATCH 284/364] add setSubEnv and getSubEnv Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 18 +++++++----------- engine/env.go | 22 ++++++++++++++++++++++ engine/job.go | 8 ++++++++ server.go | 3 +-- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/api.go b/api.go index ba8646599d..4d294667ac 100644 --- a/api.go +++ b/api.go @@ -345,11 +345,11 @@ func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Req return err } var ( - config = &Config{} + config engine.Env env engine.Env job = srv.Eng.Job("commit", r.Form.Get("container")) ) - if err := json.NewDecoder(r.Body).Decode(config); err != nil && err != io.EOF { + if err := config.Import(r.Body); err != nil { utils.Errorf("%s", err) } @@ -357,7 +357,7 @@ func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Req job.Setenv("tag", r.Form.Get("tag")) job.Setenv("author", r.Form.Get("author")) job.Setenv("comment", r.Form.Get("comment")) - job.SetenvJson("config", config) + job.SetenvSubEnv("config", &config) var id string job.Stdout.AddString(&id) @@ -704,18 +704,14 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r return fmt.Errorf("Missing parameter") } - // TODO: replace the buffer by job.AddEnv() var ( job = srv.Eng.Job("inspect", vars["name"], "container") - buffer = bytes.NewBuffer(nil) - c Container + c, err = job.Stdout.AddEnv() ) - job.Stdout.Add(buffer) - if err := job.Run(); err != nil { + if err != nil { return err } - - if err := json.Unmarshal(buffer.Bytes(), &c); err != nil { + if err = job.Run(); err != nil { return err } @@ -742,7 +738,7 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") - if !c.Config.Tty && version >= 1.6 { + if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version >= 1.6 { errStream = utils.NewStdWriter(outStream, utils.Stderr) outStream = utils.NewStdWriter(outStream, utils.Stdout) } else { diff --git a/engine/env.go b/engine/env.go index f93555a40b..f30e135555 100644 --- a/engine/env.go +++ b/engine/env.go @@ -86,6 +86,28 @@ func (env *Env) GetList(key string) []string { return l } +func (env *Env) GetSubEnv(key string) *Env { + sval := env.Get(key) + if sval == "" { + return nil + } + buf := bytes.NewBufferString(sval) + var sub Env + if err := sub.Decode(buf); err != nil { + return nil + } + return &sub +} + +func (env *Env) SetSubEnv(key string, sub *Env) error { + var buf bytes.Buffer + if err := sub.Encode(&buf); err != nil { + return err + } + env.Set(key, string(buf.Bytes())) + return nil +} + func (env *Env) GetJson(key string, iface interface{}) error { sval := env.Get(key) if sval == "" { diff --git a/engine/job.go b/engine/job.go index 34393fcab5..5447441beb 100644 --- a/engine/job.go +++ b/engine/job.go @@ -114,6 +114,14 @@ func (job *Job) SetenvBool(key string, value bool) { job.env.SetBool(key, value) } +func (job *Job) GetenvSubEnv(key string) *Env { + return job.env.GetSubEnv(key) +} + +func (job *Job) SetenvSubEnv(key string, value *Env) error { + return job.env.SetSubEnv(key, value) +} + func (job *Job) GetenvInt64(key string) int64 { return job.env.GetInt64(key) } diff --git a/server.go b/server.go index a6f869f977..2df7a8abc6 100644 --- a/server.go +++ b/server.go @@ -1660,8 +1660,7 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { } resolvConf, err := utils.GetResolvConf() if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { job.Errorf("WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v\n", defaultDns) From f556cd4186dfdb5e96b2e58b5caf9edbe7e2d7df Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 29 Jan 2014 19:26:54 +0000 Subject: [PATCH 285/364] move api to it's own package Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go => api/api.go | 185 ++++++++++++++------------- http_test.go => api/api_unit_test.go | 16 ++- api_unit_test.go | 19 --- commands.go | 11 +- docker/docker.go | 3 +- integration/api_test.go | 87 +++++-------- opts.go | 3 +- server.go | 3 +- 8 files changed, 159 insertions(+), 168 deletions(-) rename api.go => api/api.go (78%) rename http_test.go => api/api_unit_test.go (82%) delete mode 100644 api_unit_test.go diff --git a/api.go b/api/api.go similarity index 78% rename from api.go rename to api/api.go index 4d294667ac..61069445fc 100644 --- a/api.go +++ b/api/api.go @@ -1,4 +1,4 @@ -package docker +package api import ( "bufio" @@ -34,7 +34,7 @@ const ( DEFAULTUNIXSOCKET = "/var/run/docker.sock" ) -type HttpApiFunc func(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error +type HttpApiFunc func(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { conn, _, err := w.(http.Hijacker).Hijack() @@ -106,7 +106,20 @@ func getBoolParam(value string) (bool, error) { return ret, nil } -func matchesContentType(contentType, expectedType string) bool { +//TODO remove, used on < 1.5 in getContainersJSON +func displayablePorts(ports *engine.Table) string { + result := []string{} + for _, port := range ports.Data { + if port.Get("IP") == "" { + result = append(result, fmt.Sprintf("%d/%s", port.GetInt("PublicPort"), port.Get("Type"))) + } else { + result = append(result, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type"))) + } + } + return strings.Join(result, ", ") +} + +func MatchesContentType(contentType, expectedType string) bool { mimetype, _, err := mime.ParseMediaType(contentType) if err != nil { utils.Errorf("Error parsing media type: %s error: %s", contentType, err.Error()) @@ -114,10 +127,10 @@ func matchesContentType(contentType, expectedType string) bool { return err == nil && mimetype == expectedType } -func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postAuth(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { var ( authConfig, err = ioutil.ReadAll(r.Body) - job = srv.Eng.Job("auth") + job = eng.Job("auth") status string ) if err != nil { @@ -137,20 +150,20 @@ func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Reque return nil } -func getVersion(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getVersion(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") - srv.Eng.ServeHTTP(w, r) + eng.ServeHTTP(w, r) return nil } -func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersKill(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } if err := parseForm(r); err != nil { return err } - job := srv.Eng.Job("kill", vars["name"]) + job := eng.Job("kill", vars["name"]) if sig := r.Form.Get("signal"); sig != "" { job.Args = append(job.Args, sig) } @@ -161,11 +174,11 @@ func postContainersKill(srv *Server, version float64, w http.ResponseWriter, r * return nil } -func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getContainersExport(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - job := srv.Eng.Job("export", vars["name"]) + job := eng.Job("export", vars["name"]) job.Stdout.Add(w) if err := job.Run(); err != nil { return err @@ -173,7 +186,7 @@ func getContainersExport(srv *Server, version float64, w http.ResponseWriter, r return nil } -func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -181,7 +194,7 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http. var ( err error outs *engine.Table - job = srv.Eng.Job("images") + job = eng.Job("images") ) job.Setenv("filter", r.Form.Get("filter")) @@ -219,39 +232,39 @@ func getImagesJSON(srv *Server, version float64, w http.ResponseWriter, r *http. return nil } -func getImagesViz(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesViz(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if version > 1.6 { w.WriteHeader(http.StatusNotFound) return fmt.Errorf("This is now implemented in the client.") } - srv.Eng.ServeHTTP(w, r) + eng.ServeHTTP(w, r) return nil } -func getInfo(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getInfo(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") - srv.Eng.ServeHTTP(w, r) + eng.ServeHTTP(w, r) return nil } -func getEvents(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getEvents(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } w.Header().Set("Content-Type", "application/json") - var job = srv.Eng.Job("events", r.RemoteAddr) + var job = eng.Job("events", r.RemoteAddr) job.Stdout.Add(utils.NewWriteFlusher(w)) job.Setenv("since", r.Form.Get("since")) return job.Run() } -func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesHistory(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - var job = srv.Eng.Job("history", vars["name"]) + var job = eng.Job("history", vars["name"]) job.Stdout.Add(w) if err := job.Run(); err != nil { @@ -260,17 +273,17 @@ func getImagesHistory(srv *Server, version float64, w http.ResponseWriter, r *ht return nil } -func getContainersChanges(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getContainersChanges(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - var job = srv.Eng.Job("changes", vars["name"]) + var job = eng.Job("changes", vars["name"]) job.Stdout.Add(w) return job.Run() } -func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getContainersTop(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if version < 1.4 { return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.") } @@ -281,19 +294,19 @@ func getContainersTop(srv *Server, version float64, w http.ResponseWriter, r *ht return err } - job := srv.Eng.Job("top", vars["name"], r.Form.Get("ps_args")) + job := eng.Job("top", vars["name"], r.Form.Get("ps_args")) job.Stdout.Add(w) return job.Run() } -func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } var ( err error outs *engine.Table - job = srv.Eng.Job("containers") + job = eng.Job("containers") ) job.Setenv("all", r.Form.Get("all")) @@ -323,7 +336,7 @@ func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *h return nil } -func postImagesTag(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postImagesTag(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -331,7 +344,7 @@ func postImagesTag(srv *Server, version float64, w http.ResponseWriter, r *http. return fmt.Errorf("Missing parameter") } - job := srv.Eng.Job("tag", vars["name"], r.Form.Get("repo"), r.Form.Get("tag")) + job := eng.Job("tag", vars["name"], r.Form.Get("repo"), r.Form.Get("tag")) job.Setenv("force", r.Form.Get("force")) if err := job.Run(); err != nil { return err @@ -340,14 +353,14 @@ func postImagesTag(srv *Server, version float64, w http.ResponseWriter, r *http. return nil } -func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postCommit(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } var ( config engine.Env env engine.Env - job = srv.Eng.Job("commit", r.Form.Get("container")) + job = eng.Job("commit", r.Form.Get("container")) ) if err := config.Import(r.Body); err != nil { utils.Errorf("%s", err) @@ -369,7 +382,7 @@ func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Req } // Creates an image from Pull or from Import -func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postImagesCreate(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -399,12 +412,12 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht metaHeaders[k] = v } } - job = srv.Eng.Job("pull", r.Form.Get("fromImage"), tag) + job = eng.Job("pull", r.Form.Get("fromImage"), tag) job.SetenvBool("parallel", version > 1.3) job.SetenvJson("metaHeaders", metaHeaders) job.SetenvJson("authConfig", authConfig) } else { //import - job = srv.Eng.Job("import", r.Form.Get("fromSrc"), r.Form.Get("repo"), tag) + job = eng.Job("import", r.Form.Get("fromSrc"), r.Form.Get("repo"), tag) job.Stdin.Add(r.Body) } @@ -421,7 +434,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht return nil } -func getImagesSearch(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesSearch(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -445,7 +458,7 @@ func getImagesSearch(srv *Server, version float64, w http.ResponseWriter, r *htt } } - var job = srv.Eng.Job("search", r.Form.Get("term")) + var job = eng.Job("search", r.Form.Get("term")) job.SetenvJson("metaHeaders", metaHeaders) job.SetenvJson("authConfig", authConfig) job.Stdout.Add(w) @@ -453,7 +466,7 @@ func getImagesSearch(srv *Server, version float64, w http.ResponseWriter, r *htt return job.Run() } -func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postImagesInsert(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -464,7 +477,7 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht w.Header().Set("Content-Type", "application/json") } - job := srv.Eng.Job("insert", vars["name"], r.Form.Get("url"), r.Form.Get("path")) + job := eng.Job("insert", vars["name"], r.Form.Get("url"), r.Form.Get("path")) job.SetenvBool("json", version > 1.0) job.Stdout.Add(w) if err := job.Run(); err != nil { @@ -478,7 +491,7 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht return nil } -func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postImagesPush(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -512,7 +525,7 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http if version > 1.0 { w.Header().Set("Content-Type", "application/json") } - job := srv.Eng.Job("push", vars["name"]) + job := eng.Job("push", vars["name"]) job.SetenvJson("metaHeaders", metaHeaders) job.SetenvJson("authConfig", authConfig) job.SetenvBool("json", version > 1.0) @@ -528,31 +541,31 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http return nil } -func getImagesGet(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesGet(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } if version > 1.0 { w.Header().Set("Content-Type", "application/x-tar") } - job := srv.Eng.Job("image_export", vars["name"]) + job := eng.Job("image_export", vars["name"]) job.Stdout.Add(w) return job.Run() } -func postImagesLoad(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - job := srv.Eng.Job("load") +func postImagesLoad(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + job := eng.Job("load") job.Stdin.Add(r.Body) return job.Run() } -func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersCreate(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } var ( out engine.Env - job = srv.Eng.Job("create", r.Form.Get("name")) + job = eng.Job("create", r.Form.Get("name")) outWarnings []string outId string warnings = bytes.NewBuffer(nil) @@ -577,14 +590,14 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r return writeJSON(w, http.StatusCreated, out) } -func postContainersRestart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersRestart(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } if vars == nil { return fmt.Errorf("Missing parameter") } - job := srv.Eng.Job("restart", vars["name"]) + job := eng.Job("restart", vars["name"]) job.Setenv("t", r.Form.Get("t")) if err := job.Run(); err != nil { return err @@ -593,14 +606,14 @@ func postContainersRestart(srv *Server, version float64, w http.ResponseWriter, return nil } -func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func deleteContainers(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } if vars == nil { return fmt.Errorf("Missing parameter") } - job := srv.Eng.Job("container_delete", vars["name"]) + job := eng.Job("container_delete", vars["name"]) job.Setenv("removeVolume", r.Form.Get("v")) job.Setenv("removeLink", r.Form.Get("link")) if err := job.Run(); err != nil { @@ -610,29 +623,29 @@ func deleteContainers(srv *Server, version float64, w http.ResponseWriter, r *ht return nil } -func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func deleteImages(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } if vars == nil { return fmt.Errorf("Missing parameter") } - var job = srv.Eng.Job("image_delete", vars["name"]) + var job = eng.Job("image_delete", vars["name"]) job.Stdout.Add(w) job.SetenvBool("autoPrune", version > 1.1) return job.Run() } -func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersStart(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } name := vars["name"] - job := srv.Eng.Job("start", name) + job := eng.Job("start", name) // allow a nil body for backwards compatibility if r.Body != nil { - if matchesContentType(r.Header.Get("Content-Type"), "application/json") { + if MatchesContentType(r.Header.Get("Content-Type"), "application/json") { if err := job.DecodeEnv(r.Body); err != nil { return err } @@ -645,14 +658,14 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r return nil } -func postContainersStop(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersStop(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } if vars == nil { return fmt.Errorf("Missing parameter") } - job := srv.Eng.Job("stop", vars["name"]) + job := eng.Job("stop", vars["name"]) job.Setenv("t", r.Form.Get("t")) if err := job.Run(); err != nil { return err @@ -661,14 +674,14 @@ func postContainersStop(srv *Server, version float64, w http.ResponseWriter, r * return nil } -func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersWait(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } var ( env engine.Env status string - job = srv.Eng.Job("wait", vars["name"]) + job = eng.Job("wait", vars["name"]) ) job.Stdout.AddString(&status) if err := job.Run(); err != nil { @@ -683,20 +696,20 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r * return writeJSON(w, http.StatusOK, env) } -func postContainersResize(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersResize(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } if vars == nil { return fmt.Errorf("Missing parameter") } - if err := srv.Eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil { + if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil { return err } return nil } -func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersAttach(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -705,7 +718,7 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r } var ( - job = srv.Eng.Job("inspect", vars["name"], "container") + job = eng.Job("inspect", vars["name"], "container") c, err = job.Stdout.AddEnv() ) if err != nil { @@ -745,7 +758,7 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r errStream = outStream } - job = srv.Eng.Job("attach", vars["name"]) + job = eng.Job("attach", vars["name"]) job.Setenv("logs", r.Form.Get("logs")) job.Setenv("stream", r.Form.Get("stream")) job.Setenv("stdin", r.Form.Get("stdin")) @@ -761,7 +774,7 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r return nil } -func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func wsContainersAttach(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -769,13 +782,13 @@ func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r * return fmt.Errorf("Missing parameter") } - if err := srv.Eng.Job("inspect", vars["name"], "container").Run(); err != nil { + if err := eng.Job("inspect", vars["name"], "container").Run(); err != nil { return err } h := websocket.Handler(func(ws *websocket.Conn) { defer ws.Close() - job := srv.Eng.Job("attach", vars["name"]) + job := eng.Job("attach", vars["name"]) job.Setenv("logs", r.Form.Get("logs")) job.Setenv("stream", r.Form.Get("stream")) job.Setenv("stdin", r.Form.Get("stdin")) @@ -793,27 +806,27 @@ func wsContainersAttach(srv *Server, version float64, w http.ResponseWriter, r * return nil } -func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getContainersByName(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - var job = srv.Eng.Job("inspect", vars["name"], "container") + var job = eng.Job("inspect", vars["name"], "container") job.Stdout.Add(w) job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job return job.Run() } -func getImagesByName(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func getImagesByName(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - var job = srv.Eng.Job("inspect", vars["name"], "image") + var job = eng.Job("inspect", vars["name"], "image") job.Stdout.Add(w) job.SetenvBool("conflict", true) //conflict=true to detect conflict between containers and images in the job return job.Run() } -func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postBuild(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if version < 1.3 { return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.") } @@ -822,7 +835,7 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ authConfig = &auth.AuthConfig{} configFileEncoded = r.Header.Get("X-Registry-Config") configFile = &auth.ConfigFile{} - job = srv.Eng.Job("build") + job = eng.Job("build") ) // This block can be removed when API versions prior to 1.9 are deprecated. @@ -870,7 +883,7 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ return nil } -func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func postContainersCopy(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -892,7 +905,7 @@ func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r * copyData.Set("Resource", copyData.Get("Resource")[1:]) } - job := srv.Eng.Job("container_copy", vars["name"], copyData.Get("Resource")) + job := eng.Job("container_copy", vars["name"], copyData.Get("Resource")) job.Stdout.Add(w) if err := job.Run(); err != nil { utils.Errorf("%s", err.Error()) @@ -900,7 +913,7 @@ func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r * return nil } -func optionsHandler(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func optionsHandler(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.WriteHeader(http.StatusOK) return nil } @@ -910,7 +923,7 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request) { w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") } -func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, enableCors bool) http.HandlerFunc { +func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, enableCors bool, dockerVersion string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // log the request utils.Debugf("Calling %s %s", localMethod, localRoute) @@ -921,8 +934,8 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") { userAgent := strings.Split(r.Header.Get("User-Agent"), "/") - if len(userAgent) == 2 && userAgent[1] != VERSION { - utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], VERSION) + if len(userAgent) == 2 && userAgent[1] != dockerVersion { + utils.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion) } } version, err := strconv.ParseFloat(mux.Vars(r)["version"], 64) @@ -938,7 +951,7 @@ func makeHttpHandler(srv *Server, logging bool, localMethod string, localRoute s return } - if err := handlerFunc(srv, version, w, r, mux.Vars(r)); err != nil { + if err := handlerFunc(eng, version, w, r, mux.Vars(r)); err != nil { utils.Errorf("Error: %s", err) httpError(w, err) } @@ -971,7 +984,7 @@ func AttachProfiler(router *mux.Router) { router.HandleFunc("/debug/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP) } -func createRouter(srv *Server, logging, enableCors bool) (*mux.Router, error) { +func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion string) (*mux.Router, error) { r := mux.NewRouter() if os.Getenv("DEBUG") != "" { AttachProfiler(r) @@ -1032,7 +1045,7 @@ func createRouter(srv *Server, logging, enableCors bool) (*mux.Router, error) { localMethod := method // build the handler function - f := makeHttpHandler(srv, logging, localMethod, localRoute, localFct, enableCors) + f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, enableCors, dockerVersion) // add the new route if localRoute == "" { @@ -1050,8 +1063,8 @@ func createRouter(srv *Server, logging, enableCors bool) (*mux.Router, error) { // ServeRequest processes a single http request to the docker remote api. // FIXME: refactor this to be part of Server and not require re-creating a new // router each time. This requires first moving ListenAndServe into Server. -func ServeRequest(srv *Server, apiversion float64, w http.ResponseWriter, req *http.Request) error { - router, err := createRouter(srv, false, true) +func ServeRequest(eng *engine.Engine, apiversion float64, w http.ResponseWriter, req *http.Request) error { + router, err := createRouter(eng, false, true, "") if err != nil { return err } @@ -1093,8 +1106,8 @@ func ServeFd(addr string, handle http.Handler) error { // ListenAndServe sets up the required http.Server and gets it listening for // each addr passed in and does protocol specific checking. -func ListenAndServe(proto, addr string, srv *Server, logging, enableCors bool) error { - r, err := createRouter(srv, logging, enableCors) +func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors bool, dockerVersion string) error { + r, err := createRouter(eng, logging, enableCors, dockerVersion) if err != nil { return err } diff --git a/http_test.go b/api/api_unit_test.go similarity index 82% rename from http_test.go rename to api/api_unit_test.go index b9ecd6a203..2b3e76e75c 100644 --- a/http_test.go +++ b/api/api_unit_test.go @@ -1,4 +1,4 @@ -package docker +package api import ( "fmt" @@ -7,6 +7,20 @@ import ( "testing" ) +func TestJsonContentType(t *testing.T) { + if !MatchesContentType("application/json", "application/json") { + t.Fail() + } + + if !MatchesContentType("application/json; charset=utf-8", "application/json") { + t.Fail() + } + + if MatchesContentType("dockerapplication/json", "application/json") { + t.Fail() + } +} + func TestGetBoolParam(t *testing.T) { if ret, err := getBoolParam("true"); err != nil || !ret { t.Fatalf("true -> true, nil | got %t %s", ret, err) diff --git a/api_unit_test.go b/api_unit_test.go deleted file mode 100644 index 82095bd8b5..0000000000 --- a/api_unit_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package docker - -import ( - "testing" -) - -func TestJsonContentType(t *testing.T) { - if !matchesContentType("application/json", "application/json") { - t.Fail() - } - - if !matchesContentType("application/json; charset=utf-8", "application/json") { - t.Fail() - } - - if matchesContentType("dockerapplication/json", "application/json") { - t.Fail() - } -} diff --git a/commands.go b/commands.go index 5af02046a3..981ae3d0b8 100644 --- a/commands.go +++ b/commands.go @@ -8,6 +8,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/engine" @@ -79,7 +80,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { return nil } } - help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[unix://%s]: tcp://host:port to bind/connect to or unix://path/to/socket to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTUNIXSOCKET) + help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[unix://%s]: tcp://host:port to bind/connect to or unix://path/to/socket to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", api.DEFAULTUNIXSOCKET) for _, command := range [][]string{ {"attach", "Attach to a running container"}, {"build", "Build a container from a Dockerfile"}, @@ -2283,7 +2284,7 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b re := regexp.MustCompile("/+") path = re.ReplaceAllString(path, "/") - req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params) + req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", api.APIVERSION, path), params) if err != nil { return nil, -1, err } @@ -2360,7 +2361,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h re := regexp.MustCompile("/+") path = re.ReplaceAllString(path, "/") - req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in) + req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", api.APIVERSION, path), in) if err != nil { return err } @@ -2405,7 +2406,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h return fmt.Errorf("Error: %s", bytes.TrimSpace(body)) } - if matchesContentType(resp.Header.Get("Content-Type"), "application/json") { + if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") { return utils.DisplayJSONMessagesStream(resp.Body, out, cli.terminalFd, cli.isTerminal) } if _, err := io.Copy(out, resp.Body); err != nil { @@ -2424,7 +2425,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea re := regexp.MustCompile("/+") path = re.ReplaceAllString(path, "/") - req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil) + req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", api.APIVERSION, path), nil) if err != nil { return err } diff --git a/docker/docker.go b/docker/docker.go index e6c8076f36..2da9c1fa26 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -3,6 +3,7 @@ package main import ( "fmt" "github.com/dotcloud/docker" + "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/engine" flag "github.com/dotcloud/docker/pkg/mflag" "github.com/dotcloud/docker/sysinit" @@ -57,7 +58,7 @@ func main() { if defaultHost == "" || *flDaemon { // If we do not have a host, default to unix socket - defaultHost = fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET) + defaultHost = fmt.Sprintf("unix://%s", api.DEFAULTUNIXSOCKET) } flHosts.Set(defaultHost) } diff --git a/integration/api_test.go b/integration/api_test.go index b9ff079cb1..ad631f97e2 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "github.com/dotcloud/docker" + "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/utils" "io" @@ -21,7 +22,6 @@ import ( func TestGetVersion(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) var err error r := httptest.NewRecorder() @@ -31,7 +31,7 @@ func TestGetVersion(t *testing.T) { t.Fatal(err) } // FIXME getting the version should require an actual running Server - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -58,7 +58,6 @@ func TestGetVersion(t *testing.T) { func TestGetInfo(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) job := eng.Job("images") initialImages, err := job.Stdout.AddListTable() @@ -74,7 +73,7 @@ func TestGetInfo(t *testing.T) { } r := httptest.NewRecorder() - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -122,7 +121,7 @@ func TestGetEvents(t *testing.T) { r := httptest.NewRecorder() setTimeout(t, "", 500*time.Millisecond, func() { - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -146,7 +145,6 @@ func TestGetEvents(t *testing.T) { func TestGetImagesJSON(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) job := eng.Job("images") initialImages, err := job.Stdout.AddListTable() @@ -164,7 +162,7 @@ func TestGetImagesJSON(t *testing.T) { r := httptest.NewRecorder() - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -199,7 +197,7 @@ func TestGetImagesJSON(t *testing.T) { if err != nil { t.Fatal(err) } - if err := docker.ServeRequest(srv, docker.APIVERSION, r2, req2); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r2, req2); err != nil { t.Fatal(err) } assertHttpNotError(r2, t) @@ -232,7 +230,7 @@ func TestGetImagesJSON(t *testing.T) { t.Fatal(err) } - if err := docker.ServeRequest(srv, docker.APIVERSION, r3, req3); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r3, req3); err != nil { t.Fatal(err) } assertHttpNotError(r3, t) @@ -250,7 +248,6 @@ func TestGetImagesJSON(t *testing.T) { func TestGetImagesHistory(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) r := httptest.NewRecorder() @@ -258,7 +255,7 @@ func TestGetImagesHistory(t *testing.T) { if err != nil { t.Fatal(err) } - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -275,7 +272,6 @@ func TestGetImagesHistory(t *testing.T) { func TestGetImagesByName(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) req, err := http.NewRequest("GET", "/images/"+unitTestImageName+"/json", nil) if err != nil { @@ -283,7 +279,7 @@ func TestGetImagesByName(t *testing.T) { } r := httptest.NewRecorder() - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -300,7 +296,6 @@ func TestGetImagesByName(t *testing.T) { func TestGetContainersJSON(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) job := eng.Job("containers") job.SetenvBool("all", true) @@ -328,7 +323,7 @@ func TestGetContainersJSON(t *testing.T) { } r := httptest.NewRecorder() - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -347,7 +342,6 @@ func TestGetContainersJSON(t *testing.T) { func TestGetContainersExport(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) // Create a container and remove a file containerID := createTestContainer(eng, @@ -365,7 +359,7 @@ func TestGetContainersExport(t *testing.T) { if err != nil { t.Fatal(err) } - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -396,7 +390,6 @@ func TestGetContainersExport(t *testing.T) { func TestGetContainersChanges(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) // Create a container and remove a file containerID := createTestContainer(eng, @@ -413,7 +406,7 @@ func TestGetContainersChanges(t *testing.T) { if err != nil { t.Fatal(err) } - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -437,7 +430,6 @@ func TestGetContainersChanges(t *testing.T) { func TestGetContainersTop(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) containerID := createTestContainer(eng, &docker.Config{ @@ -481,7 +473,7 @@ func TestGetContainersTop(t *testing.T) { if err != nil { t.Fatal(err) } - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -514,7 +506,6 @@ func TestGetContainersTop(t *testing.T) { func TestGetContainersByName(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) // Create a container and remove a file containerID := createTestContainer(eng, @@ -530,7 +521,7 @@ func TestGetContainersByName(t *testing.T) { if err != nil { t.Fatal(err) } - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -565,7 +556,7 @@ func TestPostCommit(t *testing.T) { } r := httptest.NewRecorder() - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -585,7 +576,6 @@ func TestPostCommit(t *testing.T) { func TestPostContainersCreate(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) configJSON, err := json.Marshal(&docker.Config{ Image: unitTestImageID, @@ -602,7 +592,7 @@ func TestPostContainersCreate(t *testing.T) { } r := httptest.NewRecorder() - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -627,7 +617,6 @@ func TestPostContainersCreate(t *testing.T) { func TestPostContainersKill(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) containerID := createTestContainer(eng, &docker.Config{ @@ -652,7 +641,7 @@ func TestPostContainersKill(t *testing.T) { if err != nil { t.Fatal(err) } - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -667,7 +656,6 @@ func TestPostContainersKill(t *testing.T) { func TestPostContainersRestart(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) containerID := createTestContainer(eng, &docker.Config{ @@ -692,7 +680,7 @@ func TestPostContainersRestart(t *testing.T) { t.Fatal(err) } r := httptest.NewRecorder() - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -713,7 +701,6 @@ func TestPostContainersRestart(t *testing.T) { func TestPostContainersStart(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) containerID := createTestContainer( eng, @@ -735,7 +722,7 @@ func TestPostContainersStart(t *testing.T) { req.Header.Set("Content-Type", "application/json") r := httptest.NewRecorder() - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -752,7 +739,7 @@ func TestPostContainersStart(t *testing.T) { } r = httptest.NewRecorder() - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } // Starting an already started container should return an error @@ -767,7 +754,6 @@ func TestPostContainersStart(t *testing.T) { func TestRunErrorBindMountRootSource(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) containerID := createTestContainer( eng, @@ -791,7 +777,7 @@ func TestRunErrorBindMountRootSource(t *testing.T) { req.Header.Set("Content-Type", "application/json") r := httptest.NewRecorder() - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } if r.Code != http.StatusInternalServerError { @@ -803,7 +789,6 @@ func TestRunErrorBindMountRootSource(t *testing.T) { func TestPostContainersStop(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) containerID := createTestContainer(eng, &docker.Config{ @@ -829,7 +814,7 @@ func TestPostContainersStop(t *testing.T) { t.Fatal(err) } r := httptest.NewRecorder() - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -844,7 +829,6 @@ func TestPostContainersStop(t *testing.T) { func TestPostContainersWait(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) containerID := createTestContainer(eng, &docker.Config{ @@ -862,7 +846,7 @@ func TestPostContainersWait(t *testing.T) { if err != nil { t.Fatal(err) } - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -883,7 +867,6 @@ func TestPostContainersWait(t *testing.T) { func TestPostContainersAttach(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) containerID := createTestContainer(eng, &docker.Config{ @@ -921,7 +904,7 @@ func TestPostContainersAttach(t *testing.T) { t.Fatal(err) } - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r.ResponseRecorder, t) @@ -962,7 +945,6 @@ func TestPostContainersAttach(t *testing.T) { func TestPostContainersAttachStderr(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) containerID := createTestContainer(eng, &docker.Config{ @@ -1000,7 +982,7 @@ func TestPostContainersAttachStderr(t *testing.T) { t.Fatal(err) } - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r.ResponseRecorder, t) @@ -1044,7 +1026,6 @@ func TestPostContainersAttachStderr(t *testing.T) { func TestDeleteContainers(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) containerID := createTestContainer(eng, &docker.Config{ @@ -1058,7 +1039,7 @@ func TestDeleteContainers(t *testing.T) { t.Fatal(err) } r := httptest.NewRecorder() - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -1071,13 +1052,13 @@ func TestDeleteContainers(t *testing.T) { func TestOptionsRoute(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) + r := httptest.NewRecorder() req, err := http.NewRequest("OPTIONS", "/", nil) if err != nil { t.Fatal(err) } - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -1089,14 +1070,14 @@ func TestOptionsRoute(t *testing.T) { func TestGetEnabledCors(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) + r := httptest.NewRecorder() req, err := http.NewRequest("GET", "/version", nil) if err != nil { t.Fatal(err) } - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) @@ -1122,7 +1103,6 @@ func TestGetEnabledCors(t *testing.T) { func TestDeleteImages(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) initialImages := getImages(eng, t, true, "") @@ -1142,7 +1122,7 @@ func TestDeleteImages(t *testing.T) { } r := httptest.NewRecorder() - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } if r.Code != http.StatusConflict { @@ -1155,7 +1135,7 @@ func TestDeleteImages(t *testing.T) { } r2 := httptest.NewRecorder() - if err := docker.ServeRequest(srv, docker.APIVERSION, r2, req2); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r2, req2); err != nil { t.Fatal(err) } assertHttpNotError(r2, t) @@ -1180,7 +1160,6 @@ func TestDeleteImages(t *testing.T) { func TestPostContainersCopy(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) // Create a container and remove a file containerID := createTestContainer(eng, @@ -1208,7 +1187,7 @@ func TestPostContainersCopy(t *testing.T) { t.Fatal(err) } req.Header.Add("Content-Type", "application/json") - if err := docker.ServeRequest(srv, docker.APIVERSION, r, req); err != nil { + if err := api.ServeRequest(eng, api.APIVERSION, r, req); err != nil { t.Fatal(err) } assertHttpNotError(r, t) diff --git a/opts.go b/opts.go index 3119f9dd10..b1d71c491d 100644 --- a/opts.go +++ b/opts.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/utils" "os" "path/filepath" @@ -129,7 +130,7 @@ func ValidateEnv(val string) (string, error) { } func ValidateHost(val string) (string, error) { - host, err := utils.ParseHost(DEFAULTHTTPHOST, DEFAULTHTTPPORT, DEFAULTUNIXSOCKET, val) + host, err := utils.ParseHost(api.DEFAULTHTTPHOST, api.DEFAULTHTTPPORT, api.DEFAULTUNIXSOCKET, val) if err != nil { return val, err } diff --git a/server.go b/server.go index 2df7a8abc6..ce6024d919 100644 --- a/server.go +++ b/server.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/engine" @@ -122,7 +123,7 @@ func (srv *Server) ListenAndServe(job *engine.Job) engine.Status { protoAddrParts := strings.SplitN(protoAddr, "://", 2) go func() { log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1]) - chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, job.GetenvBool("Logging"), job.GetenvBool("EnableCors")) + chErrors <- api.ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), VERSION) }() } From 94c5f51c4a38422489f0f629d43958b282600111 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 29 Jan 2014 13:13:32 -0700 Subject: [PATCH 286/364] Add dummy "user.email" setting in our Dockerfile for smooth in-container merge commits Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 5cee5e67d0..8dbcee763e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -81,6 +81,9 @@ RUN gem install --no-rdoc --no-ri fpm --version 1.0.2 # Setup s3cmd config RUN /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY' > /.s3cfg +# Set user.email so crosbymichael's in-container merge commits go smoothly +RUN git config --global user.email 'docker-dummy@example.com' + VOLUME /var/lib/docker WORKDIR /go/src/github.com/dotcloud/docker From 54072dbbd6260a9d8a7249cae0bd17513f15c3fc Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 29 Jan 2014 20:31:49 +0000 Subject: [PATCH 287/364] fix ID -> Id api Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- commands.go | 22 +++++++++++----------- integration/api_test.go | 8 ++++---- integration/runtime_test.go | 4 ++-- integration/server_test.go | 6 +++--- server.go | 8 ++++---- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/commands.go b/commands.go index 083ca39bc5..b4f1298829 100644 --- a/commands.go +++ b/commands.go @@ -875,7 +875,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { } for _, out := range outs.Data { - outID := out.Get("ID") + outID := out.Get("Id") if !*quiet { if *noTrunc { fmt.Fprintf(w, "%s\t", outID) @@ -1167,7 +1167,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { } if filter != "" { - if filter == image.Get("ID") || filter == utils.TruncateID(image.Get("ID")) { + if filter == image.Get("Id") || filter == utils.TruncateID(image.Get("Id")) { startImage = image } @@ -1225,7 +1225,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { for _, repotag := range out.GetList("RepoTags") { repo, tag := utils.ParseRepositoryTag(repotag) - outID := out.Get("ID") + outID := out.Get("Id") if !*noTrunc { outID = utils.TruncateID(outID) } @@ -1251,12 +1251,12 @@ func (cli *DockerCli) WalkTree(noTrunc bool, images *engine.Table, byParent map[ for index, image := range images.Data { if index+1 == length { printNode(cli, noTrunc, image, prefix+"└─") - if subimages, exists := byParent[image.Get("ID")]; exists { + if subimages, exists := byParent[image.Get("Id")]; exists { cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) } } else { printNode(cli, noTrunc, image, prefix+"\u251C─") - if subimages, exists := byParent[image.Get("ID")]; exists { + if subimages, exists := byParent[image.Get("Id")]; exists { cli.WalkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) } } @@ -1264,7 +1264,7 @@ func (cli *DockerCli) WalkTree(noTrunc bool, images *engine.Table, byParent map[ } else { for _, image := range images.Data { printNode(cli, noTrunc, image, prefix+"└─") - if subimages, exists := byParent[image.Get("ID")]; exists { + if subimages, exists := byParent[image.Get("Id")]; exists { cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) } } @@ -1277,10 +1277,10 @@ func (cli *DockerCli) printVizNode(noTrunc bool, image *engine.Env, prefix strin parentID string ) if noTrunc { - imageID = image.Get("ID") + imageID = image.Get("Id") parentID = image.Get("ParentId") } else { - imageID = utils.TruncateID(image.Get("ID")) + imageID = utils.TruncateID(image.Get("Id")) parentID = utils.TruncateID(image.Get("ParentId")) } if parentID == "" { @@ -1297,9 +1297,9 @@ func (cli *DockerCli) printVizNode(noTrunc bool, image *engine.Env, prefix strin func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix string) { var imageID string if noTrunc { - imageID = image.Get("ID") + imageID = image.Get("Id") } else { - imageID = utils.TruncateID(image.Get("ID")) + imageID = utils.TruncateID(image.Get("Id")) } fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, utils.HumanSize(image.GetInt64("VirtualSize"))) @@ -1378,7 +1378,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { for _, out := range outs.Data { var ( - outID = out.Get("ID") + outID = out.Get("Id") outNames = out.GetList("Names") ) diff --git a/integration/api_test.go b/integration/api_test.go index 95cae47e15..6293f7bf30 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -204,7 +204,7 @@ func TestGetImagesJSON(t *testing.T) { } assertHttpNotError(r2, t) - images2 := engine.NewTable("ID", 0) + images2 := engine.NewTable("Id", 0) if _, err := images2.ReadListFrom(r2.Body.Bytes()); err != nil { t.Fatal(err) } @@ -215,7 +215,7 @@ func TestGetImagesJSON(t *testing.T) { found = false for _, img := range images2.Data { - if img.Get("ID") == unitTestImageID { + if img.Get("Id") == unitTestImageID { found = true break } @@ -237,7 +237,7 @@ func TestGetImagesJSON(t *testing.T) { } assertHttpNotError(r3, t) - images3 := engine.NewTable("ID", 0) + images3 := engine.NewTable("Id", 0) if _, err := images3.ReadListFrom(r3.Body.Bytes()); err != nil { t.Fatal(err) } @@ -339,7 +339,7 @@ func TestGetContainersJSON(t *testing.T) { if len(containers.Data) != beginLen+1 { t.Fatalf("Expected %d container, %d found (started with: %d)", beginLen+1, len(containers.Data), beginLen) } - if id := containers.Data[0].Get("ID"); id != containerID { + if id := containers.Data[0].Get("Id"); id != containerID { t.Fatalf("Container ID mismatch. Expected: %s, received: %s\n", containerID, id) } } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index e08643b36d..e6b4aab804 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -60,8 +60,8 @@ func cleanup(eng *engine.Engine, t *testing.T) error { t.Fatal(err) } for _, image := range images.Data { - if image.Get("ID") != unitTestImageID { - mkServerFromEngine(eng, t).DeleteImage(image.Get("ID"), false) + if image.Get("Id") != unitTestImageID { + mkServerFromEngine(eng, t).DeleteImage(image.Get("Id"), false) } } return nil diff --git a/integration/server_test.go b/integration/server_test.go index 2666d1d4fe..577ab5bb70 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -264,7 +264,7 @@ func TestRestartKillWait(t *testing.T) { } setTimeout(t, "Waiting on stopped container timedout", 5*time.Second, func() { - job = srv.Eng.Job("wait", outs.Data[0].Get("ID")) + job = srv.Eng.Job("wait", outs.Data[0].Get("Id")) var statusStr string job.Stdout.AddString(&statusStr) if err := job.Run(); err != nil { @@ -448,7 +448,7 @@ func TestRmi(t *testing.T) { } for _, image := range images.Data { - if strings.Contains(unitTestImageID, image.Get("ID")) { + if strings.Contains(unitTestImageID, image.Get("Id")) { continue } if image.GetList("RepoTags")[0] == ":" { @@ -626,7 +626,7 @@ func assertContainerList(srv *docker.Server, all bool, limit int, since, before return false } for i := 0; i < len(outs.Data); i++ { - if outs.Data[i].Get("ID") != expected[i] { + if outs.Data[i].Get("Id") != expected[i] { return false } } diff --git a/server.go b/server.go index a6731842cc..74d52afaa0 100644 --- a/server.go +++ b/server.go @@ -833,7 +833,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { delete(allImages, id) out.Set("ParentId", image.Parent) out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)}) - out.Set("ID", image.ID) + out.Set("Id", image.ID) out.SetInt64("Created", image.Created.Unix()) out.SetInt64("Size", image.Size) out.SetInt64("VirtualSize", image.getParentsSize(0)+image.Size) @@ -854,7 +854,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { out := &engine.Env{} out.Set("ParentId", image.Parent) out.SetList("RepoTags", []string{":"}) - out.Set("ID", image.ID) + out.Set("Id", image.ID) out.SetInt64("Created", image.Created.Unix()) out.SetInt64("Size", image.Size) out.SetInt64("VirtualSize", image.getParentsSize(0)+image.Size) @@ -940,7 +940,7 @@ func (srv *Server) ImageHistory(job *engine.Job) engine.Status { outs := engine.NewTable("Created", 0) err = image.WalkHistory(func(img *Image) error { out := &engine.Env{} - out.Set("ID", img.ID) + out.Set("Id", img.ID) out.SetInt64("Created", img.Created.Unix()) out.Set("CreatedBy", strings.Join(img.ContainerConfig.Cmd, " ")) out.SetList("Tags", lookupMap[img.ID]) @@ -1101,7 +1101,7 @@ func (srv *Server) Containers(job *engine.Job) engine.Status { } displayed++ out := &engine.Env{} - out.Set("ID", container.ID) + 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, " "))) From 5b82a1b726291d4aaaad797df5b29a74de28d318 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 30 Jan 2014 00:45:55 +0000 Subject: [PATCH 288/364] add tests Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- integration/server_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/integration/server_test.go b/integration/server_test.go index 2666d1d4fe..b0ad3d903b 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -114,6 +114,30 @@ func TestCreateRm(t *testing.T) { } +func TestCreateNumberHostname(t *testing.T) { + eng := NewTestEngine(t) + defer mkRuntimeFromEngine(eng, t).Nuke() + + config, _, _, err := docker.ParseRun([]string{"-h", "web.0", unitTestImageID, "echo test"}, nil) + if err != nil { + t.Fatal(err) + } + + createTestContainer(eng, config, t) +} + +func TestCreateNumberUsername(t *testing.T) { + eng := NewTestEngine(t) + defer mkRuntimeFromEngine(eng, t).Nuke() + + config, _, _, err := docker.ParseRun([]string{"-u", "1002", unitTestImageID, "echo test"}, nil) + if err != nil { + t.Fatal(err) + } + + createTestContainer(eng, config, t) +} + func TestCreateRmVolumes(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() From 187646127fa80a5ba39a53619b410eb2a13f0ffd Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 30 Jan 2014 00:56:42 +0000 Subject: [PATCH 289/364] fix convertion issues Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- container.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++ engine/env.go | 18 ---------------- engine/env_test.go | 26 ---------------------- engine/job.go | 4 ---- server.go | 18 +++++----------- 5 files changed, 59 insertions(+), 61 deletions(-) diff --git a/container.go b/container.go index c5df1f4b58..c98982b111 100644 --- a/container.go +++ b/container.go @@ -104,6 +104,44 @@ type Config struct { NetworkDisabled bool } +func ContainerConfigFromJob(job *engine.Job) *Config { + var config Config + config.Hostname = job.Getenv("Hostname") + config.Domainname = job.Getenv("Domainname") + config.User = job.Getenv("User") + config.Memory = job.GetenvInt64("Memory") + config.MemorySwap = job.GetenvInt64("MemorySwap") + config.CpuShares = job.GetenvInt64("CpuShares") + config.AttachStdin = job.GetenvBool("AttachStdin") + config.AttachStdout = job.GetenvBool("AttachStdout") + config.AttachStderr = job.GetenvBool("AttachStderr") + if PortSpecs := job.GetenvList("PortSpecs"); PortSpecs != nil { + config.PortSpecs = PortSpecs + } + job.GetenvJson("ExposedPorts", &config.ExposedPorts) + config.Tty = job.GetenvBool("Tty") + config.OpenStdin = job.GetenvBool("OpenStdin") + config.StdinOnce = job.GetenvBool("StdinOnce") + if Env := job.GetenvList("Env"); Env != nil { + config.Env = Env + } + if Cmd := job.GetenvList("Cmd"); Cmd != nil { + config.Cmd = Cmd + } + if Dns := job.GetenvList("Dns"); Dns != nil { + config.Dns = Dns + } + config.Image = job.Getenv("Image") + job.GetenvJson("Volumes", &config.Volumes) + config.VolumesFrom = job.Getenv("VolumesFrom") + config.WorkingDir = job.Getenv("WorkingDir") + if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil { + config.Entrypoint = Entrypoint + } + config.NetworkDisabled = job.GetenvBool("NetworkDisabled") + return &config +} + type HostConfig struct { Binds []string ContainerIDFile string @@ -114,6 +152,22 @@ type HostConfig struct { PublishAllPorts bool } +func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { + var hostConfig HostConfig + if Binds := job.GetenvList("Binds"); Binds != nil { + hostConfig.Binds = Binds + } + hostConfig.ContainerIDFile = job.Getenv("ContainerIDFile") + job.GetenvJson("LxcConf", &hostConfig.LxcConf) + hostConfig.Privileged = job.GetenvBool("Privileged") + job.GetenvJson("PortBindings", &hostConfig.PortBindings) + if Links := job.GetenvList("Links"); Links != nil { + hostConfig.Links = Links + } + hostConfig.PublishAllPorts = job.GetenvBool("PublishAllPorts") + return &hostConfig +} + type BindMap struct { SrcPath string DstPath string diff --git a/engine/env.go b/engine/env.go index f93555a40b..e000fe26b1 100644 --- a/engine/env.go +++ b/engine/env.go @@ -191,24 +191,6 @@ func (env *Env) WriteTo(dst io.Writer) (n int64, err error) { return 0, env.Encode(dst) } -func (env *Env) Export(dst interface{}) (err error) { - defer func() { - if err != nil { - err = fmt.Errorf("ExportEnv %s", err) - } - }() - var buf bytes.Buffer - // step 1: encode/marshal the env to an intermediary json representation - if err := env.Encode(&buf); err != nil { - return err - } - // step 2: decode/unmarshal the intermediary json into the destination object - if err := json.NewDecoder(&buf).Decode(dst); err != nil { - return err - } - return nil -} - func (env *Env) Import(src interface{}) (err error) { defer func() { if err != nil { diff --git a/engine/env_test.go b/engine/env_test.go index 24c5992dd0..419c47491e 100644 --- a/engine/env_test.go +++ b/engine/env_test.go @@ -84,32 +84,6 @@ func TestSetenvList(t *testing.T) { } } -func TestImportEnv(t *testing.T) { - type dummy struct { - DummyInt int - DummyStringArray []string - } - - job := mkJob(t, "dummy") - if err := job.ImportEnv(&dummy{42, []string{"foo", "bar"}}); err != nil { - t.Fatal(err) - } - - dmy := dummy{} - if err := job.ExportEnv(&dmy); err != nil { - t.Fatal(err) - } - - if dmy.DummyInt != 42 { - t.Fatalf("Expected 42, got %d", dmy.DummyInt) - } - - if len(dmy.DummyStringArray) != 2 || dmy.DummyStringArray[0] != "foo" || dmy.DummyStringArray[1] != "bar" { - t.Fatalf("Expected {foo, bar}, got %v", dmy.DummyStringArray) - } - -} - func TestEnviron(t *testing.T) { job := mkJob(t, "dummy") job.Setenv("foo", "bar") diff --git a/engine/job.go b/engine/job.go index 179b2ebdda..181eaa80c7 100644 --- a/engine/job.go +++ b/engine/job.go @@ -164,10 +164,6 @@ func (job *Job) EncodeEnv(dst io.Writer) error { return job.env.Encode(dst) } -func (job *Job) ExportEnv(dst interface{}) (err error) { - return job.env.Export(dst) -} - func (job *Job) ImportEnv(src interface{}) (err error) { return job.env.Import(src) } diff --git a/server.go b/server.go index a6731842cc..5a3b999c43 100644 --- a/server.go +++ b/server.go @@ -1742,11 +1742,7 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { job.Printf("Usage: %s", job.Name) return engine.StatusErr } - var config Config - if err := job.ExportEnv(&config); err != nil { - job.Error(err) - return engine.StatusErr - } + config := ContainerConfigFromJob(job) if config.Memory != 0 && config.Memory < 524288 { job.Errorf("Minimum memory limit allowed is 512k") return engine.StatusErr @@ -1769,7 +1765,7 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { config.Dns = defaultDns } - container, buildWarnings, err := srv.runtime.Create(&config, name) + container, buildWarnings, err := srv.runtime.Create(config, name) if err != nil { if srv.runtime.graph.IsNotExist(err) { _, tag := utils.ParseRepositoryTag(config.Image) @@ -2196,11 +2192,7 @@ func (srv *Server) ContainerStart(job *engine.Job) engine.Status { } // If no environment was set, then no hostconfig was passed. if len(job.Environ()) > 0 { - var hostConfig HostConfig - if err := job.ExportEnv(&hostConfig); err != nil { - job.Error(err) - return engine.StatusErr - } + hostConfig := ContainerHostConfigFromJob(job) // Validate the HostConfig binds. Make sure that: // 1) the source of a bind mount isn't / // The bind mount "/:/foo" isn't allowed. @@ -2227,11 +2219,11 @@ func (srv *Server) ContainerStart(job *engine.Job) engine.Status { } } // Register any links from the host config before starting the container - if err := srv.RegisterLinks(container, &hostConfig); err != nil { + if err := srv.RegisterLinks(container, hostConfig); err != nil { job.Error(err) return engine.StatusErr } - container.hostConfig = &hostConfig + container.hostConfig = hostConfig container.ToDisk() } if err := container.Start(); err != nil { From c914abaf15634d8039927176dfc5ff3c765f30d0 Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Wed, 29 Jan 2014 16:49:42 -0800 Subject: [PATCH 290/364] Prevent Tests from creating users on Prod Index The integration tests had previously used the environment variable DOCKER_INDEX_URL but it was apparently removed several months ago. Change the integration auth tests to specify the ServerAddress field of the AuthConfig struct to use the staging deployment of the index. Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) --- integration/auth_test.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/integration/auth_test.go b/integration/auth_test.go index 07559c01cf..c5bdabace2 100644 --- a/integration/auth_test.go +++ b/integration/auth_test.go @@ -3,6 +3,7 @@ package docker import ( "crypto/rand" "encoding/hex" + "fmt" "github.com/dotcloud/docker/auth" "os" "strings" @@ -17,7 +18,12 @@ import ( func TestLogin(t *testing.T) { os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com") defer os.Setenv("DOCKER_INDEX_URL", "") - authConfig := &auth.AuthConfig{Username: "unittester", Password: "surlautrerivejetattendrai", Email: "noise+unittester@dotcloud.com"} + authConfig := &auth.AuthConfig{ + Username: "unittester", + Password: "surlautrerivejetattendrai", + Email: "noise+unittester@docker.com", + ServerAddress: "https://indexstaging-docker.dotcloud.com/v1/", + } status, err := auth.Login(authConfig, nil) if err != nil { t.Fatal(err) @@ -28,8 +34,6 @@ func TestLogin(t *testing.T) { } func TestCreateAccount(t *testing.T) { - os.Setenv("DOCKER_INDEX_URL", "https://indexstaging-docker.dotcloud.com") - defer os.Setenv("DOCKER_INDEX_URL", "") tokenBuffer := make([]byte, 16) _, err := rand.Read(tokenBuffer) if err != nil { @@ -37,13 +41,20 @@ func TestCreateAccount(t *testing.T) { } token := hex.EncodeToString(tokenBuffer)[:12] username := "ut" + token - authConfig := &auth.AuthConfig{Username: username, Password: "test42", Email: "docker-ut+" + token + "@example.com"} + authConfig := &auth.AuthConfig{ + Username: username, + Password: "test42", + Email: fmt.Sprintf("docker-ut+%s@example.com", token), + ServerAddress: "https://indexstaging-docker.dotcloud.com/v1/", + } status, err := auth.Login(authConfig, nil) if err != nil { t.Fatal(err) } - expectedStatus := "Account created. Please use the confirmation link we sent" + - " to your e-mail to activate it." + expectedStatus := fmt.Sprintf( + "Account created. Please see the documentation of the registry %s for instructions how to activate it.", + authConfig.ServerAddress, + ) if status != expectedStatus { t.Fatalf("Expected status: \"%s\", found \"%s\" instead.", expectedStatus, status) } From 4dab34c2045b7bcc735227867126f0796cbb09c1 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Wed, 29 Jan 2014 17:41:21 -0800 Subject: [PATCH 291/364] contrib: systemd: make socket-activation Restart=always Do as was done to f09a78cd219b24d4308034c8dd13410cfe5fbec7 in the socket-activation example. Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- contrib/init/systemd/socket-activation/docker.service | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/init/systemd/socket-activation/docker.service b/contrib/init/systemd/socket-activation/docker.service index 4ab92dfef8..c795f9c3b4 100644 --- a/contrib/init/systemd/socket-activation/docker.service +++ b/contrib/init/systemd/socket-activation/docker.service @@ -5,6 +5,7 @@ After=network.target [Service] ExecStart=/usr/bin/docker -d -H fd:// +Restart=on-failure [Install] WantedBy=multi-user.target From 7d95ce6ddda89a392b94c6dcfcb7774b2a19cf1a Mon Sep 17 00:00:00 2001 From: Piergiuliano Bossi Date: Wed, 29 Jan 2014 23:07:23 -0500 Subject: [PATCH 292/364] Fix typo Docker-DCO-1.1-Signed-off-by: Piergiuliano Bossi (github: thinkingbox) --- docs/sources/installation/ubuntulinux.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index d5e4a248ba..4f4c89386f 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -224,7 +224,7 @@ Docker and UFW ^^^^^^^^^^^^^^ Docker uses a bridge to manage container networking. By default, UFW drops all -`forwarding` traffic. As a result will you need to enable UFW forwarding: +`forwarding` traffic. As a result you will need to enable UFW forwarding: .. code-block:: bash From 626a2e1112b6e802415f80ff7a3682296636f55a Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Thu, 30 Jan 2014 20:53:27 +1000 Subject: [PATCH 293/364] Add troubleshooting for missing cgroups on mint 16 #3602 Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/installation/ubuntulinux.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 4f4c89386f..3d6ee6415d 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -217,6 +217,15 @@ To install the latest version of docker, use the standard ``apt-get`` method: # install the latest sudo apt-get install lxc-docker +Troubleshooting +^^^^^^^^^^^^^^^ + +On Linux Mint, the ``cgroups-lite`` package is not installed by default. +Before Docker will work correctly, you will need to install this via: + +.. code-block:: bash + + sudo apt-get update && sudo apt-get install cgroups-lite .. _ufw: From fc1169a220196b78b73d5c1874d3c7bdc38d9fe3 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 30 Jan 2014 16:40:53 +0100 Subject: [PATCH 294/364] pkg/mount: Add "private" flag This allows "mount --make-private" functionallity. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/mount/flags_linux.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/mount/flags_linux.go b/pkg/mount/flags_linux.go index 6f4c7acffa..483f12904d 100644 --- a/pkg/mount/flags_linux.go +++ b/pkg/mount/flags_linux.go @@ -38,6 +38,7 @@ func parseOptions(options string) (int, string) { "nodiratime": {false, syscall.MS_NODIRATIME}, "bind": {false, syscall.MS_BIND}, "rbind": {false, syscall.MS_BIND | syscall.MS_REC}, + "private": {false, syscall.MS_PRIVATE}, "relatime": {false, syscall.MS_RELATIME}, "norelatime": {true, syscall.MS_RELATIME}, "strictatime": {false, syscall.MS_STRICTATIME}, From fab19d197c6cc362182f6cee4d0a6e65e799875f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 30 Jan 2014 16:43:53 +0100 Subject: [PATCH 295/364] Separate out graphdriver mount and container root This separates out the directory as returned from the graphdriver (the "base" fs) from the root filesystem of the live container. This is necessary as the "diff" operation needs access to the base filesystem without all the mounts that the running container needs (/.dockerinit, volumes, etc). We change container in the following way: Container.RootfsPath() returns the the directory which will be used as the root in a running container. It is always of the form "/var/lib/docker/container//root" and is a private bind mount to the base filesystem. It is only available while the container is running. Container.BasefsPath() returns the raw directory from the graph driver without the container runtime mounts. It is availible whenever the container is mounted (in between a container.Mount()/Unmount() pair, which are properly refcounted). This fixes issue #3840 Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- buildfile.go | 2 +- container.go | 102 +++++++++++++++++++++++--------------- integration/utils_test.go | 2 +- runtime.go | 12 ++--- 4 files changed, 69 insertions(+), 49 deletions(-) diff --git a/buildfile.go b/buildfile.go index 89afccebbd..3b45057f2b 100644 --- a/buildfile.go +++ b/buildfile.go @@ -328,7 +328,7 @@ func (b *buildFile) checkPathForAddition(orig string) error { func (b *buildFile) addContext(container *Container, orig, dest string) error { var ( origPath = path.Join(b.contextPath, orig) - destPath = path.Join(container.RootfsPath(), dest) + destPath = path.Join(container.BasefsPath(), dest) ) // Preserve the trailing '/' if strings.HasSuffix(dest, "/") { diff --git a/container.go b/container.go index c5df1f4b58..5006faafd1 100644 --- a/container.go +++ b/container.go @@ -34,7 +34,7 @@ var ( type Container struct { sync.Mutex root string // Path to the "home" of the container, including metadata. - rootfs string // Path to the root filesystem of the container. + basefs string // Path to the graphdriver mountpoint ID string @@ -208,7 +208,7 @@ func (container *Container) Inject(file io.Reader, pth string) error { defer container.Unmount() // Return error if path exists - destPath := path.Join(container.RootfsPath(), pth) + destPath := path.Join(container.basefs, pth) if _, err := os.Stat(destPath); err == nil { // Since err is nil, the path could be stat'd and it exists return fmt.Errorf("%s exists", pth) @@ -220,7 +220,7 @@ func (container *Container) Inject(file io.Reader, pth string) error { } // Make sure the directory exists - if err := os.MkdirAll(path.Join(container.RootfsPath(), path.Dir(pth)), 0755); err != nil { + if err := os.MkdirAll(path.Join(container.basefs, path.Dir(pth)), 0755); err != nil { return err } @@ -649,11 +649,9 @@ func (container *Container) Start() (err error) { return err } - root := container.RootfsPath() - if container.Config.WorkingDir != "" { container.Config.WorkingDir = path.Clean(container.Config.WorkingDir) - if err := os.MkdirAll(path.Join(root, container.Config.WorkingDir), 0755); err != nil { + if err := os.MkdirAll(path.Join(container.basefs, container.Config.WorkingDir), 0755); err != nil { return nil } } @@ -663,6 +661,23 @@ func (container *Container) Start() (err error) { return err } + // Setup the root fs as a bind mount of the base fs + root := container.RootfsPath() + if err := os.MkdirAll(root, 0755); err != nil && !os.IsExist(err) { + return nil + } + + // Create a bind mount of the base fs as a place where we can add mounts + // without affecting the ability to access the base fs + if err := mount.Mount(container.basefs, root, "none", "bind,rw"); err != nil { + return err + } + + // Make sure the root fs is private so the mounts here don't propagate to basefs + if err := mount.ForceMount(root, root, "none", "private"); err != nil { + return err + } + // Mount docker specific files into the containers root fs if err := mount.Mount(runtime.sysInitPath, path.Join(root, "/.dockerinit"), "none", "bind,ro"); err != nil { return err @@ -849,8 +864,8 @@ func (container *Container) createVolumes() error { container.VolumesRW[volPath] = srcRW // Create the mountpoint - volPath = path.Join(container.RootfsPath(), volPath) - rootVolPath, err := utils.FollowSymlinkInScope(volPath, container.RootfsPath()) + volPath = path.Join(container.basefs, volPath) + rootVolPath, err := utils.FollowSymlinkInScope(volPath, container.basefs) if err != nil { return err } @@ -939,7 +954,7 @@ func (container *Container) applyExternalVolumes() error { if _, exists := container.Volumes[volPath]; exists { continue } - if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil { + if err := os.MkdirAll(path.Join(container.basefs, volPath), 0755); err != nil { return err } container.Volumes[volPath] = id @@ -1206,6 +1221,30 @@ func (container *Container) cleanup() { } } + var ( + root = container.RootfsPath() + mounts = []string{ + root, + path.Join(root, "/.dockerinit"), + path.Join(root, "/.dockerenv"), + path.Join(root, "/etc/resolv.conf"), + } + ) + + if container.HostnamePath != "" && container.HostsPath != "" { + mounts = append(mounts, path.Join(root, "/etc/hostname"), path.Join(root, "/etc/hosts")) + } + + for r := range container.Volumes { + mounts = append(mounts, path.Join(root, r)) + } + + for i := len(mounts) - 1; i >= 0; i-- { + if lastError := mount.Unmount(mounts[i]); lastError != nil { + log.Printf("Failed to umount %v: %v", mounts[i], lastError) + } + } + if err := container.Unmount(); err != nil { log.Printf("%v: Failed to umount filesystem: %v", container.ID, err) } @@ -1309,7 +1348,7 @@ func (container *Container) Export() (archive.Archive, error) { return nil, err } - archive, err := archive.Tar(container.RootfsPath(), archive.Uncompressed) + archive, err := archive.Tar(container.basefs, archive.Uncompressed) if err != nil { return nil, err } @@ -1347,32 +1386,6 @@ func (container *Container) GetImage() (*Image, error) { } func (container *Container) Unmount() error { - var ( - err error - root = container.RootfsPath() - mounts = []string{ - path.Join(root, "/.dockerinit"), - path.Join(root, "/.dockerenv"), - path.Join(root, "/etc/resolv.conf"), - } - ) - - if container.HostnamePath != "" && container.HostsPath != "" { - mounts = append(mounts, path.Join(root, "/etc/hostname"), path.Join(root, "/etc/hosts")) - } - - for r := range container.Volumes { - mounts = append(mounts, path.Join(root, r)) - } - - for i := len(mounts) - 1; i >= 0; i-- { - if lastError := mount.Unmount(mounts[i]); lastError != nil { - err = fmt.Errorf("Failed to umount %v: %v", mounts[i], lastError) - } - } - if err != nil { - return err - } return container.runtime.Unmount(container) } @@ -1409,8 +1422,15 @@ func (container *Container) EnvConfigPath() (string, error) { } // This method must be exported to be used from the lxc template +// This directory is only usable when the container is running func (container *Container) RootfsPath() string { - return container.rootfs + return path.Join(container.root, "root") +} + +// This is the stand-alone version of the root fs, without any additional mounts. +// This directory is usable whenever the container is mounted (and not unmounted) +func (container *Container) BasefsPath() string { + return container.basefs } func validateID(id string) error { @@ -1445,14 +1465,14 @@ func (container *Container) GetSize() (int64, int64) { } else { changes, _ := container.Changes() if changes != nil { - sizeRw = archive.ChangesSize(container.RootfsPath(), changes) + sizeRw = archive.ChangesSize(container.basefs, changes) } else { sizeRw = -1 } } - if _, err = os.Stat(container.RootfsPath()); err != nil { - if sizeRootfs, err = utils.TreeSize(container.RootfsPath()); err != nil { + if _, err = os.Stat(container.basefs); err != nil { + if sizeRootfs, err = utils.TreeSize(container.basefs); err != nil { sizeRootfs = -1 } } @@ -1464,7 +1484,7 @@ func (container *Container) Copy(resource string) (archive.Archive, error) { return nil, err } var filter []string - basePath := path.Join(container.RootfsPath(), resource) + basePath := path.Join(container.basefs, resource) stat, err := os.Stat(basePath) if err != nil { container.Unmount() diff --git a/integration/utils_test.go b/integration/utils_test.go index d7a2814472..2eff13c81d 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -75,7 +75,7 @@ func containerFileExists(eng *engine.Engine, id, dir string, t utils.Fataler) bo t.Fatal(err) } defer c.Unmount() - if _, err := os.Stat(path.Join(c.RootfsPath(), dir)); err != nil { + if _, err := os.Stat(path.Join(c.BasefsPath(), dir)); err != nil { if os.IsNotExist(err) { return false } diff --git a/runtime.go b/runtime.go index 3d47a50398..51e90681bc 100644 --- a/runtime.go +++ b/runtime.go @@ -131,12 +131,12 @@ func (runtime *Runtime) Register(container *Container) error { } // Get the root filesystem from the driver - rootfs, err := runtime.driver.Get(container.ID) + basefs, err := runtime.driver.Get(container.ID) if err != nil { return fmt.Errorf("Error getting container filesystem %s from driver %s: %s", container.ID, runtime.driver, err) } defer runtime.driver.Put(container.ID) - container.rootfs = rootfs + container.basefs = basefs container.runtime = runtime @@ -764,11 +764,11 @@ func (runtime *Runtime) Mount(container *Container) error { if err != nil { return fmt.Errorf("Error getting container %s from driver %s: %s", container.ID, runtime.driver, err) } - if container.rootfs == "" { - container.rootfs = dir - } else if container.rootfs != dir { + if container.basefs == "" { + container.basefs = dir + } else if container.basefs != dir { return fmt.Errorf("Error: driver %s is returning inconsistent paths for container %s ('%s' then '%s')", - runtime.driver, container.ID, container.rootfs, dir) + runtime.driver, container.ID, container.basefs, dir) } return nil } From cc382ec62848855f6fe7302fe7a37ac62af35a9c Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 30 Jan 2014 11:03:25 -0600 Subject: [PATCH 296/364] network: insert masq rule This fixes IP masquerading on systems with reject rules at the end of the POSTROUTING table, by inserting the rule at the beginning of the table instead of adding it at the end. Docker-DCO-1.1-Signed-off-by: Josh Poimboeuf (github: jpoimboe) --- network.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network.go b/network.go index c72ea12055..d9771ac008 100644 --- a/network.go +++ b/network.go @@ -327,7 +327,7 @@ func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { natArgs := []string{"POSTROUTING", "-t", "nat", "-s", addr.String(), "!", "-d", addr.String(), "-j", "MASQUERADE"} if !iptables.Exists(natArgs...) { - if output, err := iptables.Raw(append([]string{"-A"}, natArgs...)...); err != nil { + if output, err := iptables.Raw(append([]string{"-I"}, natArgs...)...); err != nil { return nil, fmt.Errorf("Unable to enable network bridge NAT: %s", err) } else if len(output) != 0 { return nil, fmt.Errorf("Error iptables postrouting: %s", output) From 238dba831a74c89fe8f5aabc836e6b75da577c97 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 30 Jan 2014 11:51:25 -0700 Subject: [PATCH 297/364] Make unclejack the official Vagrantfile maintainer Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 4a6c0ec22c..895fba563a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6,4 +6,4 @@ Michael Crosby (@crosbymichael) api.go: Victor Vieux (@vieux) Dockerfile: Tianon Gravi (@tianon) Makefile: Tianon Gravi (@tianon) -Vagrantfile: Daniel Mizyrycki (@mzdaniel) +Vagrantfile: Cristian Staretu (@unclejack) From b111fc364611d755ac791c5c51c82c5754fe545b Mon Sep 17 00:00:00 2001 From: Chia-liang Kao Date: Fri, 31 Jan 2014 01:42:28 +0800 Subject: [PATCH 298/364] Use lxc-stop -k when lxc-kill is not found lxc-kill was removed in lxc/lxc@33ddfc2 Docker-DCO-1.1-Signed-off-by: Chia-liang Kao (github: clkao) --- execdriver/lxc/driver.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 4e8f586f82..054f16a649 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -212,7 +212,16 @@ func (d *driver) version() string { } func (d *driver) kill(c *execdriver.Command, sig int) error { - output, err := exec.Command("lxc-kill", "-n", c.ID, strconv.Itoa(sig)).CombinedOutput() + var ( + err error + output []byte + ) + _, err = exec.LookPath("lxc-kill") + if err == nil { + output, err = exec.Command("lxc-kill", "-n", c.ID, strconv.Itoa(sig)).CombinedOutput() + } else { + output, err = exec.Command("lxc-stop", "-k", "-n", c.ID, strconv.Itoa(sig)).CombinedOutput() + } if err != nil { return fmt.Errorf("Err: %s Output: %s", err, output) } From c8d1596902704076c8d85a30e80f90f8efb3e175 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 30 Jan 2014 12:27:45 -0700 Subject: [PATCH 299/364] Fix fun Travis DCO check YAML parsing issues (especially with commit messages that start with any kind of whitespace, like this one intentionally does) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/travis/dco.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/hack/travis/dco.py b/hack/travis/dco.py index d80d528f9a..f873940815 100755 --- a/hack/travis/dco.py +++ b/hack/travis/dco.py @@ -5,7 +5,7 @@ import yaml from env import commit_range -commit_format = '-%n hash: "%h"%n author: %aN <%aE>%n message: |%n%w(0,2,2)%B' +commit_format = '-%n hash: "%h"%n author: %aN <%aE>%n message: |%n%w(0,2,2).%B' gitlog = subprocess.check_output([ 'git', 'log', '--reverse', @@ -24,6 +24,11 @@ p = re.compile(r'^{0} ([^<]+) <([^<>@]+@[^<>]+)> \(github: (\S+)\)$'.format(re.e failed_commits = 0 for commit in commits: + commit['message'] = commit['message'][1:] + # trim off our '.' that exists just to prevent fun YAML parsing issues + # see https://github.com/dotcloud/docker/pull/3836#issuecomment-33723094 + # and https://travis-ci.org/dotcloud/docker/builds/17926783 + commit['stat'] = subprocess.check_output([ 'git', 'log', '--format=format:', '--max-count=1', '--name-status', commit['hash'], '--', From 9261511aa509423432f4e22f6235719deadc0969 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 30 Jan 2014 20:45:32 +0000 Subject: [PATCH 300/364] refactor all *FromJob functions Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- config.go | 26 ++++++++++++----------- container.go | 58 ++++++++++++++++++++++++++++------------------------ server.go | 3 +-- 3 files changed, 46 insertions(+), 41 deletions(-) diff --git a/config.go b/config.go index aad5d50fc0..ac34589640 100644 --- a/config.go +++ b/config.go @@ -23,29 +23,31 @@ type DaemonConfig struct { // ConfigFromJob creates and returns a new DaemonConfig object // by parsing the contents of a job's environment. -func ConfigFromJob(job *engine.Job) *DaemonConfig { - var config DaemonConfig - config.Pidfile = job.Getenv("Pidfile") - config.Root = job.Getenv("Root") - config.AutoRestart = job.GetenvBool("AutoRestart") +func DaemonConfigFromJob(job *engine.Job) *DaemonConfig { + config := &DaemonConfig{ + Pidfile: job.Getenv("Pidfile"), + Root: job.Getenv("Root"), + AutoRestart: job.GetenvBool("AutoRestart"), + EnableIptables: job.GetenvBool("EnableIptables"), + EnableIpForward: job.GetenvBool("EnableIpForward"), + BridgeIp: job.Getenv("BridgeIp"), + DefaultIp: net.ParseIP(job.Getenv("DefaultIp")), + InterContainerCommunication: job.GetenvBool("InterContainerCommunication"), + GraphDriver: job.Getenv("GraphDriver"), + } if dns := job.GetenvList("Dns"); dns != nil { config.Dns = dns } - config.EnableIptables = job.GetenvBool("EnableIptables") - config.EnableIpForward = job.GetenvBool("EnableIpForward") if br := job.Getenv("BridgeIface"); br != "" { config.BridgeIface = br } else { config.BridgeIface = DefaultNetworkBridge } - config.BridgeIp = job.Getenv("BridgeIp") - config.DefaultIp = net.ParseIP(job.Getenv("DefaultIp")) - config.InterContainerCommunication = job.GetenvBool("InterContainerCommunication") - config.GraphDriver = job.Getenv("GraphDriver") if mtu := job.GetenvInt("Mtu"); mtu != -1 { config.Mtu = mtu } else { config.Mtu = DefaultNetworkMtu } - return &config + + return config } diff --git a/container.go b/container.go index c98982b111..95e81e2063 100644 --- a/container.go +++ b/container.go @@ -105,23 +105,29 @@ type Config struct { } func ContainerConfigFromJob(job *engine.Job) *Config { - var config Config - config.Hostname = job.Getenv("Hostname") - config.Domainname = job.Getenv("Domainname") - config.User = job.Getenv("User") - config.Memory = job.GetenvInt64("Memory") - config.MemorySwap = job.GetenvInt64("MemorySwap") - config.CpuShares = job.GetenvInt64("CpuShares") - config.AttachStdin = job.GetenvBool("AttachStdin") - config.AttachStdout = job.GetenvBool("AttachStdout") - config.AttachStderr = job.GetenvBool("AttachStderr") + config := &Config{ + Hostname: job.Getenv("Hostname"), + Domainname: job.Getenv("Domainname"), + User: job.Getenv("User"), + Memory: job.GetenvInt64("Memory"), + MemorySwap: job.GetenvInt64("MemorySwap"), + CpuShares: job.GetenvInt64("CpuShares"), + AttachStdin: job.GetenvBool("AttachStdin"), + AttachStdout: job.GetenvBool("AttachStdout"), + AttachStderr: job.GetenvBool("AttachStderr"), + Tty: job.GetenvBool("Tty"), + OpenStdin: job.GetenvBool("OpenStdin"), + StdinOnce: job.GetenvBool("StdinOnce"), + Image: job.Getenv("Image"), + VolumesFrom: job.Getenv("VolumesFrom"), + WorkingDir: job.Getenv("WorkingDir"), + NetworkDisabled: job.GetenvBool("NetworkDisabled"), + } + job.GetenvJson("ExposedPorts", &config.ExposedPorts) + job.GetenvJson("Volumes", &config.Volumes) if PortSpecs := job.GetenvList("PortSpecs"); PortSpecs != nil { config.PortSpecs = PortSpecs } - job.GetenvJson("ExposedPorts", &config.ExposedPorts) - config.Tty = job.GetenvBool("Tty") - config.OpenStdin = job.GetenvBool("OpenStdin") - config.StdinOnce = job.GetenvBool("StdinOnce") if Env := job.GetenvList("Env"); Env != nil { config.Env = Env } @@ -131,15 +137,11 @@ func ContainerConfigFromJob(job *engine.Job) *Config { if Dns := job.GetenvList("Dns"); Dns != nil { config.Dns = Dns } - config.Image = job.Getenv("Image") - job.GetenvJson("Volumes", &config.Volumes) - config.VolumesFrom = job.Getenv("VolumesFrom") - config.WorkingDir = job.Getenv("WorkingDir") if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil { config.Entrypoint = Entrypoint } - config.NetworkDisabled = job.GetenvBool("NetworkDisabled") - return &config + + return config } type HostConfig struct { @@ -153,19 +155,21 @@ type HostConfig struct { } func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { - var hostConfig HostConfig + hostConfig := &HostConfig{ + ContainerIDFile: job.Getenv("ContainerIDFile"), + Privileged: job.GetenvBool("Privileged"), + PublishAllPorts: job.GetenvBool("PublishAllPorts"), + } + job.GetenvJson("LxcConf", &hostConfig.LxcConf) + job.GetenvJson("PortBindings", &hostConfig.PortBindings) if Binds := job.GetenvList("Binds"); Binds != nil { hostConfig.Binds = Binds } - hostConfig.ContainerIDFile = job.Getenv("ContainerIDFile") - job.GetenvJson("LxcConf", &hostConfig.LxcConf) - hostConfig.Privileged = job.GetenvBool("Privileged") - job.GetenvJson("PortBindings", &hostConfig.PortBindings) if Links := job.GetenvList("Links"); Links != nil { hostConfig.Links = Links } - hostConfig.PublishAllPorts = job.GetenvBool("PublishAllPorts") - return &hostConfig + + return hostConfig } type BindMap struct { diff --git a/server.go b/server.go index 5a3b999c43..06e808e117 100644 --- a/server.go +++ b/server.go @@ -42,8 +42,7 @@ func init() { // The signals SIGINT, SIGQUIT and SIGTERM are intercepted for cleanup. func jobInitApi(job *engine.Job) engine.Status { job.Logf("Creating server") - // FIXME: ImportEnv deprecates ConfigFromJob - srv, err := NewServer(job.Eng, ConfigFromJob(job)) + srv, err := NewServer(job.Eng, DaemonConfigFromJob(job)) if err != nil { job.Error(err) return engine.StatusErr From 8e619e13ca3906e849944a4b015b676b93c9f145 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 30 Jan 2014 21:53:32 +0000 Subject: [PATCH 301/364] remove TestAllocateTCPPortLocalhost faillure in tests Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 06e808e117..27c27f25de 100644 --- a/server.go +++ b/server.go @@ -1742,7 +1742,7 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { return engine.StatusErr } config := ContainerConfigFromJob(job) - if config.Memory != 0 && config.Memory < 524288 { + if config.Memory > 0 && config.Memory < 524288 { job.Errorf("Minimum memory limit allowed is 512k") return engine.StatusErr } From 720f64af1855235b6f518ad97dbb89eb61191222 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 30 Jan 2014 22:59:21 +0000 Subject: [PATCH 302/364] fix TestExitCode Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- execdriver/lxc/driver.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 4e8f586f82..11ee3b283f 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -155,7 +155,9 @@ func (d *driver) Run(c *execdriver.Command, startCallback execdriver.StartCallba ) go func() { if err := c.Wait(); err != nil { - waitErr = err + if _, ok := err.(*exec.ExitError); !ok { // Do not propagate the error if it's simply a status code != 0 + waitErr = err + } } close(waitLock) }() From 45dd051e8ee2e0e18d8ffec99f65878c20bd11e9 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 30 Jan 2014 22:41:10 +0000 Subject: [PATCH 303/364] Remove all darwin specific files and use more generic _unsupported with build tags. Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- archive/{stat_darwin.go => stat_unsupported.go} | 1 + .../lxc/{lxc_init_darwin.go => lxc_init_unsupported.go} | 1 + graphdriver/aufs/{mount_darwin.go => mount_unsupported.go} | 1 + graphdriver/btrfs/dummy_unsupported.go | 2 +- pkg/mount/{flags_darwin.go => flags_unsupported.go} | 1 + pkg/mount/{mounter_darwin.go => mounter_unsupported.go} | 1 + pkg/netlink/{netlink_darwin.go => netlink_unsupported.go} | 1 + reflink_copy_darwin.go => reflink_copy_unsupported.go | 1 + utils/fs.go | 6 ++++-- utils/{uname_darwin.go => uname_unsupported.go} | 3 ++- 10 files changed, 14 insertions(+), 4 deletions(-) rename archive/{stat_darwin.go => stat_unsupported.go} (92%) rename execdriver/lxc/{lxc_init_darwin.go => lxc_init_unsupported.go} (78%) rename graphdriver/aufs/{mount_darwin.go => mount_unsupported.go} (89%) rename pkg/mount/{flags_darwin.go => flags_unsupported.go} (78%) rename pkg/mount/{mounter_darwin.go => mounter_unsupported.go} (88%) rename pkg/netlink/{netlink_darwin.go => netlink_unsupported.go} (96%) rename reflink_copy_darwin.go => reflink_copy_unsupported.go (91%) rename utils/{uname_darwin.go => uname_unsupported.go} (52%) diff --git a/archive/stat_darwin.go b/archive/stat_unsupported.go similarity index 92% rename from archive/stat_darwin.go rename to archive/stat_unsupported.go index e041783ec6..6c2d3a04b3 100644 --- a/archive/stat_darwin.go +++ b/archive/stat_unsupported.go @@ -1,3 +1,4 @@ +// +build: !linux !amd64 package archive import "syscall" diff --git a/execdriver/lxc/lxc_init_darwin.go b/execdriver/lxc/lxc_init_unsupported.go similarity index 78% rename from execdriver/lxc/lxc_init_darwin.go rename to execdriver/lxc/lxc_init_unsupported.go index c066fead93..6b19b99285 100644 --- a/execdriver/lxc/lxc_init_darwin.go +++ b/execdriver/lxc/lxc_init_unsupported.go @@ -1,3 +1,4 @@ +// +build: !linux !amd64 package lxc func setHostname(hostname string) error { diff --git a/graphdriver/aufs/mount_darwin.go b/graphdriver/aufs/mount_unsupported.go similarity index 89% rename from graphdriver/aufs/mount_darwin.go rename to graphdriver/aufs/mount_unsupported.go index 62c84fc7c9..24b64226d2 100644 --- a/graphdriver/aufs/mount_darwin.go +++ b/graphdriver/aufs/mount_unsupported.go @@ -1,3 +1,4 @@ +// +build: !linux !amd64 package aufs import "errors" diff --git a/graphdriver/btrfs/dummy_unsupported.go b/graphdriver/btrfs/dummy_unsupported.go index 5efd18081f..6c44615763 100644 --- a/graphdriver/btrfs/dummy_unsupported.go +++ b/graphdriver/btrfs/dummy_unsupported.go @@ -1,3 +1,3 @@ -// +build !linux +// +build !linux !amd64 package btrfs diff --git a/pkg/mount/flags_darwin.go b/pkg/mount/flags_unsupported.go similarity index 78% rename from pkg/mount/flags_darwin.go rename to pkg/mount/flags_unsupported.go index e89d5e703a..d0b59a63bd 100644 --- a/pkg/mount/flags_darwin.go +++ b/pkg/mount/flags_unsupported.go @@ -1,3 +1,4 @@ +// +build: !linux !amd64 package mount func parseOptions(options string) (int, string) { diff --git a/pkg/mount/mounter_darwin.go b/pkg/mount/mounter_unsupported.go similarity index 88% rename from pkg/mount/mounter_darwin.go rename to pkg/mount/mounter_unsupported.go index 7615f94f9e..1dd7458eb0 100644 --- a/pkg/mount/mounter_darwin.go +++ b/pkg/mount/mounter_unsupported.go @@ -1,3 +1,4 @@ +// +build: !linux !amd64 package mount func mount(device, target, mType string, flag uintptr, data string) error { diff --git a/pkg/netlink/netlink_darwin.go b/pkg/netlink/netlink_unsupported.go similarity index 96% rename from pkg/netlink/netlink_darwin.go rename to pkg/netlink/netlink_unsupported.go index 298508ad10..3fa0c1e93f 100644 --- a/pkg/netlink/netlink_darwin.go +++ b/pkg/netlink/netlink_unsupported.go @@ -1,3 +1,4 @@ +// +build: !linux !amd64 package netlink import ( diff --git a/reflink_copy_darwin.go b/reflink_copy_unsupported.go similarity index 91% rename from reflink_copy_darwin.go rename to reflink_copy_unsupported.go index 4f0ea8c4fd..8ad94c4f60 100644 --- a/reflink_copy_darwin.go +++ b/reflink_copy_unsupported.go @@ -1,3 +1,4 @@ +// +build: !linux !amd64 package docker import ( diff --git a/utils/fs.go b/utils/fs.go index e710926210..92864e5e16 100644 --- a/utils/fs.go +++ b/utils/fs.go @@ -24,10 +24,12 @@ func TreeSize(dir string) (size int64, err error) { // Check inode to handle hard links correctly inode := fileInfo.Sys().(*syscall.Stat_t).Ino - if _, exists := data[inode]; exists { + // inode is not a uint64 on all platforms. Cast it to avoid issues. + if _, exists := data[uint64(inode)]; exists { return nil } - data[inode] = false + // inode is not a uint64 on all platforms. Cast it to avoid issues. + data[uint64(inode)] = false size += s diff --git a/utils/uname_darwin.go b/utils/uname_unsupported.go similarity index 52% rename from utils/uname_darwin.go rename to utils/uname_unsupported.go index a875e8c600..79ea51bd86 100644 --- a/utils/uname_darwin.go +++ b/utils/uname_unsupported.go @@ -1,3 +1,4 @@ +// +build: !linux !amd64 package utils import ( @@ -9,5 +10,5 @@ type Utsname struct { } func uname() (*Utsname, error) { - return nil, errors.New("Kernel version detection is not available on darwin") + return nil, errors.New("Kernel version detection is available only on linux") } From 2655a108e1950534f0fa1d6034ab61444fe35ee5 Mon Sep 17 00:00:00 2001 From: "Roberto G. Hashioka" Date: Thu, 30 Jan 2014 23:21:42 +0000 Subject: [PATCH 304/364] - Fixed the last cli.call's parameter from CmdSearch Docker-DCO-1.1-Signed-off-by: Roberto Hashioka (github: rogaha) --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 083ca39bc5..ff7691c916 100644 --- a/commands.go +++ b/commands.go @@ -1674,7 +1674,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { v := url.Values{} v.Set("term", cmd.Arg(0)) - body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil, false)) + body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil, true)) if err != nil { return err From 4326e541f843e5c053221f15fef546b42ba29e25 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 30 Jan 2014 23:50:23 +0000 Subject: [PATCH 305/364] add make test-integration Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 275f9dc84c..168707a80f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all binary build cross default docs docs-build docs-shell shell test +.PHONY: all binary build cross default docs docs-build docs-shell shell test test-integration GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) DOCKER_IMAGE := docker:$(GIT_BRANCH) @@ -25,6 +25,9 @@ docs-shell: docs-build test: build $(DOCKER_RUN_DOCKER) hack/make.sh test test-integration +test-integration: build + $(DOCKER_RUN_DOCKER) hack/make.sh test-integration + shell: build $(DOCKER_RUN_DOCKER) bash From 98518bbcad85217ac36112f6a18a275816b38287 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Thu, 30 Jan 2014 16:03:13 -0800 Subject: [PATCH 306/364] Fixed link to kernel dependencies. Wrapped long lines. Removed old tar dependency from list. Docker-DCO-1.1-Signed-off-by: Andy Rothfusz (github: metalivedev) --- docs/sources/installation/binaries.rst | 39 +++++++++++++++----------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/docs/sources/installation/binaries.rst b/docs/sources/installation/binaries.rst index 9b741d6918..976e94e344 100644 --- a/docs/sources/installation/binaries.rst +++ b/docs/sources/installation/binaries.rst @@ -12,18 +12,22 @@ Binaries **This instruction set is meant for hackers who want to try out Docker on a variety of environments.** -Before following these directions, you should really check if a packaged version -of Docker is already available for your distribution. We have packages for many -distributions, and more keep showing up all the time! +Before following these directions, you should really check if a +packaged version of Docker is already available for your distribution. +We have packages for many distributions, and more keep showing up all +the time! Check runtime dependencies -------------------------- +.. DOC COMMENT: this should be kept in sync with + https://github.com/dotcloud/docker/blob/master/hack/PACKAGERS.md#runtime-dependencies + To run properly, docker needs the following software to be installed at runtime: -- GNU Tar version 1.26 or later -- iproute2 version 3.5 or later (build after 2012-05-21), and specifically the "ip" utility +- iproute2 version 3.5 or later (build after 2012-05-21), and + specifically the "ip" utility - iptables version 1.4 or later - The LXC utility scripts (http://lxc.sourceforge.net) version 0.8 or later - Git version 1.7 or later @@ -33,11 +37,11 @@ To run properly, docker needs the following software to be installed at runtime: Check kernel dependencies ------------------------- -Docker in daemon mode has specific kernel requirements. For details, see -http://docs.docker.io/en/latest/articles/kernel/ +Docker in daemon mode has specific kernel requirements. For details, +check your distribution in :ref:`installation_list`. -Note that Docker also has a client mode, which can run on virtually any linux kernel (it even builds -on OSX!). +Note that Docker also has a client mode, which can run on virtually +any linux kernel (it even builds on OSX!). Get the docker binary: @@ -63,18 +67,18 @@ Run the docker daemon Giving non-root access ---------------------- -The ``docker`` daemon always runs as the root user, and since Docker version -0.5.2, the ``docker`` daemon binds to a Unix socket instead of a TCP port. By -default that Unix socket is owned by the user *root*, and so, by default, you -can access it with ``sudo``. +The ``docker`` daemon always runs as the root user, and since Docker +version 0.5.2, the ``docker`` daemon binds to a Unix socket instead of +a TCP port. By default that Unix socket is owned by the user *root*, +and so, by default, you can access it with ``sudo``. Starting in version 0.5.3, if you (or your Docker installer) create a Unix group called *docker* and add users to it, then the ``docker`` daemon will make the ownership of the Unix socket read/writable by the *docker* group when the daemon starts. The ``docker`` daemon must -always run as the root user, but if you run the ``docker`` client as a user in -the *docker* group then you don't need to add ``sudo`` to all the -client commands. +always run as the root user, but if you run the ``docker`` client as a +user in the *docker* group then you don't need to add ``sudo`` to all +the client commands. .. warning:: The *docker* group is root-equivalent. @@ -82,7 +86,8 @@ client commands. Upgrades -------- -To upgrade your manual installation of Docker, first kill the docker daemon: +To upgrade your manual installation of Docker, first kill the docker +daemon: .. code-block:: bash From ce423cc9a8b9552ee8bb75f3aac81d291f85375c Mon Sep 17 00:00:00 2001 From: Rafal Jeczalik Date: Sun, 8 Dec 2013 16:45:12 +0100 Subject: [PATCH 307/364] vagrant: added PRIVATE_NETWORK env var Docker-DCO-1.1-Signed-off-by: Rafal Jeczalik (github: rjeczalik) --- Vagrantfile | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index c130587829..f709031fdf 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -8,10 +8,9 @@ AWS_BOX_URI = ENV['BOX_URI'] || "https://github.com/mitchellh/vagrant-aws/raw/ma AWS_REGION = ENV['AWS_REGION'] || "us-east-1" AWS_AMI = ENV['AWS_AMI'] || "ami-69f5a900" AWS_INSTANCE_TYPE = ENV['AWS_INSTANCE_TYPE'] || 't1.micro' - FORWARD_DOCKER_PORTS = ENV['FORWARD_DOCKER_PORTS'] - -SSH_PRIVKEY_PATH = ENV["SSH_PRIVKEY_PATH"] +SSH_PRIVKEY_PATH = ENV['SSH_PRIVKEY_PATH'] +PRIVATE_NETWORK = ENV['PRIVATE_NETWORK'] # A script to upgrade from the 12.04 kernel to the raring backport kernel (3.8) # and install docker. @@ -174,3 +173,14 @@ if !FORWARD_DOCKER_PORTS.nil? end end end + +if !PRIVATE_NETWORK.nil? + Vagrant::VERSION < "1.1.0" and Vagrant::Config.run do |config| + config.vm.network :hostonly, PRIVATE_NETWORK + end + + Vagrant::VERSION >= "1.1.0" and Vagrant.configure("2") do |config| + config.vm.network "private_network", ip: PRIVATE_NETWORK + end +end + From 28b5ae8cc4492f7b3cc2eb2b30b0f41713822b25 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 31 Jan 2014 02:06:08 +0000 Subject: [PATCH 308/364] changed default value of getenvint to 0. fix tests Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 12 ++++++++++-- config.go | 2 +- engine/env.go | 2 +- engine/env_test.go | 2 +- server.go | 6 +++--- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/api.go b/api.go index 0a7f7abea7..79df0f28fc 100644 --- a/api.go +++ b/api.go @@ -584,7 +584,11 @@ func postContainersRestart(srv *Server, version float64, w http.ResponseWriter, return fmt.Errorf("Missing parameter") } job := srv.Eng.Job("restart", vars["name"]) - job.Setenv("t", r.Form.Get("t")) + if r.Form.Get("t") == "" { + job.Setenv("t", "-1") + } else { + job.Setenv("t", r.Form.Get("t")) + } if err := job.Run(); err != nil { return err } @@ -652,7 +656,11 @@ func postContainersStop(srv *Server, version float64, w http.ResponseWriter, r * return fmt.Errorf("Missing parameter") } job := srv.Eng.Job("stop", vars["name"]) - job.Setenv("t", r.Form.Get("t")) + if r.Form.Get("t") == "" { + job.Setenv("t", "-1") + } else { + job.Setenv("t", r.Form.Get("t")) + } if err := job.Run(); err != nil { return err } diff --git a/config.go b/config.go index ac34589640..cb7e985ca2 100644 --- a/config.go +++ b/config.go @@ -43,7 +43,7 @@ func DaemonConfigFromJob(job *engine.Job) *DaemonConfig { } else { config.BridgeIface = DefaultNetworkBridge } - if mtu := job.GetenvInt("Mtu"); mtu != -1 { + if mtu := job.GetenvInt("Mtu"); mtu != 0 { config.Mtu = mtu } else { config.Mtu = DefaultNetworkMtu diff --git a/engine/env.go b/engine/env.go index e000fe26b1..d6ca4ec07b 100644 --- a/engine/env.go +++ b/engine/env.go @@ -60,7 +60,7 @@ func (env *Env) GetInt64(key string) int64 { s := strings.Trim(env.Get(key), " \t") val, err := strconv.ParseInt(s, 10, 64) if err != nil { - return -1 + return 0 } return val } diff --git a/engine/env_test.go b/engine/env_test.go index 419c47491e..c7079ff942 100644 --- a/engine/env_test.go +++ b/engine/env_test.go @@ -62,7 +62,7 @@ func TestSetenvInt(t *testing.T) { if val := job.GetenvInt("bar"); val != 42 { t.Fatalf("GetenvInt returns incorrect value: %d", val) } - if val := job.GetenvInt("nonexistent"); val != -1 { + if val := job.GetenvInt("nonexistent"); val != 0 { t.Fatalf("GetenvInt returns incorrect value: %d", val) } } diff --git a/server.go b/server.go index 27c27f25de..6291da6699 100644 --- a/server.go +++ b/server.go @@ -1083,7 +1083,7 @@ func (srv *Server) Containers(job *engine.Job) engine.Status { }, -1) for _, container := range srv.runtime.List() { - if !container.State.IsRunning() && !all && n == -1 && since == "" && before == "" { + if !container.State.IsRunning() && !all && n <= 0 && since == "" && before == "" { continue } if before != "" && !foundBefore { @@ -1092,7 +1092,7 @@ func (srv *Server) Containers(job *engine.Job) engine.Status { } continue } - if displayed == n { + if n > 0 && displayed == n { break } if container.ID == since || utils.TruncateID(container.ID) == since { @@ -1742,7 +1742,7 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { return engine.StatusErr } config := ContainerConfigFromJob(job) - if config.Memory > 0 && config.Memory < 524288 { + if config.Memory != 0 && config.Memory < 524288 { job.Errorf("Minimum memory limit allowed is 512k") return engine.StatusErr } From 1498cd4e0540c73546a4847948f7d6a75b596178 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 31 Jan 2014 02:21:59 +0000 Subject: [PATCH 309/364] use exists Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 12 ++---------- engine/job.go | 4 ++++ server.go | 20 ++++++++++++-------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/api.go b/api.go index 79df0f28fc..0a7f7abea7 100644 --- a/api.go +++ b/api.go @@ -584,11 +584,7 @@ func postContainersRestart(srv *Server, version float64, w http.ResponseWriter, return fmt.Errorf("Missing parameter") } job := srv.Eng.Job("restart", vars["name"]) - if r.Form.Get("t") == "" { - job.Setenv("t", "-1") - } else { - job.Setenv("t", r.Form.Get("t")) - } + job.Setenv("t", r.Form.Get("t")) if err := job.Run(); err != nil { return err } @@ -656,11 +652,7 @@ func postContainersStop(srv *Server, version float64, w http.ResponseWriter, r * return fmt.Errorf("Missing parameter") } job := srv.Eng.Job("stop", vars["name"]) - if r.Form.Get("t") == "" { - job.Setenv("t", "-1") - } else { - job.Setenv("t", r.Form.Get("t")) - } + job.Setenv("t", r.Form.Get("t")) if err := job.Run(); err != nil { return err } diff --git a/engine/job.go b/engine/job.go index 181eaa80c7..c2eadccc1b 100644 --- a/engine/job.go +++ b/engine/job.go @@ -102,6 +102,10 @@ func (job *Job) String() string { return fmt.Sprintf("%s.%s%s", job.Eng, job.CallString(), job.StatusString()) } +func (job *Job) EnvExists(key string) (value bool) { + return job.env.Exists(key) +} + func (job *Job) Getenv(key string) (value string) { return job.env.Get(key) } diff --git a/server.go b/server.go index 6291da6699..90a8bb3ed8 100644 --- a/server.go +++ b/server.go @@ -1798,10 +1798,12 @@ func (srv *Server) ContainerRestart(job *engine.Job) engine.Status { job.Errorf("Usage: %s CONTAINER\n", job.Name) return engine.StatusErr } - name := job.Args[0] - t := job.GetenvInt("t") - if t == -1 { - t = 10 + var ( + name = job.Args[0] + t = 10 + ) + if job.EnvExists("t") { + t = job.GetenvInt("t") } if container := srv.runtime.Get(name); container != nil { if err := container.Restart(int(t)); err != nil { @@ -2239,10 +2241,12 @@ func (srv *Server) ContainerStop(job *engine.Job) engine.Status { job.Errorf("Usage: %s CONTAINER\n", job.Name) return engine.StatusErr } - name := job.Args[0] - t := job.GetenvInt("t") - if t == -1 { - t = 10 + var ( + name = job.Args[0] + t = 10 + ) + if job.EnvExists("t") { + t = job.GetenvInt("t") } if container := srv.runtime.Get(name); container != nil { if err := container.Stop(int(t)); err != nil { From 630459f2b84c9664b436eb076cfac39d731f08bf Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 31 Jan 2014 12:30:16 +1000 Subject: [PATCH 310/364] add a known issue for lxc-1.0.0.beta3 and beyond for removeal of lxc-kill see #3844 Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/reference/commandline/cli.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 3d215cc0b4..fc7e779e74 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -772,6 +772,8 @@ Known Issues (kill) * :issue:`197` indicates that ``docker kill`` may leave directories behind and make it difficult to remove the container. +* :issue:`3844` lxc 1.0.0 beta3 removed ``lcx-kill`` which is used by Docker versions before 0.8.0; + see the issue for a workaround. .. _cli_load: From fc2f998822699c0c7e22e4fc791d10c3b1ea1e53 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 29 Jan 2014 20:16:47 +0000 Subject: [PATCH 311/364] remove some mkServerFromEngine Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- integration/runtime_test.go | 7 ++++--- integration/sorter_test.go | 14 ++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/integration/runtime_test.go b/integration/runtime_test.go index e08643b36d..4fc3c56868 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -61,7 +61,7 @@ func cleanup(eng *engine.Engine, t *testing.T) error { } for _, image := range images.Data { if image.Get("ID") != unitTestImageID { - mkServerFromEngine(eng, t).DeleteImage(image.Get("ID"), false) + eng.Job("image_delete", image.Get("ID")).Run() } } return nil @@ -132,10 +132,11 @@ func setupBaseImage() { if err := job.Run(); err != nil { log.Fatalf("Unable to create a runtime for tests: %s", err) } - srv := mkServerFromEngine(eng, log.New(os.Stderr, "", 0)) + job = eng.Job("inspect", unitTestImageName, "image") + img, _ := job.Stdout.AddEnv() // If the unit test is not found, try to download it. - if img, err := srv.ImageInspect(unitTestImageName); err != nil || img.ID != unitTestImageID { + if err := job.Run(); err != nil || img.Get("id") != unitTestImageID { // Retrieve the Image job = eng.Job("pull", unitTestImageName) job.Stdout.Add(utils.NopWriteCloser(os.Stdout)) diff --git a/integration/sorter_test.go b/integration/sorter_test.go index d193fca1f0..3ce1225ca4 100644 --- a/integration/sorter_test.go +++ b/integration/sorter_test.go @@ -1,7 +1,7 @@ package docker import ( - "github.com/dotcloud/docker" + "github.com/dotcloud/docker/engine" "testing" "time" ) @@ -9,9 +9,8 @@ import ( func TestServerListOrderedImagesByCreationDate(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) - if err := generateImage("", srv); err != nil { + if err := generateImage("", eng); err != nil { t.Fatal(err) } @@ -25,16 +24,15 @@ func TestServerListOrderedImagesByCreationDate(t *testing.T) { func TestServerListOrderedImagesByCreationDateAndTag(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - srv := mkServerFromEngine(eng, t) - err := generateImage("bar", srv) + err := generateImage("bar", eng) if err != nil { t.Fatal(err) } time.Sleep(time.Second) - err = generateImage("zed", srv) + err = generateImage("zed", eng) if err != nil { t.Fatal(err) } @@ -46,12 +44,12 @@ func TestServerListOrderedImagesByCreationDateAndTag(t *testing.T) { } } -func generateImage(name string, srv *docker.Server) error { +func generateImage(name string, eng *engine.Engine) error { archive, err := fakeTar() if err != nil { return err } - job := srv.Eng.Job("import", "-", "repo", name) + job := eng.Job("import", "-", "repo", name) job.Stdin.Add(archive) job.SetenvBool("json", true) return job.Run() From 2b52d6e801dd888e1f5759448da025e0ddcffedd Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 24 Jan 2014 23:15:40 -0800 Subject: [PATCH 312/364] Remove api_params.go Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 69 ++++++++++++++++-------------- api_params.go | 43 ------------------- commands.go | 95 +++++++++++++++++++++++------------------ integration/api_test.go | 68 +++++++++++++++-------------- 4 files changed, 127 insertions(+), 148 deletions(-) delete mode 100644 api_params.go diff --git a/api.go b/api.go index 0a7f7abea7..ba8646599d 100644 --- a/api.go +++ b/api.go @@ -89,18 +89,10 @@ func httpError(w http.ResponseWriter, err error) { } } -func writeJSON(w http.ResponseWriter, code int, v interface{}) error { - b, err := json.Marshal(v) - - if err != nil { - return err - } - +func writeJSON(w http.ResponseWriter, code int, v engine.Env) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) - w.Write(b) - - return nil + return v.Encode(w) } func getBoolParam(value string) (bool, error) { @@ -352,12 +344,15 @@ func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Req if err := parseForm(r); err != nil { return err } - config := &Config{} + var ( + config = &Config{} + env engine.Env + job = srv.Eng.Job("commit", r.Form.Get("container")) + ) if err := json.NewDecoder(r.Body).Decode(config); err != nil && err != io.EOF { utils.Errorf("%s", err) } - job := srv.Eng.Job("commit", r.Form.Get("container")) job.Setenv("repo", r.Form.Get("repo")) job.Setenv("tag", r.Form.Get("tag")) job.Setenv("author", r.Form.Get("author")) @@ -369,8 +364,8 @@ func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Req if err := job.Run(); err != nil { return err } - - return writeJSON(w, http.StatusCreated, &APIID{id}) + env.Set("Id", id) + return writeJSON(w, http.StatusCreated, env) } // Creates an image from Pull or from Import @@ -555,15 +550,19 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r if err := parseForm(r); err != nil { return nil } - out := &APIRun{} - job := srv.Eng.Job("create", r.Form.Get("name")) + var ( + out engine.Env + job = srv.Eng.Job("create", r.Form.Get("name")) + outWarnings []string + outId string + warnings = bytes.NewBuffer(nil) + ) if err := job.DecodeEnv(r.Body); err != nil { return err } // Read container ID from the first line of stdout - job.Stdout.AddString(&out.ID) + job.Stdout.AddString(&outId) // Read warnings from stderr - warnings := &bytes.Buffer{} job.Stderr.Add(warnings) if err := job.Run(); err != nil { return err @@ -571,8 +570,10 @@ func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r // Parse warnings from stderr scanner := bufio.NewScanner(warnings) for scanner.Scan() { - out.Warnings = append(out.Warnings, scanner.Text()) + outWarnings = append(outWarnings, scanner.Text()) } + out.Set("Id", outId) + out.SetList("Warnings", outWarnings) return writeJSON(w, http.StatusCreated, out) } @@ -664,18 +665,22 @@ func postContainersWait(srv *Server, version float64, w http.ResponseWriter, r * if vars == nil { return fmt.Errorf("Missing parameter") } - job := srv.Eng.Job("wait", vars["name"]) - var statusStr string - job.Stdout.AddString(&statusStr) + var ( + env engine.Env + status string + job = srv.Eng.Job("wait", vars["name"]) + ) + job.Stdout.AddString(&status) if err := job.Run(); err != nil { return err } // Parse a 16-bit encoded integer to map typical unix exit status. - status, err := strconv.ParseInt(statusStr, 10, 16) + _, err := strconv.ParseInt(status, 10, 16) if err != nil { return err } - return writeJSON(w, http.StatusOK, &APIWait{StatusCode: int(status)}) + env.Set("StatusCode", status) + return writeJSON(w, http.StatusOK, env) } func postContainersResize(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { @@ -874,24 +879,24 @@ func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r * return fmt.Errorf("Missing parameter") } - copyData := &APICopy{} - contentType := r.Header.Get("Content-Type") - if contentType == "application/json" { - if err := json.NewDecoder(r.Body).Decode(copyData); err != nil { + var copyData engine.Env + + if contentType := r.Header.Get("Content-Type"); contentType == "application/json" { + if err := copyData.Decode(r.Body); err != nil { return err } } else { return fmt.Errorf("Content-Type not supported: %s", contentType) } - if copyData.Resource == "" { + if copyData.Get("Resource") == "" { return fmt.Errorf("Path cannot be empty") } - if copyData.Resource[0] == '/' { - copyData.Resource = copyData.Resource[1:] + if copyData.Get("Resource")[0] == '/' { + copyData.Set("Resource", copyData.Get("Resource")[1:]) } - job := srv.Eng.Job("container_copy", vars["name"], copyData.Resource) + job := srv.Eng.Job("container_copy", vars["name"], copyData.Get("Resource")) job.Stdout.Add(w) if err := job.Run(); err != nil { utils.Errorf("%s", err.Error()) diff --git a/api_params.go b/api_params.go deleted file mode 100644 index fb5ad6f388..0000000000 --- a/api_params.go +++ /dev/null @@ -1,43 +0,0 @@ -package docker - -type ( - APITop struct { - Titles []string - Processes [][]string - } - - APIRmi struct { - Deleted string `json:",omitempty"` - Untagged string `json:",omitempty"` - } - - APIID struct { - ID string `json:"Id"` - } - - APIRun struct { - ID string `json:"Id"` - Warnings []string `json:",omitempty"` - } - - APIPort struct { - PrivatePort int64 - PublicPort int64 - Type string - IP string - } - - APIWait struct { - StatusCode int - } - - APIImageConfig struct { - ID string `json:"Id"` - *Config - } - - APICopy struct { - Resource string - HostPath string - } -) diff --git a/commands.go b/commands.go index ff7691c916..1428a769d0 100644 --- a/commands.go +++ b/commands.go @@ -755,18 +755,21 @@ func (cli *DockerCli) CmdTop(args ...string) error { val.Set("ps_args", strings.Join(cmd.Args()[1:], " ")) } - body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false)) + stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false) if err != nil { return err } - procs := APITop{} - err = json.Unmarshal(body, &procs) - if err != nil { + var procs engine.Env + if err := procs.Decode(stream); err != nil { return err } w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) - fmt.Fprintln(w, strings.Join(procs.Titles, "\t")) - for _, proc := range procs.Processes { + fmt.Fprintln(w, strings.Join(procs.GetList("Titles"), "\t")) + processes := [][]string{} + if err := procs.GetJson("Processes", &processes); err != nil { + return err + } + for _, proc := range processes { fmt.Fprintln(w, strings.Join(proc, "\t")) } w.Flush() @@ -1451,25 +1454,25 @@ func (cli *DockerCli) CmdCommit(args ...string) error { v.Set("tag", tag) v.Set("comment", *flComment) v.Set("author", *flAuthor) - var config *Config + var ( + config *Config + env engine.Env + ) if *flConfig != "" { config = &Config{} if err := json.Unmarshal([]byte(*flConfig), config); err != nil { return err } } - body, _, err := readBody(cli.call("POST", "/commit?"+v.Encode(), config, false)) + stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, false) if err != nil { return err } - - apiID := &APIID{} - err = json.Unmarshal(body, apiID) - if err != nil { + if err := env.Decode(stream); err != nil { return err } - fmt.Fprintf(cli.out, "%s\n", apiID.ID) + fmt.Fprintf(cli.out, "%s\n", env.Get("ID")) return nil } @@ -1989,7 +1992,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } //create the container - body, statusCode, err := readBody(cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false)) + stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false) //if image not found try to pull it if statusCode == 404 { _, tag := utils.ParseRepositoryTag(config.Image) @@ -2026,30 +2029,30 @@ func (cli *DockerCli) CmdRun(args ...string) error { if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil { return err } - if body, _, err = readBody(cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false)); err != nil { + if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false); err != nil { return err } } else if err != nil { return err } - var runResult APIRun - if err := json.Unmarshal(body, &runResult); err != nil { + var runResult engine.Env + if err := runResult.Decode(stream); err != nil { return err } - for _, warning := range runResult.Warnings { + for _, warning := range runResult.GetList("Warnings") { fmt.Fprintf(cli.err, "WARNING: %s\n", warning) } if len(hostConfig.ContainerIDFile) > 0 { - if _, err = containerIDFile.Write([]byte(runResult.ID)); err != nil { + if _, err = containerIDFile.Write([]byte(runResult.Get("Id"))); err != nil { return fmt.Errorf("failed to write the container ID to the file: %s", err) } } if sigProxy { - sigc := cli.forwardAllSignals(runResult.ID) + sigc := cli.forwardAllSignals(runResult.Get("Id")) defer utils.StopCatch(sigc) } @@ -2063,7 +2066,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { waitDisplayId = make(chan struct{}) go func() { defer close(waitDisplayId) - fmt.Fprintf(cli.out, "%s\n", runResult.ID) + fmt.Fprintf(cli.out, "%s\n", runResult.Get("Id")) }() } @@ -2105,7 +2108,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } errCh = utils.Go(func() error { - return cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked) + return cli.hijack("POST", "/containers/"+runResult.Get("Id")+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked) }) } else { close(hijacked) @@ -2127,12 +2130,12 @@ func (cli *DockerCli) CmdRun(args ...string) error { } //start the container - if _, _, err = readBody(cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig, false)); err != nil { + if _, _, err = readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/start", hostConfig, false)); err != nil { return err } if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminal { - if err := cli.monitorTtySize(runResult.ID); err != nil { + if err := cli.monitorTtySize(runResult.Get("Id")); err != nil { utils.Errorf("Error monitoring TTY size: %s\n", err) } } @@ -2157,26 +2160,26 @@ func (cli *DockerCli) CmdRun(args ...string) error { if autoRemove { // Autoremove: wait for the container to finish, retrieve // the exit code and remove the container - if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.ID+"/wait", nil, false)); err != nil { + if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/wait", nil, false)); err != nil { return err } - if _, status, err = getExitCode(cli, runResult.ID); err != nil { + if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil { return err } - if _, _, err := readBody(cli.call("DELETE", "/containers/"+runResult.ID+"?v=1", nil, false)); err != nil { + if _, _, err := readBody(cli.call("DELETE", "/containers/"+runResult.Get("Id")+"?v=1", nil, false)); err != nil { return err } } else { if !config.Tty { // In non-tty mode, we can't dettach, so we know we need to wait. - if status, err = waitForExit(cli, runResult.ID); err != nil { + if status, err = waitForExit(cli, runResult.Get("Id")); err != nil { return err } } else { // In TTY mode, there is a race. If the process dies too slowly, the state can be update after the getExitCode call // and result in a wrong exit code. // No Autoremove: Simply retrieve the exit code - if _, status, err = getExitCode(cli, runResult.ID); err != nil { + if _, status, err = getExitCode(cli, runResult.Get("Id")); err != nil { return err } } @@ -2198,15 +2201,15 @@ func (cli *DockerCli) CmdCp(args ...string) error { return nil } - var copyData APICopy + var copyData engine.Env info := strings.Split(cmd.Arg(0), ":") if len(info) != 2 { return fmt.Errorf("Error: Path not specified") } - copyData.Resource = info[1] - copyData.HostPath = cmd.Arg(1) + copyData.Set("Resource", info[1]) + copyData.Set("HostPath", cmd.Arg(1)) stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, false) if stream != nil { @@ -2217,7 +2220,7 @@ func (cli *DockerCli) CmdCp(args ...string) error { } if statusCode == 200 { - if err := archive.Untar(stream, copyData.HostPath, nil); err != nil { + if err := archive.Untar(stream, copyData.Get("HostPath"), nil); err != nil { return err } } @@ -2260,13 +2263,21 @@ func (cli *DockerCli) CmdLoad(args ...string) error { } func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) { - var params io.Reader + params := bytes.NewBuffer(nil) if data != nil { - buf, err := json.Marshal(data) - if err != nil { - return nil, -1, err + if env, ok := data.(engine.Env); ok { + if err := env.Encode(params); err != nil { + return nil, -1, err + } + } else { + buf, err := json.Marshal(data) + if err != nil { + return nil, -1, err + } + if _, err := params.Write(buf); err != nil { + return nil, -1, err + } } - params = bytes.NewBuffer(buf) } // fixme: refactor client to support redirect re := regexp.MustCompile("/+") @@ -2569,16 +2580,16 @@ func (cli *DockerCli) LoadConfigFile() (err error) { } func waitForExit(cli *DockerCli, containerId string) (int, error) { - body, _, err := readBody(cli.call("POST", "/containers/"+containerId+"/wait", nil, false)) + stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false) if err != nil { return -1, err } - var out APIWait - if err := json.Unmarshal(body, &out); err != nil { + var out engine.Env + if err := out.Decode(stream); err != nil { return -1, err } - return out.StatusCode, nil + return out.GetInt("StatusCode"), nil } // getExitCode perform an inspect on the container. It returns diff --git a/integration/api_test.go b/integration/api_test.go index 95cae47e15..b9ff079cb1 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -485,26 +485,29 @@ func TestGetContainersTop(t *testing.T) { t.Fatal(err) } assertHttpNotError(r, t) - procs := docker.APITop{} - if err := json.Unmarshal(r.Body.Bytes(), &procs); err != nil { + var procs engine.Env + if err := procs.Decode(r.Body); err != nil { t.Fatal(err) } - if len(procs.Titles) != 11 { - t.Fatalf("Expected 11 titles, found %d.", len(procs.Titles)) + if len(procs.GetList("Titles")) != 11 { + t.Fatalf("Expected 11 titles, found %d.", len(procs.GetList("Titles"))) } - if procs.Titles[0] != "USER" || procs.Titles[10] != "COMMAND" { - t.Fatalf("Expected Titles[0] to be USER and Titles[10] to be COMMAND, found %s and %s.", procs.Titles[0], procs.Titles[10]) + if procs.GetList("Titles")[0] != "USER" || procs.GetList("Titles")[10] != "COMMAND" { + t.Fatalf("Expected Titles[0] to be USER and Titles[10] to be COMMAND, found %s and %s.", procs.GetList("Titles")[0], procs.GetList("Titles")[10]) } - - if len(procs.Processes) != 2 { - t.Fatalf("Expected 2 processes, found %d.", len(procs.Processes)) + processes := [][]string{} + if err := procs.GetJson("Processes", &processes); err != nil { + t.Fatal(err) } - if procs.Processes[0][10] != "/bin/sh -c cat" { - t.Fatalf("Expected `/bin/sh -c cat`, found %s.", procs.Processes[0][10]) + if len(processes) != 2 { + t.Fatalf("Expected 2 processes, found %d.", len(processes)) } - if procs.Processes[1][10] != "/bin/sh -c cat" { - t.Fatalf("Expected `/bin/sh -c cat`, found %s.", procs.Processes[1][10]) + if processes[0][10] != "/bin/sh -c cat" { + t.Fatalf("Expected `/bin/sh -c cat`, found %s.", processes[0][10]) + } + if processes[1][10] != "/bin/sh -c cat" { + t.Fatalf("Expected `/bin/sh -c cat`, found %s.", processes[1][10]) } } @@ -570,11 +573,11 @@ func TestPostCommit(t *testing.T) { t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) } - apiID := &docker.APIID{} - if err := json.Unmarshal(r.Body.Bytes(), apiID); err != nil { + var env engine.Env + if err := env.Decode(r.Body); err != nil { t.Fatal(err) } - if _, err := srv.ImageInspect(apiID.ID); err != nil { + if _, err := srv.ImageInspect(env.Get("Id")); err != nil { t.Fatalf("The image has not been committed") } } @@ -607,11 +610,11 @@ func TestPostContainersCreate(t *testing.T) { t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) } - apiRun := &docker.APIRun{} - if err := json.Unmarshal(r.Body.Bytes(), apiRun); err != nil { + var apiRun engine.Env + if err := apiRun.Decode(r.Body); err != nil { t.Fatal(err) } - containerID := apiRun.ID + containerID := apiRun.Get("Id") containerAssertExists(eng, containerID, t) containerRun(eng, containerID, t) @@ -863,12 +866,12 @@ func TestPostContainersWait(t *testing.T) { t.Fatal(err) } assertHttpNotError(r, t) - apiWait := &docker.APIWait{} - if err := json.Unmarshal(r.Body.Bytes(), apiWait); err != nil { + var apiWait engine.Env + if err := apiWait.Decode(r.Body); err != nil { t.Fatal(err) } - if apiWait.StatusCode != 0 { - t.Fatalf("Non zero exit code for sleep: %d\n", apiWait.StatusCode) + if apiWait.GetInt("StatusCode") != 0 { + t.Fatalf("Non zero exit code for sleep: %d\n", apiWait.GetInt("StatusCode")) } }) @@ -1160,12 +1163,12 @@ func TestDeleteImages(t *testing.T) { t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) } - var outs []docker.APIRmi - if err := json.Unmarshal(r2.Body.Bytes(), &outs); err != nil { + outs := engine.NewTable("Created", 0) + if _, err := outs.ReadListFrom(r2.Body.Bytes()); err != nil { t.Fatal(err) } - if len(outs) != 1 { - t.Fatalf("Expected %d event (untagged), got %d", 1, len(outs)) + if len(outs.Data) != 1 { + t.Fatalf("Expected %d event (untagged), got %d", 1, len(outs.Data)) } images = getImages(eng, t, false, "") @@ -1190,14 +1193,17 @@ func TestPostContainersCopy(t *testing.T) { containerRun(eng, containerID, t) r := httptest.NewRecorder() - copyData := docker.APICopy{HostPath: ".", Resource: "/test.txt"} - jsonData, err := json.Marshal(copyData) - if err != nil { + var copyData engine.Env + copyData.Set("Resource", "/test.txt") + copyData.Set("HostPath", ".") + + jsonData := bytes.NewBuffer(nil) + if err := copyData.Encode(jsonData); err != nil { t.Fatal(err) } - req, err := http.NewRequest("POST", "/containers/"+containerID+"/copy", bytes.NewReader(jsonData)) + req, err := http.NewRequest("POST", "/containers/"+containerID+"/copy", jsonData) if err != nil { t.Fatal(err) } From 24086fa75dff1ebb56dfb8fbf2c80fdfd5be0e8a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 28 Jan 2014 00:27:02 +0000 Subject: [PATCH 313/364] job.error\* now return engine.StatusErr Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- engine/job.go | 10 +- server.go | 446 +++++++++++++++++--------------------------------- 2 files changed, 154 insertions(+), 302 deletions(-) diff --git a/engine/job.go b/engine/job.go index c2eadccc1b..782bb02171 100644 --- a/engine/job.go +++ b/engine/job.go @@ -188,10 +188,12 @@ func (job *Job) Printf(format string, args ...interface{}) (n int, err error) { return fmt.Fprintf(job.Stdout, format, args...) } -func (job *Job) Errorf(format string, args ...interface{}) (n int, err error) { - return fmt.Fprintf(job.Stderr, format, args...) +func (job *Job) Errorf(format string, args ...interface{}) Status { + fmt.Fprintf(job.Stderr, format, args...) + return StatusErr } -func (job *Job) Error(err error) (int, error) { - return fmt.Fprintf(job.Stderr, "%s", err) +func (job *Job) Error(err error) Status { + fmt.Fprintf(job.Stderr, "%s", err) + return StatusErr } diff --git a/server.go b/server.go index 90a8bb3ed8..6eeca79d90 100644 --- a/server.go +++ b/server.go @@ -44,8 +44,7 @@ func jobInitApi(job *engine.Job) engine.Status { job.Logf("Creating server") srv, err := NewServer(job.Eng, DaemonConfigFromJob(job)) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if srv.runtime.config.Pidfile != "" { job.Logf("Creating pidfile") @@ -106,8 +105,7 @@ func jobInitApi(job *engine.Job) engine.Status { "auth": srv.Auth, } { if err := job.Eng.Register(name, handler); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } return engine.StatusOK @@ -130,8 +128,7 @@ func (srv *Server) ListenAndServe(job *engine.Job) engine.Status { for i := 0; i < len(protoAddrs); i += 1 { err := <-chErrors if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } @@ -199,8 +196,7 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { } if n := len(job.Args); n < 1 || n > 2 { - job.Errorf("Usage: %s CONTAINER [SIGNAL]", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER [SIGNAL]", job.Name) } name := job.Args[0] var sig uint64 @@ -211,8 +207,7 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { // The largest legal signal is 31, so let's parse on 5 bits sig, err = strconv.ParseUint(job.Args[1], 10, 5) if err != nil { - job.Errorf("Invalid signal: %s", job.Args[1]) - return engine.StatusErr + return job.Errorf("Invalid signal: %s", job.Args[1]) } } } @@ -220,21 +215,18 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { // If no signal is passed, or SIGKILL, perform regular Kill (SIGKILL + wait()) if sig == 0 || syscall.Signal(sig) == syscall.SIGKILL { if err := container.Kill(); err != nil { - job.Errorf("Cannot kill container %s: %s", name, err) - return engine.StatusErr + return job.Errorf("Cannot kill container %s: %s", name, err) } srv.LogEvent("kill", container.ID, srv.runtime.repositories.ImageName(container.Image)) } else { // Otherwise, just send the requested signal if err := container.kill(int(sig)); err != nil { - job.Errorf("Cannot kill container %s: %s", name, err) - return engine.StatusErr + return job.Errorf("Cannot kill container %s: %s", name, err) } // FIXME: Add event for signals } } else { - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } return engine.StatusOK } @@ -244,8 +236,7 @@ func (srv *Server) Auth(job *engine.Job) engine.Status { job.GetenvJson("authConfig", authConfig) status, err := auth.Login(authConfig, srv.HTTPRequestFactory(nil)) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } job.Printf("%s\n", status) return engine.StatusOK @@ -253,8 +244,7 @@ func (srv *Server) Auth(job *engine.Job) engine.Status { func (srv *Server) Events(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Usage: %s FROM", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s FROM", job.Name) } var ( @@ -304,8 +294,7 @@ func (srv *Server) Events(job *engine.Job) engine.Status { continue } if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } return engine.StatusOK @@ -313,28 +302,24 @@ func (srv *Server) Events(job *engine.Job) engine.Status { func (srv *Server) ContainerExport(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Usage: %s container_id", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s container_id", job.Name) } name := job.Args[0] if container := srv.runtime.Get(name); container != nil { data, err := container.Export() if err != nil { - job.Errorf("%s: %s", name, err) - return engine.StatusErr + return job.Errorf("%s: %s", name, err) } // Stream the entire contents of the container (basically a volatile snapshot) if _, err := io.Copy(job.Stdout, data); err != nil { - job.Errorf("%s: %s", name, err) - return engine.StatusErr + return job.Errorf("%s: %s", name, err) } // FIXME: factor job-specific LogEvent to engine.Job.Run() srv.LogEvent("export", container.ID, srv.runtime.repositories.ImageName(container.Image)) return engine.StatusOK } - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } // ImageExport exports all images with the given tag. All versions @@ -344,15 +329,13 @@ func (srv *Server) ContainerExport(job *engine.Job) engine.Status { // out is the writer where the images are written to. func (srv *Server) ImageExport(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Usage: %s CONTAINER\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER\n", job.Name) } name := job.Args[0] // get image json tempdir, err := ioutil.TempDir("", "docker-export-") if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } defer os.RemoveAll(tempdir) @@ -360,20 +343,17 @@ func (srv *Server) ImageExport(job *engine.Job) engine.Status { rootRepo, err := srv.runtime.repositories.Get(name) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if rootRepo != nil { for _, id := range rootRepo { image, err := srv.ImageInspect(id) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if err := srv.exportImage(image, tempdir); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } @@ -383,30 +363,25 @@ func (srv *Server) ImageExport(job *engine.Job) engine.Status { rootRepoJson, _ := json.Marshal(rootRepoMap) if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.ModeAppend); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } else { image, err := srv.ImageInspect(name) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if err := srv.exportImage(image, tempdir); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } fs, err := archive.Tar(tempdir, archive.Uncompressed) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if _, err := io.Copy(job.Stdout, fs); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } @@ -468,8 +443,7 @@ func (srv *Server) exportImage(image *Image, tempdir string) error { func (srv *Server) Build(job *engine.Job) engine.Status { if len(job.Args) != 0 { - job.Errorf("Usage: %s\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s\n", job.Name) } var ( remoteURL = job.Getenv("remote") @@ -494,38 +468,32 @@ func (srv *Server) Build(job *engine.Job) engine.Status { } root, err := ioutil.TempDir("", "docker-build-git") if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } defer os.RemoveAll(root) if output, err := exec.Command("git", "clone", remoteURL, root).CombinedOutput(); err != nil { - job.Errorf("Error trying to use git: %s (%s)", err, output) - return engine.StatusErr + return job.Errorf("Error trying to use git: %s (%s)", err, output) } c, err := archive.Tar(root, archive.Uncompressed) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } context = c } else if utils.IsURL(remoteURL) { f, err := utils.Download(remoteURL) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } defer f.Body.Close() dockerFile, err := ioutil.ReadAll(f.Body) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } c, err := MkBuildContext(string(dockerFile), nil) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } context = c } @@ -543,8 +511,7 @@ func (srv *Server) Build(job *engine.Job) engine.Status { !suppressOutput, !noCache, rm, job.Stdout, sf, authConfig, configFile) id, err := b.Build(context) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if repoName != "" { srv.runtime.repositories.Set(repoName, tag, id, false) @@ -557,8 +524,7 @@ func (srv *Server) Build(job *engine.Job) engine.Status { func (srv *Server) ImageLoad(job *engine.Job) engine.Status { tmpImageDir, err := ioutil.TempDir("", "docker-import-") if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } defer os.RemoveAll(tmpImageDir) @@ -569,40 +535,33 @@ func (srv *Server) ImageLoad(job *engine.Job) engine.Status { tarFile, err := os.Create(repoTarFile) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if _, err := io.Copy(tarFile, job.Stdin); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } tarFile.Close() repoFile, err := os.Open(repoTarFile) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if err := os.Mkdir(repoDir, os.ModeDir); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if err := archive.Untar(repoFile, repoDir, nil); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } dirs, err := ioutil.ReadDir(repoDir) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } for _, d := range dirs { if d.IsDir() { if err := srv.recursiveLoad(d.Name(), tmpImageDir); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } } @@ -611,21 +570,18 @@ func (srv *Server) ImageLoad(job *engine.Job) engine.Status { if err == nil { repositories := map[string]Repository{} if err := json.Unmarshal(repositoriesJson, &repositories); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } for imageName, tagMap := range repositories { for tag, address := range tagMap { if err := srv.runtime.repositories.Set(imageName, tag, address, true); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } } } else if !os.IsNotExist(err) { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK @@ -669,8 +625,7 @@ func (srv *Server) recursiveLoad(address, tmpImageDir string) error { func (srv *Server) ImagesSearch(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { - job.Errorf("Usage: %s TERM", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s TERM", job.Name) } var ( term = job.Args[0] @@ -682,13 +637,11 @@ func (srv *Server) ImagesSearch(job *engine.Job) engine.Status { r, err := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), auth.IndexServerAddress()) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } results, err := r.SearchRepositories(term) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } outs := engine.NewTable("star_count", 0) for _, result := range results.Results { @@ -698,16 +651,14 @@ func (srv *Server) ImagesSearch(job *engine.Job) engine.Status { } outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } func (srv *Server) ImageInsert(job *engine.Job) engine.Status { if len(job.Args) != 3 { - job.Errorf("Usage: %s IMAGE URL PATH\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s IMAGE URL PATH\n", job.Name) } var ( @@ -721,32 +672,27 @@ func (srv *Server) ImageInsert(job *engine.Job) engine.Status { out := utils.NewWriteFlusher(job.Stdout) img, err := srv.runtime.repositories.LookupImage(name) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } file, err := utils.Download(url) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } defer file.Body.Close() config, _, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.sysInfo) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } c, _, err := srv.runtime.Create(config, "") if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if err := c.Inject(utils.ProgressReader(file.Body, int(file.ContentLength), out, sf, false, utils.TruncateID(img.ID), "Downloading"), path); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } // FIXME: Handle custom repo, tag comment, author img, err = srv.runtime.Commit(c, "", "", img.Comment, img.Author, nil) @@ -772,8 +718,7 @@ func (srv *Server) ImagesViz(job *engine.Job) engine.Status { for _, image := range images { parentImage, err = image.GetParent() if err != nil { - job.Errorf("Error while getting parent image: %v", err) - return engine.StatusErr + return job.Errorf("Error while getting parent image: %v", err) } if parentImage != nil { job.Stdout.Write([]byte(" \"" + parentImage.ID + "\" -> \"" + image.ID + "\"\n")) @@ -808,8 +753,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { allImages, err = srv.runtime.graph.Heads() } if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } lookup := make(map[string]*engine.Env) for name, repository := range srv.runtime.repositories.Repositories { @@ -863,8 +807,7 @@ func (srv *Server) Images(job *engine.Job) engine.Status { outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } @@ -907,22 +850,19 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { v.Set("InitSha1", utils.INITSHA1) v.Set("InitPath", initPath) if _, err := v.WriteTo(job.Stdout); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } func (srv *Server) ImageHistory(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { - job.Errorf("Usage: %s IMAGE", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s IMAGE", job.Name) } name := job.Args[0] image, err := srv.runtime.repositories.LookupImage(name) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } lookupMap := make(map[string][]string) @@ -949,16 +889,14 @@ func (srv *Server) ImageHistory(job *engine.Job) engine.Status { }) outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } func (srv *Server) ContainerTop(job *engine.Job) engine.Status { if len(job.Args) != 1 && len(job.Args) != 2 { - job.Errorf("Not enough arguments. Usage: %s CONTAINER [PS_ARGS]\n", job.Name) - return engine.StatusErr + return job.Errorf("Not enough arguments. Usage: %s CONTAINER [PS_ARGS]\n", job.Name) } var ( name = job.Args[0] @@ -971,18 +909,15 @@ func (srv *Server) ContainerTop(job *engine.Job) engine.Status { if container := srv.runtime.Get(name); container != nil { if !container.State.IsRunning() { - job.Errorf("Container %s is not running", name) - return engine.StatusErr + return job.Errorf("Container %s is not running", name) } pids, err := srv.runtime.execDriver.GetPidsForContainer(container.ID) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } output, err := exec.Command("ps", psArgs).Output() if err != nil { - job.Errorf("Error running ps: %s", err) - return engine.StatusErr + return job.Errorf("Error running ps: %s", err) } lines := strings.Split(string(output), "\n") @@ -997,8 +932,7 @@ func (srv *Server) ContainerTop(job *engine.Job) engine.Status { } } if pidIndex == -1 { - job.Errorf("Couldn't find PID field in ps output") - return engine.StatusErr + return job.Errorf("Couldn't find PID field in ps output") } processes := [][]string{} @@ -1009,8 +943,7 @@ func (srv *Server) ContainerTop(job *engine.Job) engine.Status { fields := strings.Fields(line) p, err := strconv.Atoi(fields[pidIndex]) if err != nil { - job.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) - return engine.StatusErr + return job.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) } for _, pid := range pids { @@ -1028,38 +961,32 @@ func (srv *Server) ContainerTop(job *engine.Job) engine.Status { return engine.StatusOK } - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } func (srv *Server) ContainerChanges(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { - job.Errorf("Usage: %s CONTAINER", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER", job.Name) } name := job.Args[0] if container := srv.runtime.Get(name); container != nil { outs := engine.NewTable("", 0) changes, err := container.Changes() if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } for _, change := range changes { out := &engine.Env{} if err := out.Import(change); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } outs.Add(out) } if _, err := outs.WriteListTo(job.Stdout); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } else { - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } return engine.StatusOK } @@ -1108,8 +1035,7 @@ func (srv *Server) Containers(job *engine.Job) engine.Status { out.Set("Status", container.State.String()) str, err := container.NetworkSettings.PortMappingAPI().ToListString() if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } out.Set("Ports", str) if size { @@ -1121,34 +1047,29 @@ func (srv *Server) Containers(job *engine.Job) engine.Status { } outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } func (srv *Server) ContainerCommit(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) - return engine.StatusErr + return job.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) } name := job.Args[0] container := srv.runtime.Get(name) if container == nil { - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } var config Config if err := job.GetenvJson("config", &config); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } img, err := srv.runtime.Commit(container, job.Getenv("repo"), job.Getenv("tag"), job.Getenv("comment"), job.Getenv("author"), &config) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } job.Printf("%s\n", img.ID) return engine.StatusOK @@ -1156,16 +1077,14 @@ func (srv *Server) ContainerCommit(job *engine.Job) engine.Status { func (srv *Server) ImageTag(job *engine.Job) engine.Status { if len(job.Args) != 2 && len(job.Args) != 3 { - job.Errorf("Usage: %s IMAGE REPOSITORY [TAG]\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s IMAGE REPOSITORY [TAG]\n", job.Name) } var tag string if len(job.Args) == 3 { tag = job.Args[2] } if err := srv.runtime.repositories.Set(job.Args[1], tag, job.Args[0], job.GetenvBool("force")); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } @@ -1402,8 +1321,7 @@ func (srv *Server) poolRemove(kind, key string) error { func (srv *Server) ImagePull(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 && n != 2 { - job.Errorf("Usage: %s IMAGE [TAG]", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s IMAGE [TAG]", job.Name) } var ( localName = job.Args[0] @@ -1427,22 +1345,19 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { <-c return engine.StatusOK } - job.Error(err) - return engine.StatusErr + return job.Error(err) } defer srv.poolRemove("pull", localName+":"+tag) // Resolve the Repository name from fqn to endpoint + name endpoint, remoteName, err := registry.ResolveRepositoryName(localName) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } r, err := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), endpoint) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if endpoint == auth.IndexServerAddress() { @@ -1451,8 +1366,7 @@ func (srv *Server) ImagePull(job *engine.Job) engine.Status { } if err = srv.pullRepository(r, job.Stdout, localName, remoteName, tag, sf, job.GetenvBool("parallel")); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK @@ -1621,8 +1535,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, // FIXME: Allow to interrupt current push when new push of same image is done. func (srv *Server) ImagePush(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { - job.Errorf("Usage: %s IMAGE", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s IMAGE", job.Name) } var ( localName = job.Args[0] @@ -1634,23 +1547,20 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status { job.GetenvJson("authConfig", authConfig) job.GetenvJson("metaHeaders", metaHeaders) if _, err := srv.poolAdd("push", localName); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } defer srv.poolRemove("push", localName) // Resolve the Repository name from fqn to endpoint + name endpoint, remoteName, err := registry.ResolveRepositoryName(localName) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } img, err := srv.runtime.graph.Get(localName) r, err2 := registry.NewRegistry(authConfig, srv.HTTPRequestFactory(metaHeaders), endpoint) if err2 != nil { - job.Error(err2) - return engine.StatusErr + return job.Error(err2) } if err != nil { @@ -1659,28 +1569,24 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status { // If it fails, try to get the repository if localRepo, exists := srv.runtime.repositories.Repositories[localName]; exists { if err := srv.pushRepository(r, job.Stdout, localName, remoteName, localRepo, sf); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } - job.Error(err) - return engine.StatusErr + return job.Error(err) } var token []string job.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", localName)) if _, err := srv.pushImage(r, job.Stdout, remoteName, img.ID, endpoint, token, sf); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } func (srv *Server) ImageImport(job *engine.Job) engine.Status { if n := len(job.Args); n != 2 && n != 3 { - job.Errorf("Usage: %s SRC REPO [TAG]", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s SRC REPO [TAG]", job.Name) } var ( src = job.Args[0] @@ -1699,8 +1605,7 @@ func (srv *Server) ImageImport(job *engine.Job) engine.Status { } else { u, err := url.Parse(src) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if u.Scheme == "" { u.Scheme = "http" @@ -1712,21 +1617,18 @@ func (srv *Server) ImageImport(job *engine.Job) engine.Status { // If curl is not available, fallback to http.Get() resp, err = utils.Download(u.String()) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } archive = utils.ProgressReader(resp.Body, int(resp.ContentLength), job.Stdout, sf, true, "", "Importing") } img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src, "", nil) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } // Optionally register the image at REPO/TAG if repo != "" { if err := srv.runtime.repositories.Set(repo, tag, img.ID, true); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } } job.Stdout.Write(sf.FormatStatus("", img.ID)) @@ -1738,13 +1640,11 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { if len(job.Args) == 1 { name = job.Args[0] } else if len(job.Args) > 1 { - job.Printf("Usage: %s", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s", job.Name) } config := ContainerConfigFromJob(job) if config.Memory != 0 && config.Memory < 524288 { - job.Errorf("Minimum memory limit allowed is 512k") - return engine.StatusErr + return job.Errorf("Minimum memory limit allowed is 512k") } if config.Memory > 0 && !srv.runtime.sysInfo.MemoryLimit { job.Errorf("WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n") @@ -1771,11 +1671,9 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { if tag == "" { tag = DEFAULTTAG } - job.Errorf("No such image: %s (tag: %s)", config.Image, tag) - return engine.StatusErr + return job.Errorf("No such image: %s (tag: %s)", config.Image, tag) } - job.Error(err) - return engine.StatusErr + return job.Error(err) } if !container.Config.NetworkDisabled && srv.runtime.sysInfo.IPv4ForwardingDisabled { job.Errorf("WARNING: IPv4 forwarding is disabled.\n") @@ -1788,15 +1686,14 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { job.Printf("%s\n", container.ID) } for _, warning := range buildWarnings { - job.Errorf("%s\n", warning) + return job.Errorf("%s\n", warning) } return engine.StatusOK } func (srv *Server) ContainerRestart(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Usage: %s CONTAINER\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER\n", job.Name) } var ( name = job.Args[0] @@ -1807,22 +1704,18 @@ func (srv *Server) ContainerRestart(job *engine.Job) engine.Status { } if container := srv.runtime.Get(name); container != nil { if err := container.Restart(int(t)); err != nil { - job.Errorf("Cannot restart container %s: %s\n", name, err) - return engine.StatusErr + return job.Errorf("Cannot restart container %s: %s\n", name, err) } srv.LogEvent("restart", container.ID, srv.runtime.repositories.ImageName(container.Image)) } else { - job.Errorf("No such container: %s\n", name) - return engine.StatusErr + return job.Errorf("No such container: %s\n", name) } return engine.StatusOK - } func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) - return engine.StatusErr + return job.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) } name := job.Args[0] removeVolume := job.GetenvBool("removeVolume") @@ -1832,23 +1725,19 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { if removeLink { if container == nil { - job.Errorf("No such link: %s", name) - return engine.StatusErr + return job.Errorf("No such link: %s", name) } name, err := getFullName(name) if err != nil { job.Error(err) - return engine.StatusErr } parent, n := path.Split(name) if parent == "/" { - job.Errorf("Conflict, cannot remove the default name of the container") - return engine.StatusErr + return job.Errorf("Conflict, cannot remove the default name of the container") } pe := srv.runtime.containerGraph.Get(parent) if pe == nil { - job.Errorf("Cannot get parent %s for name %s", parent, name) - return engine.StatusErr + return job.Errorf("Cannot get parent %s for name %s", parent, name) } parentContainer := srv.runtime.Get(pe.ID()) @@ -1861,16 +1750,14 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { } if err := srv.runtime.containerGraph.Delete(name); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } if container != nil { if container.State.IsRunning() { - job.Errorf("Impossible to remove a running container, please stop it first") - return engine.StatusErr + return job.Errorf("Impossible to remove a running container, please stop it first") } volumes := make(map[string]struct{}) @@ -1895,8 +1782,7 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { volumes[volumeId] = struct{}{} } if err := srv.runtime.Destroy(container); err != nil { - job.Errorf("Cannot destroy container %s: %s", name, err) - return engine.StatusErr + return job.Errorf("Cannot destroy container %s: %s", name, err) } srv.LogEvent("destroy", container.ID, srv.runtime.repositories.ImageName(container.Image)) @@ -1916,14 +1802,12 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { continue } if err := srv.runtime.volumes.Delete(volumeId); err != nil { - job.Errorf("Error calling volumes.Delete(%q): %v", volumeId, err) - return engine.StatusErr + return job.Errorf("Error calling volumes.Delete(%q): %v", volumeId, err) } } } } else { - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } return engine.StatusOK } @@ -2075,22 +1959,18 @@ func (srv *Server) DeleteImage(name string, autoPrune bool) (*engine.Table, erro func (srv *Server) ImageDelete(job *engine.Job) engine.Status { if n := len(job.Args); n != 1 { - job.Errorf("Usage: %s IMAGE", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s IMAGE", job.Name) } imgs, err := srv.DeleteImage(job.Args[0], job.GetenvBool("autoPrune")) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if len(imgs.Data) == 0 { - job.Errorf("Conflict, %s wasn't deleted", job.Args[0]) - return engine.StatusErr + return job.Errorf("Conflict, %s wasn't deleted", job.Args[0]) } if _, err := imgs.WriteListTo(job.Stdout); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } @@ -2180,16 +2060,14 @@ func (srv *Server) RegisterLinks(container *Container, hostConfig *HostConfig) e func (srv *Server) ContainerStart(job *engine.Job) engine.Status { if len(job.Args) < 1 { - job.Errorf("Usage: %s container_id", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s container_id", job.Name) } name := job.Args[0] runtime := srv.runtime container := runtime.Get(name) if container == nil { - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } // If no environment was set, then no hostconfig was passed. if len(job.Environ()) > 0 { @@ -2205,8 +2083,7 @@ func (srv *Server) ContainerStart(job *engine.Job) engine.Status { // refuse to bind mount "/" to the container if source == "/" { - job.Errorf("Invalid bind mount '%s' : source can't be '/'", bind) - return engine.StatusErr + return job.Errorf("Invalid bind mount '%s' : source can't be '/'", bind) } // ensure the source exists on the host @@ -2214,22 +2091,19 @@ func (srv *Server) ContainerStart(job *engine.Job) engine.Status { if err != nil && os.IsNotExist(err) { err = os.MkdirAll(source, 0755) if err != nil { - job.Errorf("Could not create local directory '%s' for bind mount: %s!", source, err.Error()) - return engine.StatusErr + return job.Errorf("Could not create local directory '%s' for bind mount: %s!", source, err.Error()) } } } // Register any links from the host config before starting the container if err := srv.RegisterLinks(container, hostConfig); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } container.hostConfig = hostConfig container.ToDisk() } if err := container.Start(); err != nil { - job.Errorf("Cannot start container %s: %s", name, err) - return engine.StatusErr + return job.Errorf("Cannot start container %s: %s", name, err) } srv.LogEvent("start", container.ID, runtime.repositories.ImageName(container.Image)) @@ -2238,8 +2112,7 @@ func (srv *Server) ContainerStart(job *engine.Job) engine.Status { func (srv *Server) ContainerStop(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Usage: %s CONTAINER\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER\n", job.Name) } var ( name = job.Args[0] @@ -2250,21 +2123,18 @@ func (srv *Server) ContainerStop(job *engine.Job) engine.Status { } if container := srv.runtime.Get(name); container != nil { if err := container.Stop(int(t)); err != nil { - job.Errorf("Cannot stop container %s: %s\n", name, err) - return engine.StatusErr + return job.Errorf("Cannot stop container %s: %s\n", name, err) } srv.LogEvent("stop", container.ID, srv.runtime.repositories.ImageName(container.Image)) } else { - job.Errorf("No such container: %s\n", name) - return engine.StatusErr + return job.Errorf("No such container: %s\n", name) } return engine.StatusOK } func (srv *Server) ContainerWait(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Usage: %s", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s", job.Name) } name := job.Args[0] if container := srv.runtime.Get(name); container != nil { @@ -2272,41 +2142,34 @@ func (srv *Server) ContainerWait(job *engine.Job) engine.Status { job.Printf("%d\n", status) return engine.StatusOK } - job.Errorf("%s: no such container: %s", job.Name, name) - return engine.StatusErr + return job.Errorf("%s: no such container: %s", job.Name, name) } func (srv *Server) ContainerResize(job *engine.Job) engine.Status { if len(job.Args) != 3 { - job.Errorf("Not enough arguments. Usage: %s CONTAINER HEIGHT WIDTH\n", job.Name) - return engine.StatusErr + return job.Errorf("Not enough arguments. Usage: %s CONTAINER HEIGHT WIDTH\n", job.Name) } name := job.Args[0] height, err := strconv.Atoi(job.Args[1]) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } width, err := strconv.Atoi(job.Args[2]) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if container := srv.runtime.Get(name); container != nil { if err := container.Resize(height, width); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } func (srv *Server) ContainerAttach(job *engine.Job) engine.Status { if len(job.Args) != 1 { - job.Errorf("Usage: %s CONTAINER\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER\n", job.Name) } var ( @@ -2320,8 +2183,7 @@ func (srv *Server) ContainerAttach(job *engine.Job) engine.Status { container := srv.runtime.Get(name) if container == nil { - job.Errorf("No such container: %s", name) - return engine.StatusErr + return job.Errorf("No such container: %s", name) } //logs @@ -2372,8 +2234,7 @@ func (srv *Server) ContainerAttach(job *engine.Job) engine.Status { //stream if stream { if container.State.IsGhost() { - job.Errorf("Impossible to attach to a ghost container") - return engine.StatusErr + return job.Errorf("Impossible to attach to a ghost container") } var ( @@ -2427,8 +2288,7 @@ func (srv *Server) ImageInspect(name string) (*Image, error) { func (srv *Server) JobInspect(job *engine.Job) engine.Status { // TODO: deprecate KIND/conflict if n := len(job.Args); n != 2 { - job.Errorf("Usage: %s CONTAINER|IMAGE KIND", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER|IMAGE KIND", job.Name) } var ( name = job.Args[0] @@ -2440,35 +2300,30 @@ func (srv *Server) JobInspect(job *engine.Job) engine.Status { ) if conflict && image != nil && container != nil { - job.Errorf("Conflict between containers and images") - return engine.StatusErr + return job.Errorf("Conflict between containers and images") } switch kind { case "image": if errImage != nil { - job.Error(errImage) - return engine.StatusErr + return job.Error(errImage) } object = image case "container": if errContainer != nil { - job.Error(errContainer) - return engine.StatusErr + return job.Error(errContainer) } object = &struct { *Container HostConfig *HostConfig }{container, container.hostConfig} default: - job.Errorf("Unknown kind: %s", kind) - return engine.StatusErr + return job.Errorf("Unknown kind: %s", kind) } b, err := json.Marshal(object) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } job.Stdout.Write(b) return engine.StatusOK @@ -2476,8 +2331,7 @@ func (srv *Server) JobInspect(job *engine.Job) engine.Status { func (srv *Server) ContainerCopy(job *engine.Job) engine.Status { if len(job.Args) != 2 { - job.Errorf("Usage: %s CONTAINER RESOURCE\n", job.Name) - return engine.StatusErr + return job.Errorf("Usage: %s CONTAINER RESOURCE\n", job.Name) } var ( @@ -2489,19 +2343,15 @@ func (srv *Server) ContainerCopy(job *engine.Job) engine.Status { data, err := container.Copy(resource) if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if _, err := io.Copy(job.Stdout, data); err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } return engine.StatusOK } - job.Errorf("No such container: %s", name) - return engine.StatusErr - + return job.Errorf("No such container: %s", name) } func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) { From 8fbdb7b59eba078bf24546686e005cc86a60e493 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 28 Jan 2014 03:26:24 +0000 Subject: [PATCH 314/364] add setSubEnv and getSubEnv Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api.go | 18 +++++++----------- engine/env.go | 22 ++++++++++++++++++++++ engine/job.go | 8 ++++++++ server.go | 3 +-- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/api.go b/api.go index ba8646599d..4d294667ac 100644 --- a/api.go +++ b/api.go @@ -345,11 +345,11 @@ func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Req return err } var ( - config = &Config{} + config engine.Env env engine.Env job = srv.Eng.Job("commit", r.Form.Get("container")) ) - if err := json.NewDecoder(r.Body).Decode(config); err != nil && err != io.EOF { + if err := config.Import(r.Body); err != nil { utils.Errorf("%s", err) } @@ -357,7 +357,7 @@ func postCommit(srv *Server, version float64, w http.ResponseWriter, r *http.Req job.Setenv("tag", r.Form.Get("tag")) job.Setenv("author", r.Form.Get("author")) job.Setenv("comment", r.Form.Get("comment")) - job.SetenvJson("config", config) + job.SetenvSubEnv("config", &config) var id string job.Stdout.AddString(&id) @@ -704,18 +704,14 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r return fmt.Errorf("Missing parameter") } - // TODO: replace the buffer by job.AddEnv() var ( job = srv.Eng.Job("inspect", vars["name"], "container") - buffer = bytes.NewBuffer(nil) - c Container + c, err = job.Stdout.AddEnv() ) - job.Stdout.Add(buffer) - if err := job.Run(); err != nil { + if err != nil { return err } - - if err := json.Unmarshal(buffer.Bytes(), &c); err != nil { + if err = job.Run(); err != nil { return err } @@ -742,7 +738,7 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") - if !c.Config.Tty && version >= 1.6 { + if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version >= 1.6 { errStream = utils.NewStdWriter(outStream, utils.Stderr) outStream = utils.NewStdWriter(outStream, utils.Stdout) } else { diff --git a/engine/env.go b/engine/env.go index d6ca4ec07b..ce8c34bb24 100644 --- a/engine/env.go +++ b/engine/env.go @@ -86,6 +86,28 @@ func (env *Env) GetList(key string) []string { return l } +func (env *Env) GetSubEnv(key string) *Env { + sval := env.Get(key) + if sval == "" { + return nil + } + buf := bytes.NewBufferString(sval) + var sub Env + if err := sub.Decode(buf); err != nil { + return nil + } + return &sub +} + +func (env *Env) SetSubEnv(key string, sub *Env) error { + var buf bytes.Buffer + if err := sub.Encode(&buf); err != nil { + return err + } + env.Set(key, string(buf.Bytes())) + return nil +} + func (env *Env) GetJson(key string, iface interface{}) error { sval := env.Get(key) if sval == "" { diff --git a/engine/job.go b/engine/job.go index 782bb02171..1f35ac85ff 100644 --- a/engine/job.go +++ b/engine/job.go @@ -118,6 +118,14 @@ func (job *Job) SetenvBool(key string, value bool) { job.env.SetBool(key, value) } +func (job *Job) GetenvSubEnv(key string) *Env { + return job.env.GetSubEnv(key) +} + +func (job *Job) SetenvSubEnv(key string, value *Env) error { + return job.env.SetSubEnv(key, value) +} + func (job *Job) GetenvInt64(key string) int64 { return job.env.GetInt64(key) } diff --git a/server.go b/server.go index 6eeca79d90..d8195f3de0 100644 --- a/server.go +++ b/server.go @@ -1656,8 +1656,7 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { } resolvConf, err := utils.GetResolvConf() if err != nil { - job.Error(err) - return engine.StatusErr + return job.Error(err) } if !config.NetworkDisabled && len(config.Dns) == 0 && len(srv.runtime.config.Dns) == 0 && utils.CheckLocalDns(resolvConf) { job.Errorf("WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v\n", defaultDns) From d86c1b064dad349737b4a423fe8d0c42db4ca45e Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 30 Jan 2014 23:05:16 -0800 Subject: [PATCH 315/364] Not not allocate networks first ip Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- networkdriver/ipallocator/allocator.go | 7 ++++++- networkdriver/ipallocator/allocator_test.go | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/networkdriver/ipallocator/allocator.go b/networkdriver/ipallocator/allocator.go index 33401d5caf..1c5a7b4cc2 100644 --- a/networkdriver/ipallocator/allocator.go +++ b/networkdriver/ipallocator/allocator.go @@ -99,12 +99,17 @@ func getNextIp(address *net.IPNet) (*net.IP, error) { return ip, nil } + var ( + firstNetIP = address.IP.To4().Mask(address.Mask) + firstAsInt = ipToInt(&firstNetIP) + 1 + ) + pos = int32(allocated.PullBack()) for i := int32(0); i < max; i++ { pos = pos%max + 1 next := int32(base + pos) - if next == ownIP { + if next == ownIP || next == firstAsInt { continue } diff --git a/networkdriver/ipallocator/allocator_test.go b/networkdriver/ipallocator/allocator_test.go index 871f143521..5e9fcfc983 100644 --- a/networkdriver/ipallocator/allocator_test.go +++ b/networkdriver/ipallocator/allocator_test.go @@ -213,6 +213,27 @@ func TestIPAllocator(t *testing.T) { } } +func TestAllocateFirstIP(t *testing.T) { + defer reset() + network := &net.IPNet{ + IP: []byte{192, 168, 0, 0}, + Mask: []byte{255, 255, 255, 0}, + } + + firstIP := network.IP.To4().Mask(network.Mask) + first := ipToInt(&firstIP) + 1 + + ip, err := RequestIP(network, nil) + if err != nil { + t.Fatal(err) + } + allocated := ipToInt(ip) + + if allocated == first { + t.Fatalf("allocated ip should not equal first ip: %d == %d", first, allocated) + } +} + func assertIPEquals(t *testing.T, ip1, ip2 *net.IP) { if !ip1.Equal(*ip2) { t.Fatalf("Expected IP %s, got %s", ip1, ip2) From 065dd231dd7d7858df982a8decfade9df936cf63 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 31 Jan 2014 03:16:42 -0700 Subject: [PATCH 316/364] Update/fix build tags, Dockerfile, and release.sh for proper building and releasing of linux/386 and linux/arm cross-compiled client binaries Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- Dockerfile | 5 +++-- archive/{stat_unsupported.go => stat_darwin.go} | 3 ++- execdriver/lxc/lxc_init_linux.go | 2 ++ execdriver/lxc/lxc_init_unsupported.go | 3 ++- graphdriver/aufs/mount_linux.go | 2 ++ graphdriver/aufs/mount_unsupported.go | 3 ++- graphdriver/btrfs/btrfs.go | 2 +- graphdriver/devmapper/attach_loopback.go | 2 +- graphdriver/devmapper/deviceset.go | 2 +- graphdriver/devmapper/devmapper.go | 2 +- graphdriver/devmapper/devmapper_log.go | 2 +- graphdriver/devmapper/devmapper_test.go | 2 +- graphdriver/devmapper/devmapper_wrapper.go | 2 +- graphdriver/devmapper/driver.go | 2 +- graphdriver/devmapper/driver_test.go | 2 +- graphdriver/devmapper/ioctl.go | 2 +- graphdriver/devmapper/mount.go | 2 +- graphdriver/devmapper/sys.go | 2 +- hack/release.sh | 3 ++- pkg/graphdb/conn_linux.go | 2 ++ pkg/graphdb/{conn_darwin.go => conn_unsupported.go} | 2 ++ pkg/mount/flags_linux.go | 2 ++ pkg/mount/flags_unsupported.go | 3 ++- pkg/mount/mounter_linux.go | 2 ++ pkg/mount/mounter_unsupported.go | 3 ++- pkg/netlink/netlink_linux.go | 2 ++ pkg/netlink/netlink_unsupported.go | 3 ++- reflink_copy_linux.go | 2 ++ reflink_copy_unsupported.go | 3 ++- utils/uname_linux.go | 2 ++ utils/uname_unsupported.go | 3 ++- 31 files changed, 51 insertions(+), 23 deletions(-) rename archive/{stat_unsupported.go => stat_darwin.go} (92%) rename pkg/graphdb/{conn_darwin.go => conn_unsupported.go} (79%) diff --git a/Dockerfile b/Dockerfile index 5cee5e67d0..ce1c0957e5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -68,8 +68,9 @@ ENV GOPATH /go:/go/src/github.com/dotcloud/docker/vendor RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1 # Compile Go for cross compilation -ENV DOCKER_CROSSPLATFORMS darwin/amd64 darwin/386 -# TODO add linux/386 and linux/arm +ENV DOCKER_CROSSPLATFORMS linux/386 linux/arm darwin/amd64 darwin/386 +# (set an explicit GOARM of 5 for maximum compatibility) +ENV GOARM 5 RUN cd /usr/local/go/src && bash -xc 'for platform in $DOCKER_CROSSPLATFORMS; do GOOS=${platform%/*} GOARCH=${platform##*/} ./make.bash --no-clean 2>&1; done' # Grab Go's cover tool for dead-simple code coverage testing diff --git a/archive/stat_unsupported.go b/archive/stat_darwin.go similarity index 92% rename from archive/stat_unsupported.go rename to archive/stat_darwin.go index 6c2d3a04b3..32203299dd 100644 --- a/archive/stat_unsupported.go +++ b/archive/stat_darwin.go @@ -1,4 +1,5 @@ -// +build: !linux !amd64 +// +build !linux !amd64 + package archive import "syscall" diff --git a/execdriver/lxc/lxc_init_linux.go b/execdriver/lxc/lxc_init_linux.go index b0055c3668..7288f5877b 100644 --- a/execdriver/lxc/lxc_init_linux.go +++ b/execdriver/lxc/lxc_init_linux.go @@ -1,3 +1,5 @@ +// +build amd64 + package lxc import ( diff --git a/execdriver/lxc/lxc_init_unsupported.go b/execdriver/lxc/lxc_init_unsupported.go index 6b19b99285..d68cb91a1e 100644 --- a/execdriver/lxc/lxc_init_unsupported.go +++ b/execdriver/lxc/lxc_init_unsupported.go @@ -1,4 +1,5 @@ -// +build: !linux !amd64 +// +build !linux !amd64 + package lxc func setHostname(hostname string) error { diff --git a/graphdriver/aufs/mount_linux.go b/graphdriver/aufs/mount_linux.go index c86f1bbd63..6082d9f240 100644 --- a/graphdriver/aufs/mount_linux.go +++ b/graphdriver/aufs/mount_linux.go @@ -1,3 +1,5 @@ +// +build amd64 + package aufs import "syscall" diff --git a/graphdriver/aufs/mount_unsupported.go b/graphdriver/aufs/mount_unsupported.go index 24b64226d2..2735624112 100644 --- a/graphdriver/aufs/mount_unsupported.go +++ b/graphdriver/aufs/mount_unsupported.go @@ -1,4 +1,5 @@ -// +build: !linux !amd64 +// +build !linux !amd64 + package aufs import "errors" diff --git a/graphdriver/btrfs/btrfs.go b/graphdriver/btrfs/btrfs.go index e8dc6bd0e9..a50f11f851 100644 --- a/graphdriver/btrfs/btrfs.go +++ b/graphdriver/btrfs/btrfs.go @@ -1,4 +1,4 @@ -// +build linux +// +build linux,amd64 package btrfs diff --git a/graphdriver/devmapper/attach_loopback.go b/graphdriver/devmapper/attach_loopback.go index 456b5645f4..23339076e8 100644 --- a/graphdriver/devmapper/attach_loopback.go +++ b/graphdriver/devmapper/attach_loopback.go @@ -1,4 +1,4 @@ -// +build linux +// +build linux,amd64 package devmapper diff --git a/graphdriver/devmapper/deviceset.go b/graphdriver/devmapper/deviceset.go index 6e3caf657d..8432d92a4e 100644 --- a/graphdriver/devmapper/deviceset.go +++ b/graphdriver/devmapper/deviceset.go @@ -1,4 +1,4 @@ -// +build linux +// +build linux,amd64 package devmapper diff --git a/graphdriver/devmapper/devmapper.go b/graphdriver/devmapper/devmapper.go index d3eba78a27..7f83a09df9 100644 --- a/graphdriver/devmapper/devmapper.go +++ b/graphdriver/devmapper/devmapper.go @@ -1,4 +1,4 @@ -// +build linux +// +build linux,amd64 package devmapper diff --git a/graphdriver/devmapper/devmapper_log.go b/graphdriver/devmapper/devmapper_log.go index 8d54ad4e3a..18dde7cca5 100644 --- a/graphdriver/devmapper/devmapper_log.go +++ b/graphdriver/devmapper/devmapper_log.go @@ -1,4 +1,4 @@ -// +build linux +// +build linux,amd64 package devmapper diff --git a/graphdriver/devmapper/devmapper_test.go b/graphdriver/devmapper/devmapper_test.go index a43e32e059..3ffa163ceb 100644 --- a/graphdriver/devmapper/devmapper_test.go +++ b/graphdriver/devmapper/devmapper_test.go @@ -1,4 +1,4 @@ -// +build linux +// +build linux,amd64 package devmapper diff --git a/graphdriver/devmapper/devmapper_wrapper.go b/graphdriver/devmapper/devmapper_wrapper.go index 7e6dd5e0cb..bf558affc8 100644 --- a/graphdriver/devmapper/devmapper_wrapper.go +++ b/graphdriver/devmapper/devmapper_wrapper.go @@ -1,4 +1,4 @@ -// +build linux +// +build linux,amd64 package devmapper diff --git a/graphdriver/devmapper/driver.go b/graphdriver/devmapper/driver.go index dae712b9b5..664899cfbf 100644 --- a/graphdriver/devmapper/driver.go +++ b/graphdriver/devmapper/driver.go @@ -1,4 +1,4 @@ -// +build linux +// +build linux,amd64 package devmapper diff --git a/graphdriver/devmapper/driver_test.go b/graphdriver/devmapper/driver_test.go index 9a2e409b63..785845ce6e 100644 --- a/graphdriver/devmapper/driver_test.go +++ b/graphdriver/devmapper/driver_test.go @@ -1,4 +1,4 @@ -// +build linux +// +build linux,amd64 package devmapper diff --git a/graphdriver/devmapper/ioctl.go b/graphdriver/devmapper/ioctl.go index f9bf34f353..30bafff943 100644 --- a/graphdriver/devmapper/ioctl.go +++ b/graphdriver/devmapper/ioctl.go @@ -1,4 +1,4 @@ -// +build linux +// +build linux,amd64 package devmapper diff --git a/graphdriver/devmapper/mount.go b/graphdriver/devmapper/mount.go index d0050484bf..4f19109bf8 100644 --- a/graphdriver/devmapper/mount.go +++ b/graphdriver/devmapper/mount.go @@ -1,4 +1,4 @@ -// +build linux +// +build linux,amd64 package devmapper diff --git a/graphdriver/devmapper/sys.go b/graphdriver/devmapper/sys.go index 540c468988..5a9ab4d74b 100644 --- a/graphdriver/devmapper/sys.go +++ b/graphdriver/devmapper/sys.go @@ -1,4 +1,4 @@ -// +build linux +// +build linux,amd64 package devmapper diff --git a/hack/release.sh b/hack/release.sh index 9a38d2b6e9..50913dd395 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -151,7 +151,8 @@ release_build() { S3ARCH=i386 ;; arm) - # GOARCH is fine + S3ARCH=armel + # someday, we might potentially support mutliple GOARM values, in which case we might get armhf here too ;; *) echo >&2 "error: can't convert $S3ARCH to an appropriate value for 'uname -m'" diff --git a/pkg/graphdb/conn_linux.go b/pkg/graphdb/conn_linux.go index 2bd51940ce..7a1ab8c92f 100644 --- a/pkg/graphdb/conn_linux.go +++ b/pkg/graphdb/conn_linux.go @@ -1,3 +1,5 @@ +// +build amd64 + package graphdb import ( diff --git a/pkg/graphdb/conn_darwin.go b/pkg/graphdb/conn_unsupported.go similarity index 79% rename from pkg/graphdb/conn_darwin.go rename to pkg/graphdb/conn_unsupported.go index 6e75fd8edb..c2d602569f 100644 --- a/pkg/graphdb/conn_darwin.go +++ b/pkg/graphdb/conn_unsupported.go @@ -1,3 +1,5 @@ +// +build !linux !amd64 + package graphdb func NewSqliteConn(root string) (*Database, error) { diff --git a/pkg/mount/flags_linux.go b/pkg/mount/flags_linux.go index 6f4c7acffa..e2e1f91ab9 100644 --- a/pkg/mount/flags_linux.go +++ b/pkg/mount/flags_linux.go @@ -1,3 +1,5 @@ +// +build amd64 + package mount import ( diff --git a/pkg/mount/flags_unsupported.go b/pkg/mount/flags_unsupported.go index d0b59a63bd..c894efe5b1 100644 --- a/pkg/mount/flags_unsupported.go +++ b/pkg/mount/flags_unsupported.go @@ -1,4 +1,5 @@ -// +build: !linux !amd64 +// +build !linux !amd64 + package mount func parseOptions(options string) (int, string) { diff --git a/pkg/mount/mounter_linux.go b/pkg/mount/mounter_linux.go index dd4280c777..70b7798de5 100644 --- a/pkg/mount/mounter_linux.go +++ b/pkg/mount/mounter_linux.go @@ -1,3 +1,5 @@ +// +build amd64 + package mount import ( diff --git a/pkg/mount/mounter_unsupported.go b/pkg/mount/mounter_unsupported.go index 1dd7458eb0..ee27b35f89 100644 --- a/pkg/mount/mounter_unsupported.go +++ b/pkg/mount/mounter_unsupported.go @@ -1,4 +1,5 @@ -// +build: !linux !amd64 +// +build !linux !amd64 + package mount func mount(device, target, mType string, flag uintptr, data string) error { diff --git a/pkg/netlink/netlink_linux.go b/pkg/netlink/netlink_linux.go index ab572e397a..0ea5b4dbac 100644 --- a/pkg/netlink/netlink_linux.go +++ b/pkg/netlink/netlink_linux.go @@ -1,3 +1,5 @@ +// +build amd64 + package netlink import ( diff --git a/pkg/netlink/netlink_unsupported.go b/pkg/netlink/netlink_unsupported.go index 3fa0c1e93f..cd796b373f 100644 --- a/pkg/netlink/netlink_unsupported.go +++ b/pkg/netlink/netlink_unsupported.go @@ -1,4 +1,5 @@ -// +build: !linux !amd64 +// +build !linux !amd64 + package netlink import ( diff --git a/reflink_copy_linux.go b/reflink_copy_linux.go index 83c7f75413..74a0cb98f7 100644 --- a/reflink_copy_linux.go +++ b/reflink_copy_linux.go @@ -1,3 +1,5 @@ +// +build amd64 + package docker // FIXME: This could be easily rewritten in pure Go diff --git a/reflink_copy_unsupported.go b/reflink_copy_unsupported.go index 8ad94c4f60..271ed0178f 100644 --- a/reflink_copy_unsupported.go +++ b/reflink_copy_unsupported.go @@ -1,4 +1,5 @@ -// +build: !linux !amd64 +// +build !linux !amd64 + package docker import ( diff --git a/utils/uname_linux.go b/utils/uname_linux.go index 063f932c99..2f4afb41bd 100644 --- a/utils/uname_linux.go +++ b/utils/uname_linux.go @@ -1,3 +1,5 @@ +// +build amd64 + package utils import ( diff --git a/utils/uname_unsupported.go b/utils/uname_unsupported.go index 79ea51bd86..57b82ecab8 100644 --- a/utils/uname_unsupported.go +++ b/utils/uname_unsupported.go @@ -1,4 +1,5 @@ -// +build: !linux !amd64 +// +build !linux !amd64 + package utils import ( From 7b2ec7fb2d13776ab02b63f32821519621b2c4e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Mui=C3=B1o?= Date: Fri, 31 Jan 2014 14:48:06 +0100 Subject: [PATCH 317/364] Unit test ensuring that a tar.TypeXGlobalHeader does not cause an error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Abel Muiño (github: amuino) --- archive/archive_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/archive/archive_test.go b/archive/archive_test.go index a5629deff1..891f977dcf 100644 --- a/archive/archive_test.go +++ b/archive/archive_test.go @@ -1,6 +1,7 @@ package archive import ( + "archive/tar" "bytes" "fmt" "io" @@ -124,3 +125,14 @@ func TestTarUntar(t *testing.T) { } } } + +// Some tar archives such as http://haproxy.1wt.eu/download/1.5/src/devel/haproxy-1.5-dev21.tar.gz +// use PAX Global Extended Headers. +// Failing prevents the archives from being uncompressed during ADD +func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) { + hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader} + err := createTarFile("pax_global_header", "some_dir", &hdr, nil) + if err != nil { + t.Fatal(err) + } +} From ce74c8b4d2e68256d85063ee237c8d41174b27c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abel=20Mui=C3=B1o?= Date: Fri, 31 Jan 2014 14:50:07 +0100 Subject: [PATCH 318/364] Ignore tar.TypeXGlobalHeader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Abel Muiño (github: amuino) --- archive/archive.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/archive/archive.go b/archive/archive.go index 727e9289fa..b1400c2210 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -228,6 +228,10 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader *tar.Reader) return err } + case tar.TypeXGlobalHeader: + utils.Debugf("PAX Global Extended Headers found and ignored") + return nil + default: return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag) } From 65794a2c4960f9065ee439d6823ffe670610dbb6 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 31 Jan 2014 11:33:48 -0800 Subject: [PATCH 319/364] fix panic in mflag Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- pkg/mflag/example/example.go | 13 ++++++++----- pkg/mflag/flag.go | 5 +++++ pkg/mflag/flag_test.go | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/pkg/mflag/example/example.go b/pkg/mflag/example/example.go index 543592d23b..fa26c97e1b 100644 --- a/pkg/mflag/example/example.go +++ b/pkg/mflag/example/example.go @@ -6,13 +6,14 @@ import ( ) var ( - i int - str string - b, h bool + i int + str string + b, b2, h bool ) func init() { flag.BoolVar(&b, []string{"b"}, false, "a simple bool") + flag.BoolVar(&b2, []string{"-bool"}, false, "a simple bool") flag.IntVar(&i, []string{"#integer", "-integer"}, -1, "a simple integer") flag.StringVar(&str, []string{"s", "#hidden", "-string"}, "", "a simple string") //-s -hidden and --string will work, but -hidden won't be in the usage flag.BoolVar(&h, []string{"h", "#help", "-help"}, false, "display the help") @@ -22,6 +23,8 @@ func main() { if h { flag.PrintDefaults() } - fmt.Printf("%s\n", str) - fmt.Printf("%s\n", flag.Lookup("s").Value.String()) + fmt.Printf("s/#hidden/-string: %s\n", str) + fmt.Printf("b: %b\n", b) + fmt.Printf("-bool: %b\n", b2) + fmt.Printf("s/#hidden/-string(via lookup): %s\n", flag.Lookup("s").Value.String()) } diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go index 8e22c0e959..f721e04557 100644 --- a/pkg/mflag/flag.go +++ b/pkg/mflag/flag.go @@ -287,6 +287,11 @@ type Flag struct { func sortFlags(flags map[string]*Flag) []*Flag { var list sort.StringSlice for _, f := range flags { + if len(f.Names) == 1 { + list = append(list, f.Names[0]) + continue + } + found := false fName := strings.TrimPrefix(strings.TrimPrefix(f.Names[0], "#"), "-") for _, name := range list { diff --git a/pkg/mflag/flag_test.go b/pkg/mflag/flag_test.go index 631febca47..b9e8a0ef3e 100644 --- a/pkg/mflag/flag_test.go +++ b/pkg/mflag/flag_test.go @@ -228,6 +228,22 @@ func testParse(f *FlagSet, t *testing.T) { } } +func testPanic(f *FlagSet, t *testing.T) { + f.Int([]string{"-int"}, 0, "int value") + if f.Parsed() { + t.Error("f.Parse() = true before Parse") + } + args := []string{ + "-int", "21", + } + f.Parse(args) +} + +func TestParsePanic(t *testing.T) { + ResetForTesting(func() {}) + testPanic(CommandLine, t) +} + func TestParse(t *testing.T) { ResetForTesting(func() { t.Error("bad parse") }) testParse(CommandLine, t) From 90494600d357d01310d8da1310b6ad02f56de22c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 29 Jan 2014 16:59:21 -0800 Subject: [PATCH 320/364] Finish moving networking information into driver Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- network.go | 413 ------------------------------------ networkdriver/lxc/driver.go | 400 ++++++++++++++++++++++++++++++++++ networkdriver/network.go | 70 ------ networkdriver/utils.go | 102 +++++++++ 4 files changed, 502 insertions(+), 483 deletions(-) delete mode 100644 network.go create mode 100644 networkdriver/lxc/driver.go create mode 100644 networkdriver/utils.go diff --git a/network.go b/network.go deleted file mode 100644 index d9771ac008..0000000000 --- a/network.go +++ /dev/null @@ -1,413 +0,0 @@ -package docker - -import ( - "fmt" - "github.com/dotcloud/docker/networkdriver" - "github.com/dotcloud/docker/networkdriver/ipallocator" - "github.com/dotcloud/docker/networkdriver/portallocator" - "github.com/dotcloud/docker/networkdriver/portmapper" - "github.com/dotcloud/docker/pkg/iptables" - "github.com/dotcloud/docker/pkg/netlink" - "github.com/dotcloud/docker/utils" - "io/ioutil" - "log" - "net" - "strconv" - "syscall" - "unsafe" -) - -const ( - DefaultNetworkBridge = "docker0" - DisableNetworkBridge = "none" - DefaultNetworkMtu = 1500 - siocBRADDBR = 0x89a0 -) - -// CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`, -// and attempts to configure it with an address which doesn't conflict with any other interface on the host. -// If it can't find an address which doesn't conflict, it will return an error. -func CreateBridgeIface(config *DaemonConfig) error { - addrs := []string{ - // Here we don't follow the convention of using the 1st IP of the range for the gateway. - // This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges. - // In theory this shouldn't matter - in practice there's bound to be a few scripts relying - // on the internal addressing or other stupid things like that. - // The shouldn't, but hey, let's not break them unless we really have to. - "172.17.42.1/16", // Don't use 172.16.0.0/16, it conflicts with EC2 DNS 172.16.0.23 - "10.0.42.1/16", // Don't even try using the entire /8, that's too intrusive - "10.1.42.1/16", - "10.42.42.1/16", - "172.16.42.1/24", - "172.16.43.1/24", - "172.16.44.1/24", - "10.0.42.1/24", - "10.0.43.1/24", - "192.168.42.1/24", - "192.168.43.1/24", - "192.168.44.1/24", - } - - nameservers := []string{} - resolvConf, _ := utils.GetResolvConf() - // we don't check for an error here, because we don't really care - // if we can't read /etc/resolv.conf. So instead we skip the append - // if resolvConf is nil. It either doesn't exist, or we can't read it - // for some reason. - if resolvConf != nil { - nameservers = append(nameservers, utils.GetNameserversAsCIDR(resolvConf)...) - } - - var ifaceAddr string - if len(config.BridgeIp) != 0 { - _, _, err := net.ParseCIDR(config.BridgeIp) - if err != nil { - return err - } - ifaceAddr = config.BridgeIp - } else { - for _, addr := range addrs { - _, dockerNetwork, err := net.ParseCIDR(addr) - if err != nil { - return err - } - if err := networkdriver.CheckNameserverOverlaps(nameservers, dockerNetwork); err == nil { - if err := networkdriver.CheckRouteOverlaps(dockerNetwork); err == nil { - ifaceAddr = addr - break - } else { - utils.Debugf("%s %s", addr, err) - } - } - } - } - - if ifaceAddr == "" { - return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", config.BridgeIface, config.BridgeIface) - } - utils.Debugf("Creating bridge %s with network %s", config.BridgeIface, ifaceAddr) - - if err := createBridgeIface(config.BridgeIface); err != nil { - return err - } - iface, err := net.InterfaceByName(config.BridgeIface) - if err != nil { - return err - } - ipAddr, ipNet, err := net.ParseCIDR(ifaceAddr) - if err != nil { - return err - } - if netlink.NetworkLinkAddIp(iface, ipAddr, ipNet); err != nil { - return fmt.Errorf("Unable to add private network: %s", err) - } - if err := netlink.NetworkLinkUp(iface); err != nil { - return fmt.Errorf("Unable to start network bridge: %s", err) - } - - return nil -} - -// Create the actual bridge device. This is more backward-compatible than -// netlink.NetworkLinkAdd and works on RHEL 6. -func createBridgeIface(name string) error { - s, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_IP) - if err != nil { - utils.Debugf("Bridge socket creation failed IPv6 probably not enabled: %v", err) - s, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_IP) - if err != nil { - return fmt.Errorf("Error creating bridge creation socket: %s", err) - } - } - defer syscall.Close(s) - - nameBytePtr, err := syscall.BytePtrFromString(name) - if err != nil { - return fmt.Errorf("Error converting bridge name %s to byte array: %s", name, err) - } - - if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), siocBRADDBR, uintptr(unsafe.Pointer(nameBytePtr))); err != 0 { - return fmt.Errorf("Error creating bridge: %s", err) - } - return nil -} - -// Return the IPv4 address of a network interface -func getIfaceAddr(name string) (net.Addr, error) { - iface, err := net.InterfaceByName(name) - if err != nil { - return nil, err - } - addrs, err := iface.Addrs() - if err != nil { - return nil, err - } - var addrs4 []net.Addr - for _, addr := range addrs { - ip := (addr.(*net.IPNet)).IP - if ip4 := ip.To4(); len(ip4) == net.IPv4len { - addrs4 = append(addrs4, addr) - } - } - switch { - case len(addrs4) == 0: - return nil, fmt.Errorf("Interface %v has no IP addresses", name) - case len(addrs4) > 1: - fmt.Printf("Interface %v has more than 1 IPv4 address. Defaulting to using %v\n", - name, (addrs4[0].(*net.IPNet)).IP) - } - return addrs4[0], nil -} - -// Network interface represents the networking stack of a container -type NetworkInterface struct { - IPNet net.IPNet - Gateway net.IP - - manager *NetworkManager - extPorts []*Nat - disabled bool -} - -// Allocate an external port and map it to the interface -func (iface *NetworkInterface) AllocatePort(port Port, binding PortBinding) (*Nat, error) { - - if iface.disabled { - return nil, fmt.Errorf("Trying to allocate port for interface %v, which is disabled", iface) // FIXME - } - - ip := iface.manager.defaultBindingIP - - if binding.HostIp != "" { - ip = net.ParseIP(binding.HostIp) - } else { - binding.HostIp = ip.String() - } - - nat := &Nat{ - Port: port, - Binding: binding, - } - - containerPort, err := parsePort(port.Port()) - if err != nil { - return nil, err - } - - hostPort, _ := parsePort(nat.Binding.HostPort) - - extPort, err := portallocator.RequestPort(ip, nat.Port.Proto(), hostPort) - if err != nil { - return nil, err - } - - var backend net.Addr - if nat.Port.Proto() == "tcp" { - backend = &net.TCPAddr{IP: iface.IPNet.IP, Port: containerPort} - } else { - backend = &net.UDPAddr{IP: iface.IPNet.IP, Port: containerPort} - } - - if err := portmapper.Map(backend, ip, extPort); err != nil { - portallocator.ReleasePort(ip, nat.Port.Proto(), extPort) - return nil, err - } - - nat.Binding.HostPort = strconv.Itoa(extPort) - iface.extPorts = append(iface.extPorts, nat) - - return nat, nil -} - -type Nat struct { - Port Port - Binding PortBinding -} - -func (n *Nat) String() string { - return fmt.Sprintf("%s:%s:%s/%s", n.Binding.HostIp, n.Binding.HostPort, n.Port.Port(), n.Port.Proto()) -} - -// Release: Network cleanup - release all resources -func (iface *NetworkInterface) Release() { - if iface.disabled { - return - } - - for _, nat := range iface.extPorts { - hostPort, err := parsePort(nat.Binding.HostPort) - if err != nil { - log.Printf("Unable to get host port: %s", err) - continue - } - ip := net.ParseIP(nat.Binding.HostIp) - utils.Debugf("Unmaping %s/%s:%s", nat.Port.Proto, ip.String(), nat.Binding.HostPort) - - var host net.Addr - if nat.Port.Proto() == "tcp" { - host = &net.TCPAddr{IP: ip, Port: hostPort} - } else { - host = &net.UDPAddr{IP: ip, Port: hostPort} - } - - if err := portmapper.Unmap(host); err != nil { - log.Printf("Unable to unmap port %s: %s", nat, err) - } - - if err := portallocator.ReleasePort(ip, nat.Port.Proto(), hostPort); err != nil { - log.Printf("Unable to release port %s", nat) - } - } - - if err := ipallocator.ReleaseIP(iface.manager.bridgeNetwork, &iface.IPNet.IP); err != nil { - log.Printf("Unable to release ip %s\n", err) - } -} - -// Network Manager manages a set of network interfaces -// Only *one* manager per host machine should be used -type NetworkManager struct { - bridgeIface string - bridgeNetwork *net.IPNet - defaultBindingIP net.IP - disabled bool -} - -// Allocate a network interface -func (manager *NetworkManager) Allocate() (*NetworkInterface, error) { - - if manager.disabled { - return &NetworkInterface{disabled: true}, nil - } - - var ip *net.IP - var err error - - ip, err = ipallocator.RequestIP(manager.bridgeNetwork, nil) - if err != nil { - return nil, err - } - - iface := &NetworkInterface{ - IPNet: net.IPNet{IP: *ip, Mask: manager.bridgeNetwork.Mask}, - Gateway: manager.bridgeNetwork.IP, - manager: manager, - } - return iface, nil -} - -func newNetworkManager(config *DaemonConfig) (*NetworkManager, error) { - if config.BridgeIface == DisableNetworkBridge { - manager := &NetworkManager{ - disabled: true, - } - return manager, nil - } - - var network *net.IPNet - addr, err := getIfaceAddr(config.BridgeIface) - if err != nil { - // If the iface is not found, try to create it - if err := CreateBridgeIface(config); err != nil { - return nil, err - } - addr, err = getIfaceAddr(config.BridgeIface) - if err != nil { - return nil, err - } - network = addr.(*net.IPNet) - } else { - network = addr.(*net.IPNet) - } - - // Configure iptables for link support - if config.EnableIptables { - - // Enable NAT - natArgs := []string{"POSTROUTING", "-t", "nat", "-s", addr.String(), "!", "-d", addr.String(), "-j", "MASQUERADE"} - - if !iptables.Exists(natArgs...) { - if output, err := iptables.Raw(append([]string{"-I"}, natArgs...)...); err != nil { - return nil, fmt.Errorf("Unable to enable network bridge NAT: %s", err) - } else if len(output) != 0 { - return nil, fmt.Errorf("Error iptables postrouting: %s", output) - } - } - - args := []string{"FORWARD", "-i", config.BridgeIface, "-o", config.BridgeIface, "-j"} - acceptArgs := append(args, "ACCEPT") - dropArgs := append(args, "DROP") - - if !config.InterContainerCommunication { - iptables.Raw(append([]string{"-D"}, acceptArgs...)...) - if !iptables.Exists(dropArgs...) { - utils.Debugf("Disable inter-container communication") - if output, err := iptables.Raw(append([]string{"-I"}, dropArgs...)...); err != nil { - return nil, fmt.Errorf("Unable to prevent intercontainer communication: %s", err) - } else if len(output) != 0 { - return nil, fmt.Errorf("Error disabling intercontainer communication: %s", output) - } - } - } else { - iptables.Raw(append([]string{"-D"}, dropArgs...)...) - if !iptables.Exists(acceptArgs...) { - utils.Debugf("Enable inter-container communication") - if output, err := iptables.Raw(append([]string{"-I"}, acceptArgs...)...); err != nil { - return nil, fmt.Errorf("Unable to allow intercontainer communication: %s", err) - } else if len(output) != 0 { - return nil, fmt.Errorf("Error enabling intercontainer communication: %s", output) - } - } - } - - // Accept all non-intercontainer outgoing packets - outgoingArgs := []string{"FORWARD", "-i", config.BridgeIface, "!", "-o", config.BridgeIface, "-j", "ACCEPT"} - - if !iptables.Exists(outgoingArgs...) { - if output, err := iptables.Raw(append([]string{"-I"}, outgoingArgs...)...); err != nil { - return nil, fmt.Errorf("Unable to allow outgoing packets: %s", err) - } else if len(output) != 0 { - return nil, fmt.Errorf("Error iptables allow outgoing: %s", output) - } - } - - // Accept incoming packets for existing connections - existingArgs := []string{"FORWARD", "-o", config.BridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"} - - if !iptables.Exists(existingArgs...) { - if output, err := iptables.Raw(append([]string{"-I"}, existingArgs...)...); err != nil { - return nil, fmt.Errorf("Unable to allow incoming packets: %s", err) - } else if len(output) != 0 { - return nil, fmt.Errorf("Error iptables allow incoming: %s", output) - } - } - - } - - if config.EnableIpForward { - // Enable IPv4 forwarding - if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte{'1', '\n'}, 0644); err != nil { - log.Printf("WARNING: unable to enable IPv4 forwarding: %s\n", err) - } - } - - // We can always try removing the iptables - if err := iptables.RemoveExistingChain("DOCKER"); err != nil { - return nil, err - } - - if config.EnableIptables { - chain, err := iptables.NewChain("DOCKER", config.BridgeIface) - if err != nil { - return nil, err - } - portmapper.SetIptablesChain(chain) - } - - manager := &NetworkManager{ - bridgeIface: config.BridgeIface, - bridgeNetwork: network, - defaultBindingIP: config.DefaultIp, - } - return manager, nil -} diff --git a/networkdriver/lxc/driver.go b/networkdriver/lxc/driver.go new file mode 100644 index 0000000000..be99d32338 --- /dev/null +++ b/networkdriver/lxc/driver.go @@ -0,0 +1,400 @@ +package lxc + +import ( + "fmt" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/networkdriver" + "github.com/dotcloud/docker/networkdriver/ipallocator" + "github.com/dotcloud/docker/networkdriver/portallocator" + "github.com/dotcloud/docker/networkdriver/portmapper" + "github.com/dotcloud/docker/pkg/iptables" + "github.com/dotcloud/docker/pkg/netlink" + "github.com/dotcloud/docker/utils" + "io/ioutil" + "log" + "net" + "syscall" + "unsafe" +) + +const ( + DefaultNetworkBridge = "docker0" + DisableNetworkBridge = "none" + DefaultNetworkMtu = 1500 + siocBRADDBR = 0x89a0 +) + +// Network interface represents the networking stack of a container +type networkInterface struct { + IP net.IP + PortMappings []net.Addr // there are mappings to the host interfaces +} + +var ( + addrs = []string{ + // Here we don't follow the convention of using the 1st IP of the range for the gateway. + // This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges. + // In theory this shouldn't matter - in practice there's bound to be a few scripts relying + // on the internal addressing or other stupid things like that. + // The shouldn't, but hey, let's not break them unless we really have to. + "172.17.42.1/16", // Don't use 172.16.0.0/16, it conflicts with EC2 DNS 172.16.0.23 + "10.0.42.1/16", // Don't even try using the entire /8, that's too intrusive + "10.1.42.1/16", + "10.42.42.1/16", + "172.16.42.1/24", + "172.16.43.1/24", + "172.16.44.1/24", + "10.0.42.1/24", + "10.0.43.1/24", + "192.168.42.1/24", + "192.168.43.1/24", + "192.168.44.1/24", + } + + bridgeIface string + defaultBindingIP net.IP + bridgeNetwork *net.IPNet + + currentInterfaces = make(map[string]*networkInterface) +) + +func init() { + if err := engine.Register("init_networkdriver", InitDriver); err != nil { + panic(err) + } +} + +func InitDriver(job *engine.Job) engine.Status { + var ( + network *net.IPNet + enableIPTables = job.GetenvBool("EnableIptables") + icc = job.GetenvBool("InterContainerCommunication") + ipForward = job.GetenvBool("EnableIpForward") + ) + bridgeIface = job.Getenv("BridgeIface") + + addr, err := networkdriver.GetIfaceAddr(bridgeIface) + if err != nil { + // If the iface is not found, try to create it + if err := createBridgeIface(bridgeIface); err != nil { + job.Error(err) + return engine.StatusErr + } + + addr, err = networkdriver.GetIfaceAddr(bridgeIface) + if err != nil { + job.Error(err) + return engine.StatusErr + } + network = addr.(*net.IPNet) + } else { + network = addr.(*net.IPNet) + } + + // Configure iptables for link support + if enableIPTables { + if err := setupIPTables(addr, icc); err != nil { + job.Error(err) + return engine.StatusErr + } + } + + if ipForward { + // Enable IPv4 forwarding + if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte{'1', '\n'}, 0644); err != nil { + job.Logf("WARNING: unable to enable IPv4 forwarding: %s\n", err) + } + } + + // We can always try removing the iptables + if err := iptables.RemoveExistingChain("DOCKER"); err != nil { + job.Error(err) + return engine.StatusErr + } + + if enableIPTables { + chain, err := iptables.NewChain("DOCKER", bridgeIface) + if err != nil { + job.Error(err) + return engine.StatusErr + } + portmapper.SetIptablesChain(chain) + } + + bridgeNetwork = network + for name, f := range map[string]engine.Handler{ + "allocate_interface": Allocate, + "release_interface": Release, + } { + if err := job.Eng.Register(name, f); err != nil { + job.Error(err) + return engine.StatusErr + } + } + return engine.StatusOK +} + +func setupIPTables(addr net.Addr, icc bool) error { + // Enable NAT + natArgs := []string{"POSTROUTING", "-t", "nat", "-s", addr.String(), "!", "-d", addr.String(), "-j", "MASQUERADE"} + + if !iptables.Exists(natArgs...) { + if output, err := iptables.Raw(append([]string{"-A"}, natArgs...)...); err != nil { + return fmt.Errorf("Unable to enable network bridge NAT: %s", err) + } else if len(output) != 0 { + return fmt.Errorf("Error iptables postrouting: %s", output) + } + } + + var ( + args = []string{"FORWARD", "-i", bridgeIface, "-o", bridgeIface, "-j"} + acceptArgs = append(args, "ACCEPT") + dropArgs = append(args, "DROP") + ) + + if !icc { + iptables.Raw(append([]string{"-D"}, acceptArgs...)...) + + if !iptables.Exists(dropArgs...) { + + utils.Debugf("Disable inter-container communication") + if output, err := iptables.Raw(append([]string{"-I"}, dropArgs...)...); err != nil { + return fmt.Errorf("Unable to prevent intercontainer communication: %s", err) + } else if len(output) != 0 { + return fmt.Errorf("Error disabling intercontainer communication: %s", output) + } + } + } else { + iptables.Raw(append([]string{"-D"}, dropArgs...)...) + + if !iptables.Exists(acceptArgs...) { + utils.Debugf("Enable inter-container communication") + if output, err := iptables.Raw(append([]string{"-I"}, acceptArgs...)...); err != nil { + return fmt.Errorf("Unable to allow intercontainer communication: %s", err) + } else if len(output) != 0 { + return fmt.Errorf("Error enabling intercontainer communication: %s", output) + } + } + } + + // Accept all non-intercontainer outgoing packets + outgoingArgs := []string{"FORWARD", "-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"} + if !iptables.Exists(outgoingArgs...) { + if output, err := iptables.Raw(append([]string{"-I"}, outgoingArgs...)...); err != nil { + return fmt.Errorf("Unable to allow outgoing packets: %s", err) + } else if len(output) != 0 { + return fmt.Errorf("Error iptables allow outgoing: %s", output) + } + } + + // Accept incoming packets for existing connections + existingArgs := []string{"FORWARD", "-o", bridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"} + + if !iptables.Exists(existingArgs...) { + if output, err := iptables.Raw(append([]string{"-I"}, existingArgs...)...); err != nil { + return fmt.Errorf("Unable to allow incoming packets: %s", err) + } else if len(output) != 0 { + return fmt.Errorf("Error iptables allow incoming: %s", output) + } + } + return nil +} + +// CreateBridgeIface creates a network bridge interface on the host system with the name `ifaceName`, +// and attempts to configure it with an address which doesn't conflict with any other interface on the host. +// If it can't find an address which doesn't conflict, it will return an error. +func createBridge(bridgeIP string) error { + nameservers := []string{} + resolvConf, _ := utils.GetResolvConf() + // we don't check for an error here, because we don't really care + // if we can't read /etc/resolv.conf. So instead we skip the append + // if resolvConf is nil. It either doesn't exist, or we can't read it + // for some reason. + if resolvConf != nil { + nameservers = append(nameservers, utils.GetNameserversAsCIDR(resolvConf)...) + } + + var ifaceAddr string + if len(bridgeIP) != 0 { + _, _, err := net.ParseCIDR(bridgeIP) + if err != nil { + return err + } + ifaceAddr = bridgeIP + } else { + for _, addr := range addrs { + _, dockerNetwork, err := net.ParseCIDR(addr) + if err != nil { + return err + } + if err := networkdriver.CheckNameserverOverlaps(nameservers, dockerNetwork); err == nil { + if err := networkdriver.CheckRouteOverlaps(dockerNetwork); err == nil { + ifaceAddr = addr + break + } else { + utils.Debugf("%s %s", addr, err) + } + } + } + } + + if ifaceAddr == "" { + return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", bridgeIface, bridgeIface) + } + utils.Debugf("Creating bridge %s with network %s", bridgeIface, ifaceAddr) + + if err := createBridgeIface(bridgeIface); err != nil { + return err + } + + iface, err := net.InterfaceByName(bridgeIface) + if err != nil { + return err + } + + ipAddr, ipNet, err := net.ParseCIDR(ifaceAddr) + if err != nil { + return err + } + + if netlink.NetworkLinkAddIp(iface, ipAddr, ipNet); err != nil { + return fmt.Errorf("Unable to add private network: %s", err) + } + if err := netlink.NetworkLinkUp(iface); err != nil { + return fmt.Errorf("Unable to start network bridge: %s", err) + } + return nil +} + +// Create the actual bridge device. This is more backward-compatible than +// netlink.NetworkLinkAdd and works on RHEL 6. +func createBridgeIface(name string) error { + s, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_STREAM, syscall.IPPROTO_IP) + if err != nil { + utils.Debugf("Bridge socket creation failed IPv6 probably not enabled: %v", err) + s, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_IP) + if err != nil { + return fmt.Errorf("Error creating bridge creation socket: %s", err) + } + } + defer syscall.Close(s) + + nameBytePtr, err := syscall.BytePtrFromString(name) + if err != nil { + return fmt.Errorf("Error converting bridge name %s to byte array: %s", name, err) + } + + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), siocBRADDBR, uintptr(unsafe.Pointer(nameBytePtr))); err != 0 { + return fmt.Errorf("Error creating bridge: %s", err) + } + return nil +} + +// Allocate a network interface +func Allocate(job *engine.Job) engine.Status { + id := job.Args[0] + + ip, err := ipallocator.RequestIP(bridgeNetwork, nil) + if err != nil { + job.Error(err) + return engine.StatusErr + } + + out := engine.Env{} + out.Set("IP", string(*ip)) + out.Set("Mask", string(bridgeNetwork.Mask)) + out.Set("Gateway", string(bridgeNetwork.IP)) + + currentInterfaces[id] = &networkInterface{ + IP: *ip, + } + + out.WriteTo(job.Stdout) + + return engine.StatusOK +} + +// release an interface for a select ip +func Release(job *engine.Job) engine.Status { + var ( + id = job.Args[0] + containerInterface = currentInterfaces[id] + ip net.IP + port int + proto string + ) + + for _, nat := range containerInterface.PortMappings { + if err := portmapper.Unmap(nat); err != nil { + log.Printf("Unable to unmap port %s: %s", nat, err) + } + + // this is host mappings + switch a := nat.(type) { + case *net.TCPAddr: + proto = "tcp" + ip = a.IP + port = a.Port + case *net.UDPAddr: + proto = "udp" + ip = a.IP + port = a.Port + } + + if err := portallocator.ReleasePort(ip, proto, port); err != nil { + log.Printf("Unable to release port %s", nat) + } + } + + if err := ipallocator.ReleaseIP(bridgeNetwork, &containerInterface.IP); err != nil { + log.Printf("Unable to release ip %s\n", err) + } + return engine.StatusOK +} + +// Allocate an external port and map it to the interface +func AllocatePort(job *engine.Job) engine.Status { + var ( + ip = defaultBindingIP + id = job.Args[0] + hostIP = job.Getenv("HostIP") + hostPort = job.GetenvInt("HostPort") + containerPort = job.GetenvInt("ContainerPort") + proto = job.Getenv("Proto") + network = currentInterfaces[id] + ) + + if hostIP != "" { + ip = net.ParseIP(hostIP) + } + + // host ip, proto, and host port + hostPort, err := portallocator.RequestPort(ip, proto, hostPort) + if err != nil { + job.Error(err) + return engine.StatusErr + } + + var ( + container net.Addr + host net.Addr + ) + + if proto == "tcp" { + host = &net.TCPAddr{IP: ip, Port: hostPort} + container = &net.TCPAddr{IP: network.IP, Port: containerPort} + } else { + host = &net.UDPAddr{IP: ip, Port: hostPort} + container = &net.UDPAddr{IP: network.IP, Port: containerPort} + } + + if err := portmapper.Map(container, ip, hostPort); err != nil { + portallocator.ReleasePort(ip, proto, hostPort) + + job.Error(err) + return engine.StatusErr + } + network.PortMappings = append(network.PortMappings, host) + + return engine.StatusOK +} diff --git a/networkdriver/network.go b/networkdriver/network.go index f8a807975b..8dda789d2f 100644 --- a/networkdriver/network.go +++ b/networkdriver/network.go @@ -1,80 +1,10 @@ package networkdriver import ( - "encoding/binary" "errors" - "github.com/dotcloud/docker/pkg/netlink" - "net" ) var ( ErrNetworkOverlapsWithNameservers = errors.New("requested network overlaps with nameserver") ErrNetworkOverlaps = errors.New("requested network overlaps with existing network") ) - -var ( - networkGetRoutesFct = netlink.NetworkGetRoutes -) - -func CheckNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error { - if len(nameservers) > 0 { - for _, ns := range nameservers { - _, nsNetwork, err := net.ParseCIDR(ns) - if err != nil { - return err - } - if NetworkOverlaps(toCheck, nsNetwork) { - return ErrNetworkOverlapsWithNameservers - } - } - } - return nil -} - -func CheckRouteOverlaps(toCheck *net.IPNet) error { - networks, err := networkGetRoutesFct() - if err != nil { - return err - } - - for _, network := range networks { - if network.IPNet != nil && NetworkOverlaps(toCheck, network.IPNet) { - return ErrNetworkOverlaps - } - } - return nil -} - -// Detects overlap between one IPNet and another -func NetworkOverlaps(netX *net.IPNet, netY *net.IPNet) bool { - if firstIP, _ := NetworkRange(netX); netY.Contains(firstIP) { - return true - } - if firstIP, _ := NetworkRange(netY); netX.Contains(firstIP) { - return true - } - return false -} - -// Calculates the first and last IP addresses in an IPNet -func NetworkRange(network *net.IPNet) (net.IP, net.IP) { - var ( - netIP = network.IP.To4() - firstIP = netIP.Mask(network.Mask) - lastIP = net.IPv4(0, 0, 0, 0).To4() - ) - - for i := 0; i < len(lastIP); i++ { - lastIP[i] = netIP[i] | ^network.Mask[i] - } - return firstIP, lastIP -} - -// Given a netmask, calculates the number of available hosts -func NetworkSize(mask net.IPMask) int32 { - m := net.IPv4Mask(0, 0, 0, 0) - for i := 0; i < net.IPv4len; i++ { - m[i] = ^mask[i] - } - return int32(binary.BigEndian.Uint32(m)) + 1 -} diff --git a/networkdriver/utils.go b/networkdriver/utils.go new file mode 100644 index 0000000000..6f6dbe4f18 --- /dev/null +++ b/networkdriver/utils.go @@ -0,0 +1,102 @@ +package networkdriver + +import ( + "encoding/binary" + "fmt" + "github.com/dotcloud/docker/pkg/netlink" + "net" +) + +var ( + networkGetRoutesFct = netlink.NetworkGetRoutes +) + +func CheckNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error { + if len(nameservers) > 0 { + for _, ns := range nameservers { + _, nsNetwork, err := net.ParseCIDR(ns) + if err != nil { + return err + } + if NetworkOverlaps(toCheck, nsNetwork) { + return ErrNetworkOverlapsWithNameservers + } + } + } + return nil +} + +func CheckRouteOverlaps(toCheck *net.IPNet) error { + networks, err := networkGetRoutesFct() + if err != nil { + return err + } + + for _, network := range networks { + if network.IPNet != nil && NetworkOverlaps(toCheck, network.IPNet) { + return ErrNetworkOverlaps + } + } + return nil +} + +// Detects overlap between one IPNet and another +func NetworkOverlaps(netX *net.IPNet, netY *net.IPNet) bool { + if firstIP, _ := NetworkRange(netX); netY.Contains(firstIP) { + return true + } + if firstIP, _ := NetworkRange(netY); netX.Contains(firstIP) { + return true + } + return false +} + +// Calculates the first and last IP addresses in an IPNet +func NetworkRange(network *net.IPNet) (net.IP, net.IP) { + var ( + netIP = network.IP.To4() + firstIP = netIP.Mask(network.Mask) + lastIP = net.IPv4(0, 0, 0, 0).To4() + ) + + for i := 0; i < len(lastIP); i++ { + lastIP[i] = netIP[i] | ^network.Mask[i] + } + return firstIP, lastIP +} + +// Given a netmask, calculates the number of available hosts +func NetworkSize(mask net.IPMask) int32 { + m := net.IPv4Mask(0, 0, 0, 0) + for i := 0; i < net.IPv4len; i++ { + m[i] = ^mask[i] + } + return int32(binary.BigEndian.Uint32(m)) + 1 +} + +// Return the IPv4 address of a network interface +func GetIfaceAddr(name string) (net.Addr, error) { + iface, err := net.InterfaceByName(name) + if err != nil { + return nil, err + } + addrs, err := iface.Addrs() + if err != nil { + return nil, err + } + var addrs4 []net.Addr + for _, addr := range addrs { + ip := (addr.(*net.IPNet)).IP + if ip4 := ip.To4(); len(ip4) == net.IPv4len { + addrs4 = append(addrs4, addr) + } + } + switch { + case len(addrs4) == 0: + return nil, fmt.Errorf("Interface %v has no IP addresses", name) + case len(addrs4) > 1: + fmt.Printf("Interface %v has more than 1 IPv4 address. Defaulting to using %v\n", + name, (addrs4[0].(*net.IPNet)).IP) + } + return addrs4[0], nil +} From c712e74b45005e0f38297d254fb606aa18606d11 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 29 Jan 2014 18:34:43 -0800 Subject: [PATCH 321/364] Update core calls to network drivers Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- config.go | 18 ++--- container.go | 140 +++++++++++++++++++++--------------- networkdriver/lxc/driver.go | 20 +++++- runtime.go | 29 +++++--- server.go | 7 +- 5 files changed, 128 insertions(+), 86 deletions(-) diff --git a/config.go b/config.go index cb7e985ca2..5b8b4c2499 100644 --- a/config.go +++ b/config.go @@ -5,6 +5,11 @@ import ( "net" ) +const ( + DefaultNetworkMtu = 1500 + DisableNetworkBridge = "none" +) + // FIXME: separate runtime configuration from http api configuration type DaemonConfig struct { Pidfile string @@ -13,12 +18,13 @@ type DaemonConfig struct { Dns []string EnableIptables bool EnableIpForward bool - BridgeIface string - BridgeIp string DefaultIp net.IP + BridgeIface string + BridgeIP string InterContainerCommunication bool GraphDriver string Mtu int + DisableNetwork bool } // ConfigFromJob creates and returns a new DaemonConfig object @@ -30,7 +36,7 @@ func DaemonConfigFromJob(job *engine.Job) *DaemonConfig { AutoRestart: job.GetenvBool("AutoRestart"), EnableIptables: job.GetenvBool("EnableIptables"), EnableIpForward: job.GetenvBool("EnableIpForward"), - BridgeIp: job.Getenv("BridgeIp"), + BridgeIP: job.Getenv("BridgeIp"), DefaultIp: net.ParseIP(job.Getenv("DefaultIp")), InterContainerCommunication: job.GetenvBool("InterContainerCommunication"), GraphDriver: job.Getenv("GraphDriver"), @@ -38,16 +44,12 @@ func DaemonConfigFromJob(job *engine.Job) *DaemonConfig { if dns := job.GetenvList("Dns"); dns != nil { config.Dns = dns } - if br := job.Getenv("BridgeIface"); br != "" { - config.BridgeIface = br - } else { - config.BridgeIface = DefaultNetworkBridge - } if mtu := job.GetenvInt("Mtu"); mtu != 0 { config.Mtu = mtu } else { config.Mtu = DefaultNetworkMtu } + config.DisableNetwork = job.Getenv("BridgeIface") == DisableNetworkBridge return config } diff --git a/container.go b/container.go index c90930a346..0e7fd257af 100644 --- a/container.go +++ b/container.go @@ -8,7 +8,6 @@ import ( "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/graphdriver" - "github.com/dotcloud/docker/networkdriver/ipallocator" "github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/utils" @@ -16,7 +15,6 @@ import ( "io" "io/ioutil" "log" - "net" "os" "path" "path/filepath" @@ -47,7 +45,6 @@ type Container struct { State State Image string - network *NetworkInterface NetworkSettings *NetworkSettings ResolvConfPath string @@ -558,6 +555,7 @@ func populateCommand(c *Container) { en *execdriver.Network driverConfig []string ) + if !c.Config.NetworkDisabled { network := c.NetworkSettings en = &execdriver.Network{ @@ -603,15 +601,18 @@ func (container *Container) Start() (err error) { if container.State.IsRunning() { return fmt.Errorf("The container %s is already running.", container.ID) } + defer func() { if err != nil { container.cleanup() } }() + if err := container.Mount(); err != nil { return err } - if container.runtime.networkManager.disabled { + + if container.runtime.config.DisableNetwork { container.Config.NetworkDisabled = true container.buildHostnameAndHostsFiles("127.0.1.1") } else { @@ -669,34 +670,39 @@ func (container *Container) Start() (err error) { } if len(children) > 0 { - container.activeLinks = make(map[string]*Link, len(children)) + panic("todo crosbymichael") + /* + linking is specific to iptables and the bridge we need to move this to a job - // If we encounter an error make sure that we rollback any network - // config and ip table changes - rollback := func() { - for _, link := range container.activeLinks { - link.Disable() - } - container.activeLinks = nil - } + container.activeLinks = make(map[string]*Link, len(children)) - for p, child := range children { - link, err := NewLink(container, child, p, runtime.networkManager.bridgeIface) - if err != nil { - rollback() - return err - } + // If we encounter an error make sure that we rollback any network + // config and ip table changes + rollback := func() { + for _, link := range container.activeLinks { + link.Disable() + } + container.activeLinks = nil + } - container.activeLinks[link.Alias()] = link - if err := link.Enable(); err != nil { - rollback() - return err - } + for p, child := range children { + link, err := NewLink(container, child, p, runtime.networkManager.bridgeIface) + if err != nil { + rollback() + return err + } - for _, envVar := range link.ToEnv() { - env = append(env, envVar) - } - } + container.activeLinks[link.Alias()] = link + if err := link.Enable(); err != nil { + rollback() + return err + } + + for _, envVar := range link.ToEnv() { + env = append(env, envVar) + } + } + */ } for _, elem := range container.Config.Env { @@ -1102,34 +1108,44 @@ func (container *Container) allocateNetwork() error { } var ( - iface *NetworkInterface - err error + env *engine.Env + eng = container.runtime.srv.Eng ) if container.State.IsGhost() { - if manager := container.runtime.networkManager; manager.disabled { - iface = &NetworkInterface{disabled: true} + if container.runtime.config.DisableNetwork { + env = &engine.Env{} } else { - iface = &NetworkInterface{ - IPNet: net.IPNet{IP: net.ParseIP(container.NetworkSettings.IPAddress), Mask: manager.bridgeNetwork.Mask}, - Gateway: manager.bridgeNetwork.IP, - manager: manager, - } - if iface != nil && iface.IPNet.IP != nil { - if _, err := ipallocator.RequestIP(manager.bridgeNetwork, &iface.IPNet.IP); err != nil { - return err + // TODO: @crosbymichael + panic("not implemented") + /* + iface = &NetworkInterface{ + IPNet: net.IPNet{IP: net.ParseIP(container.NetworkSettings.IPAddress), Mask: manager.bridgeNetwork.Mask}, + Gateway: manager.bridgeNetwork.IP, } - } else { - iface, err = container.runtime.networkManager.Allocate() - if err != nil { - return err + + // request an existing ip + if iface != nil && iface.IPNet.IP != nil { + if _, err := ipallocator.RequestIP(manager.bridgeNetwork, &iface.IPNet.IP); err != nil { + return err + } + } else { + job = eng.Job("allocate_interface", container.ID) + if err := job.Run(); err != nil { + return err + } } - } + */ } } else { - iface, err = container.runtime.networkManager.Allocate() + job := eng.Job("allocate_interface", container.ID) + var err error + env, err = job.Stdout.AddEnv() if err != nil { return err } + if err := job.Run(); err != nil { + return err + } } if container.Config.PortSpecs != nil { @@ -1171,37 +1187,43 @@ func (container *Container) allocateNetwork() error { if container.hostConfig.PublishAllPorts && len(binding) == 0 { binding = append(binding, PortBinding{}) } + for i := 0; i < len(binding); i++ { b := binding[i] - nat, err := iface.AllocatePort(port, b) - if err != nil { - iface.Release() + + portJob := eng.Job("allocate_port", container.ID) + portJob.Setenv("HostIP", b.HostIp) + portJob.Setenv("HostPort", b.HostPort) + portJob.Setenv("Proto", port.Proto()) + portJob.Setenv("ContainerPort", port.Port()) + + if err := portJob.Run(); err != nil { + eng.Job("release_interface", container.ID).Run() return err } - utils.Debugf("Allocate port: %s:%s->%s", nat.Binding.HostIp, port, nat.Binding.HostPort) - binding[i] = nat.Binding } bindings[port] = binding } container.writeHostConfig() container.NetworkSettings.Ports = bindings - container.network = iface - container.NetworkSettings.Bridge = container.runtime.networkManager.bridgeIface - container.NetworkSettings.IPAddress = iface.IPNet.IP.String() - container.NetworkSettings.IPPrefixLen, _ = iface.IPNet.Mask.Size() - container.NetworkSettings.Gateway = iface.Gateway.String() + container.NetworkSettings.Bridge = env.Get("Bridge") + container.NetworkSettings.IPAddress = env.Get("IP") + container.NetworkSettings.IPPrefixLen = env.GetInt("IPPrefixLen") + container.NetworkSettings.Gateway = env.Get("Gateway") + fmt.Printf("\n-----> %#v\n", container.NetworkSettings) return nil } func (container *Container) releaseNetwork() { - if container.Config.NetworkDisabled || container.network == nil { + if container.Config.NetworkDisabled { return } - container.network.Release() - container.network = nil + eng := container.runtime.srv.Eng + + eng.Job("release_interface", container.ID).Run() container.NetworkSettings = &NetworkSettings{} } diff --git a/networkdriver/lxc/driver.go b/networkdriver/lxc/driver.go index be99d32338..542b4b8504 100644 --- a/networkdriver/lxc/driver.go +++ b/networkdriver/lxc/driver.go @@ -19,8 +19,6 @@ import ( const ( DefaultNetworkBridge = "docker0" - DisableNetworkBridge = "none" - DefaultNetworkMtu = 1500 siocBRADDBR = 0x89a0 ) @@ -70,17 +68,24 @@ func InitDriver(job *engine.Job) engine.Status { enableIPTables = job.GetenvBool("EnableIptables") icc = job.GetenvBool("InterContainerCommunication") ipForward = job.GetenvBool("EnableIpForward") + bridgeIP = job.Getenv("BridgeIP") ) + bridgeIface = job.Getenv("BridgeIface") + if bridgeIface == "" { + bridgeIface = DefaultNetworkBridge + } addr, err := networkdriver.GetIfaceAddr(bridgeIface) if err != nil { // If the iface is not found, try to create it - if err := createBridgeIface(bridgeIface); err != nil { + job.Logf("creating new bridge for %s", bridgeIface) + if err := createBridge(bridgeIP); err != nil { job.Error(err) return engine.StatusErr } + job.Logf("getting iface addr") addr, err = networkdriver.GetIfaceAddr(bridgeIface) if err != nil { job.Error(err) @@ -122,9 +127,14 @@ func InitDriver(job *engine.Job) engine.Status { } bridgeNetwork = network + + // https://github.com/dotcloud/docker/issues/2768 + job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", bridgeNetwork.IP) + for name, f := range map[string]engine.Handler{ "allocate_interface": Allocate, "release_interface": Release, + "allocate_port": AllocatePort, } { if err := job.Eng.Register(name, f); err != nil { job.Error(err) @@ -304,6 +314,10 @@ func Allocate(job *engine.Job) engine.Status { out.Set("IP", string(*ip)) out.Set("Mask", string(bridgeNetwork.Mask)) out.Set("Gateway", string(bridgeNetwork.IP)) + out.Set("Bridge", bridgeIface) + + size, _ := bridgeNetwork.Mask.Size() + out.SetInt("IPPrefixLen", size) currentInterfaces[id] = &networkInterface{ IP: *ip, diff --git a/runtime.go b/runtime.go index e9e6802606..475e32ed5f 100644 --- a/runtime.go +++ b/runtime.go @@ -4,6 +4,7 @@ import ( "container/list" "fmt" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/execdriver/chroot" "github.com/dotcloud/docker/execdriver/lxc" @@ -12,6 +13,7 @@ import ( _ "github.com/dotcloud/docker/graphdriver/btrfs" _ "github.com/dotcloud/docker/graphdriver/devmapper" _ "github.com/dotcloud/docker/graphdriver/vfs" + _ "github.com/dotcloud/docker/networkdriver/lxc" "github.com/dotcloud/docker/networkdriver/portallocator" "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/pkg/sysinfo" @@ -42,7 +44,6 @@ type Runtime struct { repository string sysInitPath string containers *list.List - networkManager *NetworkManager graph *Graph repositories *TagStore idIndex *utils.TruncIndex @@ -609,15 +610,15 @@ func (runtime *Runtime) RegisterLink(parent, child *Container, alias string) err } // FIXME: harmonize with NewGraph() -func NewRuntime(config *DaemonConfig) (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory(config) +func NewRuntime(config *DaemonConfig, eng *engine.Engine) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory(config, eng) if err != nil { return nil, err } return runtime, nil } -func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { +func NewRuntimeFromDirectory(config *DaemonConfig, eng *engine.Engine) (*Runtime, error) { // Set the default driver graphdriver.DefaultDriver = config.GraphDriver @@ -664,12 +665,19 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { if err != nil { return nil, fmt.Errorf("Couldn't create Tag store: %s", err) } - if config.BridgeIface == "" { - config.BridgeIface = DefaultNetworkBridge - } - netManager, err := newNetworkManager(config) - if err != nil { - return nil, err + + if !config.DisableNetwork { + job := eng.Job("init_networkdriver") + + job.SetenvBool("EnableIptables", config.EnableIptables) + job.SetenvBool("InterContainerCommunication", config.InterContainerCommunication) + job.SetenvBool("EnableIpForward", config.EnableIpForward) + job.Setenv("BridgeIface", config.BridgeIface) + job.Setenv("BridgeIP", config.BridgeIP) + + if err := job.Run(); err != nil { + return nil, err + } } graphdbPath := path.Join(config.Root, "linkgraph.db") @@ -721,7 +729,6 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { runtime := &Runtime{ repository: runtimeRepo, containers: list.New(), - networkManager: netManager, graph: g, repositories: repositories, idIndex: utils.NewTruncIndex(), diff --git a/server.go b/server.go index d037212ce3..9d38c1486e 100644 --- a/server.go +++ b/server.go @@ -65,10 +65,7 @@ func jobInitApi(job *engine.Job) engine.Status { }() job.Eng.Hack_SetGlobalVar("httpapi.server", srv) job.Eng.Hack_SetGlobalVar("httpapi.runtime", srv.runtime) - // https://github.com/dotcloud/docker/issues/2768 - if srv.runtime.networkManager.bridgeNetwork != nil { - job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", srv.runtime.networkManager.bridgeNetwork.IP) - } + for name, handler := range map[string]engine.Handler{ "export": srv.ContainerExport, "create": srv.ContainerCreate, @@ -2354,7 +2351,7 @@ func (srv *Server) ContainerCopy(job *engine.Job) engine.Status { } func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) { - runtime, err := NewRuntime(config) + runtime, err := NewRuntime(config, eng) if err != nil { return nil, err } From 2d8709696c80e992a5b20ba7a00ca3268c57c11c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 30 Jan 2014 11:25:06 -0800 Subject: [PATCH 322/364] Fix sending []byte in job env Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 1 - networkdriver/lxc/driver.go | 8 +++++--- runtime.go | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/container.go b/container.go index 0e7fd257af..365ff93c69 100644 --- a/container.go +++ b/container.go @@ -1212,7 +1212,6 @@ func (container *Container) allocateNetwork() error { container.NetworkSettings.IPAddress = env.Get("IP") container.NetworkSettings.IPPrefixLen = env.GetInt("IPPrefixLen") container.NetworkSettings.Gateway = env.Get("Gateway") - fmt.Printf("\n-----> %#v\n", container.NetworkSettings) return nil } diff --git a/networkdriver/lxc/driver.go b/networkdriver/lxc/driver.go index 542b4b8504..deb09621ba 100644 --- a/networkdriver/lxc/driver.go +++ b/networkdriver/lxc/driver.go @@ -71,6 +71,8 @@ func InitDriver(job *engine.Job) engine.Status { bridgeIP = job.Getenv("BridgeIP") ) + defaultBindingIP = net.ParseIP(job.Getenv("DefaultBindingIP")) + bridgeIface = job.Getenv("BridgeIface") if bridgeIface == "" { bridgeIface = DefaultNetworkBridge @@ -311,9 +313,9 @@ func Allocate(job *engine.Job) engine.Status { } out := engine.Env{} - out.Set("IP", string(*ip)) - out.Set("Mask", string(bridgeNetwork.Mask)) - out.Set("Gateway", string(bridgeNetwork.IP)) + out.Set("IP", ip.String()) + out.Set("Mask", bridgeNetwork.Mask.String()) + out.Set("Gateway", bridgeNetwork.IP.String()) out.Set("Bridge", bridgeIface) size, _ := bridgeNetwork.Mask.Size() diff --git a/runtime.go b/runtime.go index 475e32ed5f..d03db3d068 100644 --- a/runtime.go +++ b/runtime.go @@ -674,6 +674,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig, eng *engine.Engine) (*Runtime job.SetenvBool("EnableIpForward", config.EnableIpForward) job.Setenv("BridgeIface", config.BridgeIface) job.Setenv("BridgeIP", config.BridgeIP) + job.Setenv("DefaultBindingIP", config.DefaultIp.String()) if err := job.Run(); err != nil { return nil, err From 49b9813035b3abb445ef7238f034897fa03a9652 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 30 Jan 2014 11:50:59 -0800 Subject: [PATCH 323/364] Fix integration tests Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 4 ++-- integration/utils_test.go | 8 +++++++- runtime.go | 2 ++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/container.go b/container.go index 365ff93c69..8ceec631f0 100644 --- a/container.go +++ b/container.go @@ -1109,7 +1109,7 @@ func (container *Container) allocateNetwork() error { var ( env *engine.Env - eng = container.runtime.srv.Eng + eng = container.runtime.eng ) if container.State.IsGhost() { if container.runtime.config.DisableNetwork { @@ -1220,7 +1220,7 @@ func (container *Container) releaseNetwork() { if container.Config.NetworkDisabled { return } - eng := container.runtime.srv.Eng + eng := container.runtime.eng eng.Job("release_interface", container.ID).Run() container.NetworkSettings = &NetworkSettings{} diff --git a/integration/utils_test.go b/integration/utils_test.go index 2eff13c81d..b4f3eb597d 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -34,7 +34,13 @@ func mkRuntime(f utils.Fataler) *docker.Runtime { AutoRestart: false, Mtu: docker.DefaultNetworkMtu, } - r, err := docker.NewRuntimeFromDirectory(config) + + eng, err := engine.New(root) + if err != nil { + f.Fatal(err) + } + + r, err := docker.NewRuntimeFromDirectory(config, eng) if err != nil { f.Fatal(err) } diff --git a/runtime.go b/runtime.go index d03db3d068..7e4ae79b40 100644 --- a/runtime.go +++ b/runtime.go @@ -50,6 +50,7 @@ type Runtime struct { sysInfo *sysinfo.SysInfo volumes *Graph srv *Server + eng *engine.Engine config *DaemonConfig containerGraph *graphdb.Database driver graphdriver.Driver @@ -740,6 +741,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig, eng *engine.Engine) (*Runtime driver: driver, sysInitPath: sysInitPath, execDriver: ed, + eng: eng, } if err := runtime.restore(); err != nil { From 2df01661071e949faabf1fa9ba612c40710b686b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 30 Jan 2014 12:02:56 -0800 Subject: [PATCH 324/364] Implement requesting the name ip Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 36 ++++++++++++++++-------------------- networkdriver/lxc/driver.go | 13 +++++++++++-- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/container.go b/container.go index 8ceec631f0..a9bf32ac6d 100644 --- a/container.go +++ b/container.go @@ -1109,36 +1109,32 @@ func (container *Container) allocateNetwork() error { var ( env *engine.Env + err error eng = container.runtime.eng ) + if container.State.IsGhost() { if container.runtime.config.DisableNetwork { env = &engine.Env{} } else { - // TODO: @crosbymichael - panic("not implemented") - /* - iface = &NetworkInterface{ - IPNet: net.IPNet{IP: net.ParseIP(container.NetworkSettings.IPAddress), Mask: manager.bridgeNetwork.Mask}, - Gateway: manager.bridgeNetwork.IP, - } + currentIP := container.NetworkSettings.IPAddress - // request an existing ip - if iface != nil && iface.IPNet.IP != nil { - if _, err := ipallocator.RequestIP(manager.bridgeNetwork, &iface.IPNet.IP); err != nil { - return err - } - } else { - job = eng.Job("allocate_interface", container.ID) - if err := job.Run(); err != nil { - return err - } - } - */ + job := eng.Job("allocate_interface", container.ID) + if currentIP != "" { + job.Setenv("RequestIP", currentIP) + } + + env, err = job.Stdout.AddEnv() + if err != nil { + return err + } + + if err := job.Run(); err != nil { + return err + } } } else { job := eng.Job("allocate_interface", container.ID) - var err error env, err = job.Stdout.AddEnv() if err != nil { return err diff --git a/networkdriver/lxc/driver.go b/networkdriver/lxc/driver.go index deb09621ba..0401e2de9d 100644 --- a/networkdriver/lxc/driver.go +++ b/networkdriver/lxc/driver.go @@ -304,9 +304,18 @@ func createBridgeIface(name string) error { // Allocate a network interface func Allocate(job *engine.Job) engine.Status { - id := job.Args[0] + var ( + ip *net.IP + err error + id = job.Args[0] + requestedIP = net.ParseIP(job.Getenv("RequestedIP")) + ) - ip, err := ipallocator.RequestIP(bridgeNetwork, nil) + if requestedIP != nil { + ip, err = ipallocator.RequestIP(bridgeNetwork, &requestedIP) + } else { + ip, err = ipallocator.RequestIP(bridgeNetwork, nil) + } if err != nil { job.Error(err) return engine.StatusErr From 167403988dca37060edf37abbdd28360ee0d5d4a Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 30 Jan 2014 12:43:49 -0800 Subject: [PATCH 325/364] Move network aspect of links into driver as a job Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 53 +++++++++++++++++-------------------- links.go | 35 +++++++++++++----------- networkdriver/lxc/driver.go | 34 ++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 45 deletions(-) diff --git a/container.go b/container.go index a9bf32ac6d..707872187a 100644 --- a/container.go +++ b/container.go @@ -670,39 +670,34 @@ func (container *Container) Start() (err error) { } if len(children) > 0 { - panic("todo crosbymichael") - /* - linking is specific to iptables and the bridge we need to move this to a job + container.activeLinks = make(map[string]*Link, len(children)) - container.activeLinks = make(map[string]*Link, len(children)) + // If we encounter an error make sure that we rollback any network + // config and ip table changes + rollback := func() { + for _, link := range container.activeLinks { + link.Disable() + } + container.activeLinks = nil + } - // If we encounter an error make sure that we rollback any network - // config and ip table changes - rollback := func() { - for _, link := range container.activeLinks { - link.Disable() - } - container.activeLinks = nil - } + for p, child := range children { + link, err := NewLink(container, child, p, runtime.eng) + if err != nil { + rollback() + return err + } - for p, child := range children { - link, err := NewLink(container, child, p, runtime.networkManager.bridgeIface) - if err != nil { - rollback() - return err - } + container.activeLinks[link.Alias()] = link + if err := link.Enable(); err != nil { + rollback() + return err + } - container.activeLinks[link.Alias()] = link - if err := link.Enable(); err != nil { - rollback() - return err - } - - for _, envVar := range link.ToEnv() { - env = append(env, envVar) - } - } - */ + for _, envVar := range link.ToEnv() { + env = append(env, envVar) + } + } } for _, elem := range container.Config.Env { diff --git a/links.go b/links.go index cd96b56629..aa1c08374b 100644 --- a/links.go +++ b/links.go @@ -2,7 +2,7 @@ package docker import ( "fmt" - "github.com/dotcloud/docker/pkg/iptables" + "github.com/dotcloud/docker/engine" "path" "strings" ) @@ -11,13 +11,13 @@ type Link struct { ParentIP string ChildIP string Name string - BridgeInterface string ChildEnvironment []string Ports []Port IsEnabled bool + eng *engine.Engine } -func NewLink(parent, child *Container, name, bridgeInterface string) (*Link, error) { +func NewLink(parent, child *Container, name string, eng *engine.Engine) (*Link, error) { if parent.ID == child.ID { return nil, fmt.Errorf("Cannot link to self: %s == %s", parent.ID, child.ID) } @@ -33,12 +33,12 @@ func NewLink(parent, child *Container, name, bridgeInterface string) (*Link, err } l := &Link{ - BridgeInterface: bridgeInterface, Name: name, ChildIP: child.NetworkSettings.IPAddress, ParentIP: parent.NetworkSettings.IPAddress, ChildEnvironment: child.Config.Env, Ports: ports, + eng: eng, } return l, nil @@ -119,18 +119,21 @@ func (l *Link) Disable() { } func (l *Link) toggle(action string, ignoreErrors bool) error { - for _, p := range l.Ports { - if output, err := iptables.Raw(action, "FORWARD", - "-i", l.BridgeInterface, "-o", l.BridgeInterface, - "-p", p.Proto(), - "-s", l.ParentIP, - "--dport", p.Port(), - "-d", l.ChildIP, - "-j", "ACCEPT"); !ignoreErrors && err != nil { - return err - } else if len(output) != 0 { - return fmt.Errorf("Error toggle iptables forward: %s", output) - } + job := l.eng.Job("link", action) + + job.Setenv("ParentIP", l.ParentIP) + job.Setenv("ChildIP", l.ChildIP) + job.SetenvBool("IgnoreErrors", ignoreErrors) + + out := make([]string, len(l.Ports)) + for i, p := range l.Ports { + out[i] = fmt.Sprintf("%s/%s", p.Port(), p.Proto()) + } + job.SetenvList("Ports", out) + + if err := job.Run(); err != nil { + // TODO: get ouput from job + return err } return nil } diff --git a/networkdriver/lxc/driver.go b/networkdriver/lxc/driver.go index 0401e2de9d..84e7b3ef95 100644 --- a/networkdriver/lxc/driver.go +++ b/networkdriver/lxc/driver.go @@ -13,6 +13,7 @@ import ( "io/ioutil" "log" "net" + "strings" "syscall" "unsafe" ) @@ -137,6 +138,7 @@ func InitDriver(job *engine.Job) engine.Status { "allocate_interface": Allocate, "release_interface": Release, "allocate_port": AllocatePort, + "link": LinkContainers, } { if err := job.Eng.Register(name, f); err != nil { job.Error(err) @@ -423,3 +425,35 @@ func AllocatePort(job *engine.Job) engine.Status { return engine.StatusOK } + +func LinkContainers(job *engine.Job) engine.Status { + var ( + action = job.Args[0] + childIP = job.Getenv("ChildIP") + parentIP = job.Getenv("ParentIP") + ignoreErrors = job.GetenvBool("IgnoreErrors") + ports = job.GetenvList("Ports") + ) + split := func(p string) (string, string) { + parts := strings.Split(p, "/") + return parts[0], parts[1] + } + + for _, p := range ports { + port, proto := split(p) + if output, err := iptables.Raw(action, "FORWARD", + "-i", bridgeIface, "-o", bridgeIface, + "-p", proto, + "-s", parentIP, + "--dport", port, + "-d", childIP, + "-j", "ACCEPT"); !ignoreErrors && err != nil { + job.Error(err) + return engine.StatusErr + } else if len(output) != 0 { + job.Errorf("Error toggle iptables forward: %s", output) + return engine.StatusErr + } + } + return engine.StatusOK +} From 1d4de9ce1fe2c113cd7e9fcb6055b95c94c65b06 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 30 Jan 2014 14:52:59 -0800 Subject: [PATCH 326/364] Fix port mapping unit tests Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- container.go | 8 +++++++ networkdriver/lxc/driver.go | 22 +++++++++++++++----- networkdriver/portallocator/portallocator.go | 2 +- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/container.go b/container.go index 707872187a..1e44ddd76c 100644 --- a/container.go +++ b/container.go @@ -1188,10 +1188,18 @@ func (container *Container) allocateNetwork() error { portJob.Setenv("Proto", port.Proto()) portJob.Setenv("ContainerPort", port.Port()) + portEnv, err := portJob.Stdout.AddEnv() + if err != nil { + return err + } if err := portJob.Run(); err != nil { eng.Job("release_interface", container.ID).Run() return err } + b.HostIp = portEnv.Get("HostIP") + b.HostPort = portEnv.Get("HostPort") + + binding[i] = b } bindings[port] = binding } diff --git a/networkdriver/lxc/driver.go b/networkdriver/lxc/driver.go index 84e7b3ef95..ab7a18808b 100644 --- a/networkdriver/lxc/driver.go +++ b/networkdriver/lxc/driver.go @@ -50,10 +50,10 @@ var ( "192.168.44.1/24", } - bridgeIface string - defaultBindingIP net.IP - bridgeNetwork *net.IPNet + bridgeIface string + bridgeNetwork *net.IPNet + defaultBindingIP = net.ParseIP("0.0.0.0") currentInterfaces = make(map[string]*networkInterface) ) @@ -72,7 +72,9 @@ func InitDriver(job *engine.Job) engine.Status { bridgeIP = job.Getenv("BridgeIP") ) - defaultBindingIP = net.ParseIP(job.Getenv("DefaultBindingIP")) + if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" { + defaultBindingIP = net.ParseIP(defaultIP) + } bridgeIface = job.Getenv("BridgeIface") if bridgeIface == "" { @@ -382,6 +384,8 @@ func Release(job *engine.Job) engine.Status { // Allocate an external port and map it to the interface func AllocatePort(job *engine.Job) engine.Status { var ( + err error + ip = defaultBindingIP id = job.Args[0] hostIP = job.Getenv("HostIP") @@ -396,7 +400,7 @@ func AllocatePort(job *engine.Job) engine.Status { } // host ip, proto, and host port - hostPort, err := portallocator.RequestPort(ip, proto, hostPort) + hostPort, err = portallocator.RequestPort(ip, proto, hostPort) if err != nil { job.Error(err) return engine.StatusErr @@ -423,6 +427,14 @@ func AllocatePort(job *engine.Job) engine.Status { } network.PortMappings = append(network.PortMappings, host) + out := engine.Env{} + out.Set("HostIP", ip.String()) + out.SetInt("HostPort", hostPort) + + if _, err := out.WriteTo(job.Stdout); err != nil { + job.Error(err) + return engine.StatusErr + } return engine.StatusOK } diff --git a/networkdriver/portallocator/portallocator.go b/networkdriver/portallocator/portallocator.go index 2566ea8500..71cac82703 100644 --- a/networkdriver/portallocator/portallocator.go +++ b/networkdriver/portallocator/portallocator.go @@ -51,7 +51,7 @@ func RequestPort(ip net.IP, proto string, port int) (int, error) { } // If the user requested a specific port to be allocated - if port != 0 { + if port > 0 { if err := registerSetPort(ip, proto, port); err != nil { return 0, err } From a11bee44d74235ec436c1b2272dc9f718497f88c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 31 Jan 2014 10:20:03 -0800 Subject: [PATCH 327/364] Update code from pr #3842 post rebase Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- networkdriver/lxc/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/networkdriver/lxc/driver.go b/networkdriver/lxc/driver.go index ab7a18808b..c767fd2208 100644 --- a/networkdriver/lxc/driver.go +++ b/networkdriver/lxc/driver.go @@ -155,7 +155,7 @@ func setupIPTables(addr net.Addr, icc bool) error { natArgs := []string{"POSTROUTING", "-t", "nat", "-s", addr.String(), "!", "-d", addr.String(), "-j", "MASQUERADE"} if !iptables.Exists(natArgs...) { - if output, err := iptables.Raw(append([]string{"-A"}, natArgs...)...); err != nil { + if output, err := iptables.Raw(append([]string{"-I"}, natArgs...)...); err != nil { return fmt.Errorf("Unable to enable network bridge NAT: %s", err) } else if len(output) != 0 { return fmt.Errorf("Error iptables postrouting: %s", output) From 50fea8978257141bf85669b9d757bf184f886a1b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 31 Jan 2014 12:07:51 -0800 Subject: [PATCH 328/364] Fix links test to not accept bridge Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- links_test.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/links_test.go b/links_test.go index be8d48e45c..8a266a9a3d 100644 --- a/links_test.go +++ b/links_test.go @@ -30,7 +30,7 @@ func TestLinkNew(t *testing.T) { to := newMockLinkContainer(toID, "172.0.17.3") - link, err := NewLink(to, from, "/db/docker", "172.0.17.1") + link, err := NewLink(to, from, "/db/docker", nil) if err != nil { t.Fatal(err) } @@ -50,9 +50,6 @@ func TestLinkNew(t *testing.T) { if link.ChildIP != "172.0.17.2" { t.Fail() } - if link.BridgeInterface != "172.0.17.1" { - t.Fail() - } for _, p := range link.Ports { if p != Port("6379/tcp") { t.Fail() @@ -75,7 +72,7 @@ func TestLinkEnv(t *testing.T) { to := newMockLinkContainer(toID, "172.0.17.3") - link, err := NewLink(to, from, "/db/docker", "172.0.17.1") + link, err := NewLink(to, from, "/db/docker", nil) if err != nil { t.Fatal(err) } From cb3bd91689b9beaaa4e287e126bc8150b5161c57 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 31 Jan 2014 12:18:45 -0800 Subject: [PATCH 329/364] Fix IP case for BridgeIP Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- config.go | 2 +- docker/docker.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.go b/config.go index 5b8b4c2499..40d885810b 100644 --- a/config.go +++ b/config.go @@ -36,7 +36,7 @@ func DaemonConfigFromJob(job *engine.Job) *DaemonConfig { AutoRestart: job.GetenvBool("AutoRestart"), EnableIptables: job.GetenvBool("EnableIptables"), EnableIpForward: job.GetenvBool("EnableIpForward"), - BridgeIP: job.Getenv("BridgeIp"), + BridgeIP: job.Getenv("BridgeIP"), DefaultIp: net.ParseIP(job.Getenv("DefaultIp")), InterContainerCommunication: job.GetenvBool("InterContainerCommunication"), GraphDriver: job.Getenv("GraphDriver"), diff --git a/docker/docker.go b/docker/docker.go index e6c8076f36..739758aa2f 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -90,7 +90,7 @@ func main() { job.SetenvBool("EnableIptables", *flEnableIptables) job.SetenvBool("EnableIpForward", *flEnableIpForward) job.Setenv("BridgeIface", *bridgeName) - job.Setenv("BridgeIp", *bridgeIp) + job.Setenv("BridgeIP", *bridgeIp) job.Setenv("DefaultIp", *flDefaultIp) job.SetenvBool("InterContainerCommunication", *flInterContainerComm) job.Setenv("GraphDriver", *flGraphDriver) From 9eea7f28f0c64d43ef2a9987999a29774fa1476b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 31 Jan 2014 19:30:43 +0000 Subject: [PATCH 330/364] Move serveapi to api/api.go Remove api import from server.go Rename initapi to init server Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/api.go | 31 +++++++++++++++++++++++++++++++ docker/docker.go | 3 ++- integration/runtime_test.go | 10 +++++----- integration/server_test.go | 2 +- integration/utils_test.go | 2 +- server.go | 34 ++-------------------------------- 6 files changed, 42 insertions(+), 40 deletions(-) diff --git a/api/api.go b/api/api.go index 61069445fc..b722eb5c60 100644 --- a/api/api.go +++ b/api/api.go @@ -36,6 +36,10 @@ const ( type HttpApiFunc func(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error +func init() { + engine.Register("serveapi", ServeApi) +} + func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { conn, _, err := w.(http.Hijacker).Hijack() if err != nil { @@ -1160,3 +1164,30 @@ func ListenAndServe(proto, addr string, eng *engine.Engine, logging, enableCors httpSrv := http.Server{Addr: addr, Handler: r} return httpSrv.Serve(l) } + +// ServeApi loops through all of the protocols sent in to docker and spawns +// off a go routine to setup a serving http.Server for each. +func ServeApi(job *engine.Job) engine.Status { + protoAddrs := job.Args + chErrors := make(chan error, len(protoAddrs)) + + for _, protoAddr := range protoAddrs { + protoAddrParts := strings.SplitN(protoAddr, "://", 2) + go func() { + log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1]) + chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("Version")) + }() + } + + for i := 0; i < len(protoAddrs); i += 1 { + err := <-chErrors + if err != nil { + return job.Error(err) + } + } + + // Tell the init daemon we are accepting requests + go systemd.SdNotify("READY=1") + + return engine.StatusOK +} diff --git a/docker/docker.go b/docker/docker.go index 2da9c1fa26..ba7764050b 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -83,7 +83,7 @@ func main() { log.Fatal(err) } // Load plugin: httpapi - job := eng.Job("initapi") + job := eng.Job("initserver") job.Setenv("Pidfile", *pidfile) job.Setenv("Root", *flRoot) job.SetenvBool("AutoRestart", *flAutoRestart) @@ -103,6 +103,7 @@ func main() { job = eng.Job("serveapi", flHosts.GetAll()...) job.SetenvBool("Logging", true) job.SetenvBool("EnableCors", *flEnableCors) + job.Setenv("Version", VERSION) if err := job.Run(); err != nil { log.Fatal(err) } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 9afc10f5af..da95967a30 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -125,7 +125,7 @@ func setupBaseImage() { if err != nil { log.Fatalf("Can't initialize engine at %s: %s", unitTestStoreBase, err) } - job := eng.Job("initapi") + job := eng.Job("initserver") job.Setenv("Root", unitTestStoreBase) job.SetenvBool("Autorestart", false) job.Setenv("BridgeIface", unitTestNetworkBridge) @@ -573,7 +573,7 @@ func TestRestore(t *testing.T) { if err != nil { t.Fatal(err) } - job := eng.Job("initapi") + job := eng.Job("initserver") job.Setenv("Root", eng.Root()) job.SetenvBool("Autorestart", false) if err := job.Run(); err != nil { @@ -605,7 +605,7 @@ func TestRestore(t *testing.T) { } func TestReloadContainerLinks(t *testing.T) { - // FIXME: here we don't use NewTestEngine because it calls initapi with Autorestart=false, + // FIXME: here we don't use NewTestEngine because it calls initserver with Autorestart=false, // and we want to set it to true. root, err := newTestDirectory(unitTestStoreBase) if err != nil { @@ -615,7 +615,7 @@ func TestReloadContainerLinks(t *testing.T) { if err != nil { t.Fatal(err) } - job := eng.Job("initapi") + job := eng.Job("initserver") job.Setenv("Root", eng.Root()) job.SetenvBool("Autorestart", true) if err := job.Run(); err != nil { @@ -665,7 +665,7 @@ func TestReloadContainerLinks(t *testing.T) { if err != nil { t.Fatal(err) } - job = eng.Job("initapi") + job = eng.Job("initserver") job.Setenv("Root", eng.Root()) job.SetenvBool("Autorestart", false) if err := job.Run(); err != nil { diff --git a/integration/server_test.go b/integration/server_test.go index c3371ce8bf..45d4930ad7 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -262,7 +262,7 @@ func TestRestartKillWait(t *testing.T) { t.Fatal(err) } - job = eng.Job("initapi") + job = eng.Job("initserver") job.Setenv("Root", eng.Root()) job.SetenvBool("AutoRestart", false) // TestGetEnabledCors and TestOptionsRoute require EnableCors=true diff --git a/integration/utils_test.go b/integration/utils_test.go index d7a2814472..67f31a312a 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -188,7 +188,7 @@ func NewTestEngine(t utils.Fataler) *engine.Engine { } // Load default plugins // (This is manually copied and modified from main() until we have a more generic plugin system) - job := eng.Job("initapi") + job := eng.Job("initserver") job.Setenv("Root", root) job.SetenvBool("AutoRestart", false) // TestGetEnabledCors and TestOptionsRoute require EnableCors=true diff --git a/server.go b/server.go index 3561e76abd..f4d2f3722f 100644 --- a/server.go +++ b/server.go @@ -4,12 +4,10 @@ import ( "encoding/json" "errors" "fmt" - "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/pkg/graphdb" - "github.com/dotcloud/docker/pkg/systemd" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" "io" @@ -35,13 +33,13 @@ func (srv *Server) Close() error { } func init() { - engine.Register("initapi", jobInitApi) + engine.Register("initserver", jobInitServer) } // jobInitApi runs the remote api server `srv` as a daemon, // Only one api server can run at the same time - this is enforced by a pidfile. // The signals SIGINT, SIGQUIT and SIGTERM are intercepted for cleanup. -func jobInitApi(job *engine.Job) engine.Status { +func jobInitServer(job *engine.Job) engine.Status { job.Logf("Creating server") srv, err := NewServer(job.Eng, DaemonConfigFromJob(job)) if err != nil { @@ -77,7 +75,6 @@ func jobInitApi(job *engine.Job) engine.Status { "restart": srv.ContainerRestart, "start": srv.ContainerStart, "kill": srv.ContainerKill, - "serveapi": srv.ListenAndServe, "wait": srv.ContainerWait, "tag": srv.ImageTag, "resize": srv.ContainerResize, @@ -112,33 +109,6 @@ func jobInitApi(job *engine.Job) engine.Status { return engine.StatusOK } -// ListenAndServe loops through all of the protocols sent in to docker and spawns -// off a go routine to setup a serving http.Server for each. -func (srv *Server) ListenAndServe(job *engine.Job) engine.Status { - protoAddrs := job.Args - chErrors := make(chan error, len(protoAddrs)) - - for _, protoAddr := range protoAddrs { - protoAddrParts := strings.SplitN(protoAddr, "://", 2) - go func() { - log.Printf("Listening for HTTP on %s (%s)\n", protoAddrParts[0], protoAddrParts[1]) - chErrors <- api.ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), VERSION) - }() - } - - for i := 0; i < len(protoAddrs); i += 1 { - err := <-chErrors - if err != nil { - return job.Error(err) - } - } - - // Tell the init daemon we are accepting requests - go systemd.SdNotify("READY=1") - - return engine.StatusOK -} - // simpleVersionInfo is a simple implementation of // the interface VersionInfo, which is used // to provide version information for some product, From bf1f655fa2b5112301102f9191e0f0deb68975f1 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 31 Jan 2014 23:50:34 +0000 Subject: [PATCH 331/364] remove REMOTE_TODO.md Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- REMOTE_TODO.md | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 REMOTE_TODO.md diff --git a/REMOTE_TODO.md b/REMOTE_TODO.md deleted file mode 100644 index b21ba68bf8..0000000000 --- a/REMOTE_TODO.md +++ /dev/null @@ -1,46 +0,0 @@ -``` -**GET** - send objects deprecate multi-stream -TODO "/events": getEvents, N -ok "/info": getInfo, 1 -ok "/version": getVersion, 1 -ok "/images/json": getImagesJSON, N -ok "/images/viz": getImagesViz, 0 yes -ok "/images/search": getImagesSearch, N -ok "/images/{name:.*}/get": getImagesGet, 0 -ok "/images/{name:.*}/history": getImagesHistory, N -#3621 "/images/{name:.*}/json": getImagesByName, 1 -#3728 "/containers/ps": getContainersJSON, N -#3728 "/containers/json": getContainersJSON, 1 -ok "/containers/{name:.*}/export": getContainersExport, 0 -ok "/containers/{name:.*}/changes": getContainersChanges, N -#3621 "/containers/{name:.*}/json": getContainersByName, 1 -ok "/containers/{name:.*}/top": getContainersTop, N -ok "/containers/{name:.*}/attach/ws": wsContainersAttach, 0 yes - -**POST** -N/A "/auth": postAuth, 0 yes -ok "/commit": postCommit, 0 -#3741 "/build": postBuild, 0 yes -#3725 "/images/create": postImagesCreate, N yes yes (pull) -ok "/images/{name:.*}/insert": postImagesInsert, N yes yes -ok "/images/load": postImagesLoad, 1 yes (stdin) -#3727 "/images/{name:.*}/push": postImagesPush, N yes -ok "/images/{name:.*}/tag": postImagesTag, 0 -ok "/containers/create": postContainersCreate, 0 -ok "/containers/{name:.*}/kill": postContainersKill, 0 -ok "/containers/{name:.*}/restart": postContainersRestart, 0 -ok "/containers/{name:.*}/start": postContainersStart, 0 -ok "/containers/{name:.*}/stop": postContainersStop, 0 -ok "/containers/{name:.*}/wait": postContainersWait, 0 -ok "/containers/{name:.*}/resize": postContainersResize, 0 -ok "/containers/{name:.*}/attach": postContainersAttach, 0 yes -ok "/containers/{name:.*}/copy": postContainersCopy, 0 yes - -**DELETE** -ok "/containers/{name:.*}": deleteContainers, 0 -#3645 "/images/{name:.*}": deleteImages, N - -**OPTIONS** -N/A "": optionsHandler, 0 -``` From d22a39db265dbf68cec4ddbc5903372e936094a9 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Sat, 1 Feb 2014 01:14:59 +0000 Subject: [PATCH 332/364] fix docker login Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- commands.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index bebab65481..7e512cff26 100644 --- a/commands.go +++ b/commands.go @@ -336,7 +336,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig.ServerAddress = serverAddress cli.configFile.Configs[serverAddress] = authconfig - body, statusCode, err := readBody(cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false)) + stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false) if statusCode == 401 { delete(cli.configFile.Configs, serverAddress) auth.SaveConfig(cli.configFile) @@ -345,9 +345,8 @@ func (cli *DockerCli) CmdLogin(args ...string) error { if err != nil { return err } - var out2 engine.Env - err = json.Unmarshal(body, &out2) + err = out2.Decode(stream) if err != nil { cli.configFile, _ = auth.LoadConfig(os.Getenv("HOME")) return err From e43236c78ae8a466a36c323aa679ea581b42cdc5 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Sat, 1 Feb 2014 01:24:39 +0000 Subject: [PATCH 333/364] fix api with old version Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/api.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/api.go b/api/api.go index b722eb5c60..741dc69085 100644 --- a/api/api.go +++ b/api/api.go @@ -204,7 +204,7 @@ func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r job.Setenv("filter", r.Form.Get("filter")) job.Setenv("all", r.Form.Get("all")) - if version > 1.8 { + if version >= 1.7 { job.Stdout.Add(w) } else if outs, err = job.Stdout.AddListTable(); err != nil { return err @@ -214,7 +214,7 @@ func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r return err } - if version < 1.8 && outs != nil { // Convert to legacy format + if version < 1.7 && outs != nil { // Convert to legacy format outsLegacy := engine.NewTable("Created", 0) for _, out := range outs.Data { for _, repoTag := range out.GetList("RepoTags") { @@ -319,7 +319,7 @@ func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWrite job.Setenv("before", r.Form.Get("before")) job.Setenv("limit", r.Form.Get("limit")) - if version > 1.5 { + if version >= 1.5 { job.Stdout.Add(w) } else if outs, err = job.Stdout.AddTable(); err != nil { return err From 9b7054fd0bc707f7f8e5c9ed9ada5a1664a19a34 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 31 Jan 2014 17:22:15 -0800 Subject: [PATCH 334/364] Do not generate all numberic truncated ids Fixes #3869 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- image.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/image.go b/image.go index 7652824d49..b2d7f8eb49 100644 --- a/image.go +++ b/image.go @@ -203,12 +203,20 @@ func ValidateID(id string) error { } func GenerateID() string { - id := make([]byte, 32) - _, err := io.ReadFull(rand.Reader, id) - if err != nil { - panic(err) // This shouldn't happen + for { + id := make([]byte, 32) + if _, err := io.ReadFull(rand.Reader, id); err != nil { + panic(err) // This shouldn't happen + } + value := hex.EncodeToString(id) + // if we try to parse the truncated for as an int and we don't have + // an error then the value is all numberic and causes issues when + // used as a hostname. ref #3869 + if _, err := strconv.Atoi(utils.TruncateID(value)); err == nil { + continue + } + return value } - return hex.EncodeToString(id) } // Image includes convenience proxy functions to its graph From b0f4ad431d79869111cfbf506a783a275c274c68 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Fri, 31 Jan 2014 18:02:45 -0800 Subject: [PATCH 335/364] Fixes #3879 by updating embedding of ascii.io (now asciinema.io) Docker-DCO-1.1-Signed-off-by: Andy Rothfusz (github: metalivedev) --- docs/sources/examples/hello_world.rst | 19 ++++++++++++------- docs/sources/examples/python_web_app.rst | 9 ++++++--- docs/sources/examples/running_ssh_service.rst | 9 ++++++--- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/docs/sources/examples/hello_world.rst b/docs/sources/examples/hello_world.rst index 3a6a7b8587..fd5d6421c3 100644 --- a/docs/sources/examples/hello_world.rst +++ b/docs/sources/examples/hello_world.rst @@ -70,10 +70,12 @@ See the example in action .. raw:: html -
- -
- + ---- @@ -163,9 +165,12 @@ See the example in action .. raw:: html -
- -
+ The next example in the series is a :ref:`python_web_app` example, or you could skip to any of the other examples: diff --git a/docs/sources/examples/python_web_app.rst b/docs/sources/examples/python_web_app.rst index 02160d0753..f31b31b7d2 100644 --- a/docs/sources/examples/python_web_app.rst +++ b/docs/sources/examples/python_web_app.rst @@ -107,8 +107,11 @@ See the example in action .. raw:: html -
- -
+ Continue to :ref:`running_ssh_service`. diff --git a/docs/sources/examples/running_ssh_service.rst b/docs/sources/examples/running_ssh_service.rst index a0ce532d8d..ee9802094a 100644 --- a/docs/sources/examples/running_ssh_service.rst +++ b/docs/sources/examples/running_ssh_service.rst @@ -25,9 +25,12 @@ smooth, but it gives you a good idea. .. raw:: html -
- -
+ You can also get this sshd container by using: From 4d0a026c98721da874a03f5b6993045bae95842a Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Sat, 1 Feb 2014 03:38:39 -0800 Subject: [PATCH 336/364] docker: detect defaultNetworkMtu from default route Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: google) --- config.go | 13 +++++++++++-- docker/docker.go | 9 +++++---- networkdriver/utils.go | 16 +++++++++++++++- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/config.go b/config.go index 40d885810b..e7f87ace77 100644 --- a/config.go +++ b/config.go @@ -1,12 +1,14 @@ package docker import ( - "github.com/dotcloud/docker/engine" "net" + + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/networkdriver" ) const ( - DefaultNetworkMtu = 1500 + defaultNetworkMtu = 1500 DisableNetworkBridge = "none" ) @@ -53,3 +55,10 @@ func DaemonConfigFromJob(job *engine.Job) *DaemonConfig { return config } + +func GetDefaultNetworkMtu() int { + if iface, err := networkdriver.GetDefaultRouteIface(); err == nil { + return iface.MTU + } + return defaultNetworkMtu +} diff --git a/docker/docker.go b/docker/docker.go index aaeced95f6..8d4ae7fce7 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -2,15 +2,16 @@ package main import ( "fmt" + "log" + "os" + "strings" + "github.com/dotcloud/docker" "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/engine" flag "github.com/dotcloud/docker/pkg/mflag" "github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/utils" - "log" - "os" - "strings" ) var ( @@ -42,7 +43,7 @@ func main() { flInterContainerComm = flag.Bool([]string{"#icc", "-icc"}, true, "Enable inter-container communication") flGraphDriver = flag.String([]string{"s", "-storage-driver"}, "", "Force the docker runtime to use a specific storage driver") flHosts = docker.NewListOpts(docker.ValidateHost) - flMtu = flag.Int([]string{"#mtu", "-mtu"}, docker.DefaultNetworkMtu, "Set the containers network mtu") + flMtu = flag.Int([]string{"#mtu", "-mtu"}, docker.GetDefaultNetworkMtu(), "Set the containers network mtu") ) flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers") flag.Var(&flHosts, []string{"H", "-host"}, "tcp://host:port, unix://path/to/socket, fd://* or fd://socketfd to use in daemon mode. Multiple sockets can be specified") diff --git a/networkdriver/utils.go b/networkdriver/utils.go index 6f6dbe4f18..611ca75867 100644 --- a/networkdriver/utils.go +++ b/networkdriver/utils.go @@ -3,8 +3,9 @@ package networkdriver import ( "encoding/binary" "fmt" - "github.com/dotcloud/docker/pkg/netlink" "net" + + "github.com/dotcloud/docker/pkg/netlink" ) var ( @@ -100,3 +101,16 @@ func GetIfaceAddr(name string) (net.Addr, error) { } return addrs4[0], nil } + +func GetDefaultRouteIface() (*net.Interface, error) { + rs, err := netlink.NetworkGetRoutes() + if err != nil { + return nil, fmt.Errorf("unable to get routes: %v", err) + } + for _, r := range rs { + if r.Default { + return r.Iface, nil + } + } + return nil, fmt.Errorf("no default route") +} From 6922f1be08111d889b0585b763b08f92d7a55e05 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sat, 1 Feb 2014 21:40:51 -0700 Subject: [PATCH 337/364] Remove reference to , and instead use like we're supposed to (from btrfs-progs) This fixes compilation issues when btrfs.h isn't available (because we just need the relevant structs, which for userspace programs are supposed to come from btrfs-progs instead of the kernel headers). Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- Dockerfile | 2 +- graphdriver/btrfs/btrfs.go | 8 ++------ hack/PACKAGERS.md | 1 + 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index a9ecd8140a..e00c7283ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,6 +32,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq \ apt-utils \ aufs-tools \ automake \ + btrfs-tools \ build-essential \ curl \ dpkg-sig \ @@ -40,7 +41,6 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq \ libapparmor-dev \ libcap-dev \ libsqlite3-dev \ - linux-libc-dev \ mercurial \ reprepro \ ruby1.9.1 \ diff --git a/graphdriver/btrfs/btrfs.go b/graphdriver/btrfs/btrfs.go index a50f11f851..3d27909bd1 100644 --- a/graphdriver/btrfs/btrfs.go +++ b/graphdriver/btrfs/btrfs.go @@ -4,15 +4,11 @@ package btrfs /* #include -#include -#include -#include -#include #include -#include - +#include */ import "C" + import ( "fmt" "github.com/dotcloud/docker/graphdriver" diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index 2d869b6eda..f4e7e49330 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -39,6 +39,7 @@ To build docker, you will need the following system dependencies * Go version 1.2 or later * SQLite version 3.7.9 or later * libdevmapper version 1.02.68-cvs (2012-01-26) or later from lvm2 version 2.02.89 or later +* btrfs-progs version 3.8 or later (including commit e5cb128 from 2013-01-07) for the necessary btrfs headers * A clean checkout of the source must be added to a valid Go [workspace](http://golang.org/doc/code.html#Workspaces) under the path *src/github.com/dotcloud/docker*. From 7ffd2b0785529b0b71bec90c8468dc3cb4b37525 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sun, 2 Feb 2014 22:40:58 -0700 Subject: [PATCH 338/364] Update PACKAGERS.md to explicitly talk about stripping, quoting Dave Cheney from Golang upstream (who is very, very adamant about not stripping Golang compiled binaries) While there, remove the now-outdated section about `CGO_ENABLED=0` Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/PACKAGERS.md | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index 2d869b6eda..85d0a4dd06 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -56,15 +56,40 @@ NOTE: if you''re not able to package the exact version (to the exact commit) of please get in touch so we can remediate! Who knows what discrepancies can be caused by even the slightest deviation. We promise to do our best to make everybody happy. -## Disabling CGO +## Stripping Binaries -Make sure to disable CGO on your system, and then recompile the standard library on the build -machine: +Please, please, please do not strip any compiled binaries. This is really important. -```bash -export CGO_ENABLED=0 -cd /tmp && echo 'package main' > t.go && go test -a -i -v -``` +See the following quotes from Dave Cheney, which explain this position better +from the upstream Golang perspective. + +### [go issue #5855, comment #3](https://code.google.com/p/go/issues/detail?id=5855#c3) + +> Super super important: Do not strip go binaries or archives. It isn't tested, +> often breaks, and doesn't work. + +### [launchpad golang issue #1200255, comment #8](https://bugs.launchpad.net/ubuntu/+source/golang/+bug/1200255/comments/8) + +> To quote myself: "Please do not strip Go binaries, it is not supported, not +> tested, is often broken, and doesn't do what you want" +> +> To unpack that a bit +> +> * not supported, as in, we don't support it, and recommend against it when +> asked +> * not tested, we don't test stripped binaries as part of the build CI process +> * is often broken, stripping a go binary will produce anywhere from no, to +> subtle, to outright execution failure, see above + +### [launchpad golang issue #1200255, comment #13](https://bugs.launchpad.net/ubuntu/+source/golang/+bug/1200255/comments/13) + +> To clarify my previous statements. +> +> * I do not disagree with the debian policy, it is there for a good reason +> * Having said that, it stripping Go binaries doesn't work, and nobody is +> looking at making it work, so there is that. +> +> Thanks for patching the build formula. ## Building Docker From b61c1d0d42777023a6248c6e9de4bb4877a1857b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 3 Feb 2014 14:59:09 +0100 Subject: [PATCH 339/364] Fix docker commit output The id is returned as Id, not ID, so print the right thing. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- commands.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 7e512cff26..e6a9a7387c 100644 --- a/commands.go +++ b/commands.go @@ -1472,7 +1472,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error { return err } - fmt.Fprintf(cli.out, "%s\n", env.Get("ID")) + fmt.Fprintf(cli.out, "%s\n", env.Get("Id")) return nil } From f7f8e3c2b3818d351781f96ef68432eba0fc2fda Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sat, 1 Feb 2014 23:52:52 -0700 Subject: [PATCH 340/364] Fix several btrfs driver error messages Several of the error messages were wrong, but not one of them was helpful, so this fixes them to include the important information (the actual error), and a correct description of which operation failed. Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- graphdriver/btrfs/btrfs.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graphdriver/btrfs/btrfs.go b/graphdriver/btrfs/btrfs.go index a50f11f851..5ab246cf1f 100644 --- a/graphdriver/btrfs/btrfs.go +++ b/graphdriver/btrfs/btrfs.go @@ -99,7 +99,7 @@ func subvolCreate(path, name string) error { _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SUBVOL_CREATE, uintptr(unsafe.Pointer(&args))) if errno != 0 { - return fmt.Errorf("Can't create subvolume") + return fmt.Errorf("Failed to create btrfs subvolume: %v", errno.Error()) } return nil } @@ -126,7 +126,7 @@ func subvolSnapshot(src, dest, name string) error { _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(destDir), C.BTRFS_IOC_SNAP_CREATE_V2, uintptr(unsafe.Pointer(&args))) if errno != 0 { - return fmt.Errorf("Can't create subvolume") + return fmt.Errorf("Failed to create btrfs snapshot: %v", errno.Error()) } return nil } @@ -146,7 +146,7 @@ func subvolDelete(path, name string) error { _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SNAP_DESTROY, uintptr(unsafe.Pointer(&args))) if errno != 0 { - return fmt.Errorf("Can't create subvolume") + return fmt.Errorf("Failed to destroy btrfs snapshot: %v", errno.Error()) } return nil } From f4a7c437769b5cf25d852d1aee6a6c50ee42fc85 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 3 Feb 2014 11:08:35 -0700 Subject: [PATCH 341/364] Remove stackbrew prefix on ubuntu images now that they're reasonably up-to-date and stable Everyone probably wants to run `docker pull ubuntu` before building with this change. Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- Dockerfile | 2 +- docs/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index e00c7283ae..8eb2459215 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ # docker-version 0.6.1 -FROM stackbrew/ubuntu:13.10 +FROM ubuntu:13.10 MAINTAINER Tianon Gravi (@tianon) # Packaged dependencies diff --git a/docs/Dockerfile b/docs/Dockerfile index 0d703c8c84..69aa5cb409 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,4 +1,4 @@ -FROM stackbrew/ubuntu:12.04 +FROM ubuntu:12.04 MAINTAINER Nick Stinemates # # docker build -t docker:docs . && docker run -p 8000:8000 docker:docs From 0fa9199f781ecd27469ca192496ce7b8e24980b1 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 3 Feb 2014 11:38:34 -0800 Subject: [PATCH 342/364] Fix login prompt on push and pull because of error message Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- commands.go | 4 ++-- registry/registry.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/commands.go b/commands.go index e6a9a7387c..15d4507030 100644 --- a/commands.go +++ b/commands.go @@ -1047,7 +1047,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { } if err := push(authConfig); err != nil { - if err.Error() == registry.ErrLoginRequired.Error() { + if strings.Contains(err.Error(), "Status 401") { fmt.Fprintln(cli.out, "\nPlease login prior to push:") if err := cli.CmdLogin(endpoint); err != nil { return err @@ -1106,7 +1106,7 @@ func (cli *DockerCli) CmdPull(args ...string) error { } if err := pull(authConfig); err != nil { - if err.Error() == registry.ErrLoginRequired.Error() { + if strings.Contains(err.Error(), "Status 401") { fmt.Fprintln(cli.out, "\nPlease login prior to pull:") if err := cli.CmdLogin(endpoint); err != nil { return err diff --git a/registry/registry.go b/registry/registry.go index c0d8414de0..df94302305 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -22,7 +22,7 @@ import ( var ( ErrAlreadyExists = errors.New("Image already exists") ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") - ErrLoginRequired = errors.New("Authentication is required.") + errLoginRequired = errors.New("Authentication is required.") ) func pingRegistryEndpoint(endpoint string) (bool, error) { @@ -186,7 +186,7 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s defer res.Body.Close() if res.StatusCode != 200 { if res.StatusCode == 401 { - return nil, ErrLoginRequired + return nil, errLoginRequired } return nil, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res) } @@ -332,7 +332,7 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) { } defer res.Body.Close() if res.StatusCode == 401 { - return nil, ErrLoginRequired + return nil, errLoginRequired } // TODO: Right now we're ignoring checksums in the response body. // In the future, we need to use them to check image validity. From 720c5774e9a9cd510320a142848832f7cd314da7 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 3 Feb 2014 13:18:10 -0800 Subject: [PATCH 343/364] Fix comparing binds and volume ids from other containers Currently comparing volume ids for binds and other containers are broken Fixes #3749 Fixes #3885 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- server.go | 61 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/server.go b/server.go index af66ff059d..2942eaeb5b 100644 --- a/server.go +++ b/server.go @@ -1726,38 +1726,53 @@ func (srv *Server) ContainerDestroy(job *engine.Job) engine.Status { if container.State.IsRunning() { return job.Errorf("Impossible to remove a running container, please stop it first") } - volumes := make(map[string]struct{}) - - binds := make(map[string]struct{}) - - for _, bind := range container.hostConfig.Binds { - splitBind := strings.Split(bind, ":") - source := splitBind[0] - binds[source] = struct{}{} - } - - // Store all the deleted containers volumes - for _, volumeId := range container.Volumes { - - // Skip the volumes mounted from external - if _, exists := binds[volumeId]; exists { - continue - } - - volumeId = strings.TrimSuffix(volumeId, "/layer") - volumeId = filepath.Base(volumeId) - volumes[volumeId] = struct{}{} - } if err := srv.runtime.Destroy(container); err != nil { return job.Errorf("Cannot destroy container %s: %s", name, err) } srv.LogEvent("destroy", container.ID, srv.runtime.repositories.ImageName(container.Image)) if removeVolume { + var ( + volumes = make(map[string]struct{}) + binds = make(map[string]struct{}) + usedVolumes = make(map[string]*Container) + ) + + // the volume id is always the base of the path + getVolumeId := func(p string) string { + return filepath.Base(strings.TrimSuffix(p, "/layer")) + } + + // populate bind map so that they can be skipped and not removed + for _, bind := range container.hostConfig.Binds { + source := strings.Split(bind, ":")[0] + // TODO: refactor all volume stuff, all of it + // this is very important that we eval the link + // or comparing the keys to container.Volumes will not work + p, err := filepath.EvalSymlinks(source) + if err != nil { + return job.Error(err) + } + source = p + binds[source] = struct{}{} + } + + // Store all the deleted containers volumes + for _, volumeId := range container.Volumes { + // Skip the volumes mounted from external + // bind mounts here will will be evaluated for a symlink + if _, exists := binds[volumeId]; exists { + continue + } + + volumeId = getVolumeId(volumeId) + volumes[volumeId] = struct{}{} + } + // Retrieve all volumes from all remaining containers - usedVolumes := make(map[string]*Container) for _, container := range srv.runtime.List() { for _, containerVolumeId := range container.Volumes { + containerVolumeId = getVolumeId(containerVolumeId) usedVolumes[containerVolumeId] = container } } From 1b9c5edc6c7acbe894c4b2db3e3e4a1f1353ccc5 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Mon, 3 Feb 2014 14:27:40 -0800 Subject: [PATCH 344/364] networkdriver: add ErrNoDefaultRoute Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: google) --- networkdriver/utils.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/networkdriver/utils.go b/networkdriver/utils.go index 611ca75867..0a4ef70c95 100644 --- a/networkdriver/utils.go +++ b/networkdriver/utils.go @@ -2,6 +2,7 @@ package networkdriver import ( "encoding/binary" + "errors" "fmt" "net" @@ -10,6 +11,7 @@ import ( var ( networkGetRoutesFct = netlink.NetworkGetRoutes + ErrNoDefaultRoute = errors.New("no default route") ) func CheckNameserverOverlaps(nameservers []string, toCheck *net.IPNet) error { @@ -103,7 +105,7 @@ func GetIfaceAddr(name string) (net.Addr, error) { } func GetDefaultRouteIface() (*net.Interface, error) { - rs, err := netlink.NetworkGetRoutes() + rs, err := networkGetRoutesFct() if err != nil { return nil, fmt.Errorf("unable to get routes: %v", err) } @@ -112,5 +114,5 @@ func GetDefaultRouteIface() (*net.Interface, error) { return r.Iface, nil } } - return nil, fmt.Errorf("no default route") + return nil, ErrNoDefaultRoute } From 92e61f89aad35c3103e0db1c6dacecc0c588bd2e Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Mon, 3 Feb 2014 15:36:39 -0800 Subject: [PATCH 345/364] docker/config: update -mtu flag default Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: google) --- config.go | 2 +- docker/docker.go | 2 +- integration/utils_test.go | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/config.go b/config.go index e7f87ace77..fc04c9ff16 100644 --- a/config.go +++ b/config.go @@ -49,7 +49,7 @@ func DaemonConfigFromJob(job *engine.Job) *DaemonConfig { if mtu := job.GetenvInt("Mtu"); mtu != 0 { config.Mtu = mtu } else { - config.Mtu = DefaultNetworkMtu + config.Mtu = GetDefaultNetworkMtu() } config.DisableNetwork = job.Getenv("BridgeIface") == DisableNetworkBridge diff --git a/docker/docker.go b/docker/docker.go index 8d4ae7fce7..d92f4d98ea 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -43,7 +43,7 @@ func main() { flInterContainerComm = flag.Bool([]string{"#icc", "-icc"}, true, "Enable inter-container communication") flGraphDriver = flag.String([]string{"s", "-storage-driver"}, "", "Force the docker runtime to use a specific storage driver") flHosts = docker.NewListOpts(docker.ValidateHost) - flMtu = flag.Int([]string{"#mtu", "-mtu"}, docker.GetDefaultNetworkMtu(), "Set the containers network mtu") + flMtu = flag.Int([]string{"#mtu", "-mtu"}, 0, "Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if not default route is available") ) flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers") flag.Var(&flHosts, []string{"H", "-host"}, "tcp://host:port, unix://path/to/socket, fd://* or fd://socketfd to use in daemon mode. Multiple sockets can be specified") diff --git a/integration/utils_test.go b/integration/utils_test.go index 060a447131..450cb7527f 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -4,9 +4,6 @@ import ( "archive/tar" "bytes" "fmt" - "github.com/dotcloud/docker" - "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/utils" "io" "io/ioutil" "net/http" @@ -16,6 +13,10 @@ import ( "strings" "testing" "time" + + "github.com/dotcloud/docker" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/utils" ) // This file contains utility functions for docker's unit test suite. @@ -32,7 +33,7 @@ func mkRuntime(f utils.Fataler) *docker.Runtime { config := &docker.DaemonConfig{ Root: root, AutoRestart: false, - Mtu: docker.DefaultNetworkMtu, + Mtu: docker.GetDefaultNetworkMtu(), } eng, err := engine.New(root) From ab1482e9c25b3c27b48d97ac84e60efb39ae8af7 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Mon, 3 Feb 2014 16:01:38 -0800 Subject: [PATCH 346/364] docs/cli: add mtu option Docker-DCO-1.1-Signed-off-by: Johan Euphrosine (github: google) --- docs/sources/reference/commandline/cli.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index a4d3022867..ae77080309 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -80,6 +80,7 @@ Commands -r, --restart=true: Restart previously running containers -s, --storage-driver="": Force the docker runtime to use a specific storage driver -v, --version=false: Print version information and quit + -mtu, --mtu=0: Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if not default route is available The Docker daemon is the persistent process that manages containers. Docker uses the same binary for both the daemon and client. To run the daemon you provide the ``-d`` flag. From fca2f3e742d699688c9891ccf9dd8e58591aed08 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Mon, 3 Feb 2014 16:06:29 -0800 Subject: [PATCH 347/364] Updated width to 815 per comment. Docker-DCO-1.1-Signed-off-by: Andy Rothfusz (github: metalivedev) --- docs/sources/examples/running_ssh_service.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/examples/running_ssh_service.rst b/docs/sources/examples/running_ssh_service.rst index ee9802094a..52fe1f5914 100644 --- a/docs/sources/examples/running_ssh_service.rst +++ b/docs/sources/examples/running_ssh_service.rst @@ -25,7 +25,7 @@ smooth, but it gives you a good idea. .. raw:: html -