Merge pull request #155 from crosbymichael/execin-func

Implement execin by using registered functions
This commit is contained in:
Michael Crosby 2014-08-12 18:09:29 -07:00
commit 1befa2fe9e
10 changed files with 177 additions and 72 deletions

View File

@ -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)

View File

@ -1,4 +1,4 @@
package nsinit
package main
import (
"encoding/json"
@ -15,7 +15,7 @@ var configCommand = cli.Command{
}
func configAction(context *cli.Context) {
container, err := loadContainer()
container, err := loadConfig()
if err != nil {
log.Fatal(err)
}

View File

@ -1,4 +1,4 @@
package nsinit
package main
import (
"fmt"
@ -8,6 +8,7 @@ import (
"os/exec"
"os/signal"
"syscall"
"text/tabwriter"
"github.com/codegangsta/cli"
"github.com/docker/docker/pkg/term"
@ -20,12 +21,29 @@ var execCommand = cli.Command{
Name: "exec",
Usage: "execute a new command inside a container",
Action: execAction,
Flags: []cli.Flag{
cli.BoolFlag{Name: "list", Usage: "list all registered exec functions"},
cli.StringFlag{Name: "func", Value: "exec", Usage: "function name to exec inside a container"},
},
}
func execAction(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
container, err := loadContainer()
container, err := loadConfig()
if err != nil {
log.Fatal(err)
}
@ -36,7 +54,7 @@ func execAction(context *cli.Context) {
}
if state != nil {
exitCode, err = startInExistingContainer(container, state, context)
exitCode, err = startInExistingContainer(container, state, context.String("func"), context)
} else {
exitCode, err = startContainer(container, dataPath, []string(context.Args()))
}
@ -52,7 +70,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 +120,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

View File

@ -1,4 +1,4 @@
package nsinit
package main
import (
"log"
@ -26,7 +26,7 @@ var (
func initAction(context *cli.Context) {
runtime.LockOSThread()
container, err := loadContainer()
container, err := loadConfig()
if err != nil {
log.Fatal(err)
}

View File

@ -1,38 +1,41 @@
package nsinit
package main
import (
"log"
"os"
"strings"
"github.com/codegangsta/cli"
)
var (
logPath = os.Getenv("log")
argvs = make(map[string]func())
argvs = make(map[string]*rFunc)
)
func init() {
argvs["nsenter"] = nsenter
}
func preload(context *cli.Context) error {
if logPath != "" {
if err := openLog(logPath); err != nil {
return err
}
argvs["exec"] = &rFunc{
Usage: "execute a process inside an existing container",
Action: nsenterExec,
}
return nil
argvs["mknod"] = &rFunc{
Usage: "mknod a device inside an existing container",
Action: nsenterMknod,
}
argvs["ip"] = &rFunc{
Usage: "display the container's network interfaces",
Action: nsenterIp,
}
}
func NsInit() {
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[strings.TrimPrefix(os.Args[0], "nsenter-")]
if exists {
action()
runFunc(f)
return
}

View File

@ -1,42 +1,84 @@
package nsinit
package main
import (
"fmt"
"log"
"net"
"os"
"strconv"
"strings"
"text/tabwriter"
"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)
}
}
// nsenterIp displays the network interfaces inside a container's net namespace
func nsenterIp(config *libcontainer.Config, args []string) {
interfaces, err := net.Interfaces()
if err != nil {
log.Fatal(err)
}
w := tabwriter.NewWriter(os.Stdout, 10, 1, 3, ' ', 0)
fmt.Fprint(w, "NAME\tMTU\tMAC\tFLAG\tADDRS\n")
for _, iface := range interfaces {
addrs, err := iface.Addrs()
if err != nil {
log.Fatal(err)
}
o := []string{}
for _, a := range addrs {
o = append(o, a.String())
}
fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\n", iface.Name, iface.MTU, iface.HardwareAddr, iface.Flags, strings.Join(o, ","))
}
w.Flush()
}

View File

@ -1,7 +0,0 @@
package main
import "github.com/docker/libcontainer/nsinit"
func main() {
nsinit.NsInit()
}

View File

@ -1,4 +1,4 @@
package nsinit
package main
import (
"log"
@ -34,7 +34,7 @@ func unpauseAction(context *cli.Context) {
}
func toggle(state cgroups.FreezerState) error {
container, err := loadContainer()
container, err := loadConfig()
if err != nil {
return err
}

View File

@ -1,4 +1,4 @@
package nsinit
package main
import (
"encoding/json"
@ -16,7 +16,7 @@ var statsCommand = cli.Command{
}
func statsAction(context *cli.Context) {
container, err := loadContainer()
container, err := loadConfig()
if err != nil {
log.Fatal(err)
}

View File

@ -1,4 +1,4 @@
package nsinit
package main
import (
"encoding/json"
@ -6,10 +6,18 @@ import (
"os"
"path/filepath"
"github.com/codegangsta/cli"
"github.com/docker/libcontainer"
"github.com/docker/libcontainer/syncpipe"
)
func loadContainer() (*libcontainer.Config, error) {
// rFunc is a function registration for calling after an execin
type rFunc struct {
Usage string
Action func(*libcontainer.Config, []string)
}
func loadConfig() (*libcontainer.Config, error) {
f, err := os.Open(filepath.Join(dataPath, "container.json"))
if err != nil {
return nil, err
@ -35,12 +43,52 @@ func openLog(name string) error {
return nil
}
func loadContainerFromJson(rawData string) (*libcontainer.Config, error) {
var container *libcontainer.Config
func findUserArgs() []string {
i := 0
for _, a := range os.Args {
i++
if err := json.Unmarshal([]byte(rawData), &container); err != nil {
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
}
return container, nil
var config *libcontainer.Config
if err := syncPipe.ReadFromParent(&config); err != nil {
return nil, err
}
return config, nil
}
func preload(context *cli.Context) error {
if logPath != "" {
if err := openLog(logPath); err != nil {
return err
}
}
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)
}