diff --git a/README.md b/README.md index cecbf7b3..094ecbb8 100644 --- a/README.md +++ b/README.md @@ -746,6 +746,10 @@ Use the [Helmfile Best Practices Guide](/docs/writing-helmfile.md) to write adva - Default values - Layering +We also have dedicated documentations for the following topics you might be interested: + +- [Shared Configurations Across Teams](/docs/shared-configurations-across-teams.md) + ## Using env files Helmfile itself doesn't have an ability to load env files. But you can write some bash script to achieve the goal: diff --git a/docs/shared-configuration-across-teams.md b/docs/shared-configuration-across-teams.md new file mode 100644 index 00000000..013fab6e --- /dev/null +++ b/docs/shared-configuration-across-teams.md @@ -0,0 +1,160 @@ +# Shared Configuration Across Teams + +Assume you have a two or more teams, each works for a different internal or external service, like: + +- Product 1 +- Product 2 +- Observability + +The simplest `helmfile.yaml` that declares the whole cluster that is composed of the three services would look like the below: + +```yaml +releases: +- name: product1-api + chart: product1-charts/api + # snip +- name: product1-web + chart: product1-charts/web + # snip +- name: product2-api + chart: saas-charts/api + # snip +- name: product2-web + chart: product2-charts/web + # snip +- name: observability-prometheus-operator + chart: stable/prometheus-operator + # snip +- name: observability-process-exporter + chart: stable/prometheus-operator + # snip +``` + +This works, but what if you wanted to a separate cluster per service to achieve smaller blast radius? + +Let's start by creating a `helmfile.yaml` for each service. + +`product1/helmfile.yaml`: + +```yaml +releases: +- name: product1-api + chart: product1-charts/api + # snip +- name: product1-web + chart: product1-charts/web + # snip +- name: observability-prometheus-operator + chart: stable/prometheus-operator + # snip +- name: observability-process-exporter + chart: stable/prometheus-operator + # snip +``` + +`product2/helmfile.yaml`: + +```yaml +releases: +- name: product2-api + chart: product2-charts/api + # snip +- name: product2-web + chart: product2-charts/web + # snip +- name: observability-prometheus-operator + chart: stable/prometheus-operator + # snip +- name: observability-process-exporter + chart: stable/prometheus-operator + # snip +``` + +You will (of course!) notice this isn't DRY. + +To remove the duplication of observability stack between the two helmfiles, create a "sub-helmfile" for the observability stack. + +`observability/helmfile.yaml`: + +```yaml +- name: observability-prometheus-operator + chart: stable/prometheus-operator + # snip +- name: observability-process-exporter + chart: stable/prometheus-operator + # snip +``` + +As you might have imagined, the observability helmfile can be reused from the two product helmfiles by declaring `helmfiles`. + +`product1/helmfile.yaml`: + +```yaml +helmfiles: +- ../observability/helmfile.yaml + +releases: +- name: product1-api + chart: product1-charts/api + # snip +- name: product1-web + chart: product1-charts/web + # snip +``` + +`product2/helmfile.yaml`: + +```yaml +helmfiles: +- ../observability/helmfile.yaml + +releases: +- name: product2-api + chart: product2-charts/api + # snip +- name: product2-web + chart: product2-charts/web + # snip +``` + +## Using sub-helmfile as a template + +You can go even further by generalizing the product related releases as a pair of `api` and `web`: + +`product/helmfile.yaml`: + +```yaml +releases: +- name: product{{ env "PRODUCT_ID" }}-api + chart: product{{ env "PRODUCT_ID" }}-charts/api + # snip +- name: product{{ env "PRODUCT_ID" }}-web + chart: product{{ env "PRODUCT_ID" }}-charts/web + # snip +``` + +And reusing it from the two product helmfiles: + + +`product1/helmfile.yaml`: + +```yaml +helmfiles: +- ../observability/helmfile.yaml +- ../product/helmfile.yaml +``` + +`product2/helmfile.yaml`: + +```yaml +helmfiles: +- ../observability/helmfile.yaml +- ../product/helmfile.yaml +``` + +Now that we use the environment variable `PRODUCT_ID` to as the parameters of release names, you need to set it before running `helmfile`, so that it produces the differently named releases per product: + +```console +$ PRODUCT_ID=1 helmfile -f product1/helmfile.yaml apply +$ PRODUCT_ID=2 helmfile -f product2/helmfile.yaml apply +``` diff --git a/pkg/app/app.go b/pkg/app/app.go index bf8fa24f..582abe13 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -110,6 +110,7 @@ func (a *App) visitStateFiles(fileOrDir string, do func(string) error) error { func (a *App) VisitDesiredStates(fileOrDir string, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) error { noMatchInHelmfiles := true + err := a.visitStateFiles(fileOrDir, func(f string) error { content, err := a.readFile(f) if err != nil { @@ -153,8 +154,6 @@ func (a *App) VisitDesiredStates(fileOrDir string, converge func(*state.HelmStat } } - errs := []error{} - if len(st.Helmfiles) > 0 { noMatchInSubHelmfiles := true for _, m := range st.Helmfiles { @@ -170,26 +169,25 @@ func (a *App) VisitDesiredStates(fileOrDir string, converge func(*state.HelmStat } } noMatchInHelmfiles = noMatchInHelmfiles && noMatchInSubHelmfiles - } else { - var err error - st, err = st.ExecuteTemplates() - if err != nil { - return fmt.Errorf("failed executing release templates in \"%s\": %v", f, err) - } - - var processed bool - processed, errs = converge(st, helm) - noMatchInHelmfiles = noMatchInHelmfiles && !processed } - return clean(st, errs) + templated, tmplErr := st.ExecuteTemplates() + if tmplErr != nil { + return fmt.Errorf("failed executing release templates in \"%s\": %v", f, tmplErr) + } + processed, errs := converge(templated, helm) + noMatchInHelmfiles = noMatchInHelmfiles && !processed + return clean(templated, errs) }) + if err != nil { return err } + if noMatchInHelmfiles { return &NoMatchingHelmfileError{selectors: a.Selectors, env: a.Env} } + return nil }