From a8e405bedbc210634c6cb482cafc6a906351a13c Mon Sep 17 00:00:00 2001 From: v01dstar Date: Mon, 27 Jun 2016 09:38:21 +0800 Subject: [PATCH] add support for both attach mode and detach mode (#158) * add support for both attach mode and detach mode * fix: detach flag was parsed incorrectly --- nmz/cli/container/run/run.go | 12 ++-- nmz/cli/container/run/runflag.go | 96 ++++++++++++++++++++++++-------- nmz/container/ns/boot.go | 12 +++- 3 files changed, 92 insertions(+), 28 deletions(-) diff --git a/nmz/cli/container/run/run.go b/nmz/cli/container/run/run.go index 2d7154c..75d89de 100644 --- a/nmz/cli/container/run/run.go +++ b/nmz/cli/container/run/run.go @@ -27,7 +27,7 @@ import ( "github.com/osrg/namazu/nmz/util/config" ) -func prepare(args []string) (dockerOpt *docker.CreateContainerOptions, removeOnExit bool, nmzCfg config.Config, err error) { +func prepare(args []string) (dockerOpt *docker.CreateContainerOptions, removeOnExit bool, detach bool, nmzCfg config.Config, err error) { if len(args) < 3 { // FIXME err = fmt.Errorf("bad argument: %s", args) @@ -39,6 +39,7 @@ func prepare(args []string) (dockerOpt *docker.CreateContainerOptions, removeOnE return } removeOnExit = flagSet.IsSet("-rm") + detach = flagSet.IsSet("d") || flag.IsSet("-detach") nmzCfgPath := flagSet.Lookup("-nmz-autopilot").Value.String() nmzCfg, err = newConfig(nmzCfgPath) @@ -61,12 +62,15 @@ func help() string { Run a command in a new Namazu Container Docker-compatible options: - -d, --detach [NOT SUPPORTED] Run container in background and print container ID + -d, --detach Run container in background and print container ID -i, --interactive Keep STDIN open even if not attached + -p Publish a container's port(s) to the host --name Assign a name to the container --rm Automatically remove the container when it exits -t, --tty Allocate a pseudo-TTY -v, --volume=[] Bind mount a volume + --volumes-from Mount volumes from the specified container(s) + --privileged Give extended privileges to this container Namazu-specific options: -nmz-autopilot Namazu configuration file @@ -77,7 +81,7 @@ NOTE: Unlike docker, COMMAND is mandatory at the moment. } func Run(args []string) int { - dockerOpt, removeOnExit, nmzCfg, err := prepare(args) + dockerOpt, removeOnExit, detach, nmzCfg, err := prepare(args) if err != nil { // do not panic here fmt.Fprintf(os.Stderr, "%s\n", err) @@ -91,7 +95,7 @@ func Run(args []string) int { } containerExitStatusChan := make(chan error) - c, err := ns.Boot(client, dockerOpt, containerExitStatusChan) + c, err := ns.Boot(client, dockerOpt, detach, containerExitStatusChan) if err == docker.ErrNoSuchImage { log.Critical(err) // TODO: pull the image automatically diff --git a/nmz/cli/container/run/runflag.go b/nmz/cli/container/run/runflag.go index d7fba3c..d2dadf9 100644 --- a/nmz/cli/container/run/runflag.go +++ b/nmz/cli/container/run/runflag.go @@ -20,56 +20,108 @@ import ( // FIXME: we should not rely on internal docker packages.. "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/go-connections/nat" docker "github.com/fsouza/go-dockerclient" ) +// TODO: we should support more options.. func parseRun(cmd *flag.FlagSet, args []string) (*docker.CreateContainerOptions, error) { var ( - flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached") - flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY") - flDetach = cmd.Bool([]string{"d", "-detach"}, false, "[NOT SUPPORTED] Run container in background and print container ID") + attachStdin = true + attachStdout = true + attachStderr = true + stdinOnce = true + + flVolumes = opts.NewListOpts(nil) + flVolumesFrom = opts.NewListOpts(nil) + flPublish = opts.NewListOpts(nil) + + flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached") + flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY") + flPrivileged = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to this container") + flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID") + flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits") + flName = cmd.String([]string{"-name"}, "", "Assign a name to the container") - flVolumes = opts.NewListOpts(nil) - // the caller should handle "-rm" with cmd.IsSet() - _ = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits") + flNetMode = cmd.String([]string{"-net"}, "default", "Connect a container to a network") + // the caller should handle "-nmz-autopilot" _ = cmd.String([]string{"-nmz-autopilot"}, "", "Namazu configuration file") ) cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume") + cmd.Var(&flPublish, []string{"p"}, "Publish a container's port(s) to the host") + cmd.Var(&flVolumesFrom, []string{"-volumes-from"}, "Mount volumes from the specified container(s)") + if err := cmd.Parse(args); err != nil { return nil, err } - if !*flStdin { - return nil, fmt.Errorf("--interactive is expected.") - } - if !*flTty { - return nil, fmt.Errorf("--tty is expected.") - } - if *flDetach { - return nil, fmt.Errorf("Currently, --detach is not supported.") - } parsedArgs := cmd.Args() - if len(parsedArgs) < 2 { - return nil, fmt.Errorf("requires a minimum of 2 arguments") + + if !*flDetach { + if !*flStdin { + return nil, fmt.Errorf("--interactive is expected in attach mode.") + } + if !*flTty { + return nil, fmt.Errorf("--tty is expected in attach mode.") + } + if len(parsedArgs) < 2 { + return nil, fmt.Errorf("requires a minimum of 2 arguments in attach mode.") + } + } else { + if *flAutoRemove { + return nil, fmt.Errorf("--rm is not availible in detach mode.") + } + attachStdin = false + attachStdout = false + attachStderr = false + stdinOnce = false } + image := parsedArgs[0] execCmd := parsedArgs[1:] + // FIXME: we should implement this parse function ourselves.. + _, portMap, err := nat.ParsePortSpecs(flPublish.GetAll()) + if err != nil { + return nil, err + } + + // Transform nat.PortMap to docker.PortMap(map[string][]PortBindings). + portBindings := make(map[docker.Port][]docker.PortBinding) + for port, bindings := range portMap { + for _, binding := range bindings { + // nat.Port string + bindingSlice, exists := portBindings[docker.Port(port)] + if !exists { + bindingSlice = []docker.PortBinding{} + } + + portBindings[docker.Port(port)] = append(bindingSlice, docker.PortBinding{ + HostIP: binding.HostIP, + HostPort: binding.HostPort, + }) + } + } + dockerOpt := docker.CreateContainerOptions{ Name: *flName, Config: &docker.Config{ Image: image, Cmd: execCmd, OpenStdin: *flStdin, - StdinOnce: true, - AttachStdin: true, - AttachStdout: true, - AttachStderr: true, + StdinOnce: stdinOnce, + AttachStdin: attachStdin, + AttachStdout: attachStdout, + AttachStderr: attachStderr, Tty: *flTty, }, HostConfig: &docker.HostConfig{ - Binds: flVolumes.GetAllOrEmpty(), + Binds: flVolumes.GetAllOrEmpty(), + Privileged: *flPrivileged, + PortBindings: portBindings, + NetworkMode: *flNetMode, + VolumesFrom: flVolumesFrom.GetAllOrEmpty(), }, } return &dockerOpt, nil diff --git a/nmz/container/ns/boot.go b/nmz/container/ns/boot.go index 0398000..157a653 100644 --- a/nmz/container/ns/boot.go +++ b/nmz/container/ns/boot.go @@ -24,7 +24,7 @@ import ( docker "github.com/fsouza/go-dockerclient" ) -func Boot(client *docker.Client, opt *docker.CreateContainerOptions, +func Boot(client *docker.Client, opt *docker.CreateContainerOptions, detach bool, exitCh chan error) (*docker.Container, error) { log.Debugf("Creating container for image %s", opt.Config.Image) container, err := client.CreateContainer(*opt) @@ -34,7 +34,15 @@ func Boot(client *docker.Client, opt *docker.CreateContainerOptions, log.Debugf("Starting container %s", container.ID) go func() { - exitCh <- dockerpty.Start(client, container, opt.HostConfig) + if detach { + err = client.StartContainer(container.ID, opt.HostConfig) + if err == nil { + _, err = client.WaitContainer(container.ID) + } + exitCh <- err + } else { + exitCh <- dockerpty.Start(client, container, opt.HostConfig) + } }() trial := 0