diff --git a/api/tasks.go b/api/tasks.go index 8c681fde9..b6f89c04d 100644 --- a/api/tasks.go +++ b/api/tasks.go @@ -336,6 +336,7 @@ type Template struct { Perms *string `mapstructure:"perms"` LeftDelim *string `mapstructure:"left_delimiter"` RightDelim *string `mapstructure:"right_delimiter"` + Envvars *bool `mapstructure:"env"` } func (tmpl *Template) Canonicalize() { @@ -373,6 +374,9 @@ func (tmpl *Template) Canonicalize() { if tmpl.RightDelim == nil { tmpl.RightDelim = helper.StringToPtr("}}") } + if tmpl.Envvars == nil { + tmpl.Envvars = helper.BoolToPtr(false) + } } type Vault struct { diff --git a/client/driver/driver.go b/client/driver/driver.go index ee3298c6c..8a3af3b8f 100644 --- a/client/driver/driver.go +++ b/client/driver/driver.go @@ -1,6 +1,8 @@ package driver import ( + "bufio" + "bytes" "context" "crypto/md5" "errors" @@ -8,6 +10,7 @@ import ( "io" "log" "os" + "path/filepath" "strings" "github.com/hashicorp/nomad/client/allocdir" @@ -314,6 +317,26 @@ func GetTaskEnv(taskDir *allocdir.TaskDir, node *structs.Node, SetEnvvars(task.Env). SetTaskName(task.Name) + // Set env vars from env files + for _, tmpl := range task.Templates { + if !tmpl.Envvars { + continue + } + f, err := os.Open(filepath.Join(taskDir.Dir, tmpl.DestPath)) + if err != nil { + //FIXME GetTaskEnv may be called before env files are written + log.Printf("[DEBUG] driver: XXX FIXME Templates not rendered yet, skipping") + continue + } + defer f.Close() + vars, err := parseEnvFile(f) + if err != nil { + //TODO soft or hard fail?! + return nil, err + } + env.AppendEnvvars(vars) + } + // Vary paths by filesystem isolation used drv, err := NewDriver(task.Driver, NewEmptyDriverContext()) if err != nil { @@ -355,6 +378,41 @@ func GetTaskEnv(taskDir *allocdir.TaskDir, node *structs.Node, return env.Build(), nil } +// parseEnvFile and return a map of the environment variables suitable for +// TaskEnvironment.AppendEnvvars or an error. +// +// See nomad/structs#Template.Envvars comment for format. +func parseEnvFile(r io.Reader) (map[string]string, error) { + vars := make(map[string]string, 50) + lines := 0 + scanner := bufio.NewScanner(r) + for scanner.Scan() { + lines++ + buf := scanner.Bytes() + if len(buf) == 0 { + // Skip empty lines + continue + } + if buf[0] == '#' { + // Skip lines starting with a # + continue + } + n := bytes.IndexByte(buf, '=') + if n == -1 { + return nil, fmt.Errorf("error on line %d: no '=' sign: %q", lines, string(buf)) + } + if len(buf) > n { + vars[string(buf[0:n])] = string(buf[n+1 : len(buf)]) + } else { + vars[string(buf[0:n])] = "" + } + } + if err := scanner.Err(); err != nil { + return nil, err + } + return vars, nil +} + func mapMergeStrInt(maps ...map[string]int) map[string]int { out := map[string]int{} for _, in := range maps { diff --git a/client/task_runner.go b/client/task_runner.go index 373525868..7afc1958a 100644 --- a/client/task_runner.go +++ b/client/task_runner.go @@ -1339,6 +1339,12 @@ func (r *TaskRunner) killTask(killingEvent *structs.TaskEvent) { // startTask creates the driver, task dir, and starts the task. func (r *TaskRunner) startTask() error { + // Env vars may have been updated prior to task starting, so update the + // env vars before starting the task + if err := r.setTaskEnv(); err != nil { + return fmt.Errorf("failed updating environment before starting task: %v", err) + } + // Create a driver drv, err := r.createDriver() if err != nil { diff --git a/command/agent/job_endpoint.go b/command/agent/job_endpoint.go index ac4bec896..874f3d7a1 100644 --- a/command/agent/job_endpoint.go +++ b/command/agent/job_endpoint.go @@ -680,6 +680,7 @@ func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) { Perms: *template.Perms, LeftDelim: *template.LeftDelim, RightDelim: *template.RightDelim, + Envvars: *template.Envvars, } } } diff --git a/jobspec/parse.go b/jobspec/parse.go index 8eef5baa7..f58d8f799 100644 --- a/jobspec/parse.go +++ b/jobspec/parse.go @@ -861,6 +861,7 @@ func parseTemplates(result *[]*api.Template, list *ast.ObjectList) error { "right_delimiter", "source", "splay", + "env", } if err := checkHCLKeys(o.Val, valid); err != nil { return err diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 8fb35a54d..515ab5d4c 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -2943,6 +2943,18 @@ type Template struct { // delimiter is utilized when parsing the template. LeftDelim string RightDelim string + + // Envvars enables exposing the template as environment variables + // instead of as a file. The template must be of the form: + // + // VAR_NAME_1={{ key service/my-key }} + // VAR_NAME_2=raw string and {{ env "attr.kernel.name" }} + // + // Lines will be split on the initial "=" with the first part being the + // key name and the second part the value. + // Empty lines and lines starting with # will be ignored, but to avoid + // escaping issues #s within lines will not be treated as comments. + Envvars bool } // DefaultTemplate returns a default template.