Merge pull request #64 from vishh/runin
Adding RunIn to run a user specified command in an existing container.
This commit is contained in:
commit
80c1ae9051
|
@ -4,32 +4,96 @@ package namespaces
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/docker/libcontainer"
|
"github.com/docker/libcontainer"
|
||||||
"github.com/docker/libcontainer/label"
|
"github.com/docker/libcontainer/label"
|
||||||
"github.com/docker/libcontainer/system"
|
"github.com/docker/libcontainer/system"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Runs the command under 'args' inside an existing container referred to by 'container'.
|
||||||
|
// Returns the exitcode of the command upon success and appropriate error on failure.
|
||||||
|
func RunIn(container *libcontainer.Config, state *libcontainer.State, args []string, nsinitPath string, stdin io.Reader, stdout, stderr io.Writer, console string, startCallback func(*exec.Cmd)) (int, error) {
|
||||||
|
initArgs, err := getNsEnterCommand(strconv.Itoa(state.InitPid), container, console, args)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(nsinitPath, initArgs...)
|
||||||
|
// Note: these are only used in non-tty mode
|
||||||
|
// if there is a tty for the container it will be opened within the namespace and the
|
||||||
|
// fds will be duped to stdin, stdiout, and stderr
|
||||||
|
cmd.Stdin = stdin
|
||||||
|
cmd.Stdout = stdout
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
if startCallback != nil {
|
||||||
|
startCallback(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
if _, ok := err.(*exec.ExitError); !ok {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// ExecIn uses an existing pid and joins the pid's namespaces with the new command.
|
// ExecIn uses an existing pid and joins the pid's namespaces with the new command.
|
||||||
func ExecIn(container *libcontainer.Config, state *libcontainer.State, args []string) error {
|
func ExecIn(container *libcontainer.Config, state *libcontainer.State, args []string) error {
|
||||||
// TODO(vmarmol): If this gets too long, send it over a pipe to the child.
|
// Enter the namespace and then finish setup
|
||||||
// Marshall the container into JSON since it won't be available in the namespace.
|
args, err := getNsEnterCommand(strconv.Itoa(state.InitPid), container, "", args)
|
||||||
containerJson, err := json.Marshal(container)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enter the namespace and then finish setup
|
finalArgs := append([]string{os.Args[0]}, args...)
|
||||||
finalArgs := []string{os.Args[0], "nsenter", "--nspid", strconv.Itoa(state.InitPid), "--containerjson", string(containerJson), "--"}
|
|
||||||
finalArgs = append(finalArgs, args...)
|
|
||||||
if err := system.Execv(finalArgs[0], finalArgs[0:], os.Environ()); err != nil {
|
if err := system.Execv(finalArgs[0], finalArgs[0:], os.Environ()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getContainerJson(container *libcontainer.Config) (string, error) {
|
||||||
|
// TODO(vmarmol): If this gets too long, send it over a pipe to the child.
|
||||||
|
// Marshall the container into JSON since it won't be available in the namespace.
|
||||||
|
containerJson, err := json.Marshal(container)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(containerJson), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNsEnterCommand(initPid string, container *libcontainer.Config, console string, args []string) ([]string, error) {
|
||||||
|
containerJson, err := getContainerJson(container)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := []string{
|
||||||
|
"nsenter",
|
||||||
|
"--nspid", initPid,
|
||||||
|
"--containerjson", containerJson,
|
||||||
|
}
|
||||||
|
|
||||||
|
if console != "" {
|
||||||
|
out = append(out, "--console", console)
|
||||||
|
}
|
||||||
|
out = append(out, "--")
|
||||||
|
out = append(out, args...)
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Run a command in a container after entering the namespace.
|
// Run a command in a container after entering the namespace.
|
||||||
func NsEnter(container *libcontainer.Config, args []string) error {
|
func NsEnter(container *libcontainer.Config, args []string) error {
|
||||||
// clear the current processes env and replace it with the environment
|
// clear the current processes env and replace it with the environment
|
||||||
|
|
|
@ -88,6 +88,7 @@ void nsenter() {
|
||||||
static const struct option longopts[] = {
|
static const struct option longopts[] = {
|
||||||
{ "nspid", required_argument, NULL, 'n' },
|
{ "nspid", required_argument, NULL, 'n' },
|
||||||
{ "containerjson", required_argument, NULL, 'c' },
|
{ "containerjson", required_argument, NULL, 'c' },
|
||||||
|
{ "console", required_argument, NULL, 't' },
|
||||||
{ NULL, 0, NULL, 0 }
|
{ NULL, 0, NULL, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -95,6 +96,7 @@ void nsenter() {
|
||||||
pid_t init_pid = -1;
|
pid_t init_pid = -1;
|
||||||
char *init_pid_str = NULL;
|
char *init_pid_str = NULL;
|
||||||
char *container_json = NULL;
|
char *container_json = NULL;
|
||||||
|
char *console = NULL;
|
||||||
while ((c = getopt_long_only(argc, argv, "n:s:c:", longopts, NULL)) != -1) {
|
while ((c = getopt_long_only(argc, argv, "n:s:c:", longopts, NULL)) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'n':
|
case 'n':
|
||||||
|
@ -103,6 +105,9 @@ void nsenter() {
|
||||||
case 'c':
|
case 'c':
|
||||||
container_json = optarg;
|
container_json = optarg;
|
||||||
break;
|
break;
|
||||||
|
case 't':
|
||||||
|
console = optarg;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,6 +126,21 @@ void nsenter() {
|
||||||
argc -= 3;
|
argc -= 3;
|
||||||
argv += 3;
|
argv += 3;
|
||||||
|
|
||||||
|
if (setsid() == -1) {
|
||||||
|
fprintf(stderr, "setsid failed. Error: %s\n", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// before we setns we need to dup the console
|
||||||
|
int consolefd = -1;
|
||||||
|
if (console != NULL) {
|
||||||
|
consolefd = open(console, O_RDWR);
|
||||||
|
if (consolefd < 0) {
|
||||||
|
fprintf(stderr, "nsenter: failed to open console %s\n", console, strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Setns on all supported namespaces.
|
// Setns on all supported namespaces.
|
||||||
char ns_dir[PATH_MAX];
|
char ns_dir[PATH_MAX];
|
||||||
memset(ns_dir, 0, PATH_MAX);
|
memset(ns_dir, 0, PATH_MAX);
|
||||||
|
@ -159,6 +179,21 @@ void nsenter() {
|
||||||
// We must fork to actually enter the PID namespace.
|
// We must fork to actually enter the PID namespace.
|
||||||
int child = fork();
|
int child = fork();
|
||||||
if (child == 0) {
|
if (child == 0) {
|
||||||
|
if (consolefd != -1) {
|
||||||
|
if (dup2(consolefd, STDIN_FILENO) != 0) {
|
||||||
|
fprintf(stderr, "nsenter: failed to dup 0 %s\n", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (dup2(consolefd, STDOUT_FILENO) != STDOUT_FILENO) {
|
||||||
|
fprintf(stderr, "nsenter: failed to dup 1 %s\n", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (dup2(consolefd, STDERR_FILENO) != STDERR_FILENO) {
|
||||||
|
fprintf(stderr, "nsenter: failed to dup 2 %s\n", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Finish executing, let the Go runtime take over.
|
// Finish executing, let the Go runtime take over.
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -36,7 +36,7 @@ func execAction(context *cli.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if state != nil {
|
if state != nil {
|
||||||
err = namespaces.ExecIn(container, state, []string(context.Args()))
|
exitCode, err = runIn(container, state, []string(context.Args()))
|
||||||
} else {
|
} else {
|
||||||
exitCode, err = startContainer(container, dataPath, []string(context.Args()))
|
exitCode, err = startContainer(container, dataPath, []string(context.Args()))
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,59 @@ func execAction(context *cli.Context) {
|
||||||
os.Exit(exitCode)
|
os.Exit(exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runIn(container *libcontainer.Config, state *libcontainer.State, args []string) (int, error) {
|
||||||
|
var (
|
||||||
|
master *os.File
|
||||||
|
console string
|
||||||
|
err error
|
||||||
|
|
||||||
|
stdin = os.Stdin
|
||||||
|
stdout = os.Stdout
|
||||||
|
stderr = os.Stderr
|
||||||
|
sigc = make(chan os.Signal, 10)
|
||||||
|
)
|
||||||
|
|
||||||
|
signal.Notify(sigc)
|
||||||
|
|
||||||
|
if container.Tty {
|
||||||
|
stdin = nil
|
||||||
|
stdout = nil
|
||||||
|
stderr = nil
|
||||||
|
|
||||||
|
master, console, err = consolepkg.CreateMasterAndConsole()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go io.Copy(master, os.Stdin)
|
||||||
|
go io.Copy(os.Stdout, master)
|
||||||
|
|
||||||
|
state, err := term.SetRawTerminal(os.Stdin.Fd())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer term.RestoreTerminal(os.Stdin.Fd(), state)
|
||||||
|
}
|
||||||
|
|
||||||
|
startCallback := func(cmd *exec.Cmd) {
|
||||||
|
go func() {
|
||||||
|
resizeTty(master)
|
||||||
|
|
||||||
|
for sig := range sigc {
|
||||||
|
switch sig {
|
||||||
|
case syscall.SIGWINCH:
|
||||||
|
resizeTty(master)
|
||||||
|
default:
|
||||||
|
cmd.Process.Signal(sig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return namespaces.RunIn(container, state, args, os.Args[0], stdin, stdout, stderr, console, startCallback)
|
||||||
|
}
|
||||||
|
|
||||||
// startContainer starts the container. Returns the exit status or -1 and an
|
// startContainer starts the container. Returns the exit status or -1 and an
|
||||||
// error.
|
// error.
|
||||||
//
|
//
|
||||||
|
|
|
@ -14,6 +14,7 @@ var nsenterCommand = cli.Command{
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.IntFlag{Name: "nspid"},
|
cli.IntFlag{Name: "nspid"},
|
||||||
cli.StringFlag{Name: "containerjson"},
|
cli.StringFlag{Name: "containerjson"},
|
||||||
|
cli.StringFlag{Name: "console"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue