Improve execin to support registering funcs
This also changes the functionality of the default exec in to just be an existing func that is called than handles the implementation to exec a user user's process inside the container. This implements this functionallity in nsinit but is a base for how we will be handling these types of features inside docker. Signed-off-by: Michael Crosby <michael@docker.com>
This commit is contained in:
parent
51e6049226
commit
70367b2cf3
|
@ -3,6 +3,7 @@
|
|||
package namespaces
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
@ -18,10 +19,10 @@ import (
|
|||
|
||||
// ExecIn reexec's the initPath with the argv 0 rewrite to "nsenter" so that it is able to run the
|
||||
// setns code in a single threaded environment joining the existing containers' namespaces.
|
||||
func ExecIn(container *libcontainer.Config, state *libcontainer.State, userArgs []string, initPath string,
|
||||
func ExecIn(container *libcontainer.Config, state *libcontainer.State, userArgs []string, initPath, action string,
|
||||
stdin io.Reader, stdout, stderr io.Writer, console string, startCallback func(*exec.Cmd)) (int, error) {
|
||||
|
||||
args := []string{"nsenter", "--nspid", strconv.Itoa(state.InitPid)}
|
||||
args := []string{fmt.Sprintf("nsenter-%s", action), "--nspid", strconv.Itoa(state.InitPid)}
|
||||
|
||||
if console != "" {
|
||||
args = append(args, "--console", console)
|
||||
|
|
|
@ -36,7 +36,7 @@ func execAction(context *cli.Context) {
|
|||
}
|
||||
|
||||
if state != nil {
|
||||
exitCode, err = startInExistingContainer(container, state, context)
|
||||
exitCode, err = startInExistingContainer(container, state, "exec", context)
|
||||
} else {
|
||||
exitCode, err = startContainer(container, dataPath, []string(context.Args()))
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ func execAction(context *cli.Context) {
|
|||
// with the nsenter argument so that the C code can setns an the namespaces that we require. Then that
|
||||
// code path will drop us into the path that we can do the final setup of the namespace and exec the users
|
||||
// application.
|
||||
func startInExistingContainer(config *libcontainer.Config, state *libcontainer.State, context *cli.Context) (int, error) {
|
||||
func startInExistingContainer(config *libcontainer.Config, state *libcontainer.State, action string, context *cli.Context) (int, error) {
|
||||
var (
|
||||
master *os.File
|
||||
console string
|
||||
|
@ -102,7 +102,7 @@ func startInExistingContainer(config *libcontainer.Config, state *libcontainer.S
|
|||
}()
|
||||
}
|
||||
|
||||
return namespaces.ExecIn(config, state, context.Args(), os.Args[0], stdin, stdout, stderr, console, startCallback)
|
||||
return namespaces.ExecIn(config, state, context.Args(), os.Args[0], action, stdin, stdout, stderr, console, startCallback)
|
||||
}
|
||||
|
||||
// startContainer starts the container. Returns the exit status or -1 and an
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/docker/libcontainer"
|
||||
)
|
||||
|
||||
var execFuncCommand = cli.Command{
|
||||
Name: "func",
|
||||
Usage: "execute a registered function inside an existing container",
|
||||
Action: execFuncAction,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{Name: "list", Usage: "list all registered functions"},
|
||||
cli.StringFlag{Name: "func", Usage: "function name to exec inside a container"},
|
||||
},
|
||||
}
|
||||
|
||||
func execFuncAction(context *cli.Context) {
|
||||
if context.Bool("list") {
|
||||
w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
|
||||
fmt.Fprint(w, "NAME\tUSAGE\n")
|
||||
|
||||
for k, f := range argvs {
|
||||
fmt.Fprintf(w, "%s\t%s\n", k, f.Usage)
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var exitCode int
|
||||
|
||||
config, err := loadContainer()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// FIXME: remove tty from container config, this should be per process
|
||||
config.Tty = false
|
||||
|
||||
state, err := libcontainer.GetState(dataPath)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to read state.json: %s", err)
|
||||
}
|
||||
|
||||
exitCode, err = startInExistingContainer(config, state, context.String("func"), context)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
os.Exit(exitCode)
|
||||
}
|
|
@ -9,11 +9,19 @@ import (
|
|||
|
||||
var (
|
||||
logPath = os.Getenv("log")
|
||||
argvs = make(map[string]func())
|
||||
argvs = make(map[string]*rFunc)
|
||||
)
|
||||
|
||||
func init() {
|
||||
argvs["nsenter"] = nsenter
|
||||
argvs["nsenter-exec"] = &rFunc{
|
||||
Usage: "execute a process inside an existing container",
|
||||
Action: nsenterExec,
|
||||
}
|
||||
|
||||
argvs["nsenter-mknod"] = &rFunc{
|
||||
Usage: "mknod a device inside an existing container",
|
||||
Action: nsenterMknod,
|
||||
}
|
||||
}
|
||||
|
||||
func preload(context *cli.Context) error {
|
||||
|
@ -26,13 +34,23 @@ func preload(context *cli.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func runFunc(f *rFunc) {
|
||||
userArgs := findUserArgs()
|
||||
|
||||
config, err := loadConfigFromFd()
|
||||
if err != nil {
|
||||
log.Fatalf("unable to receive config from sync pipe: %s", err)
|
||||
}
|
||||
|
||||
f.Action(config, userArgs)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// we need to check our argv 0 for any registred functions to run instead of the
|
||||
// normal cli code path
|
||||
|
||||
action, exists := argvs[os.Args[0]]
|
||||
f, exists := argvs[os.Args[0]]
|
||||
if exists {
|
||||
action()
|
||||
runFunc(f)
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -56,6 +74,7 @@ func main() {
|
|||
configCommand,
|
||||
pauseCommand,
|
||||
unpauseCommand,
|
||||
execFuncCommand,
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
|
|
|
@ -2,41 +2,50 @@ package main
|
|||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/devices"
|
||||
"github.com/docker/libcontainer/mount/nodes"
|
||||
"github.com/docker/libcontainer/namespaces"
|
||||
_ "github.com/docker/libcontainer/namespaces/nsenter"
|
||||
"github.com/docker/libcontainer/syncpipe"
|
||||
)
|
||||
|
||||
func findUserArgs() []string {
|
||||
i := 0
|
||||
for _, a := range os.Args {
|
||||
i++
|
||||
|
||||
if a == "--" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return os.Args[i:]
|
||||
}
|
||||
|
||||
// this expects that we already have our namespaces setup by the C initializer
|
||||
// we are expected to finalize the namespace and exec the user's application
|
||||
func nsenter() {
|
||||
syncPipe, err := syncpipe.NewSyncPipeFromFd(0, 3)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to create sync pipe: %s", err)
|
||||
}
|
||||
|
||||
var config *libcontainer.Config
|
||||
if err := syncPipe.ReadFromParent(&config); err != nil {
|
||||
log.Fatalf("reading container config from parent: %s", err)
|
||||
}
|
||||
|
||||
if err := namespaces.FinalizeSetns(config, findUserArgs()); err != nil {
|
||||
// nsenterExec exec's a process inside an existing container
|
||||
func nsenterExec(config *libcontainer.Config, args []string) {
|
||||
if err := namespaces.FinalizeSetns(config, args); err != nil {
|
||||
log.Fatalf("failed to nsenter: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// nsenterMknod runs mknod inside an existing container
|
||||
//
|
||||
// mknod <path> <type> <major> <minor>
|
||||
func nsenterMknod(config *libcontainer.Config, args []string) {
|
||||
if len(args) != 4 {
|
||||
log.Fatalf("expected mknod to have 4 arguments not %d", len(args))
|
||||
}
|
||||
|
||||
t := rune(args[1][0])
|
||||
|
||||
major, err := strconv.Atoi(args[2])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
minor, err := strconv.Atoi(args[3])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
n := &devices.Device{
|
||||
Path: args[0],
|
||||
Type: t,
|
||||
MajorNumber: int64(major),
|
||||
MinorNumber: int64(minor),
|
||||
}
|
||||
|
||||
if err := nodes.CreateDeviceNode("/", n); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,15 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/docker/libcontainer"
|
||||
"github.com/docker/libcontainer/syncpipe"
|
||||
)
|
||||
|
||||
// rFunc is a function registration for calling after an execin
|
||||
type rFunc struct {
|
||||
Usage string
|
||||
Action func(*libcontainer.Config, []string)
|
||||
}
|
||||
|
||||
func loadContainer() (*libcontainer.Config, error) {
|
||||
f, err := os.Open(filepath.Join(dataPath, "container.json"))
|
||||
if err != nil {
|
||||
|
@ -44,3 +51,32 @@ func loadContainerFromJson(rawData string) (*libcontainer.Config, error) {
|
|||
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func findUserArgs() []string {
|
||||
i := 0
|
||||
for _, a := range os.Args {
|
||||
i++
|
||||
|
||||
if a == "--" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return os.Args[i:]
|
||||
}
|
||||
|
||||
// loadConfigFromFd loads a container's config from the sync pipe that is provided by
|
||||
// fd 3 when running a process
|
||||
func loadConfigFromFd() (*libcontainer.Config, error) {
|
||||
syncPipe, err := syncpipe.NewSyncPipeFromFd(0, 3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config *libcontainer.Config
|
||||
if err := syncPipe.ReadFromParent(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue