diff --git a/main.go b/main.go index cac9b57a..42ff61ce 100644 --- a/main.go +++ b/main.go @@ -476,7 +476,9 @@ func loadDesiredStateFromFile(c *cli.Context, file string) (*state.HelmState, he namespace := c.GlobalString("namespace") labels := c.GlobalStringSlice("selector") - st, err := state.ReadFromFile(file) + logger := c.App.Metadata["logger"].(*zap.SugaredLogger) + + st, err := state.CreateFromFile(file, logger) if err != nil { return nil, nil, false, fmt.Errorf("failed to read %s: %v", file, err) } @@ -514,7 +516,6 @@ func loadDesiredStateFromFile(c *cli.Context, file string) (*state.HelmState, he clean(st, errs) }() - logger := c.App.Metadata["logger"].(*zap.SugaredLogger) return st, helmexec.New(logger, kubeContext), false, nil } diff --git a/state/state.go b/state/state.go index 8abcff79..77861123 100644 --- a/state/state.go +++ b/state/state.go @@ -19,6 +19,7 @@ import ( "bytes" "regexp" + "go.uber.org/zap" yaml "gopkg.in/yaml.v2" ) @@ -32,6 +33,8 @@ type HelmState struct { Namespace string `yaml:"namespace"` Repositories []RepositorySpec `yaml:"repositories"` Releases []ReleaseSpec `yaml:"releases"` + + logger *zap.SugaredLogger } // HelmSpec to defines helmDefault values @@ -79,28 +82,17 @@ type SetValue struct { Value string `yaml:"value"` } -// ReadFromFile loads the helmfile from disk and processes the template -func ReadFromFile(file string) (*HelmState, error) { - content, err := ioutil.ReadFile(file) +// CreateFromFile loads the helmfile from disk and processes the template +func CreateFromFile(file string, logger *zap.SugaredLogger) (*HelmState, error) { + yamlBuf, err := renderTemplateFileToBuffer(file) if err != nil { return nil, err } - tpl, err := stringTemplate().Parse(string(content)) - if err != nil { - return nil, err - } - - var tplString bytes.Buffer - err = tpl.Execute(&tplString, nil) - if err != nil { - return nil, err - } - - return readFromYaml(tplString.Bytes(), file) + return readFromYaml(yamlBuf.Bytes(), file, logger) } -func readFromYaml(content []byte, file string) (*HelmState, error) { +func readFromYaml(content []byte, file string, logger *zap.SugaredLogger) (*HelmState, error) { var state HelmState state.BaseChartPath, _ = filepath.Abs(filepath.Dir(file)) @@ -117,6 +109,8 @@ func readFromYaml(content []byte, file string) (*HelmState, error) { state.DeprecatedReleases = []ReleaseSpec{} } + state.logger = logger + return &state, nil } @@ -138,19 +132,36 @@ func getRequiredEnv(name string) (string, error) { return "", fmt.Errorf("required env var `%s` is not set", name) } -func renderTemplateString(s string) (string, error) { +func renderTemplateFileToBuffer(file string) (*bytes.Buffer, error) { + content, err := ioutil.ReadFile(file) + if err != nil { + return nil, err + } + + return renderTemplateToBuffer(string(content)) +} + +func renderTemplateToBuffer(s string) (*bytes.Buffer, error) { var t, parseErr = stringTemplate().Parse(s) if parseErr != nil { - return "", parseErr + return nil, parseErr } var tplString bytes.Buffer var execErr = t.Execute(&tplString, nil) if execErr != nil { - return "", execErr + return nil, execErr } + return &tplString, nil +} + +func renderTemplateString(s string) (string, error) { + tplString, err := renderTemplateToBuffer(s) + if err != nil { + return "", err + } return tplString.String(), nil } @@ -194,7 +205,7 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues [ go func() { for release := range jobQueue { state.applyDefaultsTo(release) - flags, flagsErr := flagsForRelease(helm, state.BaseChartPath, release) + flags, flagsErr := state.flagsForRelease(helm, state.BaseChartPath, release) if flagsErr != nil { errQueue <- flagsErr doneQueue <- true @@ -274,7 +285,7 @@ func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues [ state.applyDefaultsTo(release) - flags, err := flagsForRelease(helm, state.BaseChartPath, release) + flags, err := state.flagsForRelease(helm, state.BaseChartPath, release) if err != nil { errs = append(errs, err) } @@ -358,7 +369,7 @@ func (state *HelmState) LintReleases(helm helmexec.Interface, additionalValues [ go func() { for release := range jobQueue { errs := []error{} - flags, err := flagsForRelease(helm, state.BaseChartPath, release) + flags, err := state.flagsForRelease(helm, state.BaseChartPath, release) if err != nil { errs = append(errs, err) } @@ -641,7 +652,7 @@ func chartNameWithoutRepository(chart string) string { return chartSplit[len(chartSplit)-1] } -func flagsForRelease(helm helmexec.Interface, basePath string, release *ReleaseSpec) ([]string, error) { +func (state *HelmState) flagsForRelease(helm helmexec.Interface, basePath string, release *ReleaseSpec) ([]string, error) { flags := []string{} if release.Version != "" { flags = append(flags, "--version", release.Version) @@ -664,7 +675,21 @@ func flagsForRelease(helm helmexec.Interface, basePath string, release *ReleaseS if _, err := os.Stat(path); os.IsNotExist(err) { return nil, err } - flags = append(flags, "--values", path) + yamlBuf, err := renderTemplateFileToBuffer(path) + if err != nil { + return nil, err + } + valfile, err := ioutil.TempFile("", "values") + if err != nil { + return nil, err + } + defer valfile.Close() + yamlBytes := yamlBuf.Bytes() + if _, err := valfile.Write(yamlBytes); err != nil { + return nil, fmt.Errorf("failed to write %s: %v", valfile.Name(), err) + } + state.logger.Debugf("successfully generated the value file at %s. produced:\n%s", path, string(yamlBytes)) + flags = append(flags, "--values", valfile.Name()) case map[interface{}]interface{}: valfile, err := ioutil.TempFile("", "values") diff --git a/state/state_test.go b/state/state_test.go index d8fd1043..63795ee0 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -6,9 +6,12 @@ import ( "testing" "errors" + "github.com/roboll/helmfile/helmexec" "strings" ) +var logger = helmexec.NewLogger(os.Stdout, "warn") + func TestReadFromYaml(t *testing.T) { yamlFile := "example/path/to/yaml/file" yamlContent := []byte(`releases: @@ -16,7 +19,7 @@ func TestReadFromYaml(t *testing.T) { namespace: mynamespace chart: mychart `) - state, err := readFromYaml(yamlContent, yamlFile) + state, err := readFromYaml(yamlContent, yamlFile, logger) if err != nil { t.Errorf("unxpected error: %v", err) } @@ -39,7 +42,7 @@ func TestReadFromYaml_StrictUnmarshalling(t *testing.T) { namespace: mynamespace releases: mychart `) - _, err := readFromYaml(yamlContent, yamlFile) + _, err := readFromYaml(yamlContent, yamlFile, logger) if err == nil { t.Error("expected an error for wrong key 'releases' which is not in struct") } @@ -51,7 +54,7 @@ func TestReadFromYaml_DeprecatedReleaseReferences(t *testing.T) { - name: myrelease chart: mychart `) - state, err := readFromYaml(yamlContent, yamlFile) + state, err := readFromYaml(yamlContent, yamlFile, logger) if err != nil { t.Errorf("unxpected error: %v", err) } @@ -73,7 +76,7 @@ releases: - name: myrelease2 chart: mychart2 `) - _, err := readFromYaml(yamlContent, yamlFile) + _, err := readFromYaml(yamlContent, yamlFile, logger) if err == nil { t.Error("expected error") } @@ -109,7 +112,7 @@ func TestReadFromYaml_FilterReleasesOnLabels(t *testing.T) { {LabelFilter{positiveLabels: [][]string{[]string{"tier", "frontend"}}, negativeLabels: [][]string{[]string{"foo", "bar"}}}, []bool{false, true, false}}, } - state, err := readFromYaml(yamlContent, yamlFile) + state, err := readFromYaml(yamlContent, yamlFile, logger) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -148,7 +151,7 @@ func TestReadFromYaml_FilterNegatives(t *testing.T) { {LabelFilter{negativeLabels: [][]string{[]string{"stage", "pre"}, []string{"stage", "post"}}}, []bool{false, false, true}}, } - state, err := readFromYaml(yamlContent, yamlFile) + state, err := readFromYaml(yamlContent, yamlFile, logger) if err != nil { t.Errorf("unexpected error: %v", err) }