-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add envconsul-like support and refactor environment handling #2654
Changes from 18 commits
7dac668
ace0098
3b24980
96753dc
37c1cbc
6db3501
3743d34
a96fb5d
1295f88
59b3cca
72a24ae
1188924
d7eccc1
9c66952
826aec4
0288582
43cc0a0
6571fa5
8f2ea2f
572ca97
be959a4
9ccb53d
fc5254d
de4b008
c4aa3c3
6d0bc98
aa0e977
68db2aa
5933cac
361db24
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,10 @@ | ||
package client | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"math/rand" | ||
"os" | ||
"path/filepath" | ||
|
@@ -76,7 +79,7 @@ type TaskTemplateManager struct { | |
|
||
func NewTaskTemplateManager(hook TaskHooks, tmpls []*structs.Template, | ||
config *config.Config, vaultToken, taskDir string, | ||
taskEnv *env.TaskEnvironment) (*TaskTemplateManager, error) { | ||
envBuilder *env.Builder) (*TaskTemplateManager, error) { | ||
|
||
// Check pre-conditions | ||
if hook == nil { | ||
|
@@ -85,7 +88,7 @@ func NewTaskTemplateManager(hook TaskHooks, tmpls []*structs.Template, | |
return nil, fmt.Errorf("Invalid config given") | ||
} else if taskDir == "" { | ||
return nil, fmt.Errorf("Invalid task directory given") | ||
} else if taskEnv == nil { | ||
} else if envBuilder == nil { | ||
return nil, fmt.Errorf("Invalid task environment given") | ||
} | ||
|
||
|
@@ -114,14 +117,14 @@ func NewTaskTemplateManager(hook TaskHooks, tmpls []*structs.Template, | |
} | ||
|
||
// Build the consul-template runner | ||
runner, lookup, err := templateRunner(tmpls, config, vaultToken, taskDir, taskEnv) | ||
runner, lookup, err := templateRunner(tmpls, config, vaultToken, taskDir, envBuilder.Build()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
tm.runner = runner | ||
tm.lookup = lookup | ||
|
||
go tm.run() | ||
go tm.run(envBuilder, taskDir) | ||
return tm, nil | ||
} | ||
|
||
|
@@ -144,7 +147,7 @@ func (tm *TaskTemplateManager) Stop() { | |
} | ||
|
||
// run is the long lived loop that handles errors and templates being rendered | ||
func (tm *TaskTemplateManager) run() { | ||
func (tm *TaskTemplateManager) run(envBuilder *env.Builder, taskDir string) { | ||
// Runner is nil if there is no templates | ||
if tm.runner == nil { | ||
// Unblock the start if there is nothing to do | ||
|
@@ -192,6 +195,12 @@ WAIT: | |
} | ||
} | ||
|
||
for _, t := range tm.templates { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment explaining what you are doing. Blank line between the block please |
||
if err := loadTemplateEnv(envBuilder, taskDir, t); err != nil { | ||
tm.hook.Kill("consul-template", err.Error(), true) | ||
return | ||
} | ||
} | ||
allRenderedTime = time.Now() | ||
tm.hook.UnblockStart("consul-template") | ||
|
||
|
@@ -243,6 +252,11 @@ WAIT: | |
} | ||
|
||
for _, tmpl := range tmpls { | ||
if err := loadTemplateEnv(envBuilder, taskDir, tmpl); err != nil { | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Extra line |
||
tm.hook.Kill("consul-template", err.Error(), true) | ||
return | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blank below |
||
switch tmpl.ChangeMode { | ||
case structs.TemplateChangeModeSignal: | ||
signals[tmpl.ChangeSignal] = struct{}{} | ||
|
@@ -317,7 +331,7 @@ func (tm *TaskTemplateManager) allTemplatesNoop() bool { | |
// lookup by destination to the template. If no templates are given, a nil | ||
// template runner and lookup is returned. | ||
func templateRunner(tmpls []*structs.Template, config *config.Config, | ||
vaultToken, taskDir string, taskEnv *env.TaskEnvironment) ( | ||
vaultToken, taskDir string, taskEnv *env.TaskEnv) ( | ||
*manager.Runner, map[string][]*structs.Template, error) { | ||
|
||
if len(tmpls) == 0 { | ||
|
@@ -350,7 +364,7 @@ func templateRunner(tmpls []*structs.Template, config *config.Config, | |
} | ||
|
||
// Set Nomad's environment variables | ||
runner.Env = taskEnv.Build().EnvMapAll() | ||
runner.Env = taskEnv.All() | ||
|
||
// Build the lookup | ||
idMap := runner.TemplateConfigMapping() | ||
|
@@ -368,9 +382,7 @@ func templateRunner(tmpls []*structs.Template, config *config.Config, | |
|
||
// parseTemplateConfigs converts the tasks templates into consul-templates | ||
func parseTemplateConfigs(tmpls []*structs.Template, taskDir string, | ||
taskEnv *env.TaskEnvironment, allowAbs bool) (map[ctconf.TemplateConfig]*structs.Template, error) { | ||
// Build the task environment | ||
taskEnv.Build() | ||
taskEnv *env.TaskEnv, allowAbs bool) (map[ctconf.TemplateConfig]*structs.Template, error) { | ||
|
||
ctmpls := make(map[ctconf.TemplateConfig]*structs.Template, len(tmpls)) | ||
for _, tmpl := range tmpls { | ||
|
@@ -492,3 +504,60 @@ func runnerConfig(config *config.Config, vaultToken string) (*ctconf.Config, err | |
conf.Finalize() | ||
return conf, nil | ||
} | ||
|
||
// loadTemplateEnv loads task environment variables from templates. | ||
func loadTemplateEnv(builder *env.Builder, taskDir string, t *structs.Template) error { | ||
if !t.Envvars { | ||
return nil | ||
} | ||
f, err := os.Open(filepath.Join(taskDir, t.DestPath)) | ||
if err != nil { | ||
return fmt.Errorf("error opening env template: %v", err) | ||
} | ||
defer f.Close() | ||
|
||
// Parse environment fil | ||
vars, err := parseEnvFile(f) | ||
if err != nil { | ||
return fmt.Errorf("error parsing env template %q: %v", t.DestPath, err) | ||
} | ||
|
||
// Set the environment variables | ||
builder.SetTemplateEnv(vars) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This breaks if they have multiple templates generating environment variables. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wow, good catch. Will add a test as well. |
||
return 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It may be worth just using this: https://github.com/joho/godotenv There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lots of edge cases we don't need to handle There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is tied to a template it must be a single file. It's probably worth documenting how to use env files from artifacts by setting source path to where the artifact is downloaded, but env dirs (or autoloading |
||
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("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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Believe there is a canonicalize test as well