Skip to content

Commit

Permalink
Split container.Start() into create() and runProcess()
Browse files Browse the repository at this point in the history
While in there, to show why someone may want it, I also added support for:
runc run cmd args...
runc batch batchFileOfCommands | -

Signed-off-by: Doug Davis <[email protected]>
  • Loading branch information
Doug Davis committed Feb 9, 2016
1 parent bfd3345 commit c4eaa7c
Show file tree
Hide file tree
Showing 19 changed files with 717 additions and 97 deletions.
125 changes: 125 additions & 0 deletions batch.go
Original file line number Diff line number Diff line change
@@ -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"
)

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.LinuxSpec, 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
}
96 changes: 96 additions & 0 deletions container.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// +build linux

package main

import (
"fmt"
"os"
"syscall"

"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/specs"
)

func createContainer(context *cli.Context, id string, spec *specs.LinuxSpec) (libcontainer.Container, error) {
config, err := createLibcontainerConfig(id, spec)
if err != nil {
return nil, err
}

if _, err := os.Stat(config.Rootfs); err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("rootfs (%q) does not exist", config.Rootfs)
}
return nil, err
}

factory, err := loadFactory(context)
if err != nil {
return nil, err
}
container, err := factory.Create(id, config)
if err != nil {
return nil, err
}

if err := container.Create(newProcess(spec.Process)); err != nil {
return nil, err
}

return container, nil
}

func deleteContainer(container libcontainer.Container) {
status, err := container.Status()
if err != nil {
logrus.Error(err)
}
if status != libcontainer.Checkpointed {
if err := container.Destroy(); err != nil {
logrus.Error(err)
}
}
}

// 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 := newProcess(*config)

// Add extra file descriptors if needed
if len(listenFDs) > 0 {
process.Env = append(process.Env, fmt.Sprintf("LISTEN_FDS=%d", len(listenFDs)), "LISTEN_PID=1")
process.ExtraFiles = append(process.ExtraFiles, listenFDs...)
}

rootuid, err := container.Config().HostUID()
if err != nil {
return -1, err
}

tty, err := setupIO(process, rootuid, console, config.Terminal, detach)
if err != nil {
return -1, err
}

if err := container.Start(process); err != nil {
return -1, err
}

if pidFile != "" {
if err := createPidFile(pidFile, process); err != nil {
process.Signal(syscall.SIGKILL)
process.Wait()
return -1, err
}
}
if detach {
return 0, nil
}
handler := newSignalHandler(tty)
defer handler.Close()

return handler.forward(process)
}
57 changes: 57 additions & 0 deletions create.go
Original file line number Diff line number Diff line change
@@ -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)
}
},
}
16 changes: 13 additions & 3 deletions delete.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
package main

import "github.com/codegangsta/cli"
import (
"os"

"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
)

var deleteCommand = cli.Command{
Name: "delete",
Usage: "delete any resources held by the container often used with detached containers",
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)
},
}
40 changes: 14 additions & 26 deletions exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import (
"path"
"strconv"
"strings"
"syscall"

"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/specs"
)

Expand Down Expand Up @@ -71,39 +71,26 @@ func execProcess(context *cli.Context) (int, error) {
if err != nil {
return -1, err
}

status, err := container.Status()
if err != nil {
return -1, err
}
if status != libcontainer.Running {
return -1, fmt.Errorf("Container not running")
}

var (
detach = context.Bool("detach")
rootfs = container.Config().Rootfs
)
rootuid, err := container.Config().HostUID()
if err != nil {
return -1, err
}

p, err := getProcess(context, path.Dir(rootfs))
if err != nil {
return -1, err
}
process := newProcess(*p)
tty, err := setupIO(process, rootuid, context.String("console"), p.Terminal, detach)
if err != nil {
return -1, err
}
if err := container.Start(process); err != nil {
return -1, err
}
if pidFile := context.String("pid-file"); pidFile != "" {
if err := createPidFile(pidFile, process); err != nil {
process.Signal(syscall.SIGKILL)
process.Wait()
return -1, err
}
}
if detach {
return 0, nil
}
handler := newSignalHandler(tty)
defer handler.Close()
return handler.forward(process)

return runProcess(container, p, nil, context.String("console"), context.String("pid-file"), detach)
}

func getProcess(context *cli.Context, bundle string) (*specs.Process, error) {
Expand All @@ -129,6 +116,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")
Expand Down
3 changes: 3 additions & 0 deletions libcontainer/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,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.
//
Expand Down
Loading

0 comments on commit c4eaa7c

Please sign in to comment.