diff --git a/batch.go b/batch.go new file mode 100644 index 00000000000..b38b3a09484 --- /dev/null +++ b/batch.go @@ -0,0 +1,125 @@ +// +build linux + +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" + "github.com/coreos/go-systemd/activation" + "github.com/opencontainers/specs/specs-go" +) + +var batchCommand = cli.Command{ + Name: "batch", + Usage: "create and run a container with a series of commands", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "config-file, c", + Value: "config.json", + Usage: "path to spec config file", + }, + cli.StringFlag{ + Name: "runtime-file, r", + Value: "runtime.json", + Usage: "path to runtime config file", + }, + }, + Action: func(context *cli.Context) { + spec, err := loadSpec(context.String("config-file")) + if err != nil { + fatal(err) + } + + id := context.Args().First() + if id == "" { + fatal(errEmptyID) + } + + batchFilename := context.Args().Get(1) + if batchFilename == "" { + fatal(fmt.Errorf("Missing batch-file-name")) + } + + notifySocket := os.Getenv("NOTIFY_SOCKET") + if notifySocket != "" { + setupSdNotify(spec, notifySocket) + } + + if os.Geteuid() != 0 { + logrus.Fatal("runc should be run as root") + } + + status, err := batchContainer(context, id, spec, batchFilename) + if err != nil { + logrus.Fatalf("Container start failed: %v", err) + } + // exit with the container's exit status so any external supervisor is + // notified of the exit with the correct exit status. + os.Exit(status) + }, +} + +func batchContainer(context *cli.Context, id string, spec *specs.Spec, batchFilename string) (int, error) { + var file *os.File + var err error + + if batchFilename == "-" { + file = os.Stdin + } else { + if file, err = os.Open(batchFilename); err != nil { + return -1, err + } + defer file.Close() + } + + scanner := bufio.NewScanner(file) + + container, err := createContainer(context, id, spec) + if err != nil { + return -1, err + } + defer deleteContainer(container) + + // Support on-demand socket activation by passing file descriptors into the container init process. + listenFDs := []*os.File{} + if os.Getenv("LISTEN_FDS") != "" { + listenFDs = activation.Files(false) + } + + // Loop over the list of processes that people want executed. + // Use the config Process as the template for now + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || line[0] == '#' { + continue + } + + proc := specs.Process{ + Terminal: spec.Process.Terminal, + User: spec.Process.User, + Args: strings.Split(line, " "), + Env: spec.Process.Env, + Cwd: spec.Process.Cwd, + } + + if batchFilename == "-" { + proc.Terminal = false + } + + fmt.Printf("--> %q\n", proc.Args) + + rc, err := runProcess(container, &proc, listenFDs, context.String("console"), context.String("pid-file"), false) + if rc != 0 || err != nil { + // For now just stop on first error + return rc, nil + } + } + + // All is well + return 0, nil +} diff --git a/create.go b/create.go new file mode 100644 index 00000000000..c4a86b0a9fb --- /dev/null +++ b/create.go @@ -0,0 +1,57 @@ +// +build linux + +package main + +import ( + "os" + + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" +) + +var createCommand = cli.Command{ + Name: "create", + Usage: "create container", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "bundle, b", + Value: "", + Usage: "path to the root of the bundle directory", + }, + cli.StringFlag{ + Name: "console", + Value: "", + Usage: "specify the pty slave path for use with the container", + }, + }, + Action: func(context *cli.Context) { + id := context.Args().First() + if id == "" { + fatal(errEmptyID) + } + + bundle := context.String("bundle") + if bundle != "" { + if err := os.Chdir(bundle); err != nil { + fatal(err) + } + } + spec, err := loadSpec(specConfig) + if err != nil { + fatal(err) + } + + notifySocket := os.Getenv("NOTIFY_SOCKET") + if notifySocket != "" { + setupSdNotify(spec, notifySocket) + } + + if os.Geteuid() != 0 { + logrus.Fatal("runc should be run as root") + } + _, err = createContainer(context, id, spec) + if err != nil { + logrus.Fatalf("Container create failed: %v", err) + } + }, +} diff --git a/delete.go b/delete.go index 4f46564e140..283e54cd0ca 100644 --- a/delete.go +++ b/delete.go @@ -1,6 +1,11 @@ package main -import "github.com/codegangsta/cli" +import ( + "os" + + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" +) var deleteCommand = cli.Command{ Name: "delete", @@ -14,11 +19,16 @@ status of "ubuntu01" as "destroyed" the following will delete resources held for "ubuntu01" removing "ubuntu01" from the runc list of containers: # runc delete ubuntu01`, + Flags: []cli.Flag{}, Action: func(context *cli.Context) { + if os.Geteuid() != 0 { + logrus.Fatal("runc should be run as root") + } container, err := getContainer(context) if err != nil { - fatal(err) + logrus.Fatalf("Container delete failed: %v", err) + os.Exit(-1) } - destroy(container) + deleteContainer(container) }, } diff --git a/exec.go b/exec.go index 2d70d76139c..6884a5a4944 100644 --- a/exec.go +++ b/exec.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/codegangsta/cli" + "github.com/opencontainers/runc/libcontainer" "github.com/opencontainers/specs/specs-go" ) @@ -94,16 +95,28 @@ func execProcess(context *cli.Context) (int, error) { if err != nil { return -1, err } + detach := context.Bool("detach") + state, err := container.State() if err != nil { return -1, err } bundle := searchLabels(state.Config.Labels, "bundle") + + status, err := container.Status() + if err != nil { + return -1, err + } + if status != libcontainer.Running { + return -1, fmt.Errorf("Container not running") + } + p, err := getProcess(context, bundle) if err != nil { return -1, err } + return runProcess(container, p, nil, context.String("console"), context.String("pid-file"), detach) } @@ -130,6 +143,7 @@ func getProcess(context *cli.Context, bundle string) (*specs.Process, error) { } p := spec.Process p.Args = context.Args()[1:] + // override the cwd, if passed if context.String("cwd") != "" { p.Cwd = context.String("cwd") diff --git a/libcontainer/container.go b/libcontainer/container.go index 32daa97675a..4d487789c69 100644 --- a/libcontainer/container.go +++ b/libcontainer/container.go @@ -117,6 +117,9 @@ type BaseContainer interface { // Systemerror - System error. Set(config configs.Config) error + // Create will setup a new container but not actually run a user process + Create(process *Process) (err error) + // Start a process inside the container. Returns error if process fails to // start. You can track process lifecycle with passed Process structure. // diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index 29bf1b8c0df..a269bca346f 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -174,6 +174,40 @@ func (c *linuxContainer) Set(config configs.Config) error { return c.cgroupManager.Set(c.config) } +// Create will initialize(create) a new container by +// creating the namespaces associated with it. +func (c *linuxContainer) Create(process *Process) error { + c.m.Lock() + defer c.m.Unlock() + + // generate a timestamp indicating when the container was created + c.created = time.Now().UTC() + + parent, err := c.newParentProcess(process, initCreate) + if err != nil { + return newSystemError(err) + } + if err := parent.start(); err != nil { + // terminate the process to ensure that it properly is reaped. + if err := parent.terminate(); err != nil { + logrus.Warn(err) + } + return newSystemError(err) + } + + if err := c.updateState(parent); err != nil { + return err + } + + // Wait for process to end + _, err = parent.wait() + if err != nil { + return err + } + + return nil +} + func (c *linuxContainer) Start(process *Process) error { c.m.Lock() defer c.m.Unlock() @@ -181,8 +215,17 @@ func (c *linuxContainer) Start(process *Process) error { if err != nil { return err } - doInit := status == Destroyed - parent, err := c.newParentProcess(process, doInit) + + // doInit will be true if we're creating the main proc of the container. + // Otherwise we're just joining the namespaces of the existing proc. + doInit := status == Created + + it := initStandard + if !doInit { + it = initSetns + } + + parent, err := c.newParentProcess(process, it) if err != nil { return newSystemError(err) } @@ -193,8 +236,6 @@ func (c *linuxContainer) Start(process *Process) error { } return newSystemError(err) } - // generate a timestamp indicating when the container was started - c.created = time.Now().UTC() c.state = &runningState{ c: c, @@ -230,7 +271,7 @@ func (c *linuxContainer) Signal(s os.Signal) error { return nil } -func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) { +func (c *linuxContainer) newParentProcess(p *Process, it initType) (parentProcess, error) { parentPipe, childPipe, err := newPipe() if err != nil { return nil, newSystemError(err) @@ -239,10 +280,15 @@ func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProces if err != nil { return nil, newSystemError(err) } - if !doInit { + switch it { + case initCreate: + return c.newCreateProcess(p, cmd, parentPipe, childPipe) + case initSetns: return c.newSetnsProcess(p, cmd, parentPipe, childPipe) + case initStandard: + return c.newInitProcess(p, cmd, parentPipe, childPipe) } - return c.newInitProcess(p, cmd, parentPipe, childPipe) + panic(fmt.Sprintf("should not get here - it: %v", it)) } func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.Cmd, error) { @@ -268,6 +314,34 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec. return cmd, nil } +// newCreateProcess is the same as newInitProcess except the INITTYPE value +// will differ. +func (c *linuxContainer) newCreateProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*initProcess, error) { + cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initCreate)) + nsMaps := make(map[configs.NamespaceType]string) + for _, ns := range c.config.Namespaces { + if ns.Path != "" { + nsMaps[ns.Type] = ns.Path + } + } + _, sharePidns := nsMaps[configs.NEWPID] + data, err := c.bootstrapData(c.config.Namespaces.CloneFlags(), nsMaps, "") + if err != nil { + return nil, err + } + return &initProcess{ + cmd: cmd, + childPipe: childPipe, + parentPipe: parentPipe, + manager: c.cgroupManager, + config: c.newInitConfig(p), + container: c, + process: p, + bootstrapData: data, + sharePidns: sharePidns, + }, nil +} + func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, childPipe *os.File) (*initProcess, error) { cmd.Env = append(cmd.Env, "_LIBCONTAINER_INITTYPE="+string(initStandard)) nsMaps := make(map[configs.NamespaceType]string) @@ -360,6 +434,28 @@ func newPipe() (parent *os.File, child *os.File, err error) { func (c *linuxContainer) Destroy() error { c.m.Lock() defer c.m.Unlock() + + namespaces := []string{"NEWIPC", "NEWUTS", "NEWNET", "NEWPID", "NEWUSER", "NEWNS", "NEWMNT"} + + for _, key := range namespaces { + mount := filepath.Join(c.root, string(key)) + if _, err := os.Stat(mount); err != nil { + // Unknown namespace mounts are ok to skip + continue + } + err := syscall.Unmount(mount, syscall.MNT_DETACH) + if err != nil { + // Ignore any error since it probably means something + // went wrong during setup and its ok to just remove it in + // the next step + // return fmt.Errorf("Unmount err(%s): %v", mount, err) + } + err = os.Remove(mount) + if err != nil { + return fmt.Errorf("Remove err(%s): %v", mount, err) + } + } + return c.state.destroy() } diff --git a/libcontainer/create_init_linux.go b/libcontainer/create_init_linux.go new file mode 100644 index 00000000000..ef05641ee31 --- /dev/null +++ b/libcontainer/create_init_linux.go @@ -0,0 +1,148 @@ +// +build linux + +package libcontainer + +import ( + "fmt" + "io" + "os" + "syscall" + + "github.com/opencontainers/runc/libcontainer/apparmor" + "github.com/opencontainers/runc/libcontainer/configs" + "github.com/opencontainers/runc/libcontainer/keys" + "github.com/opencontainers/runc/libcontainer/label" + "github.com/opencontainers/runc/libcontainer/seccomp" + "github.com/opencontainers/runc/libcontainer/system" +) + +type linuxCreateInit struct { + pipe io.ReadWriter + parentPid int + config *initConfig +} + +func (l *linuxCreateInit) getSessionRingParams() (string, uint32, uint32) { + var newperms uint32 + + if l.config.Config.Namespaces.Contains(configs.NEWUSER) { + // with user ns we need 'other' search permissions + newperms = 0x8 + } else { + // without user ns we need 'UID' search permissions + newperms = 0x80000 + } + + // create a unique per session container name that we can + // join in setns; however, other containers can also join it + return fmt.Sprintf("_ses.%s", l.config.ContainerId), 0xffffffff, newperms +} + +func (l *linuxCreateInit) Init() error { + ringname, keepperms, newperms := l.getSessionRingParams() + + // do not inherit the parent's session keyring + sessKeyId, err := keyctl.JoinSessionKeyring(ringname) + if err != nil { + return err + } + // make session keyring searcheable + if err := keyctl.ModKeyringPerm(sessKeyId, keepperms, newperms); err != nil { + return err + } + + var console *linuxConsole + if l.config.Console != "" { + console = newConsoleFromPath(l.config.Console) + if err := console.dupStdio(); err != nil { + return err + } + } + if console != nil { + if err := system.Setctty(); err != nil { + return err + } + } + if err := setupNetwork(l.config); err != nil { + return err + } + if err := setupRoute(l.config.Config); err != nil { + return err + } + if err := setupRlimits(l.config.Rlimits); err != nil { + return err + } + + label.Init() + // InitializeMountNamespace() can be executed only for a new mount namespace + if l.config.Config.Namespaces.Contains(configs.NEWNS) { + if err := setupRootfs(l.config.Config, console, l.pipe); err != nil { + return err + } + } + if hostname := l.config.Config.Hostname; hostname != "" { + if err := syscall.Sethostname([]byte(hostname)); err != nil { + return err + } + } + if err := apparmor.ApplyProfile(l.config.AppArmorProfile); err != nil { + return err + } + if err := label.SetProcessLabel(l.config.ProcessLabel); err != nil { + return err + } + + for key, value := range l.config.Config.Sysctl { + if err := writeSystemProperty(key, value); err != nil { + return err + } + } + for _, path := range l.config.Config.ReadonlyPaths { + if err := remountReadonly(path); err != nil { + return err + } + } + for _, path := range l.config.Config.MaskPaths { + if err := maskFile(path); err != nil { + return err + } + } + pdeath, err := system.GetParentDeathSignal() + if err != nil { + return err + } + if l.config.NoNewPrivileges { + if err := system.Prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil { + return err + } + } + + // Tell our parent that we're ready to Execv. This must be done before the + // Seccomp rules have been applied, because we need to be able to read and + // write to a socket. + if err := syncParentReady(l.pipe); err != nil { + return err + } + if l.config.Config.Seccomp != nil { + if err := seccomp.InitSeccomp(l.config.Config.Seccomp); err != nil { + return err + } + } + if err := finalizeNamespace(l.config); err != nil { + return err + } + // finalizeNamespace can change user/group which clears the parent death + // signal, so we restore it here. + if err := pdeath.Restore(); err != nil { + return err + } + // compare the parent from the inital start of the init process and make sure that it did not change. + // if the parent changes that means it died and we were reparened to something else so we should + // just kill ourself and not cause problems for someone else. + if syscall.Getppid() != l.parentPid { + return syscall.Kill(syscall.Getpid(), syscall.SIGKILL) + } + + os.Exit(0) + return nil +} diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go index 24e8f714699..e4a00aa471c 100644 --- a/libcontainer/init_linux.go +++ b/libcontainer/init_linux.go @@ -22,11 +22,13 @@ import ( "github.com/vishvananda/netlink" ) +// initType is the reason we're being called - the action should we take type initType string const ( - initSetns initType = "setns" - initStandard initType = "standard" + initCreate initType = "create" // Just setting up the namespaces + initSetns initType = "setns" // Joining and existing container + initStandard initType = "standard" // Starting the main proces ) type pid struct { @@ -73,6 +75,12 @@ func newContainerInit(t initType, pipe *os.File) (initer, error) { return nil, err } switch t { + case initCreate: + return &linuxCreateInit{ + pipe: pipe, + parentPid: syscall.Getppid(), + config: config, + }, nil case initSetns: return &linuxSetnsInit{ config: config, diff --git a/libcontainer/integration/utils_test.go b/libcontainer/integration/utils_test.go index e2ca10e0082..98ef355a888 100644 --- a/libcontainer/integration/utils_test.go +++ b/libcontainer/integration/utils_test.go @@ -123,6 +123,11 @@ func runContainer(config *configs.Config, console string, args ...string) (buffe Stderr: buffers.Stderr, } + err = container.Create(process) + if err != nil { + return buffers, -1, err + } + err = container.Start(process) if err != nil { return buffers, -1, err diff --git a/libcontainer/process_linux.go b/libcontainer/process_linux.go index 9ff43861512..d48b11aff0a 100644 --- a/libcontainer/process_linux.go +++ b/libcontainer/process_linux.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "io/ioutil" "os" "os/exec" "path/filepath" @@ -256,6 +257,42 @@ func (p *initProcess) start() error { if err := p.createNetworkInterfaces(); err != nil { return newSystemError(err) } + + nss := make(map[configs.NamespaceType]string) + for _, ns := range p.container.config.Namespaces { + nss[ns.Type] = ns.GetPath(p.pid()) + } + for _, nsType := range configs.NamespaceTypes() { + if _, ok := nss[nsType]; !ok { + ns := configs.Namespace{Type: nsType} + nss[ns.Type] = ns.GetPath(p.pid()) + } + } + for key, val := range nss { + src := val + tgt := p.container.root + "/" + string(key) + + if _, err := os.Stat(tgt); err == nil { + // skip if already there + continue + } + + err = os.MkdirAll(p.container.root, 0700) + if err != nil { + return newSystemError(fmt.Errorf("Mkdir Err: %v\n", err)) + } + + err = ioutil.WriteFile(tgt, nil, 0700) + if err != nil { + return newSystemError(fmt.Errorf("Touch Err: %v\n", err)) + } + + err = syscall.Mount(src, tgt, "", syscall.MS_BIND|syscall.MS_REC, "") + if err != nil { + return newSystemError(fmt.Errorf("Mount Err(%s): %v\n", src, err)) + } + } + if err := p.sendConfig(); err != nil { return newSystemError(err) } @@ -357,6 +394,12 @@ loop: } func (p *initProcess) wait() (*os.ProcessState, error) { + defer func() { + p.container.state.transition(&stoppedState{ + c: p.container, + }) + }() + err := p.cmd.Wait() if err != nil { return p.cmd.ProcessState, err diff --git a/libcontainer/standard_init_linux.go b/libcontainer/standard_init_linux.go index 2e10150539b..add86594771 100644 --- a/libcontainer/standard_init_linux.go +++ b/libcontainer/standard_init_linux.go @@ -120,6 +120,7 @@ func (l *linuxStandardInit) Init() error { return err } } + // Tell our parent that we're ready to Execv. This must be done before the // Seccomp rules have been applied, because we need to be able to read and // write to a socket. diff --git a/libcontainer/state_linux.go b/libcontainer/state_linux.go index 9ffe15a4367..c5b52bba88a 100644 --- a/libcontainer/state_linux.go +++ b/libcontainer/state_linux.go @@ -69,13 +69,13 @@ func runPoststopHooks(c *linuxContainer) error { return nil } -// stoppedState represents a container is a stopped/destroyed state. +// stoppedState represents a container is a created/stopped/destroyed state. type stoppedState struct { c *linuxContainer } func (b *stoppedState) status() Status { - return Destroyed + return Created } func (b *stoppedState) transition(s containerState) error { diff --git a/libcontainer/state_linux_test.go b/libcontainer/state_linux_test.go index 417d9c22eda..b88c2decb40 100644 --- a/libcontainer/state_linux_test.go +++ b/libcontainer/state_linux_test.go @@ -6,7 +6,7 @@ import "testing" func TestStateStatus(t *testing.T) { states := map[containerState]Status{ - &stoppedState{}: Destroyed, + &stoppedState{}: Created, &runningState{}: Running, &restoredState{}: Running, &pausedState{}: Paused, diff --git a/main.go b/main.go index c4cb0b60be0..3058eb6b5fa 100644 --- a/main.go +++ b/main.go @@ -81,7 +81,9 @@ func main() { }, } app.Commands = []cli.Command{ + batchCommand, checkpointCommand, + createCommand, deleteCommand, eventsCommand, execCommand, @@ -91,6 +93,7 @@ func main() { pauseCommand, restoreCommand, resumeCommand, + runCommand, specCommand, startCommand, stateCommand, diff --git a/run.go b/run.go new file mode 100644 index 00000000000..ae85b56185d --- /dev/null +++ b/run.go @@ -0,0 +1,103 @@ +// +build linux + +package main + +import ( + "os" + + "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" + "github.com/coreos/go-systemd/activation" + "github.com/opencontainers/specs/specs-go" +) + +var runCommand = cli.Command{ + Name: "run", + Usage: "create and run a single command in a new container", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "bundle, b", + Value: "", + Usage: "path to the root of the bundle directory", + }, + cli.StringFlag{ + Name: "console", + Value: "", + Usage: "specify the pty slave path for use with the container", + }, + cli.BoolFlag{ + Name: "detach,d", + Usage: "detach from the container's process", + }, + cli.StringFlag{ + Name: "pid-file", + Value: "", + Usage: "specify the file to write the process id to", + }, + }, + Action: func(context *cli.Context) { + id := context.Args().First() + if id == "" { + fatal(errEmptyID) + } + + bundle := context.String("bundle") + if bundle != "" { + if err := os.Chdir(bundle); err != nil { + fatal(err) + } + } + + spec, err := loadSpec(specConfig) + if err != nil { + fatal(err) + } + + notifySocket := os.Getenv("NOTIFY_SOCKET") + if notifySocket != "" { + setupSdNotify(spec, notifySocket) + } + + if os.Geteuid() != 0 { + logrus.Fatal("runc should be run as root") + } + + status, err := runContainer(context, id, spec, context.Args()[1:]) + if err != nil { + logrus.Fatalf("Container start failed: %v", err) + } + + // exit with the container's exit status so any external supervisor is + // notified of the exit with the correct exit status. + os.Exit(status) + }, +} + +func runContainer(context *cli.Context, id string, spec *specs.Spec, args []string) (int, error) { + container, err := createContainer(context, id, spec) + if err != nil { + return -1, err + } + + detach := context.Bool("detach") + if !detach { + defer deleteContainer(container) // delete when process dies + } + + // Support on-demand socket activation by passing file descriptors into the container init process. + listenFDs := []*os.File{} + + if os.Getenv("LISTEN_FDS") != "" { + listenFDs = activation.Files(false) + } + + proc := specs.Process{ + Terminal: spec.Process.Terminal, + User: spec.Process.User, + Args: args, + Env: spec.Process.Env, + Cwd: spec.Process.Cwd, + } + + return runProcess(container, &proc, listenFDs, context.String("console"), context.String("pid-file"), detach) +} diff --git a/start.go b/start.go index 7e268d02466..e0e343e8064 100644 --- a/start.go +++ b/start.go @@ -98,28 +98,18 @@ func startContainer(context *cli.Context, spec *specs.Spec) (int, error) { if id == "" { return -1, errEmptyID } - container, err := createContainer(context, id, spec) + detach := context.Bool("detach") + + container, err := getContainer(context) if err != nil { return -1, err } - // ensure that the container is always removed if we were the process - // that created it. - detach := context.Bool("detach") - if !detach { - defer destroy(container) - } - // Support on-demand socket activation by passing file descriptors into the container init process. listenFDs := []*os.File{} if os.Getenv("LISTEN_FDS") != "" { listenFDs = activation.Files(false) } - status, err := runProcess(container, &spec.Process, listenFDs, context.String("console"), context.String("pid-file"), detach) - if err != nil { - destroy(container) - return -1, err - } - return status, nil + return runProcess(container, &spec.Process, listenFDs, context.String("console"), context.String("pid-file"), detach) } diff --git a/utils.go b/utils.go index 5956a6cc74a..fa16ea507eb 100644 --- a/utils.go +++ b/utils.go @@ -300,10 +300,29 @@ func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcont if err != nil { return nil, err } - return factory.Create(id, config) + container, err := factory.Create(id, config) + if err != nil { + return nil, err + } + + proc, err := newProcess(spec.Process) + if err != nil { + return nil, err + } + + if err := container.Create(proc); err != nil { + return nil, err + } + return container, nil +} + +func deleteContainer(container libcontainer.Container) { + if err := container.Destroy(); err != nil { + logrus.Error(err) + } } -// runProcess will create a new process in the specified container +// runProcess will create a new process (PID ns) in the specified container // by executing the process specified in the 'config'. func runProcess(container libcontainer.Container, config *specs.Process, listenFDs []*os.File, console string, pidFile string, detach bool) (int, error) { process, err := newProcess(*config)