From 5ca7ce15bc178b4aef58100f7f249805cf237d6c Mon Sep 17 00:00:00 2001 From: Maksym Lushpenko Date: Sat, 29 Aug 2020 06:14:58 +0200 Subject: [PATCH] feat: common labels for all releases in a helmfile (#1415) This adds `comonLabels` option to helmfile by: - Adding `CommonLabels` to HelmState - Changing `markExcludedReleases` and `ListReleases` functions to merge common labels into release labels Resolves #1266 --- README.md | 38 ++++++++++++++++++++++++++++++++++++ pkg/app/app.go | 6 ++++++ pkg/app/app_test.go | 17 +++++++++------- pkg/state/state.go | 9 +++++++-- pkg/state/state_exec_tmpl.go | 6 ++++++ 5 files changed, 67 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2bfc3c3f..fa302d92 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,9 @@ helmDefaults: # when using helm 3.2+, automatically create release namespaces if they do not exist (default true) createNamespace: true +# these labels will be applied to all releases in a Helmfile. Useful in templating if you have a helmfile per environment or customer and don't want to copy the same label to each release +commonLabels: + hello: world # The desired states of Helm releases. # @@ -522,6 +525,41 @@ The `selector` parameter can be specified multiple times. Each parameter is reso In addition to user supplied labels, the name, the namespace, and the chart are available to be used as selectors. The chart will just be the chart name excluding the repository (Example `stable/filebeat` would be selected using `--selector chart=filebeat`). +`commonLabels` can be used when you want to apply the same label to all releases and use [templating](##Templates) based on that. +For instance, you install a number of charts on every customer but need to provide different values file per customer. + +templates/common.yaml: + +``` +templates: + nginx: &nginx + name: nginx + chart: stable/nginx-ingress + values: + - ../values/common/{{ .Release.Name }}.yaml + - ../values/{{ .Release.Labels.customer }}/{{ .Release.Name }}.yaml + + cert-manager: &cert-manager + name: cert-manager + chart: jetstack/cert-manager + values: + - ../values/common/{{ .Release.Name }}.yaml + - ../values/{{ .Release.Labels.customer }}/{{ .Release.Name }}.yaml +``` + +helmfile.yaml: + +``` +{{ readFile "templates/common.yaml" }} + +commonLabels: + customer: company + +releases: +- <<: *nginx +- <<: *cert-manager +``` + ## Templates You can use go's text/template expressions in `helmfile.yaml` and `values.yaml.gotmpl` (templated helm values files). `values.yaml` references will be used verbatim. In other words: diff --git a/pkg/app/app.go b/pkg/app/app.go index d04b458b..5377aaf4 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -416,6 +416,12 @@ func (a *App) ListReleases(c ListConfigProvider) error { //var releases m for _, r := range run.state.Releases { labels := "" + if r.Labels == nil { + r.Labels = map[string]string{} + } + for k, v := range run.state.CommonLabels { + r.Labels[k] = v + } for k, v := range r.Labels { labels = fmt.Sprintf("%s,%s:%s", labels, k, v) } diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index 4568d6d3..0e54e823 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -4,8 +4,6 @@ import ( "bufio" "bytes" "fmt" - "github.com/google/go-cmp/cmp" - "github.com/roboll/helmfile/pkg/remote" "io" "log" "os" @@ -17,6 +15,9 @@ import ( "sync" "testing" + "github.com/google/go-cmp/cmp" + "github.com/roboll/helmfile/pkg/remote" + "github.com/roboll/helmfile/pkg/exectest" "gotest.tools/v3/assert" @@ -4051,6 +4052,8 @@ releases: func TestList(t *testing.T) { files := map[string]string{ "/path/to/helmfile.d/first.yaml": ` +commonLabels: + common: label releases: - name: myrelease1 chart: mychart1 @@ -4094,11 +4097,11 @@ releases: assert.NilError(t, err) }) - expected := `NAME NAMESPACE ENABLED LABELS -myrelease1 false id:myrelease1 -myrelease2 true -myrelease3 true -myrelease4 true id:myrelease1 + expected := `NAME NAMESPACE ENABLED LABELS +myrelease1 false common:label,id:myrelease1 +myrelease2 true common:label +myrelease3 true +myrelease4 true id:myrelease1 ` assert.Equal(t, expected, out) } diff --git a/pkg/state/state.go b/pkg/state/state.go index a2d02c49..38d7de47 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -59,6 +59,7 @@ type HelmState struct { DeprecatedReleases []ReleaseSpec `yaml:"charts,omitempty"` OverrideNamespace string `yaml:"namespace,omitempty"` Repositories []RepositorySpec `yaml:"repositories,omitempty"` + CommonLabels map[string]string `yaml:"commonLabels,omitempty"` Releases []ReleaseSpec `yaml:"releases,omitempty"` Selectors []string `yaml:"-"` ApiVersions []string `yaml:"apiVersions,omitempty"` @@ -1634,14 +1635,14 @@ func (st *HelmState) SelectReleasesWithOverrides() ([]Release, error) { if err != nil { return nil, err } - rs, err := markExcludedReleases(st.GetReleasesWithOverrides(), st.Selectors, values) + rs, err := markExcludedReleases(st.GetReleasesWithOverrides(), st.Selectors, st.CommonLabels, values) if err != nil { return nil, err } return rs, nil } -func markExcludedReleases(releases []ReleaseSpec, selectors []string, values map[string]interface{}) ([]Release, error) { +func markExcludedReleases(releases []ReleaseSpec, selectors []string, commonLabels map[string]string, values map[string]interface{}) ([]Release, error) { var filteredReleases []Release filters := []ReleaseFilter{} for _, label := range selectors { @@ -1661,6 +1662,10 @@ func markExcludedReleases(releases []ReleaseSpec, selectors []string, values map // Strip off just the last portion for the name stable/newrelic would give newrelic chartSplit := strings.Split(r.Chart, "/") r.Labels["chart"] = chartSplit[len(chartSplit)-1] + //Merge CommonLabels into release labels + for k, v := range commonLabels { + r.Labels[k] = v + } var filterMatch bool for _, f := range filters { if r.Labels == nil { diff --git a/pkg/state/state_exec_tmpl.go b/pkg/state/state_exec_tmpl.go index c1b627db..7122f24e 100644 --- a/pkg/state/state_exec_tmpl.go +++ b/pkg/state/state_exec_tmpl.go @@ -83,6 +83,12 @@ func (st *HelmState) ExecuteTemplates() (*HelmState, error) { } for i, rt := range st.Releases { + if rt.Labels == nil { + rt.Labels = map[string]string{} + } + for k, v := range st.CommonLabels { + rt.Labels[k] = v + } successFlag := false for it, prev := 0, &rt; it < 6; it++ { tmplData := st.createReleaseTemplateData(prev, vals)