mirror of
https://github.com/helmfile/helmfile.git
synced 2026-10-18 09:49:49 +02:00
feat: Advanced Templating (#823)
1. Added `helmfile build` command to print final state Motivation: useful for debugging purposes and some CI scenarios Ref #780 2. Template interpolation is now recursive (you can cross-reference release fields) like: ```yaml templates: release: name: {{`app-{{ .Release.Namespace }}`}} namespace: {{`{{ .Release.Labels.ns }}`}} labels: ns: dev ``` 3. Experimental: Added some boolean release fields interpolation in templates: ```yaml templates: release: name: {{`app-{{ .Release.Namespace }}`}} namespace: dev installedTemplate: {{`{{ eq .Release.Namespace "dev" }}`}} ``` Resolves #818 4. Added more template interpolations: Labels, SetValues 5. Added template interpolation for inline Values 6. Added `helmfile list` command to print target releases in simple tabular form 7. Added release names in some `helm` output messages, e.g.: `Comparing release=%v, chart=%v`
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/gosuri/uitable"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/remote"
|
||||
"github.com/roboll/helmfile/pkg/state"
|
||||
@@ -158,6 +159,38 @@ func (a *App) Test(c TestConfigProvider) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (a *App) PrintState(c StateConfigProvider) error {
|
||||
|
||||
return a.ForEachState(func(run *Run) []error {
|
||||
state, err := run.state.ToYaml()
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
fmt.Printf("---\n# Source: %s\n\n%+v", run.state.FilePath, state)
|
||||
return []error{}
|
||||
})
|
||||
}
|
||||
|
||||
func (a *App) ListReleases(c StateConfigProvider) error {
|
||||
table := uitable.New()
|
||||
table.AddRow("NAME", "NAMESPACE", "INSTALLED", "LABELS")
|
||||
|
||||
err := a.ForEachState(func(run *Run) []error {
|
||||
//var releases m
|
||||
for _, r := range run.state.Releases {
|
||||
labels := ""
|
||||
for k, v := range r.Labels {
|
||||
labels = fmt.Sprintf("%s,%s:%s", labels, k, v)
|
||||
}
|
||||
installed := r.Installed == nil || *r.Installed
|
||||
table.AddRow(r.Name, r.Namespace, fmt.Sprintf("%t", installed), strings.Trim(labels, ","))
|
||||
}
|
||||
return []error{}
|
||||
})
|
||||
fmt.Println(table.String())
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *App) within(dir string, do func() error) error {
|
||||
if dir == "." {
|
||||
return do()
|
||||
|
||||
+169
-6
@@ -3,15 +3,21 @@ package app
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/state"
|
||||
"github.com/roboll/helmfile/pkg/testhelper"
|
||||
"gotest.tools/assert"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/state"
|
||||
"github.com/roboll/helmfile/pkg/testhelper"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gotest.tools/env"
|
||||
)
|
||||
@@ -1840,7 +1846,7 @@ type mockTemplates struct {
|
||||
flags []string
|
||||
}
|
||||
|
||||
func (helm *mockHelmExec) TemplateRelease(chart string, flags ...string) error {
|
||||
func (helm *mockHelmExec) TemplateRelease(name, chart string, flags ...string) error {
|
||||
helm.templated = append(helm.templated, mockTemplates{flags: flags})
|
||||
return nil
|
||||
}
|
||||
@@ -1849,7 +1855,7 @@ func (helm *mockHelmExec) UpdateDeps(chart string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (helm *mockHelmExec) BuildDeps(chart string) error {
|
||||
func (helm *mockHelmExec) BuildDeps(name, chart string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1889,7 +1895,7 @@ func (helm *mockHelmExec) TestRelease(context helmexec.HelmContext, name string,
|
||||
func (helm *mockHelmExec) Fetch(chart string, flags ...string) error {
|
||||
return nil
|
||||
}
|
||||
func (helm *mockHelmExec) Lint(chart string, flags ...string) error {
|
||||
func (helm *mockHelmExec) Lint(name, chart string, flags ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1938,3 +1944,160 @@ releases:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func captureStdout(f func()) string {
|
||||
reader, writer, err := os.Pipe()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
stdout := os.Stdout
|
||||
defer func() {
|
||||
os.Stdout = stdout
|
||||
log.SetOutput(os.Stderr)
|
||||
}()
|
||||
os.Stdout = writer
|
||||
log.SetOutput(writer)
|
||||
out := make(chan string)
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
var buf bytes.Buffer
|
||||
wg.Done()
|
||||
io.Copy(&buf, reader)
|
||||
out <- buf.String()
|
||||
}()
|
||||
wg.Wait()
|
||||
f()
|
||||
writer.Close()
|
||||
return <-out
|
||||
}
|
||||
|
||||
func TestPrint_SingleStateFile(t *testing.T) {
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: myrelease1
|
||||
chart: mychart1
|
||||
- name: myrelease2
|
||||
chart: mychart1
|
||||
`,
|
||||
}
|
||||
stdout := os.Stdout
|
||||
defer func() { os.Stdout = stdout }()
|
||||
|
||||
var buffer bytes.Buffer
|
||||
logger := helmexec.NewLogger(&buffer, "debug")
|
||||
|
||||
app := appWithFs(&App{
|
||||
glob: filepath.Glob,
|
||||
abs: filepath.Abs,
|
||||
KubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
Namespace: "testNamespace",
|
||||
}, files)
|
||||
out := captureStdout(func() {
|
||||
err := app.PrintState(configImpl{})
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
assert.Assert(t, strings.Count(out, "---") == 1,
|
||||
"state should contain '---' yaml doc separator:\n%s\n", out)
|
||||
assert.Assert(t, strings.Contains(out, "helmfile.yaml"),
|
||||
"state should contain source helmfile name:\n%s\n", out)
|
||||
assert.Assert(t, strings.Contains(out, "name: myrelease1"),
|
||||
"state should contain releases:\n%s\n", out)
|
||||
}
|
||||
|
||||
func TestPrint_MultiStateFile(t *testing.T) {
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.d/first.yaml": `
|
||||
releases:
|
||||
- name: myrelease1
|
||||
chart: mychart1
|
||||
- name: myrelease2
|
||||
chart: mychart1
|
||||
`,
|
||||
"/path/to/helmfile.d/second.yaml": `
|
||||
releases:
|
||||
- name: myrelease3
|
||||
chart: mychart1
|
||||
- name: myrelease4
|
||||
chart: mychart1
|
||||
`,
|
||||
}
|
||||
stdout := os.Stdout
|
||||
defer func() { os.Stdout = stdout }()
|
||||
|
||||
var buffer bytes.Buffer
|
||||
logger := helmexec.NewLogger(&buffer, "debug")
|
||||
|
||||
app := appWithFs(&App{
|
||||
glob: filepath.Glob,
|
||||
abs: filepath.Abs,
|
||||
KubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
Namespace: "testNamespace",
|
||||
}, files)
|
||||
out := captureStdout(func() {
|
||||
err := app.PrintState(configImpl{})
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
assert.Assert(t, strings.Count(out, "---") == 2,
|
||||
"state should contain '---' yaml doc separators:\n%s\n", out)
|
||||
assert.Assert(t, strings.Contains(out, "second.yaml"),
|
||||
"state should contain source helmfile name:\n%s\n", out)
|
||||
assert.Assert(t, strings.Contains(out, "second.yaml"),
|
||||
"state should contain source helmfile name:\n%s\n", out)
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.d/first.yaml": `
|
||||
releases:
|
||||
- name: myrelease1
|
||||
chart: mychart1
|
||||
installed: no
|
||||
labels:
|
||||
id: myrelease1
|
||||
- name: myrelease2
|
||||
chart: mychart1
|
||||
`,
|
||||
"/path/to/helmfile.d/second.yaml": `
|
||||
releases:
|
||||
- name: myrelease3
|
||||
chart: mychart1
|
||||
installed: yes
|
||||
- name: myrelease4
|
||||
chart: mychart1
|
||||
labels:
|
||||
id: myrelease1
|
||||
`,
|
||||
}
|
||||
stdout := os.Stdout
|
||||
defer func() { os.Stdout = stdout }()
|
||||
|
||||
var buffer bytes.Buffer
|
||||
logger := helmexec.NewLogger(&buffer, "debug")
|
||||
|
||||
app := appWithFs(&App{
|
||||
glob: filepath.Glob,
|
||||
abs: filepath.Abs,
|
||||
KubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
Namespace: "testNamespace",
|
||||
}, files)
|
||||
out := captureStdout(func() {
|
||||
err := app.ListReleases(configImpl{})
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
|
||||
expected := `NAME NAMESPACE INSTALLED LABELS
|
||||
myrelease1 false id:myrelease1
|
||||
myrelease2 true
|
||||
myrelease3 true
|
||||
myrelease4 true id:myrelease1
|
||||
`
|
||||
assert.Equal(t, expected, out)
|
||||
}
|
||||
|
||||
@@ -120,6 +120,9 @@ type StatusesConfigProvider interface {
|
||||
concurrencyConfig
|
||||
}
|
||||
|
||||
type StateConfigProvider interface {
|
||||
}
|
||||
|
||||
type concurrencyConfig interface {
|
||||
Concurrency() int
|
||||
}
|
||||
|
||||
+2
-1
@@ -2,10 +2,11 @@ package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/argparser"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/state"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Run struct {
|
||||
|
||||
+14
-13
@@ -90,6 +90,13 @@ func (helm *execer) UpdateRepo() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) BuildDeps(name, chart string) error {
|
||||
helm.logger.Infof("Building dependency release=%v, chart=%v", name, chart)
|
||||
out, err := helm.exec([]string{"dependency", "build", chart}, map[string]string{})
|
||||
helm.info(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) UpdateDeps(chart string) error {
|
||||
helm.logger.Infof("Updating dependency %v", chart)
|
||||
out, err := helm.exec([]string{"dependency", "update", chart}, map[string]string{})
|
||||
@@ -97,15 +104,8 @@ func (helm *execer) UpdateDeps(chart string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) BuildDeps(chart string) error {
|
||||
helm.logger.Infof("Building dependency %v", chart)
|
||||
out, err := helm.exec([]string{"dependency", "build", chart}, map[string]string{})
|
||||
helm.info(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) SyncRelease(context HelmContext, name, chart string, flags ...string) error {
|
||||
helm.logger.Infof("Upgrading %v", chart)
|
||||
helm.logger.Infof("Upgrading release=%v, chart=%v", name, chart)
|
||||
preArgs := context.GetTillerlessArgs(helm.helmBinary)
|
||||
env := context.getTillerlessEnv()
|
||||
out, err := helm.exec(append(append(preArgs, "upgrade", "--install", "--reset-values", name, chart), flags...), env)
|
||||
@@ -199,14 +199,15 @@ func (helm *execer) DecryptSecret(context HelmContext, name string, flags ...str
|
||||
return tmpFile.Name(), err
|
||||
}
|
||||
|
||||
func (helm *execer) TemplateRelease(chart string, flags ...string) error {
|
||||
out, err := helm.exec(append([]string{"template", chart}, flags...), map[string]string{})
|
||||
func (helm *execer) TemplateRelease(name string, chart string, flags ...string) error {
|
||||
helm.logger.Infof("Templating release=%v, chart=%v", name, chart)
|
||||
out, err := helm.exec(append([]string{"template", chart, "--name", name}, flags...), map[string]string{})
|
||||
helm.write(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) DiffRelease(context HelmContext, name, chart string, flags ...string) error {
|
||||
helm.logger.Infof("Comparing %v %v", name, chart)
|
||||
helm.logger.Infof("Comparing release=%v, chart=%v", name, chart)
|
||||
preArgs := context.GetTillerlessArgs(helm.helmBinary)
|
||||
env := context.getTillerlessEnv()
|
||||
out, err := helm.exec(append(append(preArgs, "diff", "upgrade", "--reset-values", "--allow-unreleased", name, chart), flags...), env)
|
||||
@@ -233,8 +234,8 @@ func (helm *execer) DiffRelease(context HelmContext, name, chart string, flags .
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) Lint(chart string, flags ...string) error {
|
||||
helm.logger.Infof("Linting %v", chart)
|
||||
func (helm *execer) Lint(name, chart string, flags ...string) error {
|
||||
helm.logger.Infof("Linting release=%v, chart=%v", name, chart)
|
||||
out, err := helm.exec(append([]string{"lint", chart}, flags...), map[string]string{})
|
||||
helm.write(out)
|
||||
return err
|
||||
|
||||
+16
-15
@@ -135,7 +135,7 @@ func Test_SyncRelease(t *testing.T) {
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.SyncRelease(HelmContext{}, "release", "chart", "--timeout 10", "--wait")
|
||||
expected := `Upgrading chart
|
||||
expected := `Upgrading release=release, chart=chart
|
||||
exec: helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev
|
||||
exec: helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev:
|
||||
`
|
||||
@@ -145,7 +145,7 @@ exec: helm upgrade --install --reset-values release chart --timeout 10 --wait --
|
||||
|
||||
buffer.Reset()
|
||||
helm.SyncRelease(HelmContext{}, "release", "chart")
|
||||
expected = `Upgrading chart
|
||||
expected = `Upgrading release=release, chart=chart
|
||||
exec: helm upgrade --install --reset-values release chart --kube-context dev
|
||||
exec: helm upgrade --install --reset-values release chart --kube-context dev:
|
||||
`
|
||||
@@ -160,7 +160,7 @@ func Test_SyncReleaseTillerless(t *testing.T) {
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.SyncRelease(HelmContext{Tillerless: true, TillerNamespace: "foo"}, "release", "chart",
|
||||
"--timeout 10", "--wait")
|
||||
expected := `Upgrading chart
|
||||
expected := `Upgrading release=release, chart=chart
|
||||
exec: helm tiller run foo -- helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev
|
||||
exec: helm tiller run foo -- helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev:
|
||||
`
|
||||
@@ -198,8 +198,8 @@ func Test_BuildDeps(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.BuildDeps("./chart/foo")
|
||||
expected := `Building dependency ./chart/foo
|
||||
helm.BuildDeps("foo", "./chart/foo")
|
||||
expected := `Building dependency release=foo, chart=./chart/foo
|
||||
exec: helm dependency build ./chart/foo --kube-context dev
|
||||
exec: helm dependency build ./chart/foo --kube-context dev:
|
||||
`
|
||||
@@ -209,8 +209,8 @@ exec: helm dependency build ./chart/foo --kube-context dev:
|
||||
|
||||
buffer.Reset()
|
||||
helm.SetExtraArgs("--verify")
|
||||
helm.BuildDeps("./chart/foo")
|
||||
expected = `Building dependency ./chart/foo
|
||||
helm.BuildDeps("foo", "./chart/foo")
|
||||
expected = `Building dependency release=foo, chart=./chart/foo
|
||||
exec: helm dependency build ./chart/foo --verify --kube-context dev
|
||||
exec: helm dependency build ./chart/foo --verify --kube-context dev:
|
||||
`
|
||||
@@ -248,7 +248,7 @@ func Test_DiffRelease(t *testing.T) {
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.DiffRelease(HelmContext{}, "release", "chart", "--timeout 10", "--wait")
|
||||
expected := `Comparing release chart
|
||||
expected := `Comparing release=release, chart=chart
|
||||
exec: helm diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --kube-context dev
|
||||
exec: helm diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --kube-context dev:
|
||||
`
|
||||
@@ -258,7 +258,7 @@ exec: helm diff upgrade --reset-values --allow-unreleased release chart --timeou
|
||||
|
||||
buffer.Reset()
|
||||
helm.DiffRelease(HelmContext{}, "release", "chart")
|
||||
expected = `Comparing release chart
|
||||
expected = `Comparing release=release, chart=chart
|
||||
exec: helm diff upgrade --reset-values --allow-unreleased release chart --kube-context dev
|
||||
exec: helm diff upgrade --reset-values --allow-unreleased release chart --kube-context dev:
|
||||
`
|
||||
@@ -272,7 +272,7 @@ func Test_DiffReleaseTillerless(t *testing.T) {
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.DiffRelease(HelmContext{Tillerless: true}, "release", "chart", "--timeout 10", "--wait")
|
||||
expected := `Comparing release chart
|
||||
expected := `Comparing release=release, chart=chart
|
||||
exec: helm tiller run -- helm diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --kube-context dev
|
||||
exec: helm tiller run -- helm diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --kube-context dev:
|
||||
`
|
||||
@@ -413,8 +413,8 @@ func Test_Lint(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.Lint("path/to/chart", "--values", "file.yml")
|
||||
expected := `Linting path/to/chart
|
||||
helm.Lint("release", "path/to/chart", "--values", "file.yml")
|
||||
expected := `Linting release=release, chart=path/to/chart
|
||||
exec: helm lint path/to/chart --values file.yml --kube-context dev
|
||||
exec: helm lint path/to/chart --values file.yml --kube-context dev:
|
||||
`
|
||||
@@ -498,9 +498,10 @@ func Test_Template(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.TemplateRelease("path/to/chart", "--values", "file.yml")
|
||||
expected := `exec: helm template path/to/chart --values file.yml --kube-context dev
|
||||
exec: helm template path/to/chart --values file.yml --kube-context dev:
|
||||
helm.TemplateRelease("release", "path/to/chart", "--values", "file.yml")
|
||||
expected := `Templating release=release, chart=path/to/chart
|
||||
exec: helm template path/to/chart --name release --values file.yml --kube-context dev
|
||||
exec: helm template path/to/chart --name release --values file.yml --kube-context dev:
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.Template()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
|
||||
@@ -7,13 +7,13 @@ type Interface interface {
|
||||
|
||||
AddRepo(name, repository, certfile, keyfile, username, password string) error
|
||||
UpdateRepo() error
|
||||
BuildDeps(chart string) error
|
||||
BuildDeps(name, chart string) error
|
||||
UpdateDeps(chart string) error
|
||||
SyncRelease(context HelmContext, name, chart string, flags ...string) error
|
||||
DiffRelease(context HelmContext, name, chart string, flags ...string) error
|
||||
TemplateRelease(chart string, flags ...string) error
|
||||
TemplateRelease(name, chart string, flags ...string) error
|
||||
Fetch(chart string, flags ...string) error
|
||||
Lint(chart string, flags ...string) error
|
||||
Lint(name, chart string, flags ...string) error
|
||||
ReleaseStatus(context HelmContext, name string, flags ...string) error
|
||||
DeleteRelease(context HelmContext, name string, flags ...string) error
|
||||
TestRelease(context HelmContext, name string, flags ...string) error
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package state
|
||||
|
||||
type EnvironmentSpec struct {
|
||||
Values []interface{} `yaml:"values"`
|
||||
Secrets []string `yaml:"secrets"`
|
||||
Values []interface{} `yaml:"values,omitempty"`
|
||||
Secrets []string `yaml:"secrets,omitempty"`
|
||||
|
||||
// MissingFileHandler instructs helmfile to fail when unable to find a environment values file listed
|
||||
// under `environments.NAME.values`.
|
||||
@@ -11,5 +11,5 @@ type EnvironmentSpec struct {
|
||||
//
|
||||
// Use "Warn", "Info", or "Debug" if you want helmfile to not fail when a values file is missing, while just leaving
|
||||
// a message about the missing file at the log-level.
|
||||
MissingFileHandler *string `yaml:"missingFileHandler"`
|
||||
MissingFileHandler *string `yaml:"missingFileHandler,omitempty"`
|
||||
}
|
||||
|
||||
@@ -48,6 +48,51 @@ func (r ReleaseSpec) ExecuteTemplateExpressions(renderer *tmpl.FileRenderer) (*R
|
||||
}
|
||||
}
|
||||
|
||||
if result.WaitTemplate != nil {
|
||||
ts := *result.WaitTemplate
|
||||
resultTmpl, err := renderer.RenderTemplateContentToString([]byte(ts))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".version = \"%s\": %v", r.Name, ts, err)
|
||||
}
|
||||
result.WaitTemplate = &resultTmpl
|
||||
}
|
||||
|
||||
if result.InstalledTemplate != nil {
|
||||
ts := *result.InstalledTemplate
|
||||
resultTmpl, err := renderer.RenderTemplateContentToString([]byte(ts))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".version = \"%s\": %v", r.Name, ts, err)
|
||||
}
|
||||
result.InstalledTemplate = &resultTmpl
|
||||
}
|
||||
|
||||
if result.TillerlessTemplate != nil {
|
||||
ts := *result.TillerlessTemplate
|
||||
resultTmpl, err := renderer.RenderTemplateContentToString([]byte(ts))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".version = \"%s\": %v", r.Name, ts, err)
|
||||
}
|
||||
result.TillerlessTemplate = &resultTmpl
|
||||
}
|
||||
|
||||
if result.VerifyTemplate != nil {
|
||||
ts := *result.VerifyTemplate
|
||||
resultTmpl, err := renderer.RenderTemplateContentToString([]byte(ts))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".version = \"%s\": %v", r.Name, ts, err)
|
||||
}
|
||||
result.VerifyTemplate = &resultTmpl
|
||||
}
|
||||
|
||||
for key, val := range result.Labels {
|
||||
ts := val
|
||||
s, err := renderer.RenderTemplateContentToBuffer([]byte(ts))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".labels[%s] = \"%s\": %v", r.Name, key, ts, err)
|
||||
}
|
||||
result.Labels[key] = s.String()
|
||||
}
|
||||
|
||||
for i, t := range result.Values {
|
||||
switch ts := t.(type) {
|
||||
case string:
|
||||
@@ -56,6 +101,24 @@ func (r ReleaseSpec) ExecuteTemplateExpressions(renderer *tmpl.FileRenderer) (*R
|
||||
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%s\": %v", r.Name, i, ts, err)
|
||||
}
|
||||
result.Values[i] = s.String()
|
||||
case map[interface{}]interface{}:
|
||||
serialized, err := yaml.Marshal(ts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%v\": %v", r.Name, i, ts, err)
|
||||
}
|
||||
|
||||
s, err := renderer.RenderTemplateContentToBuffer([]byte(serialized))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%v\": %v", r.Name, i, serialized, err)
|
||||
}
|
||||
|
||||
var deserialized map[interface{}]interface{}
|
||||
|
||||
if err := yaml.Unmarshal(s.Bytes(), &deserialized); err != nil {
|
||||
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%v\": %v", r.Name, i, ts, err)
|
||||
}
|
||||
|
||||
result.Values[i] = deserialized
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +130,44 @@ func (r ReleaseSpec) ExecuteTemplateExpressions(renderer *tmpl.FileRenderer) (*R
|
||||
result.Secrets[i] = s.String()
|
||||
}
|
||||
|
||||
for i, val := range result.SetValues {
|
||||
{
|
||||
// name
|
||||
ts := val.Name
|
||||
s, err := renderer.RenderTemplateContentToBuffer([]byte(ts))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".set[%d].name = \"%s\": %v", r.Name, i, ts, err)
|
||||
}
|
||||
result.SetValues[i].Name = s.String()
|
||||
}
|
||||
{
|
||||
// value
|
||||
ts := val.Value
|
||||
s, err := renderer.RenderTemplateContentToBuffer([]byte(ts))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".set[%d].value = \"%s\": %v", r.Name, i, ts, err)
|
||||
}
|
||||
result.SetValues[i].Value = s.String()
|
||||
}
|
||||
{
|
||||
// file
|
||||
ts := val.File
|
||||
s, err := renderer.RenderTemplateContentToBuffer([]byte(ts))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".set[%d].file = \"%s\": %v", r.Name, i, ts, err)
|
||||
}
|
||||
result.SetValues[i].File = s.String()
|
||||
}
|
||||
for j, ts := range val.Values {
|
||||
// values
|
||||
s, err := renderer.RenderTemplateContentToBuffer([]byte(ts))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".set[%d].values[%d] = \"%s\": %v", r.Name, i, j, ts, err)
|
||||
}
|
||||
result.SetValues[i].Values[j] = s.String()
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
+84
-67
@@ -34,23 +34,23 @@ type HelmState struct {
|
||||
FilePath string
|
||||
|
||||
// DefaultValues is the default values to be overrode by environment values and command-line overrides
|
||||
DefaultValues []interface{} `yaml:"values"`
|
||||
DefaultValues []interface{} `yaml:"values,omitempty"`
|
||||
|
||||
Environments map[string]EnvironmentSpec `yaml:"environments"`
|
||||
Environments map[string]EnvironmentSpec `yaml:"environments,omitempty"`
|
||||
|
||||
Bases []string `yaml:"bases"`
|
||||
HelmDefaults HelmSpec `yaml:"helmDefaults"`
|
||||
Helmfiles []SubHelmfileSpec `yaml:"helmfiles"`
|
||||
DeprecatedContext string `yaml:"context"`
|
||||
DeprecatedReleases []ReleaseSpec `yaml:"charts"`
|
||||
Namespace string `yaml:"namespace"`
|
||||
Repositories []RepositorySpec `yaml:"repositories"`
|
||||
Releases []ReleaseSpec `yaml:"releases"`
|
||||
Selectors []string
|
||||
Bases []string `yaml:"bases,omitempty"`
|
||||
HelmDefaults HelmSpec `yaml:"helmDefaults,omitempty"`
|
||||
Helmfiles []SubHelmfileSpec `yaml:"helmfiles,omitempty"`
|
||||
DeprecatedContext string `yaml:"context,omitempty"`
|
||||
DeprecatedReleases []ReleaseSpec `yaml:"charts,omitempty"`
|
||||
Namespace string `yaml:"namespace,omitempty"`
|
||||
Repositories []RepositorySpec `yaml:"repositories,omitempty"`
|
||||
Releases []ReleaseSpec `yaml:"releases,omitempty"`
|
||||
Selectors []string `yaml:"-"`
|
||||
|
||||
Templates map[string]TemplateSpec `yaml:"templates"`
|
||||
|
||||
Env environment.Environment
|
||||
Env environment.Environment `yaml:"-"`
|
||||
|
||||
logger *zap.SugaredLogger
|
||||
|
||||
@@ -67,23 +67,26 @@ type HelmState struct {
|
||||
|
||||
// SubHelmfileSpec defines the subhelmfile path and options
|
||||
type SubHelmfileSpec struct {
|
||||
Path string //path or glob pattern for the sub helmfiles
|
||||
Selectors []string //chosen selectors for the sub helmfiles
|
||||
SelectorsInherited bool //do the sub helmfiles inherits from parent selectors
|
||||
//path or glob pattern for the sub helmfiles
|
||||
Path string `yaml:"path,omitempty"`
|
||||
//chosen selectors for the sub helmfiles
|
||||
Selectors []string `yaml:"selectors,omitempty"`
|
||||
//do the sub helmfiles inherits from parent selectors
|
||||
SelectorsInherited bool `yaml:"selectorsInherited,omitempty"`
|
||||
|
||||
Environment SubhelmfileEnvironmentSpec
|
||||
}
|
||||
|
||||
type SubhelmfileEnvironmentSpec struct {
|
||||
OverrideValues []interface{} `yaml:"values"`
|
||||
OverrideValues []interface{} `yaml:"values,omitempty"`
|
||||
}
|
||||
|
||||
// HelmSpec to defines helmDefault values
|
||||
type HelmSpec struct {
|
||||
KubeContext string `yaml:"kubeContext"`
|
||||
TillerNamespace string `yaml:"tillerNamespace"`
|
||||
KubeContext string `yaml:"kubeContext,omitempty"`
|
||||
TillerNamespace string `yaml:"tillerNamespace,omitempty"`
|
||||
Tillerless bool `yaml:"tillerless"`
|
||||
Args []string `yaml:"args"`
|
||||
Args []string `yaml:"args,omitempty"`
|
||||
Verify bool `yaml:"verify"`
|
||||
// Devel, when set to true, use development versions, too. Equivalent to version '>0.0.0-0'
|
||||
Devel bool `yaml:"devel"`
|
||||
@@ -99,77 +102,83 @@ type HelmSpec struct {
|
||||
Atomic bool `yaml:"atomic"`
|
||||
|
||||
TLS bool `yaml:"tls"`
|
||||
TLSCACert string `yaml:"tlsCACert"`
|
||||
TLSKey string `yaml:"tlsKey"`
|
||||
TLSCert string `yaml:"tlsCert"`
|
||||
TLSCACert string `yaml:"tlsCACert,omitempty"`
|
||||
TLSKey string `yaml:"tlsKey,omitempty"`
|
||||
TLSCert string `yaml:"tlsCert,omitempty"`
|
||||
}
|
||||
|
||||
// RepositorySpec that defines values for a helm repo
|
||||
type RepositorySpec struct {
|
||||
Name string `yaml:"name"`
|
||||
URL string `yaml:"url"`
|
||||
CertFile string `yaml:"certFile"`
|
||||
KeyFile string `yaml:"keyFile"`
|
||||
Username string `yaml:"username"`
|
||||
Password string `yaml:"password"`
|
||||
Name string `yaml:"name,omitempty"`
|
||||
URL string `yaml:"url,omitempty"`
|
||||
CertFile string `yaml:"certFile,omitempty"`
|
||||
KeyFile string `yaml:"keyFile,omitempty"`
|
||||
Username string `yaml:"username,omitempty"`
|
||||
Password string `yaml:"password,omitempty"`
|
||||
}
|
||||
|
||||
// ReleaseSpec defines the structure of a helm release
|
||||
type ReleaseSpec struct {
|
||||
// Chart is the name of the chart being installed to create this release
|
||||
Chart string `yaml:"chart"`
|
||||
Version string `yaml:"version"`
|
||||
Verify *bool `yaml:"verify"`
|
||||
Chart string `yaml:"chart,omitempty"`
|
||||
Version string `yaml:"version,omitempty"`
|
||||
Verify *bool `yaml:"verify,omitempty"`
|
||||
// Devel, when set to true, use development versions, too. Equivalent to version '>0.0.0-0'
|
||||
Devel *bool `yaml:"devel"`
|
||||
Devel *bool `yaml:"devel,omitempty"`
|
||||
// Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful
|
||||
Wait *bool `yaml:"wait"`
|
||||
Wait *bool `yaml:"wait,omitempty"`
|
||||
// Timeout is the time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300)
|
||||
Timeout *int `yaml:"timeout"`
|
||||
Timeout *int `yaml:"timeout,omitempty"`
|
||||
// RecreatePods, when set to true, instruct helmfile to perform pods restart for the resource if applicable
|
||||
RecreatePods *bool `yaml:"recreatePods"`
|
||||
RecreatePods *bool `yaml:"recreatePods,omitempty"`
|
||||
// Force, when set to true, forces resource update through delete/recreate if needed
|
||||
Force *bool `yaml:"force"`
|
||||
Force *bool `yaml:"force,omitempty"`
|
||||
// Installed, when set to true, `delete --purge` the release
|
||||
Installed *bool `yaml:"installed"`
|
||||
Installed *bool `yaml:"installed,omitempty"`
|
||||
// Atomic, when set to true, restore previous state in case of a failed install/upgrade attempt
|
||||
Atomic *bool `yaml:"atomic"`
|
||||
Atomic *bool `yaml:"atomic,omitempty"`
|
||||
|
||||
// MissingFileHandler is set to either "Error" or "Warn". "Error" instructs helmfile to fail when unable to find a values or secrets file. When "Warn", it prints the file and continues.
|
||||
// The default value for MissingFileHandler is "Error".
|
||||
MissingFileHandler *string `yaml:"missingFileHandler"`
|
||||
MissingFileHandler *string `yaml:"missingFileHandler,omitempty"`
|
||||
|
||||
// Hooks is a list of extension points paired with operations, that are executed in specific points of the lifecycle of releases defined in helmfile
|
||||
Hooks []event.Hook `yaml:"hooks"`
|
||||
Hooks []event.Hook `yaml:"hooks,omitempty"`
|
||||
|
||||
// Name is the name of this release
|
||||
Name string `yaml:"name"`
|
||||
Namespace string `yaml:"namespace"`
|
||||
Labels map[string]string `yaml:"labels"`
|
||||
Values []interface{} `yaml:"values"`
|
||||
Secrets []string `yaml:"secrets"`
|
||||
SetValues []SetValue `yaml:"set"`
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Namespace string `yaml:"namespace,omitempty"`
|
||||
Labels map[string]string `yaml:"labels,omitempty"`
|
||||
Values []interface{} `yaml:"values,omitempty"`
|
||||
Secrets []string `yaml:"secrets,omitempty"`
|
||||
SetValues []SetValue `yaml:"set,omitempty"`
|
||||
|
||||
// The 'env' section is not really necessary any longer, as 'set' would now provide the same functionality
|
||||
EnvValues []SetValue `yaml:"env"`
|
||||
EnvValues []SetValue `yaml:"env,omitempty"`
|
||||
|
||||
ValuesPathPrefix string `yaml:"valuesPathPrefix"`
|
||||
ValuesPathPrefix string `yaml:"valuesPathPrefix,omitempty"`
|
||||
|
||||
TillerNamespace string `yaml:"tillerNamespace"`
|
||||
Tillerless *bool `yaml:"tillerless"`
|
||||
TillerNamespace string `yaml:"tillerNamespace,omitempty"`
|
||||
Tillerless *bool `yaml:"tillerless,omitempty"`
|
||||
|
||||
KubeContext string `yaml:"kubeContext"`
|
||||
KubeContext string `yaml:"kubeContext,omitempty"`
|
||||
|
||||
TLS *bool `yaml:"tls"`
|
||||
TLSCACert string `yaml:"tlsCACert"`
|
||||
TLSKey string `yaml:"tlsKey"`
|
||||
TLSCert string `yaml:"tlsCert"`
|
||||
TLS *bool `yaml:"tls,omitempty"`
|
||||
TLSCACert string `yaml:"tlsCACert,omitempty"`
|
||||
TLSKey string `yaml:"tlsKey,omitempty"`
|
||||
TLSCert string `yaml:"tlsCert,omitempty"`
|
||||
|
||||
// These values are used in templating
|
||||
TillerlessTemplate *string `yaml:"tillerlessTemplate,omitempty"`
|
||||
VerifyTemplate *string `yaml:"verifyTemplate,omitempty"`
|
||||
WaitTemplate *string `yaml:"waitTemplate,omitempty"`
|
||||
InstalledTemplate *string `yaml:"installedTemplate,omitempty"`
|
||||
|
||||
// These settings requires helm-x integration to work
|
||||
Dependencies []Dependency `yaml:"dependencies"`
|
||||
JSONPatches []interface{} `yaml:"jsonPatches"`
|
||||
StrategicMergePatches []interface{} `yaml:"strategicMergePatches"`
|
||||
Adopt []string `yaml:"adopt"`
|
||||
Dependencies []Dependency `yaml:"dependencies,omitempty"`
|
||||
JSONPatches []interface{} `yaml:"jsonPatches,omitempty"`
|
||||
StrategicMergePatches []interface{} `yaml:"strategicMergePatches,omitempty"`
|
||||
Adopt []string `yaml:"adopt,omitempty"`
|
||||
|
||||
// generatedValues are values that need cleaned up on exit
|
||||
generatedValues []string
|
||||
@@ -179,10 +188,10 @@ type ReleaseSpec struct {
|
||||
|
||||
// SetValue are the key values to set on a helm release
|
||||
type SetValue struct {
|
||||
Name string `yaml:"name"`
|
||||
Value string `yaml:"value"`
|
||||
File string `yaml:"file"`
|
||||
Values []string `yaml:"values"`
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Value string `yaml:"value,omitempty"`
|
||||
File string `yaml:"file,omitempty"`
|
||||
Values []string `yaml:"values,omitempty"`
|
||||
}
|
||||
|
||||
// AffectedReleases hold the list of released that where updated, deleted, or in error
|
||||
@@ -601,7 +610,7 @@ func (st *HelmState) TemplateReleases(helm helmexec.Interface, outputDir string,
|
||||
}
|
||||
|
||||
if len(errs) == 0 {
|
||||
if err := helm.TemplateRelease(temp[release.Name], flags...); err != nil {
|
||||
if err := helm.TemplateRelease(release.Name, temp[release.Name], flags...); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
@@ -666,7 +675,7 @@ func (st *HelmState) LintReleases(helm helmexec.Interface, additionalValues []st
|
||||
}
|
||||
|
||||
if len(errs) == 0 {
|
||||
if err := helm.Lint(temp[release.Name], flags...); err != nil {
|
||||
if err := helm.Lint(release.Name, temp[release.Name], flags...); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
@@ -1051,7 +1060,7 @@ func (st *HelmState) ResolveDeps() (*HelmState, error) {
|
||||
|
||||
// UpdateDeps wrapper for updating dependencies on the releases
|
||||
func (st *HelmState) UpdateDeps(helm helmexec.Interface) []error {
|
||||
errs := []error{}
|
||||
var errs []error
|
||||
|
||||
for _, release := range st.Releases {
|
||||
if isLocalChart(release.Chart) {
|
||||
@@ -1084,7 +1093,7 @@ func (st *HelmState) BuildDeps(helm helmexec.Interface) []error {
|
||||
|
||||
for _, release := range st.Releases {
|
||||
if isLocalChart(release.Chart) {
|
||||
if err := helm.BuildDeps(normalizeChart(st.basePath, release.Chart)); err != nil {
|
||||
if err := helm.BuildDeps(release.Name, normalizeChart(st.basePath, release.Chart)); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
@@ -1594,3 +1603,11 @@ func (st *HelmState) GenerateOutputDir(outputDir string, release ReleaseSpec) (s
|
||||
|
||||
return path.Join(outputDir, sb.String()), nil
|
||||
}
|
||||
|
||||
func (st *HelmState) ToYaml() (string, error) {
|
||||
if result, err := yaml.Marshal(st); err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
return string(result), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ package state
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/roboll/helmfile/pkg/maputil"
|
||||
"github.com/roboll/helmfile/pkg/tmpl"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func (st *HelmState) Values() (map[string]interface{}, error) {
|
||||
@@ -41,6 +44,55 @@ func (st *HelmState) valuesFileTemplateData() EnvironmentTemplateData {
|
||||
}
|
||||
}
|
||||
|
||||
func getBoolRefFromStringTemplate(templateRef string) (*bool, error) {
|
||||
var result bool
|
||||
if err := yaml.Unmarshal([]byte(templateRef), &result); err != nil {
|
||||
return nil, fmt.Errorf("failed deserialising string %s: %v", templateRef, err)
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func updateBoolTemplatedValues(r *ReleaseSpec) error {
|
||||
|
||||
if r.InstalledTemplate != nil {
|
||||
if installed, err := getBoolRefFromStringTemplate(*r.InstalledTemplate); err != nil {
|
||||
return fmt.Errorf("installedTemplate: %v", err)
|
||||
} else {
|
||||
r.InstalledTemplate = nil
|
||||
r.Installed = installed
|
||||
}
|
||||
}
|
||||
|
||||
if r.WaitTemplate != nil {
|
||||
if wait, err := getBoolRefFromStringTemplate(*r.WaitTemplate); err != nil {
|
||||
return fmt.Errorf("waitTemplate: %v", err)
|
||||
} else {
|
||||
r.WaitTemplate = nil
|
||||
r.Wait = wait
|
||||
}
|
||||
}
|
||||
|
||||
if r.TillerlessTemplate != nil {
|
||||
if tillerless, err := getBoolRefFromStringTemplate(*r.TillerlessTemplate); err != nil {
|
||||
return fmt.Errorf("tillerlessTemplate: %v", err)
|
||||
} else {
|
||||
r.TillerlessTemplate = nil
|
||||
r.Tillerless = tillerless
|
||||
}
|
||||
}
|
||||
|
||||
if r.VerifyTemplate != nil {
|
||||
if verify, err := getBoolRefFromStringTemplate(*r.VerifyTemplate); err != nil {
|
||||
return fmt.Errorf("verifyTemplate: %v", err)
|
||||
} else {
|
||||
r.VerifyTemplate = nil
|
||||
r.Verify = verify
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *HelmState) ExecuteTemplates() (*HelmState, error) {
|
||||
r := *st
|
||||
|
||||
@@ -50,17 +102,32 @@ func (st *HelmState) ExecuteTemplates() (*HelmState, error) {
|
||||
}
|
||||
|
||||
for i, rt := range st.Releases {
|
||||
tmplData := releaseTemplateData{
|
||||
Environment: st.Env,
|
||||
Release: rt,
|
||||
Values: vals,
|
||||
successFlag := false
|
||||
for it, prev := 0, &rt; it < 6; it++ {
|
||||
tmplData := releaseTemplateData{
|
||||
Environment: st.Env,
|
||||
Release: *prev,
|
||||
Values: vals,
|
||||
}
|
||||
renderer := tmpl.NewFileRenderer(st.readFile, st.basePath, tmplData)
|
||||
r, err := rt.ExecuteTemplateExpressions(renderer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %v", st.FilePath, rt.Name, err)
|
||||
}
|
||||
if reflect.DeepEqual(prev, r) {
|
||||
successFlag = true
|
||||
if err := updateBoolTemplatedValues(r); err != nil {
|
||||
return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %v", st.FilePath, rt.Name, err)
|
||||
}
|
||||
st.Releases[i] = *r
|
||||
break
|
||||
}
|
||||
prev = r
|
||||
}
|
||||
renderer := tmpl.NewFileRenderer(st.readFile, st.basePath, tmplData)
|
||||
r, err := rt.ExecuteTemplateExpressions(renderer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %v", st.FilePath, rt.Name, err)
|
||||
if !successFlag {
|
||||
return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %s", st.FilePath, rt.Name,
|
||||
"recursive references can't be resolved")
|
||||
}
|
||||
st.Releases[i] = *r
|
||||
}
|
||||
|
||||
return &r, nil
|
||||
|
||||
@@ -1,11 +1,27 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"github.com/roboll/helmfile/pkg/environment"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/roboll/helmfile/pkg/environment"
|
||||
)
|
||||
|
||||
func boolPtrToString(ptr *bool) string {
|
||||
if ptr == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return fmt.Sprintf("&%t", *ptr)
|
||||
}
|
||||
|
||||
func ptr(v interface{}) interface{} {
|
||||
r := v
|
||||
return reflect.ValueOf(r).Addr().Interface()
|
||||
}
|
||||
|
||||
func TestHelmState_executeTemplates(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -13,24 +29,103 @@ func TestHelmState_executeTemplates(t *testing.T) {
|
||||
want ReleaseSpec
|
||||
}{
|
||||
{
|
||||
name: "Has template expressions in chart, values, and secrets",
|
||||
name: "Has template expressions in chart, values, secrets, version, labels",
|
||||
input: ReleaseSpec{
|
||||
Chart: "test-charts/{{ .Release.Name }}",
|
||||
Version: "{{ .Release.Name }}-0.1",
|
||||
Verify: nil,
|
||||
Name: "test-app",
|
||||
Namespace: "test-namespace-{{ .Release.Name }}",
|
||||
Values: []interface{}{"config/{{ .Environment.Name }}/{{ .Release.Name }}/values.yaml"},
|
||||
Secrets: []string{"config/{{ .Environment.Name }}/{{ .Release.Name }}/secrets.yaml"},
|
||||
Labels: map[string]string{"id": "{{ .Release.Name }}"},
|
||||
},
|
||||
want: ReleaseSpec{
|
||||
Chart: "test-charts/test-app",
|
||||
Version: "test-app-0.1",
|
||||
Verify: nil,
|
||||
Name: "test-app",
|
||||
Namespace: "test-namespace-test-app",
|
||||
Values: []interface{}{"config/test_env/test-app/values.yaml"},
|
||||
Secrets: []string{"config/test_env/test-app/secrets.yaml"},
|
||||
Labels: map[string]string{"id": "test-app"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Has template expressions in name with recursive refs",
|
||||
input: ReleaseSpec{
|
||||
Chart: "test-chart",
|
||||
Name: "{{ .Release.Labels.id }}-{{ .Release.Namespace }}",
|
||||
Namespace: "dev",
|
||||
Labels: map[string]string{"id": "{{ .Release.Chart }}"},
|
||||
},
|
||||
want: ReleaseSpec{
|
||||
Chart: "test-chart",
|
||||
Name: "test-chart-dev",
|
||||
Namespace: "dev",
|
||||
Labels: map[string]string{"id": "test-chart"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Has template expressions in boolean values",
|
||||
input: ReleaseSpec{
|
||||
Chart: "test-chart",
|
||||
Name: "app-dev",
|
||||
Namespace: "dev",
|
||||
Labels: map[string]string{"id": "app"},
|
||||
InstalledTemplate: func(i string) *string { return &i }(`{{ eq .Release.Labels.id "app" | ternary "yes" "no" }}`),
|
||||
VerifyTemplate: func(i string) *string { return &i }(`{{ true }}`),
|
||||
Verify: func(i bool) *bool { return &i }(false),
|
||||
WaitTemplate: func(i string) *string { return &i }(`{{ false }}`),
|
||||
TillerlessTemplate: func(i string) *string { return &i }(`yes`),
|
||||
},
|
||||
want: ReleaseSpec{
|
||||
Chart: "test-chart",
|
||||
Name: "app-dev",
|
||||
Namespace: "dev",
|
||||
Labels: map[string]string{"id": "app"},
|
||||
Installed: func(i bool) *bool { return &i }(true),
|
||||
Verify: func(i bool) *bool { return &i }(true),
|
||||
Wait: func(i bool) *bool { return &i }(false),
|
||||
Tillerless: func(i bool) *bool { return &i }(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Has template in set-values",
|
||||
input: ReleaseSpec{
|
||||
Chart: "test-charts/chart",
|
||||
Name: "test-app",
|
||||
Namespace: "dev",
|
||||
SetValues: []SetValue{
|
||||
SetValue{Name: "val1", Value: "{{ .Release.Name }}-val1"},
|
||||
SetValue{Name: "val2", File: "{{ .Release.Name }}.yml"},
|
||||
SetValue{Name: "val3", Values: []string{"{{ .Release.Name }}-val2", "{{ .Release.Name }}-val3"}},
|
||||
},
|
||||
},
|
||||
want: ReleaseSpec{
|
||||
Chart: "test-charts/chart",
|
||||
Name: "test-app",
|
||||
Namespace: "dev",
|
||||
SetValues: []SetValue{
|
||||
SetValue{Name: "val1", Value: "test-app-val1"},
|
||||
SetValue{Name: "val2", File: "test-app.yml"},
|
||||
SetValue{Name: "val3", Values: []string{"test-app-val2", "test-app-val3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Has template in values (map)",
|
||||
input: ReleaseSpec{
|
||||
Chart: "test-charts/chart",
|
||||
Verify: nil,
|
||||
Name: "app",
|
||||
Namespace: "dev",
|
||||
Values: []interface{}{map[string]string{"key": "{{ .Release.Name }}-val0"}},
|
||||
},
|
||||
want: ReleaseSpec{
|
||||
Chart: "test-charts/chart",
|
||||
Verify: nil,
|
||||
Name: "app",
|
||||
Namespace: "dev",
|
||||
Values: []interface{}{map[interface{}]interface{}{"key": "app-val0"}},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -59,20 +154,102 @@ func TestHelmState_executeTemplates(t *testing.T) {
|
||||
|
||||
actual := r.Releases[0]
|
||||
|
||||
if !reflect.DeepEqual(actual.Name, tt.want.Name) {
|
||||
t.Errorf("expected Name %+v, got %+v", tt.want.Name, actual.Name)
|
||||
}
|
||||
if !reflect.DeepEqual(actual.Chart, tt.want.Chart) {
|
||||
t.Errorf("expected %+v, got %+v", tt.want.Chart, actual.Chart)
|
||||
t.Errorf("expected Chart %+v, got %+v", tt.want.Chart, actual.Chart)
|
||||
}
|
||||
if !reflect.DeepEqual(actual.Namespace, tt.want.Namespace) {
|
||||
t.Errorf("expected %+v, got %+v", tt.want.Namespace, actual.Namespace)
|
||||
t.Errorf("expected Namespace %+v, got %+v", tt.want.Namespace, actual.Namespace)
|
||||
}
|
||||
if !reflect.DeepEqual(actual.Values, tt.want.Values) {
|
||||
t.Errorf("expected %+v, got %+v", tt.want.Values, actual.Values)
|
||||
if diff := deep.Equal(actual.Values, tt.want.Values); diff != nil && len(actual.Values) > 0 {
|
||||
t.Errorf("Values differs \n%+v", strings.Join(diff, "\n"))
|
||||
}
|
||||
if !reflect.DeepEqual(actual.Secrets, tt.want.Secrets) {
|
||||
t.Errorf("expected %+v, got %+v", tt.want.Secrets, actual.Secrets)
|
||||
if diff := deep.Equal(actual.Secrets, tt.want.Secrets); diff != nil && len(actual.Secrets) > 0 {
|
||||
t.Errorf("Secrets differs \n%+v", strings.Join(diff, "\n"))
|
||||
}
|
||||
if diff := deep.Equal(actual.SetValues, tt.want.SetValues); diff != nil && len(actual.SetValues) > 0 {
|
||||
t.Errorf("SetValues differs \n%+v", strings.Join(diff, "\n"))
|
||||
}
|
||||
if diff := deep.Equal(actual.Labels, tt.want.Labels); diff != nil && len(actual.Labels) > 0 {
|
||||
t.Errorf("Labels differs \n%+v", strings.Join(diff, "\n"))
|
||||
}
|
||||
if !reflect.DeepEqual(actual.Version, tt.want.Version) {
|
||||
t.Errorf("expected %+v, got %+v", tt.want.Version, actual.Version)
|
||||
t.Errorf("expected Version %+v, got %+v", tt.want.Version, actual.Version)
|
||||
}
|
||||
if !reflect.DeepEqual(actual.Installed, tt.want.Installed) {
|
||||
t.Errorf("expected actual.Installed %+v, got %+v",
|
||||
boolPtrToString(tt.want.Installed), boolPtrToString(actual.Installed),
|
||||
)
|
||||
}
|
||||
if !reflect.DeepEqual(actual.Tillerless, tt.want.Tillerless) {
|
||||
t.Errorf("expected actual.Tillerless %+v, got %+v",
|
||||
boolPtrToString(tt.want.Tillerless), boolPtrToString(actual.Tillerless),
|
||||
)
|
||||
}
|
||||
if !reflect.DeepEqual(actual.Verify, tt.want.Verify) {
|
||||
t.Errorf("expected actual.Verify %+v, got %+v",
|
||||
boolPtrToString(tt.want.Verify), boolPtrToString(actual.Verify),
|
||||
)
|
||||
}
|
||||
if !reflect.DeepEqual(actual.Wait, tt.want.Wait) {
|
||||
t.Errorf("expected actual.Wait %+v, got %+v",
|
||||
boolPtrToString(tt.want.Wait), boolPtrToString(actual.Wait),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelmState_recursiveRefsTemplates(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input ReleaseSpec
|
||||
}{
|
||||
{
|
||||
name: "Has reqursive references",
|
||||
input: ReleaseSpec{
|
||||
Chart: "test-charts/{{ .Release.Name }}",
|
||||
Verify: nil,
|
||||
Name: "{{ .Release.Labels.id }}",
|
||||
Namespace: "dev",
|
||||
Labels: map[string]string{"id": "app-{{ .Release.Name }}"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Has unresolvable boolean templates",
|
||||
input: ReleaseSpec{
|
||||
Name: "app-dev",
|
||||
Chart: "test-charts/app",
|
||||
Verify: nil,
|
||||
Namespace: "dev",
|
||||
WaitTemplate: func(i string) *string { return &i }("hi"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
basePath: ".",
|
||||
HelmDefaults: HelmSpec{
|
||||
KubeContext: "test_context",
|
||||
},
|
||||
Env: environment.Environment{Name: "test_env"},
|
||||
Namespace: "test-namespace_",
|
||||
Repositories: nil,
|
||||
Releases: []ReleaseSpec{
|
||||
tt.input,
|
||||
},
|
||||
}
|
||||
|
||||
r, err := state.ExecuteTemplates()
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, got valid response: %v", r)
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -706,7 +706,7 @@ func (helm *mockHelmExec) UpdateDeps(chart string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (helm *mockHelmExec) BuildDeps(chart string) error {
|
||||
func (helm *mockHelmExec) BuildDeps(name, chart string) error {
|
||||
if strings.Contains(chart, "error") {
|
||||
return errors.New("error")
|
||||
}
|
||||
@@ -769,10 +769,10 @@ func (helm *mockHelmExec) TestRelease(context helmexec.HelmContext, name string,
|
||||
func (helm *mockHelmExec) Fetch(chart string, flags ...string) error {
|
||||
return nil
|
||||
}
|
||||
func (helm *mockHelmExec) Lint(chart string, flags ...string) error {
|
||||
func (helm *mockHelmExec) Lint(name, chart string, flags ...string) error {
|
||||
return nil
|
||||
}
|
||||
func (helm *mockHelmExec) TemplateRelease(chart string, flags ...string) error {
|
||||
func (helm *mockHelmExec) TemplateRelease(name, chart string, flags ...string) error {
|
||||
return nil
|
||||
}
|
||||
func TestHelmState_SyncRepos(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user