Revert "Make drivers take arguments as a list and not as a string"

This commit is contained in:
Alex Dadgar 2015-11-18 13:46:43 -08:00
parent e22d7e2a27
commit 0e51375285
29 changed files with 205 additions and 149 deletions

View file

@ -65,7 +65,7 @@ func TestAllocRunner_Destroy(t *testing.T) {
// Ensure task takes some time // Ensure task takes some time
task := ar.alloc.Job.TaskGroups[0].Tasks[0] task := ar.alloc.Job.TaskGroups[0].Tasks[0]
task.Config["command"] = "/bin/sleep" task.Config["command"] = "/bin/sleep"
task.Config["args"] = []string{"10"} task.Config["args"] = "10"
go ar.Run() go ar.Run()
start := time.Now() start := time.Now()
@ -97,7 +97,7 @@ func TestAllocRunner_Update(t *testing.T) {
// Ensure task takes some time // Ensure task takes some time
task := ar.alloc.Job.TaskGroups[0].Tasks[0] task := ar.alloc.Job.TaskGroups[0].Tasks[0]
task.Config["command"] = "/bin/sleep" task.Config["command"] = "/bin/sleep"
task.Config["args"] = []string{"10"} task.Config["args"] = "10"
go ar.Run() go ar.Run()
defer ar.Destroy() defer ar.Destroy()
start := time.Now() start := time.Now()
@ -130,7 +130,7 @@ func TestAllocRunner_SaveRestoreState(t *testing.T) {
// Ensure task takes some time // Ensure task takes some time
task := ar.alloc.Job.TaskGroups[0].Tasks[0] task := ar.alloc.Job.TaskGroups[0].Tasks[0]
task.Config["command"] = "/bin/sleep" task.Config["command"] = "/bin/sleep"
task.Config["args"] = []string{"10"} task.Config["args"] = "10"
go ar.Run() go ar.Run()
defer ar.Destroy() defer ar.Destroy()

View file

@ -336,7 +336,7 @@ func TestClient_SaveRestoreState(t *testing.T) {
alloc1.NodeID = c1.Node().ID alloc1.NodeID = c1.Node().ID
task := alloc1.Job.TaskGroups[0].Tasks[0] task := alloc1.Job.TaskGroups[0].Tasks[0]
task.Config["command"] = "/bin/sleep" task.Config["command"] = "/bin/sleep"
task.Config["args"] = []string{"10"} task.Config["args"] = "10"
state := s1.State() state := s1.State()
err := state.UpsertAllocs(100, err := state.UpsertAllocs(100,

View file

@ -1,6 +1,11 @@
package args package args
import "regexp" import (
"fmt"
"regexp"
"github.com/mattn/go-shellwords"
)
var ( var (
envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`) envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`)
@ -8,17 +13,27 @@ var (
// ParseAndReplace takes the user supplied args and a map of environment // ParseAndReplace takes the user supplied args and a map of environment
// variables. It replaces any instance of an environment variable in the args // variables. It replaces any instance of an environment variable in the args
// with the actual value. // with the actual value and does correct splitting of the arg list.
func ParseAndReplace(args []string, env map[string]string) []string { func ParseAndReplace(args string, env map[string]string) ([]string, error) {
replaced := make([]string, len(args)) // Set up parser.
for i, arg := range args { p := shellwords.NewParser()
p.ParseEnv = false
p.ParseBacktick = false
parsed, err := p.Parse(args)
if err != nil {
return nil, fmt.Errorf("Couldn't parse args %v: %v", args, err)
}
replaced := make([]string, len(parsed))
for i, arg := range parsed {
replaced[i] = ReplaceEnv(arg, env) replaced[i] = ReplaceEnv(arg, env)
} }
return replaced return replaced, nil
} }
// ReplaceEnv takes an arg and replaces all occurences of environment variables. // replaceEnv takes an arg and replaces all occurences of environment variables.
// If the variable is found in the passed map it is replaced, otherwise the // If the variable is found in the passed map it is replaced, otherwise the
// original string is returned. // original string is returned.
func ReplaceEnv(arg string, env map[string]string) string { func ReplaceEnv(arg string, env map[string]string) string {

View file

@ -21,9 +21,12 @@ var (
) )
func TestDriverArgs_ParseAndReplaceInvalidEnv(t *testing.T) { func TestDriverArgs_ParseAndReplaceInvalidEnv(t *testing.T) {
input := []string{"invalid", "$FOO"} input := "invalid $FOO"
exp := []string{"invalid", "$FOO"} exp := []string{"invalid", "$FOO"}
act := ParseAndReplace(input, envVars) act, err := ParseAndReplace(input, envVars)
if err != nil {
t.Fatalf("Failed to parse valid input args %v: %v", input, err)
}
if !reflect.DeepEqual(act, exp) { if !reflect.DeepEqual(act, exp) {
t.Fatalf("ParseAndReplace(%v, %v) returned %#v; want %#v", input, envVars, act, exp) t.Fatalf("ParseAndReplace(%v, %v) returned %#v; want %#v", input, envVars, act, exp)
@ -31,9 +34,12 @@ func TestDriverArgs_ParseAndReplaceInvalidEnv(t *testing.T) {
} }
func TestDriverArgs_ParseAndReplaceValidEnv(t *testing.T) { func TestDriverArgs_ParseAndReplaceValidEnv(t *testing.T) {
input := []string{"nomad_ip", fmt.Sprintf(`"$%v"!`, ipKey)} input := fmt.Sprintf("nomad_ip \\\"$%v\\\"!", ipKey)
exp := []string{"nomad_ip", fmt.Sprintf("\"%s\"!", ipVal)} exp := []string{"nomad_ip", fmt.Sprintf("\"%s\"!", ipVal)}
act := ParseAndReplace(input, envVars) act, err := ParseAndReplace(input, envVars)
if err != nil {
t.Fatalf("Failed to parse valid input args %v: %v", input, err)
}
if !reflect.DeepEqual(act, exp) { if !reflect.DeepEqual(act, exp) {
t.Fatalf("ParseAndReplace(%v, %v) returned %#v; want %#v", input, envVars, act, exp) t.Fatalf("ParseAndReplace(%v, %v) returned %#v; want %#v", input, envVars, act, exp)
@ -41,9 +47,32 @@ func TestDriverArgs_ParseAndReplaceValidEnv(t *testing.T) {
} }
func TestDriverArgs_ParseAndReplaceChainedEnv(t *testing.T) { func TestDriverArgs_ParseAndReplaceChainedEnv(t *testing.T) {
input := []string{"-foo", fmt.Sprintf("$%s$%s", ipKey, portKey)} input := fmt.Sprintf("-foo $%s$%s", ipKey, portKey)
exp := []string{"-foo", fmt.Sprintf("%s%s", ipVal, portVal)} exp := []string{"-foo", fmt.Sprintf("%s%s", ipVal, portVal)}
act := ParseAndReplace(input, envVars) act, err := ParseAndReplace(input, envVars)
if err != nil {
t.Fatalf("Failed to parse valid input args %v: %v", input, err)
}
if !reflect.DeepEqual(act, exp) {
t.Fatalf("ParseAndReplace(%v, %v) returned %#v; want %#v", input, envVars, act, exp)
}
}
func TestDriverArgs_ParseAndReplaceInvalidArgEscape(t *testing.T) {
input := "-c \"echo \"foo\\\" > bar.txt\""
if _, err := ParseAndReplace(input, envVars); err == nil {
t.Fatalf("ParseAndReplace(%v, %v) should have failed", input, envVars)
}
}
func TestDriverArgs_ParseAndReplaceValidArgEscape(t *testing.T) {
input := "-c \"echo \\\"foo\\\" > bar.txt\""
exp := []string{"-c", "echo \"foo\" > bar.txt"}
act, err := ParseAndReplace(input, envVars)
if err != nil {
t.Fatalf("Failed to parse valid input args %v: %v", input, err)
}
if !reflect.DeepEqual(act, exp) { if !reflect.DeepEqual(act, exp) {
t.Fatalf("ParseAndReplace(%v, %v) returned %#v; want %#v", input, envVars, act, exp) t.Fatalf("ParseAndReplace(%v, %v) returned %#v; want %#v", input, envVars, act, exp)

View file

@ -35,7 +35,7 @@ type DockerDriverAuth struct {
type DockerDriverConfig struct { type DockerDriverConfig struct {
ImageName string `mapstructure:"image"` // Container's Image Name ImageName string `mapstructure:"image"` // Container's Image Name
Command string `mapstructure:"command"` // The Command/Entrypoint to run when the container starts up Command string `mapstructure:"command"` // The Command/Entrypoint to run when the container starts up
Args []string `mapstructure:"args"` // The arguments to the Command/Entrypoint Args string `mapstructure:"args"` // The arguments to the Command/Entrypoint
NetworkMode string `mapstructure:"network_mode"` // The network mode of the container - host, net and none NetworkMode string `mapstructure:"network_mode"` // The network mode of the container - host, net and none
PortMap []map[string]int `mapstructure:"port_map"` // A map of host port labels and the ports exposed on the container PortMap []map[string]int `mapstructure:"port_map"` // A map of host port labels and the ports exposed on the container
Privileged bool `mapstructure:"privileged"` // Flag to run the container in priviledged mode Privileged bool `mapstructure:"privileged"` // Flag to run the container in priviledged mode
@ -293,18 +293,21 @@ func (d *DockerDriver) createContainer(ctx *ExecContext, task *structs.Task, dri
config.ExposedPorts = exposedPorts config.ExposedPorts = exposedPorts
} }
parsedArgs := args.ParseAndReplace(driverConfig.Args, env.Map()) parsedArgs, err := args.ParseAndReplace(driverConfig.Args, env.Map())
if err != nil {
return c, err
}
// If the user specified a custom command to run as their entrypoint, we'll // If the user specified a custom command to run as their entrypoint, we'll
// inject it here. // inject it here.
if driverConfig.Command != "" { if driverConfig.Command != "" {
cmd := []string{driverConfig.Command} cmd := []string{driverConfig.Command}
if len(driverConfig.Args) != 0 { if driverConfig.Args != "" {
cmd = append(cmd, parsedArgs...) cmd = append(cmd, parsedArgs...)
} }
d.logger.Printf("[DEBUG] driver.docker: setting container startup command to: %s", strings.Join(cmd, " ")) d.logger.Printf("[DEBUG] driver.docker: setting container startup command to: %s", strings.Join(cmd, " "))
config.Cmd = cmd config.Cmd = cmd
} else if len(driverConfig.Args) != 0 { } else if driverConfig.Args != "" {
d.logger.Println("[DEBUG] driver.docker: ignoring command arguments because command is not specified") d.logger.Println("[DEBUG] driver.docker: ignoring command arguments because command is not specified")
} }
@ -428,7 +431,7 @@ func (d *DockerDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle
if len(containers) != 1 { if len(containers) != 1 {
log.Printf("[ERR] driver.docker: failed to get id for container %s", config.Name) log.Printf("[ERR] driver.docker: failed to get id for container %s", config.Name)
return nil, fmt.Errorf("Failed to get id for container %s", config.Name) return nil, fmt.Errorf("Failed to get id for container %s: %s", config.Name, err)
} }
log.Printf("[INFO] driver.docker: a container with the name %s already exists; will attempt to purge and re-create", config.Name) log.Printf("[INFO] driver.docker: a container with the name %s already exists; will attempt to purge and re-create", config.Name)

View file

@ -137,7 +137,7 @@ func TestDockerDriver_Start_Wait(t *testing.T) {
Config: map[string]interface{}{ Config: map[string]interface{}{
"image": "redis", "image": "redis",
"command": "redis-server", "command": "redis-server",
"args": []string{"-v"}, "args": "-v",
}, },
Resources: &structs.Resources{ Resources: &structs.Resources{
MemoryMB: 256, MemoryMB: 256,
@ -190,11 +190,7 @@ func TestDockerDriver_Start_Wait_AllocDir(t *testing.T) {
Config: map[string]interface{}{ Config: map[string]interface{}{
"image": "redis", "image": "redis",
"command": "/bin/bash", "command": "/bin/bash",
"args": []string{ "args": fmt.Sprintf(`-c "sleep 1; echo -n %s > $%s/%s"`, string(exp), environment.AllocDir, file),
"-c",
fmt.Sprintf(`sleep 1; echo -n %s > $%s/%s`,
string(exp), environment.AllocDir, file),
},
}, },
Resources: &structs.Resources{ Resources: &structs.Resources{
MemoryMB: 256, MemoryMB: 256,
@ -247,7 +243,7 @@ func TestDockerDriver_Start_Kill_Wait(t *testing.T) {
Config: map[string]interface{}{ Config: map[string]interface{}{
"image": "redis", "image": "redis",
"command": "/bin/sleep", "command": "/bin/sleep",
"args": []string{"10"}, "args": "10",
}, },
Resources: basicResources, Resources: basicResources,
} }

View file

@ -24,10 +24,10 @@ type ExecDriver struct {
fingerprint.StaticFingerprinter fingerprint.StaticFingerprinter
} }
type ExecDriverConfig struct { type ExecDriverConfig struct {
ArtifactSource string `mapstructure:"artifact_source"` ArtifactSource string `mapstructure:"artifact_source"`
Checksum string `mapstructure:"checksum"` Checksum string `mapstructure:"checksum"`
Command string `mapstructure:"command"` Command string `mapstructure:"command"`
Args []string `mapstructure:"args"` Args string `mapstructure:"args"`
} }
// execHandle is returned from Start/Open as a handle to the PID // execHandle is returned from Start/Open as a handle to the PID
@ -91,8 +91,14 @@ func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle,
// Get the environment variables. // Get the environment variables.
envVars := TaskEnvironmentVariables(ctx, task) envVars := TaskEnvironmentVariables(ctx, task)
// Look for arguments
var args []string
if driverConfig.Args != "" {
args = append(args, driverConfig.Args)
}
// Setup the command // Setup the command
cmd := executor.Command(command, driverConfig.Args...) cmd := executor.Command(command, args...)
if err := cmd.Limit(task.Resources); err != nil { if err := cmd.Limit(task.Resources); err != nil {
return nil, fmt.Errorf("failed to constrain resources: %s", err) return nil, fmt.Errorf("failed to constrain resources: %s", err)
} }

View file

@ -39,7 +39,7 @@ func TestExecDriver_StartOpen_Wait(t *testing.T) {
Name: "sleep", Name: "sleep",
Config: map[string]interface{}{ Config: map[string]interface{}{
"command": "/bin/sleep", "command": "/bin/sleep",
"args": []string{"5"}, "args": "5",
}, },
Resources: basicResources, Resources: basicResources,
} }
@ -73,7 +73,7 @@ func TestExecDriver_Start_Wait(t *testing.T) {
Name: "sleep", Name: "sleep",
Config: map[string]interface{}{ Config: map[string]interface{}{
"command": "/bin/sleep", "command": "/bin/sleep",
"args": []string{"2"}, "args": "2",
}, },
Resources: basicResources, Resources: basicResources,
} }
@ -161,10 +161,7 @@ func TestExecDriver_Start_Artifact_expanded(t *testing.T) {
Config: map[string]interface{}{ Config: map[string]interface{}{
"artifact_source": fmt.Sprintf("https://dl.dropboxusercontent.com/u/47675/jar_thing/%s", file), "artifact_source": fmt.Sprintf("https://dl.dropboxusercontent.com/u/47675/jar_thing/%s", file),
"command": "/bin/bash", "command": "/bin/bash",
"args": []string{ "args": fmt.Sprintf("-c '/bin/sleep 1 && %s'", filepath.Join("$NOMAD_TASK_DIR", file)),
"-c",
fmt.Sprintf(`/bin/sleep 1 && %s`, filepath.Join("$NOMAD_TASK_DIR", file)),
},
}, },
Resources: basicResources, Resources: basicResources,
} }
@ -207,10 +204,7 @@ func TestExecDriver_Start_Wait_AllocDir(t *testing.T) {
Name: "sleep", Name: "sleep",
Config: map[string]interface{}{ Config: map[string]interface{}{
"command": "/bin/bash", "command": "/bin/bash",
"args": []string{ "args": fmt.Sprintf("-c \"sleep 1; echo -n %s > $%s/%s\"", string(exp), environment.AllocDir, file),
"-c",
fmt.Sprintf(`sleep 1; echo -n %s > $%s/%s`, string(exp), environment.AllocDir, file),
},
}, },
Resources: basicResources, Resources: basicResources,
} }
@ -256,7 +250,7 @@ func TestExecDriver_Start_Kill_Wait(t *testing.T) {
Name: "sleep", Name: "sleep",
Config: map[string]interface{}{ Config: map[string]interface{}{
"command": "/bin/sleep", "command": "/bin/sleep",
"args": []string{"1"}, "args": "1",
}, },
Resources: basicResources, Resources: basicResources,
} }

View file

@ -29,6 +29,7 @@ type BasicExecutor struct {
allocDir string allocDir string
} }
// TODO: Have raw_exec use this as well.
func NewBasicExecutor() Executor { func NewBasicExecutor() Executor {
return &BasicExecutor{} return &BasicExecutor{}
} }
@ -62,7 +63,12 @@ func (e *BasicExecutor) Start() error {
} }
e.cmd.Path = args.ReplaceEnv(e.cmd.Path, envVars.Map()) e.cmd.Path = args.ReplaceEnv(e.cmd.Path, envVars.Map())
e.cmd.Args = args.ParseAndReplace(e.cmd.Args, envVars.Map()) combined := strings.Join(e.cmd.Args, " ")
parsed, err := args.ParseAndReplace(combined, envVars.Map())
if err != nil {
return err
}
e.cmd.Args = parsed
spawnState := filepath.Join(e.allocDir, fmt.Sprintf("%s_%s", e.taskName, "exit_status")) spawnState := filepath.Join(e.allocDir, fmt.Sprintf("%s_%s", e.taskName, "exit_status"))
e.spawn = spawn.NewSpawner(spawnState) e.spawn = spawn.NewSpawner(spawnState)

View file

@ -167,7 +167,12 @@ func (e *LinuxExecutor) Start() error {
} }
e.cmd.Path = args.ReplaceEnv(e.cmd.Path, envVars.Map()) e.cmd.Path = args.ReplaceEnv(e.cmd.Path, envVars.Map())
e.cmd.Args = args.ParseAndReplace(e.cmd.Args, envVars.Map()) combined := strings.Join(e.cmd.Args, " ")
parsed, err := args.ParseAndReplace(combined, envVars.Map())
if err != nil {
return err
}
e.cmd.Args = parsed
spawnState := filepath.Join(e.allocDir, fmt.Sprintf("%s_%s", e.taskName, "exit_status")) spawnState := filepath.Join(e.allocDir, fmt.Sprintf("%s_%s", e.taskName, "exit_status"))
e.spawn = spawn.NewSpawner(spawnState) e.spawn = spawn.NewSpawner(spawnState)

View file

@ -112,7 +112,7 @@ func Executor_Start_Wait(t *testing.T, command buildExecCommand) {
expected := "hello world" expected := "hello world"
file := filepath.Join(allocdir.TaskLocal, "output.txt") file := filepath.Join(allocdir.TaskLocal, "output.txt")
absFilePath := filepath.Join(taskDir, file) absFilePath := filepath.Join(taskDir, file)
cmd := fmt.Sprintf(`/bin/sleep 1 ; echo -n %v > %v`, expected, file) cmd := fmt.Sprintf(`"%v \"%v\" > %v"`, "/bin/sleep 1 ; echo -n", expected, file)
e := command("/bin/bash", "-c", cmd) e := command("/bin/bash", "-c", cmd)
if err := e.Limit(constraint); err != nil { if err := e.Limit(constraint); err != nil {
@ -190,7 +190,7 @@ func Executor_Open(t *testing.T, command buildExecCommand, newExecutor func() Ex
expected := "hello world" expected := "hello world"
file := filepath.Join(allocdir.TaskLocal, "output.txt") file := filepath.Join(allocdir.TaskLocal, "output.txt")
absFilePath := filepath.Join(taskDir, file) absFilePath := filepath.Join(taskDir, file)
cmd := fmt.Sprintf(`/bin/sleep 1 ; echo -n %v > %v`, expected, file) cmd := fmt.Sprintf(`"%v \"%v\" > %v"`, "/bin/sleep 1 ; echo -n", expected, file)
e := command("/bin/bash", "-c", cmd) e := command("/bin/bash", "-c", cmd)
if err := e.Limit(constraint); err != nil { if err := e.Limit(constraint); err != nil {

View file

@ -28,10 +28,10 @@ type JavaDriver struct {
} }
type JavaDriverConfig struct { type JavaDriverConfig struct {
JvmOpts []string `mapstructure:"jvm_options"` JvmOpts string `mapstructure:"jvm_options"`
ArtifactSource string `mapstructure:"artifact_source"` ArtifactSource string `mapstructure:"artifact_source"`
Checksum string `mapstructure:"checksum"` Checksum string `mapstructure:"checksum"`
Args []string `mapstructure:"args"` Args string `mapstructure:"args"`
} }
// javaHandle is returned from Start/Open as a handle to the PID // javaHandle is returned from Start/Open as a handle to the PID
@ -126,15 +126,15 @@ func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle,
args := []string{} args := []string{}
// Look for jvm options // Look for jvm options
if len(driverConfig.JvmOpts) != 0 { if driverConfig.JvmOpts != "" {
d.logger.Printf("[DEBUG] driver.java: found JVM options: %s", driverConfig.JvmOpts) d.logger.Printf("[DEBUG] driver.java: found JVM options: %s", driverConfig.JvmOpts)
args = append(args, driverConfig.JvmOpts...) args = append(args, driverConfig.JvmOpts)
} }
// Build the argument list. // Build the argument list.
args = append(args, "-jar", filepath.Join(allocdir.TaskLocal, jarName)) args = append(args, "-jar", filepath.Join(allocdir.TaskLocal, jarName))
if len(driverConfig.Args) != 0 { if driverConfig.Args != "" {
args = append(args, driverConfig.Args...) args = append(args, driverConfig.Args)
} }
// Setup the command // Setup the command

View file

@ -51,7 +51,7 @@ func TestJavaDriver_StartOpen_Wait(t *testing.T) {
Name: "demo-app", Name: "demo-app",
Config: map[string]interface{}{ Config: map[string]interface{}{
"artifact_source": "https://dl.dropboxusercontent.com/u/47675/jar_thing/demoapp.jar", "artifact_source": "https://dl.dropboxusercontent.com/u/47675/jar_thing/demoapp.jar",
"jvm_options": []string{"-Xmx64m", "-Xms32m"}, "jvm_options": "-Xmx2048m -Xms256m",
"checksum": "sha256:58d6e8130308d32e197c5108edd4f56ddf1417408f743097c2e662df0f0b17c8", "checksum": "sha256:58d6e8130308d32e197c5108edd4f56ddf1417408f743097c2e662df0f0b17c8",
}, },
Resources: basicResources, Resources: basicResources,
@ -97,6 +97,7 @@ func TestJavaDriver_Start_Wait(t *testing.T) {
Name: "demo-app", Name: "demo-app",
Config: map[string]interface{}{ Config: map[string]interface{}{
"artifact_source": "https://dl.dropboxusercontent.com/u/47675/jar_thing/demoapp.jar", "artifact_source": "https://dl.dropboxusercontent.com/u/47675/jar_thing/demoapp.jar",
"jvm_options": "-Xmx2048m -Xms256m",
"checksum": "sha256:58d6e8130308d32e197c5108edd4f56ddf1417408f743097c2e662df0f0b17c8", "checksum": "sha256:58d6e8130308d32e197c5108edd4f56ddf1417408f743097c2e662df0f0b17c8",
}, },
Resources: basicResources, Resources: basicResources,

View file

@ -93,9 +93,15 @@ func (d *RawExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandl
// Get the environment variables. // Get the environment variables.
envVars := TaskEnvironmentVariables(ctx, task) envVars := TaskEnvironmentVariables(ctx, task)
// Look for arguments
var args []string
if driverConfig.Args != "" {
args = append(args, driverConfig.Args)
}
// Setup the command // Setup the command
cmd := executor.NewBasicExecutor() cmd := executor.NewBasicExecutor()
executor.SetCommand(cmd, command, driverConfig.Args) executor.SetCommand(cmd, command, args)
if err := cmd.Limit(task.Resources); err != nil { if err := cmd.Limit(task.Resources); err != nil {
return nil, fmt.Errorf("failed to constrain resources: %s", err) return nil, fmt.Errorf("failed to constrain resources: %s", err)
} }

View file

@ -53,7 +53,7 @@ func TestRawExecDriver_StartOpen_Wait(t *testing.T) {
Name: "sleep", Name: "sleep",
Config: map[string]interface{}{ Config: map[string]interface{}{
"command": "/bin/sleep", "command": "/bin/sleep",
"args": []string{"1"}, "args": "1",
}, },
Resources: basicResources, Resources: basicResources,
} }
@ -151,10 +151,7 @@ func TestRawExecDriver_Start_Artifact_expanded(t *testing.T) {
Config: map[string]interface{}{ Config: map[string]interface{}{
"artifact_source": fmt.Sprintf("https://dl.dropboxusercontent.com/u/47675/jar_thing/%s", file), "artifact_source": fmt.Sprintf("https://dl.dropboxusercontent.com/u/47675/jar_thing/%s", file),
"command": "/bin/bash", "command": "/bin/bash",
"args": []string{ "args": fmt.Sprintf("-c '/bin/sleep 1 && %s'", filepath.Join("$NOMAD_TASK_DIR", file)),
"-c",
fmt.Sprintf(`'/bin/sleep 1 && %s'`, filepath.Join("$NOMAD_TASK_DIR", file)),
},
}, },
Resources: basicResources, Resources: basicResources,
} }
@ -193,7 +190,7 @@ func TestRawExecDriver_Start_Wait(t *testing.T) {
Name: "sleep", Name: "sleep",
Config: map[string]interface{}{ Config: map[string]interface{}{
"command": "/bin/sleep", "command": "/bin/sleep",
"args": []string{"1"}, "args": "1",
}, },
Resources: basicResources, Resources: basicResources,
} }
@ -235,10 +232,7 @@ func TestRawExecDriver_Start_Wait_AllocDir(t *testing.T) {
Name: "sleep", Name: "sleep",
Config: map[string]interface{}{ Config: map[string]interface{}{
"command": "/bin/bash", "command": "/bin/bash",
"args": []string{ "args": fmt.Sprintf(`-c "sleep 1; echo -n %s > $%s/%s"`, string(exp), environment.AllocDir, file),
"-c",
fmt.Sprintf(`sleep 1; echo -n %s > $%s/%s`, string(exp), environment.AllocDir, file),
},
}, },
Resources: basicResources, Resources: basicResources,
} }
@ -283,7 +277,7 @@ func TestRawExecDriver_Start_Kill_Wait(t *testing.T) {
Name: "sleep", Name: "sleep",
Config: map[string]interface{}{ Config: map[string]interface{}{
"command": "/bin/sleep", "command": "/bin/sleep",
"args": []string{"1"}, "args": "1",
}, },
Resources: basicResources, Resources: basicResources,
} }

View file

@ -37,8 +37,8 @@ type RktDriver struct {
} }
type RktDriverConfig struct { type RktDriverConfig struct {
ImageName string `mapstructure:"image"` ImageName string `mapstructure:"image"`
Args []string `mapstructure:"args"` Args string `mapstructure:"args"`
} }
// rktHandle is returned from Start/Open as a handle to the PID // rktHandle is returned from Start/Open as a handle to the PID
@ -150,8 +150,11 @@ func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, e
} }
// Add user passed arguments. // Add user passed arguments.
if len(driverConfig.Args) != 0 { if driverConfig.Args != "" {
parsed := args.ParseAndReplace(driverConfig.Args, envVars.Map()) parsed, err := args.ParseAndReplace(driverConfig.Args, envVars.Map())
if err != nil {
return nil, err
}
// Need to start arguments with "--" // Need to start arguments with "--"
if len(parsed) > 0 { if len(parsed) > 0 {

View file

@ -119,7 +119,7 @@ func TestRktDriver_Start_Wait(t *testing.T) {
"trust_prefix": "coreos.com/etcd", "trust_prefix": "coreos.com/etcd",
"image": "coreos.com/etcd:v2.0.4", "image": "coreos.com/etcd:v2.0.4",
"command": "/etcd", "command": "/etcd",
"args": []string{"--version"}, "args": "--version",
}, },
} }
@ -160,7 +160,7 @@ func TestRktDriver_Start_Wait_Skip_Trust(t *testing.T) {
Config: map[string]interface{}{ Config: map[string]interface{}{
"image": "coreos.com/etcd:v2.0.4", "image": "coreos.com/etcd:v2.0.4",
"command": "/etcd", "command": "/etcd",
"args": []string{"--version"}, "args": "--version",
}, },
} }
@ -202,7 +202,7 @@ func TestRktDriver_Start_Wait_Logs(t *testing.T) {
"trust_prefix": "coreos.com/etcd", "trust_prefix": "coreos.com/etcd",
"image": "coreos.com/etcd:v2.0.4", "image": "coreos.com/etcd:v2.0.4",
"command": "/etcd", "command": "/etcd",
"args": []string{"--version"}, "args": "--version",
}, },
} }

View file

@ -89,7 +89,7 @@ func TestTaskRunner_Destroy(t *testing.T) {
// Change command to ensure we run for a bit // Change command to ensure we run for a bit
tr.task.Config["command"] = "/bin/sleep" tr.task.Config["command"] = "/bin/sleep"
tr.task.Config["args"] = []string{"10"} tr.task.Config["args"] = "10"
go tr.Run() go tr.Run()
// Begin the tear down // Begin the tear down
@ -128,7 +128,7 @@ func TestTaskRunner_Update(t *testing.T) {
// Change command to ensure we run for a bit // Change command to ensure we run for a bit
tr.task.Config["command"] = "/bin/sleep" tr.task.Config["command"] = "/bin/sleep"
tr.task.Config["args"] = []string{"10"} tr.task.Config["args"] = "10"
go tr.Run() go tr.Run()
defer tr.Destroy() defer tr.Destroy()
defer tr.ctx.AllocDir.Destroy() defer tr.ctx.AllocDir.Destroy()
@ -153,7 +153,7 @@ func TestTaskRunner_SaveRestoreState(t *testing.T) {
// Change command to ensure we run for a bit // Change command to ensure we run for a bit
tr.task.Config["command"] = "/bin/sleep" tr.task.Config["command"] = "/bin/sleep"
tr.task.Config["args"] = []string{"10"} tr.task.Config["args"] = "10"
go tr.Run() go tr.Run()
defer tr.Destroy() defer tr.Destroy()

View file

@ -13,6 +13,13 @@ import (
"github.com/hashicorp/raft" "github.com/hashicorp/raft"
) )
var (
msgpackHandle = &codec.MsgpackHandle{
RawToString: true,
WriteExt: true,
}
)
const ( const (
// timeTableGranularity is the granularity of index to time tracking // timeTableGranularity is the granularity of index to time tracking
timeTableGranularity = 5 * time.Minute timeTableGranularity = 5 * time.Minute
@ -321,7 +328,7 @@ func (n *nomadFSM) Restore(old io.ReadCloser) error {
defer restore.Abort() defer restore.Abort()
// Create a decoder // Create a decoder
dec := codec.NewDecoder(old, structs.MsgpackHandle) dec := codec.NewDecoder(old, msgpackHandle)
// Read in the header // Read in the header
var header snapshotHeader var header snapshotHeader
@ -405,7 +412,7 @@ func (n *nomadFSM) Restore(old io.ReadCloser) error {
func (s *nomadSnapshot) Persist(sink raft.SnapshotSink) error { func (s *nomadSnapshot) Persist(sink raft.SnapshotSink) error {
defer metrics.MeasureSince([]string{"nomad", "fsm", "persist"}, time.Now()) defer metrics.MeasureSince([]string{"nomad", "fsm", "persist"}, time.Now())
// Register the nodes // Register the nodes
encoder := codec.NewEncoder(sink, structs.MsgpackHandle) encoder := codec.NewEncoder(sink, msgpackHandle)
// Write the header // Write the header
header := snapshotHeader{} header := snapshotHeader{}

View file

@ -86,7 +86,7 @@ func Job() *structs.Job {
Driver: "exec", Driver: "exec",
Config: map[string]interface{}{ Config: map[string]interface{}{
"command": "/bin/date", "command": "/bin/date",
"args": []string{"+%s"}, "args": "+%s",
}, },
Env: map[string]string{ Env: map[string]string{
"FOO": "bar", "FOO": "bar",
@ -151,7 +151,7 @@ func SystemJob() *structs.Job {
Driver: "exec", Driver: "exec",
Config: map[string]interface{}{ Config: map[string]interface{}{
"command": "/bin/date", "command": "/bin/date",
"args": []string{"+%s"}, "args": "+%s",
}, },
Resources: &structs.Resources{ Resources: &structs.Resources{
CPU: 500, CPU: 500,

View file

@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/armon/go-metrics" "github.com/armon/go-metrics"
"github.com/hashicorp/go-msgpack/codec"
"github.com/hashicorp/net-rpc-msgpackrpc" "github.com/hashicorp/net-rpc-msgpackrpc"
"github.com/hashicorp/nomad/nomad/state" "github.com/hashicorp/nomad/nomad/state"
"github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/nomad/structs"
@ -52,16 +53,24 @@ const (
enqueueLimit = 30 * time.Second enqueueLimit = 30 * time.Second
) )
var (
// rpcHandle is the MsgpackHandle to be used by both Client and Server codecs.
rpcHandle = &codec.MsgpackHandle{
// Enables proper encoding of strings within nil interfaces.
RawToString: true,
}
)
// NewClientCodec returns a new rpc.ClientCodec to be used to make RPC calls to // NewClientCodec returns a new rpc.ClientCodec to be used to make RPC calls to
// the Nomad Server. // the Nomad Server.
func NewClientCodec(conn io.ReadWriteCloser) rpc.ClientCodec { func NewClientCodec(conn io.ReadWriteCloser) rpc.ClientCodec {
return msgpackrpc.NewCodecFromHandle(true, true, conn, structs.MsgpackHandle) return msgpackrpc.NewCodecFromHandle(true, true, conn, rpcHandle)
} }
// NewServerCodec returns a new rpc.ServerCodec to be used by the Nomad Server // NewServerCodec returns a new rpc.ServerCodec to be used by the Nomad Server
// to handle rpcs. // to handle rpcs.
func NewServerCodec(conn io.ReadWriteCloser) rpc.ServerCodec { func NewServerCodec(conn io.ReadWriteCloser) rpc.ServerCodec {
return msgpackrpc.NewCodecFromHandle(true, true, conn, structs.MsgpackHandle) return msgpackrpc.NewCodecFromHandle(true, true, conn, rpcHandle)
} }
// listen is used to listen for incoming RPC connections // listen is used to listen for incoming RPC connections

View file

@ -1704,26 +1704,25 @@ func (p *PlanResult) FullCommit(plan *Plan) (bool, int, int) {
} }
// msgpackHandle is a shared handle for encoding/decoding of structs // msgpackHandle is a shared handle for encoding/decoding of structs
var MsgpackHandle = func() *codec.MsgpackHandle { var msgpackHandle = func() *codec.MsgpackHandle {
h := &codec.MsgpackHandle{RawToString: true} h := &codec.MsgpackHandle{RawToString: true}
// Sets the default type for decoding a map into a nil interface{}. // Sets the default type for decoding a map into a nil interface{}.
// This is necessary in particular because we store the driver configs as a // This is necessary in particular because we store the driver configs as a
// nil interface{}. // nil interface{}.
h.MapType = reflect.TypeOf(map[string]interface{}(nil)) h.MapType = reflect.TypeOf(map[string]interface{}(nil))
h.SliceType = reflect.TypeOf([]string{})
return h return h
}() }()
// Decode is used to decode a MsgPack encoded object // Decode is used to decode a MsgPack encoded object
func Decode(buf []byte, out interface{}) error { func Decode(buf []byte, out interface{}) error {
return codec.NewDecoder(bytes.NewReader(buf), MsgpackHandle).Decode(out) return codec.NewDecoder(bytes.NewReader(buf), msgpackHandle).Decode(out)
} }
// Encode is used to encode a MsgPack object with type prefix // Encode is used to encode a MsgPack object with type prefix
func Encode(t MessageType, msg interface{}) ([]byte, error) { func Encode(t MessageType, msg interface{}) ([]byte, error) {
var buf bytes.Buffer var buf bytes.Buffer
buf.WriteByte(uint8(t)) buf.WriteByte(uint8(t))
err := codec.NewEncoder(&buf, MsgpackHandle).Encode(msg) err := codec.NewEncoder(&buf, msgpackHandle).Encode(msg)
return buf.Bytes(), err return buf.Bytes(), err
} }

View file

@ -7,7 +7,6 @@ import (
"time" "time"
"github.com/hashicorp/go-msgpack/codec" "github.com/hashicorp/go-msgpack/codec"
"github.com/hashicorp/nomad/nomad/structs"
) )
func TestTimeTable(t *testing.T) { func TestTimeTable(t *testing.T) {
@ -105,14 +104,14 @@ func TestTimeTable_SerializeDeserialize(t *testing.T) {
tt.Witness(50, plusHour) tt.Witness(50, plusHour)
var buf bytes.Buffer var buf bytes.Buffer
enc := codec.NewEncoder(&buf, structs.MsgpackHandle) enc := codec.NewEncoder(&buf, msgpackHandle)
err := tt.Serialize(enc) err := tt.Serialize(enc)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
dec := codec.NewDecoder(&buf, structs.MsgpackHandle) dec := codec.NewDecoder(&buf, msgpackHandle)
tt2 := NewTimeTable(time.Second, time.Minute) tt2 := NewTimeTable(time.Second, time.Minute)
err = tt2.Deserialize(dec) err = tt2.Deserialize(dec)

View file

@ -19,13 +19,13 @@ and cleaning up after containers.
The `docker` driver supports the following configuration in the job The `docker` driver supports the following configuration in the job
specification: specification:
* `image` - The Docker image to run. The image may include a tag or * `image` - (Required) The Docker image to run. The image may include a tag or
custom URL. By default it will be fetched from Docker Hub. custom URL. By default it will be fetched from Docker Hub.
* `command` - (Optional) The command to run when starting the container. * `command` - (Optional) The command to run when starting the container.
* `args` - (Optional) A list of arguments to the optional `command`. If no * `args` - (Optional) Arguments to the optional `command`. If no `command` is
`command` is present, `args` are ignored. present, `args` are ignored.
* `network_mode` - (Optional) The network mode to be used for the container. In * `network_mode` - (Optional) The network mode to be used for the container. In
order to support userspace networking plugins in Docker 1.9 this accepts any order to support userspace networking plugins in Docker 1.9 this accepts any

View file

@ -20,19 +20,15 @@ scripts or other wrappers which provide higher level features.
The `exec` driver supports the following configuration in the job spec: The `exec` driver supports the following configuration in the job spec:
* `command` - The command to execute. Must be provided. * `command` - (Required) The command to execute. Must be provided.
* `artifact_source` (Optional) Source location of an executable artifact. Must be accessible
* `artifact_source` (Optional) Source location of an executable artifact. Must from the Nomad client. If you specify an `artifact_source` to be executed, you
be accessible from the Nomad client. If you specify an `artifact_source` to be must reference it in the `command` as show in the examples below
executed, you must reference it in the `command` as show in the examples below * `checksum` - **(Optional)** The checksum type and value for the `artifact_source` image.
The format is `type:value`, where type is any of `md5`, `sha1`, `sha256`, or `sha512`,
* `checksum` - (Optional) The checksum type and value for the `artifact_source` and the value is the computed checksum. If a checksum is supplied and does not
image. The format is `type:value`, where type is any of `md5`, `sha1`, match the downloaded artifact, the driver will fail to start
`sha256`, or `sha512`, and the value is the computed checksum. If a checksum * `args` - The argument list to the command, space seperated. Optional.
is supplied and does not match the downloaded artifact, the driver will fail
to start
* `args` - (Optional) A list of arguments to the `command`.
## Client Requirements ## Client Requirements

View file

@ -18,19 +18,17 @@ HTTP from the Nomad client.
The `java` driver supports the following configuration in the job spec: The `java` driver supports the following configuration in the job spec:
* `artifact_source` - The hosted location of the source Jar file. Must be * `artifact_source` - **(Required)** The hosted location of the source Jar file. Must be accessible
accessible from the Nomad client from the Nomad client
* `checksum` - **(Optional)** The checksum type and value for the `artifact_source` image.
The format is `type:value`, where type is any of `md5`, `sha1`, `sha256`, or `sha512`,
and the value is the computed checksum. If a checksum is supplied and does not
match the downloaded artifact, the driver will fail to start
* `checksum` - (Optional) The checksum type and value for the `artifact_source` * `args` - **(Optional)** The argument list for the `java` command, space separated.
image. The format is `type:value`, where type is any of `md5`, `sha1`,
`sha256`, or `sha512`, and the value is the computed checksum. If a checksum
is supplied and does not match the downloaded artifact, the driver will fail
to start
* `args` - (Optional) A list of arguments to the `java` command. * `jvm_options` - **(Optional)** JVM options to be passed while invoking java. These options
are passed not validated in any way in Nomad.
* `jvm_options` - (Optional) A list of JVM options to be passed while invoking
java. These options are passed not validated in any way in Nomad.
## Client Requirements ## Client Requirements

View file

@ -23,19 +23,16 @@ The `Qemu` driver can execute any regular `qemu` image (e.g. `qcow`, `img`,
The `Qemu` driver supports the following configuration in the job spec: The `Qemu` driver supports the following configuration in the job spec:
* `artifact_source` - The hosted location of the source Qemu image. Must be accessible * `artifact_source` - **(Required)** The hosted location of the source Qemu image. Must be accessible
from the Nomad client, via HTTP. from the Nomad client, via HTTP.
* `checksum` - **(Optional)** The checksum type and value for the `artifact_source` image.
* `checksum` - (Optional) The checksum type and value for the `artifact_source` image.
The format is `type:value`, where type is any of `md5`, `sha1`, `sha256`, or `sha512`, The format is `type:value`, where type is any of `md5`, `sha1`, `sha256`, or `sha512`,
and the value is the computed checksum. If a checksum is supplied and does not and the value is the computed checksum. If a checksum is supplied and does not
match the downloaded artifact, the driver will fail to start match the downloaded artifact, the driver will fail to start
* `accelerator` - (Optional) The type of accelerator to use in the invocation. * `accelerator` - (Optional) The type of accelerator to use in the invocation.
If the host machine has `Qemu` installed with KVM support, users can specify If the host machine has `Qemu` installed with KVM support, users can specify
`kvm` for the `accelerator`. Default is `tcg` `kvm` for the `accelerator`. Default is `tcg`
* `port_map` - **(Optional)** A `map[string]int` that maps port labels to ports
* `port_map` - (Optional) A `map[string]int` that maps port labels to ports
on the guest. This forwards the host port to the guest vm. For example, on the guest. This forwards the host port to the guest vm. For example,
`port_map { db = 6539 }` would forward the host port with label `db` to the `port_map { db = 6539 }` would forward the host port with label `db` to the
guest vm's port 6539. guest vm's port 6539.

View file

@ -18,19 +18,15 @@ As such, it should be used with extreme care and is disabled by default.
The `raw_exec` driver supports the following configuration in the job spec: The `raw_exec` driver supports the following configuration in the job spec:
* `command` - The command to execute. Must be provided. * `command` - (Required) The command to execute. Must be provided.
* `artifact_source` (Optional) Source location of an executable artifact. Must be accessible
* `artifact_source` (Optional) Source location of an executable artifact. Must from the Nomad client. If you specify an `artifact_source` to be executed, you
be accessible from the Nomad client. If you specify an `artifact_source` to be must reference it in the `command` as show in the examples below
executed, you must reference it in the `command` as show in the examples below * `checksum` - **(Optional)** The checksum type and value for the `artifact_source` image.
The format is `type:value`, where type is any of `md5`, `sha1`, `sha256`, or `sha512`,
* `checksum` - (Optional) The checksum type and value for the `artifact_source` and the value is the computed checksum. If a checksum is supplied and does not
image. The format is `type:value`, where type is any of `md5`, `sha1`, match the downloaded artifact, the driver will fail to start
`sha256`, or `sha512`, and the value is the computed checksum. If a checksum * `args` - The argument list to the command, space seperated. Optional.
is supplied and does not match the downloaded artifact, the driver will fail
to start
* `args` - (Optional) A list of arguments to the `command`.
## Client Requirements ## Client Requirements

View file

@ -20,16 +20,13 @@ being marked as experimental and should be used with care.
The `rkt` driver supports the following configuration in the job spec: The `rkt` driver supports the following configuration in the job spec:
* `image` - The image to run which may be specified by name, hash, ACI address * `trust_prefix` - **(Optional)** The trust prefix to be passed to rkt. Must be reachable from
or docker registry. the box running the nomad agent. If not specified, the image is run without
verifying the image signature.
* `command` - (Optional) A command to execute on the ACI. * `image` - **(Required)** The image to run which may be specified by name,
hash, ACI address or docker registry.
* `args` - (Optional) A list of arguments to the image. * `command` - **(Optional**) A command to execute on the ACI.
* `args` - **(Optional**) A string of args to pass into the image.
* `trust_prefix` - (Optional) The trust prefix to be passed to rkt. Must be
reachable from the box running the nomad agent. If not specified, the image is
run without verifying the image signature.
## Task Directories ## Task Directories