Bladeren bron

Merge pull request #7110 from tiborvass/merge-6907

Docker create (rebase of 6907)
Alexandr Morozov 10 jaren geleden
bovenliggende
commit
ca39a3e36b

+ 150 - 55
api/client/commands.go

@@ -1965,91 +1965,182 @@ func (cli *DockerCli) pullImage(image string) error {
 	return nil
 }
 
-func (cli *DockerCli) CmdRun(args ...string) error {
-	// FIXME: just use runconfig.Parse already
-	config, hostConfig, cmd, err := runconfig.ParseSubcommand(cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container"), args, nil)
-	if err != nil {
-		return err
+type cidFile struct {
+	path    string
+	file    *os.File
+	written bool
+}
+
+func newCIDFile(path string) (*cidFile, error) {
+	if _, err := os.Stat(path); err == nil {
+		return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path)
 	}
-	if config.Image == "" {
-		cmd.Usage()
-		return nil
+	f, err := os.Create(path)
+	if err != nil {
+		return nil, fmt.Errorf("Failed to create the container ID file: %s", err)
 	}
 
-	// Retrieve relevant client-side config
-	var (
-		flName        = cmd.Lookup("name")
-		flRm          = cmd.Lookup("rm")
-		flSigProxy    = cmd.Lookup("sig-proxy")
-		autoRemove, _ = strconv.ParseBool(flRm.Value.String())
-		sigProxy, _   = strconv.ParseBool(flSigProxy.Value.String())
-	)
+	return &cidFile{path: path, file: f}, nil
+}
 
-	// Disable sigProxy in case on TTY
-	if config.Tty {
-		sigProxy = false
-	}
+func (cid *cidFile) Close() error {
+	cid.file.Close()
 
-	var containerIDFile io.WriteCloser
-	if len(hostConfig.ContainerIDFile) > 0 {
-		if _, err := os.Stat(hostConfig.ContainerIDFile); err == nil {
-			return fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile)
-		}
-		if containerIDFile, err = os.Create(hostConfig.ContainerIDFile); err != nil {
-			return fmt.Errorf("Failed to create the container ID file: %s", err)
+	if !cid.written {
+		if err := os.Remove(cid.path); err != nil {
+			return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
 		}
-		defer func() {
-			containerIDFile.Close()
-			var (
-				cidFileInfo os.FileInfo
-				err         error
-			)
-			if cidFileInfo, err = os.Stat(hostConfig.ContainerIDFile); err != nil {
-				return
-			}
-			if cidFileInfo.Size() == 0 {
-				if err := os.Remove(hostConfig.ContainerIDFile); err != nil {
-					fmt.Printf("failed to remove Container ID file '%s': %s \n", hostConfig.ContainerIDFile, err)
-				}
-			}
-		}()
 	}
 
+	return nil
+}
+
+func (cid *cidFile) Write(id string) error {
+	if _, err := cid.file.Write([]byte(id)); err != nil {
+		return fmt.Errorf("Failed to write the container ID to the file: %s", err)
+	}
+	cid.written = true
+	return nil
+}
+
+func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (engine.Env, error) {
 	containerValues := url.Values{}
-	if name := flName.Value.String(); name != "" {
+	if name != "" {
 		containerValues.Set("name", name)
 	}
 
+	var data interface{}
+	if hostConfig != nil {
+		data = runconfig.MergeConfigs(config, hostConfig)
+	} else {
+		data = config
+	}
+
+	var containerIDFile *cidFile
+	if cidfile != "" {
+		var err error
+		if containerIDFile, err = newCIDFile(cidfile); err != nil {
+			return nil, err
+		}
+		defer containerIDFile.Close()
+	}
+
 	//create the container
-	stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false)
+	stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), data, false)
 	//if image not found try to pull it
 	if statusCode == 404 {
 		fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", config.Image)
 
 		if err = cli.pullImage(config.Image); err != nil {
-			return err
+			return nil, err
 		}
 		// Retry
-		if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false); err != nil {
-			return err
+		if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), data, false); err != nil {
+			return nil, err
 		}
 	} else if err != nil {
+		return nil, err
+	}
+
+	var result engine.Env
+	if err := result.Decode(stream); err != nil {
+		return nil, err
+	}
+
+	for _, warning := range result.GetList("Warnings") {
+		fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
+	}
+
+	if containerIDFile != nil {
+		if err = containerIDFile.Write(result.Get("Id")); err != nil {
+			return nil, err
+		}
+	}
+
+	return result, nil
+
+}
+
+func (cli *DockerCli) CmdCreate(args ...string) error {
+	// FIXME: just use runconfig.Parse already
+	cmd := cli.Subcmd("create", "IMAGE [COMMAND] [ARG...]", "Create a new container")
+
+	// These are flags not stored in Config/HostConfig
+	var (
+		flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
+	)
+
+	config, hostConfig, cmd, err := runconfig.ParseSubcommand(cmd, args, nil)
+	if err != nil {
 		return err
 	}
+	if config.Image == "" {
+		cmd.Usage()
+		return nil
+	}
 
-	var runResult engine.Env
-	if err := runResult.Decode(stream); err != nil {
+	createResult, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
+	if err != nil {
 		return err
 	}
 
-	for _, warning := range runResult.GetList("Warnings") {
-		fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
+	fmt.Fprintf(cli.out, "%s\n", createResult.Get("Id"))
+
+	return nil
+}
+
+func (cli *DockerCli) CmdRun(args ...string) error {
+	// FIXME: just use runconfig.Parse already
+	cmd := cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
+
+	// These are flags not stored in Config/HostConfig
+	var (
+		flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)")
+		flDetach     = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run the container in the background and print the new container ID")
+		flSigProxy   = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy received signals to the process (even in non-TTY mode). SIGCHLD, SIGSTOP, and SIGKILL are not proxied.")
+		flName       = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container")
+		flAttach     *opts.ListOpts
+
+		ErrConflictAttachDetach               = fmt.Errorf("Conflicting options: -a and -d")
+		ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
+		ErrConflictDetachAutoRemove           = fmt.Errorf("Conflicting options: --rm and -d")
+	)
+
+	config, hostConfig, cmd, err := runconfig.ParseSubcommand(cmd, args, nil)
+	if err != nil {
+		return err
+	}
+	if config.Image == "" {
+		cmd.Usage()
+		return nil
 	}
 
-	if len(hostConfig.ContainerIDFile) > 0 {
-		if _, err = containerIDFile.Write([]byte(runResult.Get("Id"))); err != nil {
-			return fmt.Errorf("Failed to write the container ID to the file: %s", err)
+	if *flDetach {
+		if fl := cmd.Lookup("attach"); fl != nil {
+			flAttach = fl.Value.(*opts.ListOpts)
+			if flAttach.Len() != 0 {
+				return ErrConflictAttachDetach
+			}
+		}
+		if *flAutoRemove {
+			return ErrConflictDetachAutoRemove
 		}
+
+		config.AttachStdin = false
+		config.AttachStdout = false
+		config.AttachStderr = false
+		config.StdinOnce = false
+	}
+
+	// Disable flSigProxy in case on TTY
+	sigProxy := *flSigProxy
+	if config.Tty {
+		sigProxy = false
+	}
+
+	runResult, err := cli.createContainer(config, nil, hostConfig.ContainerIDFile, *flName)
+	if err != nil {
+		return err
 	}
 
 	if sigProxy {
@@ -2071,6 +2162,10 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 		}()
 	}
 
+	if *flAutoRemove && (hostConfig.RestartPolicy.Name == "always" || hostConfig.RestartPolicy.Name == "on-failure") {
+		return ErrConflictRestartPolicyAndAutoRemove
+	}
+
 	// We need to instanciate the chan because the select needs it. It can
 	// be closed but can't be uninitialized.
 	hijacked := make(chan io.Closer)
@@ -2158,7 +2253,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 	var status int
 
 	// Attached mode
-	if autoRemove {
+	if *flAutoRemove {
 		// Autoremove: wait for the container to finish, retrieve
 		// the exit code and remove the container
 		if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/wait", nil, false)); err != nil {

+ 70 - 0
contrib/completion/bash/docker

@@ -217,6 +217,75 @@ _docker_cp()
 	fi
 }
 
+_docker_create()
+{
+	case "$prev" in
+		-a|--attach)
+			COMPREPLY=( $( compgen -W 'stdin stdout stderr' -- "$cur" ) )
+			return
+			;;
+		--cidfile|--env-file)
+			_filedir
+			return
+			;;
+		--volumes-from)
+			__docker_containers_all
+			return
+			;;
+		-v|--volume)
+			case "$cur" in
+				*:*)
+					# TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)
+					;;
+				'')
+					COMPREPLY=( $( compgen -W '/' -- "$cur" ) )
+					compopt -o nospace
+					;;
+				/*)
+					_filedir
+					compopt -o nospace
+					;;
+			esac
+			return
+			;;
+		-e|--env)
+			COMPREPLY=( $( compgen -e -- "$cur" ) )
+			compopt -o nospace
+			return
+			;;
+		--link)
+			case "$cur" in
+				*:*)
+					;;
+				*)
+					__docker_containers_running
+					COMPREPLY=( $( compgen -W "${COMPREPLY[*]}" -S ':' ) )
+					compopt -o nospace
+					;;
+			esac
+			return
+			;;
+		--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|-c|--cpu-shares|-n|--name|-p|--publish|--expose|--dns|--lxc-conf)
+			return
+			;;
+		*)
+			;;
+	esac
+
+	case "$cur" in
+		-*)
+			COMPREPLY=( $( compgen -W "-n --networking --privileged -P --publish-all -i --interactive -t --tty --cidfile --entrypoint -h --hostname -m --memory -u --user -w --workdir -c --cpu-shares --name -a --attach -v --volume --link -e --env -p --publish --expose --dns --volumes-from --lxc-conf" -- "$cur" ) )
+			;;
+		*)
+			local counter=$(__docker_pos_first_nonflag '--cidfile|--volumes-from|-v|--volume|-e|--env|--entrypoint|-h|--hostname|-m|--memory|-u|--user|-w|--workdir|-c|--cpu-shares|-n|--name|-a|--attach|--link|-p|--publish|--expose|--dns|--lxc-conf')
+
+			if [ $cword -eq $counter ]; then
+				__docker_image_repos_and_tags_and_ids
+			fi
+			;;
+	esac
+}
+
 _docker_diff()
 {
 	local counter=$(__docker_pos_first_nonflag)
@@ -670,6 +739,7 @@ _docker()
 			build
 			commit
 			cp
+			create
 			diff
 			events
 			exec

+ 28 - 1
contrib/completion/fish/docker.fish

@@ -16,7 +16,7 @@
 
 function __fish_docker_no_subcommand --description 'Test if docker has yet to be given the subcommand'
     for i in (commandline -opc)
-        if contains -- $i attach build commit cp diff events export history images import info insert inspect kill load login logs port ps pull push restart rm rmi run save search start stop tag top version wait
+        if contains -- $i attach build commit cp create diff events export history images import info insert inspect kill load login logs port ps pull push restart rm rmi run save search start stop tag top version wait
             return 1
         end
     end
@@ -88,6 +88,33 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from commit' -a '(__fish_pri
 # cp
 complete -c docker -f -n '__fish_docker_no_subcommand' -a cp -d "Copy files/folders from a container's filesystem to the host path"
 
+# create
+complete -c docker -f -n '__fish_docker_no_subcommand' -a run -d 'Run a command in a new container'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s P -l publish-all -d 'Publish all exposed ports to the host interfaces'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s a -l attach -d 'Attach to stdin, stdout or stderr.'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s c -l cpu-shares -d 'CPU shares (relative weight)'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l cidfile -d 'Write the container ID to the file'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l dns -d 'Set custom dns servers'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s e -l env -d 'Set environment variables'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l entrypoint -d 'Overwrite the default entrypoint of the image'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l expose -d 'Expose a port from the container without publishing it to your host'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s h -l hostname -d 'Container host name'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s i -l interactive -d 'Keep stdin open even if not attached'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l link -d 'Add link to another container (name:alias)'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l lxc-conf -d 'Add custom lxc options -lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s m -l memory -d 'Memory limit (format: <number><optional unit>, where unit = b, k, m or g)'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s n -l networking -d 'Enable networking for this container'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l name -d 'Assign a name to the container'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s p -l publish -d "Publish a container's port to the host (format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort) (use 'docker port' to see the actual mapping)"
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l privileged -d 'Give extended privileges to this container'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s t -l tty -d 'Allocate a pseudo-tty'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s u -l user -d 'Username or UID'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s v -l volume -d 'Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l volumes-from -d 'Mount volumes from the specified container(s)'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s w -l workdir -d 'Working directory inside the container'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -a '(__fish_print_docker_images)' -d "Image"
+
+
 # diff
 complete -c docker -f -n '__fish_docker_no_subcommand' -a diff -d "Inspect changes on a container's filesystem"
 complete -c docker -A -f -n '__fish_seen_subcommand_from diff' -a '(__fish_print_docker_containers all)' -d "Container"

+ 26 - 0
contrib/completion/zsh/_docker

@@ -224,6 +224,32 @@ __docker_subcommand () {
                     ;;
             esac
             ;;
+        (create)
+            _arguments \
+                '-P[Publish all exposed ports to the host]' \
+                '-a[Attach to stdin, stdout or stderr]' \
+                '-c=-[CPU shares (relative weight)]:CPU shares:(0 10 100 200 500 800 1000)' \
+                '--cidfile=-[Write the container ID to the file]:CID file:_files' \
+                '*--dns=-[Set custom dns servers]:dns server: ' \
+                '*-e=-[Set environment variables]:environment variable: ' \
+                '--entrypoint=-[Overwrite the default entrypoint of the image]:entry point: ' \
+                '*--expose=-[Expose a port from the container without publishing it]: ' \
+                '-h=-[Container host name]:hostname:_hosts' \
+                '-i[Keep stdin open even if not attached]' \
+                '--link=-[Add link to another container]:link:->link' \
+                '--lxc-conf=-[Add custom lxc options]:lxc options: ' \
+                '-m=-[Memory limit (in bytes)]:limit: ' \
+                '--name=-[Container name]:name: ' \
+                '*-p=-[Expose a container'"'"'s port to the host]:port:_ports' \
+                '--privileged[Give extended privileges to this container]' \
+                '-t[Allocate a pseudo-tty]' \
+                '-u=-[Username or UID]:user:_users' \
+                '*-v=-[Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)]:volume: '\
+                '--volumes-from=-[Mount volumes from the specified container]:volume: ' \
+                '-w=-[Working directory inside the container]:directory:_directories' \
+                '(-):images:__docker_images' \
+                '(-):command: _command_names -e' \
+                '*::arguments: _normal'
         (diff|export)
             _arguments '*:containers:__docker_containers'
             ;;

+ 8 - 0
daemon/create.go

@@ -50,6 +50,14 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) engine.Status {
 	for _, warning := range buildWarnings {
 		job.Errorf("%s\n", warning)
 	}
+
+	if job.EnvExists("HostConfig") {
+		hostConfig := runconfig.ContainerHostConfigFromJob(job)
+		if err := daemon.setHostConfig(container, hostConfig); err != nil {
+			return job.Error(err)
+		}
+	}
+
 	return engine.StatusOK
 }
 

+ 1 - 0
docker/flags.go

@@ -53,6 +53,7 @@ func init() {
 			{"build", "Build an image from a Dockerfile"},
 			{"commit", "Create a new image from a container's changes"},
 			{"cp", "Copy files/folders from a container's filesystem to the host path"},
+			{"create", "Create a new container"},
 			{"diff", "Inspect changes on a container's filesystem"},
 			{"events", "Get real time events from the server"},
 			{"exec", "Run a command in an existing container"},

+ 131 - 0
docs/man/docker-create.1.md

@@ -0,0 +1,131 @@
+% DOCKER(1) Docker User Manuals
+% Docker Community
+% JUNE 2014
+# NAME
+docker-create - Create a new container
+
+# SYNOPSIS
+**docker create**
+[**-a**|**--attach**[=*[]*]]
+[**-c**|**--cpu-shares**[=*0*]]
+[**--cap-add**[=*[]*]]
+[**--cap-drop**[=*[]*]]
+[**--cidfile**[=*CIDFILE*]]
+[**--cpuset**[=*CPUSET*]]
+[**--device**[=*[]*]]
+[**--dns-search**[=*[]*]]
+[**--dns**[=*[]*]]
+[**-e**|**--env**[=*[]*]]
+[**--entrypoint**[=*ENTRYPOINT*]]
+[**--env-file**[=*[]*]]
+[**--expose**[=*[]*]]
+[**-h**|**--hostname**[=*HOSTNAME*]]
+[**-i**|**--interactive**[=*false*]]
+[**--link**[=*[]*]]
+[**--lxc-conf**[=*[]*]]
+[**-m**|**--memory**[=*MEMORY*]]
+[**--name**[=*NAME*]]
+[**--net**[=*"bridge"*]]
+[**-P**|**--publish-all**[=*false*]]
+[**-p**|**--publish**[=*[]*]]
+[**--privileged**[=*false*]]
+[**-t**|**--tty**[=*false*]]
+[**-u**|**--user**[=*USER*]]
+[**-v**|**--volume**[=*[]*]]
+[**--volumes-from**[=*[]*]]
+[**-w**|**--workdir**[=*WORKDIR*]]
+ IMAGE [COMMAND] [ARG...]
+
+# OPTIONS
+**-a**, **--attach**=[]
+   Attach to STDIN, STDOUT or STDERR.
+
+**-c**, **--cpu-shares**=0
+   CPU shares (relative weight)
+
+**--cap-add**=[]
+   Add Linux capabilities
+
+**--cap-drop**=[]
+   Drop Linux capabilities
+
+**--cidfile**=""
+   Write the container ID to the file
+
+**--cpuset**=""
+   CPUs in which to allow execution (0-3, 0,1)
+
+**--device**=[]
+   Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc)
+
+**--dns-search**=[]
+   Set custom DNS search domains
+
+**--dns**=[]
+   Set custom DNS servers
+
+**-e**, **--env**=[]
+   Set environment variables
+
+**--entrypoint**=""
+   Overwrite the default ENTRYPOINT of the image
+
+**--env-file**=[]
+   Read in a line delimited file of environment variables
+
+**--expose**=[]
+   Expose a port from the container without publishing it to your host
+
+**-h**, **--hostname**=""
+   Container host name
+
+**-i**, **--interactive**=*true*|*false*
+   Keep STDIN open even if not attached. The default is *false*.
+
+**--link**=[]
+   Add link to another container in the form of name:alias
+
+**--lxc-conf**=[]
+   (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
+
+**-m**, **--memory**=""
+   Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
+
+**--name**=""
+   Assign a name to the container
+
+**--net**="bridge"
+   Set the Network mode for the container
+                               'bridge': creates a new network stack for the container on the docker bridge
+                               'none': no networking for this container
+                               'container:<name|id>': reuses another container network stack
+                               'host': use the host network stack inside the container.  Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.
+
+**-P**, **--publish-all**=*true*|*false*
+   Publish all exposed ports to the host interfaces. The default is *false*.
+
+**-p**, **--publish**=[]
+   Publish a container's port to the host
+                               format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort
+                               (use 'docker port' to see the actual mapping)
+
+**--privileged**=*true*|*false*
+   Give extended privileges to this container. The default is *false*.
+
+**-t**, **--tty**=*true*|*false*
+   Allocate a pseudo-TTY. The default is *false*.
+
+**-u**, **--user**=""
+   Username or UID
+
+**-v**, **--volume**=[]
+   Bind mount a volume (e.g., from the host: -v /host:/container, from Docker: -v /container)
+
+**--volumes-from**=[]
+   Mount volumes from the specified container(s)
+
+**-w**, **--workdir**=""
+   Working directory inside the container
+
+# HISTORY
+August 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>

+ 3 - 1
docs/man/docker.1.md

@@ -89,10 +89,12 @@ unix://[/path/to/socket] to use.
 **docker-cp(1)**
   Copy files/folders from a container's filesystem to the host at path
 
+**docker-create(1)**
+  Create a new container
+
 **docker-diff(1)**
   Inspect changes on a container's filesystem
 
-
 **docker-events(1)**
   Get real time events from the server
 

+ 57 - 0
docs/sources/reference/commandline/cli.md

@@ -370,6 +370,63 @@ path.  Paths are relative to the root of the filesystem.
 
     Copy files/folders from the PATH to the HOSTPATH
 
+
+## create
+
+Creates a new container.
+
+    Usage: docker create [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...]
+
+
+    -a, --attach=[]            Attach to STDIN, STDOUT, STDERR.
+    -c, --cpu-shares=0         CPU shares (relative weight)
+    --cidfile=""               Write the container ID to the file
+    --dns=[]                   Set custom DNS servers
+    --dns-search=[]            Set custom DNS search domains
+    -e, --env=[]               Set environment variables
+    --entrypoint=""            Overwrite the default entrypoint of the image
+    --env-file=[]              Read in a line delimited file of environment variables
+    --expose=[]                Expose a port from the container without publishing it to your host
+    -h, --hostname=""          Container host name
+    -i, --interactive=false    Keep `STDIN` open even if not attached
+    --link=[]                  Add link to another container (name:alias)
+    --lxc-conf=[]              (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
+    -m, --memory=""            Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
+    --name=""                  Assign a name to the container
+    --net="bridge"             Set the Network mode for the container
+                                 'bridge': creates a new network stack for the container on the docker bridge
+                                 'none': no networking for this container
+                                 'container:<name|id>': reuses another container network stack
+                                 'host': use the host network stack inside the container
+    -p, --publish=[]           Publish a container's port to the host
+                                 format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort
+                                 (use 'docker port' to see the actual mapping)
+    -P, --publish-all=false    Publish all exposed ports to the host interfaces
+    --privileged=false         Give extended privileges to this container
+    -t, --tty=false            Allocate a pseudo-TTY
+    -u, --user=""              Username or UID
+    -v, --volume=[]            Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)
+    --volumes-from=[]          Mount volumes from the specified container(s)
+    -w, --workdir=""           Working directory inside the container
+
+
+The `docker create` command creates a writeable container layer over
+the specified image and prepares it for running the specified command.
+The container ID is then printed to `STDOUT`.
+This is similar to `docker run -d` except the container is never started.
+You can then use the `docker start <container_id>` command to start the
+container at any point.
+
+This is useful when you want to set up a container configuration ahead
+of time so that it is ready to start when you need it.
+
+### Example:
+
+    $ sudo docker create -t -i fedora bash
+    6d8af538ec541dd581ebc2a24153a28329acb5268abe5ef868c1f1a261221752
+    $ sudo docker start -a -i 6d8af538ec5
+    bash-4.2#
+
 ## diff
 
 List the changed files and directories in a container᾿s filesystem

+ 116 - 0
integration-cli/docker_cli_create_test.go

@@ -0,0 +1,116 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"os/exec"
+	"testing"
+	"time"
+)
+
+// Make sure we can create a simple container with some args
+func TestDockerCreateArgs(t *testing.T) {
+	runCmd := exec.Command(dockerBinary, "create", "busybox", "command", "arg1", "arg2", "arg with space")
+	out, _, _, err := runCommandWithStdoutStderr(runCmd)
+	errorOut(err, t, out)
+
+	cleanedContainerID := stripTrailingCharacters(out)
+
+	inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
+	inspectOut, _, err := runCommandWithOutput(inspectCmd)
+	errorOut(err, t, fmt.Sprintf("out should've been a container id: %v %v", inspectOut, err))
+
+	containers := []struct {
+		ID      string
+		Created time.Time
+		Path    string
+		Args    []string
+		Image   string
+	}{}
+	if err := json.Unmarshal([]byte(inspectOut), &containers); err != nil {
+		t.Fatalf("Error inspecting the container: %s", err)
+	}
+	if len(containers) != 1 {
+		t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers))
+	}
+
+	c := containers[0]
+	if c.Path != "command" {
+		t.Fatalf("Unexpected container path. Expected command, received: %s", c.Path)
+	}
+
+	b := false
+	expected := []string{"arg1", "arg2", "arg with space"}
+	for i, arg := range expected {
+		if arg != c.Args[i] {
+			b = true
+			break
+		}
+	}
+	if len(c.Args) != len(expected) || b {
+		t.Fatalf("Unexpected args. Expected %v, received: %v", expected, c.Args)
+	}
+
+	deleteAllContainers()
+
+	logDone("create - args")
+}
+
+// Make sure we can set hostconfig options too
+func TestDockerCreateHostConfig(t *testing.T) {
+	runCmd := exec.Command(dockerBinary, "create", "-P", "busybox", "echo")
+	out, _, _, err := runCommandWithStdoutStderr(runCmd)
+	errorOut(err, t, out)
+
+	cleanedContainerID := stripTrailingCharacters(out)
+
+	inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
+	inspectOut, _, err := runCommandWithOutput(inspectCmd)
+	errorOut(err, t, fmt.Sprintf("out should've been a container id: %v %v", inspectOut, err))
+
+	containers := []struct {
+		HostConfig *struct {
+			PublishAllPorts bool
+		}
+	}{}
+	if err := json.Unmarshal([]byte(inspectOut), &containers); err != nil {
+		t.Fatalf("Error inspecting the container: %s", err)
+	}
+	if len(containers) != 1 {
+		t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers))
+	}
+
+	c := containers[0]
+	if c.HostConfig == nil {
+		t.Fatalf("Expected HostConfig, got none")
+	}
+
+	if !c.HostConfig.PublishAllPorts {
+		t.Fatalf("Expected PublishAllPorts, got false")
+	}
+
+	deleteAllContainers()
+
+	logDone("create - hostconfig")
+}
+
+// "test123" should be printed by docker create + start
+func TestDockerCreateEchoStdout(t *testing.T) {
+	runCmd := exec.Command(dockerBinary, "create", "busybox", "echo", "test123")
+	out, _, _, err := runCommandWithStdoutStderr(runCmd)
+	errorOut(err, t, out)
+
+	cleanedContainerID := stripTrailingCharacters(out)
+
+	runCmd = exec.Command(dockerBinary, "start", "-ai", cleanedContainerID)
+	out, _, _, err = runCommandWithStdoutStderr(runCmd)
+	errorOut(err, t, out)
+
+	if out != "test123\n" {
+		t.Errorf("container should've printed 'test123', got '%s'", out)
+	}
+
+	deleteAllContainers()
+
+	logDone("create - echo test123")
+}

+ 20 - 0
runconfig/hostconfig.go

@@ -57,7 +57,27 @@ type HostConfig struct {
 	RestartPolicy   RestartPolicy
 }
 
+// This is used by the create command when you want to set both the
+// Config and the HostConfig in the same call
+type ConfigAndHostConfig struct {
+	Config
+	HostConfig HostConfig
+}
+
+func MergeConfigs(config *Config, hostConfig *HostConfig) *ConfigAndHostConfig {
+	return &ConfigAndHostConfig{
+		*config,
+		*hostConfig,
+	}
+}
+
 func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
+	if job.EnvExists("HostConfig") {
+		hostConfig := HostConfig{}
+		job.GetenvJson("HostConfig", &hostConfig)
+		return &hostConfig
+	}
+
 	hostConfig := &HostConfig{
 		ContainerIDFile: job.Getenv("ContainerIDFile"),
 		Privileged:      job.GetenvBool("Privileged"),

+ 22 - 35
runconfig/parse.go

@@ -17,18 +17,15 @@ import (
 )
 
 var (
-	ErrInvalidWorkingDirectory            = fmt.Errorf("The working directory is invalid. It needs to be an absolute path.")
-	ErrConflictAttachDetach               = fmt.Errorf("Conflicting options: -a and -d")
-	ErrConflictContainerNetworkAndLinks   = fmt.Errorf("Conflicting options: --net=container can't be used with links. This would result in undefined behavior.")
-	ErrConflictContainerNetworkAndDns     = fmt.Errorf("Conflicting options: --net=container can't be used with --dns. This configuration is invalid.")
-	ErrConflictDetachAutoRemove           = fmt.Errorf("Conflicting options: --rm and -d")
-	ErrConflictNetworkHostname            = fmt.Errorf("Conflicting options: -h and the network mode (--net)")
-	ErrConflictHostNetworkAndDns          = fmt.Errorf("Conflicting options: --net=host can't be used with --dns. This configuration is invalid.")
-	ErrConflictHostNetworkAndLinks        = fmt.Errorf("Conflicting options: --net=host can't be used with links. This would result in undefined behavior.")
-	ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
+	ErrInvalidWorkingDirectory          = fmt.Errorf("The working directory is invalid. It needs to be an absolute path.")
+	ErrConflictContainerNetworkAndLinks = fmt.Errorf("Conflicting options: --net=container can't be used with links. This would result in undefined behavior.")
+	ErrConflictContainerNetworkAndDns   = fmt.Errorf("Conflicting options: --net=container can't be used with --dns. This configuration is invalid.")
+	ErrConflictNetworkHostname          = fmt.Errorf("Conflicting options: -h and the network mode (--net)")
+	ErrConflictHostNetworkAndDns        = fmt.Errorf("Conflicting options: --net=host can't be used with --dns. This configuration is invalid.")
+	ErrConflictHostNetworkAndLinks      = fmt.Errorf("Conflicting options: --net=host can't be used with links. This would result in undefined behavior.")
 )
 
-//FIXME Only used in tests
+// FIXME Only used in tests
 func Parse(args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) {
 	cmd := flag.NewFlagSet("run", flag.ContinueOnError)
 	cmd.SetOutput(ioutil.Discard)
@@ -60,8 +57,6 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 		flCapAdd      = opts.NewListOpts(nil)
 		flCapDrop     = opts.NewListOpts(nil)
 
-		flAutoRemove      = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)")
-		flDetach          = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run container in the background and print new container ID")
 		flNetwork         = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container")
 		flPrivileged      = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container")
 		flPublishAll      = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to the host interfaces")
@@ -77,15 +72,13 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 		flCpuset          = cmd.String([]string{"-cpuset"}, "", "CPUs in which to allow execution (0-3, 0,1)")
 		flNetMode         = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container\n'bridge': creates a new network stack for the container on the docker bridge\n'none': no networking for this container\n'container:<name|id>': reuses another container network stack\n'host': use the host network stack inside the container.  Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure.")
 		flRestartPolicy   = cmd.String([]string{"-restart"}, "", "Restart policy to apply when a container exits (no, on-failure[:max-retry], always)")
-		// For documentation purpose
-		_ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy received signals to the process (even in non-TTY mode). SIGCHLD, SIGSTOP, and SIGKILL are not proxied.")
-		_ = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container")
 	)
 
 	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR.")
 	cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume (e.g., from the host: -v /host:/container, from Docker: -v /container)")
 	cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container in the form of name:alias")
 	cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc)")
+
 	cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
 	cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a line delimited file of environment variables")
 
@@ -109,15 +102,15 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 	}
 
 	// Validate input params
-	if *flDetach && flAttach.Len() > 0 {
-		return nil, nil, cmd, ErrConflictAttachDetach
-	}
 	if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
 		return nil, nil, cmd, ErrInvalidWorkingDirectory
 	}
-	if *flDetach && *flAutoRemove {
-		return nil, nil, cmd, ErrConflictDetachAutoRemove
-	}
+
+	var (
+		attachStdin  = flAttach.Get("stdin")
+		attachStdout = flAttach.Get("stdout")
+		attachStderr = flAttach.Get("stderr")
+	)
 
 	if *flNetMode != "bridge" && *flNetMode != "none" && *flHostname != "" {
 		return nil, nil, cmd, ErrConflictNetworkHostname
@@ -140,13 +133,11 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 	}
 
 	// If neither -d or -a are set, attach to everything by default
-	if flAttach.Len() == 0 && !*flDetach {
-		if !*flDetach {
-			flAttach.Set("stdout")
-			flAttach.Set("stderr")
-			if *flStdin {
-				flAttach.Set("stdin")
-			}
+	if flAttach.Len() == 0 {
+		attachStdout = true
+		attachStderr = true
+		if *flStdin {
+			attachStdin = true
 		}
 	}
 
@@ -254,10 +245,6 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 		return nil, nil, cmd, err
 	}
 
-	if *flAutoRemove && (restartPolicy.Name == "always" || restartPolicy.Name == "on-failure") {
-		return nil, nil, cmd, ErrConflictRestartPolicyAndAutoRemove
-	}
-
 	config := &Config{
 		Hostname:        hostname,
 		Domainname:      domainname,
@@ -270,9 +257,9 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 		Memory:          flMemory,
 		CpuShares:       *flCpuShares,
 		Cpuset:          *flCpuset,
-		AttachStdin:     flAttach.Get("stdin"),
-		AttachStdout:    flAttach.Get("stdout"),
-		AttachStderr:    flAttach.Get("stderr"),
+		AttachStdin:     attachStdin,
+		AttachStdout:    attachStdout,
+		AttachStderr:    attachStderr,
 		Env:             envVariables,
 		Cmd:             runCmd,
 		Image:           image,