diff --git a/command/spawn_daemon.go b/command/spawn_daemon.go index 0d5b3ef15..3ca825d41 100644 --- a/command/spawn_daemon.go +++ b/command/spawn_daemon.go @@ -2,34 +2,14 @@ package command import ( "encoding/json" - "fmt" "os" - "os/exec" - "strconv" "strings" - "syscall" - - cgroupFs "github.com/opencontainers/runc/libcontainer/cgroups/fs" - cgroupConfig "github.com/opencontainers/runc/libcontainer/configs" ) type SpawnDaemonCommand struct { Meta } -// Configuration for the command to start as a daemon. -type DaemonConfig struct { - exec.Cmd - - // The paths, if not /dev/null, must be either in the tasks root directory - // or in the shared alloc directory. - StdoutFile string - StdinFile string - StderrFile string - - Groups *cgroupConfig.Cgroup -} - // Status of executing the user's command. type SpawnStartStatus struct { // ErrorMsg will be empty if the user command was started successfully. @@ -37,11 +17,6 @@ type SpawnStartStatus struct { ErrorMsg string } -// The exit status of the user's command. -type SpawnExitStatus struct { - Success bool -} - func (c *SpawnDaemonCommand) Help() string { helpText := ` Usage: nomad spawn-daemon [options] @@ -65,99 +40,10 @@ func (c *SpawnDaemonCommand) Synopsis() string { return "Spawn a daemon command with configurable isolation." } -func (c *SpawnDaemonCommand) Run(args []string) int { - flags := c.Meta.FlagSet("spawn-daemon", FlagSetClient) - flags.Usage = func() { c.Ui.Output(c.Help()) } - - if err := flags.Parse(args); err != nil { - return 1 - } - - // Check that we got json input. - args = flags.Args() - if len(args) != 1 { - c.Ui.Error(c.Help()) - return 1 - } - jsonInput, err := strconv.Unquote(args[0]) - if err != nil { - return outputStartStatus(fmt.Errorf("Failed to unquote json input: %v", err), 1) - } - - // De-serialize the passed command. - var cmd DaemonConfig - dec := json.NewDecoder(strings.NewReader(jsonInput)) - if err := dec.Decode(&cmd); err != nil { - return outputStartStatus(err, 1) - } - - // Join this process to the cgroup. - if cmd.Groups != nil { - manager := cgroupFs.Manager{} - manager.Cgroups = cmd.Groups - - // Apply will place the current pid into the tasks file for each of the - // created cgroups: - // /sys/fs/cgroup/memory/user/1000.user/4.session//tasks - // - // Apply requires superuser permissions, and may fail if Nomad is not run with - // the required permissions - if err := manager.Apply(os.Getpid()); err != nil { - return outputStartStatus(fmt.Errorf("Failed to join cgroup: %v", err), 1) - } - } - - // Isolate the user process. - if _, err := syscall.Setsid(); err != nil { - return outputStartStatus(fmt.Errorf("Failed to join cgroup: %v", - fmt.Errorf("Failed setting sid: %v", err)), 1) - } - - syscall.Umask(0) - - // Redirect logs. - stdo, err := os.OpenFile(cmd.StdoutFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666) - if err != nil { - return outputStartStatus(fmt.Errorf("Error opening file to redirect Stdout: %v", err), 1) - } - - stde, err := os.OpenFile(cmd.StderrFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666) - if err != nil { - return outputStartStatus(fmt.Errorf("Error opening file to redirect Stderr: %v", err), 1) - } - - stdi, err := os.OpenFile(cmd.StdinFile, os.O_CREATE|os.O_RDONLY, 0666) - if err != nil { - return outputStartStatus(fmt.Errorf("Error opening file to redirect Stdin: %v", err), 1) - } - - cmd.Stdout = stdo - cmd.Stderr = stde - cmd.Stdin = stdi - - // Spawn the user process. - if err := cmd.Cmd.Start(); err != nil { - return outputStartStatus(fmt.Errorf("Error starting user command: %v", err), 1) - } - - // Indicate that the command was started successfully. - outputStartStatus(nil, 0) - - // Wait and then output the exit status. - exitStatus := &SpawnExitStatus{} - if err := cmd.Wait(); err == nil { - exitStatus.Success = true - } - enc := json.NewEncoder(os.Stdout) - enc.Encode(exitStatus) - - return 0 -} - // outputStartStatus is a helper function that outputs a SpawnStartStatus to // Stdout with the passed error, which may be nil to indicate no error. It // returns the passed status. -func outputStartStatus(err error, status int) int { +func (c *SpawnDaemonCommand) outputStartStatus(err error, status int) int { startStatus := &SpawnStartStatus{} enc := json.NewEncoder(os.Stdout) diff --git a/command/spawn_daemon_linux.go b/command/spawn_daemon_linux.go new file mode 100644 index 000000000..89be3fa3b --- /dev/null +++ b/command/spawn_daemon_linux.go @@ -0,0 +1,121 @@ +package command + +import ( + "encoding/json" + "fmt" + "os" + "os/exec" + "strconv" + "strings" + "syscall" + + cgroupFs "github.com/opencontainers/runc/libcontainer/cgroups/fs" + cgroupConfig "github.com/opencontainers/runc/libcontainer/configs" +) + +// Configuration for the command to start as a daemon. +type DaemonConfig struct { + exec.Cmd + + // The paths, if not /dev/null, must be either in the tasks root directory + // or in the shared alloc directory. + StdoutFile string + StdinFile string + StderrFile string + + Groups *cgroupConfig.Cgroup +} + +// The exit status of the user's command. +type SpawnExitStatus struct { + Success bool +} + +func (c *SpawnDaemonCommand) Run(args []string) int { + flags := c.Meta.FlagSet("spawn-daemon", FlagSetClient) + flags.Usage = func() { c.Ui.Output(c.Help()) } + + if err := flags.Parse(args); err != nil { + return 1 + } + + // Check that we got json input. + args = flags.Args() + if len(args) != 1 { + c.Ui.Error(c.Help()) + return 1 + } + jsonInput, err := strconv.Unquote(args[0]) + if err != nil { + return c.outputStartStatus(fmt.Errorf("Failed to unquote json input: %v", err), 1) + } + + // De-serialize the passed command. + var cmd DaemonConfig + dec := json.NewDecoder(strings.NewReader(jsonInput)) + if err := dec.Decode(&cmd); err != nil { + return c.outputStartStatus(err, 1) + } + + // Join this process to the cgroup. + if cmd.Groups != nil { + manager := cgroupFs.Manager{} + manager.Cgroups = cmd.Groups + + // Apply will place the current pid into the tasks file for each of the + // created cgroups: + // /sys/fs/cgroup/memory/user/1000.user/4.session//tasks + // + // Apply requires superuser permissions, and may fail if Nomad is not run with + // the required permissions + if err := manager.Apply(os.Getpid()); err != nil { + return c.outputStartStatus(fmt.Errorf("Failed to join cgroup: %v", err), 1) + } + } + + // Isolate the user process. + if _, err := syscall.Setsid(); err != nil { + return c.outputStartStatus(fmt.Errorf("Failed to join cgroup: %v", + fmt.Errorf("Failed setting sid: %v", err)), 1) + } + + syscall.Umask(0) + + // Redirect logs. + stdo, err := os.OpenFile(cmd.StdoutFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666) + if err != nil { + return c.outputStartStatus(fmt.Errorf("Error opening file to redirect Stdout: %v", err), 1) + } + + stde, err := os.OpenFile(cmd.StderrFile, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666) + if err != nil { + return c.outputStartStatus(fmt.Errorf("Error opening file to redirect Stderr: %v", err), 1) + } + + stdi, err := os.OpenFile(cmd.StdinFile, os.O_CREATE|os.O_RDONLY, 0666) + if err != nil { + return c.outputStartStatus(fmt.Errorf("Error opening file to redirect Stdin: %v", err), 1) + } + + cmd.Stdout = stdo + cmd.Stderr = stde + cmd.Stdin = stdi + + // Spawn the user process. + if err := cmd.Cmd.Start(); err != nil { + return c.outputStartStatus(fmt.Errorf("Error starting user command: %v", err), 1) + } + + // Indicate that the command was started successfully. + c.outputStartStatus(nil, 0) + + // Wait and then output the exit status. + exitStatus := &SpawnExitStatus{} + if err := cmd.Wait(); err == nil { + exitStatus.Success = true + } + enc := json.NewEncoder(os.Stdout) + enc.Encode(exitStatus) + + return 0 +} diff --git a/command/spawn_daemon_universal.go b/command/spawn_daemon_universal.go new file mode 100644 index 000000000..5083af5f3 --- /dev/null +++ b/command/spawn_daemon_universal.go @@ -0,0 +1,9 @@ +// +build !linux + +package command + +import "errors" + +func (c *SpawnDaemonCommand) Run(args []string) int { + return c.outputStartStatus(errors.New("spawn-daemon not supported"), 1) +}