Merge pull request #525 from crosbymichael/exec
Load process.json for exec and add detach
This commit is contained in:
commit
4f601205d4
111
exec.go
111
exec.go
|
@ -3,14 +3,17 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/opencontainers/specs"
|
||||
)
|
||||
|
||||
var execCommand = cli.Command{
|
||||
|
@ -37,15 +40,25 @@ var execCommand = cli.Command{
|
|||
Name: "user, u",
|
||||
Usage: "UID (format: <uid>[:<gid>])",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "process,p",
|
||||
Usage: "path to the process.json",
|
||||
},
|
||||
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) {
|
||||
if len(context.Args()) == 0 {
|
||||
logrus.Fatal("pass the command")
|
||||
}
|
||||
if os.Geteuid() != 0 {
|
||||
logrus.Fatal("runc should be run as root")
|
||||
}
|
||||
status, err := execProcess(context, context.Args())
|
||||
status, err := execProcess(context)
|
||||
if err != nil {
|
||||
logrus.Fatalf("exec failed: %v", err)
|
||||
}
|
||||
|
@ -53,72 +66,96 @@ var execCommand = cli.Command{
|
|||
},
|
||||
}
|
||||
|
||||
func execProcess(context *cli.Context, args []string) (int, error) {
|
||||
func execProcess(context *cli.Context) (int, error) {
|
||||
container, err := getContainer(context)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
bundle := container.Config().Rootfs
|
||||
if err := os.Chdir(path.Dir(bundle)); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
spec, _, err := loadSpec(specConfig, runtimeConfig)
|
||||
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)
|
||||
}
|
||||
|
||||
func getProcess(context *cli.Context, bundle string) (*specs.Process, error) {
|
||||
if path := context.String("process"); path != "" {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
var p specs.Process
|
||||
if err := json.NewDecoder(f).Decode(&p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
// process via cli flags
|
||||
if err := os.Chdir(bundle); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spec, _, err := loadSpec(specConfig, runtimeConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := spec.Process
|
||||
|
||||
p.Args = context.Args()
|
||||
// override the cwd, if passed
|
||||
if context.String("cwd") != "" {
|
||||
p.Cwd = context.String("cwd")
|
||||
}
|
||||
|
||||
// append the passed env variables
|
||||
for _, e := range context.StringSlice("env") {
|
||||
p.Env = append(p.Env, e)
|
||||
}
|
||||
|
||||
// set the tty
|
||||
if context.IsSet("tty") {
|
||||
p.Terminal = context.Bool("tty")
|
||||
}
|
||||
|
||||
// override the user, if passed
|
||||
if context.String("user") != "" {
|
||||
u := strings.SplitN(context.String("user"), ":", 2)
|
||||
if len(u) > 1 {
|
||||
gid, err := strconv.Atoi(u[1])
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("parsing %s as int for gid failed: %v", u[1], err)
|
||||
return nil, fmt.Errorf("parsing %s as int for gid failed: %v", u[1], err)
|
||||
}
|
||||
p.User.GID = uint32(gid)
|
||||
}
|
||||
uid, err := strconv.Atoi(u[0])
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("parsing %s as int for uid failed: %v", u[0], err)
|
||||
return nil, fmt.Errorf("parsing %s as int for uid failed: %v", u[0], err)
|
||||
}
|
||||
p.User.UID = uint32(uid)
|
||||
}
|
||||
|
||||
process := newProcess(p)
|
||||
process.Args = args
|
||||
rootuid, err := container.Config().HostUID()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
tty, err := newTty(p.Terminal, process, rootuid, context.String("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)
|
||||
return &p, nil
|
||||
}
|
||||
|
|
35
restore.go
35
restore.go
|
@ -5,6 +5,7 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
|
@ -53,6 +54,15 @@ var restoreCommand = cli.Command{
|
|||
Value: "",
|
||||
Usage: "path to the root of the bundle directory",
|
||||
},
|
||||
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) {
|
||||
imagePath := context.String("image-path")
|
||||
|
@ -108,21 +118,30 @@ func restoreContainer(context *cli.Context, spec *specs.LinuxSpec, config *confi
|
|||
|
||||
// ensure that the container is always removed if we were the process
|
||||
// that created it.
|
||||
defer destroy(container)
|
||||
process := &libcontainer.Process{
|
||||
Stdin: os.Stdin,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
detach := context.Bool("detach")
|
||||
if !detach {
|
||||
defer destroy(container)
|
||||
}
|
||||
tty, err := newTty(spec.Process.Terminal, process, rootuid, "")
|
||||
process := &libcontainer.Process{}
|
||||
tty, err := setupIO(process, rootuid, "", false, detach)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
handler := newSignalHandler(tty)
|
||||
defer handler.Close()
|
||||
if err := container.Restore(process, options); 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)
|
||||
}
|
||||
|
||||
|
|
68
start.go
68
start.go
|
@ -132,38 +132,19 @@ func startContainer(context *cli.Context, spec *specs.LinuxSpec, rspec *specs.Li
|
|||
process.ExtraFiles = append(process.ExtraFiles, os.NewFile(uintptr(i), ""))
|
||||
}
|
||||
}
|
||||
var tty *tty
|
||||
if spec.Process.Terminal {
|
||||
if tty, err = createTty(process, rootuid, context.String("console")); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
} else if detach {
|
||||
if err := dupStdio(process, rootuid); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
} else {
|
||||
if tty, err = createStdioPipes(process, rootuid); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
tty, err := setupIO(process, rootuid, context.String("console"), spec.Process.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 != "" {
|
||||
pid, err := process.Pid()
|
||||
if err != nil {
|
||||
if err := createPidFile(pidFile, process); err != nil {
|
||||
process.Signal(syscall.SIGKILL)
|
||||
process.Wait()
|
||||
return -1, err
|
||||
}
|
||||
f, err := os.Create(pidFile)
|
||||
if err != nil {
|
||||
logrus.WithField("pid", pid).Error("create pid file")
|
||||
} else {
|
||||
_, err = fmt.Fprintf(f, "%d", pid)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Error("write pid file")
|
||||
}
|
||||
}
|
||||
}
|
||||
if detach {
|
||||
return 0, nil
|
||||
|
@ -172,40 +153,3 @@ func startContainer(context *cli.Context, spec *specs.LinuxSpec, rspec *specs.Li
|
|||
defer handler.Close()
|
||||
return handler.forward(process)
|
||||
}
|
||||
|
||||
func dupStdio(process *libcontainer.Process, rootuid int) error {
|
||||
process.Stdin = os.Stdin
|
||||
process.Stdout = os.Stdout
|
||||
process.Stderr = os.Stderr
|
||||
for _, fd := range []uintptr{
|
||||
os.Stdin.Fd(),
|
||||
os.Stdout.Fd(),
|
||||
os.Stderr.Fd(),
|
||||
} {
|
||||
if err := syscall.Fchown(int(fd), rootuid, rootuid); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If systemd is supporting sd_notify protocol, this function will add support
|
||||
// for sd_notify protocol from within the container.
|
||||
func setupSdNotify(spec *specs.LinuxSpec, rspec *specs.LinuxRuntimeSpec, notifySocket string) {
|
||||
mountName := "sdNotify"
|
||||
spec.Mounts = append(spec.Mounts, specs.MountPoint{Name: mountName, Path: notifySocket})
|
||||
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", notifySocket))
|
||||
rspec.Mounts[mountName] = specs.Mount{Type: "bind", Source: notifySocket, Options: []string{"bind"}}
|
||||
}
|
||||
|
||||
// If systemd is supporting on-demand socket activation, this function will add support
|
||||
// for on-demand socket activation for the containerized service.
|
||||
func setupSocketActivation(spec *specs.LinuxSpec, listenFds string) {
|
||||
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("LISTEN_FDS=%s", listenFds), "LISTEN_PID=1")
|
||||
}
|
||||
|
||||
func destroy(container libcontainer.Container) {
|
||||
if err := container.Destroy(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
|
|
5
tty.go
5
tty.go
|
@ -35,7 +35,10 @@ func createStdioPipes(p *libcontainer.Process, rootuid int) (*tty, error) {
|
|||
i.Stderr,
|
||||
},
|
||||
}
|
||||
go io.Copy(i.Stdin, os.Stdin)
|
||||
go func() {
|
||||
io.Copy(i.Stdin, os.Stdin)
|
||||
i.Stdin.Close()
|
||||
}()
|
||||
go io.Copy(os.Stdout, i.Stdout)
|
||||
go io.Copy(os.Stderr, i.Stderr)
|
||||
return t, nil
|
||||
|
|
71
utils.go
71
utils.go
|
@ -6,7 +6,9 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/opencontainers/runc/libcontainer"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
|
@ -154,3 +156,72 @@ func newProcess(p specs.Process) *libcontainer.Process {
|
|||
Cwd: p.Cwd,
|
||||
}
|
||||
}
|
||||
|
||||
func dupStdio(process *libcontainer.Process, rootuid int) error {
|
||||
process.Stdin = os.Stdin
|
||||
process.Stdout = os.Stdout
|
||||
process.Stderr = os.Stderr
|
||||
for _, fd := range []uintptr{
|
||||
os.Stdin.Fd(),
|
||||
os.Stdout.Fd(),
|
||||
os.Stderr.Fd(),
|
||||
} {
|
||||
if err := syscall.Fchown(int(fd), rootuid, rootuid); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If systemd is supporting sd_notify protocol, this function will add support
|
||||
// for sd_notify protocol from within the container.
|
||||
func setupSdNotify(spec *specs.LinuxSpec, rspec *specs.LinuxRuntimeSpec, notifySocket string) {
|
||||
mountName := "sdNotify"
|
||||
spec.Mounts = append(spec.Mounts, specs.MountPoint{Name: mountName, Path: notifySocket})
|
||||
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", notifySocket))
|
||||
rspec.Mounts[mountName] = specs.Mount{Type: "bind", Source: notifySocket, Options: []string{"bind"}}
|
||||
}
|
||||
|
||||
// If systemd is supporting on-demand socket activation, this function will add support
|
||||
// for on-demand socket activation for the containerized service.
|
||||
func setupSocketActivation(spec *specs.LinuxSpec, listenFds string) {
|
||||
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("LISTEN_FDS=%s", listenFds), "LISTEN_PID=1")
|
||||
}
|
||||
|
||||
func destroy(container libcontainer.Container) {
|
||||
if err := container.Destroy(); err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupIO(process *libcontainer.Process, rootuid int, console string, createTTY, detach bool) (*tty, error) {
|
||||
// detach and createTty will not work unless a console path is passed
|
||||
// so error out here before changing any terminal settings
|
||||
if createTTY && detach && console == "" {
|
||||
return nil, fmt.Errorf("cannot allocate tty if runc will detach")
|
||||
}
|
||||
if createTTY {
|
||||
return createTty(process, rootuid, console)
|
||||
}
|
||||
if detach {
|
||||
if err := dupStdio(process, rootuid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
return createStdioPipes(process, rootuid)
|
||||
}
|
||||
|
||||
func createPidFile(path string, process *libcontainer.Process) error {
|
||||
pid, err := process.Pid()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = fmt.Fprintf(f, "%d", pid)
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue