Browse code

Add "docker create" support

This exposes the already existing "create container" operation. It is
very similar to "docker run -d" except it doesn't actually start the
container, but just prepares it. It can then be manually started using
"docker start" at any point.

Docker-DCO-1.1-Signed-off-by: Alexander Larsson <alexl@redhat.com> (github: alexlarsson)

Conflicts:
api/client/commands.go
runconfig/parse.go
server/container.go

Docker-DCO-1.1-Signed-off-by: Tibor Vass <teabee89@gmail.com> (github: tiborvass)

Alexander Larsson authored on 2014/03/10 22:11:23
Showing 6 changed files
... ...
@@ -1965,91 +1965,181 @@ func (cli *DockerCli) pullImage(image string) error {
1965 1965
 	return nil
1966 1966
 }
1967 1967
 
1968
-func (cli *DockerCli) CmdRun(args ...string) error {
1969
-	// FIXME: just use runconfig.Parse already
1970
-	config, hostConfig, cmd, err := runconfig.ParseSubcommand(cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container"), args, nil)
1971
-	if err != nil {
1972
-		return err
1968
+type cidFile struct {
1969
+	path    string
1970
+	file    *os.File
1971
+	written bool
1972
+}
1973
+
1974
+func newCIDFile(path string) (*cidFile, error) {
1975
+	if _, err := os.Stat(path); err == nil {
1976
+		return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path)
1973 1977
 	}
1974
-	if config.Image == "" {
1975
-		cmd.Usage()
1976
-		return nil
1978
+	f, err := os.Create(path)
1979
+	if err != nil {
1980
+		return nil, fmt.Errorf("Failed to create the container ID file: %s", err)
1977 1981
 	}
1978 1982
 
1979
-	// Retrieve relevant client-side config
1980
-	var (
1981
-		flName        = cmd.Lookup("name")
1982
-		flRm          = cmd.Lookup("rm")
1983
-		flSigProxy    = cmd.Lookup("sig-proxy")
1984
-		autoRemove, _ = strconv.ParseBool(flRm.Value.String())
1985
-		sigProxy, _   = strconv.ParseBool(flSigProxy.Value.String())
1986
-	)
1983
+	return &cidFile{path: path, file: f}, nil
1984
+}
1987 1985
 
1988
-	// Disable sigProxy in case on TTY
1989
-	if config.Tty {
1990
-		sigProxy = false
1991
-	}
1986
+func (cid *cidFile) Close() error {
1987
+	cid.file.Close()
1992 1988
 
1993
-	var containerIDFile io.WriteCloser
1994
-	if len(hostConfig.ContainerIDFile) > 0 {
1995
-		if _, err := os.Stat(hostConfig.ContainerIDFile); err == nil {
1996
-			return fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", hostConfig.ContainerIDFile)
1997
-		}
1998
-		if containerIDFile, err = os.Create(hostConfig.ContainerIDFile); err != nil {
1999
-			return fmt.Errorf("Failed to create the container ID file: %s", err)
1989
+	if !cid.written {
1990
+		if err := os.Remove(cid.path); err != nil {
1991
+			return fmt.Errorf("failed to remove CID file '%s': %s \n", cid.path, err)
2000 1992
 		}
2001
-		defer func() {
2002
-			containerIDFile.Close()
2003
-			var (
2004
-				cidFileInfo os.FileInfo
2005
-				err         error
2006
-			)
2007
-			if cidFileInfo, err = os.Stat(hostConfig.ContainerIDFile); err != nil {
2008
-				return
2009
-			}
2010
-			if cidFileInfo.Size() == 0 {
2011
-				if err := os.Remove(hostConfig.ContainerIDFile); err != nil {
2012
-					fmt.Printf("failed to remove Container ID file '%s': %s \n", hostConfig.ContainerIDFile, err)
2013
-				}
2014
-			}
2015
-		}()
2016 1993
 	}
2017 1994
 
1995
+	return nil
1996
+}
1997
+
1998
+func (cid *cidFile) Write(id string) error {
1999
+	if _, err := cid.file.Write([]byte(id)); err != nil {
2000
+		return fmt.Errorf("Failed to write the container ID to the file: %s", err)
2001
+	}
2002
+	cid.written = true
2003
+	return nil
2004
+}
2005
+
2006
+func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (engine.Env, error) {
2018 2007
 	containerValues := url.Values{}
2019
-	if name := flName.Value.String(); name != "" {
2008
+	if name != "" {
2020 2009
 		containerValues.Set("name", name)
2021 2010
 	}
2022 2011
 
2012
+	var data interface{}
2013
+	if hostConfig != nil {
2014
+		data = runconfig.MergeConfigs(config, hostConfig)
2015
+	} else {
2016
+		data = config
2017
+	}
2018
+
2019
+	var containerIDFile *cidFile
2020
+	if cidfile != "" {
2021
+		var err error
2022
+		if containerIDFile, err = newCIDFile(cidfile); err != nil {
2023
+			return nil, err
2024
+		}
2025
+		defer containerIDFile.Close()
2026
+	}
2027
+
2023 2028
 	//create the container
2024
-	stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false)
2029
+	stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), data, false)
2025 2030
 	//if image not found try to pull it
2026 2031
 	if statusCode == 404 {
2027 2032
 		fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", config.Image)
2028 2033
 
2029 2034
 		if err = cli.pullImage(config.Image); err != nil {
2030
-			return err
2035
+			return nil, err
2031 2036
 		}
2032 2037
 		// Retry
2033
-		if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false); err != nil {
2034
-			return err
2038
+		if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), data, false); err != nil {
2039
+			return nil, err
2035 2040
 		}
2036 2041
 	} else if err != nil {
2042
+		return nil, err
2043
+	}
2044
+
2045
+	var result engine.Env
2046
+	if err := result.Decode(stream); err != nil {
2047
+		return nil, err
2048
+	}
2049
+
2050
+	for _, warning := range result.GetList("Warnings") {
2051
+		fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
2052
+	}
2053
+
2054
+	if containerIDFile != nil {
2055
+		if err = containerIDFile.Write(result.Get("Id")); err != nil {
2056
+			return nil, err
2057
+		}
2058
+	}
2059
+
2060
+	return result, nil
2061
+
2062
+}
2063
+
2064
+func (cli *DockerCli) CmdCreate(args ...string) error {
2065
+	// FIXME: just use runconfig.Parse already
2066
+	cmd := cli.Subcmd("create", "IMAGE [COMMAND] [ARG...]", "Create a new container")
2067
+
2068
+	// These are flags not stored in Config/HostConfig
2069
+	var (
2070
+		flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
2071
+	)
2072
+
2073
+	config, hostConfig, cmd, err := runconfig.ParseSubcommand(cmd, args, nil)
2074
+	if err != nil {
2037 2075
 		return err
2038 2076
 	}
2077
+	if config.Image == "" {
2078
+		cmd.Usage()
2079
+		return nil
2080
+	}
2039 2081
 
2040
-	var runResult engine.Env
2041
-	if err := runResult.Decode(stream); err != nil {
2082
+	createResult, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
2083
+	if err != nil {
2042 2084
 		return err
2043 2085
 	}
2044 2086
 
2045
-	for _, warning := range runResult.GetList("Warnings") {
2046
-		fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
2087
+	fmt.Fprintf(cli.out, "%s\n", createResult.Get("Id"))
2088
+
2089
+	return nil
2090
+}
2091
+
2092
+func (cli *DockerCli) CmdRun(args ...string) error {
2093
+	// FIXME: just use runconfig.Parse already
2094
+	cmd := cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
2095
+
2096
+	// These are flags not stored in Config/HostConfig
2097
+	var (
2098
+		flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)")
2099
+		flDetach     = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run container in the background and print new container ID")
2100
+		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.")
2101
+		flName       = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container")
2102
+
2103
+		flAttach *opts.ListOpts
2104
+
2105
+		ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d")
2106
+	)
2107
+
2108
+	config, hostConfig, cmd, err := runconfig.ParseSubcommand(cmd, args, nil)
2109
+	if err != nil {
2110
+		return err
2111
+	}
2112
+	if config.Image == "" {
2113
+		cmd.Usage()
2114
+		return nil
2047 2115
 	}
2048 2116
 
2049
-	if len(hostConfig.ContainerIDFile) > 0 {
2050
-		if _, err = containerIDFile.Write([]byte(runResult.Get("Id"))); err != nil {
2051
-			return fmt.Errorf("Failed to write the container ID to the file: %s", err)
2117
+	if *flDetach {
2118
+		if fl := cmd.Lookup("attach"); fl != nil {
2119
+			flAttach = fl.Value.(*opts.ListOpts)
2120
+			if flAttach.Len() != 0 {
2121
+				return fmt.Errorf("Conflicting options: -a and -d")
2122
+			}
2123
+		}
2124
+		if *flAutoRemove {
2125
+			return fmt.Errorf("Conflicting options: --rm and -d")
2052 2126
 		}
2127
+
2128
+		config.AttachStdin = false
2129
+		config.AttachStdout = false
2130
+		config.AttachStderr = false
2131
+		config.StdinOnce = false
2132
+	}
2133
+
2134
+	// Disable flSigProxy in case on TTY
2135
+	sigProxy := *flSigProxy
2136
+	if config.Tty {
2137
+		sigProxy = false
2138
+	}
2139
+
2140
+	runResult, err := cli.createContainer(config, nil, hostConfig.ContainerIDFile, *flName)
2141
+	if err != nil {
2142
+		return err
2053 2143
 	}
2054 2144
 
2055 2145
 	if sigProxy {
... ...
@@ -2158,7 +2248,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
2158 2158
 	var status int
2159 2159
 
2160 2160
 	// Attached mode
2161
-	if autoRemove {
2161
+	if *flAutoRemove {
2162 2162
 		// Autoremove: wait for the container to finish, retrieve
2163 2163
 		// the exit code and remove the container
2164 2164
 		if _, _, err := readBody(cli.call("POST", "/containers/"+runResult.Get("Id")+"/wait", nil, false)); err != nil {
... ...
@@ -50,6 +50,14 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) engine.Status {
50 50
 	for _, warning := range buildWarnings {
51 51
 		job.Errorf("%s\n", warning)
52 52
 	}
53
+
54
+	if job.EnvExists("HostConfig") {
55
+		hostConfig := runconfig.ContainerHostConfigFromJob(job)
56
+		if err := daemon.setHostConfig(container, hostConfig); err != nil {
57
+			return job.Error(err)
58
+		}
59
+	}
60
+
53 61
 	return engine.StatusOK
54 62
 }
55 63
 
... ...
@@ -53,6 +53,7 @@ func init() {
53 53
 			{"build", "Build an image from a Dockerfile"},
54 54
 			{"commit", "Create a new image from a container's changes"},
55 55
 			{"cp", "Copy files/folders from a container's filesystem to the host path"},
56
+			{"create", "Create a new container"},
56 57
 			{"diff", "Inspect changes on a container's filesystem"},
57 58
 			{"events", "Get real time events from the server"},
58 59
 			{"export", "Stream the contents of a container as a tar archive"},
... ...
@@ -370,6 +370,63 @@ path.  Paths are relative to the root of the filesystem.
370 370
 
371 371
     Copy files/folders from the PATH to the HOSTPATH
372 372
 
373
+
374
+## create
375
+
376
+Creates a new container.
377
+
378
+    Usage: docker create [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...]
379
+
380
+
381
+    -a, --attach=[]            Attach to stdin, stdout or stderr.
382
+    -c, --cpu-shares=0         CPU shares (relative weight)
383
+    --cidfile=""               Write the container ID to the file
384
+    --dns=[]                   Set custom dns servers
385
+    --dns-search=[]            Set custom dns search domains
386
+    -e, --env=[]               Set environment variables
387
+    --entrypoint=""            Overwrite the default entrypoint of the image
388
+    --env-file=[]              Read in a line delimited file of ENV variables
389
+    --expose=[]                Expose a port from the container without publishing it to your host
390
+    -h, --hostname=""          Container host name
391
+    -i, --interactive=false    Keep stdin open even if not attached
392
+    --link=[]                  Add link to another container (name:alias)
393
+    --lxc-conf=[]              (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1"
394
+    -m, --memory=""            Memory limit (format: <number><optional unit>, where unit = b, k, m or g)
395
+    --name=""                  Assign a name to the container
396
+    --net="bridge"             Set the Network mode for the container
397
+                                 'bridge': creates a new network stack for the container on the docker bridge
398
+                                 'none': no networking for this container
399
+                                 'container:<name|id>': reuses another container network stack
400
+                                 'host': use the host network stack inside the contaner
401
+    -p, --publish=[]           Publish a container's port to the host
402
+                                 format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort
403
+                                 (use 'docker port' to see the actual mapping)
404
+    -P, --publish-all=false    Publish all exposed ports to the host interfaces
405
+    --privileged=false         Give extended privileges to this container
406
+    -t, --tty=false            Allocate a pseudo-tty
407
+    -u, --user=""              Username or UID
408
+    -v, --volume=[]            Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)
409
+    --volumes-from=[]          Mount volumes from the specified container(s)
410
+    -w, --workdir=""           Working directory inside the container
411
+
412
+
413
+The `docker create` command `creates` a writeable container layer over
414
+the specified image, and prepares it for running the specified
415
+command. The container id is then printed to stdout. This is similar
416
+to what `docker run -d <cli_run>`, does except the container is never
417
+started. You can then use the `docker start <cli_start>` command to
418
+start the container at any point.
419
+
420
+This is useful when you want to set up a container configuration ahead
421
+of time, so that it is ready to start when you need it.
422
+
423
+### Example:
424
+
425
+    $ sudo docker create -t -i fedora bash
426
+    6d8af538ec541dd581ebc2a24153a28329acb5268abe5ef868c1f1a261221752
427
+    $ sudo docker start -a -i 6d8af538ec5
428
+    bash-4.2#
429
+
373 430
 ## diff
374 431
 
375 432
 List the changed files and directories in a container᾿s filesystem
... ...
@@ -57,7 +57,27 @@ type HostConfig struct {
57 57
 	RestartPolicy   RestartPolicy
58 58
 }
59 59
 
60
+// This is used by the create command when you want to both set the
61
+// Config and the HostConfig in the same call
62
+type ConfigAndHostConfig struct {
63
+	Config
64
+	HostConfig HostConfig
65
+}
66
+
67
+func MergeConfigs(config *Config, hostConfig *HostConfig) *ConfigAndHostConfig {
68
+	return &ConfigAndHostConfig{
69
+		*config,
70
+		*hostConfig,
71
+	}
72
+}
73
+
60 74
 func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
75
+	if job.EnvExists("HostConfig") {
76
+		hostConfig := HostConfig{}
77
+		job.GetenvJson("HostConfig", &hostConfig)
78
+		return &hostConfig
79
+	}
80
+
61 81
 	hostConfig := &HostConfig{
62 82
 		ContainerIDFile: job.Getenv("ContainerIDFile"),
63 83
 		Privileged:      job.GetenvBool("Privileged"),
... ...
@@ -18,7 +18,6 @@ import (
18 18
 
19 19
 var (
20 20
 	ErrInvalidWorkingDirectory            = fmt.Errorf("The working directory is invalid. It needs to be an absolute path.")
21
-	ErrConflictAttachDetach               = fmt.Errorf("Conflicting options: -a and -d")
22 21
 	ErrConflictContainerNetworkAndLinks   = fmt.Errorf("Conflicting options: --net=container can't be used with links. This would result in undefined behavior.")
23 22
 	ErrConflictContainerNetworkAndDns     = fmt.Errorf("Conflicting options: --net=container can't be used with --dns. This configuration is invalid.")
24 23
 	ErrConflictDetachAutoRemove           = fmt.Errorf("Conflicting options: --rm and -d")
... ...
@@ -28,7 +27,7 @@ var (
28 28
 	ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
29 29
 )
30 30
 
31
-//FIXME Only used in tests
31
+// FIXME Only used in tests
32 32
 func Parse(args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) {
33 33
 	cmd := flag.NewFlagSet("run", flag.ContinueOnError)
34 34
 	cmd.SetOutput(ioutil.Discard)
... ...
@@ -60,8 +59,6 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
60 60
 		flCapAdd      = opts.NewListOpts(nil)
61 61
 		flCapDrop     = opts.NewListOpts(nil)
62 62
 
63
-		flAutoRemove      = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)")
64
-		flDetach          = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run container in the background and print new container ID")
65 63
 		flNetwork         = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container")
66 64
 		flPrivileged      = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container")
67 65
 		flPublishAll      = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to the host interfaces")
... ...
@@ -77,15 +74,13 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
77 77
 		flCpuset          = cmd.String([]string{"-cpuset"}, "", "CPUs in which to allow execution (0-3, 0,1)")
78 78
 		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.")
79 79
 		flRestartPolicy   = cmd.String([]string{"-restart"}, "", "Restart policy to apply when a container exits (no, on-failure[:max-retry], always)")
80
-		// For documentation purpose
81
-		_ = 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.")
82
-		_ = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container")
83 80
 	)
84 81
 
85 82
 	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR.")
86 83
 	cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume (e.g., from the host: -v /host:/container, from Docker: -v /container)")
87 84
 	cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container in the form of name:alias")
88 85
 	cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc)")
86
+
89 87
 	cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
90 88
 	cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a line delimited file of environment variables")
91 89
 
... ...
@@ -109,15 +104,15 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
109 109
 	}
110 110
 
111 111
 	// Validate input params
112
-	if *flDetach && flAttach.Len() > 0 {
113
-		return nil, nil, cmd, ErrConflictAttachDetach
114
-	}
115 112
 	if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
116 113
 		return nil, nil, cmd, ErrInvalidWorkingDirectory
117 114
 	}
118
-	if *flDetach && *flAutoRemove {
119
-		return nil, nil, cmd, ErrConflictDetachAutoRemove
120
-	}
115
+
116
+	var (
117
+		attachStdin  = flAttach.Get("stdin")
118
+		attachStdout = flAttach.Get("stdout")
119
+		attachStderr = flAttach.Get("stderr")
120
+	)
121 121
 
122 122
 	if *flNetMode != "bridge" && *flNetMode != "none" && *flHostname != "" {
123 123
 		return nil, nil, cmd, ErrConflictNetworkHostname
... ...
@@ -140,13 +135,11 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
140 140
 	}
141 141
 
142 142
 	// If neither -d or -a are set, attach to everything by default
143
-	if flAttach.Len() == 0 && !*flDetach {
144
-		if !*flDetach {
145
-			flAttach.Set("stdout")
146
-			flAttach.Set("stderr")
147
-			if *flStdin {
148
-				flAttach.Set("stdin")
149
-			}
143
+	if flAttach.Len() == 0 {
144
+		attachStdout = true
145
+		attachStderr = true
146
+		if *flStdin {
147
+			attachStdin = true
150 148
 		}
151 149
 	}
152 150
 
... ...
@@ -270,9 +263,9 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
270 270
 		Memory:          flMemory,
271 271
 		CpuShares:       *flCpuShares,
272 272
 		Cpuset:          *flCpuset,
273
-		AttachStdin:     flAttach.Get("stdin"),
274
-		AttachStdout:    flAttach.Get("stdout"),
275
-		AttachStderr:    flAttach.Get("stderr"),
273
+		AttachStdin:     attachStdin,
274
+		AttachStdout:    attachStdout,
275
+		AttachStderr:    attachStderr,
276 276
 		Env:             envVariables,
277 277
 		Cmd:             runCmd,
278 278
 		Image:           image,