diff --git a/README.md b/README.md index 7076b436..eb13a0de 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,9 @@ releases: values: - 1 - 2 + # set a templated value + - name: namespace + value: {{ .Namespace }} # will attempt to decrypt it using helm-secrets plugin secrets: - vault_secret.yaml @@ -200,7 +203,7 @@ GLOBAL OPTIONS: --quiet, -q Silence output. Equivalent to log-level warn --kube-context value Set kubectl context. Uses current context by default --log-level value Set log level, default info - --namespace value, -n value Set namespace. Uses the namespace set in the context by default + --namespace value, -n value Set namespace. Uses the namespace set in the context by default, and is available in templates as {{ .Namespace }} --selector value, -l value Only run using the releases that match labels. Labels can take the form of foo=bar or foo!=bar. A release must match all labels in a group in order to be used. Multiple groups can be specified at once. --selector tier=frontend,tier!=proxy --selector tier=backend. Will match all frontend, non-proxy releases AND all backend releases. diff --git a/main.go b/main.go index 263d750c..b502ebbf 100644 --- a/main.go +++ b/main.go @@ -91,7 +91,7 @@ func main() { }, cli.StringFlag{ Name: "namespace, n", - Usage: "Set namespace. Uses the namespace set in the context by default", + Usage: "Set namespace. Uses the namespace set in the context by default, and is available in templates as {{ .Namespace }}", }, cli.StringSliceFlag{ Name: "selector, l", @@ -636,11 +636,12 @@ func prependLineNumbers(text string) string { } type twoPassRenderer struct { - reader func(string) ([]byte, error) - env string - filename string - logger *zap.SugaredLogger - abs func(string) (string, error) + reader func(string) ([]byte, error) + env string + namespace string + filename string + logger *zap.SugaredLogger + abs func(string) (string, error) } func (r *twoPassRenderer) renderEnvironment(content []byte) environment.Environment { @@ -673,7 +674,7 @@ func (r *twoPassRenderer) renderTemplate(content []byte) (*bytes.Buffer, error) // try a first pass render. This will always succeed, but can produce a limited env firstPassEnv := r.renderEnvironment(content) - secondPassRenderer := tmpl.NewFileRenderer(r.reader, filepath.Dir(r.filename), firstPassEnv) + secondPassRenderer := tmpl.NewFileRenderer(r.reader, filepath.Dir(r.filename), firstPassEnv, r.namespace) yamlBuf, err := secondPassRenderer.RenderTemplateContentToBuffer(content) if err != nil { if r.logger != nil { @@ -702,11 +703,12 @@ func (a *app) FindAndIterateOverDesiredStates(fileOrDir string, converge func(*s } // render template, in two runs r := &twoPassRenderer{ - reader: a.readFile, - env: env, - filename: f, - logger: a.logger, - abs: a.abs, + reader: a.readFile, + env: env, + namespace: namespace, + filename: f, + logger: a.logger, + abs: a.abs, } yamlBuf, err := r.renderTemplate(content) if err != nil { diff --git a/main_test.go b/main_test.go index 284ed90c..bb1dd74c 100644 --- a/main_test.go +++ b/main_test.go @@ -43,11 +43,12 @@ func TestReadFromYaml_DuplicateReleaseName(t *testing.T) { func makeRenderer(readFile func(string) ([]byte, error), env string) *twoPassRenderer { return &twoPassRenderer{ - reader: readFile, - env: env, - filename: "", - logger: logger, - abs: filepath.Abs, + reader: readFile, + env: env, + namespace: "namespace", + filename: "", + logger: logger, + abs: filepath.Abs, } } @@ -99,7 +100,7 @@ releases: func TestReadFromYaml_RenderTemplate(t *testing.T) { - defaultValuesYalm := []byte(` + defaultValuesYaml := []byte(` releaseName: "hello" conditionalReleaseTag: "yes" `) @@ -127,7 +128,7 @@ releases: if !strings.HasSuffix(filename, expectedFilename) { return nil, fmt.Errorf("unexpected filename: expected=%s, actual=%s", expectedFilename, filename) } - return defaultValuesYalm, nil + return defaultValuesYaml, nil } r := makeRenderer(fileReader, "staging") @@ -158,7 +159,7 @@ releases: } func TestReadFromYaml_RenderTemplateWithValuesReferenceError(t *testing.T) { - defaultValuesYalm := []byte("") + defaultValuesYaml := []byte("") yamlContent := []byte(` environments: @@ -176,7 +177,7 @@ releases: // make a reader that returns a simulated context fileReader := func(filename string) ([]byte, error) { - return defaultValuesYalm, nil + return defaultValuesYaml, nil } r := makeRenderer(fileReader, "staging") @@ -193,7 +194,7 @@ releases: // This does not apply to .gotmpl files, which is a nice side-effect. func TestReadFromYaml_RenderTemplateWithGotmpl(t *testing.T) { - defaultValuesYalmGotmpl := []byte(` + defaultValuesYamlGotmpl := []byte(` releaseName: {{ readFile "nonIgnoredFile" }} `) @@ -215,7 +216,7 @@ releases: if strings.HasSuffix(filename, "nonIgnoredFile") { return []byte("release-a"), nil } - return defaultValuesYalmGotmpl, nil + return defaultValuesYamlGotmpl, nil } r := makeRenderer(fileReader, "staging") @@ -232,3 +233,29 @@ releases: t.Fatal("release should have been declared") } } + +func TestReadFromYaml_RenderTemplateWithNamespace(t *testing.T) { + defaultValuesYaml := []byte(``) + yamlContent := []byte(`releases: +- name: {{ .Namespace }}-myrelease + chart: mychart +`) + + // make a reader that returns a simulated context + fileReader := func(filename string) ([]byte, error) { + return defaultValuesYaml, nil + } + + r := makeRenderer(fileReader, "staging") + yamlBuf, err := r.renderTemplate(yamlContent) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + var state state.HelmState + err = yaml.Unmarshal(yamlBuf.Bytes(), &state) + + if state.Releases[0].Name != "namespace-myrelease" { + t.Errorf("release name should be namespace-myrelease") + } +} diff --git a/test/integration/run.sh b/test/integration/run.sh index e16ad880..31d1ef8a 100755 --- a/test/integration/run.sh +++ b/test/integration/run.sh @@ -61,7 +61,7 @@ wait_deploy_ready httpbin-httpbin retry 5 "curl --fail $(minikube service --url --namespace=${test_ns} httpbin-httpbin)/status/200" [ ${retry_result} -eq 0 ] || fail "httpbin failed to return 200 OK" info "Deleting release" -${helmfile} -f ${dir}/happypath.yaml delete +${helmfile} -f ${dir}/happypath.yaml delete --auto-approve ${helm} status --namespace=${test_ns} httpbin &> /dev/null && fail "release should not exist anymore after a delete" test_pass "happypath" diff --git a/tmpl/file.go b/tmpl/file.go index 5b3e6b9b..ad2d83fe 100644 --- a/tmpl/file.go +++ b/tmpl/file.go @@ -16,13 +16,15 @@ type templateFileRenderer struct { type TemplateData struct { // Environment is accessible as `.Environment` from any template executed by the renderer Environment environment.Environment + // Namespace is accessible as `.Namespace` from any non-values template executed by the renderer + Namespace string } type FileRenderer interface { RenderTemplateFileToBuffer(file string) (*bytes.Buffer, error) } -func NewFileRenderer(readFile func(filename string) ([]byte, error), basePath string, env environment.Environment) *templateFileRenderer { +func NewFileRenderer(readFile func(filename string) ([]byte, error), basePath string, env environment.Environment, namespace string) *templateFileRenderer { return &templateFileRenderer{ ReadFile: readFile, Context: &Context{ @@ -31,6 +33,7 @@ func NewFileRenderer(readFile func(filename string) ([]byte, error), basePath st }, Data: TemplateData{ Environment: env, + Namespace: namespace, }, } } diff --git a/valuesfile/valuesfile.go b/valuesfile/valuesfile.go index f964cb99..0bed26a4 100644 --- a/valuesfile/valuesfile.go +++ b/valuesfile/valuesfile.go @@ -15,7 +15,7 @@ type renderer struct { func NewRenderer(readFile func(filename string) ([]byte, error), basePath string, env environment.Environment) *renderer { return &renderer{ readFile: readFile, - tmplFileRenderer: tmpl.NewFileRenderer(readFile, basePath, env), + tmplFileRenderer: tmpl.NewFileRenderer(readFile, basePath, env, ""), } }