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 Jan 25, 2016
1 parent 3268a1e commit 65dd507
Show file tree
Hide file tree
Showing 19 changed files with 714 additions and 63 deletions.
134 changes: 134 additions & 0 deletions batch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// +build linux

package main

import (
"bufio"
"fmt"
"os"
"strconv"
"strings"

"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"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, rspec, err := loadSpec(context.String("config-file"), context.String("runtime-file"))
if err != nil {
fatal(err)
}

batchFilename := context.Args().First()
if batchFilename == "" {
fatal(fmt.Errorf("Missing batch-file-name"))
}

notifySocket := os.Getenv("NOTIFY_SOCKET")
if notifySocket != "" {
setupSdNotify(spec, rspec, notifySocket)
}

listenFds := os.Getenv("LISTEN_FDS")
listenPid := os.Getenv("LISTEN_PID")

if listenFds != "" && listenPid == strconv.Itoa(os.Getpid()) {
setupSocketActivation(spec, listenFds)
}

if os.Geteuid() != 0 {
logrus.Fatal("runc should be run as root")
}
status, err := batchContainer(context, spec, rspec, 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, spec *specs.LinuxSpec, rspec *specs.LinuxRuntimeSpec, 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, spec, rspec)
if err != nil {
return -1, err
}
defer deleteContainer(container)

// Support on-demand socket activation by passing file descriptors into the container init process.
extraFDs := []int{}

if fds := os.Getenv("LISTEN_FDS"); fds != "" {
listenFdsInt, err := strconv.Atoi(fds)
if err != nil {
return -1, err
}

for i := 0; i < listenFdsInt; i++ {
extraFDs = append(extraFDs, SD_LISTEN_FDS_START+i)
}
}

// 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, extraFDs, context.String("console"))
if rc != 0 || err != nil {
// For now just stop on first error
return rc, nil
}
}

// All is well
return 0, nil
}
81 changes: 81 additions & 0 deletions container.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// +build linux

package main

import (
"fmt"
"os"

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

func createContainer(context *cli.Context, spec *specs.LinuxSpec, rspec *specs.LinuxRuntimeSpec) (libcontainer.Container, error) {
config, err := createLibcontainerConfig(context.GlobalString("id"), spec, rspec)
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(context.GlobalString("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, extraFDs []int, console string) (int, error) {
process := newProcess(*config)

// Add extra file descriptors if needed
for _, i := range extraFDs {
process.ExtraFiles = append(process.ExtraFiles, os.NewFile(uintptr(i), ""))
}

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

tty, err := newTty(config.Terminal, process, rootuid, console)
if err != nil {
return -1, err
}
handler := newSignalHandler(tty)
defer handler.Close()
if err := container.Start(process); err != nil {
return -1, err
}
return handler.forward(process)
}
52 changes: 52 additions & 0 deletions create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// +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) {
bundle := context.String("bundle")
if bundle != "" {
if err := os.Chdir(bundle); err != nil {
fatal(err)
}
}
spec, rspec, err := loadSpec(specConfig, runtimeConfig)
if err != nil {
fatal(err)
}

notifySocket := os.Getenv("NOTIFY_SOCKET")
if notifySocket != "" {
setupSdNotify(spec, rspec, notifySocket)
}

if os.Geteuid() != 0 {
logrus.Fatal("runc should be run as root")
}
_, err = createContainer(context, spec, rspec)
if err != nil {
logrus.Fatalf("Container create failed: %v", err)
}
},
}
27 changes: 27 additions & 0 deletions delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// +build linux

package main

import (
"os"

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

var deleteCommand = cli.Command{
Name: "delete",
Usage: "delete a container",
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 {
logrus.Fatalf("Container delete failed: %v", err)
os.Exit(-1)
}
deleteContainer(container)
},
}
17 changes: 6 additions & 11 deletions exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

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

Expand Down Expand Up @@ -46,22 +47,16 @@ func execProcess(context *cli.Context, config *specs.Process) (int, error) {
if err != nil {
return -1, err
}
process := newProcess(*config)

rootuid, err := container.Config().HostUID()
status, err := container.Status()
if err != nil {
return -1, err
}
tty, err := newTty(config.Terminal, process, rootuid, context.String("console"))
if err != nil {
return -1, err
if status != libcontainer.Running {
return -1, fmt.Errorf("Container not running")
}
handler := newSignalHandler(tty)
defer handler.Close()
if err := container.Start(process); err != nil {
return -1, err
}
return handler.forward(process)

return runProcess(container, config, nil, context.String("console"))
}

// loadProcessConfig loads the process configuration from the provided path.
Expand Down
10 changes: 9 additions & 1 deletion libcontainer/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ import (
type Status int

const (
// The container exists but is not running.
Created Status = iota + 1

// The container exists and is running.
Running Status = iota + 1
Running

// The container exists, it is in the process of being paused.
Pausing
Expand All @@ -32,6 +35,8 @@ const (

func (s Status) String() string {
switch s {
case Created:
return "created"
case Running:
return "running"
case Pausing:
Expand Down Expand Up @@ -113,6 +118,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 65dd507

Please sign in to comment.