Merge pull request #75 from hashicorp/h-spawn-daemon-build-dep
Make spawn-daemon only defined on linux
This commit is contained in:
commit
33117772b7
|
@ -2,34 +2,14 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
cgroupFs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
|
|
||||||
cgroupConfig "github.com/opencontainers/runc/libcontainer/configs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SpawnDaemonCommand struct {
|
type SpawnDaemonCommand struct {
|
||||||
Meta
|
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.
|
// Status of executing the user's command.
|
||||||
type SpawnStartStatus struct {
|
type SpawnStartStatus struct {
|
||||||
// ErrorMsg will be empty if the user command was started successfully.
|
// ErrorMsg will be empty if the user command was started successfully.
|
||||||
|
@ -37,11 +17,6 @@ type SpawnStartStatus struct {
|
||||||
ErrorMsg string
|
ErrorMsg string
|
||||||
}
|
}
|
||||||
|
|
||||||
// The exit status of the user's command.
|
|
||||||
type SpawnExitStatus struct {
|
|
||||||
Success bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SpawnDaemonCommand) Help() string {
|
func (c *SpawnDaemonCommand) Help() string {
|
||||||
helpText := `
|
helpText := `
|
||||||
Usage: nomad spawn-daemon [options] <daemon_config>
|
Usage: nomad spawn-daemon [options] <daemon_config>
|
||||||
|
@ -65,99 +40,10 @@ func (c *SpawnDaemonCommand) Synopsis() string {
|
||||||
return "Spawn a daemon command with configurable isolation."
|
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/<uuid>/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
|
// outputStartStatus is a helper function that outputs a SpawnStartStatus to
|
||||||
// Stdout with the passed error, which may be nil to indicate no error. It
|
// Stdout with the passed error, which may be nil to indicate no error. It
|
||||||
// returns the passed status.
|
// returns the passed status.
|
||||||
func outputStartStatus(err error, status int) int {
|
func (c *SpawnDaemonCommand) outputStartStatus(err error, status int) int {
|
||||||
startStatus := &SpawnStartStatus{}
|
startStatus := &SpawnStartStatus{}
|
||||||
enc := json.NewEncoder(os.Stdout)
|
enc := json.NewEncoder(os.Stdout)
|
||||||
|
|
||||||
|
|
|
@ -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/<uuid>/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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
Loading…
Reference in New Issue