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:
Alexander Larsson
2014-09-16 18:40:24 -04:00
committed by Tibor Vass
parent eb21197c6b
commit 3a90004f3c
6 changed files with 261 additions and 92 deletions
+159 -69
View File
@@ -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 {