diff --git a/commands.go b/commands.go index f3f0fb16c8..23ff9e4978 100644 --- a/commands.go +++ b/commands.go @@ -22,6 +22,7 @@ import ( "net/url" "os" "os/signal" + "path" "path/filepath" "reflect" "regexp" @@ -119,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 } @@ -161,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") @@ -259,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 @@ -367,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 } @@ -390,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 } @@ -441,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 } @@ -497,7 +498,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 @@ -524,7 +525,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 @@ -567,7 +568,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 { @@ -653,7 +654,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { } 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 } @@ -704,7 +705,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 } @@ -736,7 +737,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 } @@ -778,7 +779,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 } @@ -814,7 +815,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") @@ -871,7 +872,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") @@ -905,7 +906,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 } @@ -927,7 +928,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 @@ -961,7 +962,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 } @@ -1023,7 +1024,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 @@ -1083,7 +1084,7 @@ 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 (by default filter out the intermediate images used to build)") noTrunc := cmd.Bool("notrunc", false, "Don't truncate output") @@ -1282,7 +1283,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.") @@ -1371,10 +1372,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 \"") - 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 } @@ -1423,7 +1424,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 @@ -1456,7 +1457,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 } @@ -1473,7 +1474,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 } @@ -1499,7 +1500,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 } @@ -1526,7 +1527,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 { @@ -1548,7 +1549,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") } @@ -1581,7 +1582,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") @@ -1693,7 +1694,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 @@ -1726,8 +1727,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: , 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 } @@ -1938,7 +2154,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } func (cli *DockerCli) CmdCp(args ...string) error { - cmd := Subcmd("cp", "CONTAINER:PATH HOSTPATH", "Copy files/folders from the PATH 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 } @@ -1973,7 +2189,7 @@ func (cli *DockerCli) CmdCp(args ...string) error { } func (cli *DockerCli) CmdSave(args ...string) error { - cmd := Subcmd("save", "IMAGE DESTINATION", "Save an image to a tar archive") + cmd := cli.Subcmd("save", "IMAGE DESTINATION", "Save an image to a tar archive") if err := cmd.Parse(args); err != nil { return err } @@ -1991,7 +2207,7 @@ func (cli *DockerCli) CmdSave(args ...string) error { } func (cli *DockerCli) CmdLoad(args ...string) error { - cmd := Subcmd("load", "SOURCE", "Load an image from a tar archive") + cmd := cli.Subcmd("load", "SOURCE", "Load an image from a tar archive") if err := cmd.Parse(args); err != nil { return err } @@ -2257,12 +2473,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 } @@ -2307,7 +2523,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 { diff --git a/container.go b/container.go index 5e03c65845..49cb33b536 100644 --- a/container.go +++ b/container.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "errors" - "flag" "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/graphdriver" @@ -18,14 +17,15 @@ import ( "os" "os/exec" "path" - "path/filepath" "strconv" "strings" + "sync" "syscall" "time" ) type Container struct { + sync.Mutex root string // Path to the "home" of the container, including metadata. rootfs string // Path to the root filesystem of the container. @@ -159,218 +159,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: , 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 { @@ -710,9 +498,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() { @@ -1046,7 +835,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{}) @@ -1054,14 +843,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() @@ -1078,11 +867,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 @@ -1163,11 +952,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{ @@ -1203,10 +993,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 } @@ -1315,7 +1107,7 @@ func (container *Container) monitor() { } // Report status back - container.State.setStopped(exitCode) + container.State.SetStopped(exitCode) // Release the lock close(container.waitLock) @@ -1365,10 +1157,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 } @@ -1381,7 +1173,7 @@ func (container *Container) kill(sig int) error { } func (container *Container) Kill() error { - if !container.State.Running { + if !container.State.IsRunning() { return nil } @@ -1406,7 +1198,7 @@ func (container *Container) Kill() error { } func (container *Container) Stop(seconds int) error { - if !container.State.Running { + if !container.State.IsRunning() { return nil } @@ -1440,7 +1232,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 { @@ -1576,12 +1368,9 @@ func (container *Container) GetSize() (int64, int64) { } if _, err = os.Stat(container.RootfsPath()); err != nil { - filepath.Walk(container.RootfsPath(), func(path string, fileInfo os.FileInfo, err error) error { - if fileInfo != nil { - sizeRootfs += fileInfo.Size() - } - return nil - }) + if sizeRootfs, err = utils.TreeSize(container.RootfsPath()); err != nil { + sizeRootfs = -1 + } } return sizeRw, sizeRootfs } diff --git a/docs/sources/api/remote_api_client_libraries.rst b/docs/sources/api/remote_api_client_libraries.rst index f00ab1c2b5..45ce8ff9d1 100644 --- a/docs/sources/api/remote_api_client_libraries.rst +++ b/docs/sources/api/remote_api_client_libraries.rst @@ -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 diff --git a/docs/sources/commandline/cli.rst b/docs/sources/commandline/cli.rst index b067e11652..9875b5ded9 100644 --- a/docs/sources/commandline/cli.rst +++ b/docs/sources/commandline/cli.rst @@ -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 ` 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 ` 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 " -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 ................. diff --git a/docs/sources/installation/amazon.rst b/docs/sources/installation/amazon.rst index 6d92046dbf..0c36dbfc33 100644 --- a/docs/sources/installation/amazon.rst +++ b/docs/sources/installation/amazon.rst @@ -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` 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` ) + * 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:** diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index b8dd95bad0..2035c276dd 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -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 ` 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": ["", ""]}'`` outside the builder. Refer to :ref:`port_redirection` for detailed information. +.. _dockerfile_env: + 3.6 ENV ------- @@ -203,6 +227,8 @@ with ``=`` 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 ```` 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 ====================== diff --git a/integration/commands_test.go b/integration/commands_test.go index a5845e8cd4..37bedf7f0c 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -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) @@ -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") } diff --git a/integration/container_test.go b/integration/container_test.go index 18d79f14a9..93a00a7286 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -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") } @@ -352,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 { @@ -370,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") } } @@ -400,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") } } @@ -421,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) } } @@ -446,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 { @@ -454,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) } }) @@ -471,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 @@ -533,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 { @@ -543,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 @@ -577,8 +577,8 @@ 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{ @@ -592,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) } } @@ -741,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)") { @@ -757,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)") { @@ -785,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)) @@ -806,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)") { @@ -827,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.") } } @@ -871,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") } @@ -1176,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") } @@ -1210,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") } @@ -1666,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) } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 320a3645b0..69559f332b 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -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++ } } diff --git a/integration/utils_test.go b/integration/utils_test.go index 278924edb7..1f47c45382 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -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) { diff --git a/links.go b/links.go index 3c689d0bf3..2fe255b4c5 100644 --- a/links.go +++ b/links.go @@ -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) } diff --git a/runtime.go b/runtime.go index 7eb0f2bacc..09ec0c988b 100644 --- a/runtime.go +++ b/runtime.go @@ -109,8 +109,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 } @@ -152,7 +152,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 @@ -161,14 +161,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 } diff --git a/server.go b/server.go index c04e34aeb3..6f635339b4 100644 --- a/server.go +++ b/server.go @@ -696,17 +696,14 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API }, -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 @@ -761,7 +758,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 @@ -779,13 +776,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) } @@ -793,12 +790,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 } } @@ -1348,13 +1345,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{}{} } @@ -1427,8 +1439,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 @@ -1510,7 +1522,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 @@ -1732,7 +1744,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") } diff --git a/state.go b/state.go index af7de9f4da..71775719f1 100644 --- a/state.go +++ b/state.go @@ -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() diff --git a/utils/fs.go b/utils/fs.go index f07784184c..a7bed8679a 100644 --- a/utils/fs.go +++ b/utils/fs.go @@ -3,16 +3,32 @@ package utils import ( "os" "path/filepath" + "syscall" ) // TreeSize walks a directory tree and returns its total size in bytes. func TreeSize(dir string) (size int64, err error) { + data := make(map[uint64]bool) err = filepath.Walk(dir, func(d string, fileInfo os.FileInfo, e error) error { // Ignore directory sizes - if fileInfo.IsDir() { + if fileInfo == nil { return nil } - size += fileInfo.Size() + + s := fileInfo.Size() + if fileInfo.IsDir() || s == 0 { + return nil + } + + // Check inode to handle hard links correctly + inode := fileInfo.Sys().(*syscall.Stat_t).Ino + if _, exists := data[inode]; exists { + return nil + } + data[inode] = false + + size += s + return nil }) return