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)
This commit is contained in:
parent
eb21197c6b
commit
3a90004f3c
6 changed files with 261 additions and 92 deletions
|
@ -1965,9 +1965,112 @@ func (cli *DockerCli) pullImage(image string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
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)
|
||||
}
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to create the container ID file: %s", err)
|
||||
}
|
||||
|
||||
return &cidFile{path: path, file: f}, nil
|
||||
}
|
||||
|
||||
func (cid *cidFile) Close() error {
|
||||
cid.file.Close()
|
||||
|
||||
if !cid.written {
|
||||
if err := os.Remove(cid.path); err != nil {
|
||||
return fmt.Errorf("failed to remove CID file '%s': %s \n", cid.path, 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 != "" {
|
||||
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(), 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 nil, err
|
||||
}
|
||||
// Retry
|
||||
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
|
||||
config, hostConfig, cmd, err := runconfig.ParseSubcommand(cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container"), args, nil)
|
||||
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
|
||||
}
|
||||
|
@ -1976,82 +2079,69 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Retrieve relevant client-side config
|
||||
createResult, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 (
|
||||
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())
|
||||
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")
|
||||
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")
|
||||
)
|
||||
|
||||
// Disable sigProxy in case on TTY
|
||||
config, hostConfig, cmd, err := runconfig.ParseSubcommand(cmd, args, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if config.Image == "" {
|
||||
cmd.Usage()
|
||||
return nil
|
||||
}
|
||||
|
||||
if *flDetach {
|
||||
if fl := cmd.Lookup("attach"); fl != nil {
|
||||
flAttach = fl.Value.(*opts.ListOpts)
|
||||
if flAttach.Len() != 0 {
|
||||
return fmt.Errorf("Conflicting options: -a and -d")
|
||||
}
|
||||
}
|
||||
if *flAutoRemove {
|
||||
return fmt.Errorf("Conflicting options: --rm and -d")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
containerValues := url.Values{}
|
||||
if name := flName.Value.String(); name != "" {
|
||||
containerValues.Set("name", name)
|
||||
}
|
||||
|
||||
//create the container
|
||||
stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config, 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
|
||||
}
|
||||
// Retry
|
||||
if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
runResult, err := cli.createContainer(config, nil, hostConfig.ContainerIDFile, *flName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var runResult engine.Env
|
||||
if err := runResult.Decode(stream); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, warning := range runResult.GetList("Warnings") {
|
||||
fmt.Fprintf(cli.err, "WARNING: %s\n", warning)
|
||||
}
|
||||
|
||||
if len(hostConfig.ContainerIDFile) > 0 {
|
||||
if _, err = containerIDFile.Write([]byte(runResult.Get("Id"))); err != nil {
|
||||
return fmt.Errorf("Failed to write the container ID to the file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if sigProxy {
|
||||
sigc := cli.forwardAllSignals(runResult.Get("Id"))
|
||||
defer signal.StopCatch(sigc)
|
||||
|
@ -2158,7 +2248,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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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"},
|
||||
{"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.
|
|||
|
||||
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 or 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 ENV 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 contaner
|
||||
-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 what `docker run -d <cli_run>`, does except the container is never
|
||||
started. You can then use the `docker start <cli_start>` 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
|
||||
|
|
|
@ -57,7 +57,27 @@ type HostConfig struct {
|
|||
RestartPolicy RestartPolicy
|
||||
}
|
||||
|
||||
// This is used by the create command when you want to both set 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"),
|
||||
|
|
|
@ -18,7 +18,6 @@ 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")
|
||||
|
@ -28,7 +27,7 @@ var (
|
|||
ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
|
||||
)
|
||||
|
||||
//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 +59,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 +74,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 +104,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 +135,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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,9 +263,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,
|
||||
|
|
Loading…
Add table
Reference in a new issue