feat: Release Template (#439)
This feature is supposed to help advanced use-cases like Conventional Directory Structure explained in several issues like #428. Newly added configuration keys `templates`, `missingFileHandler`, and the ability to defer executing template expressions in `values`, `secrets`, `namespace`, and `chart` of releases allows you to abstract away repetitions into a reusable template: ```yaml templates: default: &default missingFileHandler: Warn namespace: "{{`{{ .Release.Name }}`}}" chart: stable/{{`{{ .Release.Name }}`}} values: - config/{{`{{ .Release.Name }}`}}/values.yaml - config/{{`{{ .Release.Name }}`}}/{{`{{ .Environment.Name }}`}}.yaml secrets: - config/{{`{{ .Release.Name }}`}}/secrets.yaml - config/{{`{{ .Release.Name }}`}}/{{`{{ .Environment.Name }}`}}-secrets.yaml releases: - name: envoy <<: *default ``` See the updated documentation for more details. Resolves #428
This commit is contained in:
parent
23178b398c
commit
f813ac2642
|
|
@ -179,7 +179,11 @@ helmfile apply
|
||||||
|
|
||||||
Congratulations! You now have your first Prometheus deployment running inside your cluster.
|
Congratulations! You now have your first Prometheus deployment running inside your cluster.
|
||||||
|
|
||||||
Iterate on the `helmfile.yaml` by referencing the [configuration syntax](#configuration-syntax) and the [cli reference](#cli-reference).
|
Iterate on the `helmfile.yaml` by referencing:
|
||||||
|
|
||||||
|
- [Configuration syntax](#configuration-syntax)
|
||||||
|
- [CLI reference](#cli-reference).
|
||||||
|
- [Helmfile Best Practices Guide](https://github.com/roboll/helmfile/blob/master/docs/writing-helmfile.md)
|
||||||
|
|
||||||
## cli reference
|
## cli reference
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,65 @@ If you want a kind of default values that is used when a missing key was referen
|
||||||
|
|
||||||
Now, you get `1` when there is no `eventApi.replicas` defined in environment values.
|
Now, you get `1` when there is no `eventApi.replicas` defined in environment values.
|
||||||
|
|
||||||
|
## Release Template / Conventional Directory Structure
|
||||||
|
|
||||||
|
Introducing helmfile into a large-scale project that involes dozens of releases often results in a lot of repetitions in `helmfile.yaml` files.
|
||||||
|
|
||||||
|
The example below shows repetitions in `namespace`, `chart`, `values`, and `secrets`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
releases:
|
||||||
|
# *snip*
|
||||||
|
- name: heapster
|
||||||
|
namespace: kube-system
|
||||||
|
chart: stable/heapster
|
||||||
|
version: 0.3.2
|
||||||
|
values:
|
||||||
|
- "./config/heapster/values.yaml"
|
||||||
|
- "./config/heapster/{{ .Environment.Name }}.yaml"
|
||||||
|
secrets:
|
||||||
|
- "./config/heapster/secrets.yaml"
|
||||||
|
- "./config/heapster/{{ .Environment.Name }}-secrets.yaml"
|
||||||
|
|
||||||
|
- name: kubernetes-dashboard
|
||||||
|
namespace: kube-system
|
||||||
|
chart: stable/kubernetes-dashboard
|
||||||
|
version: 0.10.0
|
||||||
|
values:
|
||||||
|
- "./config/kubernetes-dashboard/values.yaml"
|
||||||
|
- "./config/kubernetes-dashboard/{{ .Environment.Name }}.yaml"
|
||||||
|
values:
|
||||||
|
- "./config/kubernetes-dashboard/secrets.yaml"
|
||||||
|
- "./config/kubernetes-dashboard/{{ .Environment.Name }}-secrets.yaml"
|
||||||
|
```
|
||||||
|
|
||||||
|
This is where Helmfile's advanced feature called Release Template comes handy.
|
||||||
|
|
||||||
|
It allows you to abstract away the repetitions in releases into a template, which is then included and executed by using YAML anchor/alias:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
templates:
|
||||||
|
default: &default
|
||||||
|
chart: stable/{{`{{ .Release.Name }}`}}
|
||||||
|
namespace: kube-system
|
||||||
|
# This prevents helmfile exiting when it encounters a missing file
|
||||||
|
missingFileHandler: Warn
|
||||||
|
values:
|
||||||
|
- config/{{`{{ .Release.Name }}`}}/values.yaml
|
||||||
|
- config/{{`{{ .Release.Name }}`}}/{{`{{ .Environment.Name }}`}}.yaml
|
||||||
|
secrets:
|
||||||
|
- config/{{`{{ .Release.Name }}`}}/secrets.yaml
|
||||||
|
- config/{{`{{ .Release.Name }}`}}/{{`{{ .Environment.Name }}`}}-secrets.yaml
|
||||||
|
|
||||||
|
releases:
|
||||||
|
- name: heapster
|
||||||
|
<<: *default
|
||||||
|
- name: kubernetes-dashboard
|
||||||
|
<<: *default
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [issue 428](https://github.com/roboll/helmfile/issues/428) for more context on how this is supposed to work.
|
||||||
|
|
||||||
## Layering
|
## Layering
|
||||||
|
|
||||||
You may occasionally end up with many helmfiles that shares common parts like which repositories to use, and whichi release to be bundled by default.
|
You may occasionally end up with many helmfiles that shares common parts like which repositories to use, and whichi release to be bundled by default.
|
||||||
|
|
|
||||||
9
main.go
9
main.go
|
|
@ -756,7 +756,8 @@ func (r *twoPassRenderer) renderTemplate(content []byte) (*bytes.Buffer, error)
|
||||||
// try a first pass render. This will always succeed, but can produce a limited env
|
// try a first pass render. This will always succeed, but can produce a limited env
|
||||||
firstPassEnv := r.renderEnvironment(content)
|
firstPassEnv := r.renderEnvironment(content)
|
||||||
|
|
||||||
secondPassRenderer := tmpl.NewFileRenderer(r.reader, filepath.Dir(r.filename), firstPassEnv, r.namespace)
|
tmplData := state.EnvironmentTemplateData{Environment: firstPassEnv, Namespace: r.namespace}
|
||||||
|
secondPassRenderer := tmpl.NewFileRenderer(r.reader, filepath.Dir(r.filename), tmplData)
|
||||||
yamlBuf, err := secondPassRenderer.RenderTemplateContentToBuffer(content)
|
yamlBuf, err := secondPassRenderer.RenderTemplateContentToBuffer(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if r.logger != nil {
|
if r.logger != nil {
|
||||||
|
|
@ -840,6 +841,12 @@ func (a *app) VisitDesiredStates(fileOrDir string, converge func(*state.HelmStat
|
||||||
}
|
}
|
||||||
noMatchInHelmfiles = noMatchInHelmfiles && noMatchInSubHelmfiles
|
noMatchInHelmfiles = noMatchInHelmfiles && noMatchInSubHelmfiles
|
||||||
} else {
|
} else {
|
||||||
|
var err error
|
||||||
|
st, err = st.ExecuteTemplates()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed executing release templates in \"%s\": %v", fileOrDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
var processed bool
|
var processed bool
|
||||||
processed, errs = converge(st, helm)
|
processed, errs = converge(st, helm)
|
||||||
noMatchInHelmfiles = noMatchInHelmfiles && !processed
|
noMatchInHelmfiles = noMatchInHelmfiles && !processed
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
"github.com/roboll/helmfile/environment"
|
"github.com/roboll/helmfile/environment"
|
||||||
"github.com/roboll/helmfile/helmexec"
|
"github.com/roboll/helmfile/helmexec"
|
||||||
"github.com/roboll/helmfile/valuesfile"
|
"github.com/roboll/helmfile/tmpl"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -118,13 +118,14 @@ func (c *creator) CreateFromYaml(content []byte, file string, env string) (*Helm
|
||||||
return &state, nil
|
return &state, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *HelmState) loadEnv(name string, readFile func(string) ([]byte, error)) (*environment.Environment, error) {
|
func (st *HelmState) loadEnv(name string, readFile func(string) ([]byte, error)) (*environment.Environment, error) {
|
||||||
envVals := map[string]interface{}{}
|
envVals := map[string]interface{}{}
|
||||||
envSpec, ok := state.Environments[name]
|
envSpec, ok := st.Environments[name]
|
||||||
if ok {
|
if ok {
|
||||||
for _, envvalFile := range envSpec.Values {
|
for _, envvalFile := range envSpec.Values {
|
||||||
envvalFullPath := filepath.Join(state.basePath, envvalFile)
|
envvalFullPath := filepath.Join(st.basePath, envvalFile)
|
||||||
r := valuesfile.NewRenderer(readFile, filepath.Dir(envvalFullPath), environment.EmptyEnvironment)
|
tmplData := EnvironmentTemplateData{Environment: environment.EmptyEnvironment, Namespace: ""}
|
||||||
|
r := tmpl.NewFileRenderer(readFile, filepath.Dir(envvalFullPath), tmplData)
|
||||||
bytes, err := r.RenderToBytes(envvalFullPath)
|
bytes, err := r.RenderToBytes(envvalFullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load environment values file \"%s\": %v", envvalFile, err)
|
return nil, fmt.Errorf("failed to load environment values file \"%s\": %v", envvalFile, err)
|
||||||
|
|
@ -139,9 +140,9 @@ func (state *HelmState) loadEnv(name string, readFile func(string) ([]byte, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(envSpec.Secrets) > 0 {
|
if len(envSpec.Secrets) > 0 {
|
||||||
helm := helmexec.New(state.logger, "")
|
helm := helmexec.New(st.logger, "")
|
||||||
for _, secFile := range envSpec.Secrets {
|
for _, secFile := range envSpec.Secrets {
|
||||||
path := filepath.Join(state.basePath, secFile)
|
path := filepath.Join(st.basePath, secFile)
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/roboll/helmfile/tmpl"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r ReleaseSpec) ExecuteTemplateExpressions(renderer *tmpl.FileRenderer) (*ReleaseSpec, error) {
|
||||||
|
var result *ReleaseSpec
|
||||||
|
var err error
|
||||||
|
|
||||||
|
result, err = r.Clone()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed executing template expressions in release \"%s\": %v", r.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ts := result.Chart
|
||||||
|
result.Chart, err = renderer.RenderTemplateContentToString([]byte(ts))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".chart = \"%s\": %v", r.Name, ts, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
ts := result.Namespace
|
||||||
|
result.Namespace, err = renderer.RenderTemplateContentToString([]byte(ts))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".namespace = \"%s\": %v", r.Name, ts, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, t := range result.Values {
|
||||||
|
switch ts := t.(type) {
|
||||||
|
case string:
|
||||||
|
s, err := renderer.RenderTemplateContentToBuffer([]byte(ts))
|
||||||
|
if err != nil {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, ts := range result.Secrets {
|
||||||
|
s, err := renderer.RenderTemplateContentToBuffer([]byte(ts))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".secrets[%d] = \"%s\": %v", r.Name, i, ts, err)
|
||||||
|
}
|
||||||
|
result.Secrets[i] = s.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r ReleaseSpec) Clone() (*ReleaseSpec, error) {
|
||||||
|
serialized, err := yaml.Marshal(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed cloning release \"%s\": %v", r.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var deserialized ReleaseSpec
|
||||||
|
|
||||||
|
if err := yaml.Unmarshal(serialized, &deserialized); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed cloning release \"%s\": %v", r.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &deserialized, nil
|
||||||
|
}
|
||||||
237
state/state.go
237
state/state.go
|
|
@ -21,7 +21,7 @@ import (
|
||||||
|
|
||||||
"github.com/roboll/helmfile/environment"
|
"github.com/roboll/helmfile/environment"
|
||||||
"github.com/roboll/helmfile/event"
|
"github.com/roboll/helmfile/event"
|
||||||
"github.com/roboll/helmfile/valuesfile"
|
"github.com/roboll/helmfile/tmpl"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
@ -39,6 +39,8 @@ type HelmState struct {
|
||||||
Repositories []RepositorySpec `yaml:"repositories"`
|
Repositories []RepositorySpec `yaml:"repositories"`
|
||||||
Releases []ReleaseSpec `yaml:"releases"`
|
Releases []ReleaseSpec `yaml:"releases"`
|
||||||
|
|
||||||
|
Templates map[string]TemplateSpec `yaml:"templates"`
|
||||||
|
|
||||||
Env environment.Environment
|
Env environment.Environment
|
||||||
|
|
||||||
logger *zap.SugaredLogger
|
logger *zap.SugaredLogger
|
||||||
|
|
@ -95,6 +97,10 @@ type ReleaseSpec struct {
|
||||||
// Installed, when set to true, `delete --purge` the release
|
// Installed, when set to true, `delete --purge` the release
|
||||||
Installed *bool `yaml:"installed"`
|
Installed *bool `yaml:"installed"`
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
|
||||||
// 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 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"`
|
||||||
|
|
||||||
|
|
@ -125,9 +131,9 @@ type SetValue struct {
|
||||||
|
|
||||||
const DefaultEnv = "default"
|
const DefaultEnv = "default"
|
||||||
|
|
||||||
func (state *HelmState) applyDefaultsTo(spec *ReleaseSpec) {
|
func (st *HelmState) applyDefaultsTo(spec *ReleaseSpec) {
|
||||||
if state.Namespace != "" {
|
if st.Namespace != "" {
|
||||||
spec.Namespace = state.Namespace
|
spec.Namespace = st.Namespace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,10 +143,10 @@ type RepoUpdater interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncRepos will update the given helm releases
|
// SyncRepos will update the given helm releases
|
||||||
func (state *HelmState) SyncRepos(helm RepoUpdater) []error {
|
func (st *HelmState) SyncRepos(helm RepoUpdater) []error {
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
|
|
||||||
for _, repo := range state.Repositories {
|
for _, repo := range st.Repositories {
|
||||||
if err := helm.AddRepo(repo.Name, repo.URL, repo.CertFile, repo.KeyFile, repo.Username, repo.Password); err != nil {
|
if err := helm.AddRepo(repo.Name, repo.URL, repo.CertFile, repo.KeyFile, repo.Username, repo.Password); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
|
@ -176,8 +182,8 @@ type syncPrepareResult struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncReleases wrapper for executing helm upgrade on the releases
|
// SyncReleases wrapper for executing helm upgrade on the releases
|
||||||
func (state *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalValues []string, concurrency int) ([]syncPrepareResult, []error) {
|
func (st *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalValues []string, concurrency int) ([]syncPrepareResult, []error) {
|
||||||
releases := state.Releases
|
releases := st.Releases
|
||||||
numReleases := len(releases)
|
numReleases := len(releases)
|
||||||
jobs := make(chan *ReleaseSpec, numReleases)
|
jobs := make(chan *ReleaseSpec, numReleases)
|
||||||
results := make(chan syncPrepareResult, numReleases)
|
results := make(chan syncPrepareResult, numReleases)
|
||||||
|
|
@ -193,9 +199,9 @@ func (state *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalV
|
||||||
for w := 1; w <= concurrency; w++ {
|
for w := 1; w <= concurrency; w++ {
|
||||||
go func() {
|
go func() {
|
||||||
for release := range jobs {
|
for release := range jobs {
|
||||||
state.applyDefaultsTo(release)
|
st.applyDefaultsTo(release)
|
||||||
|
|
||||||
flags, flagsErr := state.flagsForUpgrade(helm, release)
|
flags, flagsErr := st.flagsForUpgrade(helm, release)
|
||||||
if flagsErr != nil {
|
if flagsErr != nil {
|
||||||
results <- syncPrepareResult{errors: []*ReleaseError{&ReleaseError{release, flagsErr}}}
|
results <- syncPrepareResult{errors: []*ReleaseError{&ReleaseError{release, flagsErr}}}
|
||||||
continue
|
continue
|
||||||
|
|
@ -248,10 +254,10 @@ func (state *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalV
|
||||||
return res, errs
|
return res, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *HelmState) DetectReleasesToBeDeleted(helm helmexec.Interface) ([]*ReleaseSpec, error) {
|
func (st *HelmState) DetectReleasesToBeDeleted(helm helmexec.Interface) ([]*ReleaseSpec, error) {
|
||||||
detected := []*ReleaseSpec{}
|
detected := []*ReleaseSpec{}
|
||||||
for i, _ := range state.Releases {
|
for i, _ := range st.Releases {
|
||||||
release := state.Releases[i]
|
release := st.Releases[i]
|
||||||
if release.Installed != nil && !*release.Installed {
|
if release.Installed != nil && !*release.Installed {
|
||||||
err := helm.ReleaseStatus(release.Name)
|
err := helm.ReleaseStatus(release.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -274,8 +280,8 @@ func (state *HelmState) DetectReleasesToBeDeleted(helm helmexec.Interface) ([]*R
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncReleases wrapper for executing helm upgrade on the releases
|
// SyncReleases wrapper for executing helm upgrade on the releases
|
||||||
func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues []string, workerLimit int) []error {
|
func (st *HelmState) SyncReleases(helm helmexec.Interface, additionalValues []string, workerLimit int) []error {
|
||||||
preps, prepErrs := state.prepareSyncReleases(helm, additionalValues, workerLimit)
|
preps, prepErrs := st.prepareSyncReleases(helm, additionalValues, workerLimit)
|
||||||
if len(prepErrs) > 0 {
|
if len(prepErrs) > 0 {
|
||||||
return prepErrs
|
return prepErrs
|
||||||
}
|
}
|
||||||
|
|
@ -298,7 +304,7 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues [
|
||||||
for prep := range jobQueue {
|
for prep := range jobQueue {
|
||||||
release := prep.release
|
release := prep.release
|
||||||
flags := prep.flags
|
flags := prep.flags
|
||||||
chart := normalizeChart(state.basePath, release.Chart)
|
chart := normalizeChart(st.basePath, release.Chart)
|
||||||
if release.Installed != nil && !*release.Installed {
|
if release.Installed != nil && !*release.Installed {
|
||||||
if err := helm.ReleaseStatus(release.Name); err == nil {
|
if err := helm.ReleaseStatus(release.Name); err == nil {
|
||||||
if err := helm.DeleteRelease(release.Name, "--purge"); err != nil {
|
if err := helm.DeleteRelease(release.Name, "--purge"); err != nil {
|
||||||
|
|
@ -313,8 +319,8 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues [
|
||||||
results <- syncResult{}
|
results <- syncResult{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := state.triggerCleanupEvent(prep.release, "sync"); err != nil {
|
if _, err := st.triggerCleanupEvent(prep.release, "sync"); err != nil {
|
||||||
state.logger.Warnf("warn: %v\n", err)
|
st.logger.Warnf("warn: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
waitGroup.Done()
|
waitGroup.Done()
|
||||||
|
|
@ -349,8 +355,8 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues [
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadCharts will download and untar charts for Lint and Template
|
// downloadCharts will download and untar charts for Lint and Template
|
||||||
func (state *HelmState) downloadCharts(helm helmexec.Interface, dir string, workerLimit int, helmfileCommand string) (map[string]string, []error) {
|
func (st *HelmState) downloadCharts(helm helmexec.Interface, dir string, workerLimit int, helmfileCommand string) (map[string]string, []error) {
|
||||||
temp := make(map[string]string, len(state.Releases))
|
temp := make(map[string]string, len(st.Releases))
|
||||||
type downloadResults struct {
|
type downloadResults struct {
|
||||||
releaseName string
|
releaseName string
|
||||||
chartPath string
|
chartPath string
|
||||||
|
|
@ -358,20 +364,20 @@ func (state *HelmState) downloadCharts(helm helmexec.Interface, dir string, work
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
|
|
||||||
var wgFetch sync.WaitGroup
|
var wgFetch sync.WaitGroup
|
||||||
jobQueue := make(chan *ReleaseSpec, len(state.Releases))
|
jobQueue := make(chan *ReleaseSpec, len(st.Releases))
|
||||||
results := make(chan *downloadResults, len(state.Releases))
|
results := make(chan *downloadResults, len(st.Releases))
|
||||||
wgFetch.Add(len(state.Releases))
|
wgFetch.Add(len(st.Releases))
|
||||||
|
|
||||||
if workerLimit < 1 {
|
if workerLimit < 1 {
|
||||||
workerLimit = len(state.Releases)
|
workerLimit = len(st.Releases)
|
||||||
}
|
}
|
||||||
|
|
||||||
for w := 1; w <= workerLimit; w++ {
|
for w := 1; w <= workerLimit; w++ {
|
||||||
go func() {
|
go func() {
|
||||||
for release := range jobQueue {
|
for release := range jobQueue {
|
||||||
chartPath := ""
|
chartPath := ""
|
||||||
if pathExists(normalizeChart(state.basePath, release.Chart)) {
|
if pathExists(normalizeChart(st.basePath, release.Chart)) {
|
||||||
chartPath = normalizeChart(state.basePath, release.Chart)
|
chartPath = normalizeChart(st.basePath, release.Chart)
|
||||||
} else {
|
} else {
|
||||||
fetchFlags := []string{}
|
fetchFlags := []string{}
|
||||||
if release.Version != "" {
|
if release.Version != "" {
|
||||||
|
|
@ -400,12 +406,12 @@ func (state *HelmState) downloadCharts(helm helmexec.Interface, dir string, work
|
||||||
wgFetch.Done()
|
wgFetch.Done()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
for i := 0; i < len(state.Releases); i++ {
|
for i := 0; i < len(st.Releases); i++ {
|
||||||
jobQueue <- &state.Releases[i]
|
jobQueue <- &st.Releases[i]
|
||||||
}
|
}
|
||||||
close(jobQueue)
|
close(jobQueue)
|
||||||
|
|
||||||
for i := 0; i < len(state.Releases); i++ {
|
for i := 0; i < len(st.Releases); i++ {
|
||||||
downloadRes := <-results
|
downloadRes := <-results
|
||||||
temp[downloadRes.releaseName] = downloadRes.chartPath
|
temp[downloadRes.releaseName] = downloadRes.chartPath
|
||||||
}
|
}
|
||||||
|
|
@ -419,7 +425,7 @@ func (state *HelmState) downloadCharts(helm helmexec.Interface, dir string, work
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemplateReleases wrapper for executing helm template on the releases
|
// TemplateReleases wrapper for executing helm template on the releases
|
||||||
func (state *HelmState) TemplateReleases(helm helmexec.Interface, additionalValues []string, args []string, workerLimit int) []error {
|
func (st *HelmState) TemplateReleases(helm helmexec.Interface, additionalValues []string, args []string, workerLimit int) []error {
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
// Create tmp directory and bail immediately if it fails
|
// Create tmp directory and bail immediately if it fails
|
||||||
dir, err := ioutil.TempDir("", "")
|
dir, err := ioutil.TempDir("", "")
|
||||||
|
|
@ -429,7 +435,7 @@ func (state *HelmState) TemplateReleases(helm helmexec.Interface, additionalValu
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
temp, errs := state.downloadCharts(helm, dir, workerLimit, "template")
|
temp, errs := st.downloadCharts(helm, dir, workerLimit, "template")
|
||||||
|
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
|
|
@ -440,8 +446,8 @@ func (state *HelmState) TemplateReleases(helm helmexec.Interface, additionalValu
|
||||||
helm.SetExtraArgs(args...)
|
helm.SetExtraArgs(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, release := range state.Releases {
|
for _, release := range st.Releases {
|
||||||
flags, err := state.flagsForTemplate(helm, &release)
|
flags, err := st.flagsForTemplate(helm, &release)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
|
@ -463,8 +469,8 @@ func (state *HelmState) TemplateReleases(helm helmexec.Interface, additionalValu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := state.triggerCleanupEvent(&release, "template"); err != nil {
|
if _, err := st.triggerCleanupEvent(&release, "template"); err != nil {
|
||||||
state.logger.Warnf("warn: %v\n", err)
|
st.logger.Warnf("warn: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -476,7 +482,7 @@ func (state *HelmState) TemplateReleases(helm helmexec.Interface, additionalValu
|
||||||
}
|
}
|
||||||
|
|
||||||
// LintReleases wrapper for executing helm lint on the releases
|
// LintReleases wrapper for executing helm lint on the releases
|
||||||
func (state *HelmState) LintReleases(helm helmexec.Interface, additionalValues []string, args []string, workerLimit int) []error {
|
func (st *HelmState) LintReleases(helm helmexec.Interface, additionalValues []string, args []string, workerLimit int) []error {
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
// Create tmp directory and bail immediately if it fails
|
// Create tmp directory and bail immediately if it fails
|
||||||
dir, err := ioutil.TempDir("", "")
|
dir, err := ioutil.TempDir("", "")
|
||||||
|
|
@ -486,7 +492,7 @@ func (state *HelmState) LintReleases(helm helmexec.Interface, additionalValues [
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
temp, errs := state.downloadCharts(helm, dir, workerLimit, "lint")
|
temp, errs := st.downloadCharts(helm, dir, workerLimit, "lint")
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
return errs
|
return errs
|
||||||
|
|
@ -496,8 +502,8 @@ func (state *HelmState) LintReleases(helm helmexec.Interface, additionalValues [
|
||||||
helm.SetExtraArgs(args...)
|
helm.SetExtraArgs(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, release := range state.Releases {
|
for _, release := range st.Releases {
|
||||||
flags, err := state.flagsForLint(helm, &release)
|
flags, err := st.flagsForLint(helm, &release)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
|
@ -519,8 +525,8 @@ func (state *HelmState) LintReleases(helm helmexec.Interface, additionalValues [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := state.triggerCleanupEvent(&release, "lint"); err != nil {
|
if _, err := st.triggerCleanupEvent(&release, "lint"); err != nil {
|
||||||
state.logger.Warnf("warn: %v\n", err)
|
st.logger.Warnf("warn: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -551,9 +557,9 @@ type diffPrepareResult struct {
|
||||||
errors []*ReleaseError
|
errors []*ReleaseError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalValues []string, concurrency int, detailedExitCode, suppressSecrets bool) ([]diffPrepareResult, []error) {
|
func (st *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalValues []string, concurrency int, detailedExitCode, suppressSecrets bool) ([]diffPrepareResult, []error) {
|
||||||
releases := []ReleaseSpec{}
|
releases := []ReleaseSpec{}
|
||||||
for _, r := range state.Releases {
|
for _, r := range st.Releases {
|
||||||
if r.Installed == nil || *r.Installed {
|
if r.Installed == nil || *r.Installed {
|
||||||
releases = append(releases, r)
|
releases = append(releases, r)
|
||||||
}
|
}
|
||||||
|
|
@ -575,9 +581,9 @@ func (state *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalV
|
||||||
for release := range jobs {
|
for release := range jobs {
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
|
|
||||||
state.applyDefaultsTo(release)
|
st.applyDefaultsTo(release)
|
||||||
|
|
||||||
flags, err := state.flagsForDiff(helm, release)
|
flags, err := st.flagsForDiff(helm, release)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
|
|
@ -644,8 +650,8 @@ func (state *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalV
|
||||||
|
|
||||||
// DiffReleases wrapper for executing helm diff on the releases
|
// DiffReleases wrapper for executing helm diff on the releases
|
||||||
// It returns releases that had any changes
|
// It returns releases that had any changes
|
||||||
func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []string, workerLimit int, detailedExitCode, suppressSecrets bool, triggerCleanupEvents bool) ([]*ReleaseSpec, []error) {
|
func (st *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []string, workerLimit int, detailedExitCode, suppressSecrets bool, triggerCleanupEvents bool) ([]*ReleaseSpec, []error) {
|
||||||
preps, prepErrs := state.prepareDiffReleases(helm, additionalValues, workerLimit, detailedExitCode, suppressSecrets)
|
preps, prepErrs := st.prepareDiffReleases(helm, additionalValues, workerLimit, detailedExitCode, suppressSecrets)
|
||||||
if len(prepErrs) > 0 {
|
if len(prepErrs) > 0 {
|
||||||
return []*ReleaseSpec{}, prepErrs
|
return []*ReleaseSpec{}, prepErrs
|
||||||
}
|
}
|
||||||
|
|
@ -668,7 +674,7 @@ func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues [
|
||||||
for prep := range jobQueue {
|
for prep := range jobQueue {
|
||||||
flags := prep.flags
|
flags := prep.flags
|
||||||
release := prep.release
|
release := prep.release
|
||||||
if err := helm.DiffRelease(release.Name, normalizeChart(state.basePath, release.Chart), flags...); err != nil {
|
if err := helm.DiffRelease(release.Name, normalizeChart(st.basePath, release.Chart), flags...); err != nil {
|
||||||
switch e := err.(type) {
|
switch e := err.(type) {
|
||||||
case *exec.ExitError:
|
case *exec.ExitError:
|
||||||
// Propagate any non-zero exit status from the external command like `helm` that is failed under the hood
|
// Propagate any non-zero exit status from the external command like `helm` that is failed under the hood
|
||||||
|
|
@ -683,8 +689,8 @@ func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues [
|
||||||
}
|
}
|
||||||
|
|
||||||
if triggerCleanupEvents {
|
if triggerCleanupEvents {
|
||||||
if _, err := state.triggerCleanupEvent(prep.release, "diff"); err != nil {
|
if _, err := st.triggerCleanupEvent(prep.release, "diff"); err != nil {
|
||||||
state.logger.Warnf("warn: %v\n", err)
|
st.logger.Warnf("warn: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -718,14 +724,14 @@ func (state *HelmState) DiffReleases(helm helmexec.Interface, additionalValues [
|
||||||
return rs, errs
|
return rs, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *HelmState) ReleaseStatuses(helm helmexec.Interface, workerLimit int) []error {
|
func (st *HelmState) ReleaseStatuses(helm helmexec.Interface, workerLimit int) []error {
|
||||||
var errs []error
|
var errs []error
|
||||||
jobQueue := make(chan ReleaseSpec)
|
jobQueue := make(chan ReleaseSpec)
|
||||||
doneQueue := make(chan bool)
|
doneQueue := make(chan bool)
|
||||||
errQueue := make(chan error)
|
errQueue := make(chan error)
|
||||||
|
|
||||||
if workerLimit < 1 {
|
if workerLimit < 1 {
|
||||||
workerLimit = len(state.Releases)
|
workerLimit = len(st.Releases)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitGroup is required to wait until goroutine per job in job queue cleanly stops.
|
// WaitGroup is required to wait until goroutine per job in job queue cleanly stops.
|
||||||
|
|
@ -745,13 +751,13 @@ func (state *HelmState) ReleaseStatuses(helm helmexec.Interface, workerLimit int
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for _, release := range state.Releases {
|
for _, release := range st.Releases {
|
||||||
jobQueue <- release
|
jobQueue <- release
|
||||||
}
|
}
|
||||||
close(jobQueue)
|
close(jobQueue)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for i := 0; i < len(state.Releases); {
|
for i := 0; i < len(st.Releases); {
|
||||||
select {
|
select {
|
||||||
case err := <-errQueue:
|
case err := <-errQueue:
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
|
|
@ -770,11 +776,11 @@ func (state *HelmState) ReleaseStatuses(helm helmexec.Interface, workerLimit int
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteReleases wrapper for executing helm delete on the releases
|
// DeleteReleases wrapper for executing helm delete on the releases
|
||||||
func (state *HelmState) DeleteReleases(helm helmexec.Interface, purge bool) []error {
|
func (st *HelmState) DeleteReleases(helm helmexec.Interface, purge bool) []error {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
|
|
||||||
for _, release := range state.Releases {
|
for _, release := range st.Releases {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(wg *sync.WaitGroup, release ReleaseSpec) {
|
go func(wg *sync.WaitGroup, release ReleaseSpec) {
|
||||||
flags := []string{}
|
flags := []string{}
|
||||||
|
|
@ -797,11 +803,11 @@ func (state *HelmState) DeleteReleases(helm helmexec.Interface, purge bool) []er
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestReleases wrapper for executing helm test on the releases
|
// TestReleases wrapper for executing helm test on the releases
|
||||||
func (state *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, timeout int) []error {
|
func (st *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, timeout int) []error {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
|
|
||||||
for _, release := range state.Releases {
|
for _, release := range st.Releases {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(wg *sync.WaitGroup, release ReleaseSpec) {
|
go func(wg *sync.WaitGroup, release ReleaseSpec) {
|
||||||
flags := []string{}
|
flags := []string{}
|
||||||
|
|
@ -825,10 +831,10 @@ func (state *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, time
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean will remove any generated secrets
|
// Clean will remove any generated secrets
|
||||||
func (state *HelmState) Clean() []error {
|
func (st *HelmState) Clean() []error {
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
|
|
||||||
for _, release := range state.Releases {
|
for _, release := range st.Releases {
|
||||||
for _, value := range release.generatedValues {
|
for _, value := range release.generatedValues {
|
||||||
err := os.Remove(value)
|
err := os.Remove(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -845,7 +851,7 @@ func (state *HelmState) Clean() []error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilterReleases allows for the execution of helm commands against a subset of the releases in the helmfile.
|
// FilterReleases allows for the execution of helm commands against a subset of the releases in the helmfile.
|
||||||
func (state *HelmState) FilterReleases(labels []string) error {
|
func (st *HelmState) FilterReleases(labels []string) error {
|
||||||
var filteredReleases []ReleaseSpec
|
var filteredReleases []ReleaseSpec
|
||||||
releaseSet := map[string][]ReleaseSpec{}
|
releaseSet := map[string][]ReleaseSpec{}
|
||||||
filters := []ReleaseFilter{}
|
filters := []ReleaseFilter{}
|
||||||
|
|
@ -856,7 +862,7 @@ func (state *HelmState) FilterReleases(labels []string) error {
|
||||||
}
|
}
|
||||||
filters = append(filters, f)
|
filters = append(filters, f)
|
||||||
}
|
}
|
||||||
for _, r := range state.Releases {
|
for _, r := range st.Releases {
|
||||||
if r.Labels == nil {
|
if r.Labels == nil {
|
||||||
r.Labels = map[string]string{}
|
r.Labels = map[string]string{}
|
||||||
}
|
}
|
||||||
|
|
@ -875,17 +881,17 @@ func (state *HelmState) FilterReleases(labels []string) error {
|
||||||
for _, r := range releaseSet {
|
for _, r := range releaseSet {
|
||||||
filteredReleases = append(filteredReleases, r...)
|
filteredReleases = append(filteredReleases, r...)
|
||||||
}
|
}
|
||||||
state.Releases = filteredReleases
|
st.Releases = filteredReleases
|
||||||
numFound := len(filteredReleases)
|
numFound := len(filteredReleases)
|
||||||
state.logger.Debugf("%d release(s) matching %s found in %s\n", numFound, strings.Join(labels, ","), state.FilePath)
|
st.logger.Debugf("%d release(s) matching %s found in %s\n", numFound, strings.Join(labels, ","), st.FilePath)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *HelmState) PrepareRelease(helm helmexec.Interface, helmfileCommand string) []error {
|
func (st *HelmState) PrepareRelease(helm helmexec.Interface, helmfileCommand string) []error {
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
|
|
||||||
for _, release := range state.Releases {
|
for _, release := range st.Releases {
|
||||||
if _, err := state.triggerPrepareEvent(&release, helmfileCommand); err != nil {
|
if _, err := st.triggerPrepareEvent(&release, helmfileCommand); err != nil {
|
||||||
errs = append(errs, &ReleaseError{&release, err})
|
errs = append(errs, &ReleaseError{&release, err})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -896,23 +902,23 @@ func (state *HelmState) PrepareRelease(helm helmexec.Interface, helmfileCommand
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *HelmState) triggerPrepareEvent(r *ReleaseSpec, helmfileCommand string) (bool, error) {
|
func (st *HelmState) triggerPrepareEvent(r *ReleaseSpec, helmfileCommand string) (bool, error) {
|
||||||
return state.triggerReleaseEvent("prepare", r, helmfileCommand)
|
return st.triggerReleaseEvent("prepare", r, helmfileCommand)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *HelmState) triggerCleanupEvent(r *ReleaseSpec, helmfileCommand string) (bool, error) {
|
func (st *HelmState) triggerCleanupEvent(r *ReleaseSpec, helmfileCommand string) (bool, error) {
|
||||||
return state.triggerReleaseEvent("cleanup", r, helmfileCommand)
|
return st.triggerReleaseEvent("cleanup", r, helmfileCommand)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *HelmState) triggerReleaseEvent(evt string, r *ReleaseSpec, helmfileCmd string) (bool, error) {
|
func (st *HelmState) triggerReleaseEvent(evt string, r *ReleaseSpec, helmfileCmd string) (bool, error) {
|
||||||
bus := &event.Bus{
|
bus := &event.Bus{
|
||||||
Hooks: r.Hooks,
|
Hooks: r.Hooks,
|
||||||
StateFilePath: state.FilePath,
|
StateFilePath: st.FilePath,
|
||||||
BasePath: state.basePath,
|
BasePath: st.basePath,
|
||||||
Namespace: state.Namespace,
|
Namespace: st.Namespace,
|
||||||
Env: state.Env,
|
Env: st.Env,
|
||||||
Logger: state.logger,
|
Logger: st.logger,
|
||||||
ReadFile: state.readFile,
|
ReadFile: st.readFile,
|
||||||
}
|
}
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"Release": r,
|
"Release": r,
|
||||||
|
|
@ -922,12 +928,12 @@ func (state *HelmState) triggerReleaseEvent(evt string, r *ReleaseSpec, helmfile
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateDeps wrapper for updating dependencies on the releases
|
// UpdateDeps wrapper for updating dependencies on the releases
|
||||||
func (state *HelmState) UpdateDeps(helm helmexec.Interface) []error {
|
func (st *HelmState) UpdateDeps(helm helmexec.Interface) []error {
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
|
|
||||||
for _, release := range state.Releases {
|
for _, release := range st.Releases {
|
||||||
if isLocalChart(release.Chart) {
|
if isLocalChart(release.Chart) {
|
||||||
if err := helm.UpdateDeps(normalizeChart(state.basePath, release.Chart)); err != nil {
|
if err := helm.UpdateDeps(normalizeChart(st.basePath, release.Chart)); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -939,16 +945,16 @@ func (state *HelmState) UpdateDeps(helm helmexec.Interface) []error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// JoinBase returns an absolute path in the form basePath/relative
|
// JoinBase returns an absolute path in the form basePath/relative
|
||||||
func (state *HelmState) JoinBase(relPath string) string {
|
func (st *HelmState) JoinBase(relPath string) string {
|
||||||
return filepath.Join(state.basePath, relPath)
|
return filepath.Join(st.basePath, relPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalizes relative path to absolute one
|
// normalizes relative path to absolute one
|
||||||
func (state *HelmState) normalizePath(path string) string {
|
func (st *HelmState) normalizePath(path string) string {
|
||||||
if filepath.IsAbs(path) {
|
if filepath.IsAbs(path) {
|
||||||
return path
|
return path
|
||||||
} else {
|
} else {
|
||||||
return state.JoinBase(path)
|
return st.JoinBase(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1000,25 +1006,25 @@ func findChartDirectory(topLevelDir string) (string, error) {
|
||||||
return topLevelDir, errors.New("No Chart.yaml found")
|
return topLevelDir, errors.New("No Chart.yaml found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) {
|
func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) {
|
||||||
flags := []string{}
|
flags := []string{}
|
||||||
if release.Version != "" {
|
if release.Version != "" {
|
||||||
flags = append(flags, "--version", release.Version)
|
flags = append(flags, "--version", release.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.isDevelopment(release) {
|
if st.isDevelopment(release) {
|
||||||
flags = append(flags, "--devel")
|
flags = append(flags, "--devel")
|
||||||
}
|
}
|
||||||
|
|
||||||
if release.Verify != nil && *release.Verify || state.HelmDefaults.Verify {
|
if release.Verify != nil && *release.Verify || st.HelmDefaults.Verify {
|
||||||
flags = append(flags, "--verify")
|
flags = append(flags, "--verify")
|
||||||
}
|
}
|
||||||
|
|
||||||
if release.Wait != nil && *release.Wait || state.HelmDefaults.Wait {
|
if release.Wait != nil && *release.Wait || st.HelmDefaults.Wait {
|
||||||
flags = append(flags, "--wait")
|
flags = append(flags, "--wait")
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout := state.HelmDefaults.Timeout
|
timeout := st.HelmDefaults.Timeout
|
||||||
if release.Timeout != nil {
|
if release.Timeout != nil {
|
||||||
timeout = *release.Timeout
|
timeout = *release.Timeout
|
||||||
}
|
}
|
||||||
|
|
@ -1026,51 +1032,51 @@ func (state *HelmState) flagsForUpgrade(helm helmexec.Interface, release *Releas
|
||||||
flags = append(flags, "--timeout", fmt.Sprintf("%d", timeout))
|
flags = append(flags, "--timeout", fmt.Sprintf("%d", timeout))
|
||||||
}
|
}
|
||||||
|
|
||||||
if release.Force != nil && *release.Force || state.HelmDefaults.Force {
|
if release.Force != nil && *release.Force || st.HelmDefaults.Force {
|
||||||
flags = append(flags, "--force")
|
flags = append(flags, "--force")
|
||||||
}
|
}
|
||||||
|
|
||||||
if release.RecreatePods != nil && *release.RecreatePods || state.HelmDefaults.RecreatePods {
|
if release.RecreatePods != nil && *release.RecreatePods || st.HelmDefaults.RecreatePods {
|
||||||
flags = append(flags, "--recreate-pods")
|
flags = append(flags, "--recreate-pods")
|
||||||
}
|
}
|
||||||
|
|
||||||
common, err := state.namespaceAndValuesFlags(helm, release)
|
common, err := st.namespaceAndValuesFlags(helm, release)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return append(flags, common...), nil
|
return append(flags, common...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *HelmState) flagsForTemplate(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) {
|
func (st *HelmState) flagsForTemplate(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) {
|
||||||
flags := []string{
|
flags := []string{
|
||||||
"--name", release.Name,
|
"--name", release.Name,
|
||||||
}
|
}
|
||||||
common, err := state.namespaceAndValuesFlags(helm, release)
|
common, err := st.namespaceAndValuesFlags(helm, release)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return append(flags, common...), nil
|
return append(flags, common...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) {
|
func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) {
|
||||||
flags := []string{}
|
flags := []string{}
|
||||||
if release.Version != "" {
|
if release.Version != "" {
|
||||||
flags = append(flags, "--version", release.Version)
|
flags = append(flags, "--version", release.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.isDevelopment(release) {
|
if st.isDevelopment(release) {
|
||||||
flags = append(flags, "--devel")
|
flags = append(flags, "--devel")
|
||||||
}
|
}
|
||||||
|
|
||||||
common, err := state.namespaceAndValuesFlags(helm, release)
|
common, err := st.namespaceAndValuesFlags(helm, release)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return append(flags, common...), nil
|
return append(flags, common...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *HelmState) isDevelopment(release *ReleaseSpec) bool {
|
func (st *HelmState) isDevelopment(release *ReleaseSpec) bool {
|
||||||
result := state.HelmDefaults.Devel
|
result := st.HelmDefaults.Devel
|
||||||
if release.Devel != nil {
|
if release.Devel != nil {
|
||||||
result = *release.Devel
|
result = *release.Devel
|
||||||
}
|
}
|
||||||
|
|
@ -1078,16 +1084,16 @@ func (state *HelmState) isDevelopment(release *ReleaseSpec) bool {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *HelmState) flagsForLint(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) {
|
func (st *HelmState) flagsForLint(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) {
|
||||||
return state.namespaceAndValuesFlags(helm, release)
|
return st.namespaceAndValuesFlags(helm, release)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *HelmState) RenderValuesFileToBytes(path string) ([]byte, error) {
|
func (st *HelmState) RenderValuesFileToBytes(path string) ([]byte, error) {
|
||||||
r := valuesfile.NewRenderer(state.readFile, filepath.Dir(path), state.Env)
|
r := tmpl.NewFileRenderer(st.readFile, filepath.Dir(path), st.envTemplateData())
|
||||||
return r.RenderToBytes(path)
|
return r.RenderToBytes(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (state *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) {
|
func (st *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) {
|
||||||
flags := []string{}
|
flags := []string{}
|
||||||
if release.Namespace != "" {
|
if release.Namespace != "" {
|
||||||
flags = append(flags, "--namespace", release.Namespace)
|
flags = append(flags, "--namespace", release.Namespace)
|
||||||
|
|
@ -1095,13 +1101,18 @@ func (state *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release
|
||||||
for _, value := range release.Values {
|
for _, value := range release.Values {
|
||||||
switch typedValue := value.(type) {
|
switch typedValue := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
path := state.normalizePath(release.ValuesPathPrefix + typedValue)
|
path := st.normalizePath(release.ValuesPathPrefix + typedValue)
|
||||||
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
if release.MissingFileHandler == nil && *release.MissingFileHandler == "Error" {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else {
|
||||||
|
st.logger.Warnf("skipping missing values file \"%s\"", path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
yamlBytes, err := state.RenderValuesFileToBytes(path)
|
yamlBytes, err := st.RenderValuesFileToBytes(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to render values files \"%s\": %v", typedValue, err)
|
return nil, fmt.Errorf("failed to render values files \"%s\": %v", typedValue, err)
|
||||||
}
|
}
|
||||||
|
|
@ -1115,7 +1126,7 @@ func (state *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release
|
||||||
if _, err := valfile.Write(yamlBytes); err != nil {
|
if _, err := valfile.Write(yamlBytes); err != nil {
|
||||||
return nil, fmt.Errorf("failed to write %s: %v", valfile.Name(), err)
|
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))
|
st.logger.Debugf("successfully generated the value file at %s. produced:\n%s", path, string(yamlBytes))
|
||||||
flags = append(flags, "--values", valfile.Name())
|
flags = append(flags, "--values", valfile.Name())
|
||||||
|
|
||||||
case map[interface{}]interface{}:
|
case map[interface{}]interface{}:
|
||||||
|
|
@ -1133,10 +1144,16 @@ func (state *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release
|
||||||
flags = append(flags, "--values", valfile.Name())
|
flags = append(flags, "--values", valfile.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, value := range release.Secrets {
|
for _, value := range release.Secrets {
|
||||||
path := state.normalizePath(release.ValuesPathPrefix + value)
|
path := st.normalizePath(release.ValuesPathPrefix + value)
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
|
if release.MissingFileHandler == nil && *release.MissingFileHandler == "Error" {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else {
|
||||||
|
st.logger.Warnf("skipping missing secrets file \"%s\"", path)
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
valfile, err := helm.DecryptSecret(path)
|
valfile, err := helm.DecryptSecret(path)
|
||||||
|
|
@ -1152,7 +1169,7 @@ func (state *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release
|
||||||
if set.Value != "" {
|
if set.Value != "" {
|
||||||
flags = append(flags, "--set", fmt.Sprintf("%s=%s", escape(set.Name), escape(set.Value)))
|
flags = append(flags, "--set", fmt.Sprintf("%s=%s", escape(set.Name), escape(set.Value)))
|
||||||
} else if set.File != "" {
|
} else if set.File != "" {
|
||||||
flags = append(flags, "--set-file", fmt.Sprintf("%s=%s", escape(set.Name), state.normalizePath(set.File)))
|
flags = append(flags, "--set-file", fmt.Sprintf("%s=%s", escape(set.Name), st.normalizePath(set.File)))
|
||||||
} else if len(set.Values) > 0 {
|
} else if len(set.Values) > 0 {
|
||||||
items := make([]string, len(set.Values))
|
items := make([]string, len(set.Values))
|
||||||
for i, raw := range set.Values {
|
for i, raw := range set.Values {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/roboll/helmfile/tmpl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (st *HelmState) envTemplateData() EnvironmentTemplateData {
|
||||||
|
return EnvironmentTemplateData{
|
||||||
|
st.Env,
|
||||||
|
st.Namespace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *HelmState) ExecuteTemplates() (*HelmState, error) {
|
||||||
|
r := *st
|
||||||
|
|
||||||
|
for i, rt := range st.Releases {
|
||||||
|
tmplData := ReleaseTemplateData{
|
||||||
|
Environment: st.Env,
|
||||||
|
Release: rt,
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
st.Releases[i] = *r
|
||||||
|
}
|
||||||
|
|
||||||
|
return &r, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/roboll/helmfile/environment"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHelmState_executeTemplates(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input ReleaseSpec
|
||||||
|
want ReleaseSpec
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Has template expressions in chart, values, and secrets",
|
||||||
|
input: ReleaseSpec{
|
||||||
|
Chart: "test-charts/{{ .Release.Name }}",
|
||||||
|
Version: "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"},
|
||||||
|
},
|
||||||
|
want: ReleaseSpec{
|
||||||
|
Chart: "test-charts/test-app",
|
||||||
|
Version: "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"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
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("Unexpected error: %v", err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := r.Releases[0]
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual.Chart, tt.want.Chart) {
|
||||||
|
t.Errorf("expected %+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)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual.Values, tt.want.Values) {
|
||||||
|
t.Errorf("expected %+v, got %+v", tt.want.Values, actual.Values)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(actual.Secrets, tt.want.Secrets) {
|
||||||
|
t.Errorf("expected %+v, got %+v", tt.want.Secrets, actual.Secrets)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import "github.com/roboll/helmfile/environment"
|
||||||
|
|
||||||
|
// TemplateSpec defines the structure of a reusable and composable template for helm releases.
|
||||||
|
type TemplateSpec struct {
|
||||||
|
ReleaseSpec `yaml:",inline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnvironmentTemplateData provides variables accessible while executing golang text/template expressions in helmfile and values YAML files
|
||||||
|
type EnvironmentTemplateData 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseTemplateData provides variables accessible while executing golang text/template expressions in releases of a helmfile YAML file
|
||||||
|
type ReleaseTemplateData struct {
|
||||||
|
// Environment is accessible as `.Environment` from any template expression executed by the renderer
|
||||||
|
Environment environment.Environment
|
||||||
|
// Release is accessible as `.Release` from any template expression executed by the renderer
|
||||||
|
Release ReleaseSpec
|
||||||
|
}
|
||||||
66
tmpl/file.go
66
tmpl/file.go
|
|
@ -1,66 +0,0 @@
|
||||||
package tmpl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/roboll/helmfile/environment"
|
|
||||||
)
|
|
||||||
|
|
||||||
type templateFileRenderer struct {
|
|
||||||
ReadFile func(string) ([]byte, error)
|
|
||||||
Context *Context
|
|
||||||
Data TemplateData
|
|
||||||
}
|
|
||||||
|
|
||||||
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, namespace string) *templateFileRenderer {
|
|
||||||
return &templateFileRenderer{
|
|
||||||
ReadFile: readFile,
|
|
||||||
Context: &Context{
|
|
||||||
basePath: basePath,
|
|
||||||
readFile: readFile,
|
|
||||||
},
|
|
||||||
Data: TemplateData{
|
|
||||||
Environment: env,
|
|
||||||
Namespace: namespace,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFirstPassRenderer(basePath string, env environment.Environment) *templateFileRenderer {
|
|
||||||
return &templateFileRenderer{
|
|
||||||
ReadFile: ioutil.ReadFile,
|
|
||||||
Context: &Context{
|
|
||||||
preRender: true,
|
|
||||||
basePath: basePath,
|
|
||||||
readFile: ioutil.ReadFile,
|
|
||||||
},
|
|
||||||
Data: TemplateData{
|
|
||||||
Environment: env,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *templateFileRenderer) RenderTemplateFileToBuffer(file string) (*bytes.Buffer, error) {
|
|
||||||
content, err := r.ReadFile(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.RenderTemplateContentToBuffer(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *templateFileRenderer) RenderTemplateContentToBuffer(content []byte) (*bytes.Buffer, error) {
|
|
||||||
return r.Context.RenderTemplateToBuffer(string(content), r.Data)
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
package tmpl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileRenderer struct {
|
||||||
|
ReadFile func(string) ([]byte, error)
|
||||||
|
Context *Context
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFileRenderer(readFile func(filename string) ([]byte, error), basePath string, data interface{}) *FileRenderer {
|
||||||
|
return &FileRenderer{
|
||||||
|
ReadFile: readFile,
|
||||||
|
Context: &Context{
|
||||||
|
basePath: basePath,
|
||||||
|
readFile: readFile,
|
||||||
|
},
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFirstPassRenderer(basePath string, data interface{}) *FileRenderer {
|
||||||
|
return &FileRenderer{
|
||||||
|
ReadFile: ioutil.ReadFile,
|
||||||
|
Context: &Context{
|
||||||
|
preRender: true,
|
||||||
|
basePath: basePath,
|
||||||
|
readFile: ioutil.ReadFile,
|
||||||
|
},
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FileRenderer) RenderTemplateFileToBuffer(file string) (*bytes.Buffer, error) {
|
||||||
|
content, err := r.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.RenderTemplateContentToBuffer(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FileRenderer) RenderToBytes(path string) ([]byte, error) {
|
||||||
|
var yamlBytes []byte
|
||||||
|
splits := strings.Split(path, ".")
|
||||||
|
if len(splits) > 0 && splits[len(splits)-1] == "gotmpl" {
|
||||||
|
yamlBuf, err := r.RenderTemplateFileToBuffer(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to render [%s], because of %v", path, err)
|
||||||
|
}
|
||||||
|
yamlBytes = yamlBuf.Bytes()
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
yamlBytes, err = r.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load [%s]: %v", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return yamlBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FileRenderer) RenderTemplateContentToBuffer(content []byte) (*bytes.Buffer, error) {
|
||||||
|
return r.Context.RenderTemplateToBuffer(string(content), r.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FileRenderer) RenderTemplateContentToString(content []byte) (string, error) {
|
||||||
|
buf, err := r.Context.RenderTemplateToBuffer(string(content), r.Data)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package valuesfile
|
package tmpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -7,6 +7,11 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var emptyEnvTmplData = map[string]interface{}{
|
||||||
|
"Environment": environment.EmptyEnvironment,
|
||||||
|
"Namespace": "",
|
||||||
|
}
|
||||||
|
|
||||||
func TestRenderToBytes_Gotmpl(t *testing.T) {
|
func TestRenderToBytes_Gotmpl(t *testing.T) {
|
||||||
valuesYamlTmplContent := `foo:
|
valuesYamlTmplContent := `foo:
|
||||||
bar: '{{ readFile "data.txt" }}'
|
bar: '{{ readFile "data.txt" }}'
|
||||||
|
|
@ -17,7 +22,7 @@ func TestRenderToBytes_Gotmpl(t *testing.T) {
|
||||||
`
|
`
|
||||||
dataFile := "data.txt"
|
dataFile := "data.txt"
|
||||||
valuesTmplFile := "values.yaml.gotmpl"
|
valuesTmplFile := "values.yaml.gotmpl"
|
||||||
r := NewRenderer(func(filename string) ([]byte, error) {
|
r := NewFileRenderer(func(filename string) ([]byte, error) {
|
||||||
switch filename {
|
switch filename {
|
||||||
case valuesTmplFile:
|
case valuesTmplFile:
|
||||||
return []byte(valuesYamlTmplContent), nil
|
return []byte(valuesYamlTmplContent), nil
|
||||||
|
|
@ -25,7 +30,7 @@ func TestRenderToBytes_Gotmpl(t *testing.T) {
|
||||||
return []byte(dataFileContent), nil
|
return []byte(dataFileContent), nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unexpected filename: expected=%v or %v, actual=%s", dataFile, valuesTmplFile, filename)
|
return nil, fmt.Errorf("unexpected filename: expected=%v or %v, actual=%s", dataFile, valuesTmplFile, filename)
|
||||||
}, "", environment.EmptyEnvironment)
|
}, "", emptyEnvTmplData)
|
||||||
buf, err := r.RenderToBytes(valuesTmplFile)
|
buf, err := r.RenderToBytes(valuesTmplFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
|
@ -44,13 +49,13 @@ func TestRenderToBytes_Yaml(t *testing.T) {
|
||||||
bar: '{{ readFile "data.txt" }}'
|
bar: '{{ readFile "data.txt" }}'
|
||||||
`
|
`
|
||||||
valuesFile := "values.yaml"
|
valuesFile := "values.yaml"
|
||||||
r := NewRenderer(func(filename string) ([]byte, error) {
|
r := NewFileRenderer(func(filename string) ([]byte, error) {
|
||||||
switch filename {
|
switch filename {
|
||||||
case valuesFile:
|
case valuesFile:
|
||||||
return []byte(valuesYamlContent), nil
|
return []byte(valuesYamlContent), nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", valuesFile, filename)
|
return nil, fmt.Errorf("unexpected filename: expected=%v, actual=%s", valuesFile, filename)
|
||||||
}, "", environment.EmptyEnvironment)
|
}, "", emptyEnvTmplData)
|
||||||
buf, err := r.RenderToBytes(valuesFile)
|
buf, err := r.RenderToBytes(valuesFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unexpected error: %v", err)
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
package valuesfile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/roboll/helmfile/environment"
|
|
||||||
"github.com/roboll/helmfile/tmpl"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type renderer struct {
|
|
||||||
readFile func(string) ([]byte, error)
|
|
||||||
tmplFileRenderer tmpl.FileRenderer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRenderer(readFile func(filename string) ([]byte, error), basePath string, env environment.Environment) *renderer {
|
|
||||||
return &renderer{
|
|
||||||
readFile: readFile,
|
|
||||||
tmplFileRenderer: tmpl.NewFileRenderer(readFile, basePath, env, ""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *renderer) RenderToBytes(path string) ([]byte, error) {
|
|
||||||
var yamlBytes []byte
|
|
||||||
splits := strings.Split(path, ".")
|
|
||||||
if len(splits) > 0 && splits[len(splits)-1] == "gotmpl" {
|
|
||||||
yamlBuf, err := r.tmplFileRenderer.RenderTemplateFileToBuffer(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to render [%s], because of %v", path, err)
|
|
||||||
}
|
|
||||||
yamlBytes = yamlBuf.Bytes()
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
yamlBytes, err = r.readFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load [%s]: %v", path, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return yamlBytes, nil
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue