Compare commits

...
256 Commits
Author SHA1 Message Date
Yusuke Kuoka c623730374 Bump golang.org/x/crypto (#1877)
Resolves #1876
2021-06-10 09:16:11 +09:00
Yusuke Kuoka 72e71608b1 Fix chartify regression of missing chart dependencies (#1869)
* Fix chartify regression of missing chart dependencies

Fixes https://github.com/roboll/helmfile/issues/1867

* Add integration test cases for issues #1857 and #1867
2021-06-08 13:06:26 +09:00
dudicoco 0d4adfedab Document hooks events expressions (#1870)
Docs for https://github.com/roboll/helmfile/pull/882

Ref https://github.com/roboll/helmfile/issues/1868#issuecomment-854530386
2021-06-08 10:59:21 +09:00
Yusuke Kuoka 1a109f7400 Fix chartify inability to disable chart dependency (#1861)
Fixes #1857
2021-05-24 08:31:09 +09:00
Yusuke Kuoka 97bab23e0c Fix --skip-cleanup to work with chartify (#1860) 2021-05-24 08:30:58 +09:00
Yusuke Kuoka c881544255 Update README.md 2021-05-22 18:17:28 +09:00
dudicoco 92ba347d6f Add --skip-diff-on-install to helmfile diff (#1841)
* Add --skip-diff-on-install to helmfile diff
2021-05-16 14:02:53 +09:00
Yusuke Kuoka f502a0a779 fix: Do not fail on deps when chart is fetched by go-getter (#1852)
Fixes #1847
2021-05-16 13:43:59 +09:00
Radon Rosborough 77241607fa Fix Helmfile swallowing secrets decryption errors (#1849) 2021-05-14 09:19:32 +09:00
Quan TRAN f38958d6d7 Fix cache path issue on Windows (#1846) 2021-05-13 09:00:43 +09:00
Yusuke Kuoka 212e6acf23 Fix helmfile-lint not to stop on fisrt error (#1836)
Fixes #1826
2021-05-09 13:33:36 +09:00
Yusuke Kuoka efa404e275 Make --skip-needs=true the default option (#1835)
Ref https://github.com/roboll/helmfile/issues/1830#issuecomment-835632716
Ref #1692
Follow-up for #1772
2021-05-09 12:09:58 +09:00
Yusuke Kuoka 794ab9fc66 Fix lint and status not to fail with selector and needs (#1834)
Fixes #1830
2021-05-08 21:23:09 +09:00
Yusuke Kuoka 48bd5c0979 Update go.sum for successful build 2021-05-08 08:49:57 +00:00
Quan TRAN e39a0b3639 bump Go to 1.16.3 and cover helm 3.5.4 (#1825)
* bump go to 1.16.3

* add helm 3.5.4 to integration tests
2021-05-08 17:37:58 +09:00
katsew 58954109fb fix kubeContext does not globally defaulting its value (#1829)
Ref #1810
2021-05-08 17:35:46 +09:00
Yusuke Kuoka cb7fa6bf63 Bump vals to 0.14.0 (#1832)
This release includes new `ref+tfstates3://` and `ref+tfstategs://` backends for reading terraform states stored in s3 and google cloud storage, and features a new aws-sdk-go that supports AWS SSO, and the fix to allow referencing a local tfstate file by the absolute path.

See https://github.com/variantdev/vals/releases/tag/v0.14.0 for all the changes.
2021-05-08 17:33:37 +09:00
Yusuke Kuoka 44fb68b7e0 Use the incubator chart repo instead of chartcenter (#1833)
As ChartCenter has been shut down.

Ref https://github.com/roboll/helmfile/pull/1829#issuecomment-833330609
2021-05-08 17:33:24 +09:00
Quan TRAN ded0f1049a Fix filepath handling on Windows (#1754) 2021-05-02 09:22:25 +09:00
Yusuke Kuoka 08db073958 Fix broken selector and DAG calculation logic after --{include,skip}-needs addition with correct Release IDs (#1823)
#1772 broke `--selector` with `needs` in many ways.

The two biggest problems I've encountered were:

- duplicate releases even if you've provided a proper `selector` to deduplicate
- sync/deletion ordering broken when you have `needs`

For the first issue, we had to update `getSelectedReleases` function to also calculate the "selected releases and releases needed by the selected releases", and use that to calculate the DAG. That should have been done in #1772.

The latter started happening after I've fixed the first issue. The source of the issue was that `needs` turned out to be ambiguous in a few cases.

Previously, `needs: ["foo/bar"]` had two meanings. One for "needs release bar in kubecontext foo", another for "needs release bar in namespace foo".

Moreover, `needs: ["foo/bar/baz"]` had three meanings.

- `needs release baz in tiller namespace foo and namespace baz`
- `needs release baz in namespace bar in kubecontext foo`
- `needs release baz in tiller namespace bar in kubecontext foo`.

Especially, the first meaning doesn't make sense at all. Helm 2 solely use tillerNamespace for namespacing the release and Helm 3 uses namespace for that.

This fix sorts all the bugs and issues I've found so far around that, by changing the meanings of the above two examples as follows:

- `foo/bar` means `namespace=foo,name=bar` for Helm 3 and `tillerNamespace=foo,name=bar` for Helm 2
  - `needs release bar in kubecontext foo` is now `foo//bar`. Notice the extra `/` between `foo` and `bar`.
- `foo/bar/baz` means `kubecontext=foo,namespace=bar,name=baz` for Helm 3 and `kubecontext=foo,tillerNamespace=bar,name=baz` in Helm 2

Fixes #1818
2021-05-01 21:59:25 +09:00
Yusuke Kuoka de8644a504 Fix --selector results to correctly deduplicate releases (#1822)
Fixes #1818
2021-05-01 15:06:07 +09:00
katsew f28ad5af23 Fix defaulting and overriding kubeContext (#1814)
Ref #1810

* fix defaulting and overriding kubeContext

* make defaulting kubeContext side-effect free

* checkDuplicates should check releases with overriden values
2021-05-01 12:42:29 +09:00
dudicoco 77ee239db2 Lock helm plugin versions in Dockerfile (#1821) 2021-05-01 12:26:54 +09:00
d9e15754b7 Add documentation about re-using the environment state from parent in sub-helmfile (#1808)
Ref #762

Co-authored-by: Vladimir Avdoshka <vavdoshka@kyriba.com>
Co-authored-by: Quan TRAN <itscaro@users.noreply.github.com>
2021-05-01 12:26:00 +09:00
Yusuke Kuoka dabd7ad99f Fix repo sync to work on repos duplicated between helm v2 and v3 (#1816)
Fixes #1815
2021-04-30 09:27:23 +09:00
Yusuke Kuoka 204f78c8ff Fix --set and --set-file flags are not passed to chartify (#1803)
Fixes #1681
Ref https://github.com/variantdev/chartify/commit/286ed08a273d4229989aea5ac5c901612b43944c
2021-04-24 22:27:53 +09:00
Yusuke Kuoka 59b91a45a0 Fix adhoc dependencies not working with remote chart (#1802)
Fixes #1680

Ref https://github.com/variantdev/chartify/commit/8ff30374cb2a65af5167f3d47df81fe45d37b01d
2021-04-24 21:11:07 +09:00
Yusuke Kuoka 5a3bd7d649 Fix destory, delete, and test to work with chartify on local chart with dependencies that are not yet downloaded (#1801)
Fixes #1800
2021-04-24 20:50:16 +09:00
Yusuke Kuoka ce6a621414 Bump chartify to 0.8.3 to fix it not to break when two or more releases sharing the same chart and the namespace, but kubeContext (#1799)
Fixes #1695
2021-04-24 19:03:13 +09:00
Yusuke Kuoka 20f6c68108 Fix code format 2021-04-24 07:47:27 +00:00
astorathandAndrey Tuzhilin 3367822e89 feat: Move chartify logs to Debug (#1794)
Co-authored-by: Andrey Tuzhilin <andrey@zelf.co>
2021-04-24 16:06:21 +09:00
katsew 8702639510 fix: spec.KubeContext does not fallback to HelmDefaults.KubeContext (#1789)
* fix: spec.KubeContext does not fallback to HelmDefaults.KubeContext

Fixes #1782

* Add testcases to test without default kubecontext
2021-04-24 16:02:58 +09:00
katsew 41cbc25c35 fix: can't evaluate field KubeContext in type state.releaseTemplateDataRelease (#1797)
Fixes #1796
2021-04-24 15:28:20 +09:00
astorathandAndrey Tuzhilin 83189dbad3 Enable helmfile-diff to pass the output format to helm-diff (#1784)
* tests: fix vagrant test run

* feat: added an option to specify the different diff output format

* renamed diff-output to output

* renamed diff-output to output

Co-authored-by: Andrey Tuzhilin <andrey@zelf.co>
2021-04-24 08:07:24 +09:00
Yusuke Kuoka 28ade19419 Bump chartify to 0.8.2 (#1793)
* Bump chartify to 0.8.2

This version fixes charitfy not to fail when you used the combination of (1)helm 3 and (2)strategicMergePatches/jsonPatches/transformers etc that triggers chartify on (3)a chart that contains CRDs.

See https://github.com/roboll/helmfile/issues/1778#issuecomment-824451990 for details of the issue.

The chartify-side of this fix is https://github.com/variantdev/chartify/commit/55b23f9e9d43ae1105a536a659fa35f004806b2a

Fixes #1778
2021-04-23 09:53:52 +09:00
Yujun Zhang 378ae4ad72 Fix typo (#1788) 2021-04-23 09:27:37 +09:00
Yusuke Kuoka 30e0356ad6 Bump chartify to 0.8.1 (#1787)
This version fixes that chartify not to fail when the target chart misses `templates` directory.
2021-04-23 09:16:59 +09:00
Yusuke Kuoka eabda4cf28 Fix delete on release of uninstalling status (#1786)
* Fix helmfile destroy/delete not deleting `uninstalling` release

Ref https://github.com/roboll/helmfile/issues/1750#issuecomment-823677950

* Cover helm3 in helmfile-destroy test
2021-04-21 09:39:14 +09:00
Yusuke Kuoka 5617b593c8 Add test for helmfile-destroy with selector (#1785)
* Add test for helmfile-destroy with selector

I was curious if destroy with a selector is working at all while reading https://github.com/roboll/helmfile/issues/1750#issuecomment-823521572. So I added this test for verification. From the test result, it is working, though.

* Add one more test case for destroying disabled but installed release
2021-04-21 09:07:08 +09:00
Yusuke Kuoka 5d43b30a7c Add --{include,skip}-needs to various helmfile commands (#1772)
* Add --{include,skip}-needs to helmfile-sync and helmfile-apply

* Add --include-needs to helmfile-template

* Add TODO related to #1018

* Add a few new test files to cover new functionalities

* Update apply test to incorporate the change that the destroy and sync steps target affected releases only
2021-04-20 23:06:51 +09:00
K1Hyve d6db4b53e7 Added The Hyve to USERS.md (#1779) 2021-04-20 13:29:44 +09:00
Yusuke Kuoka 368dd13e16 Fix destroy not reversing the order of releases (#1777)
Fixes #1775
2021-04-20 13:27:45 +09:00
Yusuke Kuoka ae942c5288 Fix --skip-crds not working with chartify (#1774)
Follow-up for #1771
Ref #1770
Ref https://github.com/roboll/helmfile/pull/1771#issuecomment-817399338
2021-04-13 09:09:58 +09:00
Yusuke Kuoka a111e89b27 Add --skip-crds to helmfile sync and helmfile apply (#1771)
Resolves #1770
2021-04-11 10:38:23 +09:00
Yusuke Kuoka 589b26aaad Fix some hook log not honoring log level (#1769)
Fixes #1742
2021-04-10 16:50:42 +09:00
Yusuke Kuoka 1d7608f42c Fix helmfile repos to not fail with forceNamespace
Fixes #1752
2021-04-10 16:26:40 +09:00
Yusuke Kuoka efad26641d Add documentation about adhoc chart dependencies 2021-04-10 16:09:27 +09:00
Yusuke Kuoka 81b04fcb8b Add support for adhoc dependency to local chart
Helmfile has been providing a feature called "adhoc chart dependency" that basially enabled you to add Chart.yaml `dependencies` entry adhocly without forking or modifying the chart.

It was missing the support for using a local chart as the adhoc dependency. This patch adds that.

Usage:

`releases[].dependencies[].chart` is enhanced to accept the fs path to the local chart:

```
releases:
- name: foo
  chart: ./path/to/foo
  dependencies:
  - chart: ./path/to/bar
```

Resolves #1762
2021-04-10 16:09:27 +09:00
Yusuke Kuoka 74cd2bc147 Enable helmfile-deps to limit helm-dep-up by selector (#1764)
Ref https://github.com/roboll/helmfile/issues/809#issuecomment-814423653

This is not what had been requested originally in #809 but anyway- This enables you to limit helm-dep-up run by helmfile-deps by selector. For example, `helmfile -l name=foo deps` should result in only the release named `foo` to be helm-dep-uped.
2021-04-09 09:49:57 +09:00
Yusuke Kuoka 2ff06a8abd Fix chartify to take --include-crds into account (#1761)
Fixes #1760
2021-04-08 10:06:34 +09:00
Yusuke Kuoka d703e17239 Fix chartify not to fail on a chart with dependencies (#1759)
Manually verified to work by using the following example from @damoon (https://github.com/roboll/helmfile/issues/1655#issuecomment-801122431):

```
repositories:
  - name: prometheus-community
    url: https://prometheus-community.github.io/helm-charts
releases:
- name: metrics
  namespace: monitoring
  chart: prometheus-community/kube-prometheus-stack
  version: 13.13.0
  jsonPatches:
    - target:
        version: v1
        kind: Service
        name: metrics-kube-prometheus-st-kube-etcd
        namespace: kube-system
      patch:
        - op: replace
          path: /spec/ports/0/port
          value: "2381"
        - op: replace
          path: /spec/ports/0/targetPort
          value: "2381"
```

Before this fix:
```
$ helmfile template
(snip)
COMBINED OUTPUT:
  Error: found in Chart.yaml, but missing in charts/ directory: kube-state-metrics, prometheus-node-exporter, grafana
```

After this fix:

```
$ helmfile template | grep 2381
(snip)
Removing /tmp/chartify718204429/monitoring-metrics-f5479c8b8/kube-prometheus-stack/strategicmergepatches
Removing /tmp/chartify718204429/monitoring-metrics-f5479c8b8/kube-prometheus-stack/kustomization.yaml
Templating release=metrics, chart=/tmp/chartify718204429/monitoring-metrics-f5479c8b8/kube-prometheus-stack
    port: "2381"
    targetPort: "2381"
```

Fixes #1655

Ref https://github.com/variantdev/chartify/commit/07c3054d66021fa190464a998ade18607312c715
2021-04-08 07:03:18 +09:00
Yusuke Kuoka 3bfcc1ea42 Add support for kustomize v4 (#1758)
We use kustomize in two places. One for turning kustomize into a chart, and another for patching manifests and the chart.

This fixes the former to work with kustomize v4.

Fixes #1688
2021-04-08 06:15:16 +09:00
Quan TRANandYusuke Kuoka c65bdff62c Respect release filter in lint and status (#1672)
Ref #1190
Fixes #1651

Co-authored-by: Yusuke Kuoka <ykuoka@gmail.com>
2021-04-06 15:47:29 +09:00
Yusuke Kuoka f614e8b1bf feat: Helmfile renders *.yaml.gotmpl in a K8s manifests/kustomization directory (#1745)
Related to #494

This feature is mostly a built-in alternative to the `incubator/raw` chart without external dependency and has
access to helmfile's own template functions and template data.

The expected use-case of this feature is to add arbitrary K8s resources to your deployment.

Unlike the original issue raised in #494 this doesn't enable you to add arbitary resources to a release. That's another story. But this would be a good foundation for that, too.
2021-04-06 14:22:34 +09:00
Philipp HossnerandYusuke Kuoka 85accf7330 Add helm-secrets-encrypted values template file (#1701)
Secret files ending with .gotmpl are now also rendered as a gotemplate.

```
releases:
- name: myapp
  secrets:
  - secrets.yaml.gotmpl
```

Note that currently, .gotmpl files must be valid YAML files as well.

The expected use-case of this feature is to compose a YAML array from values and encrypted secrets.

Without this feature, you would have tried to do something like the below, which didn't work.

**Example (doesn't work!)**

`values.yaml.gotmpl`:

```
environment:
  -   name: MY_EXTERNAL_IP
      value: |
          {{ exec "./get-external-ip.sh" (list "") }}
```

`secrets.yaml`:
```
_sops:
  #...
environment:
  - name: MY_SECRET_VALUE
    value: (encrypted by sops)
```

`helmfile.yaml`:

```
releases:
- name: foo
  values:
  - values.yaml
  secrets:
  - secrets.yaml
```

This doesn't work because `values.yaml` and the decrypted `secrets.yaml` are passed to `helm` to be merged, and helm overrides the array instead of merging or concatenating the arrays.

**Example (works!)**

Instead of `values.yaml` and `secrets.yaml`, you provide a single `secrets.yaml.gotmpl` that is a valid YAML and encrypted by sops:

```
_sops:
  #...
environment:
  -   name: MY_EXTERNAL_IP
      value: |
          {{ exec "./get-external-ip.sh" (list "") }}
  - name: MY_SECRET_VALUE
    value: (encrypted by sops)
```

`helmfile.yaml`:

```
releases:
- name: foo
  secrets:
  - secrets.yaml.gotmpl
```

Helmfile decrypts the gotmpl by handing it over to helm-secrets and then renders the result as a gotmpl file. The end result is that you have a two-element array `environments` that can be just passed to helm.

Resolves #1700

Co-authored-by: Yusuke Kuoka <ykuoka@gmail.com>
2021-04-06 14:20:42 +09:00
Yujun Zhang a161796dc4 feat: Allow overriding chart via flag (#1751)
Adds `--chart` flag for overriding the selected release's chart ad-hoc-ly like `helmfile --chart $CHART template`.
This is handy when e.g. you want to have an ArgoCD application per each release in your helmfile.yaml, while also providing the ability to customize the release's chart without touching helmfile.yaml.

See https://github.com/roboll/helmfile/issues/1690#issuecomment-812321354 for more context.

Closes #1690
2021-04-06 13:20:41 +09:00
Javier Palacios 261367e7e9 Add kubectl hooks for applying file(s) or kustomize (#1736)
This enables you to write a `kubectl-apply` hook more declaratively than writing `command` and `args`:

```
releases:
- name: myapp
  chart: mychart
  hooks:
  - events: ["presync"]
    kubectlApply:
      filename: path/to/manifests
      #kustomize: path/to/kustomize
```
2021-04-06 13:16:35 +09:00
Quan TRAN 5cd0afcfa0 Add documentation about helmfile-fetch and StateValues alias (#1746)
Ref #1743, #1744, #1734
2021-04-01 09:48:58 +09:00
Quan TRAN 1748cbfcea Add .StateValues as alias of .Values in releaseTemplateDataRelease (#1744)
Ref #1743
2021-04-01 09:47:12 +09:00
Nenad Strainovic 200cae2a68 feat: --show-secrets on diff and apply commands (#1749)
Resolves #1674
2021-04-01 09:41:53 +09:00
astorathandAndrey Tuzhilin b1b7831a90 feat: make --kube-context override helmDefaults (#1673)
This would allow cli flag `--kube-context` to override value in helmDefaults allowing to use different values in local development and CI context.

Co-authored-by: Andrey Tuzhilin <andrey@3adigital.ru>
2021-03-30 16:53:19 +09:00
Quan TRAN 53c6d2f988 Add helmfile-fetch command to downloading and generating charts (#1734) 2021-03-30 16:26:31 +09:00
Yusuke Kuoka faa74962e6 Fix helmfile-template not writing to stdout (#1737)
This fixes #1691 by redirecting helm-template output to our logger when and only when --output-dir is being passed to helm-template.

See https://github.com/roboll/helmfile/pull/1691#issuecomment-805636021 for more context.
2021-03-25 10:16:52 +09:00
Gregoire MenuelandGregoire Menuel 28cf300ef2 Add support for kubeContext in environments (#1675)
Resolves #898
Resolves #1689

Co-authored-by: Gregoire Menuel <gregoire.menuel@veolia.com>
2021-03-23 18:06:53 +09:00
Chris Mellard e9d1c8534c feat: change to helmfile template so it respects logging levels (#1691) 2021-03-23 18:02:13 +09:00
astorath aafe74a3e1 Fix/secrets ordering (#1726)
Fixes issue introduced in #790: the order of secrets merged is not defined now, leading to unpredictable results in `helmfile apply`.
2021-03-23 18:00:57 +09:00
Andrey Tuzhilin db9d31ac06 test: fix DecryptSecret output 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin 4a9d753441 feat: make integration/vagrant 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin 4efaa656f9 test: return all tests 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin 59f4043fc5 fix: fixed secrets decryption failed issue 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin d04cd1b1a7 test: fix "Ensure helmfile fails when no helm-secrets is installed" test 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin 781c4e9ebe test: fix "Ensure helmfile fails when no helm-secrets is installed" test 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin 0c9934c357 test: ensure bash -eo pipefail (as in circleci) 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin 2983f5cd28 test: fix vault provisioning code 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin 7610d8d3c6 test: add secrets integration tests 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin 5cd853b773 test: add vault and sops for integration secret testing 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin f1c07879ad ci: simplify integration tests ci code for helm2 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin d1a2435ce1 ci: simplify integration tests ci code 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin 45b8eb6942 test: fix helm2 integration tests 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin 17a969ce83 ci: verify new integration tests 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin 4f02196dad test: reusable integration test 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin 85f19b6abd fix: gitignore *.lock 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin 42138d88bf test: vagrant for integration tests 2021-03-23 17:19:40 +09:00
Andrey Tuzhilin 2ef09d45ee test: sync HELM_VERSION and KUSTOMIZE_VERSION for tests 2021-03-23 16:58:56 +09:00
Andrey Tuzhilin 9b2a770dbe feat(Dockerfile): pin helm-secrets version 2021-03-23 16:58:56 +09:00
Andrey Tuzhilin 6085730a91 fix(Makefile): static-linux mod 2021-03-23 16:58:56 +09:00
Andrey Tuzhilin 94a8658635 feat(Dockerfile): bump helm version 2021-03-23 16:58:56 +09:00
Jason Witkowski 2618cfb38b Enable --wait-for-jobs flag introduced in helm 3.5 (#1715)
Fixes #1650
Fixes #785

This change introduces:

- `--wait-for-jobs` CLI override option to helmfile binary
- `waitForJobs` helmDefaults parameter (default `false`)
- `waitForJobs` release parameter (default `false`)

Note that `--wait-for-jobs` was introduced in Helm 3.5 (https://github.com/helm/helm/pull/8363)
2021-03-23 16:53:57 +09:00
Rein van 't Veer fcf9a7273f Improve error message on deps resolution error (#1727)
Fixes #1356
2021-03-23 16:44:59 +09:00
Quan TRAN 5ce755e4d8 Add chart & version to list cmd output (#1731) 2021-03-23 16:29:48 +09:00
Quan TRAN b7d6639b25 (fix) remove usage of charts.yaml in error message (#1732) 2021-03-23 16:27:51 +09:00
Nick Perry 95759958b5 Add William Hill to USERS.md (#1735) 2021-03-23 16:03:48 +09:00
kupnu4xandYusuke Kuoka 8ed8a9eab3 allow to access oci repos without auth (#1711)
* allow to access oci repos without auth

Co-authored-by: Yusuke Kuoka <ykuoka@gmail.com>
2021-03-09 10:23:36 +09:00
14a83e7cbe Update reference for helm-secrets to latest maintained fork (#1676)
* Update referance for helm-secrets to latest maintained fork

* Missed a referance in the docs to an older edition of helm secrets

* Removed docker2 changes back to original method

Co-authored-by: Nathan Flynn <nflynn@williamhill.co.uk>
Co-authored-by: Yusuke Kuoka <ykuoka@gmail.com>
2021-03-09 10:17:16 +09:00
Thomas Loubiou bf9f36b2bb refactor: simplify pull channel from #1706 (#1707) 2021-03-09 10:16:04 +09:00
Quan TRAN 3215eaf710 panic with clear message (#1709) 2021-03-09 10:13:24 +09:00
Justin Chizer 927bd30fa0 Update README.md to show current CLI Output (#1712) 2021-03-09 10:12:41 +09:00
Yusuke Kuoka 247ae9108c Disable darwin/386 build to avoid release failure 2021-03-04 10:27:39 +09:00
Nathan Flynn 97f0e59cfd Update README.md (#1677)
Added docs to Readme for disableValidationOnInstall
2021-03-04 09:33:22 +09:00
Quan TRAN 56405e7445 panic with a clear message (#1693) 2021-03-04 09:32:50 +09:00
Mark Ayers 42c97cd919 Update README.md (#1696)
- replace deprecated helm-secrets with the currently active helm-secrets
```plain
Deprecation Info
  Please note, this project is no longer being maintained.
  Link to active helm-secret plugin could be found in helm documentation: https://helm.sh/docs/community/related/#helm-plugins
```
2021-03-04 09:31:40 +09:00
Thomas Loubiou 65317e96f6 Fix incorrect chart bug in multi-cluster setup (#1698)
When the same release name is used accross namespaces/kubecontexts
a bad chart name could be used

Fixes #1694
2021-03-04 09:29:44 +09:00
Thomas Loubiou 453b498ccb fix: pull OCI charts one by one (#1706)
* fix: pull OCI charts one by one

* fix: gofmt
2021-03-04 09:24:22 +09:00
RaymondKYLiu 19cbea49f7 Update USERS.md (#1702)
Add Trend Micro to user list
2021-03-02 14:28:54 +09:00
Jason Witkowski bd3838c059 Add ability to specify and override helmDefaults.wait via cli (#1678) 2021-02-24 12:47:27 +09:00
Yusuke Kuoka f24b61f100 Fix error on concurrent go-getter on same URL (#1669)
Fixes #1660
2021-02-05 09:02:21 +09:00
Yusuke Kuoka 257c1f62d2 Fix OCI support (#1667)
* Fix OCI support

I have seen various issues related to the OCI repository support recently added to Helmfile.
This is the patch that should fix all the issues Im aware of until now.
2021-02-04 09:33:35 +09:00
Yusuke Kuoka 4e1ecb5890 Bump variantdev/vals to 0.13.0 (#1666)
Ref https://github.com/variantdev/vals/pull/42
2021-02-03 09:00:46 +09:00
Wi1dcard ad5fba56e0 Bump Helm to v3.5.0. (#1656) 2021-01-29 09:06:29 +09:00
Caleb Collins-Parks 0ad62b504a docs: update helm command name (#1635)
helm delete is now helm uninstall. See https://helm.sh/docs/helm/helm_delete/
2021-01-29 09:05:55 +09:00
Cameron Braid 8487970a2e fix: dont sent RegistryLogin password via args (#1662) 2021-01-29 09:05:03 +09:00
Chris Mellard 8212b630ff fix: fixed up OCI repos so they support paths and URLs that differ from chart names contained there (#1661) 2021-01-28 19:11:12 +09:00
Chris Mellard 2a71640095 feat: added in oci repository flag and added helm methods to pull and export charts (#1629) 2021-01-28 09:02:00 +09:00
Shunsuke Suzuki 33880dab77 docs: fix typo (#1645)
* docs: fix typo

* docs: syntax highlight
2021-01-12 10:23:21 +09:00
Sven Schliesing 87fcd83275 Fix typo "verion" (#1641) 2021-01-12 10:22:35 +09:00
jjangga0214 600721ca09 docs(readme): fix typo (#1633) 2021-01-12 10:22:04 +09:00
Yusuke Kuoka f7a1d09be7 Bump variantdev/vals to 0.12.0 (#1644)
For Azure KeyVault support for the fix for AWS SSM provider.
2021-01-12 10:20:57 +09:00
Yusuke Kuoka e5d982ed33 Fix "No valid platforms to build for" build error on release
Follow-up for #1612
May be related to https://github.com/mitchellh/gox/issues/92
2021-01-01 21:23:04 +09:00
Yusuke Kuoka c815d0192a Bump helm versions in Dockerfile to deal with stable and incubator repositories change 2021-01-01 12:16:41 +09:00
Yusuke Kuoka 9569d73e2f Bump helm versions in CI to deal with stable and incubator repositories change 2021-01-01 10:44:46 +09:00
Damiano AlbaniandDamiano Albani dd5bc46ead Include kustomize binary in helm3-based Docker image (#1626)
Co-authored-by: Damiano Albani <damiano.albani@geodan.nl>
2020-12-28 09:28:59 +09:00
Gastón Acosta Ramas fc3567ddda Adding documentation for PR #6819 (#1623)
Adding documentation for this feature: https://github.com/helm/helm/pull/6819
not being aware of this, make turn helm2 to 3 migrations very complicated.
2020-12-28 09:24:38 +09:00
Yusuke Kuoka 9b64d65d94 feat: Stabilize helmfile-diff output (#1622)
This finishes the work started at #1619 by introducing the following changes:

- You can now set `HELMFILE_TEMPDIR` and `CHARTIFY_TEMPDIR` as the directory to persist temporary values files rendered by helmfile and temporary charts generated by chartify. Note that chartify is used internally when you use kustomize integration or raw K8s manifests as a chart.
- Helmfile uses the hash sum of the release config and the values file content in the file name of the temporary values file.

  In combination with `HELMFILE_TEMPDIR` and `CHARTIFY_TEMPDIR`, this enables helmfile to use the stable file names for temporary values files, which contribute to stabilize log messages like `Comparing release=appset, chart=PATH/TO/TEMPORARY/CHART`, where the `PATH/TO/TEMPORARY/CHART` had been randomized due to formerly random helmfile tempdir and temporary values file names, and random chartify tempdir.

You can try this feature with a script like:

```
tempdir=$(mktemp -d -t helmfile)

HELMFILE_TEMPDIR=${tempdir} CHARTIFY_TEMPDIR=${tempdir} helmfile diff

rm -rf ${tempdir}
```
2020-12-15 10:24:27 +09:00
54eb73b423 Use ghodss/yaml for yaml marshaling & unmarshaling in template (#1556)
Fixes #1555

Co-authored-by: Yoann Ciabaud <yoann@linxo.com>
Co-authored-by: Yusuke Kuoka <ykuoka@gmail.com>
2020-12-13 11:03:20 +09:00
jayme-github 4c0987a618 Don't append --tiller-namespace flag when running helm3 (#1594) 2020-12-13 10:21:12 +09:00
Chris Mellard 0637973e40 fix: allow helmfiles to be corrected marshalled to YAML so they can be manipulated by other programs (#1604) 2020-12-13 10:08:03 +09:00
Felipe Santos 3690bde94c Add documentation on how to use the docker image (#1607)
* Add documentation on how to use the docker image

* Remove default workdir and add it to snippet
2020-12-13 10:00:36 +09:00
Life ChristianandLife Lofranco b92ff2baff Fix selector syntax example (#1614)
Co-authored-by: Life Lofranco <life@dealgrocer.com>
2020-12-13 09:58:24 +09:00
Chris Mellard afe82f6453 chore: suppress empty values on serializing several helmfile values (#1616) 2020-12-13 09:57:35 +09:00
Yusuke Kuoka 1c7b872476 Stabilize helmfile-diff output (#1619)
`helmfile-diff` sorts multiple and concurrent helm-diff outputs and stabilizes writes to stdout.

It's required to use the stdout from helmfile-diff to detect if there was another change(s) between 2 points in time.

For example, terraform-provider-helmfile runs a helmfile-diff on `terraform plan` and another on `terraform apply`. `terraform`, by design, fails when helmfile-diff outputs were not equivalent. Stabilized helmfile-diff output rescues that.
2020-12-11 09:51:26 +09:00
James Strachan 28e7ebb4a4 fix: support arm64 images (#1612) 2020-12-11 09:44:07 +09:00
Yusuke Kuoka 1ef9b29f6d Improve handling of releases being newly installed by helmfile-apply (#1618)
This improves helmfile-apply with two things:

- Some users had timing-out issues or annoyed by huge output from helm-diff run as part of helmfile-apply on first install. `--skip-diff-on-install` skips running helm-diff for releases being newly installed, so that you can avoid those issues.
- Some users had difficultly or found it not straight-forward to install CRDs and custom resources from separate charts in one helmfile-apply (#1353). The new helmfile.yaml release field `disableValidationOnInstall: true` adds `--disable-validation` to helm-diff only for releases being newly released, which should mostly resolve the issue.

Resolves #1353
2020-12-11 09:09:35 +09:00
Yusuke Kuoka b910591e1d Add support for Kustomize Transformers (#1592)
Please see the updated `advanced-features.md` for more details.

This is often used for adding common labels and annotations to any resources rendered from a Helm chart.
2020-11-19 09:33:05 +09:00
Krzysztof Pawlik d807510dd7 Enable shell completion (#1559)
* feat: enable EnableBashCompletion to get --generate-bash-completion command line flag

* feat: add scripts for bash and zsh

* feat: document shell completion
2020-11-19 09:32:33 +09:00
Wi1dcard 4e485219d7 Fix the logic of helmfile deps and add tests. (#1588) 2020-11-19 09:29:59 +09:00
Wi1dcard b0e042988c Fix incorrect help text of some CLI options. (#1587) 2020-11-19 07:20:08 +09:00
manfred-h f877de7778 Fix openSUSE repo URL. (#1584) 2020-11-19 07:19:00 +09:00
Baryczka 655d5934a2 Mention about helmBinary in readme (#1590) 2020-11-19 07:17:55 +09:00
Yusuke Kuoka f6bf885fb7 Fix helmfile template --include-crds not to break with chartify (#1583)
* Fix `helmfile template --include-crds` not to break with `chartify`

This bumps variantdev/chartify to 0.4.9 so that we can incorporate https://github.com/variantdev/chartify/commit/fed8bb29533904b1af3044a266220652231afe87

* Fix integration test
2020-11-12 09:26:43 +09:00
James Strachan 2857382006 fix: support arm binaries (#1580)
fixes #1433
2020-11-12 08:58:54 +09:00
manfred-h 972667f115 Mention openSUSE packages for helmfile (#1581)
* Mention openSUSE.

* Add example how to add a repo.
2020-11-12 08:57:26 +09:00
Brian Choy 3c88145329 Update readme stable/incubator URLs (#1582)
Small update to the readme repository examples since Google is no longer officially supporting hosting the stable/incubator helm charts. The official helm docs are using these repos now, eg. https://helm.sh/docs/intro/quickstart/.
2020-11-12 08:56:34 +09:00
Yusuke Kuoka 709913b5ea Bump dependencies (#1573)
Bumps dependencies mainly to fix possible go mod error that is fixed via https://github.com/variantdev/vals/commit/0edd5343228b96e1ca5f3599f923fee0ff1fd0cb, but also bumps all the other dependencies because they looked very outdated :)
2020-11-09 08:50:00 +09:00
Yusuke Kuoka 88884b68dc feat: helmfile template --skip-cleanup (#1570)
* feat: `helmfile template --skip-cleanup`

Resolves #1517
2020-11-06 09:23:49 +09:00
Yusuke Kuoka 5a15b65b94 feat: Add helmfile apply --skip-cleanup (#1571)
This deprecates the old `--retain-values` which was not working as intended.

Also see #1570 - there's now `--skip-cleanup` for `helmfile-template`, too.
2020-11-05 10:18:30 +09:00
Yusuke Kuoka 3899680672 feat: Add helmfile test --logs (#1569)
When `--logs` is provided, Helmfile runs `helm test --logs` so that it can stream test logs

Ref #1541
2020-11-05 10:17:18 +09:00
Yusuke Kuoka 6b86408500 feat: Add helmfile template --include-crds (#1568)
This allows you to use helmfile-template output as a GitOps source, when the template output contains CRDs and you use Helm 3.

Helm 3 by default removes CRDs from the template output. If you want to git-commit helmfile-template containing CRDs for GitOps and you use Helm 3 for templating, the only way is provide this newly added `--include-crds` flag.
2020-11-05 10:13:25 +09:00
Yusuke Kuoka bdbaa00628 Fix redundant helm-dep-build and prepare hook regression (#1566)
Fixes #1511
Fixes #1497
2020-11-05 09:43:42 +09:00
Yusuke Kuoka 87e86dcfd2 Fix helmfile --selector x=y template with needs (#1564)
I had mistakenly inverted the necessary flag to turn off the legacy processing on selectors that should be used only for helmfile commands that does not support DAGs/needs.

Fixes #1552
2020-11-04 09:29:23 +09:00
Christian Rebischke 995b201e47 mention Arch Linux support (#1549) 2020-10-26 08:59:05 +09:00
Yusuke Kuoka 0663831dd5 Disable dependency update while running helm-x/chartify in more cases (#1548)
`helm dep up` is now skipped while running helm-x/chartify when the chart/directory is obtained by running go-getter, or `skipDeps` is configured using a command-line flag, helmDefaults, or release configuration.

Resolves #1547
2020-10-25 11:25:59 +09:00
isindir afb2653452 fix: printing error to stdout together with templated resources (#1550)
Fixes #1551
2020-10-25 09:14:07 +09:00
isindir 9ec4a85821 Minor README improvement (#1543)
Fixes #1544
2020-10-19 08:48:39 +09:00
Wi1dcard 4d868794fd Improve capitalization and formats of README.md. (#1545) 2020-10-19 08:47:19 +09:00
Jake Hill 1aca099457 feat: Upgrade to kubectl version supported by latest stable release and upgrade helm3 to 3.3.4 (#1498)
Changes:

* Bump helm to v3.3.4.
* Bump kubectl to v1.18.9
* Update Dockerfile to be a little more verbose to help understand which command in a chain failed.

Signed-off-by: Jake Hill <jake@naphta.uk>
2020-10-19 08:45:44 +09:00
Wi1dcard f5d098876a Retain the option helm repo add --force-update when Helm is v3.3.4 or above. (#1546)
See: https://github.com/roboll/helmfile/pull/1542#issuecomment-710082201
2020-10-19 08:44:09 +09:00
Yusuke Kuoka 080080f57f Bump chartify to 0.4.4 (#1540)
To incorporate the fix for unconventional chart templates like seen in the Datadog chart https://github.com/variantdev/chartify/commit/5443ca1a1db36d2626522f39130ac1157a69c3f7
2020-10-15 10:17:37 +09:00
Wi1dcard cd4afc3269 Remove duplicated logs from helm.exec. (#1538)
While using the `--debug` option, the output of helm seems to be printed triple times:

```
Templating release=test, chart=charts/test
exec: helm template test charts/test --namespace staging --values /tmp/values266548685 --output-dir outputs//helmfile-test-staging-ed5e3c94-test --debug
helm:MRAjW> install.go:172: [debug] Original chart version: ""
helm:MRAjW> install.go:189: [debug] CHART PATH: /app/charts/test
helm:MRAjW> wrote outputs//helmfile-test-staging-ed5e3c94-test/test/templates/secret.yaml
helm:MRAjW> wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/configmap.yaml
helm:MRAjW> wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/health-configmap.yaml
helm:MRAjW> wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/headless-svc.yaml
helm:MRAjW> wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/metrics-svc.yaml
helm:MRAjW> wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/redis-master-svc.yaml
helm:MRAjW> wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/redis-master-statefulset.yaml
helm:MRAjW> wrote outputs//helmfile-test-staging-ed5e3c94-test/test/templates/ingress.yaml
helm:MRAjW> wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/metrics-prometheus.yaml
helm:MRAjW>
exec: helm template test charts/test --namespace staging --values /tmp/values266548685 --output-dir outputs//helmfile-test-staging-ed5e3c94-test --debug: wrote outputs//helmfile-test-staging-ed5e3c94-test/test/templates/secret.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/configmap.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/health-configmap.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/headless-svc.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/metrics-svc.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/redis-master-svc.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/redis-master-statefulset.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/templates/ingress.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/metrics-prometheus.yaml


wrote outputs//helmfile-test-staging-ed5e3c94-test/test/templates/secret.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/configmap.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/health-configmap.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/headless-svc.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/metrics-svc.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/redis-master-svc.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/redis-master-statefulset.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/templates/ingress.yaml
wrote outputs//helmfile-test-staging-ed5e3c94-test/test/charts/redis/templates/metrics-prometheus.yaml
```

- The first with the prefixes `helm:MRAjW>` comes from <https://github.com/roboll/helmfile/blob/563fce4adf7abf759ec31b3971d1195704621052/pkg/helmexec/runner.go#L59-L65>

- The second one with the format `exec: helm ... --debug: ...` comes from <https://github.com/roboll/helmfile/blob/563fce4adf7abf759ec31b3971d1195704621052/pkg/helmexec/exec.go#L369>

- And the last are the logs of non-debug mode.

It sometimes makes the log a bit confusing, while I first time read the log, I thought helmfile executed helm command multiple times 😅.

Since we already piped helm outputs to stdout (the first), we remove the second logs.
2020-10-15 08:50:56 +09:00
Javier Palacios 8f8669778c Support for azure acr helm repositories (#1526)
Adds a basic support for Helm repositories hosted on Azure Container Registry (not OCI but classic ones). Add a new field to RepositorySpec to state that is externally managed and runs the `az-cli` command instead of the helm one to manage the repository.
2020-10-15 08:45:45 +09:00
Yusuke Kuoka 563fce4adf Bump vals (#1537)
To incorporate unexpected SSM secret exposure issue when the parameter version is specified.
See https://github.com/variantdev/vals/pull/37
2020-10-14 09:56:17 +09:00
Karl Stoney b287101625 Update USERS.md (#1535)
Adding AT as a User :-) 
Thanks for your OSS efforts!
2020-10-14 08:38:30 +09:00
Yusuke Kuoka ab9fb2c9dc Fix concurrent-map-iteration-and-write errors while running release hooks (#1534)
Fixes #1495
2020-10-13 14:49:01 +09:00
Yusuke Kuoka c170b5a621 Minor tweaks for helmfile write-values (#1533)
See 1 and 3 in https://github.com/roboll/helmfile/issues/1460#issuecomment-691863465
2020-10-13 09:33:49 +09:00
Yusuke Kuoka d9286ed8f6 Fix intermittent failures while building deps on local chart (#1532)
Fixes #1521
2020-10-13 09:14:45 +09:00
Yusuke Kuoka 3018e82902 Fix multiple --set in --args messed up in helmfile template (#1529)
Fixes #1520
2020-10-13 08:38:06 +09:00
Yusuke Kuoka 34acf14d7c Bump chartify to v0.4.3 (#1528)
This fixes the bug that generated Chart.yaml misses the `apiVersion` field which resulted in `helm lint` always failing on the generated chart.

Fixes #1527
2020-10-12 09:23:58 +09:00
Wi1dcard 5d8eba9b29 Append --force-update for specific helm versions. (#1494)
* Parse and process helm version using github.com/Masterminds/semver/v3.

* Add --force-update only when Helm version >= 3.3.2, < 3.3.4.

See: https://github.com/helm/helm/pull/8777.

* Add test cases.
2020-10-12 09:20:55 +09:00
Roderik van der Veer b284b7be83 chore: add SettleMint to the USERS list (#1516) 2020-10-12 09:11:14 +09:00
Allie b293ac1f83 Fix typo (#1523) 2020-10-12 08:58:05 +09:00
Matthias Baur 8d9d635b8b Clarify namespace prefix in needs (#1500)
Before this, the documentation didn't mention that the release needs to
be prefixed with the namespace if the release had a namespace configured.
2020-10-05 08:51:39 +09:00
mdaisuke 954673d742 Fix: forceNamespace needs shouldRun is true (#1510) 2020-10-02 08:27:39 +09:00
Yusuke Kuoka 942b9a64c0 Add support for ChartCenter (#1492)
Resolves #1326
2020-09-21 15:31:24 +09:00
Yusuke Kuoka b176408eb2 Enable helmfile test testing only enabled and selected releases (#1486)
Resolves #1483
2020-09-21 09:44:05 +09:00
Yusuke Kuoka ff81b2f279 Enable release hook template to access .Values (#1485)
Fixes #1484
2020-09-21 09:43:30 +09:00
Andrii Dembitskyi 8db03f979c Drop duplicated entry in extra functions list from readme (#1490) 2020-09-21 09:42:47 +09:00
Wi1dcard 988c218096 Support the latest Helm (>=v3.3.2) and bump the Helm version in Docker image. (#1488)
Changes:

* Bump Helm to v2.16.12 and v3.3.3.
* Add --force-update only when using Helm 3.
2020-09-21 09:41:49 +09:00
Yusuke Kuoka 028bcc51dc Treat selector with multiple conditions an AND (#1478)
Fixes #1477
2020-09-15 09:56:05 +09:00
Yusuke Kuoka 5dd65e8d9a Bump vals (#1475)
Ref https://github.com/variantdev/vals/issues/34
2020-09-14 20:37:51 +09:00
Or Biran 134d5beba2 Add tailormed to users (#1467)
* Update USERS.md
2020-09-13 09:47:18 +09:00
Jason Witkowski 87d04f0202 Append KubeContext to temporary directory path (#1471)
Fix bug where KubeContext is not taken into account in temporary directories
2020-09-13 09:46:08 +09:00
vugardzhamalov 2d7c8e7525 Update README.md (#1472)
Made a small change to Global Hooks example in order to remove release name reference - this was probably copy/pasted from Hooks section
2020-09-13 09:41:21 +09:00
Yusuke Kuoka 0fad9f0544 Add experimental write-values command for writing values files only (#1469)
Ref #1460
2020-09-11 22:19:36 +09:00
Yusuke Kuoka 832dcf47a5 Re-add Release.Namespace in release values.yaml templates (#1466)
It was accidentally removed in #1424, and had been unexpectedly unavailable between v0.126.0 and v0.128.1.

Fixes #1464
2020-09-10 21:08:20 +09:00
Harshal Shah 1d2c8b8a3a Adding hellofresh as a helmfile user (#1465) 2020-09-10 09:41:53 +09:00
Yusuke Kuoka 0482ba3b6f Fix index out of range [1] with length 1 error on env2map (#1463)
This happened only when helmfile is run in an environment that any environment variable definition does not include `=` and the right side.
2020-09-08 11:23:36 +09:00
9d2c0d4285 Bump sprig to v3.1.0 and mergo 3.11 (#1456)
* Bump sprig to v3.1.0
test for mergeOverwrite

* Let mergo not (accidentally) try to merge unexported fields

This is also a good chance separate `HelmState` with the config loaded from YAML, which I had been wanting to do for a long time.

Co-authored-by: Johannes Alkjær <johannes.alkjaer@wunderman.com>
Co-authored-by: Yusuke Kuoka <ykuoka@gmail.com>
2020-09-04 09:58:54 +09:00
Yusuke Kuoka efd26f288a Bump sprig to v3 (#1452)
Resolves #1294
Resolves #1447
2020-09-01 19:10:29 +09:00
Yusuke Kuoka 5f1698d0ed Fix wrong deletion order (#1451)
Fixes #1450
2020-09-01 17:36:11 +09:00
Yusuke Kuoka 2ecf5780f3 Revise GitOps doc a bit 2020-09-01 12:11:26 +09:00
Yusuke Kuoka 3e6542e341 Fix test flake for commonLabels (#1449)
Ref https://github.com/roboll/helmfile/pull/1415#issuecomment-684139831
2020-09-01 10:52:08 +09:00
Yusuke Kuoka d3daea3137 Fix panic on hook since v0.126.0 (#1448)
Fixes #1445
2020-09-01 10:37:21 +09:00
Yusuke Kuoka 19d79427a1 Add experimental "forceNamespace" in helmfile.yaml (#1444)
This is an experimental feature to support a potential use-case that you need to set namespaces in manifests rendered by `helmfile template`, WHEN the chart is unconventional hence does not have `namespace: {{ .Namespace }}`.
Rather than using this, you should usually fork/maintain or update/pull-request the chart to have `namespace: {{ .Namespace }}`.
Use this only when you CAN NOT do so, but still need to use `helmfile template`.
2020-09-01 10:13:27 +09:00
Yusuke Kuoka cd0ecc5263 Fix build error after helmfile build --embed-values addition
This is a follow-up for #1436
2020-08-29 15:01:22 +09:00
AdamClarkandYusuke Kuoka dc6c59dc14 Print command output in line (#1354)
This is an attempt to log helm exec output as the helm exec command is running. It only prints if the --debug flag is set.

Co-authored-by: Yusuke Kuoka <ykuoka@gmail.com>
2020-08-29 14:47:22 +09:00
Yusuke Kuoka 0fc0869671 feat: helmfile build --embed-values to embed release values and secrets into the output (#1436) 2020-08-29 13:18:17 +09:00
Maksym Lushpenko 5ca7ce15bc 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
2020-08-29 13:14:58 +09:00
Pawel PrzeszloandPawel Przeszlo 6b4b76e2bc HELMFILE_ENVIRONMENT variable (#1425)
Co-authored-by: Pawel Przeszlo <pawel.przeszlo@comarch.pl>
2020-08-29 13:10:07 +09:00
Joshua HansenandYusuke Kuoka 7b11ce851a feat: Allow .Release.Name to be used in gotmpl values templates (#1424)
This adds the ability to utilize `.Release` inside of gotmpl files as discussed [here](https://github.com/roboll/helmfile/issues/760).

Resolves: https://github.com/roboll/helmfile/issues/760

Co-authored-by: Yusuke Kuoka <ykuoka@gmail.com>
2020-08-29 13:07:46 +09:00
Yusuke Kuoka c575587e1f Merge pull request #1442 from roboll/fix-duplicate-with-kubectx-per-release
Fix kubeContext not taken into account for release uniqueness
2020-08-29 11:45:13 +09:00
Yusuke Kuoka f2dfa7ce64 Fix kubeContext not taken into account for release uniqueness
Fixes #1440
2020-08-29 11:33:05 +09:00
Yusuke Kuoka 41cd1fe4a6 Fix race while running helm dep build on local chart (#1439)
* Fix race while running `helm dep build` on local chart

Fixes #1438
2020-08-29 10:24:48 +09:00
Yusuke Kuoka 9e54af5c2c fixup! fixup! Fix race while running helm dep build on local chart 2020-08-29 10:14:42 +09:00
Yusuke Kuoka 8a43e8f782 fixup! Fix race while running helm dep build on local chart 2020-08-29 10:04:09 +09:00
Yusuke Kuoka 94e01b79e0 Bump chartify to 0.4.2 (#1437)
To fix the issue that adhoc json patches were not working on kustomize/raw manifests.

Note that regular kustomize project was working. In other words, this only affetcts `chart: path/to/dir` combined with `jsonPatches: ...` when the `path/to/dir` points to a kustomize project or a local directory containing raw K8s manifests.

Ref https://github.com/roboll/helmfile/issues/1434#issuecomment-682247709
2020-08-28 21:13:04 +09:00
Ben Mathews 85e77982bf Update to the latest version of helm-diff plugin (#1430)
This is needed to support the --disable-validation flag added into
helmfile
2020-08-28 21:12:44 +09:00
Yusuke Kuoka b42e847154 Fix race while running helm dep build on local chart
Resolves #1438
2020-08-28 20:08:44 +09:00
Yusuke Kuoka 14e2b9ed93 Fix regression in helmfile deps (#1431)
Fixes #1421
2020-08-27 09:22:02 +09:00
Anatoly Rugalev 90a41222e6 fix: panic when using helm v3.3 (#1427) (#1428) 2020-08-24 08:57:17 +09:00
KUOKA Yusuke a9aa7af572 Fix regression installing raw K8s manifests (#1417)
Fixes #1413
2020-08-14 19:52:00 +09:00
KUOKA Yusuke 738409ee0e Fix regression for helmfile template with helm v2 since v0.125.4 (#1416)
This fixes the regression introduced by #1406 that resulted in `helmfile template` with helm v2 failing when there is any release with `version`.

Fixes #1414
2020-08-14 09:57:10 +09:00
KUOKA Yusuke ec87a39015 Revert filtering on repositories to be updated (#1412)
This reverts a part of #1383 so that repository updates are done in the pre-0.125.0 way, which tries to update any repositories only once regardless of they are referenced by selected releases or not.

Ref #1404
2020-08-13 10:10:30 +09:00
KUOKA Yusuke ab1c118022 Bump chartify to v0.4.0 (#1409)
* Bump chartify to v0.4.0

Should fix #1338

* Bump minimum Helm 3 version to v3.1.0 for `helm template --include-crds`
2020-08-12 09:52:51 +09:00
KUOKA Yusuke c8c444debc Add test case (#1410)
To ensure that `helmfile -l foo!=bar` does match against release without the label at all.
2020-08-12 09:07:22 +09:00
KUOKA Yusuke 9a03d79a84 Fix chart fetched by go-getter not to fail due to missing dependencies (#1408)
Ref https://github.com/roboll/helmfile/issues/1401#issuecomment-670915272
2020-08-08 21:22:57 +09:00
KUOKA Yusuke 0ef7e65f02 Fix race on/sometimes missing postsync and cleanup hooks (#1407)
So that those hooks are fully executed as intended. The documentation about hooks are updated as well to clarify the intended behaviour.

Fixes #1398
2020-08-08 16:45:21 +09:00
KUOKA Yusuke 61b61d3009 Fix helmfile template with Helm 3 ignoring chart version (#1406)
This fixes a regression introduced in #1399
Fixes #1377
2020-08-08 15:26:40 +09:00
KUOKA Yusuke 2710cb382f Fix go-getter URL in chart to actually work (#1405)
Fixes #1401
2020-08-08 11:01:47 +09:00
KUOKA Yusuke 4d7fcd846e doc: ArgoCD integration (#1402)
Relates to #1110
Relates to #1060
Relates to #780
2020-08-06 09:51:11 +09:00
KUOKA Yusuke b85243a6b4 Fix various issues in chart preparation (#1400)
In #1172, we accidentally changed the meaning of prepare hook that is intended to be called BEFORE the pathExists check. It broke the scenario where one used a prepare hook for generating the local chart dynamically. This fixes Helmfile not to fetch local chart generated by prepare hook.

In addition to that, this patch results in the following fixes:

- Fix an issue that `helmfile template` without `--skip-deps` fails while trying to run `helm dep build` on `helm fetch`ed chart, when the remote chart has outdated dependencies in the Chart.lock file. It should be up to the chart maintainer to update Chart.lock and the user should not be blocked due to that. So, after this patch `helm dep build` is run only on the local chart, not on fetched remote chart.
- Skip fetching chart on `helmfile template` when using Helm v3. `helm template` in helm v3 does support rendering remote charts so we do not need to fetch beforehand.

Fixes #1328
May relate to #1341
2020-08-06 09:06:25 +09:00
KUOKA Yusuke 53c3fe9f37 Add support for selector on helmfile template (#1399)
Fixes #1377
2020-08-05 09:31:58 +09:00
Kyrylo Lebediev 2ce57ba2ee Handle --disable-openapi-validation parameter in helm upgrade (#1396) 2020-08-04 08:36:18 +09:00
KUOKA Yusuke cc9b7f3942 Fix regression on template failing due to duplicate fetch target (#1395)
This fixes a regression introduced in #1388
Fixes #1394
2020-08-04 02:28:58 +09:00
KUOKA Yusuke 85a2024669 Fix helmfile lint failure when installed: false (#1391)
Fixes #1344
2020-08-01 13:47:57 +09:00
KUOKA Yusuke 87573089e8 Fix false-positive duplicate release with kubeContext (#1390)
Helmfile has been incorrectly showing releases with the same name but in different kubeContexts as duplicates. This fixes that.
2020-08-01 12:12:54 +09:00
KUOKA Yusuke 4fb47a833a Add version sub-command (#1389)
Resolves #1369
2020-08-01 10:37:34 +09:00
KUOKA Yusuke 9cffef50b0 Update documentation to mention Renovate integration (#1387) 2020-08-01 10:36:15 +09:00
KUOKA Yusuke b4857937fb Fix lint to not fail for duplicate release names across ns (#1388)
Fixes #1384
2020-08-01 10:36:01 +09:00
KUOKA Yusuke 1e260e4a5e Fix and enhancement to repository update (#1383)
Changes:

- Prevent Helmfile from unnecessarily running `helm repo add` and `helm repo up` against repositories for unused repositories(repositories of releases filtered out by selector)
- Fixes #1330
2020-07-28 10:17:43 +09:00
KUOKA Yusuke ee8ea507a6 Add pre/postuninstall hooks (#1375)
Exmple:

```
releases:
- name: test2
  chart: stable/mysql
  installed: false
  hooks:
  - events:
    - prepare
    - preuninstall
    - postuninstall
    - cleanup
    showlogs: true
    command: "echo"
    args:
    - "event name = {{` {{ .Event.Name `}} }}"
```

Output:

```
helmfile apply
Listing releases matching ^test2$
test2   default         1               2020-07-23 00:51:44.975478 +0900 JST    deployed        mysql-1.6.6     5.7.30

helmfile.yaml: basePath=.

hook[preuninstall] logs | event name =  preuninstall
hook[preuninstall] logs |
Deleting test2
release "test2" uninstalled

helmfile.yaml: basePath=.

hook[postuninstall] logs | event name =  postuninstall
hook[postuninstall] logs |
helmfile.yaml: basePath=.

hook[cleanup] logs | event name =  cleanup
hook[cleanup] logs |

DELETED RELEASES:
NAME
test2
```

Resolves #802
2020-07-23 01:09:36 +09:00
KUOKA Yusuke b5830a3011 Add ability to use go-getter for fetching remote manifests directory as chart (#1374)
This, in combination with #1172, allows you to use `go-getter`-supported URL for K8s manifests on `chart`, so that Helmfile automatically fetches it and then turning it into a temporary local chart, which is then installed by Helmfile as similar as standard Helm charts.

An example usecase of this is to install cert-manager CRDs which is distributed separately from the chart:

```
releases:
- name: cert-manager-crds
  chart: git::http://github.com/jetstack/cert-manager.git@deploy/crds?ref=v0.15.2
```

I'm adding this based on discussion with @lukasmrtvy. He was trying to install cert-manager and prometheus-opreator with Helmfile, and this combined with #1373 should do the job. Thanks for the input!
2020-07-23 00:30:16 +09:00
KUOKA Yusuke 4fde6e13db Add disableValidation and disableOpenAPIValidation per release (#1373)
`disableOpenAPIValidation: true` might be useful for workaround for broken CRDs that is known to be exist in older OpenShift versions, and `disableValidation: true` is confirmed to allow installing charts like prometheus-operator that tries to install CRDs and CRs in the same chart.

Strictly speaking, for the latter case I believe you only need `disableValidation: true` set during the first installation, but for the ease of operation I shall suggest you to always set it.

Obviously turning validation mostly(disableOpenAPIValidation) or entirely(disableValidation) result in deferring any real error until sync time. We need completely client-side validation that is able to read CRDs and use it for validating any CRs to catch any error before sync. But it worth an another (big) issue.

Fixes #1124
2020-07-22 23:10:22 +09:00
Olblak a5e790caf1 Add Jenkins OSS to the list of users (#1372) 2020-07-22 22:34:44 +09:00
KUOKA Yusuke 1e956ae8a5 Fix list failure when patches are used (#1371)
Fixes #1368
2020-07-22 22:33:45 +09:00
KUOKA Yusuke 1671b8ba7d Bump variantdev/vals for better Variant KV v2 support (#1367)
Ref https://github.com/variantdev/vals/pull/33
2020-07-21 07:17:40 +09:00
KUOKA Yusuke 608ba19c3c Bump chartify to 0.3.11 (#1366)
Fixes #1365
2020-07-21 04:56:27 +09:00
Prasanna daf7d3b876 fix the issue in the documentation (#1364) 2020-07-20 10:09:54 +09:00
KUOKA Yusuke 34f6d4d568 Bump chartify to 0.3.10 (#1363)
Fixes #1360
2020-07-19 20:08:18 +09:00
Yusuke Kuoka 1784b02d03 Add --retry-connrefused for reliable image builds 2020-07-18 22:29:58 +09:00
KUOKA Yusuke 68c51d8c16 Bump variantdev/vals to 0.9.3 (#1361) 2020-07-18 22:28:37 +09:00
KUOKA Yusuke 4bbb1699a3 Bump variantdev/chartify to 0.3.8 (#1359)
Fixes #1335
2020-07-17 09:41:05 +09:00
Sergei Ryabkov 753f65a56d Add Cherre to USERS.md (#1358)
We have been meaning to create this PR for a while now...
2020-07-17 09:28:07 +09:00
Yusuke Kuoka 43adbeaaca Update README about loading remote environment values 2020-07-16 09:33:09 +09:00
KUOKA Yusuke df6489ab43 feat: helmfile template --output-dir-template for customizing output dirs (#1357)
This is useful for e.g. removing state file names and their hash values out of output dirs so that it can be used easily in a gitops setup. For example, `--output-dir-template mybasedir/{{.Release.Name}}` produces `mybasedir/RELEASE/CHART/templates/*.yaml` for each release in your helmfile.yaml.
2020-07-16 09:02:26 +09:00
Santiago Hernandez 31d0fce6fd Update USERS.md (#1352) 2020-07-14 08:44:24 +09:00
KUOKA Yusuke cd427d9714 Bump variantdev/vals to 0.7.1 (#1332)
To incorporate the tfstate provider fix

Ref https://github.com/roboll/helmfile/issues/505#issuecomment-653848769
2020-07-06 08:30:19 +09:00
Alex VoronaandAlex Vorona 6d4f0984b6 Fix kubeContext per release override (#1312)
* Move kube-context from helmDefaults to 1'st argument to let kube-context per release override it

* fix tests

Co-authored-by: Alex Vorona <av@dysnix.com>
2020-06-27 17:19:05 +09:00
Santiago Hernandez ac40eec423 Add PedidosYa :) (#1322) 2020-06-27 17:17:30 +09:00
KUOKA Yusuke 8de9b70b33 Bump variantdev/vals to 0.6.0 (#1311)
- Add `version` parameter for Vault provider
- Add `profile` parameter for AWS SSM/SecretsManager provider
= Add `version` parameter for AWS SSM Parameter Store provider
- Add support for app-role authentication when using Vault provider
2020-06-16 09:07:04 +09:00
ento face92536c Add integration test for Kustomize inetgration (#1288)
Summary of changes:

* Output any error from Mkdir in `helmfile template`

* Add failing test for .Release.Name interpolation

* Add golden files for testing

* Parse resources with kustomize to compare them structure by structure

* Decode resources into plain maps

The RNode type from kustomize uses yaml.Node under the hood,
which carries extra information like line numbers, which
become noisy when comparing with deep.Equal.
2020-06-16 09:06:52 +09:00
KUOKA Yusuke 3a2a460fe7 Do cleanup decrypted env secrets files (#1304)
* Do cleanup decrypted env secrets files

Resolves #503
2020-06-16 08:59:48 +09:00
Max Audron f16d96bc8f Add global hooks (#1301)
Changes:

* Add global hooks
* Add top level hooks field to yaml spec
* Add functions for global prepare and cleanup events
* Call global prepare and cleanup events in withPreparedCharts function
* Update README
* Add helmfileCommand variable to withPreparedCharts
  Pass the information on what helmfileCommand has been run down from the
  top level functions through withReposAndPreparedCharts and withPreparedCharts.
2020-06-11 10:05:38 +09:00
Kevin J. Qiu 0f86cc9b87 Add the ability to load a remote environment values file (#1296)
Enables the user to specify a remote path for an environment values file, e.g.,

```yaml
environments:
  cluster-azure-us-west:
    values:
      - git::https://git.company.org/helmfiles/global/azure.yaml?ref=master
      - git::https://git.company.org/helmfiles/global/us-west.yaml?ref=master
  cluster-gcp-europe-west:
    values:
      - git::https://git.company.org/helmfiles/global/gcp.yaml?ref=master
      - git::https://git.company.org/helmfiles/global/europe-west.yaml?ref=master

releases:
  - ...
```

This is particularly useful when you co-locate helmfiles within your project repo but want to reuse the definitions in a global repo.
2020-06-11 10:04:01 +09:00
KUOKA Yusuke ca844aa044 Fix patching not using release namespace (#1300)
Ref #1299
2020-06-09 16:40:13 +09:00
Rene HernandezandRene Hernandez Remedios 604af3f1d2 Replace getOrNil with get function in documentation (#1268)
* New output flag for list command

Support output as json
Add new formatters file to handle extrac formatting to its own concern
New config interface to support list command specification

* Fix usage message

* Add error handling for formatters

* Replace usage of getOrNil with get function

* Add nil in get function

Co-authored-by: Rene Hernandez Remedios <rene.hernandez@fullscript.com>
2020-06-08 08:10:27 +09:00
Ilya Vassyutovich 28e13c3a18 typo: Fix message for detected diff (#1284) 2020-06-06 09:31:25 +09:00
KUOKA Yusuke 7db31846cf fix: Do not include stderr content in result of {{ exec }} (#1298)
Seems like this was a regression since https://github.com/roboll/helmfile/commit/f676c6142520364553c6a574fb976eeb0c31a456 and #1159

Fixes #1258
2020-06-06 09:29:12 +09:00
117 changed files with 14078 additions and 2690 deletions
+77
View File
@@ -0,0 +1,77 @@
HELM_VERSION ?= v3.5.3
HELM2_VERSION ?= v2.17.0
KUSTOMIZE_VERSION ?= v3.8.8
K8S_VERSION ?= v1.13.12
MINIKUBE_VERSION ?= v0.30.0
SOPS_VERSION ?= v3.6.1
# ---
CHANGE_MINIKUBE_NONE_USER ?= true
MINIKUBE_WANTUPDATENOTIFICATION ?= false
MINIKUBE_WANTREPORTERRORPROMPT ?= false
VAULT_ADDR := http://127.0.0.1:8200
VAULT_TOKEN := toor
tmp := $(shell mktemp -d)
HELM_FILENAME := helm-${HELM_VERSION}-linux-amd64.tar.gz
HELM2_FILENAME := helm-${HELM2_VERSION}-linux-amd64.tar.gz
KUSTOMIZE_FILENAME := kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz
all: vault sops helm kustomize minikube/destroy minikube
helm:
curl -sSLo $(tmp)/${HELM_FILENAME} "https://get.helm.sh/${HELM_FILENAME}"
tar zxf $(tmp)/${HELM_FILENAME} --directory ${tmp} linux-amd64/helm
chmod +x ${tmp}/linux-amd64/helm
sudo mv ${tmp}/linux-amd64/helm /usr/local/bin/
.PHONY: helm
helm2:
curl -sSLo $(tmp)/${HELM2_FILENAME} "https://kubernetes-helm.storage.googleapis.com/${HELM2_FILENAME}"
tar zxf $(tmp)/${HELM2_FILENAME} --directory ${tmp} linux-amd64/helm
chmod +x ${tmp}/linux-amd64/helm
sudo mv ${tmp}/linux-amd64/helm /usr/local/bin/
.PHONY: helm2
kustomize:
curl -sSLo $(tmp)/${KUSTOMIZE_FILENAME} "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/${KUSTOMIZE_FILENAME}"
tar zxf $(tmp)/${KUSTOMIZE_FILENAME} --directory ${tmp} kustomize
chmod +x ${tmp}/kustomize
sudo mv ${tmp}/kustomize /usr/local/bin/
.PHONY: kustomize
minikube/destroy:
sudo -E minikube delete || true
sudo -E rm -rf /etc/kubernetes || true
sudo -E rm -rf $$HOME/.minikube/* || true
.PHONY: minikube/destroy
.EXPORT_ALL_VARIABLES: minikube/destroy
minikube:
curl -sSLo ${tmp}/kubectl https://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl
chmod +x ${tmp}/kubectl && sudo mv ${tmp}/kubectl /usr/local/bin/
curl -sSLo ${tmp}/minikube https://storage.googleapis.com/minikube/releases/${MINIKUBE_VERSION}/minikube-linux-amd64
chmod +x ${tmp}/minikube && sudo mv ${tmp}/minikube /usr/local/bin/
sudo -E minikube delete || true
sudo -E rm -rf /etc/kubernetes || true
sudo -E rm -rf $$HOME/.minikube/* || true
sudo -E minikube start --vm-driver=none --kubernetes-version=${K8S_VERSION}
sudo -E minikube update-context
kubectl wait node/minikube --for=condition=Ready
.PHONY: minikube
.EXPORT_ALL_VARIABLES: minikube
vault:
docker kill $$(docker ps -a --filter "name=vault" -q) || true
docker run -d -p8200:8200 --rm --name vault vault:1.2.0 server -dev -dev-root-token-id=toor
docker run --rm --network="host" -e VAULT_ADDR=$$VAULT_ADDR -e VAULT_TOKEN=$$VAULT_TOKEN vault:1.2.0 secrets enable -path=sops transit
docker run --rm --network="host" -e VAULT_ADDR=$$VAULT_ADDR -e VAULT_TOKEN=$$VAULT_TOKEN vault:1.2.0 write sops/keys/key type=rsa-4096
.PHONY: vault
sops:
curl -sSLo $(tmp)/sops "https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux"
chmod +x $(tmp)/sops
sudo mv ${tmp}/sops /usr/local/bin/
.PHONY: sops
+36 -85
View File
@@ -1,10 +1,10 @@
version: 2
version: 2.1
jobs:
build:
docker:
- image: circleci/golang:1.14.2
- image: circleci/golang:1.16.3
environment:
GOPATH: /go
working_directory: /home/circleci/workspace/helmfile
@@ -16,6 +16,7 @@ jobs:
- go-mod-cache-v1-
- run: go env
- run: make build
- run: make build-test-tools
- save_cache:
key: go-mod-cache-v1-{{ checksum "./go.sum" }}
paths:
@@ -26,7 +27,7 @@ jobs:
- .
image:
docker:
- image: circleci/golang:1.14.2
- image: circleci/golang:1.16.3
working_directory: /home/circleci/workspace/helmfile
steps:
- checkout
@@ -43,7 +44,7 @@ jobs:
image_helm3:
docker:
- image: circleci/golang:1.14.2
- image: circleci/golang:1.16.3
working_directory: /home/circleci/workspace/helmfile
steps:
- checkout
@@ -62,7 +63,7 @@ jobs:
environment:
GOPATH: /go
docker:
- image: circleci/golang:1.14.2
- image: circleci/golang:1.16.3
working_directory: /home/circleci/workspace/helmfile
steps:
- checkout
@@ -77,99 +78,49 @@ jobs:
# thanks to https://raw.githubusercontent.com/weaveworks/launcher/master/.circleci/config.yml
integration_tests:
machine:
image: circleci/classic:201808-01
environment:
CHANGE_MINIKUBE_NONE_USER: true
MINIKUBE_WANTUPDATENOTIFICATION: false
MINIKUBE_WANTREPORTERRORPROMPT: false
image: ubuntu-2004:202010-01
parameters:
helm-version:
type: string
steps:
- checkout
- run: mkdir ~/build
- attach_workspace:
at: ~/build
- run: cp ~/build/helmfile ~/project/helmfile
- run:
name: Install helm
environment:
HELM_VERSION: v2.16.1
name: Install test dependencies
command: |
HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
curl -Lo ${HELM_FILENAME} "https://kubernetes-helm.storage.googleapis.com/${HELM_FILENAME}"
tar zxf ${HELM_FILENAME} linux-amd64/helm
chmod +x linux-amd64/helm
sudo mv linux-amd64/helm /usr/local/bin/
- run:
name: Deploy minikube
environment:
CHANGE_MINIKUBE_NONE_USER: true
K8S_VERSION: v1.12.3
MINIKUBE_VERSION: v0.30.0
command: |
curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl
chmod +x kubectl && sudo mv kubectl /usr/local/bin/
curl -Lo minikube https://storage.googleapis.com/minikube/releases/${MINIKUBE_VERSION}/minikube-linux-amd64
chmod +x minikube && sudo mv minikube /usr/local/bin/
sudo -E minikube start --vm-driver=none --kubernetes-version=${K8S_VERSION}
sudo -E minikube update-context
- run:
name: Wait for nodes to become ready
command: JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'; until kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 1; done
cp ~/build/helmfile ~/project/helmfile
cp ~/build/diff-yamls ~/project/diff-yamls
cp ~/build/yamldiff ~/project/yamldiff
if [[ "<< parameters.helm-version >>" == v3* ]]
then
make -C .circleci helm
else
make -C .circleci helm2
fi
make -C .circleci vault
make -C .circleci sops
make -C .circleci kustomize
make -C .circleci minikube
- run:
name: Execute integration tests
environment:
TERM: "xterm"
command: |
export TERM=xterm
make integration
integration_tests_helm3:
machine:
image: circleci/classic:201808-01
environment:
CHANGE_MINIKUBE_NONE_USER: true
MINIKUBE_WANTUPDATENOTIFICATION: false
MINIKUBE_WANTREPORTERRORPROMPT: false
steps:
- checkout
- run: mkdir ~/build
- attach_workspace:
at: ~/build
- run: cp ~/build/helmfile ~/project/helmfile
- run:
name: Install helm
environment:
HELM_VERSION: v3.0.0
command: |
HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
curl -Lo ${HELM_FILENAME} "https://get.helm.sh/${HELM_FILENAME}"
tar zxf ${HELM_FILENAME} linux-amd64/helm
chmod +x linux-amd64/helm
sudo mv linux-amd64/helm /usr/local/bin/
- run:
name: Deploy minikube
environment:
CHANGE_MINIKUBE_NONE_USER: true
K8S_VERSION: v1.12.3
MINIKUBE_VERSION: v0.30.0
command: |
curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl
chmod +x kubectl && sudo mv kubectl /usr/local/bin/
curl -Lo minikube https://storage.googleapis.com/minikube/releases/${MINIKUBE_VERSION}/minikube-linux-amd64
chmod +x minikube && sudo mv minikube /usr/local/bin/
sudo -E minikube start --vm-driver=none --kubernetes-version=${K8S_VERSION}
sudo -E minikube update-context
- run:
name: Wait for nodes to become ready
command: JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'; until kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 1; done
- run:
name: Execute integration tests
command: |
export TERM=xterm
HELMFILE_HELM3=1 make integration
if [[ "<< parameters.helm-version >>" == v3* ]]
then
HELMFILE_HELM3=1 make integration
else
make integration
fi
# GITHUB_TOKEN env var must be setup in circleci console
release:
docker:
- image: circleci/golang:1.14.2
- image: circleci/golang:1.16.3
working_directory: /home/circleci/workspace/helmfile
steps:
- checkout
@@ -196,9 +147,9 @@ workflows:
- integration_tests:
requires:
- build
- integration_tests_helm3:
requires:
- build
matrix:
parameters:
helm-version: ["v2.17.0", "v3.4.2", "v3.5.4"]
- release:
filters:
branches:
+5
View File
@@ -0,0 +1,5 @@
root = true
[*.md]
insert_final_newline = true
trim_trailing_whitespace = true
+7
View File
@@ -2,4 +2,11 @@ dist/
.idea/
helmfile
helmfile.lock
/diff-yamls
/yamldiff
test/integration/tmp
vendor/
*.log
.vagrant/
*.lock
test/integration/.gnupg/
+13 -9
View File
@@ -1,4 +1,4 @@
FROM golang:1.14.2-alpine3.11 as builder
FROM golang:1.16.3-alpine3.13 as builder
RUN apk add --no-cache make git
WORKDIR /workspace/helmfile
@@ -11,11 +11,12 @@ FROM alpine:3.11
RUN apk add --no-cache ca-certificates git bash curl jq
ARG HELM_VERSION=v2.16.1
ARG HELM_VERSION="v2.17.0"
ARG HELM_LOCATION="https://kubernetes-helm.storage.googleapis.com"
ARG HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
ARG HELM_SHA256="7eebaaa2da4734242bbcdced62cc32ba8c7164a18792c8acdf16c77abffce202"
RUN wget ${HELM_LOCATION}/${HELM_FILENAME} && \
ARG HELM_SHA256="f3bec3c7c55f6a9eb9e6586b8c503f370af92fe987fcbf741f37707606d70296"
RUN set -x && \
wget ${HELM_LOCATION}/${HELM_FILENAME} && \
echo Verifying ${HELM_FILENAME}... && \
sha256sum ${HELM_FILENAME} | grep -q "${HELM_SHA256}" && \
echo Extracting ${HELM_FILENAME}... && \
@@ -26,14 +27,17 @@ RUN wget ${HELM_LOCATION}/${HELM_FILENAME} && \
# using the install documentation found at https://kubernetes.io/docs/tasks/tools/install-kubectl/
# for now but in a future version of alpine (in the testing version at the time of writing)
# we should be able to install using apk add.
ENV KUBECTL_VERSION="v1.14.5"
ENV KUBECTL_SHA256="26681319de56820a8467c9407e9203d5b15fb010ffc75ac5b99c9945ad0bd28c"
RUN curl --retry 3 -LO "https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \
# the sha256 sum can be found at https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl.sha256
# maybe a good idea to automate in the future?
ENV KUBECTL_VERSION="v1.18.9"
ENV KUBECTL_SHA256="6a68756a2d3d04b4d0f52b00de6493ba2c1fcb28b32f3e4a0e99b3d9f6c4e8ed"
RUN set -x & \
curl --retry 5 --retry-connrefused -LO "https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \
sha256sum kubectl | grep ${KUBECTL_SHA256} && \
chmod +x kubectl && \
mv kubectl /usr/local/bin/kubectl
RUN ["helm", "init", "--client-only"]
RUN ["helm", "init", "--client-only", "--stable-repo-url", "https://charts.helm.sh/stable"]
RUN helm plugin install https://github.com/databus23/helm-diff && \
helm plugin install https://github.com/futuresimple/helm-secrets && \
helm plugin install https://github.com/hypnoglow/helm-s3.git && \
@@ -42,4 +46,4 @@ RUN helm plugin install https://github.com/databus23/helm-diff && \
COPY --from=builder /workspace/helmfile/dist/helmfile_linux_amd64 /usr/local/bin/helmfile
CMD ["/usr/local/bin/helmfile"]
CMD ["/usr/local/bin/helmfile", "--help"]
+24 -11
View File
@@ -1,4 +1,4 @@
FROM golang:1.14.2-alpine3.11 as builder
FROM golang:1.16.3-alpine3.13 as builder
RUN apk add --no-cache make git
WORKDIR /workspace/helmfile
@@ -11,11 +11,12 @@ FROM alpine:3.11
RUN apk add --no-cache ca-certificates git bash curl jq
ARG HELM_VERSION=v3.2.1
ARG HELM_VERSION="v3.5.3"
ARG HELM_LOCATION="https://get.helm.sh"
ARG HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
ARG HELM_SHA256="018f9908cb950701a5d59e757653a790c66d8eda288625dbb185354ca6f41f6b"
RUN wget ${HELM_LOCATION}/${HELM_FILENAME} && \
ARG HELM_SHA256="2170a1a644a9e0b863f00c17b761ce33d4323da64fc74562a3a6df2abbf6cd70"
RUN set -x && \
wget ${HELM_LOCATION}/${HELM_FILENAME} && \
echo Verifying ${HELM_FILENAME}... && \
sha256sum ${HELM_FILENAME} | grep -q "${HELM_SHA256}" && \
echo Extracting ${HELM_FILENAME}... && \
@@ -25,17 +26,29 @@ RUN wget ${HELM_LOCATION}/${HELM_FILENAME} && \
# using the install documentation found at https://kubernetes.io/docs/tasks/tools/install-kubectl/
# for now but in a future version of alpine (in the testing version at the time of writing)
# we should be able to install using apk add.
ENV KUBECTL_VERSION="v1.14.5"
ENV KUBECTL_SHA256="26681319de56820a8467c9407e9203d5b15fb010ffc75ac5b99c9945ad0bd28c"
RUN curl --retry 3 -LO "https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \
# the sha256 sum can be found at https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl.sha256
# maybe a good idea to automate in the future?
ENV KUBECTL_VERSION="v1.18.9"
ENV KUBECTL_SHA256="6a68756a2d3d04b4d0f52b00de6493ba2c1fcb28b32f3e4a0e99b3d9f6c4e8ed"
RUN set -x && \
curl --retry 5 --retry-connrefused -LO "https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \
sha256sum kubectl | grep ${KUBECTL_SHA256} && \
chmod +x kubectl && \
mv kubectl /usr/local/bin/kubectl
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.0.0-rc.7 && \
helm plugin install https://github.com/futuresimple/helm-secrets && \
helm plugin install https://github.com/hypnoglow/helm-s3.git && \
helm plugin install https://github.com/aslafy-z/helm-git.git
ENV KUSTOMIZE_VERSION="v3.8.8"
ENV KUSTOMIZE_SHA256="175938206f23956ec18dac3da0816ea5b5b485a8493a839da278faac82e3c303"
RUN set -x && \
curl --retry 5 --retry-connrefused -LO https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \
sha256sum kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz | grep ${KUSTOMIZE_SHA256} && \
tar zxf kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \
rm kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \
mv kustomize /usr/local/bin/kustomize
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.1.3 && \
helm plugin install https://github.com/jkroepke/helm-secrets --version v3.5.0 && \
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.10.0 && \
helm plugin install https://github.com/aslafy-z/helm-git.git --version v0.10.0
COPY --from=builder /workspace/helmfile/dist/helmfile_linux_amd64 /usr/local/bin/helmfile
+15 -4
View File
@@ -2,7 +2,7 @@ ORG ?= $(shell basename $(realpath ..))
PKGS := $(shell go list ./... | grep -v /vendor/)
build:
go build ${TARGETS}
go build -ldflags '-X github.com/roboll/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
.PHONY: build
generate:
@@ -17,6 +17,11 @@ check:
go vet ${PKGS}
.PHONY: check
build-test-tools:
go build test/diff-yamls/diff-yamls.go
go build test/yamldiff/yamldiff.go
.PHONY: build-test-tools
test:
go test -v ${PKGS} -cover -race -p=1
.PHONY: test
@@ -25,12 +30,19 @@ integration:
bash test/integration/run.sh
.PHONY: integration
integration/vagrant:
$(MAKE) build GOOS=linux GOARCH=amd64
$(MAKE) build-test-tools GOOS=linux GOARCH=amd64
vagrant up
vagrant ssh -c 'HELMFILE_HELM3=1 make -C /vagrant integration'
.PHONY: integration/vagrant
cross:
env CGO_ENABLED=0 gox -os '!openbsd !freebsd !netbsd' -arch '!arm !mips !mipsle !mips64 !mips64le !s390x' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X github.com/roboll/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
env CGO_ENABLED=0 gox -os 'windows darwin linux' -arch '386 amd64 arm64' -osarch '!darwin/arm64 !darwin/386' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X github.com/roboll/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
.PHONY: cross
static-linux:
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOFLAGS=-mod=vendor go build -o "dist/helmfile_linux_amd64" -ldflags '-X github.com/roboll/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOFLAGS=-mod=readonly go build -o "dist/helmfile_linux_amd64" -ldflags '-X github.com/roboll/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
.PHONY: static-linux
install:
@@ -59,7 +71,6 @@ run: image
push: image
docker push quay.io/${ORG}/helmfile:${TAG}
image/helm3:
docker build -f Dockerfile.helm3 -t quay.io/${ORG}/helmfile:helm3-${TAG} .
+12 -10
View File
@@ -1,25 +1,27 @@
# Paths Overivew
Using manifest files in conjunction with command line argument can be a bit confusing.
# Paths Overivew
A few rules to clear up this ambiguity:
Using manifest files in conjunction with command line argument can be a bit confusing.
A few rules to clear up this ambiguity:
- Absolute paths are always resolved as absolute paths
- Relative paths referenced *in* the helmfile manifest itself are relative to that manifest
- Relative paths referenced on the command line are relative to the current working directory the user is in
### Examples
There are several examples that we can go through in the `/examples` folder which demonstrate this.
There are several examples that we can go through in the [`/examples`](examples) folder which demonstrate this.
**Local Execution**
This is an example of a Helmfile manifest referencing a local value directly.
This is an example of a Helmfile manifest referencing a local value directly.
Indirect:
```
helmfile -f examples/deployments/local/charts.yaml sync
```
Direct:
Direct:
```
cd examples/deployments/local/
helmfile sync
@@ -27,14 +29,14 @@ helmfile sync
**Relative Paths in Helmfile**
This is an example of a Helmfile manifest using relative paths for values.
This is an example of a Helmfile manifest using relative paths for values.
Indirect:
```
helmfile -f examples/deployments/dev/charts.yaml sync
```
Direct:
Direct:
```
cd examples/deployments/dev/
helmfile sync
@@ -42,7 +44,7 @@ helmfile sync
**Relative Paths in Helmfile w/ --values overrides**
This is an example of a Helmfile manifest using relative paths for values including an additional `--values` from the command line.
This is an example of a Helmfile manifest using relative paths for values including an additional `--values` from the command line.
NOTE: The `--values` is resolved relative to the CWD of the terminal *not* the Helmfile manifest. You can see this with the `replicas` being adjusted to 3 now for the deployment.
@@ -51,7 +53,7 @@ Indirect:
helmfile -f examples/deployments/dev/charts.yaml sync --values values/replica-values.yaml
```
Direct:
Direct:
```
cd examples/deployments/dev/
helmfile sync --values ../../values/replica-values.yaml
+357 -53
View File
@@ -47,20 +47,21 @@ The default name for a helmfile is `helmfile.yaml`:
repositories:
# To use official "stable" charts a.k.a https://github.com/helm/charts/tree/master/stable
- name: stable
url: https://kubernetes-charts.storage.googleapis.com
url: https://charts.helm.sh/stable
# To use official "incubator" charts a.k.a https://github.com/helm/charts/tree/master/incubator
- name: incubator
url: https://kubernetes-charts-incubator.storage.googleapis.com
url: https://charts.helm.sh/incubator
# helm-git powered repository: You can treat any Git repository as a charts repository
- name: polaris
url: git+https://github.com/reactiveops/polaris@deploy/helm?ref=master
# Advanced configuration: You can setup basic or tls auth
# Advanced configuration: You can setup basic or tls auth and optionally enable helm OCI integration
- name: roboll
url: http://roboll.io/charts
certFile: optional_client_cert
keyFile: optional_client_key
username: optional_username
password: optional_password
oci: true
# Advanced configuration: You can use a ca bundle to use an https repo
# with a self-signed certificate
- name: insecure
@@ -69,6 +70,9 @@ repositories:
# context: kube-context # this directive is deprecated, please consider using helmDefaults.kubeContext
# Path to alternative helm binary (--helm-binary)
helmBinary: path/to/helm3
# Default values to set for args along with dedicated keys that can be set by contributors, cli args take precedence over these.
# In other words, unset values results in no flags passed to helm.
# See the helm usage (helm SUBCOMMAND -h) for more info on default values when those flags aren't provided.
@@ -84,6 +88,8 @@ helmDefaults:
verify: true
# wait for k8s resources via --wait. (default false)
wait: true
# if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout (default false, Implemented in Helm3.5)
waitForJobs: true
# 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: 600
# performs pods restart for the resource if applicable (default false)
@@ -102,7 +108,15 @@ helmDefaults:
historyMax: 10
# when using helm 3.2+, automatically create release namespaces if they do not exist (default true)
createNamespace: true
# if used with charts museum allows to pull unstable charts for deployment, for example: if 1.2.3 and 1.2.4-dev versions exist and set to true, 1.2.4-dev will be pulled (default false)
devel: true
# When set to `true`, skips running `helm dep up` and `helm dep build` on this release's chart.
# Useful when the chart is broken, like seen in https://github.com/roboll/helmfile/issues/1547
skipDeps: false
# 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.
#
@@ -157,9 +171,10 @@ releases:
# will attempt to decrypt it using helm-secrets plugin
secrets:
- vault_secret.yaml
# Override helmDefaults options for verify, wait, timeout, recreatePods and force.
# Override helmDefaults options for verify, wait, waitForJobs, timeout, recreatePods and force.
verify: true
wait: true
waitForJobs: true
timeout: 60
recreatePods: true
force: false
@@ -186,8 +201,23 @@ releases:
# See https://github.com/roboll/helmfile/issues/642
# (default "", which means the standard kubeconfig, either ~/kubeconfig or the file pointed by $KUBECONFIG environment variable)
kubeContext: kube-context
# passes --disable-validation to helm 3 diff plugin, this requires diff plugin >= 3.1.2
# It may be helpful to deploy charts with helm api v1 CRDS
# https://github.com/roboll/helmfile/pull/1373
disableValidation: false
# passes --disable-validation to helm 3 diff plugin, this requires diff plugin >= 3.1.2
# It is useful when any release contains custom resources for CRDs that is not yet installed onto the cluster.
# https://github.com/roboll/helmfile/pull/1618
disableValidationOnInstall: false
# passes --disable-openapi-validation to helm 3 diff plugin, this requires diff plugin >= 3.1.2
# It may be helpful to deploy charts with helm api v1 CRDS
# https://github.com/roboll/helmfile/pull/1373
disableOpenApiValidation: false
# limit the maximum number of revisions saved per release. Use 0 for no limit (default 10)
historyMax: 10
# When set to `true`, skips running `helm dep up` and `helm dep build` on this release's chart.
# Useful when the chart is broken, like seen in https://github.com/roboll/helmfile/issues/1547
skipDeps: false
# Local chart example
- name: grafana # name of this release
@@ -268,6 +298,8 @@ environments:
# 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: Error
# kubeContext to use for this environment
kubeContext: kube-context
#
# Advanced Configuration: Layering
@@ -298,6 +330,20 @@ apiVersions:
Helmfile uses [Go templates](https://godoc.org/text/template) for templating your helmfile.yaml. While go ships several built-in functions, we have added all of the functions in the [Sprig library](https://godoc.org/github.com/Masterminds/sprig).
We also added the following functions:
- `requiredEnv`
- `exec`
- `readFile`
- `toYaml`
- `fromYaml`
- `setValueAtPath`
- `get` (Sprig's original `get` is available as `sprigGet`)
- `tpl`
- `required`
- `fetchSecretValue`
- `expandSecretRefs`
We also added one special template function: `requiredEnv`.
The `requiredEnv` function allows you to declare a particular environment variable as required for template rendering.
If the environment variable is unset or empty, the template rendering will fail with an error message.
@@ -334,15 +380,44 @@ releases:
If you wish to treat your enviroment variables as strings always, even if they are boolean or numeric values you can use `{{ env "ENV_NAME" | quote }}` or `"{{ env "ENV_NAME" }}"`. These approaches also work with `requiredEnv`.
## installation
## Installation
- download one of [releases](https://github.com/roboll/helmfile/releases) or
- run as a [container](https://quay.io/roboll/helmfile) or
- install from [AUR](https://aur.archlinux.org/packages/kubernetes-helmfile-bin/) for Archlinux or
- [run as a container](#running-as-a-container) or
- Archlinux: install via `pacman -S helmfile` or from [AUR](https://aur.archlinux.org/packages/kubernetes-helmfile-bin/) or
- openSUSE: install via `zypper in helmfile` assuming you are on Tumbleweed; if you are on Leap you must add the [kubic](https://download.opensuse.org/repositories/devel:/kubic/) repo for your distribution version once before that command, e.g. `zypper ar https://download.opensuse.org/repositories/devel:/kubic/openSUSE_Leap_\$releasever kubic`, or
- Windows (using [scoop](https://scoop.sh/)): `scoop install helmfile`
- macOS (using [homebrew](https://brew.sh/)): `brew install helmfile`
## getting started
### Running as a container
The [Helmfile Docker images are available in Quay](https://quay.io/roboll/helmfile). There is no `latest` tag, since the `0.x` versions can contain breaking changes, so make sure you pick the right tag. Example using `helmfile 0.135.0`:
```sh-session
# helm 2
$ docker run --rm --net=host -v "${HOME}/.kube:/root/.kube" -v "${HOME}/.helm:/root/.helm" -v "${PWD}:/wd" --workdir /wd quay.io/roboll/helmfile:v0.135.0 helmfile sync
# helm 3
$ docker run --rm --net=host -v "${HOME}/.kube:/root/.kube" -v "${HOME}/.config/helm:/root/.config/helm" -v "${PWD}:/wd" --workdir /wd quay.io/roboll/helmfile:helm3-v0.135.0 helmfile sync
```
You can also use shims to make calling the binaries easier:
```sh-session
# helm 2
$ printf '%s\n' '#!/bin/sh' 'docker run --rm --net=host -v "${HOME}/.kube:/root/.kube" -v "${HOME}/.helm:/root/.helm" -v "${PWD}:/wd" --workdir /wd quay.io/roboll/helmfile:v0.135.0 helmfile "$@"' |
tee helmfile
$ chmod +x helmfile
$ ./helmfile sync
# helm 3
$ printf '%s\n' '#!/bin/sh' 'docker run --rm --net=host -v "${HOME}/.kube:/root/.kube" -v "${HOME}/.config/helm:/root/.config/helm" -v "${PWD}:/wd" --workdir /wd quay.io/roboll/helmfile:helm3-v0.135.0 helmfile "$@"' |
tee helmfile
$ chmod +x helmfile
$ ./helmfile sync
```
## Getting Started
Let's start with a simple `helmfile` and gradually improve it to fit your use-case!
@@ -372,7 +447,7 @@ Iterate on the `helmfile.yaml` by referencing:
- [CLI reference](#cli-reference).
- [Helmfile Best Practices Guide](https://github.com/roboll/helmfile/blob/master/docs/writing-helmfile.md)
## cli reference
## CLI Reference
```
NAME:
@@ -382,33 +457,37 @@ USAGE:
helmfile [global options] command [command options] [arguments...]
VERSION:
v0.92.1
v0.138.6
COMMANDS:
deps update charts based on the contents of requirements.yaml
repos sync repositories from state file (helm repo add && helm repo update)
charts DEPRECATED: sync releases from state file (helm upgrade --install)
diff diff releases from state file against env (helm diff)
template template releases from state file against env (helm template)
lint lint charts from state file (helm lint)
sync sync all resources from state file (repos, releases and chart deps)
apply apply all resources from state file only when there are changes
status retrieve status of releases in state file
delete DEPRECATED: delete releases from state file (helm delete)
destroy deletes and then purges releases
test test releases from state file (helm test)
build output compiled helmfile state(s) as YAML
list list releases defined in state file
help, h Shows a list of commands or help for one command
deps update charts based on their requirements
repos sync repositories from state file (helm repo add && helm repo update)
charts DEPRECATED: sync releases from state file (helm upgrade --install)
diff diff releases from state file against env (helm diff)
template template releases from state file against env (helm template)
write-values write values files for releases. Similar to `helmfile template`, write values files instead of manifests.
lint lint charts from state file (helm lint)
sync sync all resources from state file (repos, releases and chart deps)
apply apply all resources from state file only when there are changes
status retrieve status of releases in state file
delete DEPRECATED: delete releases from state file (helm delete)
destroy deletes and then purges releases
test test releases from state file (helm test)
build output compiled helmfile state(s) as YAML
list list releases defined in state file
fetch fetch charts from state file
version Show the version for Helmfile.
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--helm-binary value, -b value path to helm binary (default: "helm")
--file helmfile.yaml, -f helmfile.yaml load config from file or directory. defaults to helmfile.yaml or `helmfile.d`(means `helmfile.d/*.yaml`) in this preference
--environment default, -e default specify the environment name. defaults to default
--environment value, -e value specify the environment name. defaults to "default"
--state-values-set value set state values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
--state-values-file value specify state values in a YAML file
--quiet, -q Silence output. Equivalent to log-level warn
--kube-context value Set kubectl context. Uses current context by default
--debug Enable verbose output for Helm and set log-level to debug, this disables --quiet/-q effect
--no-color Output without color
--log-level value Set log level, default info
--namespace value, -n value Set namespace. Uses the namespace set in the context by default, and is available in templates as {{ .Namespace }}
@@ -462,11 +541,11 @@ An expected use-case of `apply` is to schedule it to run periodically, so that y
### destroy
The `helmfile destroy` sub-command deletes and purges all the releases defined in the manifests.
The `helmfile destroy` sub-command uninstalls and purges all the releases defined in the manifests.
`helmfile --interactive destroy` instructs Helmfile to request your confirmation before actually deleting releases.
`destroy` basically runs `helm delete --purge` on all the targeted releases. If you don't want purging, use `helmfile delete` instead.
`destroy` basically runs `helm uninstall --purge` on all the targeted releases. If you don't want purging, use `helmfile delete` instead.
### delete (DEPRECATED)
@@ -476,13 +555,12 @@ The `helmfile delete` sub-command deletes all the releases defined in the manife
Note that `delete` doesn't purge releases. So `helmfile delete && helmfile sync` results in sync failed due to that releases names are not deleted but preserved for future references. If you really want to remove releases for reuse, add `--purge` flag to run it like `helmfile delete --purge`.
### secrets
The `secrets` parameter in a `helmfile.yaml` causes the [helm-secrets](https://github.com/futuresimple/helm-secrets) plugin to be executed to decrypt the file.
The `secrets` parameter in a `helmfile.yaml` causes the [helm-secrets](https://github.com/jkroepke/helm-secrets) plugin to be executed to decrypt the file.
To supply the secret functionality Helmfile needs the `helm secrets` plugin installed. For Helm 2.3+
you should be able to simply execute `helm plugin install https://github.com/futuresimple/helm-secrets
you should be able to simply execute `helm plugin install https://github.com/jkroepke/helm-secrets
`.
### test
@@ -495,7 +573,13 @@ Use `--cleanup` to delete pods upon completion.
The `helmfile lint` sub-command runs a `helm lint` across all of the charts/releases defined in the manifest. Non local charts will be fetched into a temporary folder which will be deleted once the task is completed.
### fetch
The `helmfile fetch` sub-command downloads or copies local charts to a local directory for debug purpose. The local directory
must be specified with `--output-dir`.
## Paths Overview
Using manifest files in conjunction with command line argument can be a bit confusing.
A few rules to clear up this ambiguity:
@@ -504,9 +588,10 @@ A few rules to clear up this ambiguity:
- Relative paths referenced *in* the Helmfile manifest itself are relative to that manifest
- Relative paths referenced on the command line are relative to the current working directory the user is in
For additional context, take a look at [paths examples](PATHS.md)
For additional context, take a look at [paths examples](PATHS.md).
## Labels Overview
A selector can be used to only target a subset of releases when running Helmfile. This is useful for large helmfiles with releases that are logically grouped together.
Labels are simple key value pairs that are an optional field of the release spec. When selecting by label, the search can be inverted. `tier!=backend` would match all releases that do NOT have the `tier: backend` label. `tier=fronted` would only match releases with the `tier: frontend` label.
@@ -515,10 +600,45 @@ Multiple labels can be specified using `,` as a separator. A release must match
The `selector` parameter can be specified multiple times. Each parameter is resolved independently so a release that matches any parameter will be used.
`--selector tier=frontend --selector tier=backend` will select all the charts
`--selector tier=frontend --selector tier=backend` will select all the charts.
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:
```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:
```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:
@@ -532,6 +652,7 @@ In addition to built-in ones, the following custom template functions are availa
- `fromYaml` reads a golang string and generates a map
- `setValueAtPath PATH NEW_VALUE` traverses a golang map, replaces the value at the PATH with NEW_VALUE
- `toYaml` marshals a map into a string
- `get` returns the value of the specified key if present in the `.Values` object, otherwise will return the default value defined in the function
### Values Files Templates
@@ -560,7 +681,7 @@ foo:
bar: ""
```
The resulting, temporary values.yaml that is generated from `values.yaml.tpl` would become:
The resulting, temporary values.yaml that is generated from `values.yaml.gotmpl` would become:
```yaml
foo:
@@ -632,6 +753,8 @@ environments:
default:
production:
---
releases:
- name: newrelic-agent
installed: {{ eq .Environment.Name "production" | toYaml }}
@@ -655,6 +778,8 @@ environments:
values:
- production.yaml
---
releases:
- name: myapp
values:
@@ -671,11 +796,11 @@ releaseName: prod
`values.yaml.gotmpl`
```yaml
domain: {{ .Values | getOrNil "my.domain" | default "dev.example.com" }}
domain: {{ .Values | get "domain" "dev.example.com" }}
```
`helmfile sync` installs `myapp` with the value `domain=dev.example.com`,
whereas `helmfile --environment production sync` installs the app with the value `domain=production.example.com`.
whereas `helmfile --environment production sync` installs the app with the value `domain=prod.example.com`.
For even more flexibility, you can now use values declared in the `environments:` section in other parts of your helmfiles:
@@ -697,6 +822,8 @@ environments:
- production.yaml # bare .yaml file, content will be used verbatim
- other.yaml.gotmpl # template directives with potential side-effects like `exec` and `readFile` will be honoured
---
releases:
- name: myapp-{{ .Values.releaseName }} # release name will be one of `dev` or `prod` depending on selected environment
values:
@@ -707,7 +834,7 @@ releases:
...
```
### Note
### Note on Environment.Values vs Values
The `{{ .Values.foo }}` syntax is the recommended way of using environment values.
@@ -716,13 +843,36 @@ This is still working but is **deprecated** and the new `{{ .Values.foo }}` synt
You can read more infos about the feature proposal [here](https://github.com/roboll/helmfile/issues/640).
### Loading remote environment values files
Since #1296 and Helmfile v0.118.8, you can use `go-getter`-style URLs to refer to remote values files:
```yaml
environments:
cluster-azure-us-west:
values:
- git::https://git.company.org/helmfiles/global/azure.yaml?ref=master
- git::https://git.company.org/helmfiles/global/us-west.yaml?ref=master
cluster-gcp-europe-west:
values:
- git::https://git.company.org/helmfiles/global/gcp.yaml?ref=master
- git::https://git.company.org/helmfiles/global/europe-west.yaml?ref=master
---
releases:
- ...
```
This is particularly useful when you co-locate helmfiles within your project repo but want to reuse the definitions in a global repo.
## Environment Secrets
Environment Secrets (not to be confused with Kubernetes Secrets) are encrypted versions of `Environment Values`.
You can list any number of `secrets.yaml` files created using `helm secrets` or `sops`, so that
Helmfile could automatically decrypt and merge the secrets into the environment values.
First you must have the [helm-secrets](https://github.com/futuresimple/helm-secrets) plugin installed along with a
First you must have the [helm-secrets](https://github.com/jkroepke/helm-secrets) plugin installed along with a
`.sops.yaml` file to configure the method of encryption (this can be in the same directory as your helmfile or
in the sub-directory containing your secrets files).
@@ -740,6 +890,8 @@ environments:
secrets:
- environments/production/secrets.yaml
---
releases:
- name: myapp
chart: mychart
@@ -771,6 +923,8 @@ releases:
- [TILLER_NAMESPACE/][NAMESPACE/]anotherelease
```
Be aware that you have to specify the namespace name if you configured one for the release(s).
All the releases listed under `needs` are installed before(or deleted after) the release itself.
For the following example, `helmfile [sync|apply]` installs releases in this order:
@@ -905,14 +1059,14 @@ that is accessible by running a command:
A usual usage of `exec` would look like this:
```
```yaml
mysetting: |
{{ exec "./mycmd" (list "arg1" "arg2" "--flag1") | indent 2 }}
```
Or even with a pipeline:
```
```yaml
mysetting: |
{{ yourinput | exec "./mycmd-consume-stdin" (list "arg1" "arg2") | indent 2 }}
```
@@ -935,20 +1089,30 @@ Currently supported `events` are:
- `prepare`
- `presync`
- `preuninstall`
- `postuninstall`
- `postsync`
- `cleanup`
Hooks associated to `prepare` events are triggered after each release in your helmfile is loaded from YAML, before execution.
`prepare` hooks are triggered on the release as long as it is not excluded by the helmfile selector(e.g. `helmfile -l key=value`).
Hooks associated to `cleanup` events are triggered after each release is processed.
Hooks associated to `presync` events are triggered before each release is applied to the remote cluster.
This is the ideal event to execute any commands that may mutate the cluster state as it will not be run for read-only operations like `lint`, `diff` or `template`.
Hooks associated to `presync` events are triggered before each release is applied to the remote cluster. This is the ideal event to execute any commands that may mutate the cluster state as it will not be run for read-only operations like `lint`, `diff` or `template`.
`preuninstall` hooks are triggered immediately before a release is uninstalled as part of `helmfile apply`, `helmfile sync`, `helmfile delete`, and `helmfile destroy`.
Hooks associated to `postsync` events are triggered after each release is applied to the remote cluster. This is the ideal event to execute any commands that may mutate the cluster state as it will not be run for read-only operations like `lint`, `diff` or `template`.
`postuninstall` hooks are triggered immediately after successful uninstall of a release while running `helmfile apply`, `helmfile sync`, `helmfile delete`, `helmfile destroy`.
`postsync` hooks are triggered after each release is synced(installed, updated, or uninstalled) to/from the cluster, regardless of the sync was successful or not.
This is the ideal place to execute any commands that may mutate the cluster state as it will not be run for read-only operations like `lint`, `diff` or `template`.
`cleanup` hooks are triggered after each release is processed.
This is the counterpart to `prepare`, as any release on which `prepare` has been triggered gets `cleanup` triggered as well.
The following is an example hook that just prints the contextual information provided to hook:
```
```yaml
releases:
- name: myapp
chart: mychart
@@ -979,9 +1143,57 @@ Now, replace `echo` with any command you like, and rewrite `args` that actually
- linting
- testing
Hooks expose additional template expressions:
`.Event.Name` is the name of the hook event.
`.Event.Error` is the error generated by a failed release, exposed for `posysync` hooks only when a release fails, otherwise its value is `nil`.
You can use the hooks event expressions to send notifications to platforms such as `Slack`, `MS Teams`, etc.
The following example passes arguments to a script which sends a notification:
```yaml
releases:
- name: myapp
chart: mychart
# *snip*
hooks:
- events:
- presync
- postsync
showlogs: true
command: notify.sh
args:
- --event
- '{{`{{ .Event.Name }}`}}'
- --status
- '{{`{{ if .Event.Error }}failure{{ else }}success{{ end }}`}}'
- --environment
- '{{`{{ .Environment.Name }}`}}'
- --namespace
- '{{`{{ .Release.Namespace }}`}}'
- --release
- '{{`{{ .Release.Name }}`}}'
```
For templating, imagine that you created a hook that generates a helm chart on-the-fly by running an external tool like ksonnet, kustomize, or your own template engine.
It will allow you to write your helm releases with any language you like, while still leveraging goodies provided by helm.
### Global Hooks
In contrast to the per release hooks mentioned above these are run only once at the very beginning and end of the execution of a helmfile command and only the `prepare` and `cleanup` hooks are available respectively.
They use the same syntax as per release hooks, but at the top level of your helmfile:
```yaml
hooks:
- events: ["prepare", "cleanup"]
showlogs: true
command: "echo"
args: ["{{`{{.Environment.Name}}`}}", "{{`{{.HelmfileCommand}}`}}\
"]
```
### Helmfile + Kustomize
Do you prefer `kustomize` to write and organize your Kubernetes apps, but still want to leverage helm's useful features
@@ -1044,9 +1256,9 @@ We also have dedicated documentation on the following topics which might interes
Or join our friendly slack community in the [`#helmfile`](https://slack.sweetops.com) channel to ask questions and get help. Check out our [slack archive](https://archive.sweetops.com/helmfile/) for good examples of how others are using it.
## Using env files
## 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:
Helmfile itself doesn't have an ability to load .env files. But you can write some bash script to achieve the goal:
```console
set -a; . .env; set +a; helmfile sync
@@ -1054,7 +1266,7 @@ set -a; . .env; set +a; helmfile sync
Please see #203 for more context.
## Running helmfile interactively
## Running Helmfile interactively
`helmfile --interactive [apply|destroy]` requests confirmation from you before actually modifying your cluster.
@@ -1068,14 +1280,79 @@ Once you download all required charts into your machine, you can run `helmfile c
It basically run only `helm upgrade --install` with your already-downloaded charts, hence no Internet connection is required.
See #155 for more information on this topic.
## Experimental features
## Experimental Features
Some experimental features may be available for testing in perspective of being (or not) included in a future release.
Those features are set using the environment variable `HELMFILE_EXPERIMENTAL`. Here is the current experimental feature :
* `explicit-selector-inheritance` : remove today implicit cli selectors inheritance for composed helmfiles, see [composition selector](#selectors)
If you want to enable all experimental features set the env var to `HELMFILE_EXPERIMENTAL=true`
## Azure ACR integration
## `bash` and `zsh` completion
Copy `autocomplete/helmfile_bash_autocomplete` or `autocomplete/helmfile_zsh_autocomplete` (depending on your shell of choice) to directory where you keep other shell completion scripts to make sure it is sourced.
## Examples
For more examples, see the [examples/README.md](https://github.com/roboll/helmfile/blob/master/examples/README.md) or the [`helmfile`](https://github.com/cloudposse/helmfiles/tree/master/releases) distribution by [Cloud Posse](https://github.com/cloudposse/).
## Integrations
- [renovate](https://github.com/renovatebot/renovate) automates chart version updates. See [this PR for more information](https://github.com/renovatebot/renovate/pull/5257).
- For updating container image tags and git tags embedded within helmfile.yaml and values, you can use [renovate's regexManager](https://docs.renovatebot.com/modules/manager/regex/). Please see [this comment in the renovate repository](https://github.com/renovatebot/renovate/issues/6130#issuecomment-624061289) for more information.
- [ArgoCD Integration](#argocd-integration)
- [Azure ACR Integration](#azure-acr-integration)
### ArgoCD Integration
Use [ArgoCD](https://argoproj.github.io/argo-cd/) with `helmfile template` for GitOps.
ArgoCD has support for kustomize/manifests/helm chart by itself. Why bother with Helmfile?
The reasons may vary:
1. You do want to manage applications with ArgoCD, while letting Helmfile manage infrastructure-related components like Calico/Cilium/WeaveNet, Linkerd/Istio, and ArgoCD itself.
- This way, any application deployed by ArgoCD has access to all the infrastructure.
- Of course, you can use ArgoCD's [Sync Waves and Phases](https://argoproj.github.io/argo-cd/user-guide/sync-waves/) for ordering the infrastructure and application installations. But it may be difficult to separate the concern between the infrastructure and apps and annotate K8s resources consistently when you have different teams for managing infra and apps.
2. You want to review the exact K8s manifests being applied on pull-request time, before ArgoCD syncs.
- This is often better than using a kind of `HelmRelease` custom resources that obfuscates exactly what manifests are being applied, which makes reviewing harder.
3. Use Helmfile as the single-pane of glass for all the K8s resources deployed to your cluster(s).
- Helmfile can reduce repetition in K8s manifests across ArgoCD application
For 1, you run `helmfile apply` on CI to deploy ArgoCD and the infrastructure components.
> helmfile config for this phase often reside within the same directory as your Terraform project. So connecting the two with [terraform-provider-helmfile](https://github.com/mumoshu/terraform-provider-helmfile) may be helpful
For 2, another app-centric CI or bot should render/commit manifests by running:
```
helmfile template --output-dir-template $(pwd)/gitops//{{.Release.Name}}
cd gitops
git add .
git commit -m 'some message'
git push origin $BRANCH
```
> Note that `$(pwd)` is necessary when `hemlfile.yaml` has one or more sub-helmfiles in nested directories,
> because setting a relative file path in `--output-dir` or `--output-dir-template` results in each sub-helmfile render
> to the directory relative to the specified path.
so that they can be deployed by Argo CD as usual.
The CI or bot can optionally submit a PR to be review by human, running:
```
hub pull-request -b main -l gitops -m 'some description'
```
Recommendations:
- Do create ArgoCD `Application` custom resource per Helm/Helmfile release, each point to respective sub-directory generated by `helmfile template --output-dir-template`
- If you don't directly push it to the main Git branch and instead go through a pull-request, do lint rendered manifests on your CI, so that you can catch easy mistakes earlier/before ArgoCD finally deploys it
- See [this ArgoCD issue](https://github.com/argoproj/argo-cd/issues/2143#issuecomment-570478329) for why you may want this, and see [this helmfile issue](https://github.com/roboll/helmfile/pull/1357) for how `--output-dir-template` works.
### Azure ACR Integration
Azure offers helm repository [support for Azure Container Registry](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-helm-repos) as a preview feature.
@@ -1085,17 +1362,44 @@ To use `helmfile` with ACR, on the other hand, you must either include a usernam
An ACR repository definition in `helmfile.yaml` looks like this:
```
```yaml
repositories:
- name: <MyRegistry>
url: https://<MyRegistry>.azurecr.io/helm/v1/repo
```
## Examples
## OCI Registries
For more examples, see the [examples/README.md](https://github.com/roboll/helmfile/blob/master/examples/README.md) or the [`helmfile`](https://github.com/cloudposse/helmfiles/tree/master/releases) distribution by [Cloud Posse](https://github.com/cloudposse/).
In order to use OCI chart registries firstly they must be marked in the repository list as OCI enabled, e.g.
# Attribution
```yaml
repositories:
- name: myOCIRegistry
url: myregistry.azurecr.io
oci: true
```
It is important not to include a scheme for the URL as helm requires that these are not present for OCI registries
Secondly the credentials for the OCI registry can either be specified within `helmfile.yaml` similar to
```yaml
repositories:
- name: myOCIRegistry
url: myregistry.azurecr.io
oci: true
username: spongebob
password: squarepants
```
or for CI scenarios these can be sourced from the environment with the format `<registryName>_USERNAME` and `<registryName_PASSWORD>`, e.g.
```shell
export MYOCIREGISTRY_USERNAME=spongebob
export MYOCIREGISTRY_PASSWORD=squarepants
```
## Attribution
We use:
+10
View File
@@ -11,7 +11,10 @@ information to this file.
|---|---|---|---|---|
| [reddit.com](https://www.reddit.com/) | production | [GitHub issue](https://github.com/roboll/helmfile/issues/96#issue-312012249), [Talk](https://www.slideshare.net/GregoryTaylor11/helm-at-reddit-from-local-dev-staging-to-production) | San Francisco, CA | April 2018 |
| [ricardo.ch](https://www.ricardo.ch/) | production | We're deploying our complete application platform using Helmfile. | Zug, Switzerland | April 2018 |
| [Hellofresh](https://engineering.hellofresh.com/) | production | We've been using helmfile in production since April 2018 for deploying all our infrastructure applications | Berlin, Germany | April 2018 |
| [Cherre](https://cherre.com/) | production | We have no public posts about using Helmfile, but we have been using it for a long time now | New York, NY | October 2018 |
| [Sight Machine](https://sightmachine.com/) | production | We don't have anything publicly posted about it, but have been using it for quite a while in production. | San Francisco, CA and Ann Arbor, MI | December 2018 |
| [TailorMed](https://tailormed.co/) | production | We're deploying all of our platform and all K8s resources using Helmfile. The power of Helmfile are more then amazing. Thank you! | Tel-Aviv, Israel | September 2020 |
| [VSHN – The DevOps Company](https://vshn.ch) | production | | Zurich, Switzerland | March 2019 |
| [Vlocity](https://vlocity.com/) | proof-of-concept | | Melbourne, Australia | March 2019 |
| [transit](https://transit.app/) | production | [Blog post](https://medium.com/@naseem_60378/helmfile-its-like-a-helm-for-your-helm-74a908581599). | Montreal, Canada | March 2019 |
@@ -20,4 +23,11 @@ information to this file.
| [kloeckner-i](https://www.kloeckner-i.com/) | production | We are deploying our standard tools via helmfile to all our clusters. | Berlin, Germany | September 2019 |
| [American Express](https://www.americanexpress.com) | proof-of-concept | Orchestration of both internal cluster workloads and local developer environments. | London, GB | January 2020 |
| [Sportradar](https://www.sportradar.com) | production | Since mid-2019, we've been deploying our core infrastructure and several application stacks with Helmfile. | St. Gallen, Switzerland | March 2020 |
| [PedidosYa](https://www.pedidosya.com) | production | | Montevideo, Uruguay | June 2020 |
| [Jenkins OSS](https://jenkins.io) | production | [jenkins-infra/charts](https://github.com/jenkins-infra/charts) | * | July 2020 |
| [SettleMint](https://settlemint.com) | production | The SettleMint platform allows enterprises to spin up k8s clusters and deploy production grade blockchain networks and additional services. Helmfile is in charge of deploying these networks and services on demand out of the self service management ui. | Belgium, Singapore, UAE, India | October 2020 |
| [AutoTrader (UK)](https://www.autotrader.co.uk) | production | We've used Helmfile for 2+ years to deploy 400+ services | UK | October 2020 |
| [Trend Micro](https://www.trendmicro.com) | production | We manage 9 k8s clusters in a mono git repository with Helmfile | Taipei, Taiwan | December 2019 |
| [William Hill](https://www.williamhill.com) | production | We have standardised on Helmfile for managing both our application services and our Kubernetes platform components. | International | March 2021 |
| [The Hyve](https://www.thehyve.nl) | production | We've been using Helmfile since 2019 to deploy [Radar-base applications](https://github.com/RADAR-base/RADAR-Kubernetes). [Blog Post](https://www.thehyve.nl/articles/kubernetes-added-to-radar-base) | Utrecht, Netherlands | April 2021 |
<!-- TABLE_END -->
Vendored
+19
View File
@@ -0,0 +1,19 @@
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/focal64"
config.vm.hostname = "minikube.box"
config.vm.provision :shell, privileged: false,
inline: <<-EOS
set -e
sudo apt-get update
sudo apt-get install -y make docker.io
sudo systemctl start docker
sudo usermod -G docker $USER
cd /vagrant/.circleci
make all
EOS
config.vm.provider "virtualbox" do |v|
v.memory = 2048
v.cpus = 2
end
end
+18
View File
@@ -0,0 +1,18 @@
#! /bin/bash
_helmfile_bash_autocomplete() {
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
local cur opts base
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == "-"* ]]; then
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
else
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
fi
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
}
complete -o bashdefault -o default -o nospace -F _helmfile_bash_autocomplete helmfile
+23
View File
@@ -0,0 +1,23 @@
#compdef helmfile
_helmfile_zsh_autocomplete() {
local -a opts
local cur
cur=${words[-1]}
if [[ "$cur" == "-"* ]]; then
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
else
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
fi
if [[ "${opts[1]}" != "" ]]; then
_describe 'values' opts
else
_files
fi
return
}
compdef _helmfile_zsh_autocomplete helmfile
+165 -3
View File
@@ -1,6 +1,9 @@
## Advanced Features
- [Import Configuration Parameters into Helmfile](#import-configuration-parameters-into-helmfile)
- [Deploy Kustomization with Helmfile](#deploy-kustomizations-with-helmfile)
- [Adhoc Kustomization of Helm Charts](#adhoc-kustomization-of-helm-charts)
- [Adding dependencies without forking the chart](#adding-dependencies-without-forking-the-chart)
### Import Configuration Parameters into Helmfile
@@ -35,7 +38,7 @@ $ somehow_generate_chart_yaml ${TMPCHART}/Chart.yaml
$ TMPKUSTOMIZATION=/tmp/sometmpdir2
$ somehow_generate_temp_kustomization_yaml ${TMPKUSTOMIZATION}/kustomization.yaml
$ kustomize build ${TMPKUSTOMIZATION}/kustomization.yaml > ${TMPCHART}/templates/all.yaml
$ kustomize build ${TMPKUSTOMIZATION}/kustomization.yaml > ${TMPCHART}/templates/all.yaml
```
Let's say you have a `helmfile.yaml` that looks like the below:
@@ -57,7 +60,7 @@ bases:
Followed by the below steps:
- Running `kustomize edit set image $IMAGE` for every `$IMAGE` generated from your values.yaml
- Running `kustomize edit set image $IMAGE` for every `$IMAGE` generated from your values.yaml
- Running `kustomize edit set nameprefix $NAMEPREFIX` with the nameprefix specified in your values.yaml
- Running `kustomize edit set namesuffix $NAMESUFFIX` with the namesuffix specified in your values.yaml
- Running `kustomize edit set namespace $NS` with the namespace specified in your values.yaml
@@ -92,7 +95,17 @@ After all, Helmfile just installs the temporary chart like standard charts, whic
Please also see [test/advanced/helmfile.yaml](https://github.com/roboll/helmfile/tree/master/test/advanced/helmfile.yaml) for an example of kustomization support and more.
## Adhoc Customization of Helm charts
### Adhoc Kustomization of Helm charts
With Helmfile's integration with Helmfile, not only deploying Kustomization as a Helm chart, you can kustomize charts before installation.
Currently, Helmfile allows you to set the following fields for kustomizing the chart:
- [`releases[].strategicMergePatches`](#strategicmergepatches)
- `releases[].jsonPatches`
- [`releases[].transformers`](#transformers)
#### `strategicMergePatches`
You can add/update any Kubernetes resource field rendered from a Helm chart by specifying `releases[].strategicMergePatches`:
@@ -135,3 +148,152 @@ Please note that the second `data` field `bar` is coming from the strategic-merg
There's also `releases[].jsonPatches` that works similarly to `strategicMergePatches` but has additional capability to remove fields.
Please also see [test/advanced/helmfile.yaml](https://github.com/roboll/helmfile/tree/master/test/advanced/helmfile.yaml) for an example of patching support and more.
#### `transformers`
You can set `transformers` to apply [Kustomize's transformers](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/configureBuiltinPlugin.md#configuring-the-builtin-plugins-instead).
Each item can be a path to a YAML or go template file, or an embedded transformer declaration as a YAML hash.
It's often used to add common labels and annotations to your resources.
In the below example. we add common annotations and labels every resource rendered from the `aws-load-balancer-controller` chart:
```yaml
releases:
- name: "aws-load-balancer-controller"
namespace: "kube-system"
forceNamespace: "kube-system"
chart: "center/aws/aws-load-balancer-controller"
transformers:
- apiVersion: builtin
kind: AnnotationsTransformer
metadata:
name: notImportantHere
annotations:
area: 51
greeting: take me to your leader
fieldSpecs:
- path: metadata/annotations
create: true
- apiVersion: builtin
kind: LabelTransformer
metadata:
name: notImportantHere
labels:
foo: bar
fieldSpecs:
- path: metadata/labels
create: true
```
As explained earlier, `transformers` can be not only a list of embedded transformers, but also YAML or go template files, or a mix of those three kinds.
```yaml
transformers:
# Embedded transformer
- apiVersion: builtin
kind: AnnotationsTransformer
metadata:
name: notImportantHere
annotations:
area: 51
greeting: take me to your leader
fieldSpecs:
- path: metadata/annotations
create: true
# YAML file
- path/to/transformer.yaml
# Go template
# The same set of template parameters as release values files templates is available.
- path/to/transformer.yaml.gotmpl
```
Please see https://github.com/kubernetes-sigs/kustomize/blob/master/examples/configureBuiltinPlugin.md#configuring-the-builtin-plugins-instead for more information on how to declare transformers.
### Adding dependencies without forking the chart
With Helmfile, you can add chart dependencies to a Helm chart without forking it.
An example `helmfile.yaml` that adds a `stable/envoy` dependency to the release `foo` looks like the below:
```
repositories:
- name: stable
url: https://charts.helm.sh/stable
releases:
- name: foo
chart: ./path/to/foo
dependencies:
- chart: stable/envoy
version: 1.5
```
When Helmfile encounters `releases[].dependencies`, it creates a another temporary chart from `./path/to/foo` and adds the following `dependencies` to the `Chart.yaml`, so that you don't need to fork the chart.
```
dependencies:
- name: envoy
repo: https://charts.helm.sh/stable
condition: envoy.enabled
```
A Helm chart can have two or more dependencies for the same chart with different `alias`es. To give your dependency an `alias`, defien it like you would do in a standard `Chart.yaml`:
```
repositories:
- name: stable
url: https://charts.helm.sh/stable
releases:
- name: foo
chart: ./path/to/foo
dependencies:
- chart: stable/envoy
version: 1.5
alias: bar
- chart: stable/envoy
version: 1.5
alias: baz
```
which will tweaks the temporary chart's `Chart.yaml` to have:
```
dependencies:
- alias: bar
name: envoy
repo: https://charts.helm.sh/stable
condition: bar.enabled
- alias: baz
name: envoy
repo: https://charts.helm.sh/stable
condition: baz.enabled
```
Please see #649 for more context around this feature.
After the support for adhoc dependency to local chart (#1765),
you can even write local file paths relative to `helmfile.yaml` in `chart`:
```
releases:
- name: foo
chart: ./path/to/foo
dependencies:
- chart: ./path/to/bar
```
Internally, Helmfile creates another temporary chart from the local chart `./path/to/foo`, and modifies the chart's `Chart.yaml` dependencies to look like:
```
dependencies:
- alias: bar
name: bar
repo: file:///abs/path/to/bar
condition: bar.enabled
```
Please read https://github.com/roboll/helmfile/issues/1762#issuecomment-816341251 for more details.
+1 -1
View File
@@ -1,6 +1,6 @@
# Secrets
helmfile can handle secrets using [helm-secrets](https://github.com/zendesk/helm-secrets) plugin or using remote secrets storage
helmfile can handle secrets using [helm-secrets](https://github.com/jkroepke/helm-secrets) plugin or using remote secrets storage
(everything that package [vals](https://github.com/variantdev/vals) can handle vault, AWS SSM etc)
This section will describe the second use case.
+63 -7
View File
@@ -2,6 +2,23 @@
This guide covers the Helmfile’s considered patterns for writing advanced helmfiles. It focuses on how helmfile should be structured and executed.
## Helmfile .Values vs Helm .Values
Templating engine of Helmfile uses the same pipeline name `.Values` as Helm, so in some use-cases `.Values` of Helmfile and
Helm can be seen in the same file. To distinguish these two kinds of `.Values`, Helmfile provides an alias `.StateValues`
for its `.Values`.
```
app:
project: {{.Environmont.Name}}-{{.StateValues.project}} # Same as {{.Environmont.Name}}-{{.Values.project}}
{{`
extraEnvVars:
- name: APP_PROJECT
value: {{.Values.app.project}}
`}}
```
## Missing keys and Default values
helmfile tries its best to inform users for noticing potential mistakes.
@@ -14,10 +31,10 @@ That is, the following example let `helmfile` fail when you have no `eventApi.re
{{ .Values.eventApi.replicas | default 1 }}
```
In case it isn't a mistake and you do want to allow missing keys, use the `getOrNil` template function:
In case it isn't a mistake and you do want to allow missing keys, use the `get` template function:
```
{{ .Values | getOrNil "eventApi.replicas" }}
{{ .Values | get "eventApi.replicas" nil }}
```
This result in printing `<no value` in your template, that may or may not result in a failure.
@@ -25,7 +42,7 @@ This result in printing `<no value` in your template, that may or may not result
If you want a kind of default values that is used when a missing key was referenced, use `default` like:
```
{{ .Values | getOrNil "eventApi.replicas" | default 1 }}
{{ .Values | get "eventApi.replicas" 1 }}
```
Now, you get `1` when there is no `eventApi.replicas` defined in environment values.
@@ -91,7 +108,7 @@ releases:
Release Templating supports the following parts of release definition:
- basic fields: `name`, `namespace`, `chart`, `version`
- boolean fields: `installed`, `wait`, `tillerless`, `verify` by the means of additional text
- boolean fields: `installed`, `wait`, `waitForJobs`, `tillerless`, `verify` by the means of additional text
fields designed for templating only: `installedTemplate`, `waitTemplate`, `tillerlessTemplate`, `verifyTemplate`
```yaml
# ...
@@ -135,13 +152,13 @@ Please note, that it is not possible to layer `values` sections. If `values` is
> See **Layering State Template Files** if you're layering templates.
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 which release to be bundled by default.
Use Layering to extract the common parts into a dedicated *library helmfile*s, so that each helmfile becomes DRY.
Let's assume that your `helmfile.yaml` looks like:
```
```yaml
bases:
- environments.yaml
@@ -314,7 +331,7 @@ helmDefaults:
force: true
```
Each go template is rendered in the context where `.Values` is inherited from the previous part.
Each go template is rendered in the context where `.Values` is inherited from the previous part.
So in `mydefaults.yaml.gotmpl`, both `.Values.kubeContext` and `.Values.wait` are valid as they do exist in the environment values inherited from the previous part(=the first part) of your `helmfile.yaml.gotmpl`, and therefore the template is rendered to:
@@ -356,3 +373,42 @@ releases:
repository: "nginx"
tag: "latest"
```
## Re-using environment state in sub-helmfiles
Do you want to decouple the environment state loading from the sub-helmfiles and load it only once?
This example shows how to do this:
```yaml
environments:
stage:
values:
- env/stage.yaml
prod:
values:
- env/prod.yaml
---
helmfiles:
- path: releases/myrelease/helmfile.yaml
values:
- {{ toYaml .Values | nindent 4 }}
# pass the current state values to the sub-helmfile
# add other values to use overlay logic here
```
and `releases/myrelease/helmfile.yaml` is as DRY as
```yaml
releases:
- name: mychart-{{ .Values.myrelease.myname }}
installed: {{ .Values | get "myrelease.enabled" false }}
chart: mychart
version: {{ .Values.myrelease.version }}
labels:
chart: mychart
values:
- values.yaml.gotmpl
# templated values would also inherit the values passed from upstream
```
View File
+1 -1
View File
@@ -26,7 +26,7 @@ You would then start a database migration job by executing:
```console
# Start a database migration for the prod environment
$ RAILS_ENV=prod helmfile sync --selector job=dbmigrator
$ RAILS_ENV=prod helmfile --selector job=dbmigrator sync
# Tail log until you are satisfied
$ kubectl logs -l job=dbmigrator
+29 -24
View File
@@ -4,35 +4,40 @@ go 1.14
require (
github.com/Azure/azure-sdk-for-go v35.0.0+incompatible // indirect
github.com/Masterminds/goutils v1.1.0 // indirect
github.com/Masterminds/semver v1.4.2
github.com/Masterminds/sprig v2.22.0+incompatible
github.com/Masterminds/semver/v3 v3.1.0
github.com/Masterminds/sprig/v3 v3.1.0
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a
github.com/go-test/deep v1.0.3
github.com/golang/mock v1.4.3
github.com/google/go-cmp v0.4.0
github.com/gosuri/uitable v0.0.3
github.com/hashicorp/go-getter v1.3.0
github.com/davecgh/go-spew v1.1.1
github.com/frankban/quicktest v1.11.2 // indirect
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
github.com/go-test/deep v1.0.7
github.com/golang/mock v1.5.0
github.com/google/go-cmp v0.5.5
github.com/gosuri/uitable v0.0.4
github.com/hashicorp/go-getter v1.5.0
github.com/hashicorp/go-retryablehttp v0.6.3 // indirect
github.com/hashicorp/go-version v1.2.0
github.com/hashicorp/go-version v1.2.1
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c // indirect
github.com/huandu/xstrings v1.2.0 // indirect
github.com/imdario/mergo v0.3.9
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/imdario/mergo v0.3.11
github.com/jessevdk/go-flags v1.4.0
github.com/kylelemons/godebug v1.1.0
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/mattn/go-isatty v0.0.12
github.com/pierrec/lz4 v2.3.0+incompatible // indirect
github.com/r3labs/diff v0.0.0-20190801153147-a71de73c46ad
github.com/r3labs/diff v1.1.0
github.com/spf13/cobra v1.1.1
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
github.com/urfave/cli v1.20.0
github.com/variantdev/chartify v0.3.7
github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363
github.com/variantdev/vals v0.4.1-0.20200501114609-9cebe482281c
go.mozilla.org/sops v0.0.0-20190912205235-14a22d7a7060 // indirect
go.uber.org/multierr v1.1.0
go.uber.org/zap v1.9.1
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf // indirect
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
github.com/urfave/cli v1.22.5
github.com/variantdev/chartify v0.8.9
github.com/variantdev/dag v1.0.0
github.com/variantdev/vals v0.14.0
go.uber.org/multierr v1.6.0
go.uber.org/zap v1.16.0
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
gopkg.in/square/go-jose.v2 v2.4.0 // indirect
gopkg.in/yaml.v2 v2.2.4
gopkg.in/yaml.v2 v2.3.0
gotest.tools v2.2.0+incompatible
gotest.tools/v3 v3.0.3-0.20200410202438-4e4a41b7851a
gotest.tools/v3 v3.0.3
k8s.io/apimachinery v0.19.3
)
+422 -555
View File
File diff suppressed because it is too large Load Diff
+298 -41
View File
@@ -5,9 +5,8 @@ import (
"os"
"strings"
"github.com/roboll/helmfile/pkg/app/version"
"github.com/roboll/helmfile/pkg/app"
"github.com/roboll/helmfile/pkg/app/version"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/maputil"
"github.com/roboll/helmfile/pkg/state"
@@ -42,6 +41,7 @@ func main() {
cliApp.Name = "helmfile"
cliApp.Usage = ""
cliApp.Version = version.Version
cliApp.EnableBashCompletion = true
cliApp.Flags = []cli.Flag{
cli.StringFlag{
Name: "helm-binary, b",
@@ -54,7 +54,7 @@ func main() {
},
cli.StringFlag{
Name: "environment, e",
Usage: "specify the environment name. defaults to `default`",
Usage: `specify the environment name. defaults to "default"`,
},
cli.StringSliceFlag{
Name: "state-values-set",
@@ -88,6 +88,10 @@ func main() {
Name: "namespace, n",
Usage: "Set namespace. Uses the namespace set in the context by default, and is available in templates as {{ .Namespace }}",
},
cli.StringFlag{
Name: "chart, c",
Usage: "Set chart. Uses the chart set in release by default, and is available in template as {{ .Chart }}",
},
cli.StringSliceFlag{
Name: "selector, l",
Usage: `Only run using the releases that match labels. Labels can take the form of foo=bar or foo!=bar.
@@ -109,7 +113,7 @@ func main() {
cliApp.Commands = []cli.Command{
{
Name: "deps",
Usage: "update charts based on the contents of requirements.yaml",
Usage: "update charts based on their requirements",
Flags: []cli.Flag{
cli.StringFlag{
Name: "args",
@@ -118,11 +122,11 @@ func main() {
},
cli.BoolFlag{
Name: "skip-repos",
Usage: "skip running `helm repo update` before running `helm dependency build`",
Usage: `skip running "helm repo update" before running "helm dependency build"`,
},
},
Action: action(func(run *app.App, c configImpl) error {
return run.Deps(c)
Action: action(func(a *app.App, c configImpl) error {
return a.Deps(c)
}),
},
{
@@ -135,8 +139,8 @@ func main() {
Usage: "pass args to helm exec",
},
},
Action: action(func(run *app.App, c configImpl) error {
return run.Repos(c)
Action: action(func(a *app.App, c configImpl) error {
return a.Repos(c)
}),
},
{
@@ -162,8 +166,8 @@ func main() {
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
},
},
Action: action(func(run *app.App, c configImpl) error {
return run.DeprecatedSyncCharts(c)
Action: action(func(a *app.App, c configImpl) error {
return a.DeprecatedSyncCharts(c)
}),
},
{
@@ -185,7 +189,7 @@ func main() {
},
cli.BoolFlag{
Name: "skip-deps",
Usage: "skip running `helm repo update` and `helm dependency build`",
Usage: `skip running "helm repo update" and "helm dependency build"`,
},
cli.BoolFlag{
Name: "detailed-exitcode",
@@ -195,10 +199,26 @@ func main() {
Name: "include-tests",
Usage: "enable the diffing of the helm test hooks",
},
cli.BoolTFlag{
Name: "skip-needs",
Usage: `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided. Defaults to true when --include-needs is not provided`,
},
cli.BoolFlag{
Name: "include-needs",
Usage: `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided`,
},
cli.BoolFlag{
Name: "skip-diff-on-install",
Usage: "Skips running helm-diff on releases being newly installed on this apply. Useful when the release manifests are too huge to be reviewed, or it's too time-consuming to diff at all",
},
cli.BoolFlag{
Name: "suppress-secrets",
Usage: "suppress secrets in the output. highly recommended to specify on CI/CD use-cases",
},
cli.BoolFlag{
Name: "show-secrets",
Usage: "do not redact secret values in the output. should be used for debug purpose only",
},
cli.IntFlag{
Name: "concurrency",
Value: 0,
@@ -209,9 +229,14 @@ func main() {
Value: 0,
Usage: "output NUM lines of context around changes",
},
cli.StringFlag{
Name: "output",
Value: "",
Usage: "output format for diff plugin",
},
},
Action: action(func(run *app.App, c configImpl) error {
return run.Diff(c)
Action: action(func(a *app.App, c configImpl) error {
return a.Diff(c)
}),
},
{
@@ -235,6 +260,10 @@ func main() {
Name: "output-dir",
Usage: "output directory to pass to helm template (helm template --output-dir)",
},
cli.StringFlag{
Name: "output-dir-template",
Usage: "go text template for generating the output directory. Default: {{ .OutputDir }}/{{ .State.BaseName }}-{{ .State.AbsPathSHA1 }}-{{ .Release.Name}}",
},
cli.IntFlag{
Name: "concurrency",
Value: 0,
@@ -244,13 +273,55 @@ func main() {
Name: "validate",
Usage: "validate your manifests against the Kubernetes cluster you are currently pointing at",
},
cli.BoolFlag{
Name: "include-crds",
Usage: "include CRDs in the templated output",
},
cli.BoolFlag{
Name: "include-needs",
Usage: `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided`,
},
cli.BoolFlag{
Name: "skip-deps",
Usage: "skip running `helm repo update` and `helm dependency build`",
Usage: `skip running "helm repo update" and "helm dependency build"`,
},
cli.BoolFlag{
Name: "skip-cleanup",
Usage: "Stop cleaning up temporary values generated by helmfile and helm-secrets. Useful for debugging. Don't use in production for security",
},
},
Action: action(func(run *app.App, c configImpl) error {
return run.Template(c)
Action: action(func(a *app.App, c configImpl) error {
return a.Template(c)
}),
},
{
Name: "write-values",
Usage: "write values files for releases. Similar to `helmfile template`, write values files instead of manifests.",
Flags: []cli.Flag{
cli.StringSliceFlag{
Name: "set",
Usage: "additional values to be merged into the command",
},
cli.StringSliceFlag{
Name: "values",
Usage: "additional value files to be merged into the command",
},
cli.StringFlag{
Name: "output-file-template",
Usage: "go text template for generating the output file. Default: {{ .State.BaseName }}-{{ .State.AbsPathSHA1 }}/{{ .Release.Name}}.yaml",
},
cli.IntFlag{
Name: "concurrency",
Value: 0,
Usage: "maximum number of concurrent downloads of release charts",
},
cli.BoolFlag{
Name: "skip-deps",
Usage: `skip running "helm repo update" and "helm dependency build"`,
},
},
Action: action(func(a *app.App, c configImpl) error {
return a.WriteValues(c)
}),
},
{
@@ -277,11 +348,33 @@ func main() {
},
cli.BoolFlag{
Name: "skip-deps",
Usage: "skip running `helm repo update` and `helm dependency build`",
Usage: `skip running "helm repo update" and "helm dependency build"`,
},
},
Action: action(func(run *app.App, c configImpl) error {
return run.Lint(c)
Action: action(func(a *app.App, c configImpl) error {
return a.Lint(c)
}),
},
{
Name: "fetch",
Usage: "fetch charts from state file",
Flags: []cli.Flag{
cli.IntFlag{
Name: "concurrency",
Value: 0,
Usage: "maximum number of concurrent downloads of release charts",
},
cli.BoolFlag{
Name: "skip-deps",
Usage: `skip running "helm repo update" and "helm dependency build"`,
},
cli.StringFlag{
Name: "output-dir",
Usage: "directory to store charts (default: temporary directory which is deleted when the command terminates)",
},
},
Action: action(func(a *app.App, c configImpl) error {
return a.Fetch(c)
}),
},
{
@@ -306,13 +399,33 @@ func main() {
Value: "",
Usage: "pass args to helm exec",
},
cli.BoolFlag{
Name: "skip-crds",
Usage: "if set, no CRDs will be installed on sync. By default, CRDs are installed if not already present",
},
cli.BoolFlag{
Name: "skip-deps",
Usage: "skip running `helm repo update` and `helm dependency build`",
Usage: `skip running "helm repo update" and "helm dependency build"`,
},
cli.BoolTFlag{
Name: "skip-needs",
Usage: `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided. Defaults to true when --include-needs is not provided`,
},
cli.BoolFlag{
Name: "include-needs",
Usage: `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided`,
},
cli.BoolFlag{
Name: "wait",
Usage: `Override helmDefaults.wait setting "helm upgrade --install --wait"`,
},
cli.BoolFlag{
Name: "wait-for-jobs",
Usage: `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`,
},
},
Action: action(func(run *app.App, c configImpl) error {
return run.Sync(c)
Action: action(func(a *app.App, c configImpl) error {
return a.Sync(c)
}),
},
{
@@ -337,6 +450,11 @@ func main() {
Value: 0,
Usage: "output NUM lines of context around changes",
},
cli.StringFlag{
Name: "output",
Value: "",
Usage: "output format for diff plugin",
},
cli.BoolFlag{
Name: "detailed-exitcode",
Usage: "return a non-zero exit code 2 instead of 0 when there were changes detected AND the changes are synced successfully",
@@ -348,7 +466,27 @@ func main() {
},
cli.BoolFlag{
Name: "retain-values-files",
Usage: "Stop cleaning up values files passed to Helm. Together with --log-level=debug, you can manually rerun helm commands as Helmfile did for debugging purpose",
Usage: "DEPRECATED: Use skip-cleanup instead",
},
cli.BoolFlag{
Name: "skip-cleanup",
Usage: "Stop cleaning up temporary values generated by helmfile and helm-secrets. Useful for debugging. Don't use in production for security",
},
cli.BoolFlag{
Name: "skip-crds",
Usage: "if set, no CRDs will be installed on sync. By default, CRDs are installed if not already present",
},
cli.BoolTFlag{
Name: "skip-needs",
Usage: `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided. Defaults to true when --include-needs is not provided`,
},
cli.BoolFlag{
Name: "include-needs",
Usage: `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided`,
},
cli.BoolFlag{
Name: "skip-diff-on-install",
Usage: "Skips running helm-diff on releases being newly installed on this apply. Useful when the release manifests are too huge to be reviewed, or it's too time-consuming to diff at all",
},
cli.BoolFlag{
Name: "include-tests",
@@ -358,17 +496,29 @@ func main() {
Name: "suppress-secrets",
Usage: "suppress secrets in the diff output. highly recommended to specify on CI/CD use-cases",
},
cli.BoolFlag{
Name: "show-secrets",
Usage: "do not redact secret values in the diff output. should be used for debug purpose only",
},
cli.BoolFlag{
Name: "suppress-diff",
Usage: "suppress diff in the output. Usable in new installs",
},
cli.BoolFlag{
Name: "skip-deps",
Usage: "skip running `helm repo update` and `helm dependency build`",
Usage: `skip running "helm repo update" and "helm dependency build"`,
},
cli.BoolFlag{
Name: "wait",
Usage: `Override helmDefaults.wait setting "helm upgrade --install --wait"`,
},
cli.BoolFlag{
Name: "wait-for-jobs",
Usage: `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`,
},
},
Action: action(func(run *app.App, c configImpl) error {
return run.Apply(c)
Action: action(func(a *app.App, c configImpl) error {
return a.Apply(c)
}),
},
{
@@ -386,8 +536,8 @@ func main() {
Usage: "pass args to helm exec",
},
},
Action: action(func(run *app.App, c configImpl) error {
return run.Status(c)
Action: action(func(a *app.App, c configImpl) error {
return a.Status(c)
}),
},
{
@@ -408,9 +558,13 @@ func main() {
Name: "purge",
Usage: "purge releases i.e. free release names and histories",
},
cli.BoolFlag{
Name: "skip-deps",
Usage: `skip running "helm repo update" and "helm dependency build"`,
},
},
Action: action(func(run *app.App, c configImpl) error {
return run.Delete(c)
Action: action(func(a *app.App, c configImpl) error {
return a.Delete(c)
}),
},
{
@@ -427,9 +581,13 @@ func main() {
Value: "",
Usage: "pass args to helm exec",
},
cli.BoolFlag{
Name: "skip-deps",
Usage: `skip running "helm repo update" and "helm dependency build"`,
},
},
Action: action(func(run *app.App, c configImpl) error {
return run.Destroy(c)
Action: action(func(a *app.App, c configImpl) error {
return a.Destroy(c)
}),
},
{
@@ -440,6 +598,10 @@ func main() {
Name: "cleanup",
Usage: "delete test pods upon completion",
},
cli.BoolFlag{
Name: "logs",
Usage: "Dump the logs from test pods (this runs after all tests are complete, but before any cleanup)",
},
cli.StringFlag{
Name: "args",
Value: "",
@@ -455,17 +617,26 @@ func main() {
Value: 0,
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
},
cli.BoolFlag{
Name: "skip-deps",
Usage: `skip running "helm repo update" and "helm dependency build"`,
},
},
Action: action(func(run *app.App, c configImpl) error {
return run.Test(c)
Action: action(func(a *app.App, c configImpl) error {
return a.Test(c)
}),
},
{
Name: "build",
Usage: "output compiled helmfile state(s) as YAML",
Flags: []cli.Flag{},
Action: action(func(run *app.App, c configImpl) error {
return run.PrintState(c)
Flags: []cli.Flag{
cli.BoolFlag{
Name: "embed-values",
Usage: "Read all the values files for every release and embed into the output helmfile.yaml",
},
},
Action: action(func(a *app.App, c configImpl) error {
return a.PrintState(c)
}),
},
{
@@ -477,11 +648,24 @@ func main() {
Value: "",
Usage: "output releases list as a json string",
},
cli.BoolFlag{
Name: "keep-temp-dir",
Usage: "Keep temporary directory",
},
},
Action: action(func(run *app.App, c configImpl) error {
return run.ListReleases(c)
Action: action(func(a *app.App, c configImpl) error {
return a.ListReleases(c)
}),
},
{
Name: "version",
Usage: "Show the version for Helmfile.",
ArgsUsage: "[command]",
Action: func(c *cli.Context) error {
cli.ShowVersion(c)
return nil
},
},
}
err := cliApp.Run(os.Args)
@@ -534,6 +718,14 @@ func (c configImpl) SkipRepos() bool {
return c.c.Bool("skip-repos")
}
func (c configImpl) Wait() bool {
return c.c.Bool("wait")
}
func (c configImpl) WaitForJobs() bool {
return c.c.Bool("wait-for-jobs")
}
func (c configImpl) Values() []string {
return c.c.StringSlice("values")
}
@@ -552,6 +744,14 @@ func (c configImpl) OutputDir() string {
return c.c.String("output-dir")
}
func (c configImpl) OutputDirTemplate() string {
return c.c.String("output-dir-template")
}
func (c configImpl) OutputFileTemplate() string {
return c.c.String("output-file-template")
}
func (c configImpl) Validate() bool {
return c.c.Bool("validate")
}
@@ -564,6 +764,18 @@ func (c configImpl) HasCommandName(name string) bool {
return c.c.Command.HasName(name)
}
func (c configImpl) SkipNeeds() bool {
if !c.IncludeNeeds() {
return c.c.Bool("skip-needs")
}
return false
}
func (c configImpl) IncludeNeeds() bool {
return c.c.Bool("include-needs")
}
// DiffConfig
func (c configImpl) SkipDeps() bool {
@@ -586,6 +798,10 @@ func (c configImpl) SuppressSecrets() bool {
return c.c.Bool("suppress-secrets")
}
func (c configImpl) ShowSecrets() bool {
return c.c.Bool("show-secrets")
}
func (c configImpl) SuppressDiff() bool {
return c.c.Bool("suppress-diff")
}
@@ -602,6 +818,10 @@ func (c configImpl) Cleanup() bool {
return c.c.Bool("cleanup")
}
func (c configImpl) Logs() bool {
return c.c.Bool("logs")
}
func (c configImpl) Timeout() int {
if !c.c.IsSet("timeout") {
return state.EmptyTimeout
@@ -615,6 +835,10 @@ func (c configImpl) Output() string {
return c.c.String("output")
}
func (c configImpl) KeepTempDir() bool {
return c.c.Bool("keep-temp-dir")
}
// GlobalConfig
func (c configImpl) HelmBinary() string {
@@ -629,6 +853,10 @@ func (c configImpl) Namespace() string {
return c.c.GlobalString("namespace")
}
func (c configImpl) Chart() string {
return c.c.GlobalString("chart")
}
func (c configImpl) FileOrDir() string {
return c.c.GlobalString("file")
}
@@ -657,6 +885,30 @@ func (c configImpl) Context() int {
return c.c.Int("context")
}
func (c configImpl) DiffOutput() string {
return c.c.String("output")
}
func (c configImpl) SkipCleanup() bool {
return c.c.Bool("skip-cleanup")
}
func (c configImpl) SkipCRDs() bool {
return c.c.Bool("skip-crds")
}
func (c configImpl) SkipDiffOnInstall() bool {
return c.c.Bool("skip-diff-on-install")
}
func (c configImpl) EmbedValues() bool {
return c.c.Bool("embed-values")
}
func (c configImpl) IncludeCRDs() bool {
return c.c.Bool("include-crds")
}
func (c configImpl) Logger() *zap.SugaredLogger {
return c.c.App.Metadata["logger"].(*zap.SugaredLogger)
}
@@ -664,7 +916,10 @@ func (c configImpl) Logger() *zap.SugaredLogger {
func (c configImpl) Env() string {
env := c.c.GlobalString("environment")
if env == "" {
env = state.DefaultEnv
env = os.Getenv("HELMFILE_ENVIRONMENT")
if env == "" {
env = state.DefaultEnv
}
}
return env
}
@@ -695,6 +950,8 @@ func toCliError(c *cli.Context, err error) error {
noMatchingExitCode = 0
}
return cli.NewExitError(e.Error(), noMatchingExitCode)
case *app.MultiError:
return cli.NewExitError(e.Error(), 1)
case *app.Error:
return cli.NewExitError(e.Error(), e.Code())
default:
+801 -201
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+790
View File
@@ -0,0 +1,790 @@
package app
import (
"bufio"
"bytes"
"io"
"path/filepath"
"sync"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/roboll/helmfile/pkg/exectest"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/testhelper"
"github.com/variantdev/vals"
)
func TestSync(t *testing.T) {
type fields struct {
skipNeeds bool
includeNeeds bool
}
type testcase struct {
fields fields
ns string
concurrency int
skipDiffOnInstall bool
error string
files map[string]string
selectors []string
lists map[exectest.ListKey]string
upgraded []exectest.Release
deleted []exectest.Release
log string
}
check := func(t *testing.T, tc testcase) {
t.Helper()
wantUpgrades := tc.upgraded
wantDeletes := tc.deleted
var helm = &exectest.Helm{
FailOnUnexpectedList: true,
FailOnUnexpectedDiff: true,
Lists: tc.lists,
DiffMutex: &sync.Mutex{},
ChartsMutex: &sync.Mutex{},
ReleasesMutex: &sync.Mutex{},
}
bs := &bytes.Buffer{}
func() {
t.Helper()
logReader, logWriter := io.Pipe()
logFlushed := &sync.WaitGroup{}
// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
logFlushed.Add(1)
go func() {
scanner := bufio.NewScanner(logReader)
for scanner.Scan() {
bs.Write(scanner.Bytes())
bs.WriteString("\n")
}
logFlushed.Done()
}()
defer func() {
// This is here to avoid data-trace on bytes buffer `bs` to capture logs
if err := logWriter.Close(); err != nil {
panic(err)
}
logFlushed.Wait()
}()
logger := helmexec.NewLogger(logWriter, "debug")
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
if err != nil {
t.Errorf("unexpected error creating vals runtime: %v", err)
}
app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob,
abs: filepath.Abs,
OverrideKubeContext: "default",
Env: "default",
Logger: logger,
helms: map[helmKey]helmexec.Interface{
createHelmKey("helm", "default"): helm,
},
valsRuntime: valsRuntime,
}, tc.files)
if tc.ns != "" {
app.Namespace = tc.ns
}
if tc.selectors != nil {
app.Selectors = tc.selectors
}
syncErr := app.Sync(applyConfig{
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
concurrency: tc.concurrency,
logger: logger,
skipDiffOnInstall: tc.skipDiffOnInstall,
skipNeeds: tc.fields.skipNeeds,
includeNeeds: tc.fields.includeNeeds,
})
var gotErr string
if syncErr != nil {
gotErr = syncErr.Error()
}
if d := cmp.Diff(tc.error, gotErr); d != "" {
t.Fatalf("unexpected error: want (-), got (+): %s", d)
}
if len(wantUpgrades) > len(helm.Releases) {
t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades))
}
for relIdx := range wantUpgrades {
if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name {
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name)
}
for flagIdx := range wantUpgrades[relIdx].Flags {
if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] {
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx])
}
}
}
if len(wantDeletes) > len(helm.Deleted) {
t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes))
}
for relIdx := range wantDeletes {
if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name {
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name)
}
for flagIdx := range wantDeletes[relIdx].Flags {
if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
}
}
}
}()
if tc.log != "" {
actual := bs.String()
diff, exists := testhelper.Diff(tc.log, actual, 3)
if exists {
t.Errorf("unexpected log:\nDIFF\n%s\nEOD", diff)
}
}
}
t.Run("skip-needs=true", func(t *testing.T) {
check(t, testcase{
fields: fields{
skipNeeds: true,
},
files: map[string]string{
"/path/to/helmfile.yaml": `
{{ $mark := "a" }}
releases:
- name: kubernetes-external-secrets
chart: incubator/raw
namespace: kube-system
- name: external-secrets
chart: incubator/raw
namespace: default
labels:
app: test
needs:
- kube-system/kubernetes-external-secrets
- name: my-release
chart: incubator/raw
namespace: default
labels:
app: test
needs:
- default/external-secrets
`,
},
selectors: []string{"app=test"},
upgraded: []exectest.Release{
{Name: "external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "default"}},
{Name: "my-release", Flags: []string{"--kube-context", "default", "--namespace", "default"}},
},
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
concurrency: 1,
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile.yaml.part.0":
0:
1:
2:
3: releases:
4: - name: kubernetes-external-secrets
5: chart: incubator/raw
6: namespace: kube-system
7:
8: - name: external-secrets
9: chart: incubator/raw
10: namespace: default
11: labels:
12: app: test
13: needs:
14: - kube-system/kubernetes-external-secrets
15:
16: - name: my-release
17: chart: incubator/raw
18: namespace: default
19: labels:
20: app: test
21: needs:
22: - default/external-secrets
23:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile.yaml.part.0":
0:
1:
2:
3: releases:
4: - name: kubernetes-external-secrets
5: chart: incubator/raw
6: namespace: kube-system
7:
8: - name: external-secrets
9: chart: incubator/raw
10: namespace: default
11: labels:
12: app: test
13: needs:
14: - kube-system/kubernetes-external-secrets
15:
16: - name: my-release
17: chart: incubator/raw
18: namespace: default
19: labels:
20: app: test
21: needs:
22: - default/external-secrets
23:
merged environment: &{default map[] map[]}
2 release(s) matching app=test found in helmfile.yaml
Affected releases are:
external-secrets (incubator/raw) UPDATED
my-release (incubator/raw) UPDATED
processing 2 groups of releases in this order:
GROUP RELEASES
1 default/default/external-secrets
2 default/default/my-release
processing releases in group 1/2: default/default/external-secrets
getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
processing releases in group 2/2: default/default/my-release
getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
UPDATED RELEASES:
NAME CHART VERSION
external-secrets incubator/raw
my-release incubator/raw
`,
})
})
t.Run("skip-needs=false include-needs=true", func(t *testing.T) {
check(t, testcase{
fields: fields{
skipNeeds: false,
includeNeeds: true,
},
error: ``,
files: map[string]string{
"/path/to/helmfile.yaml": `
{{ $mark := "a" }}
releases:
- name: kubernetes-external-secrets
chart: incubator/raw
namespace: kube-system
- name: external-secrets
chart: incubator/raw
namespace: default
labels:
app: test
needs:
- kube-system/kubernetes-external-secrets
- name: my-release
chart: incubator/raw
namespace: default
labels:
app: test
needs:
- default/external-secrets
`,
},
selectors: []string{"app=test"},
upgraded: []exectest.Release{},
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
concurrency: 1,
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile.yaml.part.0":
0:
1:
2:
3: releases:
4: - name: kubernetes-external-secrets
5: chart: incubator/raw
6: namespace: kube-system
7:
8: - name: external-secrets
9: chart: incubator/raw
10: namespace: default
11: labels:
12: app: test
13: needs:
14: - kube-system/kubernetes-external-secrets
15:
16: - name: my-release
17: chart: incubator/raw
18: namespace: default
19: labels:
20: app: test
21: needs:
22: - default/external-secrets
23:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile.yaml.part.0":
0:
1:
2:
3: releases:
4: - name: kubernetes-external-secrets
5: chart: incubator/raw
6: namespace: kube-system
7:
8: - name: external-secrets
9: chart: incubator/raw
10: namespace: default
11: labels:
12: app: test
13: needs:
14: - kube-system/kubernetes-external-secrets
15:
16: - name: my-release
17: chart: incubator/raw
18: namespace: default
19: labels:
20: app: test
21: needs:
22: - default/external-secrets
23:
merged environment: &{default map[] map[]}
2 release(s) matching app=test found in helmfile.yaml
Affected releases are:
external-secrets (incubator/raw) UPDATED
kubernetes-external-secrets (incubator/raw) UPDATED
my-release (incubator/raw) UPDATED
processing 3 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/kubernetes-external-secrets
2 default/default/external-secrets
3 default/default/my-release
processing releases in group 1/3: default/kube-system/kubernetes-external-secrets
getting deployed release version failed:unexpected list key: {^kubernetes-external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
processing releases in group 2/3: default/default/external-secrets
getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
processing releases in group 3/3: default/default/my-release
getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
UPDATED RELEASES:
NAME CHART VERSION
kubernetes-external-secrets incubator/raw
external-secrets incubator/raw
my-release incubator/raw
`,
})
})
t.Run("skip-needs=false include-needs=true with installed but disabled release", func(t *testing.T) {
check(t, testcase{
fields: fields{
skipNeeds: false,
includeNeeds: true,
},
error: ``,
files: map[string]string{
"/path/to/helmfile.yaml": `
{{ $mark := "a" }}
releases:
- name: kubernetes-external-secrets
chart: incubator/raw
namespace: kube-system
installed: false
- name: external-secrets
chart: incubator/raw
namespace: default
labels:
app: test
needs:
- kube-system/kubernetes-external-secrets
- name: my-release
chart: incubator/raw
namespace: default
labels:
app: test
needs:
- default/external-secrets
`,
},
selectors: []string{"app=test"},
upgraded: []exectest.Release{},
lists: map[exectest.ListKey]string{
// delete frontend-v1 and backend-v1
exectest.ListKey{Filter: "^kubernetes-external-secrets$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
^kubernetes-external-secrets$ 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
`,
},
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
concurrency: 1,
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile.yaml.part.0":
0:
1:
2:
3: releases:
4: - name: kubernetes-external-secrets
5: chart: incubator/raw
6: namespace: kube-system
7: installed: false
8:
9: - name: external-secrets
10: chart: incubator/raw
11: namespace: default
12: labels:
13: app: test
14: needs:
15: - kube-system/kubernetes-external-secrets
16:
17: - name: my-release
18: chart: incubator/raw
19: namespace: default
20: labels:
21: app: test
22: needs:
23: - default/external-secrets
24:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile.yaml.part.0":
0:
1:
2:
3: releases:
4: - name: kubernetes-external-secrets
5: chart: incubator/raw
6: namespace: kube-system
7: installed: false
8:
9: - name: external-secrets
10: chart: incubator/raw
11: namespace: default
12: labels:
13: app: test
14: needs:
15: - kube-system/kubernetes-external-secrets
16:
17: - name: my-release
18: chart: incubator/raw
19: namespace: default
20: labels:
21: app: test
22: needs:
23: - default/external-secrets
24:
merged environment: &{default map[] map[]}
2 release(s) matching app=test found in helmfile.yaml
Affected releases are:
external-secrets (incubator/raw) UPDATED
kubernetes-external-secrets (incubator/raw) DELETED
my-release (incubator/raw) UPDATED
processing 1 groups of releases in this order:
GROUP RELEASES
1 default/kube-system/kubernetes-external-secrets
processing releases in group 1/1: default/kube-system/kubernetes-external-secrets
processing 2 groups of releases in this order:
GROUP RELEASES
1 default/default/external-secrets
2 default/default/my-release
processing releases in group 1/2: default/default/external-secrets
getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
processing releases in group 2/2: default/default/my-release
getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
UPDATED RELEASES:
NAME CHART VERSION
external-secrets incubator/raw
my-release incubator/raw
DELETED RELEASES:
NAME
kubernetes-external-secrets
`,
})
})
t.Run("skip-needs=false include-needs=true with not installed and disabled release", func(t *testing.T) {
check(t, testcase{
fields: fields{
skipNeeds: false,
includeNeeds: true,
},
error: ``,
files: map[string]string{
"/path/to/helmfile.yaml": `
{{ $mark := "a" }}
releases:
- name: kubernetes-external-secrets
chart: incubator/raw
namespace: kube-system
installed: false
- name: external-secrets
chart: incubator/raw
namespace: default
labels:
app: test
needs:
- kube-system/kubernetes-external-secrets
- name: my-release
chart: incubator/raw
namespace: default
labels:
app: test
needs:
- default/external-secrets
`,
},
selectors: []string{"app=test"},
upgraded: []exectest.Release{},
lists: map[exectest.ListKey]string{
// delete frontend-v1 and backend-v1
exectest.ListKey{Filter: "^kubernetes-external-secrets$", Flags: helmV2ListFlags}: ``,
},
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
concurrency: 1,
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile.yaml.part.0":
0:
1:
2:
3: releases:
4: - name: kubernetes-external-secrets
5: chart: incubator/raw
6: namespace: kube-system
7: installed: false
8:
9: - name: external-secrets
10: chart: incubator/raw
11: namespace: default
12: labels:
13: app: test
14: needs:
15: - kube-system/kubernetes-external-secrets
16:
17: - name: my-release
18: chart: incubator/raw
19: namespace: default
20: labels:
21: app: test
22: needs:
23: - default/external-secrets
24:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile.yaml.part.0":
0:
1:
2:
3: releases:
4: - name: kubernetes-external-secrets
5: chart: incubator/raw
6: namespace: kube-system
7: installed: false
8:
9: - name: external-secrets
10: chart: incubator/raw
11: namespace: default
12: labels:
13: app: test
14: needs:
15: - kube-system/kubernetes-external-secrets
16:
17: - name: my-release
18: chart: incubator/raw
19: namespace: default
20: labels:
21: app: test
22: needs:
23: - default/external-secrets
24:
merged environment: &{default map[] map[]}
2 release(s) matching app=test found in helmfile.yaml
Affected releases are:
external-secrets (incubator/raw) UPDATED
my-release (incubator/raw) UPDATED
processing 2 groups of releases in this order:
GROUP RELEASES
1 default/default/external-secrets
2 default/default/my-release
processing releases in group 1/2: default/default/external-secrets
getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
processing releases in group 2/2: default/default/my-release
getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
UPDATED RELEASES:
NAME CHART VERSION
external-secrets incubator/raw
my-release incubator/raw
`,
})
})
t.Run("bad --selector", func(t *testing.T) {
check(t, testcase{
files: map[string]string{
"/path/to/helmfile.yaml": `
{{ $mark := "a" }}
releases:
- name: kubernetes-external-secrets
chart: incubator/raw
namespace: kube-system
- name: external-secrets
chart: incubator/raw
namespace: default
labels:
app: test
needs:
- kube-system/kubernetes-external-secrets
- name: my-release
chart: incubator/raw
namespace: default
labels:
app: test
needs:
- default/external-secrets
`,
},
selectors: []string{"app=test_non_existent"},
upgraded: []exectest.Release{},
error: "err: no releases found that matches specified selector(app=test_non_existent) and environment(default), in any helmfile",
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
concurrency: 1,
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile.yaml.part.0":
0:
1:
2:
3: releases:
4: - name: kubernetes-external-secrets
5: chart: incubator/raw
6: namespace: kube-system
7:
8: - name: external-secrets
9: chart: incubator/raw
10: namespace: default
11: labels:
12: app: test
13: needs:
14: - kube-system/kubernetes-external-secrets
15:
16: - name: my-release
17: chart: incubator/raw
18: namespace: default
19: labels:
20: app: test
21: needs:
22: - default/external-secrets
23:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile.yaml.part.0":
0:
1:
2:
3: releases:
4: - name: kubernetes-external-secrets
5: chart: incubator/raw
6: namespace: kube-system
7:
8: - name: external-secrets
9: chart: incubator/raw
10: namespace: default
11: labels:
12: app: test
13: needs:
14: - kube-system/kubernetes-external-secrets
15:
16: - name: my-release
17: chart: incubator/raw
18: namespace: default
19: labels:
20: app: test
21: needs:
22: - default/external-secrets
23:
merged environment: &{default map[] map[]}
0 release(s) matching app=test_non_existent found in helmfile.yaml
`,
})
})
}
+1023 -319
View File
File diff suppressed because it is too large Load Diff
+48
View File
@@ -9,6 +9,7 @@ type ConfigProvider interface {
FileOrDir() string
KubeContext() string
Namespace() string
Chart() string
Selectors() []string
StateValuesSet() map[string]interface{}
StateValuesFiles() []string
@@ -38,19 +39,29 @@ type ApplyConfigProvider interface {
Values() []string
Set() []string
SkipCRDs() bool
SkipDeps() bool
Wait() bool
WaitForJobs() bool
IncludeTests() bool
SuppressSecrets() bool
ShowSecrets() bool
SuppressDiff() bool
DetailedExitcode() bool
NoColor() bool
Context() int
DiffOutput() string
RetainValuesFiles() bool
SkipCleanup() bool
SkipDiffOnInstall() bool
SkipNeeds() bool
IncludeNeeds() bool
concurrencyConfig
interactive
@@ -62,7 +73,13 @@ type SyncConfigProvider interface {
Values() []string
Set() []string
SkipCRDs() bool
SkipDeps() bool
Wait() bool
WaitForJobs() bool
SkipNeeds() bool
IncludeNeeds() bool
concurrencyConfig
loggingConfig
@@ -73,16 +90,23 @@ type DiffConfigProvider interface {
Values() []string
Set() []string
SkipCRDs() bool
SkipDeps() bool
IncludeTests() bool
SuppressSecrets() bool
ShowSecrets() bool
SuppressDiff() bool
SkipDiffOnInstall() bool
SkipNeeds() bool
IncludeNeeds() bool
DetailedExitcode() bool
NoColor() bool
Context() int
DiffOutput() string
concurrencyConfig
}
@@ -91,6 +115,7 @@ type DeleteConfigProvider interface {
Args() string
Purge() bool
SkipDeps() bool
interactive
loggingConfig
@@ -100,6 +125,8 @@ type DeleteConfigProvider interface {
type DestroyConfigProvider interface {
Args() string
SkipDeps() bool
interactive
loggingConfig
concurrencyConfig
@@ -108,8 +135,10 @@ type DestroyConfigProvider interface {
type TestConfigProvider interface {
Args() string
SkipDeps() bool
Timeout() int
Cleanup() bool
Logs() bool
concurrencyConfig
}
@@ -124,18 +153,36 @@ type LintConfigProvider interface {
concurrencyConfig
}
type FetchConfigProvider interface {
SkipDeps() bool
OutputDir() string
concurrencyConfig
}
type TemplateConfigProvider interface {
Args() string
Values() []string
Set() []string
OutputDirTemplate() string
Validate() bool
SkipDeps() bool
SkipCleanup() bool
OutputDir() string
IncludeCRDs() bool
IncludeNeeds() bool
concurrencyConfig
}
type WriteValuesConfigProvider interface {
Values() []string
Set() []string
OutputFileTemplate() string
SkipDeps() bool
}
type StatusesConfigProvider interface {
Args() string
@@ -143,6 +190,7 @@ type StatusesConfigProvider interface {
}
type StateConfigProvider interface {
EmbedValues() bool
}
type concurrencyConfig interface {
+16 -24
View File
@@ -5,40 +5,32 @@ import (
)
type Context struct {
updatedRepos map[string]struct{}
updatedRepos map[string]bool
updatedReposV2 map[string]bool
}
func NewContext() Context {
return Context{
updatedRepos: map[string]struct{}{},
updatedRepos: map[string]bool{},
updatedReposV2: map[string]bool{},
}
}
func (ctx Context) SyncReposOnce(st *state.HelmState, helm state.RepoUpdater) []error {
var errs []error
func (ctx Context) SyncReposOnce(st *state.HelmState, helm state.RepoUpdater) error {
var (
updated []string
err error
)
hasInstalled := false
for _, release := range st.Releases {
hasInstalled = hasInstalled || release.Installed == nil || *release.Installed
if helm.IsHelm3() {
updated, err = st.SyncRepos(helm, ctx.updatedRepos)
} else {
updated, err = st.SyncRepos(helm, ctx.updatedReposV2)
}
if !hasInstalled {
return errs
for _, r := range updated {
ctx.updatedRepos[r] = true
}
allUpdated := true
for _, r := range st.Repositories {
_, exists := ctx.updatedRepos[r.Name]
allUpdated = allUpdated && exists
}
if !allUpdated {
errs = st.SyncRepos(helm)
for _, r := range st.Repositories {
ctx.updatedRepos[r.Name] = struct{}{}
}
}
return errs
return err
}
+30 -17
View File
@@ -4,12 +4,12 @@ import (
"bytes"
"errors"
"fmt"
"github.com/roboll/helmfile/pkg/helmexec"
"path/filepath"
"sort"
"github.com/imdario/mergo"
"github.com/roboll/helmfile/pkg/environment"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/remote"
"github.com/roboll/helmfile/pkg/state"
"github.com/variantdev/vals"
"go.uber.org/zap"
@@ -25,13 +25,17 @@ type desiredStateLoader struct {
env string
namespace string
chart string
readFile func(string) ([]byte, error)
fileExists func(string) (bool, error)
abs func(string) (string, error)
glob func(string) ([]string, error)
getHelm func(*state.HelmState) helmexec.Interface
readFile func(string) ([]byte, error)
deleteFile func(string) error
fileExists func(string) (bool, error)
abs func(string) (string, error)
glob func(string) ([]string, error)
directoryExistsAt func(string) bool
getHelm func(*state.HelmState) helmexec.Interface
remote *remote.Remote
logger *zap.SugaredLogger
valsRuntime vals.Evaluator
}
@@ -46,7 +50,7 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e
return nil, fmt.Errorf("bug: opts.CalleePath was nil: f=%s, opts=%v", f, opts)
}
storage := state.NewStorage(opts.CalleePath, ld.logger, ld.glob)
envld := state.NewEnvironmentValuesLoader(storage, ld.readFile, ld.logger)
envld := state.NewEnvironmentValuesLoader(storage, ld.readFile, ld.logger, ld.remote)
handler := state.MissingFileHandlerError
vals, err := envld.LoadEnvironmentValues(&handler, args)
if err != nil {
@@ -65,17 +69,16 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e
}
if opts.Reverse {
rev := func(i, j int) bool {
return j < i
}
sort.Slice(st.Releases, rev)
sort.Slice(st.Helmfiles, rev)
st.Reverse()
}
if ld.overrideKubeContext != "" {
if st.HelmDefaults.KubeContext != "" {
return nil, errors.New("err: Cannot use option --kube-context and set attribute helmDefaults.kubeContext.")
if st.OverrideKubeContext != "" {
return nil, errors.New("err: Cannot use option --kube-context and set attribute kubeContext.")
}
st.OverrideKubeContext = ld.overrideKubeContext
// HelmDefaults.KubeContext is also overriden in here
// to set default release value properly.
st.HelmDefaults.KubeContext = ld.overrideKubeContext
}
@@ -86,6 +89,13 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e
st.OverrideNamespace = ld.namespace
}
if ld.chart != "" {
if st.OverrideChart != "" {
return nil, errors.New("err: Cannot use option --chart and set attribute chart.")
}
st.OverrideChart = ld.chart
}
return st, nil
}
@@ -147,7 +157,8 @@ func (ld *desiredStateLoader) loadFileWithOverrides(inheritedEnv, overrodeEnv *e
}
func (a *desiredStateLoader) underlying() *state.StateCreator {
c := state.NewCreator(a.logger, a.readFile, a.fileExists, a.abs, a.glob, a.valsRuntime, a.getHelm, a.overrideHelmBinary)
c := state.NewCreator(a.logger, a.readFile, a.fileExists, a.abs, a.glob, a.directoryExistsAt, a.valsRuntime, a.getHelm, a.overrideHelmBinary, a.remote)
c.DeleteFile = a.deleteFile
c.LoadFile = a.loadFile
return c
}
@@ -218,9 +229,11 @@ func (ld *desiredStateLoader) renderAndLoad(env, overrodeEnv *environment.Enviro
if finalState == nil {
finalState = currentState
} else {
if err := mergo.Merge(finalState, currentState, mergo.WithOverride); err != nil {
if err := mergo.Merge(&finalState.ReleaseSetSpec, &currentState.ReleaseSetSpec, mergo.WithOverride); err != nil {
return nil, err
}
finalState.RenderedValues = currentState.RenderedValues
}
env = &finalState.Env
+765
View File
@@ -0,0 +1,765 @@
package app
import (
"bufio"
"bytes"
"io"
"path/filepath"
"sync"
"testing"
"github.com/roboll/helmfile/pkg/exectest"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/testhelper"
"github.com/variantdev/vals"
)
func TestDestroy_2(t *testing.T) {
type testcase struct {
helm3 bool
ns string
concurrency int
error string
files map[string]string
selectors []string
lists map[exectest.ListKey]string
diffs map[exectest.DiffKey]error
upgraded []exectest.Release
deleted []exectest.Release
log string
}
check := func(t *testing.T, tc testcase) {
t.Helper()
wantUpgrades := tc.upgraded
wantDeletes := tc.deleted
var helm = &exectest.Helm{
Helm3: tc.helm3,
FailOnUnexpectedList: true,
FailOnUnexpectedDiff: true,
Lists: tc.lists,
Diffs: tc.diffs,
DiffMutex: &sync.Mutex{},
ChartsMutex: &sync.Mutex{},
ReleasesMutex: &sync.Mutex{},
}
bs := &bytes.Buffer{}
func() {
t.Helper()
logReader, logWriter := io.Pipe()
logFlushed := &sync.WaitGroup{}
// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
logFlushed.Add(1)
go func() {
scanner := bufio.NewScanner(logReader)
for scanner.Scan() {
bs.Write(scanner.Bytes())
bs.WriteString("\n")
}
logFlushed.Done()
}()
defer func() {
// This is here to avoid data-trace on bytes buffer `bs` to capture logs
if err := logWriter.Close(); err != nil {
panic(err)
}
logFlushed.Wait()
}()
logger := helmexec.NewLogger(logWriter, "debug")
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
if err != nil {
t.Errorf("unexpected error creating vals runtime: %v", err)
}
app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob,
abs: filepath.Abs,
OverrideKubeContext: "",
Env: "default",
Logger: logger,
helms: map[helmKey]helmexec.Interface{
createHelmKey("helm", ""): helm,
},
valsRuntime: valsRuntime,
}, tc.files)
if tc.ns != "" {
app.Namespace = tc.ns
}
if tc.selectors != nil {
app.Selectors = tc.selectors
}
destroyErr := app.Destroy(destroyConfig{
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
concurrency: tc.concurrency,
logger: logger,
})
if tc.error == "" && destroyErr != nil {
t.Fatalf("unexpected error: %v", destroyErr)
} else if tc.error != "" && destroyErr == nil {
t.Fatal("expected error did not occur")
} else if tc.error != "" && destroyErr != nil && tc.error != destroyErr.Error() {
t.Fatalf("invalid error: expected %q, got %q", tc.error, destroyErr.Error())
}
if len(wantUpgrades) > len(helm.Releases) {
t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades))
}
for relIdx := range wantUpgrades {
if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name {
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name)
}
for flagIdx := range wantUpgrades[relIdx].Flags {
if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] {
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx])
}
}
}
if len(wantDeletes) > len(helm.Deleted) {
t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes))
}
for relIdx := range wantDeletes {
if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name {
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name)
}
for flagIdx := range wantDeletes[relIdx].Flags {
if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
}
}
}
}()
if tc.log != "" {
actual := bs.String()
diff, exists := testhelper.Diff(tc.log, actual, 3)
if exists {
t.Errorf("unexpected log:\nDIFF\n%s\nEOD", diff)
}
}
}
files := map[string]string{
"/path/to/helmfile.yaml": `
releases:
- name: database
chart: charts/mysql
needs:
- logging
- name: frontend-v1
chart: charts/frontend
installed: false
needs:
- servicemesh
- logging
- backend-v1
- name: frontend-v2
chart: charts/frontend
needs:
- servicemesh
- logging
- backend-v2
- name: frontend-v3
chart: charts/frontend
needs:
- servicemesh
- logging
- backend-v2
- name: backend-v1
chart: charts/backend
installed: false
needs:
- servicemesh
- logging
- database
- anotherbackend
- name: backend-v2
chart: charts/backend
needs:
- servicemesh
- logging
- database
- anotherbackend
- name: anotherbackend
chart: charts/anotherbackend
needs:
- servicemesh
- logging
- database
- name: servicemesh
chart: charts/istio
needs:
- logging
- name: logging
chart: charts/fluent-bit
- name: front-proxy
chart: stable/envoy
`,
}
filesForTwoReleases := map[string]string{
"/path/to/helmfile.yaml": `
releases:
- name: backend-v1
chart: charts/backend
installed: false
- name: frontend-v1
chart: charts/frontend
needs:
- backend-v1
`,
}
t.Run("smoke", func(t *testing.T) {
//
// complex test cases for smoke testing
//
check(t, testcase{
files: files,
diffs: map[exectest.DiffKey]error{},
lists: map[exectest.ListKey]string{
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
exectest.ListKey{Filter: "^frontend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
frontend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^frontend-v3$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
frontend-v3 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
exectest.ListKey{Filter: "^backend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
backend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^logging$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
logging 4 Fri Nov 1 08:40:07 2019 DEPLOYED fluent-bit-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^front-proxy$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
front-proxy 4 Fri Nov 1 08:40:07 2019 DEPLOYED envoy-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^servicemesh$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
servicemesh 4 Fri Nov 1 08:40:07 2019 DEPLOYED istio-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^database$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
database 4 Fri Nov 1 08:40:07 2019 DEPLOYED mysql-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^anotherbackend$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
anotherbackend 4 Fri Nov 1 08:40:07 2019 DEPLOYED anotherbackend-3.1.0 3.1.0 default
`,
},
// Disable concurrency to avoid in-deterministic result
concurrency: 1,
upgraded: []exectest.Release{},
deleted: []exectest.Release{
{Name: "frontend-v3", Flags: []string{}},
{Name: "frontend-v2", Flags: []string{}},
{Name: "frontend-v1", Flags: []string{}},
{Name: "backend-v2", Flags: []string{}},
{Name: "backend-v1", Flags: []string{}},
{Name: "anotherbackend", Flags: []string{}},
{Name: "servicemesh", Flags: []string{}},
{Name: "database", Flags: []string{}},
{Name: "front-proxy", Flags: []string{}},
{Name: "logging", Flags: []string{}},
},
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: database
3: chart: charts/mysql
4: needs:
5: - logging
6: - name: frontend-v1
7: chart: charts/frontend
8: installed: false
9: needs:
10: - servicemesh
11: - logging
12: - backend-v1
13: - name: frontend-v2
14: chart: charts/frontend
15: needs:
16: - servicemesh
17: - logging
18: - backend-v2
19: - name: frontend-v3
20: chart: charts/frontend
21: needs:
22: - servicemesh
23: - logging
24: - backend-v2
25: - name: backend-v1
26: chart: charts/backend
27: installed: false
28: needs:
29: - servicemesh
30: - logging
31: - database
32: - anotherbackend
33: - name: backend-v2
34: chart: charts/backend
35: needs:
36: - servicemesh
37: - logging
38: - database
39: - anotherbackend
40: - name: anotherbackend
41: chart: charts/anotherbackend
42: needs:
43: - servicemesh
44: - logging
45: - database
46: - name: servicemesh
47: chart: charts/istio
48: needs:
49: - logging
50: - name: logging
51: chart: charts/fluent-bit
52: - name: front-proxy
53: chart: stable/envoy
54:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: database
3: chart: charts/mysql
4: needs:
5: - logging
6: - name: frontend-v1
7: chart: charts/frontend
8: installed: false
9: needs:
10: - servicemesh
11: - logging
12: - backend-v1
13: - name: frontend-v2
14: chart: charts/frontend
15: needs:
16: - servicemesh
17: - logging
18: - backend-v2
19: - name: frontend-v3
20: chart: charts/frontend
21: needs:
22: - servicemesh
23: - logging
24: - backend-v2
25: - name: backend-v1
26: chart: charts/backend
27: installed: false
28: needs:
29: - servicemesh
30: - logging
31: - database
32: - anotherbackend
33: - name: backend-v2
34: chart: charts/backend
35: needs:
36: - servicemesh
37: - logging
38: - database
39: - anotherbackend
40: - name: anotherbackend
41: chart: charts/anotherbackend
42: needs:
43: - servicemesh
44: - logging
45: - database
46: - name: servicemesh
47: chart: charts/istio
48: needs:
49: - logging
50: - name: logging
51: chart: charts/fluent-bit
52: - name: front-proxy
53: chart: stable/envoy
54:
merged environment: &{default map[] map[]}
10 release(s) found in helmfile.yaml
processing 5 groups of releases in this order:
GROUP RELEASES
1 frontend-v3, frontend-v2, frontend-v1
2 backend-v2, backend-v1
3 anotherbackend
4 servicemesh, database
5 front-proxy, logging
processing releases in group 1/5: frontend-v3, frontend-v2, frontend-v1
release "frontend-v3" processed
release "frontend-v2" processed
release "frontend-v1" processed
processing releases in group 2/5: backend-v2, backend-v1
release "backend-v2" processed
release "backend-v1" processed
processing releases in group 3/5: anotherbackend
release "anotherbackend" processed
processing releases in group 4/5: servicemesh, database
release "servicemesh" processed
release "database" processed
processing releases in group 5/5: front-proxy, logging
release "front-proxy" processed
release "logging" processed
DELETED RELEASES:
NAME
frontend-v3
frontend-v2
frontend-v1
backend-v2
backend-v1
anotherbackend
servicemesh
database
front-proxy
logging
`,
})
})
t.Run("destroy only one release with selector", func(t *testing.T) {
check(t, testcase{
files: files,
selectors: []string{"name=logging"},
diffs: map[exectest.DiffKey]error{},
lists: map[exectest.ListKey]string{
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
exectest.ListKey{Filter: "^frontend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
frontend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^frontend-v3$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
frontend-v3 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
exectest.ListKey{Filter: "^backend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
backend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^logging$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
logging 4 Fri Nov 1 08:40:07 2019 DEPLOYED fluent-bit-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^front-proxy$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
front-proxy 4 Fri Nov 1 08:40:07 2019 DEPLOYED envoy-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^servicemesh$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
servicemesh 4 Fri Nov 1 08:40:07 2019 DEPLOYED istio-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^database$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
database 4 Fri Nov 1 08:40:07 2019 DEPLOYED mysql-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^anotherbackend$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
anotherbackend 4 Fri Nov 1 08:40:07 2019 DEPLOYED anotherbackend-3.1.0 3.1.0 default
`,
},
// Disable concurrency to avoid in-deterministic result
concurrency: 1,
upgraded: []exectest.Release{},
deleted: []exectest.Release{
{Name: "logging", Flags: []string{}},
},
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: database
3: chart: charts/mysql
4: needs:
5: - logging
6: - name: frontend-v1
7: chart: charts/frontend
8: installed: false
9: needs:
10: - servicemesh
11: - logging
12: - backend-v1
13: - name: frontend-v2
14: chart: charts/frontend
15: needs:
16: - servicemesh
17: - logging
18: - backend-v2
19: - name: frontend-v3
20: chart: charts/frontend
21: needs:
22: - servicemesh
23: - logging
24: - backend-v2
25: - name: backend-v1
26: chart: charts/backend
27: installed: false
28: needs:
29: - servicemesh
30: - logging
31: - database
32: - anotherbackend
33: - name: backend-v2
34: chart: charts/backend
35: needs:
36: - servicemesh
37: - logging
38: - database
39: - anotherbackend
40: - name: anotherbackend
41: chart: charts/anotherbackend
42: needs:
43: - servicemesh
44: - logging
45: - database
46: - name: servicemesh
47: chart: charts/istio
48: needs:
49: - logging
50: - name: logging
51: chart: charts/fluent-bit
52: - name: front-proxy
53: chart: stable/envoy
54:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: database
3: chart: charts/mysql
4: needs:
5: - logging
6: - name: frontend-v1
7: chart: charts/frontend
8: installed: false
9: needs:
10: - servicemesh
11: - logging
12: - backend-v1
13: - name: frontend-v2
14: chart: charts/frontend
15: needs:
16: - servicemesh
17: - logging
18: - backend-v2
19: - name: frontend-v3
20: chart: charts/frontend
21: needs:
22: - servicemesh
23: - logging
24: - backend-v2
25: - name: backend-v1
26: chart: charts/backend
27: installed: false
28: needs:
29: - servicemesh
30: - logging
31: - database
32: - anotherbackend
33: - name: backend-v2
34: chart: charts/backend
35: needs:
36: - servicemesh
37: - logging
38: - database
39: - anotherbackend
40: - name: anotherbackend
41: chart: charts/anotherbackend
42: needs:
43: - servicemesh
44: - logging
45: - database
46: - name: servicemesh
47: chart: charts/istio
48: needs:
49: - logging
50: - name: logging
51: chart: charts/fluent-bit
52: - name: front-proxy
53: chart: stable/envoy
54:
merged environment: &{default map[] map[]}
1 release(s) matching name=logging found in helmfile.yaml
processing 1 groups of releases in this order:
GROUP RELEASES
1 logging
processing releases in group 1/1: logging
release "logging" processed
DELETED RELEASES:
NAME
logging
`,
})
})
t.Run("destroy installed but disabled release", func(t *testing.T) {
check(t, testcase{
files: filesForTwoReleases,
diffs: map[exectest.DiffKey]error{},
lists: map[exectest.ListKey]string{
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
},
// Disable concurrency to avoid in-deterministic result
concurrency: 1,
upgraded: []exectest.Release{},
deleted: []exectest.Release{
{Name: "frontend-v1", Flags: []string{}},
},
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: backend-v1
3: chart: charts/backend
4: installed: false
5: - name: frontend-v1
6: chart: charts/frontend
7: needs:
8: - backend-v1
9:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: backend-v1
3: chart: charts/backend
4: installed: false
5: - name: frontend-v1
6: chart: charts/frontend
7: needs:
8: - backend-v1
9:
merged environment: &{default map[] map[]}
2 release(s) found in helmfile.yaml
processing 2 groups of releases in this order:
GROUP RELEASES
1 frontend-v1
2 backend-v1
processing releases in group 1/2: frontend-v1
release "frontend-v1" processed
processing releases in group 2/2: backend-v1
release "backend-v1" processed
DELETED RELEASES:
NAME
frontend-v1
backend-v1
`,
})
})
t.Run("helm3", func(t *testing.T) {
check(t, testcase{
helm3: true,
files: filesForTwoReleases,
diffs: map[exectest.DiffKey]error{},
lists: map[exectest.ListKey]string{
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV3ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV3ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
},
// Disable concurrency to avoid in-deterministic result
concurrency: 1,
upgraded: []exectest.Release{},
deleted: []exectest.Release{
{Name: "frontend-v1", Flags: []string{}},
},
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: backend-v1
3: chart: charts/backend
4: installed: false
5: - name: frontend-v1
6: chart: charts/frontend
7: needs:
8: - backend-v1
9:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: backend-v1
3: chart: charts/backend
4: installed: false
5: - name: frontend-v1
6: chart: charts/frontend
7: needs:
8: - backend-v1
9:
merged environment: &{default map[] map[]}
2 release(s) found in helmfile.yaml
processing 2 groups of releases in this order:
GROUP RELEASES
1 frontend-v1
2 backend-v1
processing releases in group 1/2: frontend-v1
release "frontend-v1" processed
processing releases in group 2/2: backend-v1
release "backend-v1" processed
DELETED RELEASES:
NAME
frontend-v1
backend-v1
`,
})
})
}
+514 -166
View File
@@ -3,21 +3,30 @@ package app
import (
"bufio"
"bytes"
"io"
"path/filepath"
"sync"
"testing"
"github.com/roboll/helmfile/pkg/exectest"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/testhelper"
"github.com/variantdev/vals"
"go.uber.org/zap"
"io"
"path/filepath"
"sync"
"testing"
)
const (
helmV2ListFlags = "--kube-contextdefault--deleting--deployed--failed--pending"
helmV2ListFlagsWithoutKubeContext = "--deleting--deployed--failed--pending"
helmV3ListFlags = "--kube-contextdefault--uninstalling--deployed--failed--pending"
helmV3ListFlagsWithoutKubeContext = "--uninstalling--deployed--failed--pending"
)
type destroyConfig struct {
args string
concurrency int
interactive bool
skipDeps bool
logger *zap.SugaredLogger
}
@@ -37,10 +46,13 @@ func (d destroyConfig) Concurrency() int {
return d.concurrency
}
func (d destroyConfig) SkipDeps() bool {
return d.skipDeps
}
func TestDestroy(t *testing.T) {
testcases := []struct {
name string
loc string
type testcase struct {
helm3 bool
ns string
concurrency int
error string
@@ -51,15 +63,137 @@ func TestDestroy(t *testing.T) {
upgraded []exectest.Release
deleted []exectest.Release
log string
}{
//
// complex test cases for smoke testing
//
{
name: "smoke",
loc: location(),
files: map[string]string{
"/path/to/helmfile.yaml": `
}
check := func(t *testing.T, tc testcase) {
t.Helper()
wantUpgrades := tc.upgraded
wantDeletes := tc.deleted
var helm = &exectest.Helm{
Helm3: tc.helm3,
FailOnUnexpectedList: true,
FailOnUnexpectedDiff: true,
Lists: tc.lists,
Diffs: tc.diffs,
DiffMutex: &sync.Mutex{},
ChartsMutex: &sync.Mutex{},
ReleasesMutex: &sync.Mutex{},
}
bs := &bytes.Buffer{}
func() {
t.Helper()
logReader, logWriter := io.Pipe()
logFlushed := &sync.WaitGroup{}
// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
logFlushed.Add(1)
go func() {
scanner := bufio.NewScanner(logReader)
for scanner.Scan() {
bs.Write(scanner.Bytes())
bs.WriteString("\n")
}
logFlushed.Done()
}()
defer func() {
// This is here to avoid data-trace on bytes buffer `bs` to capture logs
if err := logWriter.Close(); err != nil {
panic(err)
}
logFlushed.Wait()
}()
logger := helmexec.NewLogger(logWriter, "debug")
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
if err != nil {
t.Errorf("unexpected error creating vals runtime: %v", err)
}
app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob,
abs: filepath.Abs,
OverrideKubeContext: "default",
Env: "default",
Logger: logger,
helms: map[helmKey]helmexec.Interface{
createHelmKey("helm", "default"): helm,
},
valsRuntime: valsRuntime,
}, tc.files)
if tc.ns != "" {
app.Namespace = tc.ns
}
if tc.selectors != nil {
app.Selectors = tc.selectors
}
destroyErr := app.Destroy(destroyConfig{
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
concurrency: tc.concurrency,
logger: logger,
})
if tc.error == "" && destroyErr != nil {
t.Fatalf("unexpected error: %v", destroyErr)
} else if tc.error != "" && destroyErr == nil {
t.Fatal("expected error did not occur")
} else if tc.error != "" && destroyErr != nil && tc.error != destroyErr.Error() {
t.Fatalf("invalid error: expected %q, got %q", tc.error, destroyErr.Error())
}
if len(wantUpgrades) > len(helm.Releases) {
t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades))
}
for relIdx := range wantUpgrades {
if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name {
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name)
}
for flagIdx := range wantUpgrades[relIdx].Flags {
if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] {
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx])
}
}
}
if len(wantDeletes) > len(helm.Deleted) {
t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes))
}
for relIdx := range wantDeletes {
if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name {
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name)
}
for flagIdx := range wantDeletes[relIdx].Flags {
if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
}
}
}
}()
if tc.log != "" {
actual := bs.String()
diff, exists := testhelper.Diff(tc.log, actual, 3)
if exists {
t.Errorf("unexpected log:\nDIFF\n%s\nEOD", diff)
}
}
}
files := map[string]string{
"/path/to/helmfile.yaml": `
releases:
- name: database
chart: charts/mysql
@@ -114,35 +248,55 @@ releases:
- name: front-proxy
chart: stable/envoy
`,
},
}
filesForTwoReleases := map[string]string{
"/path/to/helmfile.yaml": `
releases:
- name: backend-v1
chart: charts/backend
installed: false
- name: frontend-v1
chart: charts/frontend
needs:
- backend-v1
`,
}
t.Run("smoke", func(t *testing.T) {
//
// complex test cases for smoke testing
//
check(t, testcase{
files: files,
diffs: map[exectest.DiffKey]error{},
lists: map[exectest.ListKey]string{
exectest.ListKey{Filter: "^frontend-v1$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
exectest.ListKey{Filter: "^frontend-v2$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^frontend-v2$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
frontend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^frontend-v3$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^frontend-v3$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
frontend-v3 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^backend-v1$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
exectest.ListKey{Filter: "^backend-v2$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^backend-v2$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
backend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^logging$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^logging$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
logging 4 Fri Nov 1 08:40:07 2019 DEPLOYED fluent-bit-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^front-proxy$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^front-proxy$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
front-proxy 4 Fri Nov 1 08:40:07 2019 DEPLOYED envoy-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^servicemesh$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^servicemesh$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
servicemesh 4 Fri Nov 1 08:40:07 2019 DEPLOYED istio-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^database$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^database$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
database 4 Fri Nov 1 08:40:07 2019 DEPLOYED mysql-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^anotherbackend$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^anotherbackend$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
anotherbackend 4 Fri Nov 1 08:40:07 2019 DEPLOYED anotherbackend-3.1.0 3.1.0 default
`,
},
@@ -156,8 +310,8 @@ anotherbackend 4 Fri Nov 1 08:40:07 2019 DEPLOYED anotherbackend-3.1.0
{Name: "backend-v2", Flags: []string{}},
{Name: "backend-v1", Flags: []string{}},
{Name: "anotherbackend", Flags: []string{}},
{Name: "database", Flags: []string{}},
{Name: "servicemesh", Flags: []string{}},
{Name: "database", Flags: []string{}},
{Name: "front-proxy", Flags: []string{}},
{Name: "logging", Flags: []string{}},
},
@@ -288,25 +442,25 @@ merged environment: &{default map[] map[]}
processing 5 groups of releases in this order:
GROUP RELEASES
1 frontend-v3, frontend-v2, frontend-v1
2 backend-v2, backend-v1
3 anotherbackend
4 database, servicemesh
5 front-proxy, logging
1 default//frontend-v3, default//frontend-v2, default//frontend-v1
2 default//backend-v2, default//backend-v1
3 default//anotherbackend
4 default//servicemesh, default//database
5 default//front-proxy, default//logging
processing releases in group 1/5: frontend-v3, frontend-v2, frontend-v1
processing releases in group 1/5: default//frontend-v3, default//frontend-v2, default//frontend-v1
release "frontend-v3" processed
release "frontend-v2" processed
release "frontend-v1" processed
processing releases in group 2/5: backend-v2, backend-v1
processing releases in group 2/5: default//backend-v2, default//backend-v1
release "backend-v2" processed
release "backend-v1" processed
processing releases in group 3/5: anotherbackend
processing releases in group 3/5: default//anotherbackend
release "anotherbackend" processed
processing releases in group 4/5: database, servicemesh
release "database" processed
processing releases in group 4/5: default//servicemesh, default//database
release "servicemesh" processed
processing releases in group 5/5: front-proxy, logging
release "database" processed
processing releases in group 5/5: default//front-proxy, default//logging
release "front-proxy" processed
release "logging" processed
@@ -318,136 +472,330 @@ frontend-v1
backend-v2
backend-v1
anotherbackend
database
servicemesh
database
front-proxy
logging
`,
},
}
for i := range testcases {
tc := testcases[i]
t.Run(tc.name, func(t *testing.T) {
wantUpgrades := tc.upgraded
wantDeletes := tc.deleted
var helm = &exectest.Helm{
FailOnUnexpectedList: true,
FailOnUnexpectedDiff: true,
Lists: tc.lists,
Diffs: tc.diffs,
DiffMutex: &sync.Mutex{},
ChartsMutex: &sync.Mutex{},
ReleasesMutex: &sync.Mutex{},
}
bs := &bytes.Buffer{}
func() {
logReader, logWriter := io.Pipe()
logFlushed := &sync.WaitGroup{}
// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
logFlushed.Add(1)
go func() {
scanner := bufio.NewScanner(logReader)
for scanner.Scan() {
bs.Write(scanner.Bytes())
bs.WriteString("\n")
}
logFlushed.Done()
}()
defer func() {
// This is here to avoid data-trace on bytes buffer `bs` to capture logs
if err := logWriter.Close(); err != nil {
panic(err)
}
logFlushed.Wait()
}()
logger := helmexec.NewLogger(logWriter, "debug")
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
if err != nil {
t.Errorf("unexpected error creating vals runtime: %v", err)
}
app := appWithFs(&App{
OverrideHelmBinary: DefaultHelmBinary,
glob: filepath.Glob,
abs: filepath.Abs,
OverrideKubeContext: "default",
Env: "default",
Logger: logger,
helms: map[helmKey]helmexec.Interface{
createHelmKey("helm", "default"): helm,
},
valsRuntime: valsRuntime,
}, tc.files)
if tc.ns != "" {
app.Namespace = tc.ns
}
if tc.selectors != nil {
app.Selectors = tc.selectors
}
destroyErr := app.Destroy(destroyConfig{
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
concurrency: tc.concurrency,
logger: logger,
})
if tc.error == "" && destroyErr != nil {
t.Fatalf("unexpected error for data defined at %s: %v", tc.loc, destroyErr)
} else if tc.error != "" && destroyErr == nil {
t.Fatalf("expected error did not occur for data defined at %s", tc.loc)
} else if tc.error != "" && destroyErr != nil && tc.error != destroyErr.Error() {
t.Fatalf("invalid error: expected %q, got %q", tc.error, destroyErr.Error())
}
if len(wantUpgrades) > len(helm.Releases) {
t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades))
}
for relIdx := range wantUpgrades {
if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name {
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name)
}
for flagIdx := range wantUpgrades[relIdx].Flags {
if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] {
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx])
}
}
}
if len(wantDeletes) > len(helm.Deleted) {
t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes))
}
for relIdx := range wantDeletes {
if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name {
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name)
}
for flagIdx := range wantDeletes[relIdx].Flags {
if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
}
}
}
}()
if tc.log != "" {
actual := bs.String()
diff, exists := testhelper.Diff(tc.log, actual, 3)
if exists {
t.Errorf("unexpected log for data defined %s:\nDIFF\n%s\nEOD", tc.loc, diff)
}
}
})
}
})
t.Run("destroy only one release with selector", func(t *testing.T) {
check(t, testcase{
files: files,
selectors: []string{"name=logging"},
diffs: map[exectest.DiffKey]error{},
lists: map[exectest.ListKey]string{
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
exectest.ListKey{Filter: "^frontend-v2$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
frontend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^frontend-v3$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
frontend-v3 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
exectest.ListKey{Filter: "^backend-v2$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
backend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^logging$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
logging 4 Fri Nov 1 08:40:07 2019 DEPLOYED fluent-bit-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^front-proxy$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
front-proxy 4 Fri Nov 1 08:40:07 2019 DEPLOYED envoy-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^servicemesh$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
servicemesh 4 Fri Nov 1 08:40:07 2019 DEPLOYED istio-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^database$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
database 4 Fri Nov 1 08:40:07 2019 DEPLOYED mysql-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^anotherbackend$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
anotherbackend 4 Fri Nov 1 08:40:07 2019 DEPLOYED anotherbackend-3.1.0 3.1.0 default
`,
},
// Disable concurrency to avoid in-deterministic result
concurrency: 1,
upgraded: []exectest.Release{},
deleted: []exectest.Release{
{Name: "logging", Flags: []string{}},
},
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: database
3: chart: charts/mysql
4: needs:
5: - logging
6: - name: frontend-v1
7: chart: charts/frontend
8: installed: false
9: needs:
10: - servicemesh
11: - logging
12: - backend-v1
13: - name: frontend-v2
14: chart: charts/frontend
15: needs:
16: - servicemesh
17: - logging
18: - backend-v2
19: - name: frontend-v3
20: chart: charts/frontend
21: needs:
22: - servicemesh
23: - logging
24: - backend-v2
25: - name: backend-v1
26: chart: charts/backend
27: installed: false
28: needs:
29: - servicemesh
30: - logging
31: - database
32: - anotherbackend
33: - name: backend-v2
34: chart: charts/backend
35: needs:
36: - servicemesh
37: - logging
38: - database
39: - anotherbackend
40: - name: anotherbackend
41: chart: charts/anotherbackend
42: needs:
43: - servicemesh
44: - logging
45: - database
46: - name: servicemesh
47: chart: charts/istio
48: needs:
49: - logging
50: - name: logging
51: chart: charts/fluent-bit
52: - name: front-proxy
53: chart: stable/envoy
54:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: database
3: chart: charts/mysql
4: needs:
5: - logging
6: - name: frontend-v1
7: chart: charts/frontend
8: installed: false
9: needs:
10: - servicemesh
11: - logging
12: - backend-v1
13: - name: frontend-v2
14: chart: charts/frontend
15: needs:
16: - servicemesh
17: - logging
18: - backend-v2
19: - name: frontend-v3
20: chart: charts/frontend
21: needs:
22: - servicemesh
23: - logging
24: - backend-v2
25: - name: backend-v1
26: chart: charts/backend
27: installed: false
28: needs:
29: - servicemesh
30: - logging
31: - database
32: - anotherbackend
33: - name: backend-v2
34: chart: charts/backend
35: needs:
36: - servicemesh
37: - logging
38: - database
39: - anotherbackend
40: - name: anotherbackend
41: chart: charts/anotherbackend
42: needs:
43: - servicemesh
44: - logging
45: - database
46: - name: servicemesh
47: chart: charts/istio
48: needs:
49: - logging
50: - name: logging
51: chart: charts/fluent-bit
52: - name: front-proxy
53: chart: stable/envoy
54:
merged environment: &{default map[] map[]}
1 release(s) matching name=logging found in helmfile.yaml
processing 1 groups of releases in this order:
GROUP RELEASES
1 default//logging
processing releases in group 1/1: default//logging
release "logging" processed
DELETED RELEASES:
NAME
logging
`,
})
})
t.Run("destroy installed but disabled release", func(t *testing.T) {
check(t, testcase{
files: filesForTwoReleases,
diffs: map[exectest.DiffKey]error{},
lists: map[exectest.ListKey]string{
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
},
// Disable concurrency to avoid in-deterministic result
concurrency: 1,
upgraded: []exectest.Release{},
deleted: []exectest.Release{
{Name: "frontend-v1", Flags: []string{}},
},
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: backend-v1
3: chart: charts/backend
4: installed: false
5: - name: frontend-v1
6: chart: charts/frontend
7: needs:
8: - backend-v1
9:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: backend-v1
3: chart: charts/backend
4: installed: false
5: - name: frontend-v1
6: chart: charts/frontend
7: needs:
8: - backend-v1
9:
merged environment: &{default map[] map[]}
2 release(s) found in helmfile.yaml
processing 2 groups of releases in this order:
GROUP RELEASES
1 default//frontend-v1
2 default//backend-v1
processing releases in group 1/2: default//frontend-v1
release "frontend-v1" processed
processing releases in group 2/2: default//backend-v1
release "backend-v1" processed
DELETED RELEASES:
NAME
frontend-v1
backend-v1
`,
})
})
t.Run("helm3", func(t *testing.T) {
check(t, testcase{
helm3: true,
files: filesForTwoReleases,
diffs: map[exectest.DiffKey]error{},
lists: map[exectest.ListKey]string{
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV3ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV3ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
`,
},
// Disable concurrency to avoid in-deterministic result
concurrency: 1,
upgraded: []exectest.Release{},
deleted: []exectest.Release{
{Name: "frontend-v1", Flags: []string{}},
},
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: backend-v1
3: chart: charts/backend
4: installed: false
5: - name: frontend-v1
6: chart: charts/frontend
7: needs:
8: - backend-v1
9:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: backend-v1
3: chart: charts/backend
4: installed: false
5: - name: frontend-v1
6: chart: charts/frontend
7: needs:
8: - backend-v1
9:
merged environment: &{default map[] map[]}
2 release(s) found in helmfile.yaml
processing 2 groups of releases in this order:
GROUP RELEASES
1 default//frontend-v1
2 default//backend-v1
processing releases in group 1/2: default//frontend-v1
release "frontend-v1" processed
processing releases in group 2/2: default//backend-v1
release "backend-v1" processed
DELETED RELEASES:
NAME
frontend-v1
backend-v1
`,
})
})
}
File diff suppressed because it is too large Load Diff
+282 -66
View File
@@ -3,15 +3,17 @@ package app
import (
"bufio"
"bytes"
"io"
"path/filepath"
"sync"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/roboll/helmfile/pkg/exectest"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/testhelper"
"github.com/variantdev/vals"
"go.uber.org/zap"
"io"
"path/filepath"
"sync"
"testing"
)
type diffConfig struct {
@@ -20,15 +22,21 @@ type diffConfig struct {
retainValuesFiles bool
set []string
validate bool
skipCRDs bool
skipDeps bool
includeTests bool
includeNeeds bool
skipNeeds bool
suppressSecrets bool
showSecrets bool
suppressDiff bool
noColor bool
context int
diffOutput string
concurrency int
detailedExitcode bool
interactive bool
skipDiffOnInstall bool
logger *zap.SugaredLogger
}
@@ -48,6 +56,10 @@ func (a diffConfig) Validate() bool {
return a.validate
}
func (a diffConfig) SkipCRDs() bool {
return a.skipCRDs
}
func (a diffConfig) SkipDeps() bool {
return a.skipDeps
}
@@ -56,10 +68,22 @@ func (a diffConfig) IncludeTests() bool {
return a.includeTests
}
func (a diffConfig) IncludeNeeds() bool {
return a.includeNeeds
}
func (a diffConfig) SkipNeeds() bool {
return a.skipNeeds
}
func (a diffConfig) SuppressSecrets() bool {
return a.suppressSecrets
}
func (a diffConfig) ShowSecrets() bool {
return a.showSecrets
}
func (a diffConfig) SuppressDiff() bool {
return a.suppressDiff
}
@@ -72,6 +96,10 @@ func (a diffConfig) Context() int {
return a.context
}
func (a diffConfig) DiffOutput() string {
return a.diffOutput
}
func (a diffConfig) Concurrency() int {
return a.concurrency
}
@@ -84,6 +112,10 @@ func (a diffConfig) Interactive() bool {
return a.interactive
}
func (a diffConfig) SkipDiffOnInstall() bool {
return a.skipDiffOnInstall
}
func (a diffConfig) Logger() *zap.SugaredLogger {
return a.logger
}
@@ -93,20 +125,26 @@ func (a diffConfig) RetainValuesFiles() bool {
}
func TestDiff(t *testing.T) {
type flags struct {
skipNeeds bool
}
testcases := []struct {
name string
loc string
ns string
concurrency int
detailedExitcode bool
error string
files map[string]string
selectors []string
lists map[exectest.ListKey]string
diffs map[exectest.DiffKey]error
upgraded []exectest.Release
deleted []exectest.Release
log string
name string
loc string
ns string
concurrency int
skipDiffOnInstall bool
detailedExitcode bool
error string
flags flags
files map[string]string
selectors []string
lists map[exectest.ListKey]string
diffs map[exectest.DiffKey]error
upgraded []exectest.Release
deleted []exectest.Release
log string
}{
//
// complex test cases for smoke testing
@@ -172,7 +210,7 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
// noop on frontend-v2
exectest.DiffKey{Name: "frontend-v2", Chart: "charts/frontend", Flags: "--kube-contextdefault--detailed-exitcode"}: nil,
@@ -188,10 +226,10 @@ releases:
},
lists: map[exectest.ListKey]string{
// delete frontend-v1 and backend-v1
exectest.ListKey{Filter: "^frontend-v1$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
frontend-v1 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^backend-v1$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
backend-v1 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
`,
},
@@ -361,8 +399,8 @@ releases:
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: nil,
},
lists: map[exectest.ListKey]string{
exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault--deployed--failed--pending"}: ``,
exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlags}: ``,
exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
`,
},
@@ -389,7 +427,7 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -461,7 +499,7 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -483,7 +521,7 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -506,7 +544,7 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -529,7 +567,7 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -553,7 +591,7 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -561,7 +599,7 @@ releases:
upgraded: []exectest.Release{},
},
{
name: "upgrade when ns2/bar needs ns1/foo",
name: "helm3: upgrade when ns2/bar needs ns1/foo",
loc: location(),
files: map[string]string{
"/path/to/helmfile.yaml": `
@@ -577,7 +615,7 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -596,7 +634,7 @@ releases:
namespace: ns1
tillerNamespace: tns1
needs:
- tns2/ns2/bar
- tns2/bar
- name: bar
chart: mychart2
namespace: ns2
@@ -604,7 +642,7 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--tiller-namespacetns2--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--tiller-namespacetns1--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -612,7 +650,7 @@ releases:
upgraded: []exectest.Release{},
},
{
name: "upgrade when tns2/ns2/bar needs tns1/ns1/foo",
name: "helm2: upgrade when tns2/bar needs tns1/foo",
loc: location(),
files: map[string]string{
"/path/to/helmfile.yaml": `
@@ -622,7 +660,7 @@ releases:
namespace: ns2
tillerNamespace: tns2
needs:
- tns1/ns1/foo
- tns1/foo
- name: foo
chart: mychart1
namespace: ns1
@@ -630,7 +668,7 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--tiller-namespacetns2--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--tiller-namespacetns1--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
@@ -649,7 +687,7 @@ first-pass rendering output of "helmfile.yaml.part.0":
4: namespace: ns2
5: tillerNamespace: tns2
6: needs:
7: - tns1/ns1/foo
7: - tns1/foo
8: - name: foo
9: chart: mychart1
10: namespace: ns1
@@ -669,7 +707,7 @@ second-pass rendering result of "helmfile.yaml.part.0":
4: namespace: ns2
5: tillerNamespace: tns2
6: needs:
7: - tns1/ns1/foo
7: - tns1/foo
8: - name: foo
9: chart: mychart1
10: namespace: ns1
@@ -679,6 +717,74 @@ second-pass rendering result of "helmfile.yaml.part.0":
merged environment: &{default map[] map[]}
2 release(s) found in helmfile.yaml
Affected releases are:
bar (mychart2) UPDATED
foo (mychart1) UPDATED
`,
},
{
name: "helm3: upgrade when ns2/bar needs ns1/foo",
loc: location(),
files: map[string]string{
"/path/to/helmfile.yaml": `
releases:
- name: bar
chart: mychart2
namespace: ns2
needs:
- ns1/foo
- name: foo
chart: mychart1
namespace: ns1
`,
},
detailedExitcode: true,
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
},
upgraded: []exectest.Release{},
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
concurrency: 1,
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: bar
3: chart: mychart2
4: namespace: ns2
5: needs:
6: - ns1/foo
7: - name: foo
8: chart: mychart1
9: namespace: ns1
10:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: bar
3: chart: mychart2
4: namespace: ns2
5: needs:
6: - ns1/foo
7: - name: foo
8: chart: mychart1
9: namespace: ns1
10:
merged environment: &{default map[] map[]}
2 release(s) found in helmfile.yaml
Affected releases are:
bar (mychart2) UPDATED
foo (mychart1) UPDATED
@@ -705,16 +811,16 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
},
lists: map[exectest.ListKey]string{
exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
`,
},
@@ -737,16 +843,16 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
},
lists: map[exectest.ListKey]string{
exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
`,
},
@@ -771,16 +877,16 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
},
lists: map[exectest.ListKey]string{
exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
`,
},
@@ -803,16 +909,16 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
},
lists: map[exectest.ListKey]string{
exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
`,
},
@@ -835,16 +941,16 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
},
lists: map[exectest.ListKey]string{
exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
`,
},
@@ -867,16 +973,16 @@ releases:
`,
},
detailedExitcode: true,
error: "Identified at least on change",
error: "Identified at least one change",
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
},
lists: map[exectest.ListKey]string{
exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default
`,
exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
`,
},
@@ -888,8 +994,9 @@ bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 defau
//
{
// see https://github.com/roboll/helmfile/issues/919#issuecomment-549831747
name: "upgrades with good selector",
loc: location(),
name: "upgrades with good selector with --skip-needs=true",
loc: location(),
flags: flags{skipNeeds: true},
files: map[string]string{
"/path/to/helmfile.yaml": `
{{ $mark := "a" }}
@@ -925,7 +1032,7 @@ releases:
upgraded: []exectest.Release{},
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
concurrency: 1,
error: "Identified at least on change",
error: "Identified at least one change",
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
@@ -993,6 +1100,112 @@ Affected releases are:
external-secrets (incubator/raw) UPDATED
my-release (incubator/raw) UPDATED
`,
},
{
name: "upgrades with good selector with --skip-needs=false",
loc: location(),
flags: flags{skipNeeds: false},
files: map[string]string{
"/path/to/helmfile.yaml": `
{{ $mark := "a" }}
releases:
- name: kubernetes-external-secrets
chart: incubator/raw
namespace: kube-system
- name: external-secrets
chart: incubator/raw
namespace: default
labels:
app: test
needs:
- kube-system/kubernetes-external-secrets
- name: my-release
chart: incubator/raw
namespace: default
labels:
app: test
needs:
- default/external-secrets
`,
},
selectors: []string{"app=test"},
detailedExitcode: true,
diffs: map[exectest.DiffKey]error{
exectest.DiffKey{Name: "external-secrets", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
exectest.DiffKey{Name: "my-release", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
},
upgraded: []exectest.Release{},
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
concurrency: 1,
error: `in ./helmfile.yaml: release "default/default/external-secrets" depends on "default/kube-system/kubernetes-external-secrets" which does not match the selectors. Please add a selector like "--selector name=kubernetes-external-secrets", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies`,
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile.yaml.part.0":
0:
1:
2:
3: releases:
4: - name: kubernetes-external-secrets
5: chart: incubator/raw
6: namespace: kube-system
7:
8: - name: external-secrets
9: chart: incubator/raw
10: namespace: default
11: labels:
12: app: test
13: needs:
14: - kube-system/kubernetes-external-secrets
15:
16: - name: my-release
17: chart: incubator/raw
18: namespace: default
19: labels:
20: app: test
21: needs:
22: - default/external-secrets
23:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile.yaml.part.0":
0:
1:
2:
3: releases:
4: - name: kubernetes-external-secrets
5: chart: incubator/raw
6: namespace: kube-system
7:
8: - name: external-secrets
9: chart: incubator/raw
10: namespace: default
11: labels:
12: app: test
13: needs:
14: - kube-system/kubernetes-external-secrets
15:
16: - name: my-release
17: chart: incubator/raw
18: namespace: default
19: labels:
20: app: test
21: needs:
22: - default/external-secrets
23:
merged environment: &{default map[] map[]}
2 release(s) matching app=test found in helmfile.yaml
err: release "default/default/external-secrets" depends on "default/kube-system/kubernetes-external-secrets" which does not match the selectors. Please add a selector like "--selector name=kubernetes-external-secrets", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies
`,
},
{
@@ -1124,7 +1337,7 @@ releases:
upgraded: []exectest.Release{},
deleted: []exectest.Release{},
concurrency: 1,
error: `in ./helmfile.yaml: "foo" depends on nonexistent release "bar"`,
error: `in ./helmfile.yaml: release "default//foo" depends on "default//bar" which does not match the selectors. Please add a selector like "--selector name=bar", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies`,
log: `processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
@@ -1160,7 +1373,7 @@ second-pass rendering result of "helmfile.yaml.part.0":
merged environment: &{default map[] map[]}
2 release(s) found in helmfile.yaml
err: "foo" depends on nonexistent release "bar"
err: release "default//foo" depends on "default//bar" which does not match the selectors. Please add a selector like "--selector name=bar", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies
`,
},
}
@@ -1239,13 +1452,16 @@ err: "foo" depends on nonexistent release "bar"
concurrency: tc.concurrency,
logger: logger,
detailedExitcode: tc.detailedExitcode,
skipNeeds: tc.flags.skipNeeds,
})
if tc.error == "" && diffErr != nil {
t.Fatalf("unexpected error for data defined at %s: %v", tc.loc, diffErr)
} else if tc.error != "" && diffErr == nil {
t.Fatalf("expected error did not occur for data defined at %s", tc.loc)
} else if tc.error != "" && diffErr != nil && tc.error != diffErr.Error() {
t.Fatalf("invalid error: expected %q, got %q", tc.error, diffErr.Error())
var diffErrStr string
if diffErr != nil {
diffErrStr = diffErr.Error()
}
if d := cmp.Diff(tc.error, diffErrStr); d != "" {
t.Fatalf("invalid error: want (-), got (+): %s", d)
}
if len(wantUpgrades) > len(helm.Releases) {
+2 -2
View File
@@ -9,10 +9,10 @@ import (
func FormatAsTable(releases []*HelmRelease) error {
table := uitable.New()
table.AddRow("NAME", "NAMESPACE", "ENABLED", "LABELS")
table.AddRow("NAME", "NAMESPACE", "ENABLED", "LABELS", "CHART", "VERSION")
for _, r := range releases {
table.AddRow(r.Name, r.Namespace, fmt.Sprintf("%t", r.Enabled), r.Labels)
table.AddRow(r.Name, r.Namespace, fmt.Sprintf("%t", r.Enabled), r.Labels, r.Chart, r.Version)
}
fmt.Println(table.String())
+2
View File
@@ -15,6 +15,8 @@ type LoadOpts struct {
CalleePath string
Reverse bool
Filter bool
}
func (o LoadOpts) DeepCopy() LoadOpts {
+14 -3
View File
@@ -22,7 +22,14 @@ func (helm *noCallHelmExec) TemplateRelease(name, chart string, flags ...string)
helm.doPanic()
return nil
}
func (helm *noCallHelmExec) ChartPull(chart string, flags ...string) error {
helm.doPanic()
return nil
}
func (helm *noCallHelmExec) ChartExport(chart string, path string, flags ...string) error {
helm.doPanic()
return nil
}
func (helm *noCallHelmExec) UpdateDeps(chart string) error {
helm.doPanic()
return nil
@@ -41,7 +48,7 @@ func (helm *noCallHelmExec) SetHelmBinary(bin string) {
helm.doPanic()
return
}
func (helm *noCallHelmExec) AddRepo(name, repository, cafile, certfile, keyfile, username, password string) error {
func (helm *noCallHelmExec) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string) error {
helm.doPanic()
return nil
}
@@ -49,6 +56,10 @@ func (helm *noCallHelmExec) UpdateRepo() error {
helm.doPanic()
return nil
}
func (helm *noCallHelmExec) RegistryLogin(name string, username string, password string) error {
helm.doPanic()
return nil
}
func (helm *noCallHelmExec) SyncRelease(context helmexec.HelmContext, name, chart string, flags ...string) error {
helm.doPanic()
return nil
@@ -97,7 +108,7 @@ func (helm *noCallHelmExec) GetVersion() helmexec.Version {
return helmexec.Version{}
}
func (helm *noCallHelmExec) IsVersionAtLeast(major int, minor int) bool {
func (helm *noCallHelmExec) IsVersionAtLeast(versionStr string) bool {
helm.doPanic()
return false
}
+46 -127
View File
@@ -2,13 +2,14 @@ package app
import (
"fmt"
"github.com/roboll/helmfile/pkg/argparser"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/state"
"io/ioutil"
"os"
"sort"
"strings"
"github.com/roboll/helmfile/pkg/argparser"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/state"
)
type Run struct {
@@ -16,12 +17,16 @@ type Run struct {
helm helmexec.Interface
ctx Context
ReleaseToChart map[string]string
ReleaseToChart map[state.PrepareChartKey]string
Ask func(string) bool
}
func NewRun(st *state.HelmState, helm helmexec.Interface, ctx Context) *Run {
if helm == nil {
panic("Assertion failed: helmexec.Interface must not be nil")
}
return &Run{state: st, helm: helm, ctx: ctx}
}
@@ -32,34 +37,37 @@ func (r *Run) askForConfirmation(msg string) bool {
return AskForConfirmation(msg)
}
func (r *Run) withReposAndPreparedCharts(forceDownload bool, skipRepos bool, f func()) []error {
if !skipRepos {
ctx := r.ctx
if errs := ctx.SyncReposOnce(r.state, r.helm); errs != nil && len(errs) > 0 {
return errs
}
}
if err := r.withPreparedCharts(forceDownload, f); err != nil {
return []error{err}
}
return nil
}
func (r *Run) withPreparedCharts(forceDownload bool, f func()) error {
func (r *Run) withPreparedCharts(helmfileCommand string, opts state.ChartPrepareOptions, f func()) error {
if r.ReleaseToChart != nil {
panic("Run.PrepareCharts can be called only once")
}
if !opts.SkipRepos {
ctx := r.ctx
if err := ctx.SyncReposOnce(r.state, r.helm); err != nil {
return err
}
}
// Create tmp directory and bail immediately if it fails
dir, err := ioutil.TempDir("", "")
if err != nil {
var dir string
if len(opts.OutputDir) == 0 {
tempDir, err := ioutil.TempDir("", "helmfile*")
if err != nil {
return err
}
defer os.RemoveAll(tempDir)
dir = tempDir
} else {
dir = opts.OutputDir
fmt.Printf("Charts will be downloaded to: %s\n", dir)
}
if _, err := r.state.TriggerGlobalPrepareEvent(helmfileCommand); err != nil {
return err
}
defer os.RemoveAll(dir)
releaseToChart, errs := state.PrepareCharts(r.helm, r.state, dir, 2, "template", forceDownload)
releaseToChart, errs := r.state.PrepareCharts(r.helm, dir, 2, helmfileCommand, opts)
if len(errs) > 0 {
return fmt.Errorf("%v", errs)
@@ -68,14 +76,23 @@ func (r *Run) withPreparedCharts(forceDownload bool, f func()) error {
for i := range r.state.Releases {
rel := &r.state.Releases[i]
rel.Chart = releaseToChart[rel.Name]
key := state.PrepareChartKey{
Name: rel.Name,
Namespace: rel.Namespace,
KubeContext: rel.KubeContext,
}
if chart := releaseToChart[key]; chart != "" {
rel.Chart = chart
}
}
r.ReleaseToChart = releaseToChart
f()
return nil
_, err := r.state.TriggerGlobalCleanupEvent(helmfileCommand)
return err
}
func (r *Run) Deps(c DepsConfigProvider) []error {
@@ -84,7 +101,7 @@ func (r *Run) Deps(c DepsConfigProvider) []error {
return r.state.UpdateDeps(r.helm)
}
func (r *Run) Repos(c ReposConfigProvider) []error {
func (r *Run) Repos(c ReposConfigProvider) error {
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
return r.ctx.SyncReposOnce(r.state, r.helm)
@@ -100,115 +117,17 @@ func (r *Run) DeprecatedSyncCharts(c DeprecatedChartsConfigProvider) []error {
return errs
}
func (r *Run) Status(c StatusesConfigProvider) []error {
workers := c.Concurrency()
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
return r.state.ReleaseStatuses(r.helm, workers)
}
func (r *Run) Diff(c DiffConfigProvider) (*string, bool, bool, []error) {
func (r *Run) diff(triggerCleanupEvent bool, detailedExitCode bool, c DiffConfigProvider, diffOpts *state.DiffOpts) (*string, map[string]state.ReleaseSpec, map[string]state.ReleaseSpec, []error) {
st := r.state
helm := r.helm
allReleases := st.GetReleasesWithOverrides()
toDiff, err := st.GetSelectedReleasesWithOverrides()
if err != nil {
return nil, false, false, []error{err}
}
if len(toDiff) == 0 {
return nil, false, false, nil
}
// Do build deps and prepare only on selected releases so that we won't waste time
// on running various helm commands on unnecessary releases
st.Releases = toDiff
if !c.SkipDeps() {
if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
return nil, false, false, errs
}
}
if errs := st.PrepareReleases(helm, "diff"); errs != nil && len(errs) > 0 {
return nil, false, false, errs
}
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
opts := &state.DiffOpts{
Context: c.Context(),
NoColor: c.NoColor(),
Set: c.Set(),
}
// Validate all releases for missing `needs` targets
st.Releases = allReleases
if _, err := st.PlanReleases(false); err != nil {
return nil, false, false, []error{err}
}
// Diff only targeted releases
st.Releases = toDiff
filtered := &Run{
state: st,
helm: r.helm,
ctx: r.ctx,
Ask: r.Ask,
}
infoMsg, updated, deleted, errs := filtered.diff(true, c.DetailedExitcode(), c, opts)
return infoMsg, true, len(deleted) > 0 || len(updated) > 0, errs
}
func (r *Run) Test(c TestConfigProvider) []error {
cleanup := c.Cleanup()
timeout := c.Timeout()
concurrency := c.Concurrency()
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
return r.state.TestReleases(r.helm, cleanup, timeout, concurrency)
}
func (r *Run) Lint(c LintConfigProvider) []error {
st := r.state
helm := r.helm
values := c.Values()
args := argparser.GetArgs(c.Args(), st)
workers := c.Concurrency()
if !c.SkipDeps() {
if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
return errs
}
}
if errs := st.PrepareReleases(helm, "lint"); errs != nil && len(errs) > 0 {
return errs
}
opts := &state.LintOpts{
Set: c.Set(),
}
return st.LintReleases(helm, values, args, workers, opts)
}
func (run *Run) diff(triggerCleanupEvent bool, detailedExitCode bool, c DiffConfigProvider, diffOpts *state.DiffOpts) (*string, map[string]state.ReleaseSpec, map[string]state.ReleaseSpec, []error) {
st := run.state
helm := run.helm
var changedReleases []state.ReleaseSpec
var deletingReleases []state.ReleaseSpec
var planningErrs []error
// TODO Better way to detect diff on only filtered releases
{
changedReleases, planningErrs = st.DiffReleases(helm, c.Values(), c.Concurrency(), detailedExitCode, c.IncludeTests(), c.SuppressSecrets(), c.SuppressDiff(), triggerCleanupEvent, diffOpts)
changedReleases, planningErrs = st.DiffReleases(helm, c.Values(), c.Concurrency(), detailedExitCode, c.IncludeTests(), c.SuppressSecrets(), c.ShowSecrets(), c.SuppressDiff(), triggerCleanupEvent, diffOpts)
var err error
deletingReleases, err = st.DetectReleasesToBeDeletedForSync(helm, st.Releases)
+65
View File
@@ -0,0 +1,65 @@
package app
import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
)
func assertEqualsToSnapshot(t *testing.T, name string, data string) {
type thisPkgLocator struct{}
t.Helper()
snapshotFileName := snapshotFileName(t, name)
if os.Getenv("HELMFILE_UPDATE_SNAPSHOT") != "" {
update(t, snapshotFileName, []byte(data))
return
}
wantData, err := ioutil.ReadFile(snapshotFileName)
if err != nil {
t.Fatalf(
"Snapshot file %q does not exist. Rerun this test with `HELMFILE_UPDATE_SNAPSHOT=1 go test -v -run %s %s` to create the snapshot",
snapshotFileName,
t.Name(),
reflect.TypeOf(thisPkgLocator{}).PkgPath(),
)
}
want := string(wantData)
if d := cmp.Diff(want, data); d != "" {
t.Errorf("unexpected %s: want (-), got (+): %s", name, d)
t.Errorf(
"If you think this is due to the snapshot file being outdated, rerun this test with `HELMFILE_UPDATE_SNAPSHOT=1 go test -v -run %s %s` to update the snapshot",
t.Name(),
reflect.TypeOf(thisPkgLocator{}).PkgPath(),
)
}
}
func update(t *testing.T, snapshotFileName string, data []byte) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(snapshotFileName), 0755); err != nil {
t.Fatalf("%v", err)
}
if err := ioutil.WriteFile(snapshotFileName, data, 0644); err != nil {
t.Fatalf("%v", err)
}
}
func snapshotFileName(t *testing.T, name string) string {
dir := filepath.Join(strings.Split(strings.ToLower(t.Name()), "/")...)
return filepath.Join("testdata", dir, name)
}
@@ -0,0 +1,65 @@
processing file "helmfile.yaml" in directory "."
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
first-pass uses: &{default map[] map[]}
first-pass rendering output of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: foo
3: chart: incubator/raw
4: namespace: default
5: labels:
6: app: test
7: component: raw
8: index: '1'
9:
10: - name: foo
11: chart: incubator/raw
12: namespace: default
13: labels:
14: app: test
15: component: raw
16: index: '2'
17:
first-pass produced: &{default map[] map[]}
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
vals:
map[]
defaultVals:[]
second-pass rendering result of "helmfile.yaml.part.0":
0:
1: releases:
2: - name: foo
3: chart: incubator/raw
4: namespace: default
5: labels:
6: app: test
7: component: raw
8: index: '1'
9:
10: - name: foo
11: chart: incubator/raw
12: namespace: default
13: labels:
14: app: test
15: component: raw
16: index: '2'
17:
merged environment: &{default map[] map[]}
1 release(s) matching index=1 found in helmfile.yaml
Affected releases are:
foo (incubator/raw) UPDATED
processing 1 groups of releases in this order:
GROUP RELEASES
1 default/default/foo
processing releases in group 1/1: default/default/foo
getting deployed release version failed:unexpected list key: {^foo$ --kube-contextdefault--deleting--deployed--failed--pending}
UPDATED RELEASES:
NAME CHART VERSION
foo incubator/raw
+2 -1
View File
@@ -3,11 +3,12 @@ package app
import (
"bytes"
"fmt"
"strings"
"github.com/google/go-cmp/cmp"
"github.com/roboll/helmfile/pkg/environment"
"github.com/roboll/helmfile/pkg/state"
"github.com/roboll/helmfile/pkg/tmpl"
"strings"
)
func prependLineNumbers(text string) string {
+4
View File
@@ -1,6 +1,7 @@
package app
import (
"github.com/roboll/helmfile/pkg/remote"
"os"
"strings"
"testing"
@@ -14,6 +15,8 @@ import (
func makeLoader(files map[string]string, env string) (*desiredStateLoader, *testhelper.TestFs) {
testfs := testhelper.NewTestFs(files)
logger := helmexec.NewLogger(os.Stdout, "debug")
r := remote.NewRemote(logger, testfs.Cwd, testfs.ReadFile, testfs.DirectoryExistsAt, testfs.FileExistsAt)
return &desiredStateLoader{
env: env,
namespace: "namespace",
@@ -22,6 +25,7 @@ func makeLoader(files map[string]string, env string) (*desiredStateLoader, *test
fileExists: testfs.FileExists,
abs: testfs.Abs,
glob: testfs.Glob,
remote: r,
}, testfs
}
+10 -2
View File
@@ -10,7 +10,11 @@ func TestGetArgs(t *testing.T) {
args := "--timeout=3600 --set app1.bootstrap=true --set app2.bootstrap=false --tiller-namespace ns"
defaultArgs := []string{"--recreate-pods", "--force"}
Helmdefaults := state.HelmSpec{KubeContext: "test", TillerNamespace: "test-namespace", Args: defaultArgs}
testState := &state.HelmState{HelmDefaults: Helmdefaults}
testState := &state.HelmState{
ReleaseSetSpec: state.ReleaseSetSpec{
HelmDefaults: Helmdefaults,
},
}
receivedArgs := GetArgs(args, testState)
expectedOutput := "--timeout=3600 --set app1.bootstrap=true --set app2.bootstrap=false --tiller-namespace ns --recreate-pods --force"
@@ -24,7 +28,11 @@ func Test2(t *testing.T) {
args := "--timeout=3600 --set app1.bootstrap=true --set app2.bootstrap=false,app3.bootstrap=true --tiller-namespace ns"
defaultArgs := []string{"--recreate-pods", "--force"}
Helmdefaults := state.HelmSpec{KubeContext: "test", TillerNamespace: "test-namespace", Args: defaultArgs}
testState := &state.HelmState{HelmDefaults: Helmdefaults}
testState := &state.HelmState{
ReleaseSetSpec: state.ReleaseSetSpec{
HelmDefaults: Helmdefaults,
},
}
receivedArgs := GetArgs(args, testState)
expectedOutput := "--timeout=3600 --set app1.bootstrap=true --set app2.bootstrap=false,app3.bootstrap=true --tiller-namespace ns --recreate-pods --force"
+32 -9
View File
@@ -2,7 +2,6 @@ package event
import (
"fmt"
"os"
"strings"
"github.com/roboll/helmfile/pkg/environment"
@@ -12,11 +11,12 @@ import (
)
type Hook struct {
Name string `yaml:"name"`
Events []string `yaml:"events"`
Command string `yaml:"command"`
Args []string `yaml:"args"`
ShowLogs bool `yaml:"showlogs"`
Name string `yaml:"name"`
Events []string `yaml:"events"`
Command string `yaml:"command"`
Kubectl map[string]string `yaml:"kubectlApply,omitempty"`
Args []string `yaml:"args"`
ShowLogs bool `yaml:"showlogs"`
}
type event struct {
@@ -31,6 +31,7 @@ type Bus struct {
BasePath string
StateFilePath string
Namespace string
Chart string
Env environment.Environment
@@ -41,7 +42,8 @@ type Bus struct {
func (bus *Bus) Trigger(evt string, evtErr error, context map[string]interface{}) (bool, error) {
if bus.Runner == nil {
bus.Runner = helmexec.ShellRunner{
Dir: bus.BasePath,
Dir: bus.BasePath,
Logger: bus.Logger,
}
}
@@ -60,10 +62,31 @@ func (bus *Bus) Trigger(evt string, evtErr error, context map[string]interface{}
name := hook.Name
if name == "" {
name = hook.Command
if hook.Kubectl != nil {
name = "kubectlApply"
} else {
name = hook.Command
}
}
fmt.Fprintf(os.Stderr, "%s: basePath=%s\n", bus.StateFilePath, bus.BasePath)
if hook.Kubectl != nil {
if hook.Command != "" {
bus.Logger.Warnf("warn: ignoring command '%s' given within a kubectlApply hook", hook.Command)
}
hook.Command = "kubectl"
if val, found := hook.Kubectl["filename"]; found {
if _, found := hook.Kubectl["kustomize"]; found {
return false, fmt.Errorf("hook[%s]: kustomize & filename cannot be used together", name)
}
hook.Args = append([]string{"apply", "-f"}, val)
} else if val, found := hook.Kubectl["kustomize"]; found {
hook.Args = append([]string{"apply", "-k"}, val)
} else {
return false, fmt.Errorf("hook[%s]: either kustomize or filename must be given", name)
}
}
bus.Logger.Debugf("hook[%s]: stateFilePath=%s, basePath=%s\n", name, bus.StateFilePath, bus.BasePath)
data := map[string]interface{}{
"Environment": bus.Env,
+67 -6
View File
@@ -2,6 +2,7 @@ package event
import (
"fmt"
"io"
"os"
"testing"
@@ -16,6 +17,10 @@ var logger = helmexec.NewLogger(os.Stdout, "warn")
type runner struct {
}
func (r *runner) ExecuteStdIn(cmd string, args []string, env map[string]string, stdin io.Reader) ([]byte, error) {
return []byte(""), nil
}
func (r *runner) Execute(cmd string, args []string, env map[string]string) ([]byte, error) {
if cmd == "ng" {
return nil, fmt.Errorf("cmd failed due to invalid cmd: %s", cmd)
@@ -38,21 +43,21 @@ func TestTrigger(t *testing.T) {
}{
{
"okhook1",
&Hook{"okhook1", []string{"foo"}, "ok", []string{}, true},
&Hook{"okhook1", []string{"foo"}, "ok", nil, []string{}, true},
"foo",
true,
"",
},
{
"okhooké",
&Hook{"okhook2", []string{"foo"}, "ok", []string{}, false},
&Hook{"okhook2", []string{"foo"}, "ok", nil, []string{}, false},
"foo",
true,
"",
},
{
"missinghook1",
&Hook{"okhook1", []string{"foo"}, "ok", []string{}, false},
&Hook{"okhook1", []string{"foo"}, "ok", nil, []string{}, false},
"bar",
false,
"",
@@ -66,18 +71,74 @@ func TestTrigger(t *testing.T) {
},
{
"nghook1",
&Hook{"nghook1", []string{"foo"}, "ng", []string{}, false},
&Hook{"nghook1", []string{"foo"}, "ng", nil, []string{}, false},
"foo",
false,
"hook[nghook1]: command `ng` failed: cmd failed due to invalid cmd: ng",
},
{
"nghook2",
&Hook{"nghook2", []string{"foo"}, "ok", []string{"ng"}, false},
&Hook{"nghook2", []string{"foo"}, "ok", nil, []string{"ng"}, false},
"foo",
false,
"hook[nghook2]: command `ok` failed: cmd failed due to invalid arg: ng",
},
{
"okkubeapply1",
&Hook{"okkubeapply1", []string{"foo"}, "", map[string]string{"kustomize": "kustodir"}, []string{}, false},
"foo",
true,
"",
},
{
"okkubeapply2",
&Hook{"okkubeapply2", []string{"foo"}, "", map[string]string{"filename": "resource.yaml"}, []string{}, false},
"foo",
true,
"",
},
{
"kokubeapply",
&Hook{"kokubeapply", []string{"foo"}, "", map[string]string{"kustomize": "kustodir", "filename": "resource.yaml"}, []string{}, true},
"foo",
false,
"hook[kokubeapply]: kustomize & filename cannot be used together",
},
{
"kokubeapply2",
&Hook{"kokubeapply2", []string{"foo"}, "", map[string]string{}, []string{}, true},
"foo",
false,
"hook[kokubeapply2]: either kustomize or filename must be given",
},
{
"kokubeapply3",
&Hook{"", []string{"foo"}, "", map[string]string{}, []string{}, true},
"foo",
false,
"hook[kubectlApply]: either kustomize or filename must be given",
},
{
"warnkubeapply1",
&Hook{"warnkubeapply1", []string{"foo"}, "ok", map[string]string{"filename": "resource.yaml"}, []string{}, true},
"foo",
true,
"",
},
{
"warnkubeapply2",
&Hook{"warnkubeapply2", []string{"foo"}, "", map[string]string{"filename": "resource.yaml"}, []string{"ng"}, true},
"foo",
true,
"",
},
{
"warnkubeapply3",
&Hook{"warnkubeapply3", []string{"foo"}, "ok", map[string]string{"filename": "resource.yaml"}, []string{"ng"}, true},
"foo",
true,
"",
},
}
readFile := func(filename string) ([]byte, error) {
return nil, fmt.Errorf("unexpected call to readFile: %s", filename)
@@ -112,7 +173,7 @@ func TestTrigger(t *testing.T) {
if c.expectedErr != "" {
if err == nil {
t.Error("error expected, but not occurred")
t.Errorf("error expected for case \"%s\", but not occurred", c.name)
} else if err.Error() != c.expectedErr {
t.Errorf("unexpected error for case \"%s\": expected=%s, actual=%v", c.name, c.expectedErr, err)
}
+23 -11
View File
@@ -6,6 +6,7 @@ import (
"strings"
"sync"
"github.com/Masterminds/semver/v3"
"github.com/roboll/helmfile/pkg/helmexec"
)
@@ -30,13 +31,15 @@ type Helm struct {
Diffed []Release
FailOnUnexpectedDiff bool
FailOnUnexpectedList bool
Version *helmexec.Version
Version *semver.Version
UpdateDepsCallbacks map[string]func(string) error
DiffMutex *sync.Mutex
ChartsMutex *sync.Mutex
ReleasesMutex *sync.Mutex
Helm3 bool
}
type Release struct {
@@ -81,13 +84,16 @@ func (helm *Helm) SetExtraArgs(args ...string) {
func (helm *Helm) SetHelmBinary(bin string) {
return
}
func (helm *Helm) AddRepo(name, repository, cafile, certfile, keyfile, username, password string) error {
helm.Repo = []string{name, repository, cafile, certfile, keyfile, username, password}
func (helm *Helm) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string) error {
helm.Repo = []string{name, repository, cafile, certfile, keyfile, username, password, managed}
return nil
}
func (helm *Helm) UpdateRepo() error {
return nil
}
func (helm *Helm) RegistryLogin(name string, username string, password string) error {
return nil
}
func (helm *Helm) SyncRelease(context helmexec.HelmContext, name, chart string, flags ...string) error {
if strings.Contains(name, "error") {
return errors.New("error")
@@ -157,25 +163,31 @@ func (helm *Helm) Lint(name, chart string, flags ...string) error {
func (helm *Helm) TemplateRelease(name, chart string, flags ...string) error {
return nil
}
func (helm *Helm) ChartPull(chart string, flags ...string) error {
return nil
}
func (helm *Helm) ChartExport(chart string, path string, flags ...string) error {
return nil
}
func (helm *Helm) IsHelm3() bool {
return false
return helm.Helm3
}
func (helm *Helm) GetVersion() helmexec.Version {
if helm.Version != nil {
return *helm.Version
return helmexec.Version{
Major: int(helm.Version.Major()),
Minor: int(helm.Version.Minor()),
Patch: int(helm.Version.Patch()),
}
return helmexec.Version{}
}
func (helm *Helm) IsVersionAtLeast(major int, minor int) bool {
func (helm *Helm) IsVersionAtLeast(versionStr string) bool {
if helm.Version == nil {
return false
}
return helm.Version.Major >= major && minor >= helm.Version.Minor
ver := semver.MustParse(versionStr)
return helm.Version.Equal(ver) || helm.Version.GreaterThan(ver)
}
func (helm *Helm) sync(m *sync.Mutex, f func()) {
+2
View File
@@ -1,6 +1,7 @@
package helmexec
import (
"io"
"os"
"path/filepath"
)
@@ -10,6 +11,7 @@ type HelmContext struct {
TillerNamespace string
HistoryMax int
WorkerIndex int
Writer io.Writer
}
func (context *HelmContext) GetTillerlessArgs(helm *execer) []string {
+213 -82
View File
@@ -1,16 +1,17 @@
package helmexec
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"github.com/Masterminds/semver/v3"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
@@ -18,17 +19,19 @@ import (
type decryptedSecret struct {
mutex sync.RWMutex
bytes []byte
err error
}
type execer struct {
helmBinary string
version Version
version semver.Version
runner Runner
logger *zap.SugaredLogger
kubeContext string
extra []string
decryptedSecretMutex sync.Mutex
decryptedSecrets map[string]*decryptedSecret
writeTempFile func([]byte) (string, error)
}
func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger {
@@ -48,62 +51,48 @@ func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger {
return zap.New(core).Sugar()
}
func getHelmVersion(helmBinary string, logger *zap.SugaredLogger, runner Runner) Version {
func parseHelmVersion(versionStr string) (semver.Version, error) {
if len(versionStr) == 0 {
return semver.Version{}, nil
}
// Autodetect from `helm verison`
bytes, err := runner.Execute(helmBinary, []string{"version", "--client", "--short"}, nil)
versionStr = strings.TrimLeft(versionStr, "Client: ")
versionStr = strings.TrimRight(versionStr, "\n")
ver, err := semver.NewVersion(versionStr)
if err != nil {
panic(err)
}
if bytes == nil || len(bytes) == 0 {
return Version{}
}
re := regexp.MustCompile("v(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)")
matches := re.FindStringSubmatch(string(bytes))
result := make(map[string]string)
for i, name := range re.SubexpNames() {
result[name] = matches[i]
}
major, err := strconv.Atoi(result["major"])
if err != nil {
panic(err)
}
minor, err := strconv.Atoi(result["minor"])
if err != nil {
panic(err)
}
patch, err := strconv.Atoi(result["patch"])
if err != nil {
panic(err)
return semver.Version{}, fmt.Errorf("error parsing helm version '%s'", versionStr)
}
// Support explicit helm3 opt-in via environment variable
if os.Getenv("HELMFILE_HELM3") != "" && major < 3 {
return Version{
Major: 3,
Minor: 0,
Patch: 0,
}
if os.Getenv("HELMFILE_HELM3") != "" && ver.Major() < 3 {
return *semver.MustParse("v3.0.0"), nil
}
return Version{
Major: major,
Minor: minor,
Patch: patch,
return *ver, nil
}
func getHelmVersion(helmBinary string, runner Runner) (semver.Version, error) {
// Autodetect from `helm version`
outBytes, err := runner.Execute(helmBinary, []string{"version", "--client", "--short"}, nil)
if err != nil {
return semver.Version{}, fmt.Errorf("error determining helm version: %w", err)
}
return parseHelmVersion(string(outBytes))
}
// New for running helm commands
func New(helmBinary string, logger *zap.SugaredLogger, kubeContext string, runner Runner) *execer {
// TODO: proper error handling
version, err := getHelmVersion(helmBinary, runner)
if err != nil {
panic(err)
}
return &execer{
helmBinary: helmBinary,
version: getHelmVersion(helmBinary, logger, runner),
version: version,
logger: logger,
kubeContext: kubeContext,
runner: runner,
@@ -119,24 +108,46 @@ func (helm *execer) SetHelmBinary(bin string) {
helm.helmBinary = bin
}
func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, username, password string) error {
func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string) error {
var args []string
var out []byte
var err error
if name == "" && repository != "" {
helm.logger.Infof("empty field name\n")
return fmt.Errorf("empty field name")
}
args = append(args, "repo", "add", name, repository)
if certfile != "" && keyfile != "" {
args = append(args, "--cert-file", certfile, "--key-file", keyfile)
switch managed {
case "acr":
helm.logger.Infof("Adding repo %v (acr)", name)
out, err = helm.azcli(name)
case "":
args = append(args, "repo", "add", name, repository)
// See https://github.com/helm/helm/pull/8777
if cons, err := semver.NewConstraint(">= 3.3.2"); err == nil {
if cons.Check(&helm.version) {
args = append(args, "--force-update")
}
} else {
panic(err)
}
if certfile != "" && keyfile != "" {
args = append(args, "--cert-file", certfile, "--key-file", keyfile)
}
if cafile != "" {
args = append(args, "--ca-file", cafile)
}
if username != "" && password != "" {
args = append(args, "--username", username, "--password", password)
}
helm.logger.Infof("Adding repo %v %v", name, repository)
out, err = helm.exec(args, map[string]string{})
default:
helm.logger.Errorf("ERROR: unknown type '%v' for repository %v", managed, name)
out = nil
err = nil
}
if cafile != "" {
args = append(args, "--ca-file", cafile)
}
if username != "" && password != "" {
args = append(args, "--username", username, "--password", password)
}
helm.logger.Infof("Adding repo %v %v", name, repository)
out, err := helm.exec(args, map[string]string{})
helm.info(out)
return err
}
@@ -148,6 +159,23 @@ func (helm *execer) UpdateRepo() error {
return err
}
func (helm *execer) RegistryLogin(repository string, username string, password string) error {
helm.logger.Info("Logging in to registry")
args := []string{
"registry",
"login",
repository,
"--username",
username,
"--password-stdin",
}
buffer := bytes.Buffer{}
buffer.Write([]byte(fmt.Sprintf("%s\n", password)))
out, err := helm.execStdIn(args, map[string]string{"HELM_EXPERIMENTAL_OCI": "1"}, &buffer)
helm.info(out)
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{})
@@ -174,7 +202,7 @@ func (helm *execer) SyncRelease(context HelmContext, name, chart string, flags .
}
out, err := helm.exec(append(append(preArgs, "upgrade", "--install", "--reset-values", name, chart), flags...), env)
helm.write(out)
helm.write(nil, out)
return err
}
@@ -183,7 +211,7 @@ func (helm *execer) ReleaseStatus(context HelmContext, name string, flags ...str
preArgs := context.GetTillerlessArgs(helm)
env := context.getTillerlessEnv()
out, err := helm.exec(append(append(preArgs, "status", name), flags...), env)
helm.write(out)
helm.write(nil, out)
return err
}
@@ -210,7 +238,7 @@ func (helm *execer) List(context HelmContext, filter string, flags ...string) (s
lines = lines[1:]
out = []byte(strings.Join(lines, "\n"))
}
helm.write(out)
helm.write(nil, out)
return string(out), err
}
@@ -241,6 +269,7 @@ func (helm *execer) DecryptSecret(context HelmContext, name string, flags ...str
out, err := helm.exec(append(append(preArgs, "secrets", "dec", absPath), flags...), env)
helm.info(out)
if err != nil {
secret.err = err
return "", err
}
@@ -249,10 +278,18 @@ func (helm *execer) DecryptSecret(context HelmContext, name string, flags ...str
if len(decSuffix) == 0 {
decSuffix = ".yaml.dec"
}
decFilename := strings.Replace(absPath, ".yaml", decSuffix, 1)
// helm secrets replaces the extension with its suffix ONLY when the extension is ".yaml"
var decFilename string
if strings.HasSuffix(absPath, ".yaml") {
decFilename = strings.Replace(absPath, ".yaml", decSuffix, 1)
} else {
decFilename = absPath + decSuffix
}
secretBytes, err := ioutil.ReadFile(decFilename)
if err != nil {
secret.err = err
return "", err
}
secret.bytes = secretBytes
@@ -268,18 +305,40 @@ func (helm *execer) DecryptSecret(context HelmContext, name string, flags ...str
secret.mutex.RLock()
helm.decryptedSecretMutex.Unlock()
defer secret.mutex.RUnlock()
if secret.err != nil {
return "", secret.err
}
}
tmpFile, err := ioutil.TempFile("", "secret")
if err != nil {
return "", err
tempFile := helm.writeTempFile
if tempFile == nil {
tempFile = func(content []byte) (string, error) {
dir := filepath.Dir(name)
extension := filepath.Ext(name)
tmpFile, err := ioutil.TempFile(dir, "secret*"+extension)
if err != nil {
return "", err
}
_, err = tmpFile.Write(content)
if err != nil {
return "", err
}
return tmpFile.Name(), nil
}
}
_, err = tmpFile.Write(secret.bytes)
tmpFileName, err := tempFile(secret.bytes)
if err != nil {
return "", err
}
return tmpFile.Name(), err
helm.logger.Debugf("Decrypted %s into %s", absPath, tmpFileName)
return tmpFileName, err
}
func (helm *execer) TemplateRelease(name string, chart string, flags ...string) error {
@@ -292,12 +351,40 @@ func (helm *execer) TemplateRelease(name string, chart string, flags ...string)
}
out, err := helm.exec(append(args, flags...), map[string]string{})
helm.write(out)
var outputToFile bool
for _, f := range flags {
if strings.HasPrefix("--output-dir", f) {
outputToFile = true
break
}
}
if outputToFile {
// With --output-dir is passed to helm-template,
// we can safely direct all the logs from it to our logger.
//
// It's safe because anything written to stdout by helm-template with output-dir is logs,
// like excessive `wrote path/to/output/dir/chart/template/file.yaml` messages,
// but manifets.
//
// See https://github.com/roboll/helmfile/pull/1691#issuecomment-805636021 for more information.
helm.info(out)
} else {
// Always write to stdout for use with e.g. `helmfile template | kubectl apply -f -`
helm.write(nil, out)
}
return err
}
func (helm *execer) DiffRelease(context HelmContext, name, chart string, suppressDiff bool, flags ...string) error {
helm.logger.Infof("Comparing release=%v, chart=%v", name, chart)
if context.Writer != nil {
fmt.Fprintf(context.Writer, "Comparing release=%v, chart=%v\n", name, chart)
} else {
helm.logger.Infof("Comparing release=%v, chart=%v", name, chart)
}
preArgs := context.GetTillerlessArgs(helm)
env := context.getTillerlessEnv()
out, err := helm.exec(append(append(preArgs, "diff", "upgrade", "--reset-values", "--allow-unreleased", name, chart), flags...), env)
@@ -315,13 +402,13 @@ func (helm *execer) DiffRelease(context HelmContext, name, chart string, suppres
case ExitError:
if e.ExitStatus() == 2 {
if !(suppressDiff) {
helm.write(out)
helm.write(context.Writer, out)
}
return err
}
}
} else if !(suppressDiff) {
helm.write(out)
helm.write(context.Writer, out)
}
return err
}
@@ -329,7 +416,7 @@ func (helm *execer) DiffRelease(context HelmContext, name, chart string, suppres
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)
helm.write(nil, out)
return err
}
@@ -340,12 +427,26 @@ func (helm *execer) Fetch(chart string, flags ...string) error {
return err
}
func (helm *execer) ChartPull(chart string, flags ...string) error {
helm.logger.Infof("Pulling %v", chart)
out, err := helm.exec(append([]string{"chart", "pull", chart}, flags...), map[string]string{"HELM_EXPERIMENTAL_OCI": "1"})
helm.info(out)
return err
}
func (helm *execer) ChartExport(chart string, path string, flags ...string) error {
helm.logger.Infof("Exporting %v", chart)
out, err := helm.exec(append([]string{"chart", "export", chart, "--destination", path}, flags...), map[string]string{"HELM_EXPERIMENTAL_OCI": "1"})
helm.info(out)
return err
}
func (helm *execer) DeleteRelease(context HelmContext, name string, flags ...string) error {
helm.logger.Infof("Deleting %v", name)
preArgs := context.GetTillerlessArgs(helm)
env := context.getTillerlessEnv()
out, err := helm.exec(append(append(preArgs, "delete", name), flags...), env)
helm.write(out)
helm.write(nil, out)
return err
}
@@ -355,7 +456,7 @@ func (helm *execer) TestRelease(context HelmContext, name string, flags ...strin
env := context.getTillerlessEnv()
args := []string{"test", name}
out, err := helm.exec(append(append(preArgs, args...), flags...), env)
helm.write(out)
helm.write(nil, out)
return err
}
@@ -365,13 +466,35 @@ func (helm *execer) exec(args []string, env map[string]string) ([]byte, error) {
cmdargs = append(cmdargs, helm.extra...)
}
if helm.kubeContext != "" {
cmdargs = append(cmdargs, "--kube-context", helm.kubeContext)
cmdargs = append([]string{"--kube-context", helm.kubeContext}, cmdargs...)
}
cmd := fmt.Sprintf("exec: %s %s", helm.helmBinary, strings.Join(cmdargs, " "))
helm.logger.Debug(cmd)
bytes, err := helm.runner.Execute(helm.helmBinary, cmdargs, env)
helm.logger.Debugf("%s: %s", cmd, bytes)
return bytes, err
outBytes, err := helm.runner.Execute(helm.helmBinary, cmdargs, env)
return outBytes, err
}
func (helm *execer) execStdIn(args []string, env map[string]string, stdin io.Reader) ([]byte, error) {
cmdargs := args
if len(helm.extra) > 0 {
cmdargs = append(cmdargs, helm.extra...)
}
if helm.kubeContext != "" {
cmdargs = append([]string{"--kube-context", helm.kubeContext}, cmdargs...)
}
cmd := fmt.Sprintf("exec: %s %s", helm.helmBinary, strings.Join(cmdargs, " "))
helm.logger.Debug(cmd)
outBytes, err := helm.runner.ExecuteStdIn(helm.helmBinary, cmdargs, env, stdin)
return outBytes, err
}
func (helm *execer) azcli(name string) ([]byte, error) {
cmdargs := append(strings.Split("acr helm repo add --name", " "), name)
cmd := fmt.Sprintf("exec: az %s", strings.Join(cmdargs, " "))
helm.logger.Debug(cmd)
outBytes, err := helm.runner.Execute("az", cmdargs, map[string]string{})
helm.logger.Debugf("%s: %s", cmd, outBytes)
return outBytes, err
}
func (helm *execer) info(out []byte) {
@@ -380,20 +503,28 @@ func (helm *execer) info(out []byte) {
}
}
func (helm *execer) write(out []byte) {
func (helm *execer) write(w io.Writer, out []byte) {
if len(out) > 0 {
fmt.Printf("%s\n", out)
if w == nil {
w = os.Stdout
}
fmt.Fprintf(w, "%s\n", out)
}
}
func (helm *execer) IsHelm3() bool {
return helm.version.Major == 3
return helm.version.Major() == 3
}
func (helm *execer) GetVersion() Version {
return helm.version
return Version{
Major: int(helm.version.Major()),
Minor: int(helm.version.Minor()),
Patch: int(helm.version.Patch()),
}
}
func (helm *execer) IsVersionAtLeast(major int, minor int) bool {
return helm.version.Major >= major && helm.version.Minor >= minor
func (helm *execer) IsVersionAtLeast(versionStr string) bool {
ver := semver.MustParse(versionStr)
return helm.version.Equal(ver) || helm.version.GreaterThan(ver)
}
+118 -75
View File
@@ -3,12 +3,16 @@ package helmexec
import (
"bytes"
"fmt"
"io"
"os"
"path"
"path/filepath"
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/Masterminds/semver/v3"
"go.uber.org/zap"
)
@@ -19,6 +23,10 @@ type mockRunner struct {
err error
}
func (mock *mockRunner) ExecuteStdIn(cmd string, args []string, env map[string]string, stdin io.Reader) ([]byte, error) {
return mock.output, mock.err
}
func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string) ([]byte, error) {
return mock.output, mock.err
}
@@ -71,51 +79,84 @@ func Test_SetHelmBinary(t *testing.T) {
}
}
func Test_AddRepo_Helm_3_3_2(t *testing.T) {
var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug")
helm := &execer{
helmBinary: "helm",
version: *semver.MustParse("3.3.2"),
logger: logger,
kubeContext: "dev",
runner: &mockRunner{},
}
helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "", "")
expected := `Adding repo myRepo https://repo.example.com/
exec: helm --kube-context dev repo add myRepo https://repo.example.com/ --force-update --cert-file cert.pem --key-file key.pem
`
if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
}
func Test_AddRepo(t *testing.T) {
var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "")
helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "", "")
expected := `Adding repo myRepo https://repo.example.com/
exec: helm repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem --kube-context dev
exec: helm repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem --kube-context dev:
exec: helm --kube-context dev repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem
`
if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
buffer.Reset()
helm.AddRepo("myRepo", "https://repo.example.com/", "ca.crt", "", "", "", "")
helm.AddRepo("myRepo", "https://repo.example.com/", "ca.crt", "", "", "", "", "")
expected = `Adding repo myRepo https://repo.example.com/
exec: helm repo add myRepo https://repo.example.com/ --ca-file ca.crt --kube-context dev
exec: helm repo add myRepo https://repo.example.com/ --ca-file ca.crt --kube-context dev:
exec: helm --kube-context dev repo add myRepo https://repo.example.com/ --ca-file ca.crt
`
if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
buffer.Reset()
helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "", "")
helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "", "", "")
expected = `Adding repo myRepo https://repo.example.com/
exec: helm repo add myRepo https://repo.example.com/ --kube-context dev
exec: helm repo add myRepo https://repo.example.com/ --kube-context dev:
exec: helm --kube-context dev repo add myRepo https://repo.example.com/
`
if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
buffer.Reset()
helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password")
helm.AddRepo("acrRepo", "", "", "", "", "", "", "acr")
expected = `Adding repo acrRepo (acr)
exec: az acr helm repo add --name acrRepo
exec: az acr helm repo add --name acrRepo:
`
if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
buffer.Reset()
helm.AddRepo("otherRepo", "", "", "", "", "", "", "unknown")
expected = `ERROR: unknown type 'unknown' for repository otherRepo
`
if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
buffer.Reset()
helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password", "")
expected = `Adding repo myRepo https://repo.example.com/
exec: helm repo add myRepo https://repo.example.com/ --username example_user --password example_password --kube-context dev
exec: helm repo add myRepo https://repo.example.com/ --username example_user --password example_password --kube-context dev:
exec: helm --kube-context dev repo add myRepo https://repo.example.com/ --username example_user --password example_password
`
if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
buffer.Reset()
helm.AddRepo("", "https://repo.example.com/", "", "", "", "", "")
helm.AddRepo("", "https://repo.example.com/", "", "", "", "", "", "")
expected = `empty field name
`
@@ -130,8 +171,7 @@ func Test_UpdateRepo(t *testing.T) {
helm := MockExecer(logger, "dev")
helm.UpdateRepo()
expected := `Updating repo
exec: helm repo update --kube-context dev
exec: helm repo update --kube-context dev:
exec: helm --kube-context dev repo update
`
if buffer.String() != expected {
t.Errorf("helmexec.UpdateRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -142,10 +182,9 @@ func Test_SyncRelease(t *testing.T) {
var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.SyncRelease(HelmContext{}, "release", "chart", "--timeout 10", "--wait")
helm.SyncRelease(HelmContext{}, "release", "chart", "--timeout 10", "--wait", "--wait-for-jobs")
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:
exec: helm --kube-context dev upgrade --install --reset-values release chart --timeout 10 --wait --wait-for-jobs
`
if buffer.String() != expected {
t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -154,8 +193,7 @@ exec: helm upgrade --install --reset-values release chart --timeout 10 --wait --
buffer.Reset()
helm.SyncRelease(HelmContext{}, "release", "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:
exec: helm --kube-context dev upgrade --install --reset-values release chart
`
if buffer.String() != expected {
t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -167,10 +205,9 @@ func Test_SyncReleaseTillerless(t *testing.T) {
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.SyncRelease(HelmContext{Tillerless: true, TillerNamespace: "foo"}, "release", "chart",
"--timeout 10", "--wait")
"--timeout 10", "--wait", "--wait-for-jobs")
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:
exec: helm --kube-context dev tiller run foo -- helm upgrade --install --reset-values release chart --timeout 10 --wait --wait-for-jobs
`
if buffer.String() != expected {
t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -183,8 +220,7 @@ func Test_UpdateDeps(t *testing.T) {
helm := MockExecer(logger, "dev")
helm.UpdateDeps("./chart/foo")
expected := `Updating dependency ./chart/foo
exec: helm dependency update ./chart/foo --kube-context dev
exec: helm dependency update ./chart/foo --kube-context dev:
exec: helm --kube-context dev dependency update ./chart/foo
`
if buffer.String() != expected {
t.Errorf("helmexec.UpdateDeps()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -194,8 +230,7 @@ exec: helm dependency update ./chart/foo --kube-context dev:
helm.SetExtraArgs("--verify")
helm.UpdateDeps("./chart/foo")
expected = `Updating dependency ./chart/foo
exec: helm dependency update ./chart/foo --verify --kube-context dev
exec: helm dependency update ./chart/foo --verify --kube-context dev:
exec: helm --kube-context dev dependency update ./chart/foo --verify
`
if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -208,8 +243,7 @@ func Test_BuildDeps(t *testing.T) {
helm := MockExecer(logger, "dev")
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:
exec: helm --kube-context dev dependency build ./chart/foo
`
if buffer.String() != expected {
t.Errorf("helmexec.BuildDeps()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -219,8 +253,7 @@ exec: helm dependency build ./chart/foo --kube-context dev:
helm.SetExtraArgs("--verify")
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:
exec: helm --kube-context dev dependency build ./chart/foo --verify
`
if buffer.String() != expected {
t.Errorf("helmexec.BuildDeps()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -231,6 +264,12 @@ func Test_DecryptSecret(t *testing.T) {
var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
tmpFilePath := "path/to/temp/file"
helm.writeTempFile = func(content []byte) (string, error) {
return tmpFilePath, nil
}
helm.DecryptSecret(HelmContext{}, "secretName")
cwd, err := filepath.Abs(".")
if err != nil {
@@ -241,13 +280,35 @@ func Test_DecryptSecret(t *testing.T) {
expected := fmt.Sprintf(`Preparing to decrypt secret %v/secretName
Decrypting secret %s/secretName
exec: helm secrets dec %s/secretName --kube-context dev
exec: helm secrets dec %s/secretName --kube-context dev:
exec: helm --kube-context dev secrets dec %s/secretName
Preparing to decrypt secret %s/secretName
Found secret in cache %s/secretName
`, cwd, cwd, cwd, cwd, cwd, cwd)
if buffer.String() != expected {
t.Errorf("helmexec.DecryptSecret()\nactual = %v\nexpect = %v", buffer.String(), expected)
`, cwd, cwd, cwd, cwd, cwd)
if d := cmp.Diff(expected, buffer.String()); d != "" {
t.Errorf("helmexec.DecryptSecret(): want (-), got (+):\n%s", d)
}
}
func Test_DecryptSecretWithGotmpl(t *testing.T) {
var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
tmpFilePath := "path/to/temp/file"
helm.writeTempFile = func(content []byte) (string, error) {
return tmpFilePath, nil
}
secretName := "secretName.yaml.gotmpl"
_, decryptErr := helm.DecryptSecret(HelmContext{}, secretName)
cwd, err := filepath.Abs(".")
if err != nil {
t.Errorf("Error: %v", err)
}
expected := fmt.Sprintf(`%s/%s.yaml.dec`, cwd, secretName)
if d := cmp.Diff(expected, decryptErr.(*os.PathError).Path); d != "" {
t.Errorf("helmexec.DecryptSecret(): want (-), got (+):\n%s", d)
}
}
@@ -255,10 +316,9 @@ func Test_DiffRelease(t *testing.T) {
var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.DiffRelease(HelmContext{}, "release", "chart", false, "--timeout 10", "--wait")
helm.DiffRelease(HelmContext{}, "release", "chart", false, "--timeout 10", "--wait", "--wait-for-jobs")
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:
exec: helm --kube-context dev diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --wait-for-jobs
`
if buffer.String() != expected {
t.Errorf("helmexec.DiffRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -267,8 +327,7 @@ exec: helm diff upgrade --reset-values --allow-unreleased release chart --timeou
buffer.Reset()
helm.DiffRelease(HelmContext{}, "release", "chart", false)
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:
exec: helm --kube-context dev diff upgrade --reset-values --allow-unreleased release chart
`
if buffer.String() != expected {
t.Errorf("helmexec.DiffRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -279,10 +338,9 @@ func Test_DiffReleaseTillerless(t *testing.T) {
var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
helm.DiffRelease(HelmContext{Tillerless: true}, "release", "chart", false, "--timeout 10", "--wait")
helm.DiffRelease(HelmContext{Tillerless: true}, "release", "chart", false, "--timeout 10", "--wait", "--wait-for-jobs")
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:
exec: helm --kube-context dev tiller run -- helm diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --wait-for-jobs
`
if buffer.String() != expected {
t.Errorf("helmexec.DiffRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -295,8 +353,7 @@ func Test_DeleteRelease(t *testing.T) {
helm := MockExecer(logger, "dev")
helm.DeleteRelease(HelmContext{}, "release")
expected := `Deleting release
exec: helm delete release --kube-context dev
exec: helm delete release --kube-context dev:
exec: helm --kube-context dev delete release
`
if buffer.String() != expected {
t.Errorf("helmexec.DeleteRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -308,8 +365,7 @@ func Test_DeleteRelease_Flags(t *testing.T) {
helm := MockExecer(logger, "dev")
helm.DeleteRelease(HelmContext{}, "release", "--purge")
expected := `Deleting release
exec: helm delete release --purge --kube-context dev
exec: helm delete release --purge --kube-context dev:
exec: helm --kube-context dev delete release --purge
`
if buffer.String() != expected {
t.Errorf("helmexec.DeleteRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -322,8 +378,7 @@ func Test_TestRelease(t *testing.T) {
helm := MockExecer(logger, "dev")
helm.TestRelease(HelmContext{}, "release")
expected := `Testing release
exec: helm test release --kube-context dev
exec: helm test release --kube-context dev:
exec: helm --kube-context dev test release
`
if buffer.String() != expected {
t.Errorf("helmexec.TestRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -335,8 +390,7 @@ func Test_TestRelease_Flags(t *testing.T) {
helm := MockExecer(logger, "dev")
helm.TestRelease(HelmContext{}, "release", "--cleanup", "--timeout", "60")
expected := `Testing release
exec: helm test release --cleanup --timeout 60 --kube-context dev
exec: helm test release --cleanup --timeout 60 --kube-context dev:
exec: helm --kube-context dev test release --cleanup --timeout 60
`
if buffer.String() != expected {
t.Errorf("helmexec.TestRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -349,8 +403,7 @@ func Test_ReleaseStatus(t *testing.T) {
helm := MockExecer(logger, "dev")
helm.ReleaseStatus(HelmContext{}, "myRelease")
expected := `Getting status myRelease
exec: helm status myRelease --kube-context dev
exec: helm status myRelease --kube-context dev:
exec: helm --kube-context dev status myRelease
`
if buffer.String() != expected {
t.Errorf("helmexec.ReleaseStatus()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -364,7 +417,6 @@ func Test_exec(t *testing.T) {
env := map[string]string{}
helm.exec([]string{"version"}, env)
expected := `exec: helm version
exec: helm version:
`
if buffer.String() != expected {
t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -378,9 +430,8 @@ exec: helm version:
buffer.Reset()
helm = MockExecer(logger, "dev")
helm.exec([]string{"diff", "release", "chart", "--timeout 10", "--wait"}, env)
expected = `exec: helm diff release chart --timeout 10 --wait --kube-context dev
exec: helm diff release chart --timeout 10 --wait --kube-context dev:
helm.exec([]string{"diff", "release", "chart", "--timeout 10", "--wait", "--wait-for-jobs"}, env)
expected = `exec: helm --kube-context dev diff release chart --timeout 10 --wait --wait-for-jobs
`
if buffer.String() != expected {
t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -388,8 +439,7 @@ exec: helm diff release chart --timeout 10 --wait --kube-context dev:
buffer.Reset()
helm.exec([]string{"version"}, env)
expected = `exec: helm version --kube-context dev
exec: helm version --kube-context dev:
expected = `exec: helm --kube-context dev version
`
if buffer.String() != expected {
t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -398,8 +448,7 @@ exec: helm version --kube-context dev:
buffer.Reset()
helm.SetExtraArgs("foo")
helm.exec([]string{"version"}, env)
expected = `exec: helm version foo --kube-context dev
exec: helm version foo --kube-context dev:
expected = `exec: helm --kube-context dev version foo
`
if buffer.String() != expected {
t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -410,7 +459,6 @@ exec: helm version foo --kube-context dev:
helm.SetHelmBinary("overwritten")
helm.exec([]string{"version"}, env)
expected = `exec: overwritten version
exec: overwritten version:
`
if buffer.String() != expected {
t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -423,8 +471,7 @@ func Test_Lint(t *testing.T) {
helm := MockExecer(logger, "dev")
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:
exec: helm --kube-context dev lint path/to/chart --values file.yml
`
if buffer.String() != expected {
t.Errorf("helmexec.Lint()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -437,8 +484,7 @@ func Test_Fetch(t *testing.T) {
helm := MockExecer(logger, "dev")
helm.Fetch("chart", "--version", "1.2.3", "--untar", "--untardir", "/tmp/dir")
expected := `Fetching chart
exec: helm fetch chart --version 1.2.3 --untar --untardir /tmp/dir --kube-context dev
exec: helm fetch chart --version 1.2.3 --untar --untardir /tmp/dir --kube-context dev:
exec: helm --kube-context dev fetch chart --version 1.2.3 --untar --untardir /tmp/dir
`
if buffer.String() != expected {
t.Errorf("helmexec.Lint()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -448,7 +494,6 @@ exec: helm fetch chart --version 1.2.3 --untar --untardir /tmp/dir --kube-contex
var logLevelTests = map[string]string{
"debug": `Adding repo myRepo https://repo.example.com/
exec: helm repo add myRepo https://repo.example.com/ --username example_user --password example_password
exec: helm repo add myRepo https://repo.example.com/ --username example_user --password example_password:
`,
"info": `Adding repo myRepo https://repo.example.com/
`,
@@ -461,7 +506,7 @@ func Test_LogLevels(t *testing.T) {
buffer.Reset()
logger := NewLogger(&buffer, logLevel)
helm := MockExecer(logger, "")
helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password")
helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password", "")
if buffer.String() != expected {
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
}
@@ -508,8 +553,7 @@ func Test_Template(t *testing.T) {
helm := MockExecer(logger, "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:
exec: helm --kube-context dev template path/to/chart --name release --values file.yml
`
if buffer.String() != expected {
t.Errorf("helmexec.Template()\nactual = %v\nexpect = %v", buffer.String(), expected)
@@ -557,16 +601,15 @@ func Test_GetVersion(t *testing.T) {
func Test_IsVersionAtLeast(t *testing.T) {
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")}
helm := New("helm", NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
if !helm.IsVersionAtLeast(2, 1) {
if !helm.IsVersionAtLeast("2.1.0") {
t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1")
}
if helm.IsVersionAtLeast(2, 19) {
if helm.IsVersionAtLeast("2.19.0") {
t.Error("helmexec.IsVersionAtLeast - 2.16.1 is atleast 2.19")
}
if helm.IsVersionAtLeast(3, 2) {
if helm.IsVersionAtLeast("3.2.0") {
t.Error("helmexec.IsVersionAtLeast - 2.16.1 is atleast 3.2")
}
}
+42 -6
View File
@@ -5,19 +5,55 @@ import (
"strings"
)
func newExitError(helmCmdPath string, exitStatus int, errorMessage string) ExitError {
func newExitError(path string, args []string, exitStatus int, err error, stderr, combined string) ExitError {
var out string
out += fmt.Sprintf("PATH:\n%s", Indent(path, " "))
out += "\n\nARGS:"
for i, a := range args {
out += fmt.Sprintf("\n%s", Indent(fmt.Sprintf("%d: %s (%d bytes)", i, a, len(a)), " "))
}
out += fmt.Sprintf("\n\nERROR:\n%s", Indent(err.Error(), " "))
out += fmt.Sprintf("\n\nEXIT STATUS\n%s", Indent(fmt.Sprintf("%d", exitStatus), " "))
if len(stderr) > 0 {
out += fmt.Sprintf("\n\nSTDERR:\n%s", Indent(stderr, " "))
}
if len(combined) > 0 {
out += fmt.Sprintf("\n\nCOMBINED OUTPUT:\n%s", Indent(combined, " "))
}
return ExitError{
Message: fmt.Sprintf("the following cmd exited with status %d:\n%s\n\n%s", exitStatus, indent(strings.TrimSpace(helmCmdPath)), indent(strings.TrimSpace(errorMessage))),
Message: fmt.Sprintf("command %q exited with non-zero status:\n\n%s", path, out),
Code: exitStatus,
}
}
func indent(text string) string {
// indents a block of text with an indent string
func Indent(text, indent string) string {
var b strings.Builder
b.Grow(len(text) * 2)
lines := strings.Split(text, "\n")
for i := range lines {
lines[i] = " " + lines[i]
last := len(lines) - 1
for i, j := range lines {
if i > 0 && i < last && j != "" {
b.WriteString("\n")
}
if j != "" {
b.WriteString(indent + j)
}
}
return strings.Join(lines, "\n")
return b.String()
}
// ExitError is created whenever your shell command exits with a non-zero exit status
+5 -2
View File
@@ -12,14 +12,17 @@ type Interface interface {
SetExtraArgs(args ...string)
SetHelmBinary(bin string)
AddRepo(name, repository, cafile, certfile, keyfile, username, password string) error
AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string) error
UpdateRepo() error
RegistryLogin(name string, username string, password 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, suppressDiff bool, flags ...string) error
TemplateRelease(name, chart string, flags ...string) error
Fetch(chart string, flags ...string) error
ChartPull(chart string, flags ...string) error
ChartExport(chart string, path 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
@@ -28,7 +31,7 @@ type Interface interface {
DecryptSecret(context HelmContext, name string, flags ...string) (string, error)
IsHelm3() bool
GetVersion() Version
IsVersionAtLeast(major int, minor int) bool
IsVersionAtLeast(versionStr string) bool
}
type DependencyUpdater interface {
+13
View File
@@ -0,0 +1,13 @@
package helmexec
import "math/rand"
var executionIDComponents = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func newExecutionID() string {
b := make([]rune, 5)
for i := range b {
b[i] = executionIDComponents[rand.Intn(len(executionIDComponents))]
}
return string(b)
}
+27
View File
@@ -0,0 +1,27 @@
package helmexec
import (
"go.uber.org/zap"
"strings"
)
type logWriterGenerator struct {
log *zap.SugaredLogger
}
func (g logWriterGenerator) Writer(prefix string) *logWriter {
return &logWriter{
log: g.log,
prefix: prefix,
}
}
type logWriter struct {
log *zap.SugaredLogger
prefix string
}
func (w *logWriter) Write(p []byte) (int, error) {
w.log.Debugf("%s%s", w.prefix, strings.TrimSpace(string(p)))
return len(p), nil
}
+46 -12
View File
@@ -4,11 +4,14 @@ import (
"bytes"
"errors"
"fmt"
"go.uber.org/zap"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"go.uber.org/zap"
)
const (
@@ -19,6 +22,7 @@ const (
// Runner interface for shell commands
type Runner interface {
Execute(cmd string, args []string, env map[string]string) ([]byte, error)
ExecuteStdIn(cmd string, args []string, env map[string]string, stdin io.Reader) ([]byte, error)
}
// ShellRunner implemention for shell commands
@@ -33,24 +37,46 @@ func (shell ShellRunner) Execute(cmd string, args []string, env map[string]strin
preparedCmd := exec.Command(cmd, args...)
preparedCmd.Dir = shell.Dir
preparedCmd.Env = mergeEnv(os.Environ(), env)
return combinedOutput(preparedCmd, shell.Logger)
return Output(preparedCmd, &logWriterGenerator{
log: shell.Logger,
})
}
func combinedOutput(c *exec.Cmd, logger *zap.SugaredLogger) ([]byte, error) {
// Execute a shell command
func (shell ShellRunner) ExecuteStdIn(cmd string, args []string, env map[string]string, stdin io.Reader) ([]byte, error) {
preparedCmd := exec.Command(cmd, args...)
preparedCmd.Dir = shell.Dir
preparedCmd.Env = mergeEnv(os.Environ(), env)
preparedCmd.Stdin = stdin
return Output(preparedCmd, &logWriterGenerator{
log: shell.Logger,
})
}
func Output(c *exec.Cmd, logWriterGenerators ...*logWriterGenerator) ([]byte, error) {
if c.Stdout != nil {
return nil, errors.New("exec: Stdout already set")
}
if c.Stderr != nil {
return nil, errors.New("exec: Stderr already set")
}
var stdout bytes.Buffer
var stderr bytes.Buffer
c.Stdout = &stdout
c.Stderr = &stderr
err := c.Run()
var combined bytes.Buffer
o := stdout.Bytes()
e := stderr.Bytes()
var logWriters []io.Writer
id := newExecutionID()
for _, g := range logWriterGenerators {
logPrefix := fmt.Sprintf("%s:%s> ", filepath.Base(c.Path), id)
logWriters = append(logWriters, g.Writer(logPrefix))
}
c.Stdout = io.MultiWriter(append([]io.Writer{&stdout, &combined}, logWriters...)...)
c.Stderr = io.MultiWriter(append([]io.Writer{&stderr, &combined}, logWriters...)...)
err := c.Run()
if err != nil {
// TrimSpace is necessary, because otherwise helmfile prints the redundant new-lines after each error like:
@@ -64,14 +90,13 @@ func combinedOutput(c *exec.Cmd, logger *zap.SugaredLogger) ([]byte, error) {
// so that helmfile could return its own exit code accordingly
waitStatus := ee.Sys().(syscall.WaitStatus)
exitStatus := waitStatus.ExitStatus()
cmd := fmt.Sprintf("%s %s", c.Path, strings.Join(c.Args, " "))
err = newExitError(cmd, exitStatus, string(e))
err = newExitError(c.Path, c.Args, exitStatus, ee, stderr.String(), combined.String())
default:
panic(fmt.Sprintf("unexpected error: %v", err))
}
}
return o, err
return stdout.Bytes(), err
}
func mergeEnv(orig []string, new map[string]string) []string {
@@ -94,7 +119,16 @@ func env2map(env []string) map[string]string {
wanted := map[string]string{}
for _, cur := range env {
pair := strings.SplitN(cur, "=", 2)
wanted[pair[0]] = pair[1]
var v string
// An environment can completely miss `=` and the right side.
// If we didn't deal with that, this may fail due to an index-out-of-range error
if len(pair) > 1 {
v = pair[1]
}
wanted[pair[0]] = v
}
return wanted
}
+18 -1
View File
@@ -149,7 +149,7 @@ func Parse(goGetterSrc string) (*Source, error) {
}, nil
}
func (r *Remote) Fetch(goGetterSrc string) (string, error) {
func (r *Remote) Fetch(goGetterSrc string, cacheDirOpt ...string) (string, error) {
u, err := Parse(goGetterSrc)
if err != nil {
return "", err
@@ -167,6 +167,11 @@ func (r *Remote) Fetch(goGetterSrc string) (string, error) {
// This should be shared across variant commands, so that they can share cache for the shared imports
cacheBaseDir := DefaultCacheDir
if len(cacheDirOpt) == 1 {
cacheBaseDir = cacheDirOpt[0]
} else if len(cacheDirOpt) > 0 {
return "", fmt.Errorf("[bug] cacheDirOpt's length: want 0 or 1, got %d", len(cacheDirOpt))
}
query := u.RawQuery
@@ -260,3 +265,15 @@ func (g *GoGetter) Get(wd, src, dst string) error {
return nil
}
func NewRemote(logger *zap.SugaredLogger, homeDir string, readFile func(string) ([]byte, error), dirExists func(string) bool, fileExists func(string) bool) *Remote {
remote := &Remote{
Logger: logger,
Home: homeDir,
Getter: &GoGetter{Logger: logger},
ReadFile: readFile,
DirExists: dirExists,
FileExists: fileExists,
}
return remote
}
+83 -12
View File
@@ -2,18 +2,20 @@ package remote
import (
"fmt"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/testhelper"
"os"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/testhelper"
)
func TestRemote_HttpsGitHub(t *testing.T) {
cleanfs := map[string]string{
"path/to/home": "",
"/path/to/home": "",
}
cachefs := map[string]string{
"path/to/home/.helmfile/cache/https_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml": "foo: bar",
"/path/to/home/.helmfile/cache/https_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml": "foo: bar",
}
type testcase struct {
@@ -35,7 +37,7 @@ func TestRemote_HttpsGitHub(t *testing.T) {
hit := true
get := func(wd, src, dst string) error {
if wd != "path/to/home" {
if wd != "/path/to/home" {
return fmt.Errorf("unexpected wd: %s", wd)
}
if src != "git::https://github.com/cloudposse/helmfiles.git?ref=0.40.0" {
@@ -52,7 +54,7 @@ func TestRemote_HttpsGitHub(t *testing.T) {
}
remote := &Remote{
Logger: helmexec.NewLogger(os.Stderr, "debug"),
Home: "path/to/home",
Home: "/path/to/home",
Getter: getter,
ReadFile: testfs.ReadFile,
FileExists: testfs.FileExistsAt,
@@ -71,7 +73,7 @@ func TestRemote_HttpsGitHub(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
if file != "path/to/home/.helmfile/cache/https_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml" {
if file != "/path/to/home/.helmfile/cache/https_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml" {
t.Errorf("unexpected file located: %s", file)
}
@@ -87,10 +89,10 @@ func TestRemote_HttpsGitHub(t *testing.T) {
func TestRemote_SShGitHub(t *testing.T) {
cleanfs := map[string]string{
"path/to/home": "",
"/path/to/home": "",
}
cachefs := map[string]string{
"path/to/home/.helmfile/cache/ssh_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml": "foo: bar",
"/path/to/home/.helmfile/cache/ssh_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml": "foo: bar",
}
type testcase struct {
@@ -112,7 +114,7 @@ func TestRemote_SShGitHub(t *testing.T) {
hit := true
get := func(wd, src, dst string) error {
if wd != "path/to/home" {
if wd != "/path/to/home" {
return fmt.Errorf("unexpected wd: %s", wd)
}
if src != "git::ssh://git@github.com/cloudposse/helmfiles.git?ref=0.40.0" {
@@ -129,7 +131,7 @@ func TestRemote_SShGitHub(t *testing.T) {
}
remote := &Remote{
Logger: helmexec.NewLogger(os.Stderr, "debug"),
Home: "path/to/home",
Home: "/path/to/home",
Getter: getter,
ReadFile: testfs.ReadFile,
FileExists: testfs.FileExistsAt,
@@ -142,7 +144,7 @@ func TestRemote_SShGitHub(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
if file != "path/to/home/.helmfile/cache/ssh_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml" {
if file != "/path/to/home/.helmfile/cache/ssh_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml" {
t.Errorf("unexpected file located: %s", file)
}
@@ -156,6 +158,75 @@ func TestRemote_SShGitHub(t *testing.T) {
}
}
func TestParse(t *testing.T) {
type testcase struct {
input string
getter, scheme, dir, file, query string
err string
}
testcases := []testcase{
{
input: "raw/incubator",
err: "parse url: missing scheme - probably this is a local file path? raw/incubator",
},
{
input: "git::https://github.com/stakater/Forecastle.git@deployments/kubernetes/chart/forecastle?ref=v1.0.54",
getter: "git",
scheme: "https",
dir: "/stakater/Forecastle.git",
file: "deployments/kubernetes/chart/forecastle",
query: "ref=v1.0.54",
},
}
for i := range testcases {
tc := testcases[i]
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
src, err := Parse(tc.input)
var errMsg string
if err != nil {
errMsg = err.Error()
}
if diff := cmp.Diff(tc.err, errMsg); diff != "" {
t.Fatalf("Unexpected error:\n%s", diff)
}
var getter, scheme, dir, file, query string
if src != nil {
getter = src.Getter
scheme = src.Scheme
dir = src.Dir
file = src.File
query = src.RawQuery
}
if diff := cmp.Diff(tc.getter, getter); diff != "" {
t.Fatalf("Unexpected getter:\n%s", diff)
}
if diff := cmp.Diff(tc.scheme, scheme); diff != "" {
t.Fatalf("Unexpected scheme:\n%s", diff)
}
if diff := cmp.Diff(tc.file, file); diff != "" {
t.Fatalf("Unexpected file:\n%s", diff)
}
if diff := cmp.Diff(tc.dir, dir); diff != "" {
t.Fatalf("Unexpected dir:\n%s", diff)
}
if diff := cmp.Diff(tc.query, query); diff != "" {
t.Fatalf("Unexpected query:\n%s", diff)
}
})
}
}
type testGetter struct {
get func(wd, src, dst string) error
}
+9 -8
View File
@@ -2,18 +2,19 @@ package state
import (
"fmt"
"github.com/Masterminds/semver"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/Masterminds/semver/v3"
goversion "github.com/hashicorp/go-version"
"github.com/r3labs/diff"
"github.com/roboll/helmfile/pkg/app/version"
"github.com/roboll/helmfile/pkg/helmexec"
"go.uber.org/zap"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
)
type ChartMeta struct {
@@ -129,7 +130,7 @@ func (d *ResolvedDependencies) Get(chart, versionConstraint string) (string, err
}
}
}
return "", fmt.Errorf("no resolved dependency found for \"%s\"", chart)
return "", fmt.Errorf("no resolved dependency found for \"%s\", running \"helmfile deps\" may resolve the issue", chart)
}
func (st *HelmState) mergeLockedDependencies() (*HelmState, error) {
@@ -254,7 +255,7 @@ func updateDependencies(st *HelmState, shell helmexec.DependencyUpdater, unresol
_, err := depMan.Update(shell, wd, unresolved)
if err != nil {
return nil, fmt.Errorf("unable to resolve %d deps: %v", len(unresolved.deps), err)
return nil, fmt.Errorf("unable to update %d deps: %v", len(unresolved.deps), err)
}
return resolveDependencies(st, depMan, unresolved)
+71 -29
View File
@@ -4,10 +4,12 @@ import (
"bytes"
"errors"
"fmt"
"github.com/roboll/helmfile/pkg/helmexec"
"io"
"os"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/remote"
"github.com/imdario/mergo"
"github.com/roboll/helmfile/pkg/environment"
"github.com/roboll/helmfile/pkg/maputil"
@@ -38,11 +40,15 @@ func (e *UndefinedEnvError) Error() string {
}
type StateCreator struct {
logger *zap.SugaredLogger
readFile func(string) ([]byte, error)
fileExists func(string) (bool, error)
abs func(string) (string, error)
glob func(string) ([]string, error)
logger *zap.SugaredLogger
readFile func(string) ([]byte, error)
fileExists func(string) (bool, error)
abs func(string) (string, error)
glob func(string) ([]string, error)
DeleteFile func(string) error
directoryExistsAt func(string) bool
valsRuntime vals.Evaluator
Strict bool
@@ -52,20 +58,27 @@ type StateCreator struct {
getHelm func(*HelmState) helmexec.Interface
overrideHelmBinary string
remote *remote.Remote
}
func NewCreator(logger *zap.SugaredLogger, readFile func(string) ([]byte, error), fileExists func(string) (bool, error), abs func(string) (string, error), glob func(string) ([]string, error), valsRuntime vals.Evaluator, getHelm func(*HelmState) helmexec.Interface, overrideHelmBinary string) *StateCreator {
func NewCreator(logger *zap.SugaredLogger, readFile func(string) ([]byte, error), fileExists func(string) (bool, error), abs func(string) (string, error), glob func(string) ([]string, error), directoryExistsAt func(string) bool, valsRuntime vals.Evaluator, getHelm func(*HelmState) helmexec.Interface, overrideHelmBinary string, remote *remote.Remote) *StateCreator {
return &StateCreator{
logger: logger,
readFile: readFile,
fileExists: fileExists,
abs: abs,
glob: glob,
logger: logger,
readFile: readFile,
fileExists: fileExists,
abs: abs,
glob: glob,
directoryExistsAt: directoryExistsAt,
Strict: true,
valsRuntime: valsRuntime,
getHelm: getHelm,
overrideHelmBinary: overrideHelmBinary,
remote: remote,
}
}
@@ -125,6 +138,7 @@ func (c *StateCreator) Parse(content []byte, baseDir, file string) (*HelmState,
state.removeFile = os.Remove
state.fileExists = c.fileExists
state.glob = c.glob
state.directoryExistsAt = c.directoryExistsAt
state.valsRuntime = c.valsRuntime
return &state, nil
@@ -139,11 +153,15 @@ func (c *StateCreator) LoadEnvValues(target *HelmState, env string, ctxEnv *envi
return nil, &StateLoadError{fmt.Sprintf("failed to read %s", state.FilePath), err}
}
e.Defaults, err = state.loadValuesEntries(nil, state.DefaultValues)
newDefaults, err := state.loadValuesEntries(nil, state.DefaultValues, c.remote)
if err != nil {
return nil, err
}
if err := mergo.Merge(&e.Defaults, newDefaults, mergo.WithOverride, mergo.WithOverwriteWithEmptyValue); err != nil {
return nil, err
}
state.Env = *e
return &state, nil
@@ -175,6 +193,12 @@ func (c *StateCreator) ParseAndLoad(content []byte, baseDir, file string, envNam
state.FilePath = file
vals, err := state.Env.GetMergedValues()
if err != nil {
return nil, fmt.Errorf("rendering values: %w", err)
}
state.RenderedValues = vals
return state, nil
}
@@ -203,7 +227,7 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn
envSpec, ok := st.Environments[name]
if ok {
var err error
envVals, err = st.loadValuesEntries(envSpec.MissingFileHandler, envSpec.Values)
envVals, err = st.loadValuesEntries(envSpec.MissingFileHandler, envSpec.Values, c.remote)
if err != nil {
return nil, err
}
@@ -248,42 +272,54 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn
func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles []string, envVals map[string]interface{}, readFile func(string) ([]byte, error)) error {
var errs []error
helm := c.getHelm(st)
inputs := envSecretFiles
inputsSize := len(inputs)
type secretResult struct {
id int
result map[string]interface{}
err error
path string
}
secrets := make(chan string, inputsSize)
type secretInput struct {
id int
path string
}
secrets := make(chan secretInput, inputsSize)
results := make(chan secretResult, inputsSize)
st.scatterGather(0, inputsSize,
func() {
for _, secretFile := range envSecretFiles {
secrets <- secretFile
for i, secretFile := range envSecretFiles {
secrets <- secretInput{i, secretFile}
}
close(secrets)
},
func(id int) {
for path := range secrets {
for secret := range secrets {
release := &ReleaseSpec{}
flags := st.appendConnectionFlags([]string{}, release)
decFile, err := c.getHelm(st).DecryptSecret(st.createHelmContext(release, 0), path, flags...)
flags := st.appendConnectionFlags([]string{}, helm, release)
decFile, err := helm.DecryptSecret(st.createHelmContext(release, 0), secret.path, flags...)
if err != nil {
results <- secretResult{nil, err, path}
results <- secretResult{secret.id, nil, err, secret.path}
continue
}
defer func() {
if err := c.DeleteFile(decFile); err != nil {
c.logger.Warnf("removing decrypted file %s: %w", decFile, err)
}
}()
bytes, err := readFile(decFile)
if err != nil {
results <- secretResult{nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", path, err), path}
results <- secretResult{secret.id, nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", secret.path, err), secret.path}
continue
}
m := map[string]interface{}{}
if err := yaml.Unmarshal(bytes, &m); err != nil {
results <- secretResult{nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", path, err), path}
results <- secretResult{secret.id, nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", secret.path, err), secret.path}
continue
}
// All the nested map key should be string. Otherwise we get strange errors due to that
@@ -291,15 +327,22 @@ func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles
// See https://github.com/roboll/helmfile/issues/677
vals, err := maputil.CastKeysToStrings(m)
if err != nil {
results <- secretResult{nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", path, err), path}
results <- secretResult{secret.id, nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", secret.path, err), secret.path}
continue
}
results <- secretResult{vals, nil, path}
results <- secretResult{secret.id, vals, nil, secret.path}
}
},
func() {
sortedSecrets := make([]secretResult, inputsSize)
for i := 0; i < inputsSize; i++ {
result := <-results
sortedSecrets[result.id] = result
}
close(results)
for _, result := range sortedSecrets {
if result.err != nil {
errs = append(errs, result.err)
} else {
@@ -308,11 +351,10 @@ func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles
}
}
}
close(results)
},
)
if len(errs) > 1 {
if len(errs) > 0 {
for _, err := range errs {
st.logger.Error(err)
}
@@ -321,11 +363,11 @@ func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles
return nil
}
func (st *HelmState) loadValuesEntries(missingFileHandler *string, entries []interface{}) (map[string]interface{}, error) {
func (st *HelmState) loadValuesEntries(missingFileHandler *string, entries []interface{}, remote *remote.Remote) (map[string]interface{}, error) {
envVals := map[string]interface{}{}
valuesEntries := append([]interface{}{}, entries...)
ld := NewEnvironmentValuesLoader(st.storage(), st.readFile, st.logger)
ld := NewEnvironmentValuesLoader(st.storage(), st.readFile, st.logger, remote)
var err error
envVals, err = ld.LoadEnvironmentValues(missingFileHandler, valuesEntries)
if err != nil {
+140 -5
View File
@@ -2,10 +2,13 @@ package state
import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"
"github.com/roboll/helmfile/pkg/remote"
"github.com/roboll/helmfile/pkg/testhelper"
"go.uber.org/zap"
@@ -18,7 +21,9 @@ func createFromYaml(content []byte, file string, env string, logger *zap.Sugared
logger: logger,
readFile: ioutil.ReadFile,
abs: filepath.Abs,
Strict: true,
DeleteFile: os.Remove,
Strict: true,
}
return c.ParseAndLoad(content, filepath.Dir(file), file, env, true, nil)
}
@@ -59,6 +64,35 @@ func TestReadFromYaml_NonexistentEnv(t *testing.T) {
}
}
type stateTestEnv struct {
Files map[string]string
WorkDir string
}
func (testEnv stateTestEnv) MustLoadState(t *testing.T, file, envName string) *HelmState {
t.Helper()
testFs := testhelper.NewTestFs(testEnv.Files)
if testFs.Cwd == "" {
testFs.Cwd = "/"
}
yamlContent, ok := testEnv.Files[file]
if !ok {
t.Fatalf("no file named %q registered", file)
}
r := remote.NewRemote(logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt)
state, err := NewCreator(logger, testFs.ReadFile, testFs.FileExists, testFs.Abs, testFs.Glob, testFs.DirectoryExistsAt, nil, nil, "", r).
ParseAndLoad([]byte(yamlContent), filepath.Dir(file), file, envName, true, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return state
}
func TestReadFromYaml_NonDefaultEnv(t *testing.T) {
yamlFile := "/example/path/to/helmfile.yaml"
yamlContent := []byte(`environments:
@@ -96,9 +130,15 @@ bar: {{ readFile "bar.txt" }}
}
valuesFile := "/example/path/to/values.yaml.gotmpl"
valuesContent := []byte(`env: {{ .Environment.Name }}`)
valuesContent := []byte(`env: {{ .Environment.Name }}
releaseName: {{ .Release.Name }}
releaseNamespace: {{ .Release.Namespace }}
`)
expectedValues := `env: production`
expectedValues := `env: production
releaseName: myrelease
releaseNamespace: mynamespace
`
testFs := testhelper.NewTestFs(map[string]string{
fooYamlFile: string(fooYamlContent),
@@ -108,7 +148,9 @@ bar: {{ readFile "bar.txt" }}
})
testFs.Cwd = "/example/path/to"
state, err := NewCreator(logger, testFs.ReadFile, testFs.FileExists, testFs.Abs, testFs.Glob, nil, nil, "").ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, nil)
r := remote.NewRemote(logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt)
state, err := NewCreator(logger, testFs.ReadFile, testFs.FileExists, testFs.Abs, testFs.Glob, testFs.DirectoryExistsAt, nil, nil, "", r).
ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -118,7 +160,98 @@ bar: {{ readFile "bar.txt" }}
t.Errorf("unexpected environment values: expected=%v, actual=%v", expected, actual)
}
actualValuesData, err := state.RenderValuesFileToBytes(valuesFile)
release := state.Releases[0]
state.ApplyOverrides(&release)
actualValuesData, err := state.RenderReleaseValuesFileToBytes(&release, valuesFile)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
actualValues := string(actualValuesData)
if !reflect.DeepEqual(expectedValues, actualValues) {
t.Errorf("unexpected values: expected=%v, actual=%v", expectedValues, actualValues)
}
}
func TestReadFromYaml_OverrideNamespace(t *testing.T) {
yamlFile := "/example/path/to/helmfile.yaml"
yamlContent := []byte(`environments:
production:
values:
- foo.yaml
- bar.yaml.gotmpl
# A.k.a helmfile apply --namespace myns
namespace: myns
releases:
- name: myrelease
namespace: mynamespace
chart: mychart
values:
- values.yaml.gotmpl
`)
fooYamlFile := "/example/path/to/foo.yaml"
fooYamlContent := []byte(`foo: foo
# As this file doesn't have an file extension ".gotmpl", this template expression should not be evaluated
baz: "{{ readFile \"baz.txt\" }}"`)
barYamlFile := "/example/path/to/bar.yaml.gotmpl"
barYamlContent := []byte(`foo: FOO
bar: {{ readFile "bar.txt" }}
`)
barTextFile := "/example/path/to/bar.txt"
barTextContent := []byte("BAR")
expected := map[string]interface{}{
"foo": "FOO",
"bar": "BAR",
// As the file doesn't have an file extension ".gotmpl", this template expression should not be evaluated
"baz": "{{ readFile \"baz.txt\" }}",
}
valuesFile := "/example/path/to/values.yaml.gotmpl"
valuesContent := []byte(`env: {{ .Environment.Name }}
releaseName: {{ .Release.Name }}
releaseNamespace: {{ .Release.Namespace }}
overrideNamespace: {{ .Namespace }}
`)
expectedValues := `env: production
releaseName: myrelease
releaseNamespace: myns
overrideNamespace: myns
`
testFs := testhelper.NewTestFs(map[string]string{
fooYamlFile: string(fooYamlContent),
barYamlFile: string(barYamlContent),
barTextFile: string(barTextContent),
valuesFile: string(valuesContent),
})
testFs.Cwd = "/example/path/to"
r := remote.NewRemote(logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt)
state, err := NewCreator(logger, testFs.ReadFile, testFs.FileExists, testFs.Abs, testFs.Glob, testFs.DirectoryExistsAt, nil, nil, "", r).
ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
actual := state.Env.Values
if !reflect.DeepEqual(actual, expected) {
t.Errorf("unexpected environment values: expected=%v, actual=%v", expected, actual)
}
release := state.Releases[0]
state.ApplyOverrides(&release)
actualValuesData, err := state.RenderReleaseValuesFileToBytes(&release, valuesFile)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@@ -244,6 +377,8 @@ func TestReadFromYaml_FilterNegatives(t *testing.T) {
[]bool{false, true, false}},
{LabelFilter{negativeLabels: [][]string{[]string{"stage", "pre"}, []string{"stage", "post"}}},
[]bool{false, false, true}},
{LabelFilter{negativeLabels: [][]string{[]string{"foo", "bar"}}},
[]bool{false, true, true}},
}
state, err := createFromYaml(yamlContent, yamlFile, DefaultEnv, logger)
if err != nil {
+3 -2
View File
@@ -1,8 +1,9 @@
package state
type EnvironmentSpec struct {
Values []interface{} `yaml:"values,omitempty"`
Secrets []string `yaml:"secrets,omitempty"`
Values []interface{} `yaml:"values,omitempty"`
Secrets []string `yaml:"secrets,omitempty"`
KubeContext string `yaml:"kubeContext,omitempty"`
// MissingFileHandler instructs helmfile to fail when unable to find a environment values file listed
// under `environments.NAME.values`.
+10 -1
View File
@@ -5,6 +5,7 @@ import (
"github.com/imdario/mergo"
"github.com/roboll/helmfile/pkg/environment"
"github.com/roboll/helmfile/pkg/maputil"
"github.com/roboll/helmfile/pkg/remote"
"github.com/roboll/helmfile/pkg/tmpl"
"go.uber.org/zap"
"gopkg.in/yaml.v2"
@@ -17,13 +18,16 @@ type EnvironmentValuesLoader struct {
readFile func(string) ([]byte, error)
logger *zap.SugaredLogger
remote *remote.Remote
}
func NewEnvironmentValuesLoader(storage *Storage, readFile func(string) ([]byte, error), logger *zap.SugaredLogger) *EnvironmentValuesLoader {
func NewEnvironmentValuesLoader(storage *Storage, readFile func(string) ([]byte, error), logger *zap.SugaredLogger, remote *remote.Remote) *EnvironmentValuesLoader {
return &EnvironmentValuesLoader{
storage: storage,
readFile: readFile,
logger: logger,
remote: remote,
}
}
@@ -36,6 +40,11 @@ func (ld *EnvironmentValuesLoader) LoadEnvironmentValues(missingFileHandler *str
switch strOrMap := entry.(type) {
case string:
urlOrPath := strOrMap
localPath, err := ld.remote.Locate(urlOrPath)
if err == nil {
urlOrPath = localPath
}
files, skipped, err := ld.storage.resolveFile(missingFileHandler, "environment values", urlOrPath)
if err != nil {
return nil, err
+5 -1
View File
@@ -2,6 +2,7 @@ package state
import (
"github.com/google/go-cmp/cmp"
"github.com/roboll/helmfile/pkg/remote"
"go.uber.org/zap"
"io/ioutil"
"path/filepath"
@@ -23,7 +24,10 @@ func newLoader() *EnvironmentValuesLoader {
logger: sugar,
}
return NewEnvironmentValuesLoader(storage, ioutil.ReadFile, sugar)
readFile := func(s string) ([]byte, error) { return []byte{}, nil }
dirExists := func(d string) bool { return false }
fileExists := func(f string) bool { return false }
return NewEnvironmentValuesLoader(storage, ioutil.ReadFile, sugar, remote.NewRemote(sugar, "/tmp", readFile, dirExists, fileExists))
}
// See https://github.com/roboll/helmfile/pull/1169
+141 -39
View File
@@ -1,11 +1,13 @@
package state
import (
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/variantdev/chartify"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/remote"
"github.com/variantdev/chartify"
)
type Dependency struct {
@@ -22,18 +24,85 @@ func (st *HelmState) appendHelmXFlags(flags []string, release *ReleaseSpec) ([]s
return flags, nil
}
func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSpec, workerIndex int) (bool, *chartify.ChartifyOpts, error) {
var opts chartify.ChartifyOpts
func fileExistsAt(path string) bool {
fileInfo, err := os.Stat(path)
return err == nil && fileInfo.Mode().IsRegular()
}
opts.WorkaroundOutputDirIssue = true
func directoryExistsAt(path string) bool {
fileInfo, err := os.Stat(path)
return err == nil && fileInfo.Mode().IsDir()
}
type Chartify struct {
Opts *chartify.ChartifyOpts
Clean func()
}
func (st *HelmState) downloadChartWithGoGetter(r *ReleaseSpec) (string, error) {
pathElems := []string{
remote.DefaultCacheDir,
}
if r.Namespace != "" {
pathElems = append(pathElems, r.Namespace)
}
if r.KubeContext != "" {
pathElems = append(pathElems, r.KubeContext)
}
pathElems = append(pathElems, r.Name)
cacheDir := filepath.Join(pathElems...)
return st.goGetterChart(r.Chart, r.Directory, cacheDir, r.ForceGoGetter)
}
func (st *HelmState) goGetterChart(chart, dir, cacheDir string, force bool) (string, error) {
if dir != "" && chart == "" {
chart = dir
}
_, err := remote.Parse(chart)
if err != nil {
if force {
return "", fmt.Errorf("Parsing url from dir failed due to error %q.\nContinuing the process assuming this is a regular Helm chart or a local dir.", err.Error())
}
} else {
r := remote.NewRemote(st.logger, st.basePath, st.readFile, directoryExistsAt, fileExistsAt)
fetchedDir, err := r.Fetch(chart, cacheDir)
if err != nil {
return "", fmt.Errorf("fetching %q: %v", chart, err)
}
chart = fetchedDir
}
return chart, nil
}
func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSpec, chart string, workerIndex int) (*Chartify, func(), error) {
c := &Chartify{
Opts: &chartify.ChartifyOpts{
WorkaroundOutputDirIssue: true,
EnableKustomizeAlphaPlugins: true,
ChartVersion: release.Version,
Namespace: release.Namespace,
ID: ReleaseToID(release),
},
}
var filesNeedCleaning []string
clean := func() {
st.removeFiles(filesNeedCleaning)
}
var shouldRun bool
opts.EnableKustomizeAlphaPlugins = true
opts.ChartVersion = release.Version
dir := filepath.Join(st.basePath, release.Chart)
dir := filepath.Join(st.basePath, chart)
if stat, _ := os.Stat(dir); stat != nil && stat.IsDir() {
if exists, err := st.fileExists(filepath.Join(dir, "Chart.yaml")); err == nil && !exists {
shouldRun = true
@@ -41,57 +110,79 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
}
for _, d := range release.Dependencies {
var dep string
chart := d.Chart
if st.directoryExistsAt(chart) {
var err error
if d.Alias != "" {
dep += d.Alias + "="
} else {
a := strings.Split(d.Chart, "/")
chart := a[len(a)-1]
dep += chart + "="
// Otherwise helm-dependency-up on the temporary chart generated by chartify ends up errors like:
// Error: directory /tmp/chartify945964195/myapp-57fb4495cf/test/integration/charts/httpbin not found]
// which is due to that the temporary chart is generated outside of the current working directory/basePath,
// and therefore the relative path in `chart` points to somewhere inexistent.
chart, err = filepath.Abs(filepath.Join(st.basePath, chart))
if err != nil {
return nil, clean, err
}
}
dep += d.Chart
if d.Version != "" {
dep += ":" + d.Version
}
opts.AdhocChartDependencies = append(opts.AdhocChartDependencies, dep)
c.Opts.AdhocChartDependencies = append(c.Opts.AdhocChartDependencies, chartify.ChartDependency{
Alias: d.Alias,
Chart: chart,
Version: d.Version,
})
shouldRun = true
}
jsonPatches := release.JSONPatches
if len(jsonPatches) > 0 {
generatedFiles, err := st.generateTemporaryValuesFiles(jsonPatches, release.MissingFileHandler)
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, jsonPatches, release.MissingFileHandler)
if err != nil {
return false, nil, err
return nil, clean, err
}
filesNeedCleaning = append(filesNeedCleaning, generatedFiles...)
for _, f := range generatedFiles {
opts.JsonPatches = append(opts.JsonPatches, f)
c.Opts.JsonPatches = append(c.Opts.JsonPatches, f)
}
release.generatedValues = append(release.generatedValues, generatedFiles...)
shouldRun = true
}
strategicMergePatches := release.StrategicMergePatches
if len(strategicMergePatches) > 0 {
generatedFiles, err := st.generateTemporaryValuesFiles(strategicMergePatches, release.MissingFileHandler)
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, strategicMergePatches, release.MissingFileHandler)
if err != nil {
return false, nil, err
return nil, clean, err
}
for _, f := range generatedFiles {
opts.StrategicMergePatches = append(opts.StrategicMergePatches, f)
c.Opts.StrategicMergePatches = append(c.Opts.StrategicMergePatches, f)
}
release.generatedValues = append(release.generatedValues, generatedFiles...)
filesNeedCleaning = append(filesNeedCleaning, generatedFiles...)
shouldRun = true
}
transformers := release.Transformers
if len(transformers) > 0 {
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, transformers, release.MissingFileHandler)
if err != nil {
return nil, clean, err
}
for _, f := range generatedFiles {
c.Opts.Transformers = append(c.Opts.Transformers, f)
}
filesNeedCleaning = append(filesNeedCleaning, generatedFiles...)
shouldRun = true
}
if release.ForceNamespace != "" {
c.Opts.OverrideNamespace = release.ForceNamespace
shouldRun = true
}
@@ -99,11 +190,22 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
if shouldRun {
generatedFiles, err := st.generateValuesFiles(helm, release, workerIndex)
if err != nil {
return false, nil, err
return nil, clean, err
}
opts.ValuesFiles = generatedFiles
filesNeedCleaning = append(filesNeedCleaning, generatedFiles...)
c.Opts.ValuesFiles = generatedFiles
setFlags, err := st.setFlags(release.SetValues)
if err != nil {
return nil, clean, fmt.Errorf("rendering set value entry for release %s: %v", release.Name, err)
}
c.Opts.SetFlags = setFlags
c.Opts.TemplateData = st.newReleaseTemplateData(release)
c.Opts.TemplateFuncs = st.newReleaseTemplateFuncMap(dir)
return c, clean, nil
}
return shouldRun, &opts, nil
return nil, clean, nil
}
+8 -5
View File
@@ -133,12 +133,15 @@ func (r ReleaseSpec) ExecuteTemplateExpressions(renderer *tmpl.FileRenderer) (*R
}
}
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)
for i, t := range result.Secrets {
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\".secrets[%d] = \"%s\": %v", r.Name, i, ts, err)
}
result.Secrets[i] = s.String()
}
result.Secrets[i] = s.String()
}
if len(result.SetValuesTemplate) > 0 {
+1 -1
View File
@@ -38,7 +38,7 @@ func (l LabelFilter) Match(r ReleaseSpec) bool {
k := element[0]
v := element[1]
if rVal, ok := r.Labels[k]; !ok {
return true
} else if rVal == v {
return false
}
+97
View File
@@ -0,0 +1,97 @@
package state
import (
"github.com/google/go-cmp/cmp"
"testing"
)
func TestSelectReleasesWithOverrides(t *testing.T) {
type testcase struct {
subject string
selector []string
want []string
}
testcases := []testcase{
{
subject: "multiple OR selectors (nillable label first)",
selector: []string{"type=bar", "name=nolabel2", "name=nolabel1"},
want: []string{"nolabel1", "nolabel2", "foo"},
},
{
subject: "multiple OR selectors (non-nillable label first)",
selector: []string{"name=foo", "type!=bar"},
want: []string{"nolabel1", "nolabel2", "foo"},
},
{
subject: "multiple AND conditions (nillable label first)",
selector: []string{"type!=bar,name!=nolabel2"},
want: []string{"nolabel1"},
},
{
subject: "multiple AND conditions (non-nillable label first)",
selector: []string{"name!=nolabel2,type!=bar"},
want: []string{"nolabel1"},
},
{
subject: "inequality on nillable label",
selector: []string{"type!=bar"},
want: []string{"nolabel1", "nolabel2"},
},
{
subject: "equality on nillable label",
selector: []string{"type=bar"},
want: []string{"foo"},
},
{
subject: "inequality on non-nillable label",
selector: []string{"name!=nolabel1"},
want: []string{"nolabel2", "foo"},
},
{
subject: "equality on non-nillable label",
selector: []string{"name=nolabel1"},
want: []string{"nolabel1"},
},
}
example := []byte(`releases:
- name: nolabel1
namespace: kube-system
chart: stable/nolabel
- name: nolabel2
namespace: default
chart: stable/nolabel
- name: foo
namespace: kube-system
chart: stable/foo
labels:
type: bar
`)
state := stateTestEnv{
Files: map[string]string{
"/helmfile.yaml": string(example),
},
WorkDir: "/",
}.MustLoadState(t, "/helmfile.yaml", "default")
for _, tc := range testcases {
state.Selectors = tc.selector
rs, err := state.GetSelectedReleasesWithOverrides()
if err != nil {
t.Fatalf("%s %s: %v", tc.selector, tc.subject, err)
}
var got []string
for _, r := range rs {
got = append(got, r.Name)
}
if d := cmp.Diff(tc.want, got); d != "" {
t.Errorf("%s %s: %s", tc.selector, tc.subject, d)
}
}
}
+1434 -362
View File
File diff suppressed because it is too large Load Diff
+30 -21
View File
@@ -8,24 +8,31 @@ import (
"gopkg.in/yaml.v2"
)
func (st *HelmState) Values() (map[string]interface{}, error) {
return st.Env.GetMergedValues()
}
func (st *HelmState) mustLoadVals() map[string]interface{} {
vals, err := st.Values()
if err != nil {
panic(err)
func (st *HelmState) Values() map[string]interface{} {
if st.RenderedValues == nil {
panic("[bug] RenderedValues is nil")
}
return vals
return st.RenderedValues
}
func (st *HelmState) valuesFileTemplateData() EnvironmentTemplateData {
return EnvironmentTemplateData{
func (st *HelmState) createReleaseTemplateData(release *ReleaseSpec, vals map[string]interface{}) releaseTemplateData {
tmplData := releaseTemplateData{
Environment: st.Env,
KubeContext: st.OverrideKubeContext,
Namespace: st.OverrideNamespace,
Values: st.mustLoadVals(),
Chart: st.OverrideChart,
Values: vals,
Release: releaseTemplateDataRelease{
Name: release.Name,
Chart: release.Chart,
Namespace: release.Namespace,
Labels: release.Labels,
KubeContext: release.KubeContext,
},
}
tmplData.StateValues = &tmplData.Values
return tmplData
}
func getBoolRefFromStringTemplate(templateRef string) (*bool, error) {
@@ -80,19 +87,21 @@ func updateBoolTemplatedValues(r *ReleaseSpec) error {
func (st *HelmState) ExecuteTemplates() (*HelmState, error) {
r := *st
vals, err := st.Values()
if err != nil {
return nil, err
}
vals := st.Values()
for i, rt := range st.Releases {
if rt.KubeContext == "" {
rt.KubeContext = r.HelmDefaults.KubeContext
}
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 := releaseTemplateData{
Environment: st.Env,
Release: *prev,
Values: vals,
}
tmplData := st.createReleaseTemplateData(prev, vals)
renderer := tmpl.NewFileRenderer(st.readFile, st.basePath, tmplData)
r, err := rt.ExecuteTemplateExpressions(renderer)
if err != nil {
+24 -18
View File
@@ -36,7 +36,7 @@ func TestHelmState_executeTemplates(t *testing.T) {
Name: "test-app",
Namespace: "test-namespace-{{ .Release.Name }}",
ValuesTemplate: []interface{}{"config/{{ .Environment.Name }}/{{ .Release.Name }}/values.yaml"},
Secrets: []string{"config/{{ .Environment.Name }}/{{ .Release.Name }}/secrets.yaml"},
Secrets: []interface{}{"config/{{ .Environment.Name }}/{{ .Release.Name }}/secrets.yaml"},
Labels: map[string]string{"id": "{{ .Release.Name }}"},
},
want: ReleaseSpec{
@@ -45,7 +45,7 @@ func TestHelmState_executeTemplates(t *testing.T) {
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"},
Secrets: []interface{}{"config/test_env/test-app/secrets.yaml"},
Labels: map[string]string{"id": "test-app"},
},
},
@@ -135,15 +135,18 @@ func TestHelmState_executeTemplates(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
state := &HelmState{
basePath: ".",
HelmDefaults: HelmSpec{
KubeContext: "test_context",
},
Env: environment.Environment{Name: "test_env"},
OverrideNamespace: "test-namespace_",
Repositories: nil,
Releases: []ReleaseSpec{
tt.input,
ReleaseSetSpec: ReleaseSetSpec{
HelmDefaults: HelmSpec{
KubeContext: "test_context",
},
Env: environment.Environment{Name: "test_env"},
OverrideNamespace: "test-namespace_",
Repositories: nil,
Releases: []ReleaseSpec{
tt.input,
},
},
RenderedValues: map[string]interface{}{},
}
r, err := state.ExecuteTemplates()
@@ -235,15 +238,18 @@ func TestHelmState_recursiveRefsTemplates(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
state := &HelmState{
basePath: ".",
HelmDefaults: HelmSpec{
KubeContext: "test_context",
},
Env: environment.Environment{Name: "test_env"},
OverrideNamespace: "test-namespace_",
Repositories: nil,
Releases: []ReleaseSpec{
tt.input,
ReleaseSetSpec: ReleaseSetSpec{
HelmDefaults: HelmSpec{
KubeContext: "test_context",
},
Env: environment.Environment{Name: "test_env"},
OverrideNamespace: "test-namespace_",
Repositories: nil,
Releases: []ReleaseSpec{
tt.input,
},
},
RenderedValues: map[string]interface{}{},
}
r, err := state.ExecuteTemplates()
+61
View File
@@ -0,0 +1,61 @@
package state
import (
"fmt"
"github.com/google/go-cmp/cmp"
"github.com/roboll/helmfile/pkg/helmexec"
"io/ioutil"
"os"
"testing"
)
func TestGoGetter(t *testing.T) {
logger := helmexec.NewLogger(os.Stderr, "warn")
testcases := []struct {
chart, dir string
force bool
out, err string
}{
{
chart: "raw/incubator",
dir: "",
force: false,
out: "raw/incubator",
err: "",
},
}
for i, tc := range testcases {
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
d, err := ioutil.TempDir("", "testgogetter")
if err != nil {
panic(err)
}
defer os.RemoveAll(d)
st := &HelmState{
logger: logger,
readFile: ioutil.ReadFile,
basePath: d,
}
out, err := st.goGetterChart(tc.chart, tc.dir, "", false)
if diff := cmp.Diff(tc.out, out); diff != "" {
t.Fatalf("Unexpected out:\n%s", diff)
}
var errMsg string
if err != nil {
errMsg = err.Error()
}
if diff := cmp.Diff(tc.err, errMsg); diff != "" {
t.Fatalf("Unexpected err:\n%s", diff)
}
})
}
}
+82 -25
View File
@@ -1,8 +1,10 @@
package state
import (
"errors"
"fmt"
"sort"
"strings"
"sync"
"github.com/roboll/helmfile/pkg/helmexec"
@@ -97,17 +99,20 @@ func (st *HelmState) iterateOnReleases(helm helmexec.Interface, concurrency int,
return nil
}
type PlanOpts struct {
Reverse bool
type PlanOptions struct {
Reverse bool
IncludeNeeds bool
SkipNeeds bool
SelectedReleases []ReleaseSpec
}
func (st *HelmState) PlanReleases(reverse bool) ([][]Release, error) {
func (st *HelmState) PlanReleases(opts PlanOptions) ([][]Release, error) {
marked, err := st.SelectReleasesWithOverrides()
if err != nil {
return nil, err
}
groups, err := SortedReleaseGroups(marked, reverse)
groups, err := SortedReleaseGroups(marked, opts)
if err != nil {
return nil, err
}
@@ -115,22 +120,24 @@ func (st *HelmState) PlanReleases(reverse bool) ([][]Release, error) {
return groups, nil
}
func SortedReleaseGroups(releases []Release, reverse bool) ([][]Release, error) {
groups, err := GroupReleasesByDependency(releases)
func SortedReleaseGroups(releases []Release, opts PlanOptions) ([][]Release, error) {
reverse := opts.Reverse
groups, err := GroupReleasesByDependency(releases, opts)
if err != nil {
return nil, err
}
if reverse {
sort.Slice(groups, func(i, j int) bool {
return j < i
})
for i, j := 0, len(groups)-1; i < j; i, j = i+1, j-1 {
groups[i], groups[j] = groups[j], groups[i]
}
}
return groups, nil
}
func GroupReleasesByDependency(releases []Release) ([][]Release, error) {
func GroupReleasesByDependency(releases []Release, opts PlanOptions) ([][]Release, error) {
idToReleases := map[string][]Release{}
idToIndex := map[string]int{}
@@ -144,23 +151,73 @@ func GroupReleasesByDependency(releases []Release) ([][]Release, error) {
// Only compute dependencies from non-filtered releases
if !r.Filtered {
d.Add(id, dag.Dependencies(r.Needs))
}
}
for _, r := range releases {
if !r.Filtered {
for _, n := range r.Needs {
if _, ok := idToReleases[n]; !ok {
id := ReleaseToID(&r.ReleaseSpec)
return nil, fmt.Errorf("%q depends on nonexistent release %q", id, n)
}
var needs []string
for i := 0; i < len(r.Needs); i++ {
n := r.Needs[i]
needs = append(needs, n)
}
d.Add(id, dag.Dependencies(needs))
}
}
plan, err := d.Plan()
var ids []string
for id := range idToReleases {
ids = append(ids, id)
}
var selectedReleaseIDs []string
for _, r := range opts.SelectedReleases {
id := ReleaseToID(&r)
selectedReleaseIDs = append(selectedReleaseIDs, id)
}
plan, err := d.Plan(dag.SortOptions{
Only: selectedReleaseIDs,
WithDependencies: opts.IncludeNeeds,
WithoutDependencies: opts.SkipNeeds,
})
if err != nil {
if ude, ok := err.(*dag.UnhandledDependencyError); ok {
msgs := make([]string, len(ude.UnhandledDependencies))
for i, ud := range ude.UnhandledDependencies {
id := ud.Id
ds := make([]string, len(ud.Dependents))
for i, d := range ud.Dependents {
ds[i] = fmt.Sprintf("%q", d)
}
var dsHumanized string
if len(ds) < 3 {
dsHumanized = strings.Join(ds, " and ")
} else {
dsHumanized = strings.Join(ds[:len(ds)-1], ", ")
dsHumanized += ", and " + ds[len(ds)-1]
}
var verb string
if len(ds) == 1 {
verb = "depends"
} else {
verb = "depend"
}
idComponents := strings.Split(id, "/")
name := idComponents[len(idComponents)-1]
msg := fmt.Sprintf(
"release %s %s on %q which does not match the selectors. "+
"Please add a selector like \"--selector name=%s\", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies",
dsHumanized,
verb,
id,
name,
)
msgs[i] = msg
}
return nil, errors.New(msgs[0])
}
return nil, err
}
@@ -186,11 +243,11 @@ func GroupReleasesByDependency(releases []Release) ([][]Release, error) {
})
for _, id := range idsInGroup {
releases, ok := idToReleases[id]
rs, ok := idToReleases[id]
if !ok {
panic(fmt.Errorf("bug: unexpectedly failed to get releases for id %q", id))
panic(fmt.Errorf("bug: unexpectedly failed to get releases for id %q: %v", id, ids))
}
releasesInGroup = append(releasesInGroup, releases...)
releasesInGroup = append(releasesInGroup, rs...)
}
result = append(result, releasesInGroup)
+252 -144
View File
@@ -8,6 +8,7 @@ import (
"reflect"
"testing"
"github.com/Masterminds/semver/v3"
"github.com/roboll/helmfile/pkg/exectest"
"github.com/roboll/helmfile/pkg/helmexec"
"github.com/roboll/helmfile/pkg/testhelper"
@@ -21,6 +22,7 @@ func injectFs(st *HelmState, fs *testhelper.TestFs) *HelmState {
st.glob = fs.Glob
st.readFile = fs.ReadFile
st.fileExists = fs.FileExists
st.directoryExistsAt = fs.DirectoryExistsAt
return st
}
@@ -127,12 +129,14 @@ func TestHelmState_applyDefaultsTo(t *testing.T) {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
state := &HelmState{
basePath: tt.fields.BaseChartPath,
DeprecatedContext: tt.fields.Context,
DeprecatedReleases: tt.fields.DeprecatedReleases,
OverrideNamespace: tt.fields.Namespace,
Repositories: tt.fields.Repositories,
Releases: tt.fields.Releases,
basePath: tt.fields.BaseChartPath,
ReleaseSetSpec: ReleaseSetSpec{
DeprecatedContext: tt.fields.Context,
DeprecatedReleases: tt.fields.DeprecatedReleases,
OverrideNamespace: tt.fields.Namespace,
Repositories: tt.fields.Repositories,
Releases: tt.fields.Releases,
},
}
if state.ApplyOverrides(&tt.args.spec); !reflect.DeepEqual(tt.args.spec, tt.want) {
t.Errorf("HelmState.ApplyOverrides() = %v, want %v", tt.args.spec, tt.want)
@@ -163,7 +167,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
tests := []struct {
name string
version *helmexec.Version
version *semver.Version
defaults HelmSpec
release *ReleaseSpec
want []string
@@ -309,6 +313,24 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
"--namespace", "test-namespace",
},
},
{
name: "wait-for-jobs",
defaults: HelmSpec{
WaitForJobs: false,
},
release: &ReleaseSpec{
Chart: "test/chart",
Version: "0.1",
WaitForJobs: &enable,
Name: "test-charts",
Namespace: "test-namespace",
},
want: []string{
"--version", "0.1",
"--wait-for-jobs",
"--namespace", "test-namespace",
},
},
{
name: "devel",
defaults: HelmSpec{
@@ -580,11 +602,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
defaults: HelmSpec{
Verify: false,
},
version: &helmexec.Version{
Major: 3,
Minor: 2,
Patch: 0,
},
version: semver.MustParse("3.2.0"),
release: &ReleaseSpec{
Chart: "test/chart",
Version: "0.1",
@@ -604,11 +622,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
Verify: false,
CreateNamespace: &disable,
},
version: &helmexec.Version{
Major: 3,
Minor: 2,
Patch: 0,
},
version: semver.MustParse("3.2.0"),
release: &ReleaseSpec{
Chart: "test/chart",
Version: "0.1",
@@ -627,11 +641,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
Verify: false,
CreateNamespace: &disable,
},
version: &helmexec.Version{
Major: 3,
Minor: 2,
Patch: 0,
},
version: semver.MustParse("3.2.0"),
release: &ReleaseSpec{
Chart: "test/chart",
Version: "0.1",
@@ -652,11 +662,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
Verify: false,
CreateNamespace: &enable,
},
version: &helmexec.Version{
Major: 3,
Minor: 2,
Patch: 0,
},
version: semver.MustParse("3.2.0"),
release: &ReleaseSpec{
Chart: "test/chart",
Version: "0.1",
@@ -676,11 +682,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
Verify: false,
CreateNamespace: &enable,
},
version: &helmexec.Version{
Major: 2,
Minor: 16,
Patch: 0,
},
version: semver.MustParse("2.16.0"),
release: &ReleaseSpec{
Chart: "test/chart",
Version: "0.1",
@@ -695,17 +697,19 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
state := &HelmState{
basePath: "./",
DeprecatedContext: "default",
Releases: []ReleaseSpec{*tt.release},
HelmDefaults: tt.defaults,
valsRuntime: valsRuntime,
basePath: "./",
ReleaseSetSpec: ReleaseSetSpec{
DeprecatedContext: "default",
Releases: []ReleaseSpec{*tt.release},
HelmDefaults: tt.defaults,
},
valsRuntime: valsRuntime,
}
helm := &exectest.Helm{
Version: tt.version,
}
args, err := state.flagsForUpgrade(helm, tt.release, 0)
args, _, err := state.flagsForUpgrade(helm, tt.release, 0)
if err != nil && tt.wantErr == "" {
t.Errorf("unexpected error flagsForUpgrade: %v", err)
}
@@ -771,11 +775,11 @@ func Test_isLocalChart(t *testing.T) {
want: true,
},
{
name: "local chart in 3-level deep dir",
name: "remote chart in 3-level deep dir (e.g. ChartCenter)",
args: args{
chart: "foo/bar/baz",
chart: "center/bar/baz",
},
want: true,
want: false,
},
}
for i := range tests {
@@ -801,26 +805,50 @@ func Test_normalizeChart(t *testing.T) {
{
name: "construct local chart path",
args: args{
basePath: "/Users/jane/code/deploy/charts",
basePath: "/src",
chart: "./app",
},
want: "/Users/jane/code/deploy/charts/app",
want: "/src/app",
},
{
name: "construct local chart path, without leading dot",
args: args{
basePath: "/src",
chart: "published",
},
want: "/src/published",
},
{
name: "repo path",
args: args{
basePath: "/Users/jane/code/deploy/charts",
basePath: "/src",
chart: "remote/app",
},
want: "remote/app",
},
{
name: "construct local chart path, parent dir",
name: "chartcenter repo path",
args: args{
basePath: "/Users/jane/code/deploy/charts",
basePath: "/src",
chart: "center/stable/myapp",
},
want: "center/stable/myapp",
},
{
name: "construct local chart path, sibling dir",
args: args{
basePath: "/src",
chart: "../app",
},
want: "/Users/jane/code/deploy/app",
want: "/app",
},
{
name: "construct local chart path, parent dir",
args: args{
basePath: "/src",
chart: "./..",
},
want: "/",
},
{
name: "too much parent levels",
@@ -864,7 +892,18 @@ func TestHelmState_SyncRepos(t *testing.T) {
},
},
helm: &exectest.Helm{},
want: []string{"name", "http://example.com/", "", "", "", "", ""},
want: []string{"name", "http://example.com/", "", "", "", "", "", ""},
},
{
name: "ACR hosted repository",
repos: []RepositorySpec{
{
Name: "name",
Managed: "acr",
},
},
helm: &exectest.Helm{},
want: []string{"name", "", "", "", "", "", "", "acr"},
},
{
name: "repository with cert and key",
@@ -879,7 +918,7 @@ func TestHelmState_SyncRepos(t *testing.T) {
},
},
helm: &exectest.Helm{},
want: []string{"name", "http://example.com/", "", "certfile", "keyfile", "", ""},
want: []string{"name", "http://example.com/", "", "certfile", "keyfile", "", "", ""},
},
{
name: "repository with ca file",
@@ -893,7 +932,7 @@ func TestHelmState_SyncRepos(t *testing.T) {
},
},
helm: &exectest.Helm{},
want: []string{"name", "http://example.com/", "cafile", "", "", "", ""},
want: []string{"name", "http://example.com/", "cafile", "", "", "", "", ""},
},
{
name: "repository with username and password",
@@ -908,7 +947,7 @@ func TestHelmState_SyncRepos(t *testing.T) {
},
},
helm: &exectest.Helm{},
want: []string{"name", "http://example.com/", "", "", "", "example_user", "example_password"},
want: []string{"name", "http://example.com/", "", "", "", "example_user", "example_password", ""},
},
}
for i := range tests {
@@ -921,9 +960,11 @@ func TestHelmState_SyncRepos(t *testing.T) {
}
}
state := &HelmState{
Repositories: tt.repos,
ReleaseSetSpec: ReleaseSetSpec{
Repositories: tt.repos,
},
}
if _ = state.SyncRepos(tt.helm); !reflect.DeepEqual(tt.helm.Repo, tt.want) {
if _, _ = state.SyncRepos(tt.helm, map[string]bool{}); !reflect.DeepEqual(tt.helm.Repo, tt.want) {
t.Errorf("HelmState.SyncRepos() for [%s] = %v, want %v", tt.name, tt.helm.Repo, tt.want)
}
})
@@ -1032,9 +1073,12 @@ func TestHelmState_SyncReleases(t *testing.T) {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
state := &HelmState{
Releases: tt.releases,
logger: logger,
valsRuntime: valsRuntime,
ReleaseSetSpec: ReleaseSetSpec{
Releases: tt.releases,
},
logger: logger,
valsRuntime: valsRuntime,
RenderedValues: map[string]interface{}{},
}
if errs := state.SyncReleases(&AffectedReleases{}, tt.helm, []string{}, 1); errs != nil && len(errs) > 0 {
if len(errs) != len(tt.wantErrorMsgs) {
@@ -1135,10 +1179,13 @@ func TestHelmState_SyncReleases_MissingValuesFileForUndesiredRelease(t *testing.
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
state := &HelmState{
basePath: ".",
Releases: []ReleaseSpec{tt.release},
logger: logger,
valsRuntime: valsRuntime,
basePath: ".",
ReleaseSetSpec: ReleaseSetSpec{
Releases: []ReleaseSpec{tt.release},
},
logger: logger,
valsRuntime: valsRuntime,
RenderedValues: map[string]interface{}{},
}
fs := testhelper.NewTestFs(map[string]string{})
state = injectFs(state, fs)
@@ -1281,9 +1328,12 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
state := &HelmState{
Releases: tt.releases,
logger: logger,
valsRuntime: valsRuntime,
ReleaseSetSpec: ReleaseSetSpec{
Releases: tt.releases,
},
logger: logger,
valsRuntime: valsRuntime,
RenderedValues: map[string]interface{}{},
}
helm := &exectest.Helm{
Lists: map[exectest.ListKey]string{},
@@ -1291,7 +1341,7 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) {
//simulate the release is already installed
for i, release := range tt.releases {
if tt.installed != nil && tt.installed[i] {
helm.Lists[exectest.ListKey{Filter: "^" + release.Name + "$", Flags: "--deployed--failed--pending"}] = release.Name
helm.Lists[exectest.ListKey{Filter: "^" + release.Name + "$", Flags: "--deleting--deployed--failed--pending"}] = release.Name
}
}
@@ -1383,15 +1433,18 @@ func TestGetDeployedVersion(t *testing.T) {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
state := &HelmState{
Releases: []ReleaseSpec{tt.release},
logger: logger,
valsRuntime: valsRuntime,
ReleaseSetSpec: ReleaseSetSpec{
Releases: []ReleaseSpec{tt.release},
},
logger: logger,
valsRuntime: valsRuntime,
RenderedValues: map[string]interface{}{},
}
helm := &exectest.Helm{
Lists: map[exectest.ListKey]string{},
}
//simulate the helm.list call result
helm.Lists[exectest.ListKey{Filter: "^" + tt.release.Name + "$", Flags: "--deployed--failed--pending"}] = tt.listResult
helm.Lists[exectest.ListKey{Filter: "^" + tt.release.Name + "$", Flags: "--deleting--deployed--failed--pending"}] = tt.listResult
affectedReleases := AffectedReleases{}
state.SyncReleases(&affectedReleases, helm, []string{}, 1)
@@ -1510,11 +1563,14 @@ func TestHelmState_DiffReleases(t *testing.T) {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
state := &HelmState{
Releases: tt.releases,
logger: logger,
valsRuntime: valsRuntime,
ReleaseSetSpec: ReleaseSetSpec{
Releases: tt.releases,
},
logger: logger,
valsRuntime: valsRuntime,
RenderedValues: map[string]interface{}{},
}
_, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, false, false, false)
_, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, false, false, false, false)
if errs != nil && len(errs) > 0 {
t.Errorf("unexpected error: %v", errs)
}
@@ -1582,13 +1638,16 @@ func TestHelmState_SyncReleasesCleanup(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
numRemovedFiles := 0
state := &HelmState{
Releases: tt.releases,
ReleaseSetSpec: ReleaseSetSpec{
Releases: tt.releases,
},
logger: logger,
valsRuntime: valsRuntime,
removeFile: func(f string) error {
numRemovedFiles += 1
return nil
},
RenderedValues: map[string]interface{}{},
}
testfs := testhelper.NewTestFs(map[string]string{
"/path/to/someFile": `foo: FOO`,
@@ -1666,20 +1725,23 @@ func TestHelmState_DiffReleasesCleanup(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
numRemovedFiles := 0
state := &HelmState{
Releases: tt.releases,
ReleaseSetSpec: ReleaseSetSpec{
Releases: tt.releases,
},
logger: logger,
valsRuntime: valsRuntime,
removeFile: func(f string) error {
numRemovedFiles += 1
return nil
},
RenderedValues: map[string]interface{}{},
}
testfs := testhelper.NewTestFs(map[string]string{
"/path/to/someFile": `foo: bar
`,
})
state = injectFs(state, testfs)
if _, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, false, false, false); errs != nil && len(errs) > 0 {
if _, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, false, false, false, false); errs != nil && len(errs) > 0 {
t.Errorf("unexpected errors: %v", errs)
}
@@ -1725,63 +1787,67 @@ generated: 2019-05-16T15:42:45.50486+09:00
}
logger := helmexec.NewLogger(os.Stderr, "debug")
basePath := "/src"
state := &HelmState{
basePath: "/src",
basePath: basePath,
FilePath: "/src/helmfile.yaml",
Releases: []ReleaseSpec{
{
Chart: "./..",
ReleaseSetSpec: ReleaseSetSpec{
Releases: []ReleaseSpec{
{
Chart: "/example",
},
{
Chart: "./example",
},
{
Chart: "published/deeper",
},
{
Chart: "stable/envoy",
Version: "1.5.0",
},
{
Chart: "stable/envoy",
Version: "1.4.0",
},
},
{
Chart: "../examples",
},
{
Chart: "../../helmfile",
},
{
Chart: "published",
},
{
Chart: "published/deeper",
},
{
Chart: "stable/envoy",
Version: "1.5.0",
},
{
Chart: "stable/envoy",
Version: "1.4.0",
},
},
Repositories: []RepositorySpec{
{
Name: "stable",
URL: "https://kubernetes-charts.storage.googleapis.com",
Repositories: []RepositorySpec{
{
Name: "stable",
URL: "https://kubernetes-charts.storage.googleapis.com",
},
},
},
tempDir: tempDir,
logger: logger,
}
fs := testhelper.NewTestFs(map[string]string{
"/example/Chart.yaml": `foo: FOO`,
"/src/example/Chart.yaml": `foo: FOO`,
})
fs.Cwd = basePath
state = injectFs(state, fs)
errs := state.UpdateDeps(helm)
want := []string{"/", "/examples", "/helmfile", "/src/published", generatedDir}
want := []string{"/example", "./example", generatedDir}
if !reflect.DeepEqual(helm.Charts, want) {
t.Errorf("HelmState.UpdateDeps() = %v, want %v", helm.Charts, want)
}
if len(errs) != 0 {
t.Errorf("HelmState.UpdateDeps() - no errors, but got %d: %v", len(errs), errs)
t.Errorf("HelmState.UpdateDeps() - unexpected %d errors: %v", len(errs), errs)
}
resolved, err := state.ResolveDeps()
if err != nil {
t.Errorf("unexpected error: %v", err)
t.Errorf("HelmState.ResolveDeps() - unexpected error: %v", err)
}
if resolved.Releases[5].Version != "1.5.0" {
t.Errorf("unexpected version number: expected=1.5.0, got=%s", resolved.Releases[5].Version)
if resolved.Releases[3].Version != "1.5.0" {
t.Errorf("HelmState.ResolveDeps() - unexpected version number: expected=1.5.0, got=%s", resolved.Releases[5].Version)
}
if resolved.Releases[6].Version != "1.4.0" {
t.Errorf("unexpected version number: expected=1.4.0, got=%s", resolved.Releases[6].Version)
if resolved.Releases[4].Version != "1.4.0" {
t.Errorf("HelmState.ResolveDeps() - unexpected version number: expected=1.4.0, got=%s", resolved.Releases[6].Version)
}
}
@@ -1790,30 +1856,32 @@ func TestHelmState_ResolveDeps_NoLockFile(t *testing.T) {
state := &HelmState{
basePath: "/src",
FilePath: "/src/helmfile.yaml",
Releases: []ReleaseSpec{
{
Chart: "./..",
ReleaseSetSpec: ReleaseSetSpec{
Releases: []ReleaseSpec{
{
Chart: "./..",
},
{
Chart: "../examples",
},
{
Chart: "../../helmfile",
},
{
Chart: "published",
},
{
Chart: "published/deeper",
},
{
Chart: "stable/envoy",
},
},
{
Chart: "../examples",
},
{
Chart: "../../helmfile",
},
{
Chart: "published",
},
{
Chart: "published/deeper",
},
{
Chart: "stable/envoy",
},
},
Repositories: []RepositorySpec{
{
Name: "stable",
URL: "https://kubernetes-charts.storage.googleapis.com",
Repositories: []RepositorySpec{
{
Name: "stable",
URL: "https://kubernetes-charts.storage.googleapis.com",
},
},
},
logger: logger,
@@ -1906,8 +1974,10 @@ func TestHelmState_ReleaseStatuses(t *testing.T) {
tt := tests[i]
f := func(t *testing.T) {
state := &HelmState{
Releases: tt.releases,
logger: logger,
ReleaseSetSpec: ReleaseSetSpec{
Releases: tt.releases,
},
logger: logger,
fileExists: func(f string) (bool, error) {
if f != "foo.yaml" {
return false, fmt.Errorf("unexpected file: %s", f)
@@ -1991,8 +2061,10 @@ func TestHelmState_TestReleasesNoCleanUp(t *testing.T) {
tt := tests[i]
f := func(t *testing.T) {
state := &HelmState{
Releases: tt.releases,
logger: logger,
ReleaseSetSpec: ReleaseSetSpec{
Releases: tt.releases,
},
logger: logger,
}
errs := state.TestReleases(tt.helm, tt.cleanup, 1, 1)
if (errs != nil) != tt.wantErr {
@@ -2042,8 +2114,11 @@ func TestHelmState_NoReleaseMatched(t *testing.T) {
tt := tests[i]
f := func(t *testing.T) {
state := &HelmState{
Releases: releases,
logger: logger,
ReleaseSetSpec: ReleaseSetSpec{
Releases: releases,
},
logger: logger,
RenderedValues: map[string]interface{}{},
}
state.Selectors = []string{tt.labels}
errs := state.FilterReleases()
@@ -2208,11 +2283,14 @@ func TestHelmState_Delete(t *testing.T) {
release,
}
state := &HelmState{
HelmDefaults: HelmSpec{
KubeContext: tt.defKubeContext,
ReleaseSetSpec: ReleaseSetSpec{
HelmDefaults: HelmSpec{
KubeContext: tt.defKubeContext,
},
Releases: releases,
},
Releases: releases,
logger: logger,
logger: logger,
RenderedValues: map[string]interface{}{},
}
helm := &exectest.Helm{
Lists: map[exectest.ListKey]string{},
@@ -2235,3 +2313,33 @@ func TestHelmState_Delete(t *testing.T) {
t.Run(tt.name, f)
}
}
func TestReverse(t *testing.T) {
num := 8
st := &HelmState{}
for i := 0; i < num; i++ {
name := fmt.Sprintf("%d", i)
st.Helmfiles = append(st.Helmfiles, SubHelmfileSpec{
Path: name,
})
st.Releases = append(st.Releases, ReleaseSpec{
Name: name,
})
}
st.Reverse()
for i := 0; i < num; i++ {
j := num - 1 - i
want := fmt.Sprintf("%d", j)
if got := st.Helmfiles[i].Path; got != want {
t.Errorf("sub-helmfile at %d has incorrect path: want %q, got %q", i, want, got)
}
if got := st.Releases[i].Name; got != want {
t.Errorf("release at %d has incorrect name: want %q, got %q", i, want, got)
}
}
}
+90
View File
@@ -0,0 +1,90 @@
package state
import (
"errors"
"fmt"
"hash/fnv"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/davecgh/go-spew/spew"
)
func createTempValuesFile(release *ReleaseSpec, data interface{}) (*os.File, error) {
p, err := tempValuesFilePath(release, data)
if err != nil {
return nil, err
}
f, err := os.Create(*p)
if err != nil {
return nil, err
}
return f, nil
}
func tempValuesFilePath(release *ReleaseSpec, data interface{}) (*string, error) {
id, err := generateValuesID(release, data)
if err != nil {
panic(err)
}
workDir := os.Getenv("HELMFILE_TEMPDIR")
if workDir == "" {
workDir, err = ioutil.TempDir(os.TempDir(), "helmfile")
if err != nil {
panic(err)
}
}
d := filepath.Join(workDir, id)
info, err := os.Stat(d)
if err != nil && !errors.Is(err, os.ErrNotExist) {
panic(err)
} else if info == nil {
}
return &d, nil
}
func generateValuesID(release *ReleaseSpec, data interface{}) (string, error) {
var id []string
if release.Namespace != "" {
id = append(id, release.Namespace)
}
id = append(id, release.Name, "values")
hash, err := HashObject([]interface{}{release, data})
if err != nil {
return "", err
}
id = append(id, hash)
return strings.Join(id, "-"), nil
}
func HashObject(obj interface{}) (string, error) {
hash := fnv.New32a()
hash.Reset()
printer := spew.ConfigState{
Indent: " ",
SortKeys: true,
DisableMethods: true,
SpewKeys: true,
}
printer.Fprintf(hash, "%#v", obj)
sum := fmt.Sprint(hash.Sum32())
return SafeEncodeString(sum), nil
}
+33
View File
@@ -0,0 +1,33 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// See k8s.io/apimachinery/pkg/util/rand/rand.go
package state
const (
// We omit vowels from the set of available characters to reduce the chances
// of "bad words" being formed.
alphanums = "bcdfghjklmnpqrstvwxz2456789"
)
// SafeEncodeString encodes s using the same characters as rand.String. This reduces the chances of bad words and
// ensures that strings generated from hash functions appear consistent throughout the API.
func SafeEncodeString(s string) string {
r := make([]byte, len(s))
for i, b := range []rune(s) {
r[i] = alphanums[(int(b) % len(alphanums))]
}
return string(r)
}
+81
View File
@@ -0,0 +1,81 @@
package state
import (
"testing"
"github.com/google/go-cmp/cmp"
)
func TestGenerateID(t *testing.T) {
type testcase struct {
subject string
release ReleaseSpec
data interface{}
want string
}
ids := map[string]int{}
run := func(tc testcase) {
t.Helper()
t.Run(tc.subject, func(t *testing.T) {
t.Helper()
got, err := generateValuesID(&tc.release, tc.data)
if err != nil {
t.Fatalf("uenxpected error: %v", err)
}
if d := cmp.Diff(tc.want, got); d != "" {
t.Fatalf("unexpected result: want (-), got (+):\n%s", d)
}
ids[got]++
})
}
run(testcase{
subject: "baseline",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
want: "foo-values-779795898b",
})
run(testcase{
subject: "different bytes content",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
data: []byte(`{"k":"v"}`),
want: "foo-values-6b7ffbccc",
})
run(testcase{
subject: "different map content",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
data: map[string]interface{}{"k": "v"},
want: "foo-values-6d57bbfccf",
})
run(testcase{
subject: "different chart",
release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"},
want: "foo-values-5c45b58947",
})
run(testcase{
subject: "different name",
release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"},
want: "bar-values-99d5fdbcc",
})
run(testcase{
subject: "specific ns",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"},
want: "myns-foo-values-544cb97d7f",
})
for id, n := range ids {
if n > 1 {
t.Fatalf("too many occurences of %s: %d", id, n)
}
}
}
+36 -4
View File
@@ -19,12 +19,44 @@ type EnvironmentTemplateData struct {
Values map[string]interface{}
}
// releaseTemplateData provides variables accessible while executing golang text/template expressions in releases of a helmfile YAML file
// releaseTemplateData provides variables accessible while executing golang text/template expressions in release templates
// and release values templates within 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
// Release is accessible as `.Release` from any template expression executed by the renderer.
// It contains a subset of ReleaseSpec that is known to be useful to dynamically render values.
Release releaseTemplateDataRelease
// Values is accessible as `.Values` and it contains default state values overrode by environment values and override values.
Values map[string]interface{}
Values map[string]interface{}
StateValues *map[string]interface{}
// KubeContext is HelmState.OverrideKubeContext.
// You should better use Release.KubeContext as it might work as you'd expect even if HelmState.OverrideKubeContext is not set.
// See releaseTemplateDataRelease.KubeContext for more information.
KubeContext string
// Namespace is HelmState.OverrideNamespace.
// You should better use Release.Namespace as it might work as you'd expect even if OverrideNamespace is not set.
// See releaseTemplateDataRelease.Namespace for more information.
Namespace string
// Chart is HelmState.OverrideChart.
// You should better use Release.Chart as it might work as you'd expect even if OverrideChart is not set.
// See releaseTemplateDataRelease.Chart for more information.
Chart string
}
type releaseTemplateDataRelease struct {
// Name is basically ReleaseSpec.Name exposed to the template
Name string
// Namespace is HelmState.OverrideNamespace, or if it's empty, ReleaseSpec.Namespace.
Namespace string
// Labels is ReleaseSpec.Labels
Labels map[string]string
// Chart is ReleaseSpec.Chart
Chart string
// KubeContext is ReleaseSpec.KubeContext
KubeContext string
}
+9 -3
View File
@@ -21,7 +21,8 @@ func isLocalChart(chart string) bool {
return chart == "" ||
chart[0] == '/' ||
strings.Index(chart, "/") == -1 ||
len(strings.Split(chart, "/")) != 2
(len(strings.Split(chart, "/")) != 2 &&
len(strings.Split(chart, "/")) != 3)
}
func resolveRemoteChart(repoAndChart string) (string, string, bool) {
@@ -29,8 +30,13 @@ func resolveRemoteChart(repoAndChart string) (string, string, bool) {
return "", "", false
}
parts := strings.Split(repoAndChart, "/")
if len(parts) != 2 {
uriLike := strings.Index(repoAndChart, "://") > -1
if uriLike {
return "", "", false
}
parts := strings.SplitN(repoAndChart, "/", 2)
if len(parts) < 2 {
return "", "", false
}
+10 -4
View File
@@ -15,13 +15,17 @@ func TestIsLocalChart(t *testing.T) {
input: "stable/mysql",
expected: false,
},
{
input: "center/stable/mysql",
expected: false,
},
{
input: "./charts/myapp",
expected: true,
},
{
input: "charts/mysubsystem/myapp",
expected: true,
input: "center/stable/myapp",
expected: false,
},
{
input: "./charts/mysubsystem/myapp",
@@ -77,8 +81,10 @@ func TestResolveRemortChart(t *testing.T) {
remote: false,
},
{
input: "charts/mysubsystem/myapp",
remote: false,
input: "center/stable/myapp",
repo: "center",
chart: "stable/myapp",
remote: true,
},
{
input: "./charts/mysubsystem/myapp",
+13 -12
View File
@@ -21,7 +21,7 @@ type TestFs struct {
func NewTestFs(files map[string]string) *TestFs {
dirs := map[string]bool{}
for abs, _ := range files {
for d := filepath.Dir(abs); !dirs[d]; d = filepath.Dir(d) {
for d := filepath.ToSlash(filepath.Dir(abs)); !dirs[d]; d = filepath.ToSlash(filepath.Dir(d)) {
dirs[d] = true
fmt.Fprintf(os.Stderr, "testfs: recognized dir: %s\n", d)
}
@@ -39,10 +39,10 @@ func NewTestFs(files map[string]string) *TestFs {
func (f *TestFs) FileExistsAt(path string) bool {
var ok bool
if strings.Contains(path, "/") {
if strings.HasPrefix(path, "/") {
_, ok = f.files[path]
} else {
_, ok = f.files[filepath.Join(f.Cwd, path)]
_, ok = f.files[filepath.ToSlash(filepath.Join(f.Cwd, path))]
}
return ok
}
@@ -53,10 +53,10 @@ func (f *TestFs) FileExists(path string) (bool, error) {
func (f *TestFs) DirectoryExistsAt(path string) bool {
var ok bool
if strings.Contains(path, "/") {
if strings.HasPrefix(path, "/") {
_, ok = f.dirs[path]
} else {
_, ok = f.dirs[filepath.Join(f.Cwd, path)]
_, ok = f.dirs[filepath.ToSlash(filepath.Join(f.Cwd, path))]
}
return ok
}
@@ -64,13 +64,13 @@ func (f *TestFs) DirectoryExistsAt(path string) bool {
func (f *TestFs) ReadFile(filename string) ([]byte, error) {
var str string
var ok bool
if filename[0] == '/' {
if strings.HasPrefix(filename, "/") {
str, ok = f.files[filename]
} else {
str, ok = f.files[filepath.Join(f.Cwd, filename)]
str, ok = f.files[filepath.ToSlash(filepath.Join(f.Cwd, filename))]
}
if !ok {
return []byte(nil), fmt.Errorf("no registered file found: %s", filename)
return []byte(nil), os.ErrNotExist
}
f.fileReaderCalls += 1
@@ -90,10 +90,10 @@ func (f *TestFs) FileReaderCalls() int {
func (f *TestFs) Glob(relPattern string) ([]string, error) {
var pattern string
if relPattern[0] == '/' {
if strings.HasPrefix(relPattern, "/") {
pattern = relPattern
} else {
pattern = filepath.Join(f.Cwd, relPattern)
pattern = filepath.ToSlash(filepath.Join(f.Cwd, relPattern))
}
fixtures, ok := f.GlobFixtures[pattern]
@@ -115,13 +115,14 @@ func (f *TestFs) Glob(relPattern string) ([]string, error) {
}
func (f *TestFs) Abs(path string) (string, error) {
path = filepath.ToSlash(path)
var p string
if path[0] == '/' {
if strings.HasPrefix(path, "/") {
p = path
} else {
p = filepath.Join(f.Cwd, path)
}
return filepath.Clean(p), nil
return filepath.ToSlash(filepath.Clean(p)), nil
}
func (f *TestFs) Getwd() (string, error) {
+4 -42
View File
@@ -2,8 +2,9 @@ package tmpl
import (
"fmt"
"github.com/ghodss/yaml"
"github.com/roboll/helmfile/pkg/helmexec"
"golang.org/x/sync/errgroup"
"gopkg.in/yaml.v2"
"io"
"os"
"os/exec"
@@ -97,25 +98,9 @@ func (c *Context) Exec(command string, args []interface{}, inputs ...string) (st
g.Go(func() error {
// We use CombinedOutput to produce helpful error messages
// See https://github.com/roboll/helmfile/issues/1158
bs, err := cmd.CombinedOutput()
bs, err := helmexec.Output(cmd)
if err != nil {
args := strings.Join(strArgs, ", ")
shownCmd := []string{command}
if len(args) > 0 {
shownCmd = append(shownCmd, args)
}
var out string
out += fmt.Sprintf("\n\nCOMMAND:\n%s", Indent(strings.Join(shownCmd, " "), " "))
out += fmt.Sprintf("\n\nERROR:\n%s", Indent(err.Error(), " "))
if len(bs) > 0 {
out += fmt.Sprintf("\n\nCOMBINED OUTPUT:\n%s", Indent(string(bs), " "))
}
return fmt.Errorf("%v%s", err, out)
return err
}
bytes = bs
@@ -130,29 +115,6 @@ func (c *Context) Exec(command string, args []interface{}, inputs ...string) (st
return string(bytes), nil
}
// indents a block of text with an indent string
func Indent(text, indent string) string {
var b strings.Builder
b.Grow(len(text) * 2)
lines := strings.Split(text, "\n")
last := len(lines) - 1
for i, j := range lines {
if i > 0 && i < last && j != "" {
b.WriteString("\n")
}
if j != "" {
b.WriteString(indent + j)
}
}
return b.String()
}
func (c *Context) ReadFile(filename string) (string, error) {
var path string
if filepath.IsAbs(filename) {
+44 -2
View File
@@ -1,7 +1,9 @@
package tmpl
import (
"encoding/json"
"fmt"
"github.com/google/go-cmp/cmp"
"path/filepath"
"reflect"
"testing"
@@ -47,12 +49,31 @@ func TestReadFile_PassAbsPath(t *testing.T) {
}
}
func TestToYaml_UnsupportedNestedMapKey(t *testing.T) {
expected := ``
vals := Values(map[string]interface{}{
"foo": map[interface{}]interface{}{
"bar": "BAR",
},
})
actual, err := ToYaml(vals)
if err == nil {
t.Fatalf("expected error but got none")
} else if err.Error() != "error marshaling into JSON: json: unsupported type: map[interface {}]interface {}" {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(actual, expected) {
t.Errorf("unexpected result: expected=%v, actual=%v", expected, actual)
}
}
func TestToYaml(t *testing.T) {
expected := `foo:
bar: BAR
`
vals := Values(map[string]interface{}{
"foo": map[interface{}]interface{}{
"foo": map[string]interface{}{
"bar": "BAR",
},
})
@@ -70,7 +91,7 @@ func TestFromYaml(t *testing.T) {
bar: BAR
`
expected := Values(map[string]interface{}{
"foo": map[interface{}]interface{}{
"foo": map[string]interface{}{
"bar": "BAR",
},
})
@@ -83,6 +104,27 @@ func TestFromYaml(t *testing.T) {
}
}
func TestFromYamlToJson(t *testing.T) {
input := `foo:
bar: BAR
`
want := `{"foo":{"bar":"BAR"}}`
m, err := FromYaml(input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
got, err := json.Marshal(m)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if d := cmp.Diff(want, string(got)); d != "" {
t.Errorf("unexpected result: want (-), got (+):\n%s", d)
}
}
func TestSetValueAtPath_OneComponent(t *testing.T) {
input := map[string]interface{}{
"foo": "",
+24 -2
View File
@@ -2,15 +2,37 @@ package tmpl
import (
"bytes"
"github.com/Masterminds/sprig"
"github.com/Masterminds/sprig/v3"
"text/template"
)
func (c *Context) newTemplate() *template.Template {
func (c *Context) CreateFuncMap() template.FuncMap {
aliased := template.FuncMap{}
aliases := map[string]string{
"get": "sprigGet",
}
funcMap := sprig.TxtFuncMap()
for orig, alias := range aliases {
aliased[alias] = funcMap[orig]
}
for name, f := range c.createFuncMap() {
funcMap[name] = f
}
for name, f := range aliased {
funcMap[name] = f
}
return funcMap
}
func (c *Context) newTemplate() *template.Template {
funcMap := c.CreateFuncMap()
tmpl := template.New("stringTemplate").Funcs(funcMap)
if c.preRender {
tmpl = tmpl.Option("missingkey=zero")
+23
View File
@@ -0,0 +1,23 @@
package tmpl
import (
"testing"
)
func TestMergeOverwrite(t *testing.T) {
ctx := &Context{}
buf, err := ctx.RenderTemplateToBuffer(`
{{- $v1 := dict "bool" true "int" 2 "str" "v1" "str2" "v1" -}}
{{- $v2 := dict "bool" false "int" 0 "str" "v2" "str2" "" -}}
{{- $mo1 := mergeOverwrite (dict) $v1 $v2 }}
{{- $mo1 -}}
`)
expected := "map[bool:false int:0 str:v2 str2:]"
if err != nil {
t.Errorf("unexpected error: %v", err)
}
actual := buf.String()
if actual != expected {
t.Errorf("unexpected result: expected=%v, actual=%v", expected, actual)
}
}
+275
View File
@@ -0,0 +1,275 @@
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/go-test/deep"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/yaml"
)
type diffSource string
const (
diffSourceLeft diffSource = "left"
diffSourceRight diffSource = "right"
)
var (
rootCmd = &cobra.Command{
Use: "diff-yamls dir-with-yamls/ dir-with-yamls/",
Short: "Print any diff between the given directories",
Long: `Similar to the 'diff' command, but file contents
are compared after being parsed as a set of Kubernetes manifests.`,
Args: cobra.ExactArgs(2),
Run: run,
}
)
func run(cmd *cobra.Command, args []string) {
left := args[0]
right := args[1]
leftYamls, err := globYamlFilenames(left)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
rightYamls, err := globYamlFilenames(right)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
exitCode := 0
onlyInLeft := leftYamls.Difference(rightYamls)
if onlyInLeft.Len() > 0 {
exitCode = 1
for _, f := range onlyInLeft.List() {
fmt.Fprintf(os.Stderr, "Only in %s: %s\n", left, f)
}
}
onlyInRight := rightYamls.Difference(leftYamls)
if onlyInRight.Len() > 0 {
exitCode = 1
for _, f := range onlyInRight.List() {
fmt.Fprintf(os.Stderr, "Only in %s: %s\n", right, f)
}
}
inBoth := leftYamls.Intersection(rightYamls)
for _, f := range inBoth.List() {
leftPath := filepath.Join(left, f)
leftNodes, err := readManifest(leftPath)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
rightPath := filepath.Join(right, f)
rightNodes, err := readManifest(rightPath)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
ps := pairs{}
for _, node := range leftNodes {
if err := ps.add(node, diffSourceLeft); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
for _, node := range rightNodes {
if err := ps.add(node, diffSourceRight); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
for _, p := range ps.list {
switch {
case p.left != nil && p.right == nil:
fmt.Fprintf(os.Stderr, "Only in %s: %s\n", leftPath, p.left.getId())
exitCode = 1
case p.left == nil && p.right != nil:
fmt.Fprintf(os.Stderr, "Only in %s: %s\n", rightPath, p.right.getId())
exitCode = 1
default:
diff := deep.Equal(p.left, p.right)
if diff != nil {
id := p.left.getId()
fmt.Fprintf(os.Stderr, "< %s %s\n", id, leftPath)
fmt.Fprintf(os.Stderr, "> %s %s\n", id, rightPath)
for _, d := range diff {
fmt.Fprintf(os.Stderr, "%s\n", d)
}
exitCode = 1
}
}
}
}
os.Exit(exitCode)
}
func globYamlFilenames(dir string) (sets.String, error) {
matches, err := filepath.Glob(filepath.Join(dir, "*.yaml"))
if err != nil {
return nil, err
}
set := sets.String{}
for _, f := range matches {
set.Insert(filepath.Base(f))
}
return set, nil
}
type resource map[string]interface{}
type meta struct {
apiVersion string
kind string
name string
namespace string
}
func (res resource) getMeta() (meta, error) {
if len(res) == 0 {
return meta{}, nil
}
m := meta{}
apiVersion, _ := res["apiVersion"].(string)
m.apiVersion = apiVersion
kind, _ := res["kind"].(string)
m.kind = kind
metadata, _ := res["metadata"].(map[string]interface{})
name, _ := metadata["name"].(string)
m.name = name
namespace, _ := metadata["namespace"].(string)
m.namespace = namespace
return m, nil
}
func readManifest(path string) ([]resource, error) {
var err error
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
decoder := yaml.NewYAMLToJSONDecoder(f)
resources := []resource{}
for {
r := make(resource)
err = decoder.Decode(&r)
if err != nil {
break
}
if len(r) > 0 {
resources = append(resources, r)
}
}
if err != nil && err != io.EOF {
return nil, err
}
return resources, nil
}
func (res resource) getId() string {
meta, err := res.getMeta()
if err != nil {
return fmt.Sprintf("%v", res)
}
ns := meta.namespace
if ns == "" {
ns = "~X"
}
nm := meta.name
if nm == "" {
nm = "~N"
}
gv := meta.apiVersion
if gv == "" {
gv = "~G_~V"
}
k := meta.kind
if k == "" {
k = "~K"
}
gvk := strings.Join([]string{gv, k}, "_")
return strings.Join([]string{gvk, ns, nm}, "|")
}
// lifted from kustomize/kyaml/kio/filters/merge3.go
type pairs struct {
list []*pair
}
func (ps *pairs) isSameResource(meta1, meta2 meta) bool {
if meta1.name != meta2.name {
return false
}
if meta1.namespace != meta2.namespace {
return false
}
if meta1.apiVersion != meta2.apiVersion {
return false
}
if meta1.kind != meta2.kind {
return false
}
return true
}
func (ps *pairs) add(node resource, source diffSource) error {
nodeMeta, err := node.getMeta()
if err != nil {
return err
}
for i := range ps.list {
p := ps.list[i]
if ps.isSameResource(p.meta, nodeMeta) {
return p.add(node, source)
}
}
p := &pair{meta: nodeMeta}
if err := p.add(node, source); err != nil {
return err
}
ps.list = append(ps.list, p)
return nil
}
type pair struct {
meta meta
left resource
right resource
}
func (p *pair) add(node resource, source diffSource) error {
switch source {
case diffSourceLeft:
if p.left != nil {
return fmt.Errorf("left source already specified")
}
p.left = node
case diffSourceRight:
if p.right != nil {
return fmt.Errorf("right source already specified")
}
p.right = node
default:
return fmt.Errorf("unknown diff source value: %s", source)
}
return nil
}
func main() {
rootCmd.Execute()
}
@@ -0,0 +1,15 @@
:-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: This is a revocation certificate
iQG2BCABCgAgFiEEstbXu+wDsuZlccjACtGOFs/e9wAFAmA4+f8CHQAACgkQCtGO
Fs/e9wBN4wv/S0D4RCu5+BLt8y0vpAI9o1iRXj+yzOvPJUCm/QH1kN39saeI3rbr
8pyytpmzO5wIr+G+6BHh56p3NzjxWxfIt/RQ1vPMYf/dxzvtRuvYY00Fu683A65i
UqU9hiA6q1310OrMyEds4GmIteM++5xtKhV2s3A4bXJp6QD83MKV3m1IFYJ5cWfM
GmUSVlXBMiZ5Vfe8a04KaPKU3EkVSSIxLsvOW5+tPDZzkdUAHMCoHRAIBeOd7Aqo
uNytWylZtk1SeSeglbpm22NrXdvQbxV2oZplEazegqkydyUy8XmCh/57t1lHekAZ
Dcw0XgXu1s5TWj0vXa2g9sW+CKLGsDNg5XYp2cR4UGkGXCctp7aZb6k1+wv33zjB
hEL03GRq4AQ7yRzMsP0ue1YveolUfHRy+9OlFVdN25rL2VOvIkAxknnpe1c0gPLk
NOer0NAVPzE8QzHFQIDICriJ2urfzMm/nfa2UHW8O81opib5LNRbcPz+GOmKEgLf
aKRmcXNIZkPx
=ohmg
-----END PGP PUBLIC KEY BLOCK-----
Binary file not shown.
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
description: Chart for testing helmx features
name: helmx
version: 0.1.0
@@ -0,0 +1,16 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "helmx.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}}
{{- define "helmx.fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
@@ -0,0 +1,13 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: release-name
data:
name: {{ .Release.Name }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: release-namespace
data:
namespace: {{ .Release.Namespace }}
@@ -1,4 +1,4 @@
apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
metadata:
@@ -10,6 +10,9 @@ metadata:
heritage: {{ .Release.Service }}
spec:
replicas: 1
selector:
matchLabels:
app: {{ template "httpbin.name" . }}
strategy: {}
template:
metadata:
+3
View File
@@ -0,0 +1,3 @@
key_1: value
key_2: value
key_shared: value
+2
View File
@@ -0,0 +1,2 @@
key_1: value_1
key_shared: value_1
+2
View File
@@ -0,0 +1,2 @@
key_2: value_2
key_shared: value_2
+2
View File
@@ -1 +1,3 @@
mysecret: MYSECRET
raw2:
enabled: false
+32 -3
View File
@@ -4,10 +4,8 @@ environments:
- environment.values.yaml
---
repositories:
- name: stable
url: https://kubernetes-charts.storage.googleapis.com/
- name: incubator
url: https://kubernetes-charts-incubator.storage.googleapis.com
url: https://charts.helm.sh/incubator
helmDefaults:
kubeContext: minikube
@@ -19,9 +17,40 @@ releases:
set:
- name: ingress.enabled
value: false
hooks:
- events: ["prepare", "cleanup"]
showlogs: true
command: "echo"
args:
- "{{`{{.Environment.Name}}`}}"
- "{{`{{.Release.Name}}`}}"
- "{{`{{.HelmfileCommand}}`}}"
- name: raw
chart: incubator/raw
version: 0.2.3
values:
- mysecret: {{ .Environment.Values.mysecret }}
- values.yaml
- name: helmx
chart: ./charts/helmx
namespace: helmx-system
jsonPatches:
- target:
version: v1
kind: ConfigMap
name: release-name
patch:
- op: add
path: /metadata/annotations
value:
foo: bar
- name: raw2
chart: incubator/raw
version: 0.2.3
condition: raw2.enabled
values:
- mysecret: {{ .Environment.Values.mysecret }}
- values.yaml
+22
View File
@@ -0,0 +1,22 @@
repositories:
- name: prometheus-community
url: https://prometheus-community.github.io/helm-charts
releases:
- name: prometheus-operator-k8s-test
chart: prometheus-community/kube-prometheus-stack
namespace: monitoring
forceNamespace: monitoring
version: 15.4.6
labels:
component: test-upg
values:
- nameOverride: k8s
fullnameOverride: prometheus-k8s
global:
imagePullSecrets:
- name: regsecret
alertmanager:
enabled: false
grafana:
enabled: {{ .Values.grafanaEnabled }}

Some files were not shown because too many files have changed in this diff Show More