mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-22 05:56:17 +00:00
Compare commits
63 Commits
v0.7.0-rc6
...
v0.6.7
| Author | SHA1 | Date | |
|---|---|---|---|
| 53f1bf0f99 | |||
| 9dc59797e0 | |||
| 25e443a3c7 | |||
| 33e70864a2 | |||
| d6e6214d37 | |||
| 70f1bd3104 | |||
| f7c2a00557 | |||
| 8498b44eac | |||
| e2dcfc2cf7 | |||
| 9b4c151142 | |||
| 50239e0573 | |||
| 42c23b0f04 | |||
| eec91e7941 | |||
| 3f17844b6e | |||
| bcdeb37bb6 | |||
| 362e9d6b3c | |||
| c4ab498920 | |||
| cb70eedfda | |||
| 75a7f4d90c | |||
| eaeb969138 | |||
| d7e2fc8982 | |||
| f20c738963 | |||
| fd7ab143bf | |||
| 82cdd21a34 | |||
| 2bc35287a0 | |||
| b4f7078a02 | |||
| 9e68913397 | |||
| 304a80fcd5 | |||
| 04f1d4dcdb | |||
| 171d681724 | |||
| cd4c1ac356 | |||
| b8af68a92b | |||
| 9de4590498 | |||
| 0ef6fed5c7 | |||
| 383f95bba1 | |||
| 1211065c8d | |||
| 844c13bce6 | |||
| 6014db4a7e | |||
| bf504f2afa | |||
| 61a8020e51 | |||
| 7eaa59f626 | |||
| 2cccbbdadd | |||
| 66beafa9f3 | |||
| 8e5ab5bfca | |||
| 4f9f83d6c6 | |||
| 145c2008ae | |||
| aeb304b37c | |||
| 78c843c8ef | |||
| ac821f2446 | |||
| 2fe4467d73 | |||
| 4b80ec9aae | |||
| fef41ef7bf | |||
| fe302fbfd2 | |||
| 72d02ecdde | |||
| baa687bed2 | |||
| 30ea0bebce | |||
| bc74f65068 | |||
| 152459b727 | |||
| 27159ce6ba | |||
| 0013aa7d9f | |||
| 1d7f22c0d4 | |||
| 6a693176d6 | |||
| 6d420407ca |
@@ -1,5 +1,43 @@
|
||||
# Changelog
|
||||
|
||||
## 0.6.7 (2013-11-21)
|
||||
|
||||
#### Runtime
|
||||
|
||||
* Improved stability, fixes some race conditons
|
||||
* Skip the volumes mounted when deleting the volumes of container.
|
||||
* Fix layer size computation: handle hard links correctly
|
||||
* Use the work Path for docker cp CONTAINER:PATH
|
||||
* Fix tmp dir never cleanup
|
||||
* Speedup docker ps
|
||||
* More informative error message on name collisions
|
||||
* Fix nameserver regex
|
||||
* Always return long id's
|
||||
* Fix container restart race condition
|
||||
* Keep published ports on docker stop;docker start
|
||||
* Fix container networking on Fedora
|
||||
* Correctly express "any address" to iptables
|
||||
* Fix network setup when reconnecting to ghost container
|
||||
* Prevent deletion if image is used by a running container
|
||||
* Lock around read operations in graph
|
||||
|
||||
#### RemoteAPI
|
||||
|
||||
* Return full ID on docker rmi
|
||||
|
||||
#### Client
|
||||
|
||||
+ Add -tree option to images
|
||||
+ Offline image transfer
|
||||
* Exit with status 2 on usage error and display usage on stderr
|
||||
* Do not forward SIGCHLD to container
|
||||
* Use string timestamp for docker events -since
|
||||
|
||||
#### Other
|
||||
|
||||
* Update to go 1.2rc5
|
||||
+ Add /etc/default/docker support to upstart
|
||||
|
||||
## 0.6.6 (2013-11-06)
|
||||
|
||||
#### Runtime
|
||||
@@ -17,6 +55,7 @@
|
||||
+ Prevent DNS server conflicts in CreateBridgeIface
|
||||
+ Validate bind mounts on the server side
|
||||
+ Use parent image config in docker build
|
||||
* Fix regression in /etc/hosts
|
||||
|
||||
#### Client
|
||||
|
||||
|
||||
+2
-3
@@ -46,10 +46,9 @@ run apt-get install -y -q ruby1.9.3 rubygems libffi-dev
|
||||
run gem install --no-rdoc --no-ri fpm
|
||||
run apt-get install -y -q reprepro dpkg-sig
|
||||
|
||||
# Install s3cmd 1.0.1 (earlier versions don't support env variables in the config)
|
||||
run apt-get install -y -q python-pip
|
||||
run pip install s3cmd
|
||||
run pip install python-magic
|
||||
run pip install s3cmd==1.1.0-beta3
|
||||
run pip install python-magic==0.4.6
|
||||
run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg
|
||||
|
||||
# Runtime dependencies
|
||||
|
||||
@@ -534,6 +534,18 @@ 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 {
|
||||
name := vars["name"]
|
||||
if version > 1.0 {
|
||||
w.Header().Set("Content-Type", "application/x-tar")
|
||||
}
|
||||
return srv.ImageExport(name, w)
|
||||
}
|
||||
|
||||
func postImagesLoad(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return srv.ImageLoad(r.Body)
|
||||
}
|
||||
|
||||
func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := parseForm(r); err != nil {
|
||||
return nil
|
||||
@@ -967,7 +979,7 @@ func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r *
|
||||
}
|
||||
|
||||
if copyData.Resource == "" {
|
||||
return fmt.Errorf("Resource cannot be empty")
|
||||
return fmt.Errorf("Path cannot be empty")
|
||||
}
|
||||
if copyData.Resource[0] == '/' {
|
||||
copyData.Resource = copyData.Resource[1:]
|
||||
@@ -1036,6 +1048,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
"/images/json": getImagesJSON,
|
||||
"/images/viz": getImagesViz,
|
||||
"/images/search": getImagesSearch,
|
||||
"/images/{name:.*}/get": getImagesGet,
|
||||
"/images/{name:.*}/history": getImagesHistory,
|
||||
"/images/{name:.*}/json": getImagesByName,
|
||||
"/containers/ps": getContainersJSON,
|
||||
@@ -1052,6 +1065,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
||||
"/build": postBuild,
|
||||
"/images/create": postImagesCreate,
|
||||
"/images/{name:.*}/insert": postImagesInsert,
|
||||
"/images/load": postImagesLoad,
|
||||
"/images/{name:.*}/push": postImagesPush,
|
||||
"/images/{name:.*}/tag": postImagesTag,
|
||||
"/containers/create": postContainersCreate,
|
||||
|
||||
+301
-45
@@ -22,6 +22,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
@@ -92,6 +93,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||
{"insert", "Insert a file in an image"},
|
||||
{"inspect", "Return low-level information on a container"},
|
||||
{"kill", "Kill a running container"},
|
||||
{"load", "Load an image from a tar archive"},
|
||||
{"login", "Register or Login to the docker registry server"},
|
||||
{"logs", "Fetch the logs of a container"},
|
||||
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
|
||||
@@ -102,6 +104,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||
{"rm", "Remove one or more containers"},
|
||||
{"rmi", "Remove one or more images"},
|
||||
{"run", "Run a command in a new container"},
|
||||
{"save", "Save an image to a tar archive"},
|
||||
{"search", "Search for an image in the docker index"},
|
||||
{"start", "Start a stopped container"},
|
||||
{"stop", "Stop a running container"},
|
||||
@@ -117,7 +120,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdInsert(args ...string) error {
|
||||
cmd := Subcmd("insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH")
|
||||
cmd := cli.Subcmd("insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -159,7 +162,7 @@ func MkBuildContext(dockerfile string, files [][2]string) (archive.Archive, erro
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
cmd := Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
|
||||
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")
|
||||
@@ -257,7 +260,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
|
||||
// 'docker login': login / register a user to registry service.
|
||||
func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||
cmd := Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+auth.IndexServerAddress()+"\" is the default.")
|
||||
cmd := cli.Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+auth.IndexServerAddress()+"\" is the default.")
|
||||
|
||||
var username, password, email string
|
||||
|
||||
@@ -365,7 +368,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||
|
||||
// 'docker wait': block until a container stops
|
||||
func (cli *DockerCli) CmdWait(args ...string) error {
|
||||
cmd := Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.")
|
||||
cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -388,7 +391,7 @@ func (cli *DockerCli) CmdWait(args ...string) error {
|
||||
|
||||
// 'docker version': show version information
|
||||
func (cli *DockerCli) CmdVersion(args ...string) error {
|
||||
cmd := Subcmd("version", "", "Show the docker version information.")
|
||||
cmd := cli.Subcmd("version", "", "Show the docker version information.")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -439,7 +442,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
|
||||
|
||||
// 'docker info': display system-wide information.
|
||||
func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
cmd := Subcmd("info", "", "Display system-wide information")
|
||||
cmd := cli.Subcmd("info", "", "Display system-wide information")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -491,7 +494,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdStop(args ...string) error {
|
||||
cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container (Send SIGTERM, and then SIGKILL after grace period)")
|
||||
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.")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -518,7 +521,7 @@ func (cli *DockerCli) CmdStop(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdRestart(args ...string) error {
|
||||
cmd := Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container")
|
||||
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")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -561,7 +564,7 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
|
||||
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")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
@@ -573,6 +576,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
}
|
||||
|
||||
var cErr chan error
|
||||
var tty bool
|
||||
if *attach || *openStdin {
|
||||
if cmd.NArg() > 1 {
|
||||
return fmt.Errorf("Impossible to start and attach multiple containers at once.")
|
||||
@@ -589,17 +593,13 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
tty = container.Config.Tty
|
||||
|
||||
if !container.Config.Tty {
|
||||
sigc := cli.forwardAllSignals(cmd.Arg(0))
|
||||
defer utils.StopCatch(sigc)
|
||||
}
|
||||
|
||||
if container.Config.Tty && cli.isTerminal {
|
||||
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var in io.ReadCloser
|
||||
|
||||
v := url.Values{}
|
||||
@@ -637,14 +637,20 @@ func (cli *DockerCli) CmdStart(args ...string) error {
|
||||
}
|
||||
return encounteredError
|
||||
}
|
||||
|
||||
if *openStdin || *attach {
|
||||
if tty && cli.isTerminal {
|
||||
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
|
||||
utils.Errorf("Error monitoring TTY size: %s\n", err)
|
||||
}
|
||||
}
|
||||
return <-cErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
cmd := Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container/image")
|
||||
cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container/image")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -695,7 +701,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdTop(args ...string) error {
|
||||
cmd := Subcmd("top", "CONTAINER [ps OPTIONS]", "Lookup the running processes of a container")
|
||||
cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Lookup the running processes of a container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -727,7 +733,7 @@ func (cli *DockerCli) CmdTop(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdPort(args ...string) error {
|
||||
cmd := Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
|
||||
cmd := cli.Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -769,7 +775,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
|
||||
|
||||
// 'docker rmi IMAGE' removes all images with the name IMAGE
|
||||
func (cli *DockerCli) CmdRmi(args ...string) error {
|
||||
cmd := Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
|
||||
cmd := cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -805,7 +811,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdHistory(args ...string) error {
|
||||
cmd := Subcmd("history", "[OPTIONS] IMAGE", "Show the history of an image")
|
||||
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")
|
||||
|
||||
@@ -862,7 +868,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdRm(args ...string) error {
|
||||
cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
|
||||
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")
|
||||
|
||||
@@ -896,7 +902,7 @@ func (cli *DockerCli) CmdRm(args ...string) error {
|
||||
|
||||
// 'docker kill NAME' kills a running container
|
||||
func (cli *DockerCli) CmdKill(args ...string) error {
|
||||
cmd := Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL)")
|
||||
cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL)")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -918,7 +924,7 @@ func (cli *DockerCli) CmdKill(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdImport(args ...string) error {
|
||||
cmd := Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create a new filesystem image from the contents of a tarball(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).")
|
||||
cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create a new filesystem image from the contents of a tarball(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -952,7 +958,7 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdPush(args ...string) error {
|
||||
cmd := Subcmd("push", "NAME", "Push an image or a repository to the registry")
|
||||
cmd := cli.Subcmd("push", "NAME", "Push an image or a repository to the registry")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1014,7 +1020,7 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdPull(args ...string) error {
|
||||
cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
|
||||
cmd := cli.Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
|
||||
tag := cmd.String("t", "", "Download tagged image in repository")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -1074,9 +1080,9 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdImages(args ...string) error {
|
||||
cmd := Subcmd("images", "[OPTIONS] [NAME]", "List images")
|
||||
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")
|
||||
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")
|
||||
@@ -1271,7 +1277,7 @@ func displayablePorts(ports []APIPort) string {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
cmd := Subcmd("ps", "[OPTIONS]", "List containers")
|
||||
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.")
|
||||
@@ -1360,10 +1366,10 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdCommit(args ...string) error {
|
||||
cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes")
|
||||
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 <hannibal@a-team.com>\"")
|
||||
flConfig := cmd.String("run", "", "Config automatically applied when the image is run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`)
|
||||
flConfig := cmd.String("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
|
||||
}
|
||||
@@ -1412,7 +1418,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||
cmd := Subcmd("events", "[OPTIONS]", "Get real time events from the server")
|
||||
cmd := cli.Subcmd("events", "[OPTIONS]", "Get real time events from the server")
|
||||
since := cmd.String("since", "", "Show previously created events and then stream.")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -1445,7 +1451,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdExport(args ...string) error {
|
||||
cmd := Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive")
|
||||
cmd := cli.Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1462,7 +1468,7 @@ func (cli *DockerCli) CmdExport(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdDiff(args ...string) error {
|
||||
cmd := Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem")
|
||||
cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1488,7 +1494,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||
cmd := Subcmd("logs", "CONTAINER", "Fetch the logs of a container")
|
||||
cmd := cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1515,7 +1521,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||
cmd := Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container")
|
||||
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)")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
@@ -1537,7 +1543,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
|
||||
}
|
||||
|
||||
@@ -1570,7 +1576,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdSearch(args ...string) error {
|
||||
cmd := Subcmd("search", "TERM", "Search the docker index for images")
|
||||
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")
|
||||
@@ -1682,7 +1688,7 @@ func (opts PathOpts) Set(val string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdTag(args ...string) error {
|
||||
cmd := Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY[:TAG]", "Tag an image into a repository")
|
||||
cmd := cli.Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY[:TAG]", "Tag an image into a repository")
|
||||
force := cmd.Bool("f", false, "Force")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
@@ -1715,8 +1721,223 @@ func (cli *DockerCli) CmdTag(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//FIXME Only used in tests
|
||||
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
|
||||
cmd := flag.NewFlagSet("run", flag.ContinueOnError)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.Usage = nil
|
||||
return parseRun(cmd, args, capabilities)
|
||||
}
|
||||
|
||||
func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
|
||||
|
||||
flHostname := cmd.String("h", "", "Container host name")
|
||||
flWorkingDir := cmd.String("w", "", "Working directory inside the container")
|
||||
flUser := cmd.String("u", "", "Username or UID")
|
||||
flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
|
||||
flAttach := NewAttachOpts()
|
||||
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
|
||||
flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
|
||||
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
|
||||
flMemoryString := cmd.String("m", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
|
||||
flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
|
||||
flNetwork := cmd.Bool("n", true, "Enable networking for this container")
|
||||
flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
|
||||
flAutoRemove := cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
|
||||
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")
|
||||
flPublishAll := cmd.Bool("P", false, "Publish all exposed ports to the host interfaces")
|
||||
|
||||
if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit {
|
||||
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
|
||||
*flMemoryString = ""
|
||||
}
|
||||
|
||||
flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
|
||||
|
||||
var flPublish utils.ListOpts
|
||||
cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)")
|
||||
|
||||
var flExpose utils.ListOpts
|
||||
cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
|
||||
|
||||
var flEnv utils.ListOpts
|
||||
cmd.Var(&flEnv, "e", "Set environment variables")
|
||||
|
||||
var flDns utils.ListOpts
|
||||
cmd.Var(&flDns, "dns", "Set custom dns servers")
|
||||
|
||||
flVolumes := NewPathOpts()
|
||||
cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
|
||||
|
||||
var flVolumesFrom utils.ListOpts
|
||||
cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
|
||||
|
||||
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
|
||||
|
||||
var flLxcOpts utils.ListOpts
|
||||
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
|
||||
|
||||
var flLinks utils.ListOpts
|
||||
cmd.Var(&flLinks, "link", "Add link to another container (name:alias)")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
if *flDetach && len(flAttach) > 0 {
|
||||
return nil, nil, cmd, ErrConflictAttachDetach
|
||||
}
|
||||
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
|
||||
return nil, nil, cmd, ErrInvalidWorikingDirectory
|
||||
}
|
||||
if *flDetach && *flAutoRemove {
|
||||
return nil, nil, cmd, ErrConflictDetachAutoRemove
|
||||
}
|
||||
|
||||
// If neither -d or -a are set, attach to everything by default
|
||||
if len(flAttach) == 0 && !*flDetach {
|
||||
if !*flDetach {
|
||||
flAttach.Set("stdout")
|
||||
flAttach.Set("stderr")
|
||||
if *flStdin {
|
||||
flAttach.Set("stdin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
envs := []string{}
|
||||
|
||||
for _, env := range flEnv {
|
||||
arr := strings.Split(env, "=")
|
||||
if len(arr) > 1 {
|
||||
envs = append(envs, env)
|
||||
} else {
|
||||
v := os.Getenv(env)
|
||||
envs = append(envs, env+"="+v)
|
||||
}
|
||||
}
|
||||
|
||||
var flMemory int64
|
||||
|
||||
if *flMemoryString != "" {
|
||||
parsedMemory, err := utils.RAMInBytes(*flMemoryString)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
flMemory = parsedMemory
|
||||
}
|
||||
|
||||
var binds []string
|
||||
|
||||
// add any bind targets to the list of container volumes
|
||||
for bind := range flVolumes {
|
||||
arr := strings.Split(bind, ":")
|
||||
if len(arr) > 1 {
|
||||
if arr[0] == "/" {
|
||||
return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'")
|
||||
}
|
||||
dstDir := arr[1]
|
||||
flVolumes[dstDir] = struct{}{}
|
||||
binds = append(binds, bind)
|
||||
delete(flVolumes, bind)
|
||||
}
|
||||
}
|
||||
|
||||
parsedArgs := cmd.Args()
|
||||
runCmd := []string{}
|
||||
entrypoint := []string{}
|
||||
image := ""
|
||||
if len(parsedArgs) >= 1 {
|
||||
image = cmd.Arg(0)
|
||||
}
|
||||
if len(parsedArgs) > 1 {
|
||||
runCmd = parsedArgs[1:]
|
||||
}
|
||||
if *flEntrypoint != "" {
|
||||
entrypoint = []string{*flEntrypoint}
|
||||
}
|
||||
|
||||
var lxcConf []KeyValuePair
|
||||
lxcConf, err := parseLxcConfOpts(flLxcOpts)
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
hostname := *flHostname
|
||||
domainname := ""
|
||||
|
||||
parts := strings.SplitN(hostname, ".", 2)
|
||||
if len(parts) > 1 {
|
||||
hostname = parts[0]
|
||||
domainname = parts[1]
|
||||
}
|
||||
|
||||
ports, portBindings, err := parsePortSpecs(flPublish)
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
// Merge in exposed ports to the map of published ports
|
||||
for _, e := range flExpose {
|
||||
if strings.Contains(e, ":") {
|
||||
return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e)
|
||||
}
|
||||
p := NewPort(splitProtoPort(e))
|
||||
if _, exists := ports[p]; !exists {
|
||||
ports[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
Hostname: hostname,
|
||||
Domainname: domainname,
|
||||
PortSpecs: nil, // Deprecated
|
||||
ExposedPorts: ports,
|
||||
User: *flUser,
|
||||
Tty: *flTty,
|
||||
NetworkDisabled: !*flNetwork,
|
||||
OpenStdin: *flStdin,
|
||||
Memory: flMemory,
|
||||
CpuShares: *flCpuShares,
|
||||
AttachStdin: flAttach.Get("stdin"),
|
||||
AttachStdout: flAttach.Get("stdout"),
|
||||
AttachStderr: flAttach.Get("stderr"),
|
||||
Env: envs,
|
||||
Cmd: runCmd,
|
||||
Dns: flDns,
|
||||
Image: image,
|
||||
Volumes: flVolumes,
|
||||
VolumesFrom: strings.Join(flVolumesFrom, ","),
|
||||
Entrypoint: entrypoint,
|
||||
WorkingDir: *flWorkingDir,
|
||||
}
|
||||
|
||||
hostConfig := &HostConfig{
|
||||
Binds: binds,
|
||||
ContainerIDFile: *flContainerIDFile,
|
||||
LxcConf: lxcConf,
|
||||
Privileged: *flPrivileged,
|
||||
PortBindings: portBindings,
|
||||
Links: flLinks,
|
||||
PublishAllPorts: *flPublishAll,
|
||||
}
|
||||
|
||||
if capabilities != nil && flMemory > 0 && !capabilities.SwapLimit {
|
||||
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
|
||||
config.MemorySwap = -1
|
||||
}
|
||||
|
||||
// When allocating stdin in attached mode, close stdin at client disconnect
|
||||
if config.OpenStdin && config.AttachStdin {
|
||||
config.StdinOnce = true
|
||||
}
|
||||
return config, hostConfig, cmd, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
config, hostConfig, cmd, err := ParseRun(args, nil)
|
||||
config, hostConfig, cmd, err := parseRun(cli.Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container"), args, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1927,7 +2148,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdCp(args ...string) error {
|
||||
cmd := Subcmd("cp", "CONTAINER:RESOURCE HOSTPATH", "Copy files/folders from the RESOURCE to the HOSTPATH")
|
||||
cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTPATH", "Copy files/folders from the PATH to the HOSTPATH")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -1941,7 +2162,7 @@ func (cli *DockerCli) CmdCp(args ...string) error {
|
||||
info := strings.Split(cmd.Arg(0), ":")
|
||||
|
||||
if len(info) != 2 {
|
||||
return fmt.Errorf("Error: Resource not specified")
|
||||
return fmt.Errorf("Error: Path not specified")
|
||||
}
|
||||
|
||||
copyData.Resource = info[1]
|
||||
@@ -1961,6 +2182,41 @@ func (cli *DockerCli) CmdCp(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdSave(args ...string) error {
|
||||
cmd := cli.Subcmd("save", "IMAGE DESTINATION", "Save an image to a tar archive")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.NArg() != 1 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
image := cmd.Arg(0)
|
||||
if err := cli.stream("GET", "/images/"+image+"/get", nil, cli.out, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdLoad(args ...string) error {
|
||||
cmd := cli.Subcmd("load", "SOURCE", "Load an image from a tar archive")
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cmd.NArg() != 0 {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := cli.stream("POST", "/images/load", cli.in, cli.out, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) {
|
||||
var params io.Reader
|
||||
if data != nil {
|
||||
@@ -2211,12 +2467,12 @@ func (cli *DockerCli) monitorTtySize(id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func Subcmd(name, signature, description string) *flag.FlagSet {
|
||||
func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {
|
||||
flags := flag.NewFlagSet(name, flag.ContinueOnError)
|
||||
flags.Usage = func() {
|
||||
// FIXME: use custom stdout or return error
|
||||
fmt.Fprintf(os.Stdout, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
|
||||
fmt.Fprintf(cli.err, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
|
||||
flags.PrintDefaults()
|
||||
os.Exit(2)
|
||||
}
|
||||
return flags
|
||||
}
|
||||
@@ -2261,7 +2517,7 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
|
||||
if err := json.Unmarshal(body, c); err != nil {
|
||||
return false, -1, err
|
||||
}
|
||||
return c.State.Running, c.State.ExitCode, nil
|
||||
return c.State.IsRunning(), c.State.GetExitCode(), nil
|
||||
}
|
||||
|
||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
|
||||
|
||||
+60
-240
@@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/archive"
|
||||
"github.com/dotcloud/docker/term"
|
||||
@@ -20,11 +19,14 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Container struct {
|
||||
sync.Mutex
|
||||
|
||||
root string
|
||||
|
||||
ID string
|
||||
@@ -156,218 +158,6 @@ func NewPort(proto, port string) Port {
|
||||
return Port(fmt.Sprintf("%s/%s", port, proto))
|
||||
}
|
||||
|
||||
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
|
||||
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
|
||||
if os.Getenv("TEST") != "" {
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.Usage = nil
|
||||
}
|
||||
|
||||
flHostname := cmd.String("h", "", "Container host name")
|
||||
flWorkingDir := cmd.String("w", "", "Working directory inside the container")
|
||||
flUser := cmd.String("u", "", "Username or UID")
|
||||
flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
|
||||
flAttach := NewAttachOpts()
|
||||
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
|
||||
flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
|
||||
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
|
||||
flMemoryString := cmd.String("m", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
|
||||
flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
|
||||
flNetwork := cmd.Bool("n", true, "Enable networking for this container")
|
||||
flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
|
||||
flAutoRemove := cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
|
||||
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")
|
||||
flPublishAll := cmd.Bool("P", false, "Publish all exposed ports to the host interfaces")
|
||||
|
||||
if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit {
|
||||
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
|
||||
*flMemoryString = ""
|
||||
}
|
||||
|
||||
flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
|
||||
|
||||
var flPublish utils.ListOpts
|
||||
cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)")
|
||||
|
||||
var flExpose utils.ListOpts
|
||||
cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
|
||||
|
||||
var flEnv utils.ListOpts
|
||||
cmd.Var(&flEnv, "e", "Set environment variables")
|
||||
|
||||
var flDns utils.ListOpts
|
||||
cmd.Var(&flDns, "dns", "Set custom dns servers")
|
||||
|
||||
flVolumes := NewPathOpts()
|
||||
cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
|
||||
|
||||
var flVolumesFrom utils.ListOpts
|
||||
cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
|
||||
|
||||
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
|
||||
|
||||
var flLxcOpts utils.ListOpts
|
||||
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
|
||||
|
||||
var flLinks utils.ListOpts
|
||||
cmd.Var(&flLinks, "link", "Add link to another container (name:alias)")
|
||||
|
||||
if err := cmd.Parse(args); err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
if *flDetach && len(flAttach) > 0 {
|
||||
return nil, nil, cmd, ErrConflictAttachDetach
|
||||
}
|
||||
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
|
||||
return nil, nil, cmd, ErrInvalidWorikingDirectory
|
||||
}
|
||||
if *flDetach && *flAutoRemove {
|
||||
return nil, nil, cmd, ErrConflictDetachAutoRemove
|
||||
}
|
||||
|
||||
// If neither -d or -a are set, attach to everything by default
|
||||
if len(flAttach) == 0 && !*flDetach {
|
||||
if !*flDetach {
|
||||
flAttach.Set("stdout")
|
||||
flAttach.Set("stderr")
|
||||
if *flStdin {
|
||||
flAttach.Set("stdin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
envs := []string{}
|
||||
|
||||
for _, env := range flEnv {
|
||||
arr := strings.Split(env, "=")
|
||||
if len(arr) > 1 {
|
||||
envs = append(envs, env)
|
||||
} else {
|
||||
v := os.Getenv(env)
|
||||
envs = append(envs, env+"="+v)
|
||||
}
|
||||
}
|
||||
|
||||
var flMemory int64
|
||||
|
||||
if *flMemoryString != "" {
|
||||
parsedMemory, err := utils.RAMInBytes(*flMemoryString)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
flMemory = parsedMemory
|
||||
}
|
||||
|
||||
var binds []string
|
||||
|
||||
// add any bind targets to the list of container volumes
|
||||
for bind := range flVolumes {
|
||||
arr := strings.Split(bind, ":")
|
||||
if len(arr) > 1 {
|
||||
if arr[0] == "/" {
|
||||
return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'")
|
||||
}
|
||||
dstDir := arr[1]
|
||||
flVolumes[dstDir] = struct{}{}
|
||||
binds = append(binds, bind)
|
||||
delete(flVolumes, bind)
|
||||
}
|
||||
}
|
||||
|
||||
parsedArgs := cmd.Args()
|
||||
runCmd := []string{}
|
||||
entrypoint := []string{}
|
||||
image := ""
|
||||
if len(parsedArgs) >= 1 {
|
||||
image = cmd.Arg(0)
|
||||
}
|
||||
if len(parsedArgs) > 1 {
|
||||
runCmd = parsedArgs[1:]
|
||||
}
|
||||
if *flEntrypoint != "" {
|
||||
entrypoint = []string{*flEntrypoint}
|
||||
}
|
||||
|
||||
var lxcConf []KeyValuePair
|
||||
lxcConf, err := parseLxcConfOpts(flLxcOpts)
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
hostname := *flHostname
|
||||
domainname := ""
|
||||
|
||||
parts := strings.SplitN(hostname, ".", 2)
|
||||
if len(parts) > 1 {
|
||||
hostname = parts[0]
|
||||
domainname = parts[1]
|
||||
}
|
||||
|
||||
ports, portBindings, err := parsePortSpecs(flPublish)
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
|
||||
// Merge in exposed ports to the map of published ports
|
||||
for _, e := range flExpose {
|
||||
if strings.Contains(e, ":") {
|
||||
return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e)
|
||||
}
|
||||
p := NewPort(splitProtoPort(e))
|
||||
if _, exists := ports[p]; !exists {
|
||||
ports[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
Hostname: hostname,
|
||||
Domainname: domainname,
|
||||
PortSpecs: nil, // Deprecated
|
||||
ExposedPorts: ports,
|
||||
User: *flUser,
|
||||
Tty: *flTty,
|
||||
NetworkDisabled: !*flNetwork,
|
||||
OpenStdin: *flStdin,
|
||||
Memory: flMemory,
|
||||
CpuShares: *flCpuShares,
|
||||
AttachStdin: flAttach.Get("stdin"),
|
||||
AttachStdout: flAttach.Get("stdout"),
|
||||
AttachStderr: flAttach.Get("stderr"),
|
||||
Env: envs,
|
||||
Cmd: runCmd,
|
||||
Dns: flDns,
|
||||
Image: image,
|
||||
Volumes: flVolumes,
|
||||
VolumesFrom: strings.Join(flVolumesFrom, ","),
|
||||
Entrypoint: entrypoint,
|
||||
WorkingDir: *flWorkingDir,
|
||||
}
|
||||
|
||||
hostConfig := &HostConfig{
|
||||
Binds: binds,
|
||||
ContainerIDFile: *flContainerIDFile,
|
||||
LxcConf: lxcConf,
|
||||
Privileged: *flPrivileged,
|
||||
PortBindings: portBindings,
|
||||
Links: flLinks,
|
||||
PublishAllPorts: *flPublishAll,
|
||||
}
|
||||
|
||||
if capabilities != nil && flMemory > 0 && !capabilities.SwapLimit {
|
||||
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
|
||||
config.MemorySwap = -1
|
||||
}
|
||||
|
||||
// When allocating stdin in attached mode, close stdin at client disconnect
|
||||
if config.OpenStdin && config.AttachStdin {
|
||||
config.StdinOnce = true
|
||||
}
|
||||
return config, hostConfig, cmd, nil
|
||||
}
|
||||
|
||||
type PortMapping map[string]string // Deprecated
|
||||
|
||||
type NetworkSettings struct {
|
||||
@@ -700,9 +490,10 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
|
||||
}
|
||||
|
||||
func (container *Container) Start() (err error) {
|
||||
container.State.Lock()
|
||||
defer container.State.Unlock()
|
||||
if container.State.Running {
|
||||
container.Lock()
|
||||
defer container.Unlock()
|
||||
|
||||
if container.State.IsRunning() {
|
||||
return fmt.Errorf("The container %s is already running.", container.ID)
|
||||
}
|
||||
defer func() {
|
||||
@@ -1031,7 +822,7 @@ func (container *Container) Start() (err error) {
|
||||
}
|
||||
// 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)
|
||||
container.State.SetRunning(container.cmd.Process.Pid)
|
||||
|
||||
// Init the lock
|
||||
container.waitLock = make(chan struct{})
|
||||
@@ -1039,14 +830,14 @@ func (container *Container) Start() (err error) {
|
||||
container.ToDisk()
|
||||
go container.monitor()
|
||||
|
||||
defer utils.Debugf("Container running: %v", container.State.Running)
|
||||
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.Running {
|
||||
if !container.State.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
output, err := exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput()
|
||||
@@ -1063,11 +854,11 @@ func (container *Container) Start() (err error) {
|
||||
if strings.Contains(string(output), "RUNNING") {
|
||||
return nil
|
||||
}
|
||||
utils.Debugf("Waiting for the container to start (running: %v): %s", container.State.Running, bytes.TrimSpace(output))
|
||||
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.Running {
|
||||
if container.State.IsRunning() {
|
||||
return ErrContainerStartTimeout
|
||||
}
|
||||
return ErrContainerStart
|
||||
@@ -1148,11 +939,12 @@ func (container *Container) allocateNetwork() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var iface *NetworkInterface
|
||||
var err error
|
||||
if container.State.Ghost {
|
||||
manager := container.runtime.networkManager
|
||||
if manager.disabled {
|
||||
var (
|
||||
iface *NetworkInterface
|
||||
err error
|
||||
)
|
||||
if container.State.IsGhost() {
|
||||
if manager := container.runtime.networkManager; manager.disabled {
|
||||
iface = &NetworkInterface{disabled: true}
|
||||
} else {
|
||||
iface = &NetworkInterface{
|
||||
@@ -1188,10 +980,12 @@ func (container *Container) allocateNetwork() error {
|
||||
}
|
||||
}
|
||||
|
||||
portSpecs := make(map[Port]struct{})
|
||||
bindings := make(map[Port][]PortBinding)
|
||||
var (
|
||||
portSpecs = make(map[Port]struct{})
|
||||
bindings = make(map[Port][]PortBinding)
|
||||
)
|
||||
|
||||
if !container.State.Ghost {
|
||||
if !container.State.IsGhost() {
|
||||
if container.Config.ExposedPorts != nil {
|
||||
portSpecs = container.Config.ExposedPorts
|
||||
}
|
||||
@@ -1300,7 +1094,7 @@ func (container *Container) monitor() {
|
||||
}
|
||||
|
||||
// Report status back
|
||||
container.State.setStopped(exitCode)
|
||||
container.State.SetStopped(exitCode)
|
||||
|
||||
// Release the lock
|
||||
close(container.waitLock)
|
||||
@@ -1350,10 +1144,10 @@ func (container *Container) cleanup() {
|
||||
}
|
||||
|
||||
func (container *Container) kill(sig int) error {
|
||||
container.State.Lock()
|
||||
defer container.State.Unlock()
|
||||
container.Lock()
|
||||
defer container.Unlock()
|
||||
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1366,7 +1160,7 @@ func (container *Container) kill(sig int) error {
|
||||
}
|
||||
|
||||
func (container *Container) Kill() error {
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1391,7 +1185,7 @@ func (container *Container) Kill() error {
|
||||
}
|
||||
|
||||
func (container *Container) Stop(seconds int) error {
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1425,7 +1219,7 @@ 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.ExitCode
|
||||
return container.State.GetExitCode()
|
||||
}
|
||||
|
||||
func (container *Container) Resize(h, w int) error {
|
||||
@@ -1559,20 +1353,46 @@ func validateID(id string) error {
|
||||
// GetSize, return real size, virtual size
|
||||
func (container *Container) GetSize() (int64, int64) {
|
||||
var sizeRw, sizeRootfs int64
|
||||
data := make(map[uint64]bool)
|
||||
|
||||
filepath.Walk(container.rwPath(), func(path string, fileInfo os.FileInfo, err error) error {
|
||||
if fileInfo != nil {
|
||||
sizeRw += fileInfo.Size()
|
||||
if fileInfo == nil {
|
||||
return nil
|
||||
}
|
||||
size := fileInfo.Size()
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
inode := fileInfo.Sys().(*syscall.Stat_t).Ino
|
||||
if _, entryExists := data[inode]; entryExists {
|
||||
return nil
|
||||
}
|
||||
data[inode] = false
|
||||
|
||||
sizeRw += size
|
||||
return nil
|
||||
})
|
||||
|
||||
data = make(map[uint64]bool)
|
||||
_, err := os.Stat(container.RootfsPath())
|
||||
if err == nil {
|
||||
filepath.Walk(container.RootfsPath(), func(path string, fileInfo os.FileInfo, err error) error {
|
||||
if fileInfo != nil {
|
||||
sizeRootfs += fileInfo.Size()
|
||||
if fileInfo == nil {
|
||||
return nil
|
||||
}
|
||||
size := fileInfo.Size()
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
inode := fileInfo.Sys().(*syscall.Stat_t).Ino
|
||||
if _, entryExists := data[inode]; entryExists {
|
||||
return nil
|
||||
}
|
||||
data[inode] = false
|
||||
|
||||
sizeRootfs += size
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1171,6 +1171,53 @@ Monitor Docker's events
|
||||
: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
|
||||
================
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
:title: Registry API
|
||||
:title: Remote API Client Libraries
|
||||
:description: Various client libraries available to use with the Docker remote API
|
||||
:keywords: API, Docker, index, registry, REST, documentation, clients, Python, Ruby, Javascript, Erlang, Go
|
||||
|
||||
|
||||
@@ -88,31 +88,65 @@ 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 to the resulting image in case of success.
|
||||
-t="": 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
|
||||
When a single Dockerfile is given as URL, then no context is set. When a git repository is set as URL, the repository is used as context
|
||||
|
||||
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 using an :ref:`ADD <dockerfile_add>` instruction. When a
|
||||
single ``Dockerfile`` is given as URL, then no context is set. When a
|
||||
git repository is set as URL, then the repository is used as the
|
||||
context
|
||||
|
||||
.. _cli_build_examples:
|
||||
|
||||
.. seealso:: :ref:`dockerbuilder`.
|
||||
|
||||
Examples:
|
||||
~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker build .
|
||||
Uploading context 10240 bytes
|
||||
Step 1 : FROM busybox
|
||||
Pulling repository busybox
|
||||
---> e9aa60c60128MB/2.284 MB (100%) endpoint: https://cdn-registry-1.docker.io/v1/
|
||||
Step 2 : RUN ls -lh /
|
||||
---> Running in 9c9e81692ae9
|
||||
total 24
|
||||
drwxr-xr-x 2 root root 4.0K Mar 12 2013 bin
|
||||
drwxr-xr-x 5 root root 4.0K Oct 19 00:19 dev
|
||||
drwxr-xr-x 2 root root 4.0K Oct 19 00:19 etc
|
||||
drwxr-xr-x 2 root root 4.0K Nov 15 23:34 lib
|
||||
lrwxrwxrwx 1 root root 3 Mar 12 2013 lib64 -> lib
|
||||
dr-xr-xr-x 116 root root 0 Nov 15 23:34 proc
|
||||
lrwxrwxrwx 1 root root 3 Mar 12 2013 sbin -> bin
|
||||
dr-xr-xr-x 13 root root 0 Nov 15 23:34 sys
|
||||
drwxr-xr-x 2 root root 4.0K Mar 12 2013 tmp
|
||||
drwxr-xr-x 2 root root 4.0K Nov 15 23:34 usr
|
||||
---> b35f4035db3f
|
||||
Step 3 : CMD echo Hello World
|
||||
---> Running in 02071fceb21b
|
||||
---> f52f38b7823e
|
||||
Successfully built f52f38b7823e
|
||||
|
||||
This will read the ``Dockerfile`` from the current directory. It will
|
||||
also send any other files and directories found in the current
|
||||
directory to the ``docker`` daemon.
|
||||
This example specifies that the PATH is ``.``, and so all the files in
|
||||
the local directory get tar'd and sent to the Docker daemon. The PATH
|
||||
specifies where to find the files for the "context" of the build on
|
||||
the Docker daemon. Remember that the daemon could be running on a
|
||||
remote machine and that no parsing of the Dockerfile happens at the
|
||||
client side (where you're running ``docker build``). That means that
|
||||
*all* the files at PATH get sent, not just the ones listed to
|
||||
:ref:`ADD <dockerfile_add>` in the ``Dockerfile``.
|
||||
|
||||
The transfer of context from the local machine to the Docker daemon is
|
||||
what the ``docker`` client means when you see the "Uploading context"
|
||||
message.
|
||||
|
||||
The contents of this directory would be used by ``ADD`` commands found
|
||||
within the ``Dockerfile``. This will send a lot of data to the
|
||||
``docker`` daemon if the current directory contains a lot of data. If
|
||||
the absolute path is provided instead of ``.`` then only the files and
|
||||
directories required by the ADD commands from the ``Dockerfile`` will be
|
||||
added to the context and transferred to the ``docker`` daemon.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
@@ -129,16 +163,15 @@ tag will be ``2.0``
|
||||
|
||||
This will read a ``Dockerfile`` from *stdin* without context. Due to
|
||||
the lack of a context, no contents of any local directory will be sent
|
||||
to the ``docker`` daemon. ``ADD`` doesn't work when running in this
|
||||
mode because the absence of the context provides no source files to
|
||||
copy to the container.
|
||||
to the ``docker`` daemon. Since there is no context, a Dockerfile
|
||||
``ADD`` only works if it refers to a remote URL.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sudo docker build github.com/creack/docker-firefox
|
||||
|
||||
This will clone the Github repository and use it as context. The
|
||||
``Dockerfile`` at the root of the repository is used as
|
||||
This will clone the Github repository and use the cloned repository as
|
||||
context. The ``Dockerfile`` at the root of the repository is used as
|
||||
``Dockerfile``. Note that you can specify an arbitrary git repository
|
||||
by using the ``git://`` schema.
|
||||
|
||||
@@ -157,7 +190,7 @@ by using the ``git://`` schema.
|
||||
-m="": Commit message
|
||||
-author="": Author (eg. "John Hannibal Smith <hannibal@a-team.com>"
|
||||
-run="": Configuration to be applied when the image is launched with `docker run`.
|
||||
(ex: '{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
|
||||
(ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
|
||||
|
||||
Simple commit of an existing container
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -173,7 +206,7 @@ Simple commit of an existing container
|
||||
$ docker images | head
|
||||
REPOSITORY TAG ID CREATED SIZE
|
||||
SvenDowideit/testimage version3 f5283438590d 16 seconds ago 204.2 MB (virtual 335.7 MB)
|
||||
S
|
||||
|
||||
|
||||
Full -run example
|
||||
.................
|
||||
@@ -219,10 +252,15 @@ Full -run example
|
||||
|
||||
::
|
||||
|
||||
Usage: docker cp CONTAINER:RESOURCE HOSTPATH
|
||||
Usage: docker cp CONTAINER:PATH HOSTPATH
|
||||
|
||||
Copy files/folders from the containers filesystem to the host
|
||||
path. Paths are relative to the root of the filesystem.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker cp 7bb0e258aefe:/etc/debian_version .
|
||||
$ sudo docker cp blue_frog:/etc/hosts .
|
||||
|
||||
.. _cli_diff:
|
||||
|
||||
@@ -392,18 +430,52 @@ To see how the docker:latest image was built:
|
||||
|
||||
List images
|
||||
|
||||
-a=false: show all 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
|
||||
|
||||
Listing the most recently created images
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker images | head
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
<none> <none> 77af4d6b9913 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
committest latest b6fa739cedf5 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 78a85c484f71 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
docker latest 30557a29d5ab 20 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 0124422dd9f9 20 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 18ad6fad3402 22 hours ago 23.68 MB (virtual 1.082 GB)
|
||||
<none> <none> f9f1e26352f0 23 hours ago 30.46 MB (virtual 1.089 GB)
|
||||
tryout latest 2629d1fa0b81 23 hours ago 16.4 kB (virtual 131.5 MB)
|
||||
<none> <none> 5ed6274db6ce 24 hours ago 30.44 MB (virtual 1.089 GB)
|
||||
|
||||
Listing the full length image IDs
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ sudo docker images -notrunc | head
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
<none> <none> 77af4d6b9913e693e8d0b4b294fa62ade6054e6b2f1ffb617ac955dd63fb0182 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
committest latest b6fa739cedf5ea12a620a439402b6004d057da800f91c7524b5086a5e4749c9f 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 78a85c484f71509adeaace20e72e941f6bdd2b25b4c75da8693efd9f61a37921 19 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
docker latest 30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4 20 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 0124422dd9f9cf7ef15c0617cda3931ee68346455441d66ab8bdc5b05e9fdce5 20 hours ago 30.53 MB (virtual 1.089 GB)
|
||||
<none> <none> 18ad6fad340262ac2a636efd98a6d1f0ea775ae3d45240d3418466495a19a81b 22 hours ago 23.68 MB (virtual 1.082 GB)
|
||||
<none> <none> f9f1e26352f0a3ba6a0ff68167559f64f3e21ff7ada60366e2d44a04befd1d3a 23 hours ago 30.46 MB (virtual 1.089 GB)
|
||||
tryout latest 2629d1fa0b81b222fca63371ca16cbf6a0772d07759ff80e8d1369b926940074 23 hours ago 16.4 kB (virtual 131.5 MB)
|
||||
<none> <none> 5ed6274db6ceb2397844896966ea239290555e74ef307030ebb01ff91b1914df 24 hours ago 30.44 MB (virtual 1.089 GB)
|
||||
|
||||
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.
|
||||
@@ -412,9 +484,9 @@ Displaying images visually
|
||||
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)
|
||||
@@ -559,6 +631,18 @@ Known Issues (kill)
|
||||
* :issue:`197` indicates that ``docker kill`` may leave directories
|
||||
behind and make it difficult to remove the container.
|
||||
|
||||
.. _cli_load:
|
||||
|
||||
``load``
|
||||
--------
|
||||
|
||||
::
|
||||
|
||||
Usage: docker load < repository.tar
|
||||
|
||||
Loads a tarred repository from the standard input stream.
|
||||
Restores both images and tags.
|
||||
|
||||
.. _cli_login:
|
||||
|
||||
``login``
|
||||
@@ -852,6 +936,17 @@ Known Issues (run -volumes-from)
|
||||
could indicate a permissions problem with AppArmor. Please see the
|
||||
issue for a workaround.
|
||||
|
||||
.. _cli_save:
|
||||
|
||||
``save``
|
||||
|
||||
::
|
||||
|
||||
Usage: docker save image > repository.tar
|
||||
|
||||
Streams a tarred repository to the standard output stream.
|
||||
Contains all parent layers, and all tags + versions.
|
||||
|
||||
.. _cli_search:
|
||||
|
||||
``search``
|
||||
|
||||
@@ -22,12 +22,10 @@ Amazon QuickStart
|
||||
|
||||
1. **Choose an image:**
|
||||
|
||||
* Open http://cloud-images.ubuntu.com/locator/ec2/
|
||||
* Enter ``amd64 precise`` in the search field (it will search as you
|
||||
type)
|
||||
* Pick an image by clicking on the image name. *An EBS-enabled
|
||||
image will let you use a t1.micro instance.* Clicking on the image
|
||||
name will take you to your AWS Console.
|
||||
* Launch the `Create Instance Wizard` <https://console.aws.amazon.com/ec2/v2/home?#LaunchInstanceWizard:> menu on your AWS Console
|
||||
* Select "Community AMIs" option and serch for ``amd64 precise`` (click enter to search)
|
||||
* If you choose a EBS enabled AMI you will be able to launch a `t1.micro` instance (more info on `pricing` <http://aws.amazon.com/en/ec2/pricing/> )
|
||||
* When you click select you'll be taken to the instance setup, and you're one click away from having your Ubuntu VM up and running.
|
||||
|
||||
2. **Tell CloudInit to install Docker:**
|
||||
|
||||
|
||||
@@ -15,27 +15,39 @@ commit them along the way, giving you a final image.
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
.. _dockerfile_usage:
|
||||
|
||||
1. Usage
|
||||
========
|
||||
|
||||
To build an image from a source repository, create a description file
|
||||
called ``Dockerfile`` at the root of your repository. This file will
|
||||
describe the steps to assemble the image.
|
||||
To :ref:`build <cli_build>` an image from a source repository, create
|
||||
a description file called ``Dockerfile`` at the root of your
|
||||
repository. This file will describe the steps to assemble the image.
|
||||
|
||||
Then call ``docker build`` with the path of your source repository as
|
||||
argument:
|
||||
argument (for example, ``.``):
|
||||
|
||||
``sudo docker build .``
|
||||
|
||||
The path to the source repository defines where to find the *context*
|
||||
of the build. The build is run by the Docker daemon, not by the CLI,
|
||||
so the whole context must be transferred to the daemon. The Docker CLI
|
||||
reports "Uploading context" when the context is sent to the daemon.
|
||||
|
||||
You can specify a repository and tag at which to save the new image if the
|
||||
build succeeds:
|
||||
|
||||
``sudo docker build -t shykes/myapp .``
|
||||
|
||||
Docker 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 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.
|
||||
|
||||
When you're done with your build, you're ready to look into :ref:`image_push`.
|
||||
When you're done with your build, you're ready to look into
|
||||
:ref:`image_push`.
|
||||
|
||||
.. _dockerfile_format:
|
||||
|
||||
2. Format
|
||||
=========
|
||||
@@ -63,12 +75,16 @@ allows statements like:
|
||||
# Comment
|
||||
RUN echo 'we are running some # of cool things'
|
||||
|
||||
.. _dockerfile_instructions:
|
||||
|
||||
3. Instructions
|
||||
===============
|
||||
|
||||
Here is the set of instructions you can use in a ``Dockerfile`` for
|
||||
building images.
|
||||
|
||||
.. _dockerfile_from:
|
||||
|
||||
3.1 FROM
|
||||
--------
|
||||
|
||||
@@ -94,6 +110,8 @@ output by the commit before each new ``FROM`` command.
|
||||
If no ``tag`` is given to the ``FROM`` instruction, ``latest`` is
|
||||
assumed. If the used tag does not exist, an error will be returned.
|
||||
|
||||
.. _dockerfile_maintainer:
|
||||
|
||||
3.2 MAINTAINER
|
||||
--------------
|
||||
|
||||
@@ -102,6 +120,8 @@ assumed. If the used tag does not exist, an error will be returned.
|
||||
The ``MAINTAINER`` instruction allows you to set the *Author* field of
|
||||
the generated images.
|
||||
|
||||
.. _dockerfile_run:
|
||||
|
||||
3.3 RUN
|
||||
-------
|
||||
|
||||
@@ -124,7 +144,7 @@ Known Issues (RUN)
|
||||
``rm`` a file, for example. The issue describes a workaround.
|
||||
* :issue:`2424` Locale will not be set automatically.
|
||||
|
||||
|
||||
.. _dockerfile_cmd:
|
||||
|
||||
3.4 CMD
|
||||
-------
|
||||
@@ -169,7 +189,7 @@ array:
|
||||
|
||||
If you would like your container to run the same executable every
|
||||
time, then you should consider using ``ENTRYPOINT`` in combination
|
||||
with ``CMD``. See :ref:`entrypoint_def`.
|
||||
with ``CMD``. See :ref:`dockerfile_entrypoint`.
|
||||
|
||||
If the user specifies arguments to ``docker run`` then they will
|
||||
override the default specified in CMD.
|
||||
@@ -179,6 +199,8 @@ override the default specified in CMD.
|
||||
command and commits the result; ``CMD`` does not execute anything at
|
||||
build time, but specifies the intended command for the image.
|
||||
|
||||
.. _dockerfile_expose:
|
||||
|
||||
3.5 EXPOSE
|
||||
----------
|
||||
|
||||
@@ -189,6 +211,8 @@ functionally equivalent to running ``docker commit -run '{"PortSpecs":
|
||||
["<port>", "<port2>"]}'`` outside the builder. Refer to
|
||||
:ref:`port_redirection` for detailed information.
|
||||
|
||||
.. _dockerfile_env:
|
||||
|
||||
3.6 ENV
|
||||
-------
|
||||
|
||||
@@ -203,6 +227,8 @@ with ``<key>=<value>``
|
||||
The environment variables will persist when a container is run
|
||||
from the resulting image.
|
||||
|
||||
.. _dockerfile_add:
|
||||
|
||||
3.7 ADD
|
||||
-------
|
||||
|
||||
@@ -263,7 +289,7 @@ The copy obeys the following rules:
|
||||
* If ``<dest>`` doesn't exist, it is created along with all missing
|
||||
directories in its path.
|
||||
|
||||
.. _entrypoint_def:
|
||||
.. _dockerfile_entrypoint:
|
||||
|
||||
3.8 ENTRYPOINT
|
||||
--------------
|
||||
@@ -312,6 +338,7 @@ this optional but default, you could use a CMD:
|
||||
CMD ["-l", "-"]
|
||||
ENTRYPOINT ["/usr/bin/wc"]
|
||||
|
||||
.. _dockerfile_volume:
|
||||
|
||||
3.9 VOLUME
|
||||
----------
|
||||
@@ -322,6 +349,8 @@ The ``VOLUME`` instruction will create a mount point with the specified name and
|
||||
as holding externally mounted volumes from native host or other containers. For more information/examples
|
||||
and mounting instructions via docker client, refer to :ref:`volume_def` documentation.
|
||||
|
||||
.. _dockerfile_user:
|
||||
|
||||
3.10 USER
|
||||
---------
|
||||
|
||||
@@ -330,6 +359,8 @@ and mounting instructions via docker client, refer to :ref:`volume_def` document
|
||||
The ``USER`` instruction sets the username or UID to use when running
|
||||
the image.
|
||||
|
||||
.. _dockerfile_workdir:
|
||||
|
||||
3.11 WORKDIR
|
||||
------------
|
||||
|
||||
@@ -338,6 +369,7 @@ the image.
|
||||
The ``WORKDIR`` instruction sets the working directory in which
|
||||
the command given by ``CMD`` is executed.
|
||||
|
||||
.. _dockerfile_examples:
|
||||
|
||||
4. Dockerfile Examples
|
||||
======================
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
if err := Register("dummy1", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := Register("dummy1", nil); err == nil {
|
||||
t.Fatalf("Expecting error, got none")
|
||||
}
|
||||
|
||||
eng := newTestEngine(t)
|
||||
|
||||
//Should fail because globan handlers are copied
|
||||
//at the engine creation
|
||||
if err := eng.Register("dummy1", nil); err == nil {
|
||||
t.Fatalf("Expecting error, got none")
|
||||
}
|
||||
|
||||
if err := eng.Register("dummy2", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := eng.Register("dummy2", nil); err == nil {
|
||||
t.Fatalf("Expecting error, got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJob(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
job1 := eng.Job("dummy1", "--level=awesome")
|
||||
|
||||
if job1.handler != nil {
|
||||
t.Fatalf("job1.handler should be empty")
|
||||
}
|
||||
|
||||
h := func(j *Job) string {
|
||||
return j.Name
|
||||
}
|
||||
|
||||
eng.Register("dummy2", h)
|
||||
job2 := eng.Job("dummy2", "--level=awesome")
|
||||
|
||||
if job2.handler == nil {
|
||||
t.Fatalf("job2.handler shouldn't be nil")
|
||||
}
|
||||
|
||||
if job2.handler(job2) != job2.Name {
|
||||
t.Fatalf("handler dummy2 was not found in job2")
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,101 @@ func TestSetenv(t *testing.T) {
|
||||
if val := job.Getenv("foo"); val != "bar" {
|
||||
t.Fatalf("Getenv returns incorrect value: %s", val)
|
||||
}
|
||||
|
||||
job.Setenv("bar", "")
|
||||
if val := job.Getenv("bar"); val != "" {
|
||||
t.Fatalf("Getenv returns incorrect value: %s", val)
|
||||
}
|
||||
if val := job.Getenv("nonexistent"); val != "" {
|
||||
t.Fatalf("Getenv returns incorrect value: %s", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetenvBool(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
job.SetenvBool("foo", true)
|
||||
if val := job.GetenvBool("foo"); !val {
|
||||
t.Fatalf("GetenvBool returns incorrect value: %b", val)
|
||||
}
|
||||
|
||||
job.SetenvBool("bar", false)
|
||||
if val := job.GetenvBool("bar"); val {
|
||||
t.Fatalf("GetenvBool returns incorrect value: %b", val)
|
||||
}
|
||||
|
||||
if val := job.GetenvBool("nonexistent"); val {
|
||||
t.Fatalf("GetenvBool returns incorrect value: %b", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetenvInt(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
|
||||
job.SetenvInt("foo", -42)
|
||||
if val := job.GetenvInt("foo"); val != -42 {
|
||||
t.Fatalf("GetenvInt returns incorrect value: %d", val)
|
||||
}
|
||||
|
||||
job.SetenvInt("bar", 42)
|
||||
if val := job.GetenvInt("bar"); val != 42 {
|
||||
t.Fatalf("GetenvInt returns incorrect value: %d", val)
|
||||
}
|
||||
if val := job.GetenvInt("nonexistent"); val != -1 {
|
||||
t.Fatalf("GetenvInt returns incorrect value: %d", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetenvList(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
|
||||
job.SetenvList("foo", []string{"bar"})
|
||||
if val := job.GetenvList("foo"); len(val) != 1 || val[0] != "bar" {
|
||||
t.Fatalf("GetenvList returns incorrect value: %v", val)
|
||||
}
|
||||
|
||||
job.SetenvList("bar", nil)
|
||||
if val := job.GetenvList("bar"); val != nil {
|
||||
t.Fatalf("GetenvList returns incorrect value: %v", val)
|
||||
}
|
||||
if val := job.GetenvList("nonexistent"); val != nil {
|
||||
t.Fatalf("GetenvList returns incorrect value: %v", val)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
val, exists := job.Environ()["foo"]
|
||||
if !exists {
|
||||
t.Fatalf("foo not found in the environ")
|
||||
}
|
||||
if val != "bar" {
|
||||
t.Fatalf("bar not found in the environ")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,6 @@ import (
|
||||
|
||||
var globalTestID string
|
||||
|
||||
func init() {
|
||||
Register("dummy", func(job *Job) string { return "" })
|
||||
}
|
||||
|
||||
func newTestEngine(t *testing.T) *Engine {
|
||||
// Use the caller function name as a prefix.
|
||||
// This helps trace temp directories back to their test.
|
||||
+5
-1
@@ -205,8 +205,12 @@ func (job *Job) SetenvInt(key string, value int64) {
|
||||
job.Setenv(key, fmt.Sprintf("%d", value))
|
||||
}
|
||||
|
||||
// Returns nil if key not found
|
||||
func (job *Job) GetenvList(key string) []string {
|
||||
sval := job.Getenv(key)
|
||||
if sval == "" {
|
||||
return nil
|
||||
}
|
||||
l := make([]string, 0, 1)
|
||||
if err := json.Unmarshal([]byte(sval), &l); err != nil {
|
||||
l = append(l, sval)
|
||||
@@ -234,7 +238,7 @@ func (job *Job) Setenv(key, value string) {
|
||||
// DecodeEnv decodes `src` as a json dictionary, and adds
|
||||
// each decoded key-value pair to the environment.
|
||||
//
|
||||
// If `text` cannot be decoded as a json dictionary, an error
|
||||
// If `src` cannot be decoded as a json dictionary, an error
|
||||
// is returned.
|
||||
func (job *Job) DecodeEnv(src io.Reader) error {
|
||||
m := make(map[string]interface{})
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -114,10 +115,22 @@ func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root str
|
||||
|
||||
func StoreSize(img *Image, root string) error {
|
||||
layer := layerPath(root)
|
||||
data := make(map[uint64]bool)
|
||||
|
||||
var totalSize int64
|
||||
filepath.Walk(layer, func(path string, fileInfo os.FileInfo, err error) error {
|
||||
totalSize += fileInfo.Size()
|
||||
size := fileInfo.Size()
|
||||
if size == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
inode := fileInfo.Sys().(*syscall.Stat_t).Ino
|
||||
if _, entryExists := data[inode]; entryExists {
|
||||
return nil
|
||||
}
|
||||
data[inode] = false
|
||||
|
||||
totalSize += size
|
||||
return nil
|
||||
})
|
||||
img.Size = totalSize
|
||||
|
||||
@@ -289,7 +289,7 @@ func TestRunDisconnect(t *testing.T) {
|
||||
setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() {
|
||||
container := globalRuntime.List()[0]
|
||||
container.Wait()
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Fatalf("/bin/cat is still running after closing stdin")
|
||||
}
|
||||
})
|
||||
@@ -319,7 +319,7 @@ func TestRunDisconnectTty(t *testing.T) {
|
||||
for {
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
l := globalRuntime.List()
|
||||
if len(l) == 1 && l[0].State.Running {
|
||||
if len(l) == 1 && l[0].State.IsRunning() {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
@@ -329,7 +329,7 @@ func TestRunDisconnectTty(t *testing.T) {
|
||||
// Client disconnect after run -i should keep stdin out in TTY mode
|
||||
container := globalRuntime.List()[0]
|
||||
|
||||
setTimeout(t, "Read/Write assertion timed out", 2000*time.Second, func() {
|
||||
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
|
||||
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -345,7 +345,7 @@ func TestRunDisconnectTty(t *testing.T) {
|
||||
|
||||
// Give some time to monitor to do his thing
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
|
||||
}
|
||||
}
|
||||
@@ -454,7 +454,7 @@ func TestRunDetach(t *testing.T) {
|
||||
})
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
t.Fatal("The detached container should be still running")
|
||||
}
|
||||
|
||||
@@ -534,7 +534,7 @@ func TestAttachDetach(t *testing.T) {
|
||||
})
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
t.Fatal("The detached container should be still running")
|
||||
}
|
||||
|
||||
@@ -596,7 +596,7 @@ func TestAttachDetachTruncatedID(t *testing.T) {
|
||||
})
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
t.Fatal("The detached container should be still running")
|
||||
}
|
||||
|
||||
@@ -629,7 +629,7 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
|
||||
for {
|
||||
l := globalRuntime.List()
|
||||
if len(l) == 1 && l[0].State.Running {
|
||||
if len(l) == 1 && l[0].State.IsRunning() {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
@@ -665,7 +665,7 @@ func TestAttachDisconnect(t *testing.T) {
|
||||
// We closed stdin, expect /bin/cat to still be running
|
||||
// Wait a little bit to make sure container.monitor() did his thing
|
||||
err := container.WaitTimeout(500 * time.Millisecond)
|
||||
if err == nil || !container.State.Running {
|
||||
if err == nil || !container.State.IsRunning() {
|
||||
t.Fatalf("/bin/cat is not running after closing stdin")
|
||||
}
|
||||
|
||||
|
||||
@@ -224,13 +224,13 @@ func TestCommitAutoRun(t *testing.T) {
|
||||
container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container1.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
|
||||
@@ -280,13 +280,13 @@ func TestCommitRun(t *testing.T) {
|
||||
container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
|
||||
defer runtime.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container1.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
|
||||
@@ -330,6 +330,11 @@ func TestCommitRun(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStart(t *testing.T) {
|
||||
_, err1 := os.Stat("/sys/fs/cgroup/cpuacct,cpu")
|
||||
_, err2 := os.Stat("/sys/fs/cgroup/cpu,cpuacct")
|
||||
if err1 == nil || err2 == nil {
|
||||
t.Skip("Fixme. Setting cpu cgroup shares doesn't work in dind on a Fedora host. The lxc utils are confused by the cpu,cpuacct mount.")
|
||||
}
|
||||
runtime := mkRuntime(t)
|
||||
defer nuke(runtime)
|
||||
container, _, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t)
|
||||
@@ -347,7 +352,7 @@ func TestStart(t *testing.T) {
|
||||
// Give some time to the process to start
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
t.Errorf("Container should be running")
|
||||
}
|
||||
if err := container.Start(); err == nil {
|
||||
@@ -365,13 +370,13 @@ func TestRun(t *testing.T) {
|
||||
container, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
}
|
||||
@@ -395,7 +400,7 @@ func TestOutput(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(output) != "foobar" {
|
||||
t.Error(string(output))
|
||||
t.Fatalf("%s != %s", string(output), "foobar")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,8 +421,8 @@ func TestContainerNetwork(t *testing.T) {
|
||||
if err := container.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container.State.ExitCode != 0 {
|
||||
t.Errorf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", container.State.ExitCode)
|
||||
if code := container.State.GetExitCode(); code != 0 {
|
||||
t.Fatalf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", code)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,7 +446,7 @@ func TestKillDifferentUser(t *testing.T) {
|
||||
// there is a side effect I'm not seeing.
|
||||
// defer container.stdin.Close()
|
||||
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container.Start(); err != nil {
|
||||
@@ -449,7 +454,7 @@ func TestKillDifferentUser(t *testing.T) {
|
||||
}
|
||||
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
|
||||
for !container.State.Running {
|
||||
for !container.State.IsRunning() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
})
|
||||
@@ -466,11 +471,11 @@ func TestKillDifferentUser(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
container.Wait()
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
// Try stopping twice
|
||||
@@ -528,7 +533,7 @@ func TestKill(t *testing.T) {
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container.Start(); err != nil {
|
||||
@@ -538,17 +543,17 @@ func TestKill(t *testing.T) {
|
||||
// Give some time to lxc to spawn the process
|
||||
container.WaitTimeout(500 * time.Millisecond)
|
||||
|
||||
if !container.State.Running {
|
||||
if !container.State.IsRunning() {
|
||||
t.Errorf("Container should be running")
|
||||
}
|
||||
if err := container.Kill(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
container.Wait()
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
// Try stopping twice
|
||||
@@ -563,7 +568,7 @@ func TestExitCode(t *testing.T) {
|
||||
|
||||
trueContainer, _, err := runtime.Create(&docker.Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/true", ""},
|
||||
Cmd: []string{"/bin/true"},
|
||||
}, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -572,13 +577,13 @@ func TestExitCode(t *testing.T) {
|
||||
if err := trueContainer.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if trueContainer.State.ExitCode != 0 {
|
||||
t.Errorf("Unexpected exit code %d (expected 0)", trueContainer.State.ExitCode)
|
||||
if code := trueContainer.State.GetExitCode(); code != 0 {
|
||||
t.Fatalf("Unexpected exit code %d (expected 0)", code)
|
||||
}
|
||||
|
||||
falseContainer, _, err := runtime.Create(&docker.Config{
|
||||
Image: GetTestImage(runtime).ID,
|
||||
Cmd: []string{"/bin/false", ""},
|
||||
Cmd: []string{"/bin/false"},
|
||||
}, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -587,8 +592,8 @@ func TestExitCode(t *testing.T) {
|
||||
if err := falseContainer.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if falseContainer.State.ExitCode != 1 {
|
||||
t.Errorf("Unexpected exit code %d (expected 1)", falseContainer.State.ExitCode)
|
||||
if code := falseContainer.State.GetExitCode(); code != 1 {
|
||||
t.Fatalf("Unexpected exit code %d (expected 1)", code)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -736,7 +741,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
output, err = container.Output()
|
||||
if err != nil || container.State.ExitCode != 0 {
|
||||
if code := container.State.GetExitCode(); err != nil || code != 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(output), "uid=0(root) gid=0(root)") {
|
||||
@@ -752,12 +757,12 @@ func TestUser(t *testing.T) {
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil || container.State.ExitCode != 0 {
|
||||
if code := container.State.GetExitCode(); err != nil || code != 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
output, err = container.Output()
|
||||
if err != nil || container.State.ExitCode != 0 {
|
||||
if code := container.State.GetExitCode(); err != nil || code != 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(output), "uid=0(root) gid=0(root)") {
|
||||
@@ -780,8 +785,8 @@ func TestUser(t *testing.T) {
|
||||
output, err = container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if container.State.ExitCode != 0 {
|
||||
t.Fatalf("Container exit code is invalid: %d\nOutput:\n%s\n", container.State.ExitCode, output)
|
||||
} else if code := container.State.GetExitCode(); code != 0 {
|
||||
t.Fatalf("Container exit code is invalid: %d\nOutput:\n%s\n", code, output)
|
||||
}
|
||||
if !strings.Contains(string(output), "uid=1(daemon) gid=1(daemon)") {
|
||||
t.Error(string(output))
|
||||
@@ -801,7 +806,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
output, err = container.Output()
|
||||
if err != nil || container.State.ExitCode != 0 {
|
||||
if code := container.State.GetExitCode(); err != nil || code != 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(output), "uid=1(daemon) gid=1(daemon)") {
|
||||
@@ -822,7 +827,7 @@ func TestUser(t *testing.T) {
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
output, err = container.Output()
|
||||
if container.State.ExitCode == 0 {
|
||||
if container.State.GetExitCode() == 0 {
|
||||
t.Fatal("Starting container with wrong uid should fail but it passed.")
|
||||
}
|
||||
}
|
||||
@@ -866,10 +871,10 @@ func TestMultipleContainers(t *testing.T) {
|
||||
container2.WaitTimeout(250 * time.Millisecond)
|
||||
|
||||
// If we are here, both containers should be running
|
||||
if !container1.State.Running {
|
||||
if !container1.State.IsRunning() {
|
||||
t.Fatal("Container not running")
|
||||
}
|
||||
if !container2.State.Running {
|
||||
if !container2.State.IsRunning() {
|
||||
t.Fatal("Container not running")
|
||||
}
|
||||
|
||||
@@ -1171,13 +1176,13 @@ func TestCopyVolumeUidGid(t *testing.T) {
|
||||
container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && touch /hello/test.txt && chown daemon.daemon /hello"}, t)
|
||||
defer r.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container1.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
|
||||
@@ -1205,13 +1210,13 @@ func TestCopyVolumeContent(t *testing.T) {
|
||||
container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello/local && echo hello > /hello/local/world"}, t)
|
||||
defer r.Destroy(container1)
|
||||
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
if err := container1.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container1.State.Running {
|
||||
if container1.State.IsRunning() {
|
||||
t.Errorf("Container shouldn't be running")
|
||||
}
|
||||
|
||||
@@ -1661,17 +1666,17 @@ func TestRestartGhost(t *testing.T) {
|
||||
},
|
||||
"",
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := container.Kill(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
container.State.Ghost = true
|
||||
_, err = container.Output()
|
||||
container.State.SetGhost(true)
|
||||
|
||||
_, err = container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -419,7 +419,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*docker.Runtime, *doc
|
||||
}
|
||||
|
||||
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
|
||||
for !container.State.Running {
|
||||
for !container.State.IsRunning() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
})
|
||||
@@ -533,7 +533,7 @@ func TestRestore(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !container2.State.Running {
|
||||
if !container2.State.IsRunning() {
|
||||
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
|
||||
}
|
||||
|
||||
@@ -543,7 +543,7 @@ func TestRestore(t *testing.T) {
|
||||
if err := container2.WaitTimeout(2 * time.Second); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2.State.Running = true
|
||||
container2.State.SetRunning(42)
|
||||
container2.ToDisk()
|
||||
|
||||
if len(runtime1.List()) != 2 {
|
||||
@@ -553,7 +553,7 @@ func TestRestore(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !container2.State.Running {
|
||||
if !container2.State.IsRunning() {
|
||||
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
|
||||
}
|
||||
|
||||
@@ -577,7 +577,7 @@ func TestRestore(t *testing.T) {
|
||||
}
|
||||
runningCount := 0
|
||||
for _, c := range runtime2.List() {
|
||||
if c.State.Running {
|
||||
if c.State.IsRunning() {
|
||||
t.Errorf("Running container found: %v (%v)", c.ID, c.Path)
|
||||
runningCount++
|
||||
}
|
||||
@@ -592,7 +592,7 @@ func TestRestore(t *testing.T) {
|
||||
if err := container3.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
container2.State.Running = false
|
||||
container2.State.SetStopped(0)
|
||||
}
|
||||
|
||||
func TestReloadContainerLinks(t *testing.T) {
|
||||
@@ -638,11 +638,11 @@ func TestReloadContainerLinks(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !container2.State.Running {
|
||||
if !container2.State.IsRunning() {
|
||||
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
|
||||
}
|
||||
|
||||
if !container1.State.Running {
|
||||
if !container1.State.IsRunning() {
|
||||
t.Fatalf("Container %s should appear as running but isn't", container1.ID)
|
||||
}
|
||||
|
||||
@@ -669,7 +669,7 @@ func TestReloadContainerLinks(t *testing.T) {
|
||||
}
|
||||
runningCount := 0
|
||||
for _, c := range runtime2.List() {
|
||||
if c.State.Running {
|
||||
if c.State.IsRunning() {
|
||||
runningCount++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ func TestCreateRmVolumes(t *testing.T) {
|
||||
srv := mkServerFromEngine(eng, t)
|
||||
defer mkRuntimeFromEngine(eng, t).Nuke()
|
||||
|
||||
config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo test"}, nil)
|
||||
config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo", "test"}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -164,7 +164,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
|
||||
srv := mkServerFromEngine(eng, t)
|
||||
defer mkRuntimeFromEngine(eng, t).Nuke()
|
||||
|
||||
config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "/bin/cat"}, nil)
|
||||
config, hostConfig, _, err := docker.ParseRun([]string{"-i", unitTestImageID, "/bin/cat"}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -240,7 +240,7 @@ func TestRmi(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil)
|
||||
config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo", "test"}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -256,6 +256,10 @@ func TestRmi(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := srv.ContainerWait(containerID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
imageID, err := srv.ContainerCommit(containerID, "test", "", "", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -277,6 +281,10 @@ func TestRmi(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := srv.ContainerWait(containerID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = srv.ContainerCommit(containerID, "test", "", "", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -111,7 +111,7 @@ func containerKill(eng *engine.Engine, id string, t utils.Fataler) {
|
||||
}
|
||||
|
||||
func containerRunning(eng *engine.Engine, id string, t utils.Fataler) bool {
|
||||
return getContainer(eng, id, t).State.Running
|
||||
return getContainer(eng, id, t).State.IsRunning()
|
||||
}
|
||||
|
||||
func containerAssertExists(eng *engine.Engine, id string, t utils.Fataler) {
|
||||
|
||||
@@ -21,7 +21,7 @@ func NewLink(parent, child *Container, name, bridgeInterface string) (*Link, err
|
||||
if parent.ID == child.ID {
|
||||
return nil, fmt.Errorf("Cannot link to self: %s == %s", parent.ID, child.ID)
|
||||
}
|
||||
if !child.State.Running {
|
||||
if !child.State.IsRunning() {
|
||||
return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, name)
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -100,8 +100,8 @@ func (runtime *Runtime) load(id string) (*Container, error) {
|
||||
if container.ID != id {
|
||||
return container, fmt.Errorf("Container %s is stored at %s", container.ID, id)
|
||||
}
|
||||
if container.State.Running {
|
||||
container.State.Ghost = true
|
||||
if container.State.IsRunning() {
|
||||
container.State.SetGhost(true)
|
||||
}
|
||||
return container, nil
|
||||
}
|
||||
@@ -136,7 +136,7 @@ func (runtime *Runtime) Register(container *Container) error {
|
||||
// FIXME: if the container is supposed to be running but is not, auto restart it?
|
||||
// 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.Running {
|
||||
if container.State.IsRunning() {
|
||||
output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -145,14 +145,14 @@ func (runtime *Runtime) Register(container *Container) error {
|
||||
utils.Debugf("Container %s was supposed to be running be is not.", container.ID)
|
||||
if runtime.config.AutoRestart {
|
||||
utils.Debugf("Restarting")
|
||||
container.State.Ghost = false
|
||||
container.State.setStopped(0)
|
||||
container.State.SetGhost(false)
|
||||
container.State.SetStopped(0)
|
||||
if err := container.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
utils.Debugf("Marking as stopped")
|
||||
container.State.setStopped(-127)
|
||||
container.State.SetStopped(-127)
|
||||
if err := container.ToDisk(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -197,6 +197,222 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
|
||||
return fmt.Errorf("No such container: %s", name)
|
||||
}
|
||||
|
||||
// ImageExport exports all images with the given tag. All versions
|
||||
// containing the same tag are exported. The resulting output is an
|
||||
// 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 {
|
||||
// get image json
|
||||
tempdir, err := ioutil.TempDir("", "docker-export-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tempdir)
|
||||
|
||||
utils.Debugf("Serializing %s", name)
|
||||
|
||||
rootRepo, err := srv.runtime.repositories.Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rootRepo != nil {
|
||||
for _, id := range rootRepo {
|
||||
image, err := srv.ImageInspect(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := srv.exportImage(image, tempdir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// write repositories
|
||||
rootRepoMap := map[string]Repository{}
|
||||
rootRepoMap[name] = rootRepo
|
||||
rootRepoJson, _ := json.Marshal(rootRepoMap)
|
||||
|
||||
if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.ModeAppend); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
image, err := srv.ImageInspect(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := srv.exportImage(image, tempdir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fs, err := archive.Tar(tempdir, archive.Uncompressed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(out, fs); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) exportImage(image *Image, tempdir string) error {
|
||||
for i := image; i != nil; {
|
||||
// temporary directory
|
||||
tmpImageDir := path.Join(tempdir, i.ID)
|
||||
if err := os.Mkdir(tmpImageDir, os.ModeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var version = "1.0"
|
||||
var versionBuf = []byte(version)
|
||||
|
||||
if err := ioutil.WriteFile(path.Join(tmpImageDir, "VERSION"), versionBuf, os.ModeAppend); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// serialize json
|
||||
b, err := json.Marshal(i)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(path.Join(tmpImageDir, "json"), b, os.ModeAppend); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// serialize filesystem
|
||||
fs, err := archive.Tar(path.Join(srv.runtime.graph.Root, i.ID, "layer"), archive.Uncompressed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = io.Copy(fsTar, fs); err != nil {
|
||||
return err
|
||||
}
|
||||
fsTar.Close()
|
||||
|
||||
// find parent
|
||||
if i.Parent != "" {
|
||||
i, err = srv.ImageInspect(i.Parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
i = nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
tmpImageDir, err := ioutil.TempDir("", "docker-import-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpImageDir)
|
||||
|
||||
var (
|
||||
repoTarFile = path.Join(tmpImageDir, "repo.tar")
|
||||
repoDir = path.Join(tmpImageDir, "repo")
|
||||
)
|
||||
|
||||
tarFile, err := os.Create(repoTarFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(tarFile, in); err != nil {
|
||||
return err
|
||||
}
|
||||
tarFile.Close()
|
||||
|
||||
repoFile, err := os.Open(repoTarFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Mkdir(repoDir, os.ModeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := archive.Untar(repoFile, repoDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dirs, err := ioutil.ReadDir(repoDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, d := range dirs {
|
||||
if d.IsDir() {
|
||||
if err := srv.recursiveLoad(d.Name(), tmpImageDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositoriesJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories"))
|
||||
if err == nil {
|
||||
repositories := map[string]Repository{}
|
||||
if err := json.Unmarshal(repositoriesJson, &repositories); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for imageName, tagMap := range repositories {
|
||||
for tag, address := range tagMap {
|
||||
if err := srv.runtime.repositories.Set(imageName, tag, address, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) recursiveLoad(address, tmpImageDir string) error {
|
||||
if _, err := srv.ImageInspect(address); err != nil {
|
||||
utils.Debugf("Loading %s", address)
|
||||
|
||||
imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json"))
|
||||
if err != nil {
|
||||
return err
|
||||
utils.Debugf("Error reading json", err)
|
||||
}
|
||||
|
||||
layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar"))
|
||||
if err != nil {
|
||||
utils.Debugf("Error reading embedded tar", err)
|
||||
return err
|
||||
}
|
||||
img, err := NewImgJSON(imageJson)
|
||||
if err != nil {
|
||||
utils.Debugf("Error unmarshalling json", err)
|
||||
return err
|
||||
}
|
||||
if img.Parent != "" {
|
||||
if !srv.runtime.graph.Exists(img.Parent) {
|
||||
if err := srv.recursiveLoad(img.Parent, tmpImageDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := srv.runtime.graph.Register(imageJson, layer, img); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
utils.Debugf("Completed processing %s", address)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (srv *Server) ImagesSearch(term string) ([]registry.SearchResult, error) {
|
||||
r, err := registry.NewRegistry(srv.runtime.config.Root, nil, srv.HTTPRequestFactory(nil))
|
||||
if err != nil {
|
||||
@@ -471,18 +687,21 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
|
||||
var displayed int
|
||||
out := []APIContainers{}
|
||||
|
||||
names := map[string][]string{}
|
||||
srv.runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error {
|
||||
names[e.ID()] = append(names[e.ID()], p)
|
||||
return nil
|
||||
}, -1)
|
||||
|
||||
for _, container := range srv.runtime.List() {
|
||||
if !container.State.Running && !all && n == -1 && since == "" && before == "" {
|
||||
if !container.State.IsRunning() && !all && n == -1 && since == "" && before == "" {
|
||||
continue
|
||||
}
|
||||
if before != "" {
|
||||
if before != "" && !foundBefore {
|
||||
if container.ID == before || utils.TruncateID(container.ID) == before {
|
||||
foundBefore = true
|
||||
continue
|
||||
}
|
||||
if !foundBefore {
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
if displayed == n {
|
||||
break
|
||||
@@ -491,25 +710,17 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
|
||||
break
|
||||
}
|
||||
displayed++
|
||||
c := createAPIContainer(container, size, srv.runtime)
|
||||
c := createAPIContainer(names[container.ID], container, size, srv.runtime)
|
||||
out = append(out, c)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func createAPIContainer(container *Container, size bool, runtime *Runtime) APIContainers {
|
||||
func createAPIContainer(names []string, container *Container, size bool, runtime *Runtime) APIContainers {
|
||||
c := APIContainers{
|
||||
ID: container.ID,
|
||||
}
|
||||
names := []string{}
|
||||
runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error {
|
||||
if e.ID() == container.ID {
|
||||
names = append(names, p)
|
||||
}
|
||||
return nil
|
||||
}, -1)
|
||||
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()
|
||||
@@ -545,7 +756,7 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling", "dependend layers"))
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling", "dependent layers"))
|
||||
// FIXME: Try to stream the images?
|
||||
// FIXME: Launch the getRemoteImage() in goroutines
|
||||
|
||||
@@ -562,13 +773,13 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling", "metadata"))
|
||||
imgJSON, imgSize, err := r.GetRemoteImageJSON(id, endpoint, token)
|
||||
if err != nil {
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependend layers"))
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependent layers"))
|
||||
// FIXME: Keep going in case of error?
|
||||
return err
|
||||
}
|
||||
img, err := NewImgJSON(imgJSON)
|
||||
if err != nil {
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependend layers"))
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependent layers"))
|
||||
return fmt.Errorf("Failed to parse json: %s", err)
|
||||
}
|
||||
|
||||
@@ -576,12 +787,12 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling", "fs layer"))
|
||||
layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token)
|
||||
if err != nil {
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependend layers"))
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependent layers"))
|
||||
return err
|
||||
}
|
||||
defer layer.Close()
|
||||
if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf.FormatProgress(utils.TruncateID(id), "Downloading", "%8v/%v (%v)"), sf, false), img); err != nil {
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "downloading dependend layers"))
|
||||
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "downloading dependent layers"))
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1128,13 +1339,28 @@ func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool)
|
||||
}
|
||||
|
||||
if container != nil {
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
return fmt.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 {
|
||||
volumeId = strings.TrimRight(volumeId, "/layer")
|
||||
|
||||
// Skip the volumes mounted from external
|
||||
if _, exists := binds[volumeId]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
volumeId = strings.TrimSuffix(volumeId, "/layer")
|
||||
volumeId = filepath.Base(volumeId)
|
||||
volumes[volumeId] = struct{}{}
|
||||
}
|
||||
@@ -1207,8 +1433,8 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*imgs = append(*imgs, APIRmi{Deleted: utils.TruncateID(id)})
|
||||
srv.LogEvent("delete", utils.TruncateID(id), "")
|
||||
*imgs = append(*imgs, APIRmi{Deleted: id})
|
||||
srv.LogEvent("delete", id, "")
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
@@ -1290,7 +1516,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) {
|
||||
|
||||
// Prevent deletion if image is used by a running container
|
||||
for _, container := range srv.runtime.List() {
|
||||
if container.State.Running {
|
||||
if container.State.IsRunning() {
|
||||
parent, err := srv.runtime.repositories.LookupImage(container.Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1512,7 +1738,7 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
|
||||
|
||||
//stream
|
||||
if stream {
|
||||
if container.State.Ghost {
|
||||
if container.State.IsGhost() {
|
||||
return fmt.Errorf("Impossible to attach to a ghost container")
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
type State struct {
|
||||
sync.Mutex
|
||||
sync.RWMutex
|
||||
Running bool
|
||||
Pid int
|
||||
ExitCode int
|
||||
@@ -19,6 +19,9 @@ type State struct {
|
||||
|
||||
// String returns a human-readable description of the state
|
||||
func (s *State) String() string {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
if s.Running {
|
||||
if s.Ghost {
|
||||
return fmt.Sprintf("Ghost")
|
||||
@@ -28,7 +31,38 @@ func (s *State) String() string {
|
||||
return fmt.Sprintf("Exit %d", s.ExitCode)
|
||||
}
|
||||
|
||||
func (s *State) setRunning(pid int) {
|
||||
func (s *State) IsRunning() bool {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.Running
|
||||
}
|
||||
|
||||
func (s *State) IsGhost() bool {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.Ghost
|
||||
}
|
||||
|
||||
func (s *State) GetExitCode() int {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
return s.ExitCode
|
||||
}
|
||||
|
||||
func (s *State) SetGhost(val bool) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.Ghost = val
|
||||
}
|
||||
|
||||
func (s *State) SetRunning(pid int) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.Running = true
|
||||
s.Ghost = false
|
||||
s.ExitCode = 0
|
||||
@@ -36,7 +70,10 @@ func (s *State) setRunning(pid int) {
|
||||
s.StartedAt = time.Now()
|
||||
}
|
||||
|
||||
func (s *State) setStopped(exitCode int) {
|
||||
func (s *State) SetStopped(exitCode int) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
s.Running = false
|
||||
s.Pid = 0
|
||||
s.FinishedAt = time.Now()
|
||||
|
||||
Reference in New Issue
Block a user