Compare commits

...

74 Commits
v1.1.3 ... main

Author SHA1 Message Date
Simon Bouchard a6fab4dc75
feat: update strategy for reinstall (#2019)
* feat: Add updateStrategy option in the state file with 'reinstall'/'reinstallIfForbidden' choices to uninstall and apply the specific release(s) (if forbidden to update)

Signed-off-by: Simon Bouchard <sbouchard@rbbn.com>

* Fix unit tests related to the new updateStrategy feature

Signed-off-by: Simon Bouchard <sbouchard@rbbn.com>

* Fix unit tests related to the new updateStrategy feature

Signed-off-by: Simon Bouchard <sbouchard@rbbn.com>

* Resolve linter issue due to cognitive complexity

Signed-off-by: Simon Bouchard <sbouchard@rbbn.com>

* Updated index.md to describe the possible values of updateStrategy

Signed-off-by: Simon Bouchard <sbouchard@rbbn.com>

* Add validation of updateStrategy parameter and unit test

Signed-off-by: Simon Bouchard <sbouchard@rbbn.com>

* Updated unit test

Signed-off-by: Simon Bouchard <sbouchard@rbbn.com>

* Removed 'reinstall' update strategy option to only have reinstallIfForbidden, cleanup of pre-sync changes, adapted unit tests

Signed-off-by: Simon Bouchard <sbouchard@rbbn.com>

* Display affected releases that were reinstalled

Signed-off-by: Simon Bouchard <sbouchard@rbbn.com>

* Make sure to add --wait when deleting a release to be reinstalled due to reinstallIfForbidden

Signed-off-by: Simon Bouchard <sbouchard@rbbn.com>

* Apply suggestions from Copilot code review

Signed-off-by: Simon Bouchard <sbouchard@rbbn.com>

---------

Signed-off-by: Simon Bouchard <sbouchard@rbbn.com>
2025-10-29 08:47:46 +08:00
dependabot[bot] a45d681a08
build(deps): bump actions/download-artifact from 5 to 6 (#2235)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-28 07:29:24 +08:00
dependabot[bot] 1c8e3d087d
build(deps): bump actions/upload-artifact from 4 to 5 (#2236)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-28 07:29:03 +08:00
Copilot d3908e6a3c
Fix helmBinary and kustomizeBinary being ignored when using bases (#2228)
* Fix helmBinary and kustomizeBinary being ignored when using bases

- Add mergo.WithOverride to merge operations for proper precedence
- Move default binary setting after base loading
- Add comprehensive tests for various base scenarios

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* Fix code formatting in create_test.go

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* Remove duplicate comment block in create_test.go

Removed duplicate comment lines (530-532) as identified by code review.

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
2025-10-26 19:41:39 -04:00
dependabot[bot] daebbfb0ad
build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.31.13 to 1.31.15 (#2233)
build(deps): bump github.com/aws/aws-sdk-go-v2/config

Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.31.13 to 1.31.15.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.31.13...config/v1.31.15)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-version: 1.31.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-26 10:16:26 +08:00
dependabot[bot] 8034acff6e
build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.88.6 to 1.88.7 (#2232)
build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3

Bumps [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) from 1.88.6 to 1.88.7.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.88.6...service/s3/v1.88.7)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/service/s3
  dependency-version: 1.88.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-25 08:45:32 +08:00
dependabot[bot] 55adae872e
build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.88.5 to 1.88.6 (#2230)
build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3

Bumps [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) from 1.88.5 to 1.88.6.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.88.5...service/s3/v1.88.6)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/service/s3
  dependency-version: 1.88.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-24 09:13:44 +08:00
dependabot[bot] 4fdc4affae
build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.31.12 to 1.31.13 (#2225)
build(deps): bump github.com/aws/aws-sdk-go-v2/config

Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.31.12 to 1.31.13.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.31.12...config/v1.31.13)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-version: 1.31.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-19 07:23:23 +08:00
dependabot[bot] fc54ff76d2
build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.88.4 to 1.88.5 (#2226)
build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3

Bumps [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) from 1.88.4 to 1.88.5.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.88.4...service/s3/v1.88.5)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/service/s3
  dependency-version: 1.88.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-18 12:00:21 +08:00
Copilot 377ca5c1a2
Bump helm-diff to v3.13.1 (#2223)
* Initial plan

* Bump helm-diff to v3.13.1

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* Update Dockerfiles to use helm-diff v3.13.1

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
2025-10-16 17:55:49 +08:00
yxxhero 160753c87f
docs: add zread badge to README (#2219)
Signed-off-by: yxxhero <aiopsclub@163.com>
2025-10-13 14:10:00 +08:00
dependabot[bot] ff60d0b565
build(deps): bump github.com/helmfile/vals from 0.42.2 to 0.42.4 (#2217) 2025-10-11 10:46:05 +08:00
Copilot 391c677058
Avoid fetching same chart/version multiple times (#2197)
* Initial plan

* Implement chart fetch deduplication mechanism

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* Fix CI issues: resolve gci formatting and reduce cognitive complexity

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* Remove redundant Fetching log message from OCI chart processing

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
2025-10-10 12:13:29 +08:00
dependabot[bot] 310cdead2e
build(deps): bump golang.org/x/term from 0.35.0 to 0.36.0 (#2214) 2025-10-09 08:36:00 +08:00
dependabot[bot] c2d783e872
build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.88.3 to 1.88.4 (#2213) 2025-10-09 08:35:27 +08:00
dependabot[bot] 98d9cf4b28
build(deps): bump github.com/hashicorp/go-getter from 1.8.1 to 1.8.2 (#2210)
Bumps [github.com/hashicorp/go-getter](https://github.com/hashicorp/go-getter) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/hashicorp/go-getter/releases)
- [Changelog](https://github.com/hashicorp/go-getter/blob/main/.goreleaser.yml)
- [Commits](https://github.com/hashicorp/go-getter/compare/v1.8.1...v1.8.2)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/go-getter
  dependency-version: 1.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-05 07:43:29 +08:00
Shane Starcher 6673ebad84
fix: skip chartify for build command jsonPatches (#2212)
The build command is intended to be a read-only inspection command that
outputs the helmfile state. However, when releases use jsonPatches,
strategicMergePatches, or transformers, the chart preparation step
triggers chartify, which runs helm template and requires dependencies to
be built.

This causes two issues:
1. helm template is executed unnecessarily for a simple state inspection
2. Missing chart dependencies cause errors even with SkipDeps enabled

This change modifies PrepareCharts to filter out releases that require
chartify when the command is "build". These releases are excluded from
chart preparation, preventing helm template from being invoked.

The state output will still include these releases, but their charts
won't be processed during the build operation.

Signed-off-by: Shane Starcher <shanestarcher@gmail.com>
2025-10-05 07:39:54 +08:00
Ori Shamir 1b8f2871f6
Add yq to Dockerfile (#2208)
Signed-off-by: Ori Shamir <orishamir04@gmail.com>
2025-10-01 21:51:45 +08:00
dependabot[bot] e34ea571fc
build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.31.10 to 1.31.12 (#2207)
build(deps): bump github.com/aws/aws-sdk-go-v2/config

Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.31.10 to 1.31.12.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.31.10...config/v1.31.12)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/config
  dependency-version: 1.31.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-01 11:57:46 +08:00
Ori Shamir 70205ac9ce
Bump Dockerfile to alpine 3.22 (#2205)
Bump Alpine to 3.22

Signed-off-by: Ori Shamir <orishamir04@gmail.com>
2025-10-01 11:34:32 +08:00
dependabot[bot] 3a5c57e144
build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.88.2 to 1.88.3 (#2206)
build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3

Bumps [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) from 1.88.2 to 1.88.3.
- [Release notes](https://github.com/aws/aws-sdk-go-v2/releases)
- [Changelog](https://github.com/aws/aws-sdk-go-v2/blob/main/changelog-template.json)
- [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.88.2...service/s3/v1.88.3)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go-v2/service/s3
  dependency-version: 1.88.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-01 11:08:16 +08:00
dependabot[bot] e4267a4317
build(deps): bump github.com/helmfile/vals from 0.42.1 to 0.42.2 (#2200)
Bumps [github.com/helmfile/vals](https://github.com/helmfile/vals) from 0.42.1 to 0.42.2.
- [Release notes](https://github.com/helmfile/vals/releases)
- [Changelog](https://github.com/helmfile/vals/blob/main/.goreleaser.yml)
- [Commits](https://github.com/helmfile/vals/compare/v0.42.1...v0.42.2)

---
updated-dependencies:
- dependency-name: github.com/helmfile/vals
  dependency-version: 0.42.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-30 11:00:44 +08:00
Copilot d94a7ada2b
Migrate AWS SDK from v1 to v2 to resolve deprecation warnings (#2202)
* Migrate AWS SDK from v1 to v2 to resolve deprecation warnings

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* Fix error message style issue for staticcheck

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
2025-09-30 09:59:00 +08:00
yxxhero c31ecf5061
cleanup disk in release ci (#2203)
* cleanup disk in release ci

Signed-off-by: yxxhero <aiopsclub@163.com>

* cleanup disk in release ci

Signed-off-by: yxxhero <aiopsclub@163.com>

* cleanup disk in release ci

Signed-off-by: yxxhero <aiopsclub@163.com>

* cleanup disk in release ci

Signed-off-by: yxxhero <aiopsclub@163.com>

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2025-09-30 09:01:42 +08:00
Davood a30957409d
fix typos in both comment and error message (#2199)
Signed-off-by: davood <falahati.davood@gmail.com>
2025-09-26 15:46:53 -04:00
dependabot[bot] c354768e60
build(deps): bump github.com/hashicorp/go-getter from 1.8.0 to 1.8.1 (#2194)
Bumps [github.com/hashicorp/go-getter](https://github.com/hashicorp/go-getter) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/hashicorp/go-getter/releases)
- [Changelog](https://github.com/hashicorp/go-getter/blob/main/.goreleaser.yml)
- [Commits](https://github.com/hashicorp/go-getter/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/go-getter
  dependency-version: 1.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-18 11:31:37 +08:00
dependabot[bot] 4de4c46f87
build(deps): bump github.com/helmfile/chartify from 0.24.7 to 0.25.0 (#2190)
Bumps [github.com/helmfile/chartify](https://github.com/helmfile/chartify) from 0.24.7 to 0.25.0.
- [Release notes](https://github.com/helmfile/chartify/releases)
- [Commits](https://github.com/helmfile/chartify/compare/v0.24.7...v0.25.0)

---
updated-dependencies:
- dependency-name: github.com/helmfile/chartify
  dependency-version: 0.25.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-13 17:19:16 +08:00
Ruslan d646b3cbd4
feat: Implement caching for pulling OCI charts (#2171)
Signed-off-by: Ruslan Khizhnyak <mustdiechik@gmail.com>
Co-authored-by: Ruslan Khizhnyak <rkhizhnyak@ptsecurity.com>
2025-09-13 12:49:03 +08:00
yxxhero 3f5d4110f6
build: update helm-diff plugin to v3.13.0 (#2189) 2025-09-13 10:08:15 +08:00
yxxhero c443baa103
build: update Helm to v3.19.0 across all components (#2187)
Signed-off-by: yxxhero <aiopsclub@163.com>
2025-09-12 13:58:08 -04:00
dependabot[bot] 9c1b393b35
build(deps): bump go.yaml.in/yaml/v2 from 2.4.2 to 2.4.3 (#2183)
Bumps [go.yaml.in/yaml/v2](https://github.com/yaml/go-yaml) from 2.4.2 to 2.4.3.
- [Release notes](https://github.com/yaml/go-yaml/releases)
- [Commits](https://github.com/yaml/go-yaml/compare/v2.4.2...v2.4.3)

---
updated-dependencies:
- dependency-name: go.yaml.in/yaml/v2
  dependency-version: 2.4.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-12 06:10:11 +08:00
Copilot 3728b6f647
Remove deprecated --wait-retries flag support to fix Helm compatibility error (#2179)
* Remove --wait-retries flag support and update documentation

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* Fix unused helm parameter in appendWaitFlags function

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
2025-09-11 13:57:28 +08:00
dependabot[bot] 2ad21b3df0
build(deps): bump k8s.io/apimachinery from 0.34.0 to 0.34.1 (#2180)
Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.34.0 to 0.34.1.
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.34.0...v0.34.1)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-version: 0.34.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-11 12:32:23 +08:00
yxxhero e3de97fcbd
ci: update minikube and kubernetes versions (#2181)
* ci: update minikube and kubernetes versions

Signed-off-by: yxxhero <aiopsclub@163.com>

* debug minikube version

Signed-off-by: yxxhero <aiopsclub@163.com>

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2025-09-11 11:34:01 +08:00
dependabot[bot] 1ecffc87e4
build(deps): bump golang.org/x/sync from 0.16.0 to 0.17.0 (#2172)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/sync/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 07:16:11 +08:00
Nick Neisen f708d06200
Fix panic when helm isn't installed (#2169)
Return error instead of panic

Signed-off-by: Nick Neisen <nwneisen@gmail.com>
2025-09-09 07:15:46 +08:00
dependabot[bot] 55030e4eee
build(deps): bump github.com/zclconf/go-cty from 1.16.4 to 1.17.0 (#2173)
Bumps [github.com/zclconf/go-cty](https://github.com/zclconf/go-cty) from 1.16.4 to 1.17.0.
- [Release notes](https://github.com/zclconf/go-cty/releases)
- [Changelog](https://github.com/zclconf/go-cty/blob/main/CHANGELOG.md)
- [Commits](https://github.com/zclconf/go-cty/compare/v1.16.4...v1.17.0)

---
updated-dependencies:
- dependency-name: github.com/zclconf/go-cty
  dependency-version: 1.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 06:17:21 +08:00
dependabot[bot] 27d6fb08c6
build(deps): bump golang.org/x/term from 0.34.0 to 0.35.0 (#2174)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.34.0 to 0.35.0.
- [Commits](https://github.com/golang/term/compare/v0.34.0...v0.35.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-version: 0.35.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 06:17:04 +08:00
dependabot[bot] 6fc2278f5f
build(deps): bump github.com/hashicorp/go-getter from 1.7.10 to 1.8.0 (#2175)
Bumps [github.com/hashicorp/go-getter](https://github.com/hashicorp/go-getter) from 1.7.10 to 1.8.0.
- [Release notes](https://github.com/hashicorp/go-getter/releases)
- [Changelog](https://github.com/hashicorp/go-getter/blob/main/.goreleaser.yml)
- [Commits](https://github.com/hashicorp/go-getter/commits/v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/go-getter
  dependency-version: 1.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-09 06:16:41 +08:00
Nick Neisen 2116c93cc4
Add helm diff installation to README (#2170)
Add helm-diff to install section

Signed-off-by: Nick Neisen <nwneisen@gmail.com>
2025-09-06 13:28:26 +08:00
dependabot[bot] fc900dda54
build(deps): bump github.com/spf13/pflag from 1.0.9 to 1.0.10 (#2163) 2025-09-04 23:02:06 +00:00
dependabot[bot] 074de257f8
build(deps): bump github.com/hashicorp/go-getter from 1.7.9 to 1.7.10 (#2165)
Bumps [github.com/hashicorp/go-getter](https://github.com/hashicorp/go-getter) from 1.7.9 to 1.7.10.
- [Release notes](https://github.com/hashicorp/go-getter/releases)
- [Changelog](https://github.com/hashicorp/go-getter/blob/main/.goreleaser.yml)
- [Commits](https://github.com/hashicorp/go-getter/compare/v1.7.9...v1.7.10)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/go-getter
  dependency-version: 1.7.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 06:32:17 +08:00
dependabot[bot] ce6197a514
build(deps): bump actions/setup-go from 5 to 6 (#2166)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 06:31:26 +08:00
Zubair Haque 31b3bd4e62
fix pflag error (#2164)
fix pflag error lint

Signed-off-by: zhaque44 <haque.zubair@gmail.com>
2025-09-05 06:30:53 +08:00
Copilot a9594eb158
Fix error propagation in helmfile diff when Kubernetes is unreachable (#2149)
* Fix error propagation in helmfile diff when Kubernetes is unreachable

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* Fix golangci-lint issue: replace custom contains function with strings.Contains

Co-authored-by: zhaque44 <20215376+zhaque44@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
Co-authored-by: zhaque44 <20215376+zhaque44@users.noreply.github.com>
2025-09-02 12:51:17 -04:00
dependabot[bot] a5814ff01c
build(deps): bump github.com/spf13/cobra from 1.9.1 to 1.10.1 (#2162)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.9.1 to 1.10.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.9.1...v1.10.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-version: 1.10.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-02 17:48:41 +08:00
dependabot[bot] 7842a0cd09
build(deps): bump github.com/spf13/pflag from 1.0.7 to 1.0.9 (#2160)
Bumps [github.com/spf13/pflag](https://github.com/spf13/pflag) from 1.0.7 to 1.0.9.
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.7...v1.0.9)

---
updated-dependencies:
- dependency-name: github.com/spf13/pflag
  dependency-version: 1.0.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-02 09:15:09 +08:00
dependabot[bot] ae9b6872db
build(deps): bump github.com/helmfile/vals from 0.42.0 to 0.42.1 (#2161)
Bumps [github.com/helmfile/vals](https://github.com/helmfile/vals) from 0.42.0 to 0.42.1.
- [Release notes](https://github.com/helmfile/vals/releases)
- [Changelog](https://github.com/helmfile/vals/blob/main/.goreleaser.yml)
- [Commits](https://github.com/helmfile/vals/compare/v0.42.0...v0.42.1)

---
updated-dependencies:
- dependency-name: github.com/helmfile/vals
  dependency-version: 0.42.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
2025-09-02 08:25:49 +08:00
Copilot d14e894cf3
Bump github.com/ulikunitz/xz from v0.5.14 to v0.5.15 (#2159)
* Initial plan

* Bump github.com/ulikunitz/xz from v0.5.14 to v0.5.15

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
2025-09-01 18:44:02 +08:00
dependabot[bot] 6d756bdf8a
build(deps): bump github.com/ulikunitz/xz from 0.5.10 to 0.5.14 (#2154)
Bumps [github.com/ulikunitz/xz](https://github.com/ulikunitz/xz) from 0.5.10 to 0.5.14.
- [Commits](https://github.com/ulikunitz/xz/compare/v0.5.10...v0.5.14)

---
updated-dependencies:
- dependency-name: github.com/ulikunitz/xz
  dependency-version: 0.5.14
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 06:57:59 +08:00
dependabot[bot] 0ac9ea7993
build(deps): bump github.com/stretchr/testify from 1.11.0 to 1.11.1 (#2151)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.11.0 to 1.11.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.11.0...v1.11.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-version: 1.11.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-28 06:54:55 +08:00
Copilot d37f937c9e
Fix enableDNS flag missing in diff command and refactor duplicate logic (#2147)
* Initial plan

* Add enableDNS flag support to diff command

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* Extract EnableDNS flag logic into reusable function

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
2025-08-27 14:32:17 +08:00
Copilot 135ff63aa3
Add missing --timeout flag to helmfile sync command with documentation (#2148)
* Initial plan

* Implement --timeout flag for helmfile sync command

- Add Timeout field to SyncOptions struct in pkg/config/sync.go
- Add --timeout flag to sync command in cmd/sync.go
- Add Timeout field to SyncOpts struct in pkg/state/state.go
- Modify timeoutFlags() function to prioritize CLI timeout over release and default configs
- Add test case to verify CLI timeout overrides other timeout settings
- Follow same pattern as existing --wait and --wait-for-jobs flags

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* Fix lint issues: format test struct fields properly

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* Update docs: Add --timeout flag documentation for helmfile sync command

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
2025-08-27 14:32:02 +08:00
dependabot[bot] e695637b08
build(deps): bump github.com/stretchr/testify from 1.10.0 to 1.11.0 (#2150)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-version: 1.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-26 20:42:06 +08:00
yxxhero a05b93de5c
build: update helm to v3.18.6 (#2144)
Signed-off-by: yxxhero <aiopsclub@163.com>
2025-08-22 14:10:01 +08:00
dependabot[bot] 008a5322bd
build(deps): bump github.com/zclconf/go-cty from 1.16.3 to 1.16.4 (#2145)
Bumps [github.com/zclconf/go-cty](https://github.com/zclconf/go-cty) from 1.16.3 to 1.16.4.
- [Release notes](https://github.com/zclconf/go-cty/releases)
- [Changelog](https://github.com/zclconf/go-cty/blob/main/CHANGELOG.md)
- [Commits](https://github.com/zclconf/go-cty/compare/v1.16.3...v1.16.4)

---
updated-dependencies:
- dependency-name: github.com/zclconf/go-cty
  dependency-version: 1.16.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-22 13:53:22 +08:00
dependabot[bot] 0fa965e011
build(deps): bump github.com/hashicorp/go-getter from 1.7.8 to 1.7.9 (#2139)
Bumps [github.com/hashicorp/go-getter](https://github.com/hashicorp/go-getter) from 1.7.8 to 1.7.9.
- [Release notes](https://github.com/hashicorp/go-getter/releases)
- [Changelog](https://github.com/hashicorp/go-getter/blob/main/.goreleaser.yml)
- [Commits](https://github.com/hashicorp/go-getter/compare/v1.7.8...v1.7.9)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/go-getter
  dependency-version: 1.7.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-19 07:08:09 +08:00
dependabot[bot] 33b6fca12c
build(deps): bump github.com/helmfile/chartify from 0.24.6 to 0.24.7 (#2135)
Bumps [github.com/helmfile/chartify](https://github.com/helmfile/chartify) from 0.24.6 to 0.24.7.
- [Release notes](https://github.com/helmfile/chartify/releases)
- [Commits](https://github.com/helmfile/chartify/compare/v0.24.6...v0.24.7)

---
updated-dependencies:
- dependency-name: github.com/helmfile/chartify
  dependency-version: 0.24.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-15 07:08:12 +08:00
dependabot[bot] ceef3f1a6b
build(deps): bump k8s.io/apimachinery from 0.33.3 to 0.33.4 (#2136)
Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.33.3 to 0.33.4.
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.33.3...v0.33.4)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-version: 0.33.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-15 07:07:50 +08:00
Copilot 7f18858182
Fix parseHelmVersion to handle helm versions without 'v' prefix (#2132)
* Initial plan

* Fix panic in helmfile init when parsing invalid helm versions

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* Fix parseHelmVersion to handle versions without v prefix

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

* Simplify parseHelmVersion function to be more readable

Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com>
2025-08-14 21:11:09 +08:00
yxxhero 8c123dcdda
refactor(state): extract getMissingFileHandler method for clarity (#2133)
* refactor(state): extract getMissingFileHandler method for clarity

Signed-off-by: yxxhero <aiopsclub@163.com>
2025-08-14 21:10:44 +08:00
Copilot bb6df72463
Add comprehensive .github/copilot-instructions.md for coding agents (#2131) 2025-08-14 10:21:26 +08:00
yxxhero 444275281f
Update recommended Helm versions in init.go and run.sh (#2129)
- Bump HelmDiffRecommendedVersion from v3.12.3 to v3.12.5 in pkg/app/init.go
- Bump default HELM_DIFF_VERSION from 3.12.3 to 3.12.5 in test/integration/run.sh
- Update HelmRecommendedVersion from v3.18.4 to v3.18.5 in pkg/app/init.go

Signed-off-by: yxxhero <aiopsclub@163.com>
2025-08-14 08:41:43 +08:00
dependabot[bot] 4aae348a46
build(deps): bump actions/checkout from 4 to 5 (#2128)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 07:44:59 +08:00
dependabot[bot] 9dbb4a4a27
build(deps): bump golang.org/x/term from 0.33.0 to 0.34.0 (#2123)
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.33.0 to 0.34.0.
- [Commits](https://github.com/golang/term/compare/v0.33.0...v0.34.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-version: 0.34.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-08 21:27:42 +08:00
dependabot[bot] 64d676a7e3
build(deps): bump actions/download-artifact from 4 to 5 (#2121)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-07 06:28:40 +08:00
yxxhero 959aae5791
refactor(yaml): switch yaml library import paths from gopkg.in to go.yaml.in (#2114)
Signed-off-by: yxxhero <aiopsclub@163.com>
2025-08-01 11:28:39 +08:00
Jess 9a88372449
Allow caching of remote files to be disabled (#2112)
* Allow caching of remote files to be disabled

Make it possible to automatically update the cache of remote
resources by disabling the caching of those resources using a query
string parameter (`cache=false`).

Signed-off-by: Jess <jess@ros.io>

* Fix test that broke

Because query parameters are being re-encoded, = is being encoded to %3D.

Signed-off-by: Jess <jess@ros.io>

* Add test for disabling caching of remote resources

Signed-off-by: Jess <jess@ros.io>

* Include example usage in docs

Signed-off-by: Jess <jess@ros.io>

---------

Signed-off-by: Jess <jess@ros.io>
2025-07-31 13:38:36 +08:00
yxxhero a76bec234c
refactor(filesystem): add CopyDir method and optimize Fetch function (#2111)
* refactor(filesystem): add CopyDir method and optimize Fetch function

Signed-off-by: yxxhero <aiopsclub@163.com>

* fix(state): conditionally prepare charts for local helmfile command

Signed-off-by: yxxhero <aiopsclub@163.com>

* fix(state): conditionally prepare charts for local helmfile command

Signed-off-by: yxxhero <aiopsclub@163.com>

* refactor(state): optimize chart path generation and update dependencies

Signed-off-by: yxxhero <aiopsclub@163.com>

* fix(test): update path in fetch-forl-local-chart test

Signed-off-by: yxxhero <aiopsclub@163.com>

* add more test cases

Signed-off-by: yxxhero <aiopsclub@163.com>

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2025-07-28 16:10:25 -04:00
yxxhero b0911ab1a2
feat(state): add missingFileHandlerConfig and related logic (#2105)
* feat(state): add missingFileHandlerConfig and related logic

Signed-off-by: yxxhero <aiopsclub@163.com>

* feat(state): add missingFileHandlerConfig and related logic

Signed-off-by: yxxhero <aiopsclub@163.com>

---------

Signed-off-by: yxxhero <aiopsclub@163.com>
2025-07-21 19:15:51 -04:00
dependabot[bot] 6fd4048653
build(deps): bump github.com/spf13/pflag from 1.0.6 to 1.0.7 (#2104)
Bumps [github.com/spf13/pflag](https://github.com/spf13/pflag) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/spf13/pflag/releases)
- [Commits](https://github.com/spf13/pflag/compare/v1.0.6...v1.0.7)

---
updated-dependencies:
- dependency-name: github.com/spf13/pflag
  dependency-version: 1.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-17 19:05:50 -04:00
yxxhero 4a3f923b1a
fix: update Helm version to v3.17.4 in CI and init.go (#2102)
Signed-off-by: yxxhero <aiopsclub@163.com>
2025-07-17 19:01:31 -04:00
dependabot[bot] 899b7791d2
build(deps): bump k8s.io/apimachinery from 0.33.2 to 0.33.3 (#2101)
Bumps [k8s.io/apimachinery](https://github.com/kubernetes/apimachinery) from 0.33.2 to 0.33.3.
- [Commits](https://github.com/kubernetes/apimachinery/compare/v0.33.2...v0.33.3)

---
updated-dependencies:
- dependency-name: k8s.io/apimachinery
  dependency-version: 0.33.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-17 06:35:01 +08:00
dependabot[bot] 0b29a3bf31
build(deps): bump github.com/helmfile/vals from 0.41.2 to 0.41.3 (#2100)
Bumps [github.com/helmfile/vals](https://github.com/helmfile/vals) from 0.41.2 to 0.41.3.
- [Release notes](https://github.com/helmfile/vals/releases)
- [Changelog](https://github.com/helmfile/vals/blob/main/.goreleaser.yml)
- [Commits](https://github.com/helmfile/vals/compare/v0.41.2...v0.41.3)

---
updated-dependencies:
- dependency-name: github.com/helmfile/vals
  dependency-version: 0.41.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-15 06:03:58 +08:00
60 changed files with 2976 additions and 2515 deletions

234
.github/copilot-instructions.md vendored Normal file
View File

@ -0,0 +1,234 @@
# Copilot Instructions for Helmfile
## Repository Overview
Helmfile is a tool for deploying Helm charts that manages Kubernetes deployments as code. It provides templating, environment management, and GitOps workflows for Helm chart deployments.
Helmfile is a declarative tool. In Helmfile, all elements of the desired state for deployments must be included in the `helmfile.yaml` config file and any associated files. Only operational matters can be provided dynamically, via command-line flags and environment variables.
**Key Details:**
- **Language:** Go 1.24.2+
- **Type:** CLI tool / Kubernetes deployment management
- **Size:** Large codebase (~229MB binary, 200+ dependencies)
- **Runtime:** Requires Helm 3.x as external dependency
- **Target Platform:** Linux/macOS/Windows, Kubernetes clusters
## Build and Validation Commands
### Essential Setup
Helmfile requires Helm 3.x and Kustomize 5.x as runtime dependencies:
```bash
# Check for Helm dependency (REQUIRED)
helm version # Must show version 3.x
# Initialize Helm plugins after helmfile installation
./helmfile init # Installs required helm-diff plugin
# Alternative: Force install without prompts
./helmfile init --force
```
### Build Process
```bash
# Standard build (takes 2-3 minutes due to many dependencies)
make build
# Alternative direct build
go build -o helmfile .
# Build with test tools (required for integration tests, ~1 minute)
make build-test-tools # Creates diff-yamls and downloads dyff
# Cross-platform builds
make cross
```
**Build Timing:** First build downloads 200+ Go packages and takes 2-3 minutes. Subsequent builds are faster due to module cache. Test tools build is faster (~1 minute).
### Validation Pipeline
Run in this exact order to match CI requirements:
```bash
# 1. Code formatting and linting
make check # Run go vet (required - always works)
# Note: make fmt requires gci tool (go install github.com/daixiang0/gci@latest)
# 2. Unit tests (fast, ~30 seconds)
go test -v ./pkg/... -race -p=1
# 3. Integration tests (requires Kubernetes - see Environment Setup)
make integration # Takes 5-10 minutes, needs minikube/k8s cluster
# 4. E2E tests (optional, needs expect package)
sudo apt-get install expect # On Ubuntu/Debian
bash test/e2e/helmfile-init/init_linux.sh
```
### Linting Configuration
Uses golangci-lint with configuration in `.golangci.yaml`. Install via:
```bash
# For local development
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.1.6
golangci-lint run
```
**Critical Lint Rules:** staticcheck, errcheck, revive, unused. Fix lint errors before committing.
## Environment Setup Requirements
### Dependencies for Development
```bash
# Required for building/testing
go version # Must be 1.24.2+
helm version # Must be 3.x
kubectl version # For K8s integration
# Required for integration tests
minikube start # Or other K8s cluster
kustomize version # v5.2.1+ for some tests
```
### Integration Test Environment
Integration tests require a running Kubernetes cluster:
```bash
# Using minikube (recommended for CI)
minikube start
export KUBECONFIG=$(minikube kubeconfig-path)
# Using kind (alternative)
kind create cluster --name helmfile-test
# Verify cluster access
kubectl cluster-info
```
**Timing:** Integration tests take 5-10 minutes and may fail due to timing issues in resource-constrained environments.
## Project Architecture and Layout
### Core Directory Structure
```
/
├── main.go # Entry point - CLI initialization and signal handling
├── cmd/ # CLI commands (apply, diff, sync, template, etc.)
│ ├── root.go # Main cobra command setup and global flags
│ ├── apply.go # helmfile apply command
│ ├── diff.go # helmfile diff command
│ └── ... # Other subcommands
├── pkg/ # Core library packages
│ ├── app/ # Main application logic and execution
│ ├── state/ # Helmfile state management and chart dependencies
│ ├── helmexec/ # Helm execution and command wrapper
│ ├── tmpl/ # Go template processing and functions
│ ├── environment/ # Environment and values management
│ └── ... # Other core packages
├── test/ # Test suites
│ ├── integration/ # Integration tests with real K8s clusters
│ ├── e2e/ # End-to-end user workflow tests
│ └── advanced/ # Advanced feature tests
├── docs/ # Documentation (mkdocs format)
├── examples/ # Example helmfile configurations
└── .github/workflows/ # CI/CD pipeline definitions
```
### Key Source Files
- **main.go:** Signal handling, CLI execution entry point
- **cmd/root.go:** Global CLI configuration, error handling, logging setup
- **pkg/app/app.go:** Main application orchestration, state management
- **pkg/state/state.go:** Helmfile state parsing, release management
- **pkg/helmexec/exec.go:** Helm command execution, version detection
### Configuration Files
- **go.mod/go.sum:** Go dependencies (many cloud providers, k8s libraries)
- **.golangci.yaml:** Linting rules and settings
- **Makefile:** Build targets and development workflows
- **mkdocs.yml:** Documentation generation configuration
- **.github/workflows/ci.yaml:** Complete CI pipeline definition
## Continuous Integration Pipeline
### GitHub Actions Workflow (`.github/workflows/ci.yaml`)
1. **Lint Job:** golangci-lint with custom configuration (~5 minutes)
2. **Test Job:** Unit tests + binary build (~10 minutes)
3. **Integration Job:** Tests with multiple Helm/Kustomize versions (~15-20 minutes each)
4. **E2E Job:** User workflow validation (~5 minutes)
**Matrix Testing:** CI tests against multiple Helm versions (3.17.x, 3.18.x) and Kustomize versions (5.2.x, 5.4.x).
### Pre-commit Validation Steps
Always run these locally before pushing:
```bash
make check # Format and vet (required)
go test ./pkg/... # Unit tests
make build # Verify build works
# Note: make fmt requires gci tool: go install github.com/daixiang0/gci@latest
```
### Common CI Failure Causes
- **Linting errors:** Run `golangci-lint run` locally first
- **Integration test timeouts:** K8s cluster setup timing issues
- **Version compatibility:** Ensure Go 1.24.2+ and Helm 3.x
- **Race conditions:** Some tests are sensitive to parallel execution
## Development Gotchas and Known Issues
### Build Issues
- **Long initial build time:** First `make build` downloads 200+ packages (~2-3 minutes)
- **Memory usage:** Large binary size due to embedded dependencies
- **Git tags:** Build may show version warnings if not on tagged commit
- **Tool dependencies:** `make fmt` requires `gci` tool installation
### Testing Issues
- **Integration tests require K8s:** Will fail without cluster access
- **Test isolation:** Use `-p=1` flag to avoid race conditions
- **Minikube timing:** May need to wait for cluster ready state
- **Plugin dependencies:** Tests need helm-diff and helm-secrets plugins
### Runtime Requirements
- **Helm dependency:** Always required at runtime, not just build time (available in CI)
- **kubectl access:** Most operations need valid kubeconfig
- **Plugin management:** `helmfile init` must be run after installation
### Common Error Patterns
```bash
# Missing Helm
"helm: command not found" → Install Helm first
# Plugin missing
"Error: plugin 'diff' not found" → Run helmfile init
# K8s access
"connection refused" → Check kubectl cluster-info
# Permission errors
"permission denied" → Check kubeconfig and cluster access
# Missing tools
"gci: No such file or directory" → go install github.com/daixiang0/gci@latest
```
## Working with the Codebase
### Making Changes
- **Follow Helmfile design**: Helmfile is a declarative deployment tool. Anything that is part of the desired state of the deployments needs to be managed by Helmfile configs. Only operational knowledge that affects "how" to apply the desired state needs to be runtime options, like command-like flags and environment variables.
- **Small, focused changes:** Each PR should address single concern
- **Test coverage:** Add unit tests for new pkg/ functionality
- **Integration tests:** Update test-cases/ for new CLI features
- **Documentation:** Update docs/ for user-facing changes
### Key Packages to Understand
- **pkg/app:** Main business logic, start here for feature changes
- **pkg/state:** Helmfile parsing and release orchestration
- **cmd/:** CLI interface changes and new subcommands
- **pkg/helmexec:** Helm integration and command execution
### Architecture Patterns
- **Dependency injection:** App uses interfaces for testability
- **State management:** Immutable state objects, functional transforms
- **Error handling:** Custom error types with exit codes
- **Plugin system:** Extensible via Helm plugins and Go templates
---
**Trust these instructions:** This information is validated against the current codebase. Only search for additional details if these instructions are incomplete or found to be incorrect for your specific task.

View File

@ -13,8 +13,8 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 10
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: actions/setup-go@v5 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
cache: false cache: false
@ -25,10 +25,10 @@ jobs:
tests: tests:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-go@v5 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
- name: Build - name: Build
@ -37,7 +37,7 @@ jobs:
run: make check test run: make check test
- name: Archive built binaries - name: Archive built binaries
run: tar -cvf built-binaries.tar helmfile diff-yamls dyff run: tar -cvf built-binaries.tar helmfile diff-yamls dyff
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v5
with: with:
name: built-binaries-${{ github.run_id }} name: built-binaries-${{ github.run_id }}
path: built-binaries.tar path: built-binaries.tar
@ -54,12 +54,12 @@ jobs:
# Helm maintains the latest minor version only and therefore each Helmfile version supports 2 Helm minor versions. # Helm maintains the latest minor version only and therefore each Helmfile version supports 2 Helm minor versions.
# That's why we cover only 2 Helm minor versions in this matrix. # That's why we cover only 2 Helm minor versions in this matrix.
# See https://github.com/helmfile/helmfile/pull/286#issuecomment-1250161182 for more context. # See https://github.com/helmfile/helmfile/pull/286#issuecomment-1250161182 for more context.
- helm-version: v3.17.3 - helm-version: v3.18.6
kustomize-version: v5.2.1 kustomize-version: v5.2.1
plugin-secrets-version: 4.6.5 plugin-secrets-version: 4.6.5
plugin-diff-version: 3.11.0 plugin-diff-version: 3.11.0
extra-helmfile-flags: '' extra-helmfile-flags: ''
- helm-version: v3.17.3 - helm-version: v3.18.6
kustomize-version: v5.4.3 kustomize-version: v5.4.3
# We assume that the helm-secrets plugin is supposed to # We assume that the helm-secrets plugin is supposed to
# work with the two most recent helm minor versions. # work with the two most recent helm minor versions.
@ -67,32 +67,32 @@ jobs:
# we will mark this combination as failable, # we will mark this combination as failable,
# and instruct users to upgrade helm and helm-secrets at once. # and instruct users to upgrade helm and helm-secrets at once.
plugin-secrets-version: 4.6.5 plugin-secrets-version: 4.6.5
plugin-diff-version: 3.12.3 plugin-diff-version: 3.12.5
extra-helmfile-flags: '' extra-helmfile-flags: ''
- helm-version: v3.18.4 - helm-version: v3.19.0
kustomize-version: v5.2.1 kustomize-version: v5.2.1
plugin-secrets-version: 4.6.5 plugin-secrets-version: 4.6.5
plugin-diff-version: 3.11.0 plugin-diff-version: 3.11.0
extra-helmfile-flags: '' extra-helmfile-flags: ''
- helm-version: v3.18.4 - helm-version: v3.19.0
kustomize-version: v5.4.3 kustomize-version: v5.4.3
plugin-secrets-version: 4.6.5 plugin-secrets-version: 4.6.5
plugin-diff-version: 3.12.3 plugin-diff-version: 3.12.5
extra-helmfile-flags: '' extra-helmfile-flags: ''
# In case you need to test some optional helmfile features, # In case you need to test some optional helmfile features,
# enable it via extra-helmfile-flags below. # enable it via extra-helmfile-flags below.
- helm-version: v3.18.4 - helm-version: v3.19.0
kustomize-version: v5.4.3 kustomize-version: v5.4.3
plugin-secrets-version: 4.6.5 plugin-secrets-version: 4.6.5
plugin-diff-version: 3.12.3 plugin-diff-version: 3.12.5
extra-helmfile-flags: '--enable-live-output' extra-helmfile-flags: '--enable-live-output'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: actions/setup-go@v5 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v6
with: with:
name: built-binaries-${{ github.run_id }} name: built-binaries-${{ github.run_id }}
- name: install semver - name: install semver
@ -109,6 +109,8 @@ jobs:
run: make -C .github/workflows helm vault sops kustomize run: make -C .github/workflows helm vault sops kustomize
- name: Start minikube - name: Start minikube
uses: medyagh/setup-minikube@latest uses: medyagh/setup-minikube@latest
with:
kubernetes-version: v1.33.1
- name: Execute integration tests - name: Execute integration tests
run: make integration run: make integration
env: env:
@ -122,10 +124,10 @@ jobs:
needs: tests needs: tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v6
with: with:
name: built-binaries-${{ github.run_id }} name: built-binaries-${{ github.run_id }}
- name: Extract tar to get built binaries - name: Extract tar to get built binaries

View File

@ -39,7 +39,7 @@ jobs:
suffix: "-ubuntu" suffix: "-ubuntu"
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0

View File

@ -22,12 +22,25 @@ jobs:
goreleaser: goreleaser:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: actions/setup-go@v5 - uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version-file: go.mod
- name: check disk usage
run: df -h
- name: cleanup disk
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf /usr/local/share/boost
sudo rm -fr /usr/local/lib/android
sudo rm -fr /opt/hostedtoolcache/CodeQL
sudo docker image prune --all --force
sudo docker builder prune -a
- name: check disk usage
run: df -h
- uses: goreleaser/goreleaser-action@v6 - uses: goreleaser/goreleaser-action@v6
with: with:
version: latest version: latest

View File

@ -45,7 +45,7 @@ linters:
lines: 280 lines: 280
statements: 140 statements: 140
gocognit: gocognit:
min-complexity: 100 min-complexity: 110
goconst: goconst:
min-len: 3 min-len: 3
min-occurrences: 8 min-occurrences: 8

View File

@ -12,11 +12,11 @@ RUN make static-${TARGETOS}-${TARGETARCH}
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
FROM alpine:3.19 FROM alpine:3.22
LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile
RUN apk add --no-cache ca-certificates git bash curl jq openssh-client gnupg RUN apk add --no-cache ca-certificates git bash curl jq yq openssh-client gnupg
ARG TARGETARCH TARGETOS TARGETPLATFORM ARG TARGETARCH TARGETOS TARGETPLATFORM
@ -30,7 +30,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}"
ARG HELM_DATA_HOME="${HOME}/.local/share/helm" ARG HELM_DATA_HOME="${HOME}/.local/share/helm"
ENV HELM_DATA_HOME="${HELM_DATA_HOME}" ENV HELM_DATA_HOME="${HELM_DATA_HOME}"
ARG HELM_VERSION="v3.18.4" ARG HELM_VERSION="v3.19.0"
ENV HELM_VERSION="${HELM_VERSION}" ENV HELM_VERSION="${HELM_VERSION}"
ARG HELM_LOCATION="https://get.helm.sh" ARG HELM_LOCATION="https://get.helm.sh"
ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz" ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz"
@ -38,8 +38,8 @@ RUN set -x && \
curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \
echo Verifying ${HELM_FILENAME}... && \ echo Verifying ${HELM_FILENAME}... && \
case ${TARGETPLATFORM} in \ case ${TARGETPLATFORM} in \
"linux/amd64") HELM_SHA256="f8180838c23d7c7d797b208861fecb591d9ce1690d8704ed1e4cb8e2add966c1" ;; \ "linux/amd64") HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496" ;; \
"linux/arm64") HELM_SHA256="c0a45e67eef0c7416a8a8c9e9d5d2d30d70e4f4d3f7bea5de28241fffa8f3b89" ;; \ "linux/arm64") HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad" ;; \
esac && \ esac && \
echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \ echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \
echo Extracting ${HELM_FILENAME}... && \ echo Extracting ${HELM_FILENAME}... && \
@ -93,7 +93,7 @@ RUN set -x && \
[ "$(age --version)" = "${AGE_VERSION}" ] && \ [ "$(age --version)" = "${AGE_VERSION}" ] && \
[ "$(age-keygen --version)" = "${AGE_VERSION}" ] [ "$(age-keygen --version)" = "${AGE_VERSION}" ]
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.12.3 && \ RUN helm plugin install https://github.com/databus23/helm-diff --version v3.13.1 && \
helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \ helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \ helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \
helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \ helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \

View File

@ -25,6 +25,9 @@ RUN apt update -qq && \
ARG TARGETARCH TARGETOS TARGETPLATFORM ARG TARGETARCH TARGETOS TARGETPLATFORM
RUN wget https://github.com/mikefarah/yq/releases/latest/download/yq_${TARGETOS}_${TARGETARCH} -O /usr/local/bin/yq &&\
chmod +x /usr/local/bin/yq
# Set Helm home variables so that also non-root users can use plugins etc. # Set Helm home variables so that also non-root users can use plugins etc.
ARG HOME="/helm" ARG HOME="/helm"
ENV HOME="${HOME}" ENV HOME="${HOME}"
@ -35,7 +38,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}"
ARG HELM_DATA_HOME="${HOME}/.local/share/helm" ARG HELM_DATA_HOME="${HOME}/.local/share/helm"
ENV HELM_DATA_HOME="${HELM_DATA_HOME}" ENV HELM_DATA_HOME="${HELM_DATA_HOME}"
ARG HELM_VERSION="v3.18.4" ARG HELM_VERSION="v3.19.0"
ENV HELM_VERSION="${HELM_VERSION}" ENV HELM_VERSION="${HELM_VERSION}"
ARG HELM_LOCATION="https://get.helm.sh" ARG HELM_LOCATION="https://get.helm.sh"
ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz" ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz"
@ -43,8 +46,8 @@ RUN set -x && \
curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \
echo Verifying ${HELM_FILENAME}... && \ echo Verifying ${HELM_FILENAME}... && \
case ${TARGETPLATFORM} in \ case ${TARGETPLATFORM} in \
"linux/amd64") HELM_SHA256="f8180838c23d7c7d797b208861fecb591d9ce1690d8704ed1e4cb8e2add966c1" ;; \ "linux/amd64") HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496" ;; \
"linux/arm64") HELM_SHA256="c0a45e67eef0c7416a8a8c9e9d5d2d30d70e4f4d3f7bea5de28241fffa8f3b89" ;; \ "linux/arm64") HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad" ;; \
esac && \ esac && \
echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \ echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \
echo Extracting ${HELM_FILENAME}... && \ echo Extracting ${HELM_FILENAME}... && \
@ -99,7 +102,7 @@ RUN set -x && \
[ "$(age --version)" = "${AGE_VERSION}" ] && \ [ "$(age --version)" = "${AGE_VERSION}" ] && \
[ "$(age-keygen --version)" = "${AGE_VERSION}" ] [ "$(age-keygen --version)" = "${AGE_VERSION}" ]
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.12.3 && \ RUN helm plugin install https://github.com/databus23/helm-diff --version v3.13.1 && \
helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \ helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \ helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \
helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \ helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \

View File

@ -12,7 +12,7 @@ RUN make static-${TARGETOS}-${TARGETARCH}
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
FROM ubuntu:24.10 FROM ubuntu:24.04
LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile
@ -25,6 +25,9 @@ RUN apt update -qq && \
ARG TARGETARCH TARGETOS TARGETPLATFORM ARG TARGETARCH TARGETOS TARGETPLATFORM
RUN wget https://github.com/mikefarah/yq/releases/latest/download/yq_${TARGETOS}_${TARGETARCH} -O /usr/local/bin/yq &&\
chmod +x /usr/local/bin/yq
# Set Helm home variables so that also non-root users can use plugins etc. # Set Helm home variables so that also non-root users can use plugins etc.
ARG HOME="/helm" ARG HOME="/helm"
ENV HOME="${HOME}" ENV HOME="${HOME}"
@ -35,7 +38,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}"
ARG HELM_DATA_HOME="${HOME}/.local/share/helm" ARG HELM_DATA_HOME="${HOME}/.local/share/helm"
ENV HELM_DATA_HOME="${HELM_DATA_HOME}" ENV HELM_DATA_HOME="${HELM_DATA_HOME}"
ARG HELM_VERSION="v3.18.4" ARG HELM_VERSION="v3.19.0"
ENV HELM_VERSION="${HELM_VERSION}" ENV HELM_VERSION="${HELM_VERSION}"
ARG HELM_LOCATION="https://get.helm.sh" ARG HELM_LOCATION="https://get.helm.sh"
ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz" ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz"
@ -43,8 +46,8 @@ RUN set -x && \
curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \
echo Verifying ${HELM_FILENAME}... && \ echo Verifying ${HELM_FILENAME}... && \
case ${TARGETPLATFORM} in \ case ${TARGETPLATFORM} in \
"linux/amd64") HELM_SHA256="f8180838c23d7c7d797b208861fecb591d9ce1690d8704ed1e4cb8e2add966c1" ;; \ "linux/amd64") HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496" ;; \
"linux/arm64") HELM_SHA256="c0a45e67eef0c7416a8a8c9e9d5d2d30d70e4f4d3f7bea5de28241fffa8f3b89" ;; \ "linux/arm64") HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad" ;; \
esac && \ esac && \
echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \ echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \
echo Extracting ${HELM_FILENAME}... && \ echo Extracting ${HELM_FILENAME}... && \
@ -99,7 +102,7 @@ RUN set -x && \
[ "$(age --version)" = "${AGE_VERSION}" ] && \ [ "$(age --version)" = "${AGE_VERSION}" ] && \
[ "$(age-keygen --version)" = "${AGE_VERSION}" ] [ "$(age-keygen --version)" = "${AGE_VERSION}" ]
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.12.3 && \ RUN helm plugin install https://github.com/databus23/helm-diff --version v3.13.1 && \
helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \ helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \ helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \
helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \ helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \

View File

@ -17,6 +17,7 @@
[![Slack Community #helmfile](https://slack.sweetops.com/badge.svg)](https://slack.sweetops.com) [![Slack Community #helmfile](https://slack.sweetops.com/badge.svg)](https://slack.sweetops.com)
[![Documentation](https://readthedocs.org/projects/helmfile/badge/?version=latest&style=flat)](https://helmfile.readthedocs.io/en/latest/) [![Documentation](https://readthedocs.org/projects/helmfile/badge/?version=latest&style=flat)](https://helmfile.readthedocs.io/en/latest/)
[![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Helmfile%20Guru-006BFF)](https://gurubase.io/g/helmfile) [![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Helmfile%20Guru-006BFF)](https://gurubase.io/g/helmfile)
[![zread](https://img.shields.io/badge/Ask_Zread-_.svg?style=flat&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS4zMTM1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff)](https://zread.ai/helmfile/helmfile)
声明式Helm Chart管理工具 声明式Helm Chart管理工具
<br /> <br />

View File

@ -17,6 +17,7 @@
[![Slack Community #helmfile](https://slack.sweetops.com/badge.svg)](https://slack.sweetops.com) [![Slack Community #helmfile](https://slack.sweetops.com/badge.svg)](https://slack.sweetops.com)
[![Documentation](https://readthedocs.org/projects/helmfile/badge/?version=latest&style=flat)](https://helmfile.readthedocs.io/en/latest/) [![Documentation](https://readthedocs.org/projects/helmfile/badge/?version=latest&style=flat)](https://helmfile.readthedocs.io/en/latest/)
[![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Helmfile%20Guru-006BFF)](https://gurubase.io/g/helmfile) [![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Helmfile%20Guru-006BFF)](https://gurubase.io/g/helmfile)
[![zread](https://img.shields.io/badge/Ask_Zread-_.svg?style=flat&color=00b0aa&labelColor=000000&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuOTYxNTYgMS42MDAxSDIuMjQxNTZDMS44ODgxIDEuNjAwMSAxLjYwMTU2IDEuODg2NjQgMS42MDE1NiAyLjI0MDFWNC45NjAxQzEuNjAxNTYgNS4zMTM1NiAxLjg4ODEgNS42MDAxIDIuMjQxNTYgNS42MDAxSDQuOTYxNTZDNS4zMTUwMiA1LjYwMDEgNS42MDE1NiA1LjMxMzU2IDUuNjAxNTYgNC45NjAxVjIuMjQwMUM1LjYwMTU2IDEuODg2NjQgNS4zMTUwMiAxLjYwMDEgNC45NjE1NiAxLjYwMDFaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00Ljk2MTU2IDEwLjM5OTlIMi4yNDE1NkMxLjg4ODEgMTAuMzk5OSAxLjYwMTU2IDEwLjY4NjQgMS42MDE1NiAxMS4wMzk5VjEzLjc1OTlDMS42MDE1NiAxNC4xMTM0IDEuODg4MSAxNC4zOTk5IDIuMjQxNTYgMTQuMzk5OUg0Ljk2MTU2QzUuMzE1MDIgMTQuMzk5OSA1LjYwMTU2IDE0LjExMzQgNS42MDE1NiAxMy43NTk5VjExLjAzOTlDNS42MDE1NiAxMC42ODY0IDUuMzE1MDIgMTAuMzk5OSA0Ljk2MTU2IDEwLjM5OTlaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik0xMy43NTg0IDEuNjAwMUgxMS4wMzg0QzEwLjY4NSAxLjYwMDEgMTAuMzk4NCAxLjg4NjY0IDEwLjM5ODQgMi4yNDAxVjQuOTYwMUMxMC4zOTg0IDUuMzEzNTYgMTAuNjg1IDUuNjAwMSAxMS4wMzg0IDUuNjAwMUgxMy43NTg0QzE0LjExMTkgNS42MDAxIDE0LjM5ODQgNS4zMTM1NiAxNC4zOTg0IDQuOTYwMVYyLjI0MDFDMTQuMzk4NCAxLjg4NjY0IDE0LjExMTkgMS42MDAxIDEzLjc1ODQgMS42MDAxWiIgZmlsbD0iI2ZmZiIvPgo8cGF0aCBkPSJNNCAxMkwxMiA0TDQgMTJaIiBmaWxsPSIjZmZmIi8%2BCjxwYXRoIGQ9Ik00IDEyTDEyIDQiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K&logoColor=ffffff)](https://zread.ai/helmfile/helmfile)
Deploy Kubernetes Helm Charts Deploy Kubernetes Helm Charts
<br /> <br />
@ -33,7 +34,9 @@ Helmfile is a declarative spec for deploying helm charts. It lets you...
* Apply CI/CD to configuration changes. * Apply CI/CD to configuration changes.
* Periodically sync to avoid skew in environments. * Periodically sync to avoid skew in environments.
To avoid upgrades for each iteration of `helm`, the `helmfile` executable delegates to `helm` - as a result, `helm` must be installed. To avoid upgrades for each iteration of `helm`, the `helmfile` executable delegates to `helm` - as a result, the following must be installed
- [helm](https://helm.sh/docs/intro/install/)
- [helm-diff](https://github.com/databus23/helm-diff)
## Highlights ## Highlights

19
Vagrantfile vendored
View File

@ -1,19 +0,0 @@
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

View File

@ -76,7 +76,7 @@ func NewRootCmd(globalConfig *config.GlobalOptions) (*cobra.Command, error) {
// Set the global options for the root command. // Set the global options for the root command.
setGlobalOptionsForRootCmd(flags, globalConfig) setGlobalOptionsForRootCmd(flags, globalConfig)
flags.ParseErrorsWhitelist.UnknownFlags = true flags.ParseErrorsAllowlist.UnknownFlags = true
globalImpl := config.NewGlobalImpl(globalConfig) globalImpl := config.NewGlobalImpl(globalConfig)

View File

@ -46,6 +46,7 @@ func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command {
f.BoolVar(&syncOptions.SyncReleaseLabels, "sync-release-labels", false, "sync release labels to the target release") f.BoolVar(&syncOptions.SyncReleaseLabels, "sync-release-labels", false, "sync release labels to the target release")
f.BoolVar(&syncOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`) f.BoolVar(&syncOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`)
f.BoolVar(&syncOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`) f.BoolVar(&syncOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`)
f.IntVar(&syncOptions.Timeout, "timeout", 0, `Override helmDefaults.timeout setting "helm upgrade --install --timeout" (default 0, which means no timeout)`)
f.BoolVar(&syncOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`) f.BoolVar(&syncOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`)
f.BoolVar(&syncOptions.ResetValues, "reset-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reset-values"`) f.BoolVar(&syncOptions.ResetValues, "reset-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reset-values"`)
f.StringVar(&syncOptions.PostRenderer, "post-renderer", "", `pass --post-renderer to "helm template" or "helm upgrade --install"`) f.StringVar(&syncOptions.PostRenderer, "post-renderer", "", `pass --post-renderer to "helm template" or "helm upgrade --install"`)

View File

@ -194,8 +194,9 @@ helmDefaults:
skipSchemaValidation: false skipSchemaValidation: false
# wait for k8s resources via --wait. (default false) # wait for k8s resources via --wait. (default false)
wait: true wait: true
# if set and --wait enabled, will retry any failed check on resource state subject to the specified number of retries (default 0) # DEPRECATED: waitRetries is no longer supported as the --wait-retries flag was removed from Helm.
waitRetries: 3 # This configuration is ignored and preserved only for backward compatibility.
# waitRetries: 3
# 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) # 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 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) # time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300)
@ -318,7 +319,8 @@ releases:
# --skip-schema-validation flag to helm 'install', 'upgrade' and 'lint', starts with helm 3.16.0 (default false) # --skip-schema-validation flag to helm 'install', 'upgrade' and 'lint', starts with helm 3.16.0 (default false)
skipSchemaValidation: false skipSchemaValidation: false
wait: true wait: true
waitRetries: 3 # DEPRECATED: waitRetries is no longer supported - see documentation above
# waitRetries: 3
waitForJobs: true waitForJobs: true
timeout: 60 timeout: 60
recreatePods: true recreatePods: true
@ -326,6 +328,9 @@ releases:
reuseValues: false reuseValues: false
# set `false` to uninstall this release on sync. (default true) # set `false` to uninstall this release on sync. (default true)
installed: true installed: true
# Defines the strategy to use when updating. Possible value is:
# - "reinstallIfForbidden": Performs an uninstall before the update only if the update is forbidden (e.g., due to permission issues or conflicts).
updateStrategy: ""
# restores previous state in case of failed release (default false) # restores previous state in case of failed release (default false)
atomic: true atomic: true
# when true, cleans up any new resources created during a failed release (default false) # when true, cleans up any new resources created during a failed release (default false)
@ -409,9 +414,16 @@ helmfiles:
# The nested-state file is locally checked-out along with the remote directory containing it. # The nested-state file is locally checked-out along with the remote directory containing it.
# Therefore all the local paths in the file are resolved relative to the file # Therefore all the local paths in the file are resolved relative to the file
path: git::https://github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=0.40.0 path: git::https://github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=0.40.0
- # By default git repositories aren't updated unless the ref is updated.
# Alternatively, refer to a named ref and disable the caching.
path: git::ssh://git@github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=main&cache=false
# If set to "Error", return an error when a subhelmfile points to a # If set to "Error", return an error when a subhelmfile points to a
# non-existent path. The default behavior is to print a warning and continue. # non-existent path. The default behavior is to print a warning and continue.
missingFileHandler: Error missingFileHandler: Error
missingFileHandlerConfig:
# Ignores missing git branch error so that the Debug/Info/Warn handler can treat a missing branch as non-error.
# See https://github.com/helmfile/helmfile/issues/392
ignoreMissingGitBranch: true
# #
# Advanced Configuration: Environments # Advanced Configuration: Environments
@ -567,7 +579,7 @@ Helmfile uses some OS environment variables to override default behaviour:
* `HELMFILE_ENVIRONMENT` - specify [Helmfile environment](https://helmfile.readthedocs.io/en/latest/#environment), it has lower priority than CLI argument `--environment` * `HELMFILE_ENVIRONMENT` - specify [Helmfile environment](https://helmfile.readthedocs.io/en/latest/#environment), it has lower priority than CLI argument `--environment`
* `HELMFILE_TEMPDIR` - specify directory to store temporary files * `HELMFILE_TEMPDIR` - specify directory to store temporary files
* `HELMFILE_UPGRADE_NOTICE_DISABLED` - expecting any non-empty value to skip the check for the latest version of Helmfile in [helmfile version](https://helmfile.readthedocs.io/en/latest/#version) * `HELMFILE_UPGRADE_NOTICE_DISABLED` - expecting any non-empty value to skip the check for the latest version of Helmfile in [helmfile version](https://helmfile.readthedocs.io/en/latest/#version)
* `HELMFILE_GO_YAML_V3` - use *gopkg.in/yaml.v3* instead of *gopkg.in/yaml.v2*. It's `false` by default in Helmfile v0.x, and `true` in Helmfile v1.x. * `HELMFILE_GO_YAML_V3` - use *go.yaml.in/yaml/v3* instead of *go.yaml.in/yaml/v2*. It's `false` by default in Helmfile v0.x, and `true` in Helmfile v1.x.
* `HELMFILE_CACHE_HOME` - specify directory to store cached files for remote operations * `HELMFILE_CACHE_HOME` - specify directory to store cached files for remote operations
* `HELMFILE_FILE_PATH` - specify the path to the helmfile.yaml file * `HELMFILE_FILE_PATH` - specify the path to the helmfile.yaml file
* `HELMFILE_INTERACTIVE` - enable interactive mode, expecting `true` lower case. The same as `--interactive` CLI flag * `HELMFILE_INTERACTIVE` - enable interactive mode, expecting `true` lower case. The same as `--interactive` CLI flag
@ -577,7 +589,7 @@ Helmfile uses some OS environment variables to override default behaviour:
``` ```
Declaratively deploy your Kubernetes manifests, Kustomize configs, and Charts as Helm releases in one shot Declaratively deploy your Kubernetes manifests, Kustomize configs, and Charts as Helm releases in one shot
V1 mode = false V1 mode = false
YAML library = gopkg.in/yaml.v3 YAML library = go.yaml.in/yaml/v3
Usage: Usage:
helmfile [command] helmfile [command]
@ -639,6 +651,8 @@ Flags:
Use "helmfile [command] --help" for more information about a command. Use "helmfile [command] --help" for more information about a command.
``` ```
**Note:** Each command has its own specific flags. Use `helmfile [command] --help` to see command-specific options. For example, `helmfile sync --help` shows operational flags like `--timeout`, `--wait`, and `--wait-for-jobs`.
### init ### init
The `helmfile init` sub-command checks the dependencies required for helmfile operation, such as `helm`, `helm diff plugin`, `helm secrets plugin`, `helm helm-git plugin`, `helm s3 plugin`. When it does not exist or the version is too low, it can be installed automatically. The `helmfile init` sub-command checks the dependencies required for helmfile operation, such as `helm`, `helm diff plugin`, `helm secrets plugin`, `helm helm-git plugin`, `helm s3 plugin`. When it does not exist or the version is too low, it can be installed automatically.
@ -654,6 +668,25 @@ The `helmfile sync` sub-command sync your cluster state as described in your `he
Under the covers, Helmfile executes `helm upgrade --install` for each `release` declared in the manifest, by optionally decrypting [secrets](#secrets) to be consumed as helm chart values. It also updates specified chart repositories and updates the Under the covers, Helmfile executes `helm upgrade --install` for each `release` declared in the manifest, by optionally decrypting [secrets](#secrets) to be consumed as helm chart values. It also updates specified chart repositories and updates the
dependencies of any referenced local charts. dependencies of any referenced local charts.
#### Common sync flags
* `--timeout SECONDS` - Override the default timeout for all releases in this sync operation. This takes precedence over `helmDefaults.timeout` and per-release `timeout` settings.
* `--wait` - Override the default wait behavior for all releases
* `--wait-for-jobs` - Override the default wait-for-jobs behavior for all releases
Examples:
```bash
# Override timeout for all releases to 10 minutes
helmfile sync --timeout 600
# Combine timeout with wait flags
helmfile sync --timeout 900 --wait --wait-for-jobs
# Target specific releases with custom timeout
helmfile sync --selector tier=backend --timeout 1200
```
For Helm 2.9+ you can use a username and password to authenticate to a remote repository. For Helm 2.9+ you can use a username and password to authenticate to a remote repository.
### deps ### deps

313
go.mod
View File

@ -1,42 +1,44 @@
module github.com/helmfile/helmfile module github.com/helmfile/helmfile
go 1.24.2 go 1.24.6
require ( require (
dario.cat/mergo v1.0.2 dario.cat/mergo v1.0.2
github.com/Masterminds/semver/v3 v3.4.0 github.com/Masterminds/semver/v3 v3.4.0
github.com/Masterminds/sprig/v3 v3.3.0 github.com/Masterminds/sprig/v3 v3.3.0
github.com/aws/aws-sdk-go-v2/config v1.31.15
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.7
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/go-test/deep v1.1.1 github.com/go-test/deep v1.1.1
github.com/golang/mock v1.6.0 github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.7.0
github.com/gosuri/uitable v0.0.4 github.com/gosuri/uitable v0.0.4
github.com/hashicorp/go-getter v1.7.8 github.com/hashicorp/go-getter v1.8.2
github.com/hashicorp/hcl/v2 v2.24.0 github.com/hashicorp/hcl/v2 v2.24.0
github.com/helmfile/chartify v0.24.6 github.com/helmfile/chartify v0.25.0
github.com/helmfile/vals v0.41.2 github.com/helmfile/vals v0.42.4
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.10.1
github.com/spf13/pflag v1.0.6 github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.11.1
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939 github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
github.com/tj/assert v0.0.3 github.com/tj/assert v0.0.3
github.com/variantdev/dag v1.1.0 github.com/variantdev/dag v1.1.0
github.com/zclconf/go-cty v1.16.3 github.com/zclconf/go-cty v1.17.0
github.com/zclconf/go-cty-yaml v1.1.0 github.com/zclconf/go-cty-yaml v1.1.0
go.szostok.io/version v1.2.0 go.szostok.io/version v1.2.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
golang.org/x/sync v0.16.0 go.yaml.in/yaml/v2 v2.4.3
golang.org/x/term v0.33.0 go.yaml.in/yaml/v3 v3.0.4
gopkg.in/yaml.v2 v2.4.0 golang.org/x/sync v0.17.0
gopkg.in/yaml.v3 v3.0.1 golang.org/x/term v0.36.0
helm.sh/helm/v3 v3.18.4 helm.sh/helm/v3 v3.19.0
k8s.io/apimachinery v0.33.2 k8s.io/apimachinery v0.34.1
) )
require ( require (
cloud.google.com/go v0.116.0 // indirect cloud.google.com/go v0.121.6 // indirect
cloud.google.com/go/iam v1.2.2 // indirect cloud.google.com/go/iam v1.5.2 // indirect
cloud.google.com/go/storage v1.50.0 // indirect cloud.google.com/go/storage v1.57.0 // indirect
filippo.io/age v1.2.1 // indirect filippo.io/age v1.2.1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
@ -46,190 +48,198 @@ require (
github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/a8m/envsubst v1.4.3 // indirect github.com/a8m/envsubst v1.4.3 // indirect
github.com/aws/aws-sdk-go v1.55.7
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/blang/semver v3.5.1+incompatible // indirect github.com/blang/semver v3.5.1+incompatible // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/fatih/color v1.18.0 github.com/fatih/color v1.18.0
github.com/fujiwara/tfstate-lookup v1.6.0 // indirect github.com/fujiwara/tfstate-lookup v1.7.1 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/googleapis/gax-go/v2 v2.15.0 // indirect
github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-slug v0.16.4 // indirect
github.com/hashicorp/go-slug v0.16.3 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/go-tfe v1.56.0 // indirect github.com/hashicorp/go-tfe v1.84.0 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
github.com/hashicorp/jsonapi v1.3.1 // indirect github.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e // indirect
github.com/hashicorp/vault/api v1.16.0 // indirect github.com/hashicorp/vault/api v1.22.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/itchyny/gojq v0.12.16 // indirect github.com/itchyny/gojq v0.12.16 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/lib/pq v1.10.9 // indirect github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/otiai10/copy v1.14.1 // indirect github.com/otiai10/copy v1.14.1
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cast v1.7.0 // indirect
github.com/ulikunitz/xz v0.5.10 // indirect github.com/ulikunitz/xz v0.5.15 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
golang.org/x/net v0.40.0 // indirect golang.org/x/net v0.44.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/oauth2 v0.31.0 // indirect
golang.org/x/sys v0.34.0 // indirect golang.org/x/sys v0.37.0 // indirect
golang.org/x/text v0.26.0 // indirect golang.org/x/text v0.29.0 // indirect
golang.org/x/time v0.9.0 // indirect golang.org/x/time v0.13.0 // indirect
google.golang.org/api v0.215.0 // indirect google.golang.org/api v0.252.0 // indirect
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.68.1 // indirect google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.5 // indirect google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect
) )
require ( require (
al.essio.dev/pkg/shellescape v1.5.1 // indirect al.essio.dev/pkg/shellescape v1.6.0 // indirect
cel.dev/expr v0.19.1 // indirect cel.dev/expr v0.24.0 // indirect
cloud.google.com/go/auth v0.13.0 // indirect cloud.google.com/go/auth v0.17.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/kms v1.20.5 // indirect cloud.google.com/go/kms v1.23.0 // indirect
cloud.google.com/go/longrunning v0.6.2 // indirect cloud.google.com/go/longrunning v0.6.7 // indirect
cloud.google.com/go/monitoring v1.21.2 // indirect cloud.google.com/go/monitoring v1.24.2 // indirect
cloud.google.com/go/secretmanager v1.14.3 // indirect cloud.google.com/go/secretmanager v1.15.0 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/1Password/connect-sdk-go v1.5.3 // indirect github.com/1Password/connect-sdk-go v1.5.3 // indirect
github.com/1password/onepassword-sdk-go v0.3.0 // indirect github.com/1password/onepassword-sdk-go v0.3.1 // indirect
github.com/AlecAivazis/survey/v2 v2.3.6 // indirect github.com/AlecAivazis/survey/v2 v2.3.6 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // indirect
github.com/DopplerHQ/cli v0.5.11-0.20230908185655-7aef4713e1a4 // indirect github.com/DopplerHQ/cli v0.5.11-0.20230908185655-7aef4713e1a4 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/squirrel v1.5.4 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect
github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/agext/levenshtein v1.2.3 // indirect github.com/agext/levenshtein v1.2.3 // indirect
github.com/antchfx/jsonquery v1.3.6 // indirect github.com/antchfx/jsonquery v1.3.6 // indirect
github.com/antchfx/xpath v1.3.4 // indirect github.com/antchfx/xpath v1.3.5 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/atotto/clipboard v0.1.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect github.com/aws/aws-sdk-go-v2 v1.39.4 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.18.19 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.9 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.72 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.45.6 // indirect
github.com/aws/aws-sdk-go-v2/service/kms v1.37.7 // indirect github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.6 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.2 // indirect github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 // indirect
github.com/aws/smithy-go v1.22.2 // indirect github.com/aws/smithy-go v1.23.1 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
github.com/containerd/containerd v1.7.27 // indirect github.com/containerd/containerd v1.7.28 // indirect
github.com/containerd/errdefs v0.3.0 // indirect github.com/containerd/errdefs v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/cyberark/conjur-api-go v0.13.0 // indirect github.com/cyberark/conjur-api-go v0.13.7 // indirect
github.com/danieljoos/wincred v1.2.2 // indirect github.com/danieljoos/wincred v1.2.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect
github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/envoyproxy/go-control-plane v0.13.1 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
github.com/extism/go-sdk v1.7.0 // indirect github.com/extism/go-sdk v1.7.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/getsops/gopgagent v0.0.0-20240527072608-0c14999532fe // indirect github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e // indirect
github.com/getsops/sops/v3 v3.9.2 // indirect github.com/getsops/sops/v3 v3.11.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect github.com/go-errors/errors v1.4.2 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/go-jose/go-jose/v4 v4.1.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect github.com/go-openapi/analysis v0.24.0 // indirect
github.com/go-openapi/errors v0.22.1 // indirect github.com/go-openapi/errors v0.22.3 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonpointer v0.22.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.2 // indirect
github.com/go-openapi/loads v0.22.0 // indirect github.com/go-openapi/loads v0.23.1 // indirect
github.com/go-openapi/runtime v0.28.0 // indirect github.com/go-openapi/runtime v0.29.0 // indirect
github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/spec v0.22.0 // indirect
github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-openapi/strfmt v0.24.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect github.com/go-openapi/swag v0.24.1 // indirect
github.com/go-openapi/validate v0.24.0 // indirect github.com/go-openapi/swag/cmdutils v0.24.0 // indirect
github.com/go-openapi/swag/conv v0.25.1 // indirect
github.com/go-openapi/swag/fileutils v0.25.1 // indirect
github.com/go-openapi/swag/jsonname v0.25.1 // indirect
github.com/go-openapi/swag/jsonutils v0.25.1 // indirect
github.com/go-openapi/swag/loading v0.25.1 // indirect
github.com/go-openapi/swag/mangling v0.25.1 // indirect
github.com/go-openapi/swag/netutils v0.24.0 // indirect
github.com/go-openapi/swag/stringutils v0.25.1 // indirect
github.com/go-openapi/swag/typeutils v0.25.1 // indirect
github.com/go-openapi/swag/yamlutils v0.25.1 // indirect
github.com/go-openapi/validate v0.25.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-yaml v1.17.1 // indirect github.com/goccy/go-yaml v1.17.1 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
github.com/google/btree v1.1.3 // indirect github.com/google/btree v1.1.3 // indirect
github.com/google/gnostic-models v0.6.9 // indirect github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-jsonnet v0.20.0 // indirect github.com/google/go-jsonnet v0.20.0 // indirect
github.com/google/s2a-go v0.1.8 // indirect github.com/google/s2a-go v0.1.9 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/hcp-sdk-go v0.144.0 // indirect github.com/hashicorp/hcp-sdk-go v0.162.0 // indirect
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect
github.com/itchyny/timefmt-go v0.1.6 // indirect github.com/itchyny/timefmt-go v0.1.6 // indirect
github.com/jmoiron/sqlx v1.4.0 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect
@ -237,14 +247,13 @@ require (
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/moby/spdystream v0.5.0 // indirect github.com/moby/spdystream v0.5.0 // indirect
github.com/moby/term v0.5.2 // indirect github.com/moby/term v0.5.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/muesli/termenv v0.15.1 // indirect github.com/muesli/termenv v0.15.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
@ -260,7 +269,9 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rubenv/sql-migrate v1.8.0 // indirect github.com/rubenv/sql-migrate v1.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
github.com/tetratelabs/wazero v1.9.0 // indirect github.com/tetratelabs/wazero v1.9.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/gjson v1.18.0 // indirect
@ -268,44 +279,48 @@ require (
github.com/tidwall/pretty v1.2.0 // indirect github.com/tidwall/pretty v1.2.0 // indirect
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
github.com/urfave/cli v1.22.16 // indirect github.com/urfave/cli v1.22.17 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xlab/treeprint v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect
github.com/yandex-cloud/go-genproto v0.29.0 // indirect
github.com/yandex-cloud/go-sdk v0.22.0 // indirect
github.com/zalando/go-keyring v0.2.6 // indirect github.com/zalando/go-keyring v0.2.6 // indirect
go.mongodb.org/mongo-driver v1.14.0 // indirect github.com/zeebo/errs v1.4.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.mongodb.org/mongo-driver v1.17.4 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.29.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/otel v1.33.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel/metric v1.33.0 // indirect go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/sdk v1.33.0 // indirect go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.33.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/proto/otlp v1.4.0 // indirect go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.39.0 // indirect golang.org/x/crypto v0.42.0 // indirect
golang.org/x/mod v0.25.0 // indirect golang.org/x/mod v0.27.0 // indirect
golang.org/x/tools v0.33.0 // indirect golang.org/x/tools v0.36.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // indirect
google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/gookit/color.v1 v1.1.6 // indirect gopkg.in/gookit/color.v1 v1.1.6 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
k8s.io/api v0.33.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/apiextensions-apiserver v0.33.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/cli-runtime v0.33.2 // indirect k8s.io/api v0.34.1 // indirect
k8s.io/client-go v0.33.2 // indirect k8s.io/apiextensions-apiserver v0.34.0 // indirect
k8s.io/component-base v0.33.2 // indirect k8s.io/cli-runtime v0.34.0 // indirect
k8s.io/client-go v0.34.1 // indirect
k8s.io/component-base v0.34.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
k8s.io/kubectl v0.33.2 // indirect k8s.io/kubectl v0.34.0 // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
oras.land/oras-go/v2 v2.6.0 // indirect oras.land/oras-go/v2 v2.6.0 // indirect
sigs.k8s.io/kustomize/api v0.19.0 // indirect sigs.k8s.io/kustomize/api v0.20.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
) )

2168
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -199,7 +199,7 @@ func (a *App) Diff(c DiffConfigProvider) error {
} }
if c.DetailedExitcode() && (len(allDiffDetectedErrs) > 0 || affectedAny) { if c.DetailedExitcode() && (len(allDiffDetectedErrs) > 0 || affectedAny) {
// We take the first release error w/ exit status 2 (although all the defered errs should have exit status 2) // We take the first release error w/ exit status 2 (although all the deferred errs should have exit status 2)
// to just let helmfile itself to exit with 2 // to just let helmfile itself to exit with 2
// See https://github.com/roboll/helmfile/issues/749 // See https://github.com/roboll/helmfile/issues/749
code := 2 code := 2
@ -345,8 +345,7 @@ func (a *App) Fetch(c FetchConfigProvider) error {
OutputDir: c.OutputDir(), OutputDir: c.OutputDir(),
OutputDirTemplate: c.OutputDirTemplate(), OutputDirTemplate: c.OutputDirTemplate(),
Concurrency: c.Concurrency(), Concurrency: c.Concurrency(),
}, func() { }, func() {})
})
if prepErr != nil { if prepErr != nil {
errs = append(errs, prepErr) errs = append(errs, prepErr)
@ -681,7 +680,7 @@ func (a *App) within(dir string, do func() error) error {
prev, err := a.fs.Getwd() prev, err := a.fs.Getwd()
if err != nil { if err != nil {
return fmt.Errorf("failed getting current working direcotyr: %v", err) return fmt.Errorf("failed getting current working directory: %v", err)
} }
absDir, err := a.fs.Abs(dir) absDir, err := a.fs.Abs(dir)
@ -785,7 +784,7 @@ func createHelmKey(bin, kubectx string) helmKey {
// //
// This is currently used for running all the helm commands for reconciling releases. But this may change in the future // This is currently used for running all the helm commands for reconciling releases. But this may change in the future
// once we enable each release to have its own helm binary/version. // once we enable each release to have its own helm binary/version.
func (a *App) getHelm(st *state.HelmState) helmexec.Interface { func (a *App) getHelm(st *state.HelmState) (helmexec.Interface, error) {
a.helmsMutex.Lock() a.helmsMutex.Lock()
defer a.helmsMutex.Unlock() defer a.helmsMutex.Unlock()
@ -800,14 +799,18 @@ func (a *App) getHelm(st *state.HelmState) helmexec.Interface {
key := createHelmKey(bin, kubectx) key := createHelmKey(bin, kubectx)
if _, ok := a.helms[key]; !ok { if _, ok := a.helms[key]; !ok {
a.helms[key] = helmexec.New(bin, helmexec.HelmExecOptions{EnableLiveOutput: a.EnableLiveOutput, DisableForceUpdate: a.DisableForceUpdate}, a.Logger, kubeconfig, kubectx, &helmexec.ShellRunner{ exec, err := helmexec.New(bin, helmexec.HelmExecOptions{EnableLiveOutput: a.EnableLiveOutput, DisableForceUpdate: a.DisableForceUpdate}, a.Logger, kubeconfig, kubectx, &helmexec.ShellRunner{
Logger: a.Logger, Logger: a.Logger,
Ctx: a.ctx, Ctx: a.ctx,
StripArgsValuesOnExitError: a.StripArgsValuesOnExitError, StripArgsValuesOnExitError: a.StripArgsValuesOnExitError,
}) })
if err != nil {
return nil, err
}
a.helms[key] = exec
} }
return a.helms[key] return a.helms[key], nil
} }
func (a *App) visitStates(fileOrDir string, defOpts LoadOpts, converge func(*state.HelmState) (bool, []error)) error { func (a *App) visitStates(fileOrDir string, defOpts LoadOpts, converge func(*state.HelmState) (bool, []error)) error {
@ -959,7 +962,10 @@ var (
func (a *App) ForEachState(do func(*Run) (bool, []error), includeTransitiveNeeds bool, o ...LoadOption) error { func (a *App) ForEachState(do func(*Run) (bool, []error), includeTransitiveNeeds bool, o ...LoadOption) error {
ctx := NewContext() ctx := NewContext()
err := a.visitStatesWithSelectorsAndRemoteSupport(a.FileOrDir, func(st *state.HelmState) (bool, []error) { err := a.visitStatesWithSelectorsAndRemoteSupport(a.FileOrDir, func(st *state.HelmState) (bool, []error) {
helm := a.getHelm(st) helm, err := a.getHelm(st)
if err != nil {
return false, []error{err}
}
run, err := NewRun(st, helm, ctx) run, err := NewRun(st, helm, ctx)
if err != nil { if err != nil {

View File

@ -415,4 +415,28 @@ releases:
}, },
}) })
}) })
t.Run("show diff on changed selected release with reinstall", func(t *testing.T) {
check(t, testcase{
helmfile: `
releases:
- name: a
chart: incubator/raw
namespace: default
updateStrategy: reinstallIfForbidden
- name: b
chart: incubator/raw
namespace: default
`,
selectors: []string{"name=a"},
lists: map[exectest.ListKey]string{
{Filter: "^a$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
`,
},
diffed: []exectest.Release{
{Name: "a", Flags: []string{"--kube-context", "default", "--namespace", "default", "--reset-values"}},
},
})
})
} }

View File

@ -411,7 +411,7 @@ releases:
}) })
} }
t.Run("fail due to unknown field with gopkg.in/yaml.v3", func(t *testing.T) { t.Run("fail due to unknown field with go.yaml.in/yaml/v3", func(t *testing.T) {
check(t, testcase{ check(t, testcase{
GoYamlV3: true, GoYamlV3: true,
error: `in ./helmfile.yaml: failed to read helmfile.yaml: reading document at index 1. Started seeing this since Helmfile v1? Add the .gotmpl file extension: yaml: unmarshal errors: error: `in ./helmfile.yaml: failed to read helmfile.yaml: reading document at index 1. Started seeing this since Helmfile v1? Add the .gotmpl file extension: yaml: unmarshal errors:
@ -419,7 +419,7 @@ releases:
}) })
}) })
t.Run("fail due to unknown field with gopkg.in/yaml.v2", func(t *testing.T) { t.Run("fail due to unknown field with go.yaml.in/yaml/v2", func(t *testing.T) {
check(t, testcase{ check(t, testcase{
GoYamlV3: false, GoYamlV3: false,
error: `in ./helmfile.yaml: failed to read helmfile.yaml: reading document at index 1. Started seeing this since Helmfile v1? Add the .gotmpl file extension: yaml: unmarshal errors: error: `in ./helmfile.yaml: failed to read helmfile.yaml: reading document at index 1. Started seeing this since Helmfile v1? Add the .gotmpl file extension: yaml: unmarshal errors:

View File

@ -220,6 +220,97 @@ releases:
} }
} }
func TestUpdateStrategyParamValidation(t *testing.T) {
cases := []struct {
files map[string]string
updateStrategy string
isValid bool
}{
{map[string]string{
"/path/to/helmfile.yaml": `releases:
- name: zipkin
chart: stable/zipkin
updateStrategy: reinstallIfForbidden
`},
"reinstallIfForbidden",
true},
{map[string]string{
"/path/to/helmfile.yaml": `releases:
- name: zipkin
chart: stable/zipkin
updateStrategy: reinstallIfForbidden
`},
"reinstallIfForbidden",
true},
{map[string]string{
"/path/to/helmfile.yaml": `releases:
- name: zipkin
chart: stable/zipkin
updateStrategy:
`},
"",
true},
{map[string]string{
"/path/to/helmfile.yaml": `releases:
- name: zipkin
chart: stable/zipkin
updateStrategy: foo
`},
"foo",
false},
{map[string]string{
"/path/to/helmfile.yaml": `releases:
- name: zipkin
chart: stable/zipkin
updateStrategy: reinstal
`},
"reinstal",
false},
{map[string]string{
"/path/to/helmfile.yaml": `releases:
- name: zipkin
chart: stable/zipkin
updateStrategy: reinstall1
`},
"reinstall1",
false},
}
for idx, c := range cases {
fs := testhelper.NewTestFs(c.files)
app := &App{
OverrideHelmBinary: DefaultHelmBinary,
OverrideKubeContext: "default",
Logger: newAppTestLogger(),
Namespace: "",
Env: "default",
FileOrDir: "helmfile.yaml",
}
expectNoCallsToHelm(app)
app = injectFs(app, fs)
err := app.ForEachState(
Noop,
false,
SetFilter(true),
)
if c.isValid && err != nil {
t.Errorf("[case: %d] Unexpected error for valid case: %v", idx, err)
} else if !c.isValid {
var invalidUpdateStrategy state.InvalidUpdateStrategyError
invalidUpdateStrategy.UpdateStrategy = c.updateStrategy
if err == nil {
t.Errorf("[case: %d] Expected error for invalid case", idx)
} else if !strings.Contains(err.Error(), invalidUpdateStrategy.Error()) {
t.Errorf("[case: %d] Unexpected error returned for invalid case\ngot: %v\nexpected underlying error: %s", idx, err, invalidUpdateStrategy.Error())
}
}
}
}
func TestVisitDesiredStatesWithReleasesFiltered_Issue1008_MissingNonDefaultEnvInBase(t *testing.T) { func TestVisitDesiredStatesWithReleasesFiltered_Issue1008_MissingNonDefaultEnvInBase(t *testing.T) {
files := map[string]string{ files := map[string]string{
"/path/to/base.yaml": ` "/path/to/base.yaml": `
@ -2492,9 +2583,12 @@ func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string
return []byte{}, nil return []byte{}, nil
} }
func MockExecer(logger *zap.SugaredLogger, kubeContext string) helmexec.Interface { func MockExecer(logger *zap.SugaredLogger, kubeContext string) (helmexec.Interface, error) {
execer := helmexec.New("helm", helmexec.HelmExecOptions{}, logger, "", kubeContext, &mockRunner{}) execer, err := helmexec.New("helm", helmexec.HelmExecOptions{}, logger, "", kubeContext, &mockRunner{})
return execer if err != nil {
return nil, err
}
return execer, nil
} }
// mocking helmexec.Interface // mocking helmexec.Interface
@ -3073,6 +3167,97 @@ baz 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart3-3.1.0 3.1.0 defau
concurrency: 1, concurrency: 1,
}, },
// //
// install with upgrade with reinstallIfForbidden
//
{
name: "install-with-upgrade-with-reinstallIfForbidden",
loc: location(),
files: map[string]string{
"/path/to/helmfile.yaml": `
releases:
- name: baz
chart: stable/mychart3
disableValidationOnInstall: true
updateStrategy: reinstallIfForbidden
- name: foo
chart: stable/mychart1
disableValidationOnInstall: true
needs:
- bar
- name: bar
chart: stable/mychart2
disableValidation: true
updateStrategy: reinstallIfForbidden
`,
},
diffs: map[exectest.DiffKey]error{
{Name: "baz", Chart: "stable/mychart3", Flags: "--kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
{Name: "foo", Chart: "stable/mychart1", Flags: "--disable-validation --kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
{Name: "bar", Chart: "stable/mychart2", Flags: "--disable-validation --kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
},
lists: map[exectest.ListKey]string{
{Filter: "^foo$", Flags: listFlags("", "default")}: ``,
{Filter: "^bar$", Flags: listFlags("", "default")}: `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
`,
{Filter: "^baz$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
baz 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart3-3.1.0 3.1.0 default
`,
},
upgraded: []exectest.Release{
{Name: "baz", Flags: []string{"--kube-context", "default"}},
{Name: "bar", Flags: []string{"--kube-context", "default"}},
{Name: "foo", Flags: []string{"--kube-context", "default"}},
},
deleted: []exectest.Release{},
concurrency: 1,
},
//
// install with upgrade and --skip-diff-on-install with reinstallIfForbidden
//
{
name: "install-with-upgrade-with-skip-diff-on-install-with-reinstallIfForbidden",
loc: location(),
skipDiffOnInstall: true,
files: map[string]string{
"/path/to/helmfile.yaml": `
releases:
- name: baz
chart: stable/mychart3
disableValidationOnInstall: true
updateStrategy: reinstallIfForbidden
- name: foo
chart: stable/mychart1
disableValidationOnInstall: true
needs:
- bar
- name: bar
chart: stable/mychart2
disableValidation: true
updateStrategy: reinstallIfForbidden
`,
},
diffs: map[exectest.DiffKey]error{
{Name: "baz", Chart: "stable/mychart3", Flags: "--kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
{Name: "bar", Chart: "stable/mychart2", Flags: "--disable-validation --kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
},
lists: map[exectest.ListKey]string{
{Filter: "^foo$", Flags: listFlags("", "default")}: ``,
{Filter: "^bar$", Flags: listFlags("", "default")}: `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
`,
{Filter: "^baz$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
baz 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart3-3.1.0 3.1.0 default
`,
},
upgraded: []exectest.Release{
{Name: "baz", Flags: []string{"--kube-context", "default"}},
{Name: "bar", Flags: []string{"--kube-context", "default"}},
{Name: "foo", Flags: []string{"--kube-context", "default"}},
},
concurrency: 1,
},
//
// upgrades // upgrades
// //
{ {
@ -3769,7 +3954,7 @@ releases:
} }
for flagIdx := range wantDeletes[relIdx].Flags { for flagIdx := range wantDeletes[relIdx].Flags {
if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] { 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]) t.Errorf("releases[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
} }
} }
} }
@ -4161,21 +4346,21 @@ releases:
} }
func TestSetValuesTemplate(t *testing.T) { func TestSetValuesTemplate(t *testing.T) {
t.Run("with gopkg.in/yaml.v3", func(t *testing.T) { t.Run("with go.yaml.in/yaml/v3", func(t *testing.T) {
testSetValuesTemplate(t, true) testSetValuesTemplate(t, true)
}) })
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) { t.Run("with go.yaml.in/yaml/v2", func(t *testing.T) {
testSetValuesTemplate(t, false) testSetValuesTemplate(t, false)
}) })
} }
func TestSetStringValuesTemplate(t *testing.T) { func TestSetStringValuesTemplate(t *testing.T) {
t.Run("with gopkg.in/yaml.v3", func(t *testing.T) { t.Run("with go.yaml.in/yaml/v3", func(t *testing.T) {
testSetStringValuesTemplate(t, true) testSetStringValuesTemplate(t, true)
}) })
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) { t.Run("with go.yaml.in/yaml/v2", func(t *testing.T) {
testSetStringValuesTemplate(t, false) testSetStringValuesTemplate(t, false)
}) })
} }

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"path/filepath" "path/filepath"
"slices"
"dario.cat/mergo" "dario.cat/mergo"
"github.com/helmfile/vals" "github.com/helmfile/vals"
@ -34,7 +35,7 @@ type desiredStateLoader struct {
chart string chart string
fs *filesystem.FileSystem fs *filesystem.FileSystem
getHelm func(*state.HelmState) helmexec.Interface getHelm func(*state.HelmState) (helmexec.Interface, error)
remote *remote.Remote remote *remote.Remote
logger *zap.SugaredLogger logger *zap.SugaredLogger
@ -285,6 +286,18 @@ func (ld *desiredStateLoader) load(env, overrodeEnv *environment.Environment, ba
} }
} }
// Validate updateStrategy value if set in the releases
for i := range finalState.Releases {
if finalState.Releases[i].UpdateStrategy != "" {
if !slices.Contains(state.ValidUpdateStrategyValues, finalState.Releases[i].UpdateStrategy) {
return nil, &state.StateLoadError{
Msg: fmt.Sprintf("failed to read %s", finalState.FilePath),
Cause: &state.InvalidUpdateStrategyError{UpdateStrategy: finalState.Releases[i].UpdateStrategy},
}
}
}
}
finalState.OrginReleases = finalState.Releases finalState.OrginReleases = finalState.Releases
return finalState, nil return finalState, nil
} }

View File

@ -17,9 +17,9 @@ import (
) )
const ( const (
HelmRequiredVersion = "v3.17.3" HelmRequiredVersion = "v3.18.6"
HelmDiffRecommendedVersion = "v3.12.3" HelmDiffRecommendedVersion = "v3.13.1"
HelmRecommendedVersion = "v3.18.4" HelmRecommendedVersion = "v3.19.0"
HelmSecretsRecommendedVersion = "v4.6.5" HelmSecretsRecommendedVersion = "v4.6.5"
HelmGitRecommendedVersion = "v1.3.0" HelmGitRecommendedVersion = "v1.3.0"
HelmS3RecommendedVersion = "v0.16.3" HelmS3RecommendedVersion = "v0.16.3"
@ -163,7 +163,10 @@ func (h *HelmfileInit) WhetherContinue(ask string) error {
func (h *HelmfileInit) CheckHelmPlugins() error { func (h *HelmfileInit) CheckHelmPlugins() error {
settings := cli.New() settings := cli.New()
helm := helmexec.New(h.helmBinary, helmexec.HelmExecOptions{}, h.logger, "", "", h.runner) helm, err := helmexec.New(h.helmBinary, helmexec.HelmExecOptions{}, h.logger, "", "", h.runner)
if err != nil {
return err
}
for _, p := range helmPlugins { for _, p := range helmPlugins {
pluginVersion, err := helmexec.GetPluginVersion(p.name, settings.PluginsDirectory) pluginVersion, err := helmexec.GetPluginVersion(p.name, settings.PluginsDirectory)
if err != nil { if err != nil {

View File

@ -0,0 +1,11 @@
processing file "helmfile.yaml" in directory "."
changing working directory to "/path/to"
merged environment: &{default map[] map[]}
1 release(s) matching name=a found in helmfile.yaml
processing 1 groups of releases in this order:
GROUP RELEASES
1 default/default/a
processing releases in group 1/1: default/default/a
changing working directory back to "/path/to"

View File

@ -0,0 +1,35 @@
processing file "helmfile.yaml" in directory "."
changing working directory to "/path/to"
merged environment: &{default map[] map[]}
3 release(s) found in helmfile.yaml
Affected releases are:
bar (stable/mychart2) UPDATED
baz (stable/mychart3) UPDATED
foo (stable/mychart1) UPDATED
invoking preapply hooks for 2 groups of releases in this order:
GROUP RELEASES
1 default//foo
2 default//baz, default//bar
invoking preapply hooks for releases in group 1/2: default//foo
invoking preapply hooks for releases in group 2/2: default//baz, default//bar
processing 2 groups of releases in this order:
GROUP RELEASES
1 default//baz, default//bar
2 default//foo
processing releases in group 1/2: default//baz, default//bar
update strategy - sync success
update strategy - sync success
processing releases in group 2/2: default//foo
getting deployed release version failed: Failed to get the version for: mychart1
UPDATED RELEASES:
NAME NAMESPACE CHART VERSION DURATION
baz stable/mychart3 3.1.0 0s
bar stable/mychart2 3.1.0 0s
foo stable/mychart1 0s
changing working directory back to "/path/to"

View File

@ -0,0 +1,35 @@
processing file "helmfile.yaml" in directory "."
changing working directory to "/path/to"
merged environment: &{default map[] map[]}
3 release(s) found in helmfile.yaml
Affected releases are:
bar (stable/mychart2) UPDATED
baz (stable/mychart3) UPDATED
foo (stable/mychart1) UPDATED
invoking preapply hooks for 2 groups of releases in this order:
GROUP RELEASES
1 default//foo
2 default//baz, default//bar
invoking preapply hooks for releases in group 1/2: default//foo
invoking preapply hooks for releases in group 2/2: default//baz, default//bar
processing 2 groups of releases in this order:
GROUP RELEASES
1 default//baz, default//bar
2 default//foo
processing releases in group 1/2: default//baz, default//bar
update strategy - sync success
update strategy - sync success
processing releases in group 2/2: default//foo
getting deployed release version failed: Failed to get the version for: mychart1
UPDATED RELEASES:
NAME NAMESPACE CHART VERSION DURATION
baz stable/mychart3 3.1.0 0s
bar stable/mychart2 3.1.0 0s
foo stable/mychart1 0s
changing working directory back to "/path/to"

View File

@ -24,6 +24,8 @@ type SyncOptions struct {
WaitRetries int WaitRetries int
// WaitForJobs is the wait for jobs flag // WaitForJobs is the wait for jobs flag
WaitForJobs bool WaitForJobs bool
// Timeout is the timeout flag in seconds
Timeout int
// ReuseValues is true if the helm command should reuse the values // ReuseValues is true if the helm command should reuse the values
ReuseValues bool ReuseValues bool
// ResetValues is true if helm command should reset values to charts' default // ResetValues is true if helm command should reset values to charts' default
@ -124,6 +126,11 @@ func (t *SyncImpl) WaitForJobs() bool {
return t.SyncOptions.WaitForJobs return t.SyncOptions.WaitForJobs
} }
// Timeout returns the timeout
func (t *SyncImpl) Timeout() int {
return t.SyncOptions.Timeout
}
// ReuseValues returns the ReuseValues. // ReuseValues returns the ReuseValues.
func (t *SyncImpl) ReuseValues() bool { func (t *SyncImpl) ReuseValues() bool {
if !t.ResetValues() { if !t.ResetValues() {

View File

@ -56,9 +56,10 @@ type Release struct {
} }
type Affected struct { type Affected struct {
Upgraded []*Release Upgraded []*Release
Deleted []*Release Reinstalled []*Release
Failed []*Release Deleted []*Release
Failed []*Release
} }
func (helm *Helm) UpdateDeps(chart string) error { func (helm *Helm) UpdateDeps(chart string) error {
@ -107,7 +108,24 @@ func (helm *Helm) RegistryLogin(name, username, password, caFile, certFile, keyF
return nil return nil
} }
func (helm *Helm) SyncRelease(context helmexec.HelmContext, name, chart, namespace string, flags ...string) error { func (helm *Helm) SyncRelease(context helmexec.HelmContext, name, chart, namespace string, flags ...string) error {
if strings.Contains(name, "error") { if strings.Contains(name, "forbidden") {
releaseExists := false
for _, release := range helm.Releases {
if release.Name == name {
releaseExists = true
}
}
releaseDeleted := false
for _, release := range helm.Deleted {
if release.Name == name {
releaseDeleted = true
}
}
// Only fail if the release is present in the helm.Releases to simulate a forbidden update if it exists
if releaseExists && !releaseDeleted {
return fmt.Errorf("cannot patch %q with kind StatefulSet: StatefulSet.apps %q is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden", name, name)
}
} else if strings.Contains(name, "error") {
return errors.New("error") return errors.New("error")
} }
helm.sync(helm.ReleasesMutex, func() { helm.sync(helm.ReleasesMutex, func() {

View File

@ -6,6 +6,8 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
copyDir "github.com/otiai10/copy"
) )
type fileStat struct { type fileStat struct {
@ -36,6 +38,7 @@ type FileSystem struct {
Chdir func(string) error Chdir func(string) error
Abs func(string) (string, error) Abs func(string) (string, error)
EvalSymlinks func(string) (string, error) EvalSymlinks func(string) (string, error)
CopyDir func(src, dst string) error
} }
func DefaultFileSystem() *FileSystem { func DefaultFileSystem() *FileSystem {
@ -55,6 +58,7 @@ func DefaultFileSystem() *FileSystem {
dfs.DirectoryExistsAt = dfs.directoryExistsDefault dfs.DirectoryExistsAt = dfs.directoryExistsDefault
dfs.FileExists = dfs.fileExistsDefault dfs.FileExists = dfs.fileExistsDefault
dfs.Abs = dfs.absDefault dfs.Abs = dfs.absDefault
dfs.CopyDir = dfs.copyDirDefault
return &dfs return &dfs
} }
@ -100,6 +104,9 @@ func FromFileSystem(params FileSystem) *FileSystem {
if params.Dir != nil { if params.Dir != nil {
dfs.Dir = params.Dir dfs.Dir = params.Dir
} }
if params.CopyDir != nil {
dfs.CopyDir = params.CopyDir
}
return dfs return dfs
} }
@ -180,3 +187,8 @@ func (filesystem *FileSystem) absDefault(path string) (string, error) {
} }
return filepath.Abs(path) return filepath.Abs(path)
} }
// copyDirDefault recursively copies a directory tree, preserving permissions.
func (filesystem *FileSystem) copyDirDefault(src string, dst string) error {
return copyDir.Copy(src, dst, copyDir.Options{Sync: true})
}

View File

@ -69,7 +69,13 @@ func parseHelmVersion(versionStr string) (*semver.Version, error) {
return nil, fmt.Errorf("empty helm version") return nil, fmt.Errorf("empty helm version")
} }
v, err := chartify.FindSemVerInfo(versionStr) // Check if version string starts with "v", if not add it
processedVersion := strings.TrimSpace(versionStr)
if !strings.HasPrefix(processedVersion, "v") {
processedVersion = "v" + processedVersion
}
v, err := chartify.FindSemVerInfo(processedVersion)
if err != nil { if err != nil {
return nil, fmt.Errorf("error find helm srmver version '%s': %w", versionStr, err) return nil, fmt.Errorf("error find helm srmver version '%s': %w", versionStr, err)
@ -116,11 +122,10 @@ func redactedURL(chart string) string {
} }
// New for running helm commands // New for running helm commands
func New(helmBinary string, options HelmExecOptions, logger *zap.SugaredLogger, kubeconfig string, kubeContext string, runner Runner) *execer { func New(helmBinary string, options HelmExecOptions, logger *zap.SugaredLogger, kubeconfig string, kubeContext string, runner Runner) (*execer, error) {
// TODO: proper error handling
version, err := GetHelmVersion(helmBinary, runner) version, err := GetHelmVersion(helmBinary, runner)
if err != nil { if err != nil {
panic(err) return nil, err
} }
if version.Prerelease() != "" { if version.Prerelease() != "" {
@ -137,7 +142,7 @@ func New(helmBinary string, options HelmExecOptions, logger *zap.SugaredLogger,
kubeContext: kubeContext, kubeContext: kubeContext,
runner: runner, runner: runner,
decryptedSecrets: make(map[string]*decryptedSecret), decryptedSecrets: make(map[string]*decryptedSecret),
} }, nil
} }
func (helm *execer) SetExtraArgs(args ...string) { func (helm *execer) SetExtraArgs(args ...string) {

View File

@ -36,16 +36,22 @@ func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string
return mock.output, mock.err return mock.output, mock.err
} }
func MockExecer(logger *zap.SugaredLogger, kubeconfig, kubeContext string) *execer { func MockExecer(logger *zap.SugaredLogger, kubeconfig, kubeContext string) (*execer, error) {
execer := New("helm", HelmExecOptions{}, logger, kubeconfig, kubeContext, &mockRunner{}) execer, err := New("helm", HelmExecOptions{}, logger, kubeconfig, kubeContext, &mockRunner{})
return execer if err != nil {
return nil, err
}
return execer, nil
} }
// Test methods // Test methods
func TestNewHelmExec(t *testing.T) { func TestNewHelmExec(t *testing.T) {
buffer := bytes.NewBufferString("something") buffer := bytes.NewBufferString("something")
helm := MockExecer(NewLogger(buffer, "debug"), "config", "dev") helm, err := MockExecer(NewLogger(buffer, "debug"), "config", "dev")
if err != nil {
t.Error(err)
}
if helm.kubeContext != "dev" { if helm.kubeContext != "dev" {
t.Error("helmexec.New() - kubeContext") t.Error("helmexec.New() - kubeContext")
} }
@ -58,7 +64,10 @@ func TestNewHelmExec(t *testing.T) {
} }
func Test_SetExtraArgs(t *testing.T) { func Test_SetExtraArgs(t *testing.T) {
helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
if err != nil {
t.Error(err)
}
helm.SetExtraArgs() helm.SetExtraArgs()
if len(helm.extra) != 0 { if len(helm.extra) != 0 {
t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field") t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field")
@ -74,7 +83,10 @@ func Test_SetExtraArgs(t *testing.T) {
} }
func Test_SetHelmBinary(t *testing.T) { func Test_SetHelmBinary(t *testing.T) {
helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
if err != nil {
t.Error(err)
}
if helm.helmBinary != "helm" { if helm.helmBinary != "helm" {
t.Error("helmexec.command - default command is not helm") t.Error("helmexec.command - default command is not helm")
} }
@ -85,7 +97,10 @@ func Test_SetHelmBinary(t *testing.T) {
} }
func Test_SetEnableLiveOutput(t *testing.T) { func Test_SetEnableLiveOutput(t *testing.T) {
helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
if err != nil {
t.Error(err)
}
if helm.options.EnableLiveOutput { if helm.options.EnableLiveOutput {
t.Error("helmexec.options.EnableLiveOutput should not be enabled by default") t.Error("helmexec.options.EnableLiveOutput should not be enabled by default")
} }
@ -96,7 +111,10 @@ func Test_SetEnableLiveOutput(t *testing.T) {
} }
func Test_SetDisableForceUpdate(t *testing.T) { func Test_SetDisableForceUpdate(t *testing.T) {
helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev") helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
if err != nil {
t.Error(err)
}
if helm.options.DisableForceUpdate { if helm.options.DisableForceUpdate {
t.Error("helmexec.options.ForceUpdate should not be enabled by default") t.Error("helmexec.options.ForceUpdate should not be enabled by default")
} }
@ -155,11 +173,14 @@ exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.e
func Test_AddRepo(t *testing.T) { func Test_AddRepo(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "config", "dev") helm, err := MockExecer(logger, "config", "dev")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
// Test case with certfile and keyfile // Test case with certfile and keyfile
buffer.Reset() buffer.Reset()
err := helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "", "", false, false) err = helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "", "", false, false)
expected := `Adding repo myRepo https://repo.example.com/ expected := `Adding repo myRepo https://repo.example.com/
exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem
` `
@ -292,8 +313,11 @@ exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.e
func Test_UpdateRepo(t *testing.T) { func Test_UpdateRepo(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "config", "dev") helm, err := MockExecer(logger, "config", "dev")
err := helm.UpdateRepo() if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.UpdateRepo()
expected := `Updating repo expected := `Updating repo
exec: helm --kubeconfig config --kube-context dev repo update exec: helm --kubeconfig config --kube-context dev repo update
` `
@ -346,8 +370,11 @@ exec: helm --kubeconfig config --kube-context dev registry login repo.example.co
func Test_SyncRelease(t *testing.T) { func Test_SyncRelease(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "config", "dev") helm, err := MockExecer(logger, "config", "dev")
err := helm.SyncRelease(HelmContext{}, "release", "chart", "default", "--timeout 10", "--wait", "--wait-for-jobs") if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.SyncRelease(HelmContext{}, "release", "chart", "default", "--timeout 10", "--wait", "--wait-for-jobs")
expected := `Upgrading release=release, chart=chart, namespace=default expected := `Upgrading release=release, chart=chart, namespace=default
exec: helm --kubeconfig config --kube-context dev upgrade --install release chart --timeout 10 --wait --wait-for-jobs --history-max 0 exec: helm --kubeconfig config --kube-context dev upgrade --install release chart --timeout 10 --wait --wait-for-jobs --history-max 0
` `
@ -386,8 +413,11 @@ exec: helm --kubeconfig config --kube-context dev upgrade --install release http
func Test_UpdateDeps(t *testing.T) { func Test_UpdateDeps(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "config", "dev") helm, err := MockExecer(logger, "config", "dev")
err := helm.UpdateDeps("./chart/foo") if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.UpdateDeps("./chart/foo")
expected := `Updating dependency ./chart/foo expected := `Updating dependency ./chart/foo
exec: helm --kubeconfig config --kube-context dev dependency update ./chart/foo exec: helm --kubeconfig config --kube-context dev dependency update ./chart/foo
` `
@ -416,8 +446,11 @@ func Test_BuildDeps(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a")} helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a")}
helm := New("helm", HelmExecOptions{}, logger, "config", "dev", &helm3Runner) helm, err := New("helm", HelmExecOptions{}, logger, "config", "dev", &helm3Runner)
err := helm.BuildDeps("foo", "./chart/foo", []string{"--skip-refresh"}...) if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.BuildDeps("foo", "./chart/foo", []string{"--skip-refresh"}...)
expected := `Building dependency release=foo, chart=./chart/foo expected := `Building dependency release=foo, chart=./chart/foo
exec: helm --kubeconfig config --kube-context dev dependency build ./chart/foo --skip-refresh exec: helm --kubeconfig config --kube-context dev dependency build ./chart/foo --skip-refresh
v3.2.4+ge29ce2a v3.2.4+ge29ce2a
@ -458,7 +491,10 @@ v3.2.4+ge29ce2a
buffer.Reset() buffer.Reset()
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94")} helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94")}
helm = New("helm", HelmExecOptions{}, logger, "config", "dev", &helm2Runner) helm, err = New("helm", HelmExecOptions{}, logger, "config", "dev", &helm2Runner)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.BuildDeps("foo", "./chart/foo") err = helm.BuildDeps("foo", "./chart/foo")
expected = `Building dependency release=foo, chart=./chart/foo expected = `Building dependency release=foo, chart=./chart/foo
exec: helm --kubeconfig config --kube-context dev dependency build ./chart/foo exec: helm --kubeconfig config --kube-context dev dependency build ./chart/foo
@ -484,14 +520,17 @@ func Test_DecryptSecret(t *testing.T) {
}() }()
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "config", "dev") helm, err := MockExecer(logger, "config", "dev")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
tmpFilePath := "path/to/temp/file" tmpFilePath := "path/to/temp/file"
helm.writeTempFile = func(content []byte) (string, error) { helm.writeTempFile = func(content []byte) (string, error) {
return tmpFilePath, nil return tmpFilePath, nil
} }
_, err := helm.DecryptSecret(HelmContext{}, "secretName") _, err = helm.DecryptSecret(HelmContext{}, "secretName")
if err != nil { if err != nil {
t.Errorf("Error: %v", err) t.Errorf("Error: %v", err)
} }
@ -533,7 +572,10 @@ func Test_DecryptSecretWithGotmpl(t *testing.T) {
}() }()
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "config", "dev") helm, err := MockExecer(logger, "config", "dev")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
tmpFilePath := "path/to/temp/file" tmpFilePath := "path/to/temp/file"
helm.writeTempFile = func(content []byte) (string, error) { helm.writeTempFile = func(content []byte) (string, error) {
@ -541,7 +583,7 @@ func Test_DecryptSecretWithGotmpl(t *testing.T) {
} }
secretName := "secretName.yaml.gotmpl" secretName := "secretName.yaml.gotmpl"
_, err := helm.DecryptSecret(HelmContext{}, secretName) _, err = helm.DecryptSecret(HelmContext{}, secretName)
if err != nil { if err != nil {
t.Errorf("Error: %v", err) t.Errorf("Error: %v", err)
} }
@ -566,8 +608,11 @@ Decrypted %s/secretName.yaml.gotmpl into %s
func Test_DiffRelease(t *testing.T) { func Test_DiffRelease(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "config", "dev") helm, err := MockExecer(logger, "config", "dev")
err := helm.DiffRelease(HelmContext{}, "release", "chart", "default", false, "--timeout 10", "--wait", "--wait-for-jobs") if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.DiffRelease(HelmContext{}, "release", "chart", "default", false, "--timeout 10", "--wait", "--wait-for-jobs")
expected := `Comparing release=release, chart=chart, namespace=default expected := `Comparing release=release, chart=chart, namespace=default
exec: helm --kubeconfig config --kube-context dev diff upgrade --allow-unreleased release chart --timeout 10 --wait --wait-for-jobs exec: helm --kubeconfig config --kube-context dev diff upgrade --allow-unreleased release chart --timeout 10 --wait --wait-for-jobs
@ -609,8 +654,11 @@ exec: helm --kubeconfig config --kube-context dev diff upgrade --allow-unrelease
func Test_DeleteRelease(t *testing.T) { func Test_DeleteRelease(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "config", "dev") helm, err := MockExecer(logger, "config", "dev")
err := helm.DeleteRelease(HelmContext{}, "release") if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.DeleteRelease(HelmContext{}, "release")
expected := `Deleting release expected := `Deleting release
exec: helm --kubeconfig config --kube-context dev delete release exec: helm --kubeconfig config --kube-context dev delete release
` `
@ -624,8 +672,11 @@ exec: helm --kubeconfig config --kube-context dev delete release
func Test_DeleteRelease_Flags(t *testing.T) { func Test_DeleteRelease_Flags(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "config", "dev") helm, err := MockExecer(logger, "config", "dev")
err := helm.DeleteRelease(HelmContext{}, "release", "--purge") if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.DeleteRelease(HelmContext{}, "release", "--purge")
expected := `Deleting release expected := `Deleting release
exec: helm --kubeconfig config --kube-context dev delete release --purge exec: helm --kubeconfig config --kube-context dev delete release --purge
` `
@ -640,8 +691,11 @@ exec: helm --kubeconfig config --kube-context dev delete release --purge
func Test_TestRelease(t *testing.T) { func Test_TestRelease(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "config", "dev") helm, err := MockExecer(logger, "config", "dev")
err := helm.TestRelease(HelmContext{}, "release") if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.TestRelease(HelmContext{}, "release")
expected := `Testing release expected := `Testing release
exec: helm --kubeconfig config --kube-context dev test release exec: helm --kubeconfig config --kube-context dev test release
` `
@ -655,8 +709,11 @@ exec: helm --kubeconfig config --kube-context dev test release
func Test_TestRelease_Flags(t *testing.T) { func Test_TestRelease_Flags(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "config", "dev") helm, err := MockExecer(logger, "config", "dev")
err := helm.TestRelease(HelmContext{}, "release", "--cleanup", "--timeout", "60") if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.TestRelease(HelmContext{}, "release", "--cleanup", "--timeout", "60")
expected := `Testing release expected := `Testing release
exec: helm --kubeconfig config --kube-context dev test release --cleanup --timeout 60 exec: helm --kubeconfig config --kube-context dev test release --cleanup --timeout 60
` `
@ -671,8 +728,11 @@ exec: helm --kubeconfig config --kube-context dev test release --cleanup --timeo
func Test_ReleaseStatus(t *testing.T) { func Test_ReleaseStatus(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "config", "dev") helm, err := MockExecer(logger, "config", "dev")
err := helm.ReleaseStatus(HelmContext{}, "myRelease") if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.ReleaseStatus(HelmContext{}, "myRelease")
expected := `Getting status myRelease expected := `Getting status myRelease
exec: helm --kubeconfig config --kube-context dev status myRelease exec: helm --kubeconfig config --kube-context dev status myRelease
` `
@ -687,9 +747,12 @@ exec: helm --kubeconfig config --kube-context dev status myRelease
func Test_exec(t *testing.T) { func Test_exec(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "", "") helm, err := MockExecer(logger, "", "")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
env := map[string]string{} env := map[string]string{}
_, err := helm.exec([]string{"version"}, env, nil) _, err = helm.exec([]string{"version"}, env, nil)
expected := `exec: helm version expected := `exec: helm version
` `
if err != nil { if err != nil {
@ -699,14 +762,20 @@ func Test_exec(t *testing.T) {
t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected) t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected)
} }
helm = MockExecer(logger, "config", "dev") helm, err = MockExecer(logger, "config", "dev")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
ret, _ := helm.exec([]string{"diff"}, env, nil) ret, _ := helm.exec([]string{"diff"}, env, nil)
if len(ret) != 0 { if len(ret) != 0 {
t.Error("helmexec.exec() - expected empty return value") t.Error("helmexec.exec() - expected empty return value")
} }
buffer.Reset() buffer.Reset()
helm = MockExecer(logger, "config", "dev") helm, err = MockExecer(logger, "config", "dev")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
_, err = helm.exec([]string{"diff", "release", "chart", "--timeout 10", "--wait", "--wait-for-jobs"}, env, nil) _, err = helm.exec([]string{"diff", "release", "chart", "--timeout 10", "--wait", "--wait-for-jobs"}, env, nil)
expected = `exec: helm --kubeconfig config --kube-context dev diff release chart --timeout 10 --wait --wait-for-jobs expected = `exec: helm --kubeconfig config --kube-context dev diff release chart --timeout 10 --wait --wait-for-jobs
` `
@ -741,7 +810,10 @@ func Test_exec(t *testing.T) {
} }
buffer.Reset() buffer.Reset()
helm = MockExecer(logger, "", "") helm, err = MockExecer(logger, "", "")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
helm.SetHelmBinary("overwritten") helm.SetHelmBinary("overwritten")
_, err = helm.exec([]string{"version"}, env, nil) _, err = helm.exec([]string{"version"}, env, nil)
expected = `exec: overwritten version expected = `exec: overwritten version
@ -757,8 +829,11 @@ func Test_exec(t *testing.T) {
func Test_Lint(t *testing.T) { func Test_Lint(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "config", "dev") helm, err := MockExecer(logger, "config", "dev")
err := helm.Lint("release", "path/to/chart", "--values", "file.yml") if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.Lint("release", "path/to/chart", "--values", "file.yml")
expected := `Linting release=release, chart=path/to/chart expected := `Linting release=release, chart=path/to/chart
exec: helm --kubeconfig config --kube-context dev lint path/to/chart --values file.yml exec: helm --kubeconfig config --kube-context dev lint path/to/chart --values file.yml
` `
@ -773,8 +848,11 @@ exec: helm --kubeconfig config --kube-context dev lint path/to/chart --values fi
func Test_Fetch(t *testing.T) { func Test_Fetch(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "config", "dev") helm, err := MockExecer(logger, "config", "dev")
err := helm.Fetch("chart", "--version", "1.2.3", "--untar", "--untardir", "/tmp/dir") if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.Fetch("chart", "--version", "1.2.3", "--untar", "--untardir", "/tmp/dir")
expected := `Fetching chart expected := `Fetching chart
exec: helm --kubeconfig config --kube-context dev fetch chart --version 1.2.3 --untar --untardir /tmp/dir exec: helm --kubeconfig config --kube-context dev fetch chart --version 1.2.3 --untar --untardir /tmp/dir
` `
@ -848,8 +926,11 @@ exec: helm --kubeconfig config --kube-context dev pull oci://repo/helm-charts --
tt := tests[i] tt := tests[i]
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
buffer.Reset() buffer.Reset()
helm := New(tt.helmBin, HelmExecOptions{}, logger, "config", "dev", &mockRunner{output: []byte(tt.helmVersion)}) helm, err := New(tt.helmBin, HelmExecOptions{}, logger, "config", "dev", &mockRunner{output: []byte(tt.helmVersion)})
err := helm.ChartPull(tt.chartName, tt.chartPath, tt.chartFlags...) if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.ChartPull(tt.chartName, tt.chartPath, tt.chartFlags...)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -922,8 +1003,11 @@ func Test_LogLevels(t *testing.T) {
for logLevel, expected := range logLevelTests { for logLevel, expected := range logLevelTests {
buffer.Reset() buffer.Reset()
logger := NewLogger(&buffer, logLevel) logger := NewLogger(&buffer, logLevel)
helm := MockExecer(logger, "", "") helm, err := MockExecer(logger, "", "")
err := helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password", "", false, false) if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password", "", false, false)
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -951,8 +1035,11 @@ func Test_mergeEnv(t *testing.T) {
func Test_Template(t *testing.T) { func Test_Template(t *testing.T) {
var buffer bytes.Buffer var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug") logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "config", "dev") helm, err := MockExecer(logger, "config", "dev")
err := helm.TemplateRelease("release", "path/to/chart", "--values", "file.yml") if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = helm.TemplateRelease("release", "path/to/chart", "--values", "file.yml")
expected := `Templating release=release, chart=path/to/chart expected := `Templating release=release, chart=path/to/chart
exec: helm --kubeconfig config --kube-context dev template release path/to/chart --values file.yml exec: helm --kubeconfig config --kube-context dev template release path/to/chart --values file.yml
` `
@ -978,13 +1065,19 @@ exec: helm --kubeconfig config --kube-context dev template release https://examp
func Test_IsHelm3(t *testing.T) { func Test_IsHelm3(t *testing.T) {
helm2Runner := mockRunner{output: []byte("Client: v2.16.0+ge13bc94\n")} helm2Runner := mockRunner{output: []byte("Client: v2.16.0+ge13bc94\n")}
helm := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) helm, err := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if helm.IsHelm3() { if helm.IsHelm3() {
t.Error("helmexec.IsHelm3() - Detected Helm 3 with Helm 2 version") t.Error("helmexec.IsHelm3() - Detected Helm 3 with Helm 2 version")
} }
helm3Runner := mockRunner{output: []byte("v3.0.0+ge29ce2a\n")} helm3Runner := mockRunner{output: []byte("v3.0.0+ge29ce2a\n")}
helm = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner) helm, err = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !helm.IsHelm3() { if !helm.IsHelm3() {
t.Error("helmexec.IsHelm3() - Failed to detect Helm 3") t.Error("helmexec.IsHelm3() - Failed to detect Helm 3")
} }
@ -1012,14 +1105,20 @@ func Test_GetPluginVersion(t *testing.T) {
func Test_GetVersion(t *testing.T) { func Test_GetVersion(t *testing.T) {
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")} helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")}
helm := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) helm, err := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
ver := helm.GetVersion() ver := helm.GetVersion()
if ver.Major != 2 || ver.Minor != 16 || ver.Patch != 1 { if ver.Major != 2 || ver.Minor != 16 || ver.Patch != 1 {
t.Errorf("helmexec.GetVersion - did not detect correct Helm2 version; it was: %+v", ver) t.Errorf("helmexec.GetVersion - did not detect correct Helm2 version; it was: %+v", ver)
} }
helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a\n")} helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a\n")}
helm = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner) helm, err = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
ver = helm.GetVersion() ver = helm.GetVersion()
if ver.Major != 3 || ver.Minor != 2 || ver.Patch != 4 { if ver.Major != 3 || ver.Minor != 2 || ver.Patch != 4 {
t.Errorf("helmexec.GetVersion - did not detect correct Helm3 version; it was: %+v", ver) t.Errorf("helmexec.GetVersion - did not detect correct Helm3 version; it was: %+v", ver)
@ -1028,7 +1127,10 @@ func Test_GetVersion(t *testing.T) {
func Test_IsVersionAtLeast(t *testing.T) { func Test_IsVersionAtLeast(t *testing.T) {
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")} helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")}
helm := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner) helm, err := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if !helm.IsVersionAtLeast("2.1.0") { if !helm.IsVersionAtLeast("2.1.0") {
t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1") t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1")
} }
@ -1140,6 +1242,24 @@ func TestParseHelmVersion(t *testing.T) {
want: nil, want: nil,
wantErr: true, wantErr: true,
}, },
{
name: "version without v prefix",
version: "3.2.4",
want: semver.MustParse("v3.2.4"),
wantErr: false,
},
{
name: "version without v prefix with build info",
version: "3.2.4+ge29ce2a",
want: semver.MustParse("v3.2.4+ge29ce2a"),
wantErr: false,
},
{
name: "version without v prefix with spaces",
version: " 3.2.4 ",
want: semver.MustParse("v3.2.4"),
wantErr: false,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@ -118,7 +118,7 @@ func Output(ctx context.Context, c *exec.Cmd, stripArgsValuesOnExitError bool, l
exitStatus := waitStatus.ExitStatus() exitStatus := waitStatus.ExitStatus()
err = newExitError(c.Path, c.Args, exitStatus, ee, stderr.String(), combined.String(), stripArgsValuesOnExitError) err = newExitError(c.Path, c.Args, exitStatus, ee, stderr.String(), combined.String(), stripArgsValuesOnExitError)
default: default:
panic(fmt.Sprintf("unexpected error: %v", err)) err = fmt.Errorf("unexpected error: %v", err)
} }
} }

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"maps"
"net/http" "net/http"
neturl "net/url" neturl "net/url"
"os" "os"
@ -14,9 +15,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/hashicorp/go-getter" "github.com/hashicorp/go-getter"
"github.com/hashicorp/go-getter/helper/url" "github.com/hashicorp/go-getter/helper/url"
"go.uber.org/zap" "go.uber.org/zap"
@ -213,13 +213,16 @@ func (r *Remote) Fetch(path string, cacheDirOpt ...string) (string, error) {
return "", fmt.Errorf("[bug] cacheDirOpt's length: want 0 or 1, got %d", len(cacheDirOpt)) return "", fmt.Errorf("[bug] cacheDirOpt's length: want 0 or 1, got %d", len(cacheDirOpt))
} }
query := u.RawQuery query, _ := neturl.ParseQuery(u.RawQuery)
should_cache := query.Get("cache") != "false"
delete(query, "cache")
var cacheKey string var cacheKey string
replacer := strings.NewReplacer(":", "", "//", "_", "/", "_", ".", "_") replacer := strings.NewReplacer(":", "", "//", "_", "/", "_", ".", "_")
dirKey := replacer.Replace(srcDir) dirKey := replacer.Replace(srcDir)
if len(query) > 0 { if len(query) > 0 {
q, _ := neturl.ParseQuery(query) q := maps.Clone(query)
if q.Has("sshkey") { if q.Has("sshkey") {
q.Set("sshkey", "redacted") q.Set("sshkey", "redacted")
} }
@ -262,7 +265,7 @@ func (r *Remote) Fetch(path string, cacheDirOpt ...string) (string, error) {
} }
} }
if !cached { if !cached || !should_cache {
var getterSrc string var getterSrc string
if u.User != "" { if u.User != "" {
getterSrc = fmt.Sprintf("%s://%s@%s%s", u.Scheme, u.User, u.Host, u.Dir) getterSrc = fmt.Sprintf("%s://%s@%s%s", u.Scheme, u.User, u.Host, u.Dir)
@ -271,7 +274,7 @@ func (r *Remote) Fetch(path string, cacheDirOpt ...string) (string, error) {
} }
if len(query) > 0 { if len(query) > 0 {
getterSrc = strings.Join([]string{getterSrc, query}, "?") getterSrc = strings.Join([]string{getterSrc, query.Encode()}, "?")
} }
r.Logger.Debugf("remote> downloading %s to %s", getterSrc, getterDst) r.Logger.Debugf("remote> downloading %s to %s", getterSrc, getterDst)
@ -364,22 +367,22 @@ func (g *S3Getter) Get(wd, src, dst string) error {
return err return err
} }
// Create a new AWS session using the default AWS configuration // Create a new AWS config and S3 client using AWS SDK v2
sess := session.Must(session.NewSessionWithOptions(session.Options{ cfg, err := config.LoadDefaultConfig(context.TODO(),
SharedConfigState: session.SharedConfigEnable, config.WithRegion(region),
Config: aws.Config{ )
Region: aws.String(region), if err != nil {
}, return err
})) }
// Create an S3 client using the session // Create an S3 client using the config
s3Client := s3.New(sess) s3Client := s3.NewFromConfig(cfg)
getObjectInput := &s3.GetObjectInput{ getObjectInput := &s3.GetObjectInput{
Bucket: &bucket, Bucket: &bucket,
Key: &key, Key: &key,
} }
resp, err := s3Client.GetObject(getObjectInput) resp, err := s3Client.GetObject(context.TODO(), getObjectInput)
defer func(Body io.ReadCloser) { defer func(Body io.ReadCloser) {
err := Body.Close() err := Body.Close()
if err != nil { if err != nil {
@ -463,48 +466,47 @@ func (g *S3Getter) S3FileExists(path string) (string, error) {
} }
// Region // Region
g.Logger.Debugf("Creating session for determining S3 region %s", path) g.Logger.Debugf("Creating config for determining S3 region %s", path)
sess := session.Must(session.NewSessionWithOptions(session.Options{ cfg, err := config.LoadDefaultConfig(context.TODO())
SharedConfigState: session.SharedConfigEnable, if err != nil {
})) return "", err
}
g.Logger.Debugf("Getting bucket %s location %s", bucket, path) g.Logger.Debugf("Getting bucket %s location %s", bucket, path)
s3Client := s3.New(sess) s3Client := s3.NewFromConfig(cfg)
bucketRegion := "us-east-1" bucketRegion := "us-east-1"
getBucketLocationInput := &s3.GetBucketLocationInput{ getBucketLocationInput := &s3.GetBucketLocationInput{
Bucket: aws.String(bucket), Bucket: &bucket,
} }
resp, err := s3Client.GetBucketLocation(getBucketLocationInput) resp, err := s3Client.GetBucketLocation(context.TODO(), getBucketLocationInput)
if err != nil { if err != nil {
return "", fmt.Errorf("Error: Failed to retrieve bucket location: %v\n", err) return "", fmt.Errorf("failed to retrieve bucket location: %v", err)
} }
if resp == nil || resp.LocationConstraint == nil { if resp == nil || string(resp.LocationConstraint) == "" {
g.Logger.Debugf("Bucket has no location Assuming us-east-1") g.Logger.Debugf("Bucket has no location Assuming us-east-1")
} else { } else {
bucketRegion = *resp.LocationConstraint bucketRegion = string(resp.LocationConstraint)
} }
g.Logger.Debugf("Got bucket location %s", bucketRegion) g.Logger.Debugf("Got bucket location %s", bucketRegion)
// File existence // File existence
g.Logger.Debugf("Creating new session with region to see if file exists") g.Logger.Debugf("Creating new config with region to see if file exists")
regionSession, err := session.NewSessionWithOptions(session.Options{ regionCfg, err := config.LoadDefaultConfig(context.TODO(),
SharedConfigState: session.SharedConfigEnable, config.WithRegion(bucketRegion),
Config: aws.Config{ )
Region: aws.String(bucketRegion),
},
})
if err != nil { if err != nil {
g.Logger.Error(err) g.Logger.Error(err)
return bucketRegion, err
} }
g.Logger.Debugf("Creating new s3 client to check if object exists") g.Logger.Debugf("Creating new s3 client to check if object exists")
s3Client = s3.New(regionSession) s3Client = s3.NewFromConfig(regionCfg)
headObjectInput := &s3.HeadObjectInput{ headObjectInput := &s3.HeadObjectInput{
Bucket: &bucket, Bucket: &bucket,
Key: &key, Key: &key,
} }
g.Logger.Debugf("Fethcing head %s", path) g.Logger.Debugf("Fethcing head %s", path)
_, err = s3Client.HeadObject(headObjectInput) _, err = s3Client.HeadObject(context.TODO(), headObjectInput)
return bucketRegion, err return bucketRegion, err
} }

View File

@ -179,7 +179,7 @@ func TestRemote_SShGitHub_WithSshKey(t *testing.T) {
if wd != CacheDir() { if wd != CacheDir() {
return fmt.Errorf("unexpected wd: %s", wd) return fmt.Errorf("unexpected wd: %s", wd)
} }
if src != "git::ssh://git@github.com/helmfile/helmfiles.git?ref=0.40.0&sshkey=ZWNkc2Etc2hhMi1uaXN0cDI1NiBBQUFBRTJWalpITmhMWE5vWVRJdGJtbHpkSEF5TlRZQUFBQUlibWx6ZEhBeU5UWUFBQUJCQkJTU3dOY2xoVzQ2Vm9VR3dMQ3JscVRHYUdOVWdRVUVEUEptc1ZzdUViL2RBNUcrQk9YMWxGaUVMYU9HQ2F6bS9KQkR2V3Y2Y0ZDQUtVRjVocVJOUjdJPSA=" { if src != "git::ssh://git@github.com/helmfile/helmfiles.git?ref=0.40.0&sshkey=ZWNkc2Etc2hhMi1uaXN0cDI1NiBBQUFBRTJWalpITmhMWE5vWVRJdGJtbHpkSEF5TlRZQUFBQUlibWx6ZEhBeU5UWUFBQUJCQkJTU3dOY2xoVzQ2Vm9VR3dMQ3JscVRHYUdOVWdRVUVEUEptc1ZzdUViL2RBNUcrQk9YMWxGaUVMYU9HQ2F6bS9KQkR2V3Y2Y0ZDQUtVRjVocVJOUjdJPSA%3D" {
return fmt.Errorf("unexpected src: %s", src) return fmt.Errorf("unexpected src: %s", src)
} }
@ -219,6 +219,73 @@ func TestRemote_SShGitHub_WithSshKey(t *testing.T) {
} }
} }
func TestRemote_SShGitHub_WithDisableCacheKey(t *testing.T) {
cleanfs := map[string]string{
CacheDir(): "",
}
cachefs := map[string]string{
filepath.Join(CacheDir(), "ssh_github_com_helmfile_helmfiles_git.ref=main/releases/kiam.yaml"): "foo: bar",
}
testcases := []struct {
name string
files map[string]string
expectCacheHit bool
}{
{name: "not expectCacheHit", files: cleanfs, expectCacheHit: false},
{name: "forceNoCacheHit", files: cachefs, expectCacheHit: false},
}
for _, tt := range testcases {
t.Run(tt.name, func(t *testing.T) {
testfs := testhelper.NewTestFs(tt.files)
hit := true
get := func(wd, src, dst string) error {
if wd != CacheDir() {
return fmt.Errorf("unexpected wd: %s", wd)
}
if src != "git::ssh://git@github.com/helmfile/helmfiles.git?ref=main" {
return fmt.Errorf("unexpected src: %s", src)
}
hit = false
return nil
}
getter := &testGetter{
get: get,
}
remote := &Remote{
Logger: helmexec.NewLogger(io.Discard, "debug"),
Home: CacheDir(),
Getter: getter,
fs: testfs.ToFileSystem(),
}
url := "git::ssh://git@github.com/helmfile/helmfiles.git@releases/kiam.yaml?ref=main&cache=false"
file, err := remote.Locate(url)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expectedFile := filepath.Join(CacheDir(), "ssh_github_com_helmfile_helmfiles_git.ref=main/releases/kiam.yaml")
if file != expectedFile {
t.Errorf("unexpected file located: %s vs expected: %s", file, expectedFile)
}
if tt.expectCacheHit && !hit {
t.Errorf("unexpected result: unexpected cache miss")
}
if !tt.expectCacheHit && hit {
t.Errorf("unexpected result: unexpected cache hit")
}
})
}
}
func TestRemote_S3(t *testing.T) { func TestRemote_S3(t *testing.T) {
cleanfs := map[string]string{ cleanfs := map[string]string{
CacheDir(): "", CacheDir(): "",

View File

@ -9,15 +9,15 @@ import (
var ( var (
// GoYamlV3 is set to true in order to let Helmfile use // GoYamlV3 is set to true in order to let Helmfile use
// gopkg.in/yaml.v3 instead of gopkg.in/yaml.v2. // go.yaml.in/yaml/v3 instead of go.yaml.in/yaml/v2.
// It's false by default in Helmfile v0.x and true in Helmfile v1.x. // It's false by default in Helmfile v0.x and true in Helmfile v1.x.
GoYamlV3 bool GoYamlV3 bool
) )
func Info() string { func Info() string {
yamlLib := "gopkg.in/yaml.v2" yamlLib := "go.yaml.in/yaml/v2"
if GoYamlV3 { if GoYamlV3 {
yamlLib = "gopkg.in/yaml.v3" yamlLib = "go.yaml.in/yaml/v3"
} }
return fmt.Sprintf("YAML library = %v", yamlLib) return fmt.Sprintf("YAML library = %v", yamlLib)

View File

@ -26,6 +26,8 @@ const (
DefaultHCLFileExtension = ".hcl" DefaultHCLFileExtension = ".hcl"
) )
var ValidUpdateStrategyValues = []string{UpdateStrategyReinstallIfForbidden}
type StateLoadError struct { type StateLoadError struct {
Msg string Msg string
Cause error Cause error
@ -43,6 +45,14 @@ func (e *UndefinedEnvError) Error() string {
return fmt.Sprintf("environment \"%s\" is not defined", e.Env) return fmt.Sprintf("environment \"%s\" is not defined", e.Env)
} }
type InvalidUpdateStrategyError struct {
UpdateStrategy string
}
func (e *InvalidUpdateStrategyError) Error() string {
return fmt.Sprintf("updateStrategy %q is invalid, valid values are: %s or not set", e.UpdateStrategy, strings.Join(ValidUpdateStrategyValues, ", "))
}
type StateCreator struct { type StateCreator struct {
logger *zap.SugaredLogger logger *zap.SugaredLogger
@ -54,7 +64,7 @@ type StateCreator struct {
LoadFile func(inheritedEnv, overrodeEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*HelmState, error) LoadFile func(inheritedEnv, overrodeEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*HelmState, error)
getHelm func(*HelmState) helmexec.Interface getHelm func(*HelmState) (helmexec.Interface, error)
overrideHelmBinary string overrideHelmBinary string
@ -67,7 +77,7 @@ type StateCreator struct {
lockFile string lockFile string
} }
func NewCreator(logger *zap.SugaredLogger, fs *filesystem.FileSystem, valsRuntime vals.Evaluator, getHelm func(*HelmState) helmexec.Interface, overrideHelmBinary string, overrideKustomizeBinary string, remote *remote.Remote, enableLiveOutput bool, lockFile string) *StateCreator { func NewCreator(logger *zap.SugaredLogger, fs *filesystem.FileSystem, valsRuntime vals.Evaluator, getHelm func(*HelmState) (helmexec.Interface, error), overrideHelmBinary string, overrideKustomizeBinary string, remote *remote.Remote, enableLiveOutput bool, lockFile string) *StateCreator {
return &StateCreator{ return &StateCreator{
logger: logger, logger: logger,
@ -116,11 +126,19 @@ func (c *StateCreator) Parse(content []byte, baseDir, file string) (*HelmState,
return nil, &StateLoadError{fmt.Sprintf("failed to read %s: reading document at index %d", file, i), err} return nil, &StateLoadError{fmt.Sprintf("failed to read %s: reading document at index %d", file, i), err}
} }
if err := mergo.Merge(&state, &intermediate, mergo.WithAppendSlice); err != nil { if err := mergo.Merge(&state, &intermediate, mergo.WithAppendSlice, mergo.WithOverride); err != nil {
return nil, &StateLoadError{fmt.Sprintf("failed to read %s: merging document at index %d", file, i), err} return nil, &StateLoadError{fmt.Sprintf("failed to read %s: merging document at index %d", file, i), err}
} }
} }
state.logger = c.logger
state.valsRuntime = c.valsRuntime
return &state, nil
}
// applyDefaultsAndOverrides applies default binary paths and command-line overrides
func (c *StateCreator) applyDefaultsAndOverrides(state *HelmState) {
if c.overrideHelmBinary != "" && c.overrideHelmBinary != DefaultHelmBinary { if c.overrideHelmBinary != "" && c.overrideHelmBinary != DefaultHelmBinary {
state.DefaultHelmBinary = c.overrideHelmBinary state.DefaultHelmBinary = c.overrideHelmBinary
} else if state.DefaultHelmBinary == "" { } else if state.DefaultHelmBinary == "" {
@ -134,11 +152,6 @@ func (c *StateCreator) Parse(content []byte, baseDir, file string) (*HelmState,
// Let `helmfile --kustomize-binary ""` not break this helmfile run // Let `helmfile --kustomize-binary ""` not break this helmfile run
state.DefaultKustomizeBinary = DefaultKustomizeBinary state.DefaultKustomizeBinary = DefaultKustomizeBinary
} }
state.logger = c.logger
state.valsRuntime = c.valsRuntime
return &state, nil
} }
// LoadEnvValues loads environment values files relative to the `baseDir` // LoadEnvValues loads environment values files relative to the `baseDir`
@ -181,6 +194,11 @@ func (c *StateCreator) ParseAndLoad(content []byte, baseDir, file string, envNam
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Apply default binaries and command-line overrides only for the main helmfile
// after loading and merging all bases. This ensures that values from bases are
// properly respected and that later bases/documents can override earlier ones.
c.applyDefaultsAndOverrides(state)
} }
state, err = c.LoadEnvValues(state, envName, failOnMissingEnv, envValues, overrode) state, err = c.LoadEnvValues(state, envName, failOnMissingEnv, envValues, overrode)
@ -216,7 +234,7 @@ func (c *StateCreator) loadBases(envValues, overrodeEnv *environment.Environment
layers = append(layers, st) layers = append(layers, st)
for i := 1; i < len(layers); i++ { for i := 1; i < len(layers); i++ {
if err := mergo.Merge(layers[0], layers[i], mergo.WithAppendSlice); err != nil { if err := mergo.Merge(layers[0], layers[i], mergo.WithAppendSlice, mergo.WithOverride); err != nil {
return nil, err return nil, err
} }
} }
@ -224,6 +242,31 @@ func (c *StateCreator) loadBases(envValues, overrodeEnv *environment.Environment
return layers[0], nil return layers[0], nil
} }
// getEnvMissingFileHandlerConfig returns the first non-nil MissingFileHandlerConfig from the environment spec, state, or default.
func (st *HelmState) getEnvMissingFileHandlerConfig(es EnvironmentSpec) *MissingFileHandlerConfig {
switch {
case es.MissingFileHandlerConfig != nil:
return es.MissingFileHandlerConfig
case st.MissingFileHandlerConfig != nil:
return st.MissingFileHandlerConfig
default:
return nil
}
}
// getEnvMissingFileHandler returns the first non-nil MissingFileHandler from the environment spec, state, or default.
func (st *HelmState) getEnvMissingFileHandler(es EnvironmentSpec) *string {
defaultMissingFileHandler := "Error"
switch {
case es.MissingFileHandler != nil:
return es.MissingFileHandler
case st.MissingFileHandler != nil:
return st.MissingFileHandler
default:
return &defaultMissingFileHandler
}
}
// nolint: unparam // nolint: unparam
func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEnv bool, ctxEnv, overrode *environment.Environment) (*environment.Environment, error) { func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEnv bool, ctxEnv, overrode *environment.Environment) (*environment.Environment, error) {
secretVals := map[string]any{} secretVals := map[string]any{}
@ -240,7 +283,7 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn
var envSecretFiles []string var envSecretFiles []string
if len(envSpec.Secrets) > 0 { if len(envSpec.Secrets) > 0 {
for _, urlOrPath := range envSpec.Secrets { for _, urlOrPath := range envSpec.Secrets {
resolved, skipped, err := st.storage().resolveFile(envSpec.MissingFileHandler, "environment values", urlOrPath, envSpec.MissingFileHandlerConfig.resolveFileOptions()...) resolved, skipped, err := st.storage().resolveFile(st.getEnvMissingFileHandler(envSpec), "environment values", urlOrPath, st.getEnvMissingFileHandlerConfig(envSpec).resolveFileOptions()...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -317,7 +360,10 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn
func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles []string, envVals map[string]any, keepFileExtensions []string) ([]string, error) { func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles []string, envVals map[string]any, keepFileExtensions []string) ([]string, error) {
var errs []error var errs []error
var decryptedFilesKeeper []string var decryptedFilesKeeper []string
helm := c.getHelm(st) helm, err := c.getHelm(st)
if err != nil {
return nil, err
}
inputs := envSecretFiles inputs := envSecretFiles
inputsSize := len(inputs) inputsSize := len(inputs)

View File

@ -1,6 +1,7 @@
package state package state
import ( import (
"fmt"
"path/filepath" "path/filepath"
"reflect" "reflect"
"testing" "testing"
@ -525,3 +526,205 @@ releaseContext:
t.Errorf("unexpected values: expected=%v, actual=%v", expectedValues, actualValues) t.Errorf("unexpected values: expected=%v, actual=%v", expectedValues, actualValues)
} }
} }
// TestHelmBinaryInBases tests that helmBinary and kustomizeBinary settings
// from bases are properly merged with later values overriding earlier ones
func TestHelmBinaryInBases(t *testing.T) {
tests := []struct {
name string
files map[string]string
mainFile string
expectedHelmBinary string
expectedKustomizeBinary string
}{
{
name: "helmBinary in second base should be used",
files: map[string]string{
"/path/to/helmfile.yaml": `bases:
- ./bases/env.yaml
---
bases:
- ./bases/repos.yaml
---
bases:
- ./bases/releases.yaml
`,
"/path/to/bases/env.yaml": `environments:
default:
values:
- key: value1
`,
"/path/to/bases/repos.yaml": `repositories:
- name: stable
url: https://charts.helm.sh/stable
helmBinary: /path/to/custom/helm
`,
"/path/to/bases/releases.yaml": `releases:
- name: myapp
chart: stable/nginx
`,
},
mainFile: "/path/to/helmfile.yaml",
expectedHelmBinary: "/path/to/custom/helm",
expectedKustomizeBinary: DefaultKustomizeBinary,
},
{
name: "helmBinary in main file after bases should override",
files: map[string]string{
"/path/to/helmfile.yaml": `bases:
- ./bases/env.yaml
---
bases:
- ./bases/repos.yaml
---
bases:
- ./bases/releases.yaml
helmBinary: /path/to/main/helm
`,
"/path/to/bases/env.yaml": `environments:
default:
values:
- key: value1
`,
"/path/to/bases/repos.yaml": `repositories:
- name: stable
url: https://charts.helm.sh/stable
helmBinary: /path/to/base/helm
`,
"/path/to/bases/releases.yaml": `releases:
- name: myapp
chart: stable/nginx
`,
},
mainFile: "/path/to/helmfile.yaml",
expectedHelmBinary: "/path/to/main/helm",
expectedKustomizeBinary: DefaultKustomizeBinary,
},
{
name: "helmBinary in main file between bases should override earlier bases",
files: map[string]string{
"/path/to/helmfile.yaml": `bases:
- ./bases/env.yaml
---
bases:
- ./bases/repos.yaml
helmBinary: /path/to/middle/helm
---
bases:
- ./bases/releases.yaml
`,
"/path/to/bases/env.yaml": `environments:
default:
values:
- key: value1
`,
"/path/to/bases/repos.yaml": `repositories:
- name: stable
url: https://charts.helm.sh/stable
helmBinary: /path/to/base/helm
`,
"/path/to/bases/releases.yaml": `releases:
- name: myapp
chart: stable/nginx
`,
},
mainFile: "/path/to/helmfile.yaml",
expectedHelmBinary: "/path/to/middle/helm",
expectedKustomizeBinary: DefaultKustomizeBinary,
},
{
name: "kustomizeBinary in base should be used",
files: map[string]string{
"/path/to/helmfile.yaml": `bases:
- ./bases/base.yaml
`,
"/path/to/bases/base.yaml": `kustomizeBinary: /path/to/custom/kustomize
releases:
- name: myapp
chart: mychart
`,
},
mainFile: "/path/to/helmfile.yaml",
expectedHelmBinary: DefaultHelmBinary,
expectedKustomizeBinary: "/path/to/custom/kustomize",
},
{
name: "both helmBinary and kustomizeBinary in different bases",
files: map[string]string{
"/path/to/helmfile.yaml": `bases:
- ./bases/helm.yaml
---
bases:
- ./bases/kustomize.yaml
`,
"/path/to/bases/helm.yaml": `helmBinary: /path/to/custom/helm
`,
"/path/to/bases/kustomize.yaml": `kustomizeBinary: /path/to/custom/kustomize
`,
},
mainFile: "/path/to/helmfile.yaml",
expectedHelmBinary: "/path/to/custom/helm",
expectedKustomizeBinary: "/path/to/custom/kustomize",
},
{
name: "later base overrides earlier base for helmBinary",
files: map[string]string{
"/path/to/helmfile.yaml": `bases:
- ./bases/first.yaml
---
bases:
- ./bases/second.yaml
`,
"/path/to/bases/first.yaml": `helmBinary: /path/to/first/helm
`,
"/path/to/bases/second.yaml": `helmBinary: /path/to/second/helm
`,
},
mainFile: "/path/to/helmfile.yaml",
expectedHelmBinary: "/path/to/second/helm",
expectedKustomizeBinary: DefaultKustomizeBinary,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
testFs := testhelper.NewTestFs(tt.files)
if testFs.Cwd == "" {
testFs.Cwd = "/"
}
r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
creator := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", "", r, false, "")
// Set up LoadFile for recursive base loading
creator.LoadFile = func(inheritedEnv, overrodeEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*HelmState, error) {
path := filepath.Join(baseDir, file)
content, ok := tt.files[path]
if !ok {
return nil, fmt.Errorf("file not found: %s", path)
}
return creator.ParseAndLoad([]byte(content), filepath.Dir(path), path, DefaultEnv, true, evaluateBases, inheritedEnv, overrodeEnv)
}
yamlContent, ok := tt.files[tt.mainFile]
if !ok {
t.Fatalf("no file named %q registered", tt.mainFile)
}
state, err := creator.ParseAndLoad([]byte(yamlContent), filepath.Dir(tt.mainFile), tt.mainFile, DefaultEnv, true, true, nil, nil)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if state.DefaultHelmBinary != tt.expectedHelmBinary {
t.Errorf("helmBinary mismatch: expected=%s, actual=%s",
tt.expectedHelmBinary, state.DefaultHelmBinary)
}
if state.DefaultKustomizeBinary != tt.expectedKustomizeBinary {
t.Errorf("kustomizeBinary mismatch: expected=%s, actual=%s",
tt.expectedKustomizeBinary, state.DefaultKustomizeBinary)
}
})
}
}

View File

@ -0,0 +1,64 @@
package state
import (
"errors"
"strings"
"sync"
"testing"
"go.uber.org/zap"
"github.com/helmfile/helmfile/pkg/exectest"
"github.com/helmfile/helmfile/pkg/helmexec"
)
func TestIsReleaseInstalled_HandlesConnectionError(t *testing.T) {
logger := zap.NewNop().Sugar()
state := &HelmState{
logger: logger,
}
// Create a custom helm mock that fails on List operations
helm := &CustomFailingHelm{
Helm: &exectest.Helm{
DiffMutex: &sync.Mutex{},
ChartsMutex: &sync.Mutex{},
ReleasesMutex: &sync.Mutex{},
Helm3: true,
},
}
release := ReleaseSpec{
Name: "test-release",
Chart: "test/chart",
Namespace: "default",
}
// This should return an error due to connection failure
_, err := state.isReleaseInstalled(helmexec.HelmContext{}, helm, release)
// Verify that error was propagated
if err == nil {
t.Fatalf("expected isReleaseInstalled to return error when Kubernetes is unreachable, but got no error")
}
if err.Error() == "" {
t.Fatalf("expected isReleaseInstalled to return meaningful error when Kubernetes is unreachable, but got empty error")
}
// Check if the error contains the expected message
expectedMsg := "Kubernetes cluster unreachable"
if err.Error() != expectedMsg && !strings.Contains(err.Error(), "Kubernetes cluster unreachable") {
t.Fatalf("expected error to contain 'Kubernetes cluster unreachable', but got: %v", err.Error())
}
}
// CustomFailingHelm wraps exectest.Helm and overrides List to simulate failures
type CustomFailingHelm struct {
*exectest.Helm
}
func (h *CustomFailingHelm) List(context helmexec.HelmContext, filter string, flags ...string) (string, error) {
return "", errors.New("Kubernetes cluster unreachable: Get \"http://localhost:8080/version\": dial tcp [::1]:8080: connect: connection refused")
}

View File

@ -14,5 +14,5 @@ type EnvironmentSpec struct {
// a message about the missing file at the log-level. // a message about the missing file at the log-level.
MissingFileHandler *string `yaml:"missingFileHandler,omitempty"` MissingFileHandler *string `yaml:"missingFileHandler,omitempty"`
// MissingFileHandlerConfig is composed of various settings for the MissingFileHandler // MissingFileHandlerConfig is composed of various settings for the MissingFileHandler
MissingFileHandlerConfig MissingFileHandlerConfig `yaml:"missingFileHandlerConfig,omitempty"` MissingFileHandlerConfig *MissingFileHandlerConfig `yaml:"missingFileHandlerConfig,omitempty"`
} }

View File

@ -6,7 +6,6 @@ import (
"path/filepath" "path/filepath"
"slices" "slices"
"sort" "sort"
"strconv"
"strings" "strings"
"github.com/helmfile/chartify" "github.com/helmfile/chartify"
@ -158,31 +157,17 @@ func (st *HelmState) appendWaitForJobsFlags(flags []string, release *ReleaseSpec
return flags return flags
} }
func (st *HelmState) appendWaitFlags(flags []string, helm helmexec.Interface, release *ReleaseSpec, ops *SyncOpts) []string { func (st *HelmState) appendWaitFlags(flags []string, release *ReleaseSpec, ops *SyncOpts) []string {
var hasWait bool
switch { switch {
case release.Wait != nil && *release.Wait: case release.Wait != nil && *release.Wait:
hasWait = true
flags = append(flags, "--wait") flags = append(flags, "--wait")
case ops != nil && ops.Wait: case ops != nil && ops.Wait:
hasWait = true
flags = append(flags, "--wait") flags = append(flags, "--wait")
case release.Wait == nil && st.HelmDefaults.Wait: case release.Wait == nil && st.HelmDefaults.Wait:
hasWait = true
flags = append(flags, "--wait") flags = append(flags, "--wait")
} }
// see https://github.com/helm/helm/releases/tag/v3.15.0 // Note: --wait-retries flag has been removed from Helm and is no longer supported
// https://github.com/helm/helm/commit/fc74964 // WaitRetries configuration is preserved for backward compatibility but ignored
if hasWait && helm.IsVersionAtLeast("3.15.0") {
switch {
case release.WaitRetries != nil && *release.WaitRetries > 0:
flags = append(flags, "--wait-retries", strconv.Itoa(*release.WaitRetries))
case ops != nil && ops.WaitRetries > 0:
flags = append(flags, "--wait-retries", strconv.Itoa(ops.WaitRetries))
case release.WaitRetries == nil && st.HelmDefaults.WaitRetries > 0:
flags = append(flags, "--wait-retries", strconv.Itoa(st.HelmDefaults.WaitRetries))
}
}
return flags return flags
} }
@ -350,7 +335,7 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
jsonPatches := release.JSONPatches jsonPatches := release.JSONPatches
if len(jsonPatches) > 0 { if len(jsonPatches) > 0 {
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, jsonPatches, release.MissingFileHandler) generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, jsonPatches)
if err != nil { if err != nil {
return nil, clean, err return nil, clean, err
} }
@ -364,7 +349,7 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
strategicMergePatches := release.StrategicMergePatches strategicMergePatches := release.StrategicMergePatches
if len(strategicMergePatches) > 0 { if len(strategicMergePatches) > 0 {
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, strategicMergePatches, release.MissingFileHandler) generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, strategicMergePatches)
if err != nil { if err != nil {
return nil, clean, err return nil, clean, err
} }
@ -378,7 +363,7 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
transformers := release.Transformers transformers := release.Transformers
if len(transformers) > 0 { if len(transformers) > 0 {
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, transformers, release.MissingFileHandler) generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, transformers)
if err != nil { if err != nil {
return nil, clean, err return nil, clean, err
} }

View File

@ -76,7 +76,6 @@ func TestAppendWaitFlags(t *testing.T) {
name string name string
release *ReleaseSpec release *ReleaseSpec
syncOpts *SyncOpts syncOpts *SyncOpts
helm helmexec.Interface
helmSpec HelmSpec helmSpec HelmSpec
expected []string expected []string
}{ }{
@ -85,7 +84,6 @@ func TestAppendWaitFlags(t *testing.T) {
name: "release wait", name: "release wait",
release: &ReleaseSpec{Wait: &[]bool{true}[0]}, release: &ReleaseSpec{Wait: &[]bool{true}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{}, helmSpec: HelmSpec{},
expected: []string{"--wait"}, expected: []string{"--wait"},
}, },
@ -93,7 +91,6 @@ func TestAppendWaitFlags(t *testing.T) {
name: "cli flags wait", name: "cli flags wait",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: &SyncOpts{Wait: true}, syncOpts: &SyncOpts{Wait: true},
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{}, helmSpec: HelmSpec{},
expected: []string{"--wait"}, expected: []string{"--wait"},
}, },
@ -101,7 +98,6 @@ func TestAppendWaitFlags(t *testing.T) {
name: "helm defaults wait", name: "helm defaults wait",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{Wait: true}, helmSpec: HelmSpec{Wait: true},
expected: []string{"--wait"}, expected: []string{"--wait"},
}, },
@ -109,7 +105,6 @@ func TestAppendWaitFlags(t *testing.T) {
name: "release wait false", name: "release wait false",
release: &ReleaseSpec{Wait: &[]bool{false}[0]}, release: &ReleaseSpec{Wait: &[]bool{false}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{Wait: true}, helmSpec: HelmSpec{Wait: true},
expected: []string{}, expected: []string{},
}, },
@ -117,7 +112,6 @@ func TestAppendWaitFlags(t *testing.T) {
name: "cli flags wait false", name: "cli flags wait false",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: &SyncOpts{}, syncOpts: &SyncOpts{},
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{Wait: true}, helmSpec: HelmSpec{Wait: true},
expected: []string{"--wait"}, expected: []string{"--wait"},
}, },
@ -125,66 +119,58 @@ func TestAppendWaitFlags(t *testing.T) {
name: "helm defaults wait false", name: "helm defaults wait false",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{Wait: false}, helmSpec: HelmSpec{Wait: false},
expected: []string{}, expected: []string{},
}, },
// --wait-retries // --wait-retries flag has been removed from Helm
{ {
name: "release wait and retry unsupported", name: "release wait and retry unsupported",
release: &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]}, release: &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{}, helmSpec: HelmSpec{},
expected: []string{"--wait"}, expected: []string{"--wait"},
}, },
{ {
name: "release wait and retry supported", name: "release wait and retry - retries ignored",
release: &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]}, release: &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{}, helmSpec: HelmSpec{},
expected: []string{"--wait", "--wait-retries", "1"}, expected: []string{"--wait"},
}, },
{ {
name: "no wait retry", name: "no wait retry",
release: &ReleaseSpec{WaitRetries: &[]int{1}[0]}, release: &ReleaseSpec{WaitRetries: &[]int{1}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{}, helmSpec: HelmSpec{},
expected: []string{}, expected: []string{},
}, },
{ {
name: "cli flags wait and retry", name: "cli flags wait and retry - retries ignored",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: &SyncOpts{Wait: true, WaitRetries: 2}, syncOpts: &SyncOpts{Wait: true, WaitRetries: 2},
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{}, helmSpec: HelmSpec{},
expected: []string{"--wait", "--wait-retries", "2"}, expected: []string{"--wait"},
}, },
{ {
name: "helm defaults wait retry", name: "helm defaults wait retry - retries ignored",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{Wait: true, WaitRetries: 3}, helmSpec: HelmSpec{Wait: true, WaitRetries: 3},
expected: []string{"--wait", "--wait-retries", "3"}, expected: []string{"--wait"},
}, },
{ {
name: "release wait default retries", name: "release wait default retries - retries ignored",
release: &ReleaseSpec{Wait: &[]bool{true}[0]}, release: &ReleaseSpec{Wait: &[]bool{true}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{WaitRetries: 4}, helmSpec: HelmSpec{WaitRetries: 4},
expected: []string{"--wait", "--wait-retries", "4"}, expected: []string{"--wait"},
}, },
{ {
name: "release retries default wait", name: "release retries default wait - retries ignored",
release: &ReleaseSpec{WaitRetries: &[]int{5}[0]}, release: &ReleaseSpec{WaitRetries: &[]int{5}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{Wait: true}, helmSpec: HelmSpec{Wait: true},
expected: []string{"--wait", "--wait-retries", "5"}, expected: []string{"--wait"},
}, },
} }
@ -192,7 +178,7 @@ func TestAppendWaitFlags(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
st := &HelmState{} st := &HelmState{}
st.HelmDefaults = tt.helmSpec st.HelmDefaults = tt.helmSpec
got := st.appendWaitFlags([]string{}, tt.helm, tt.release, tt.syncOpts) got := st.appendWaitFlags([]string{}, tt.release, tt.syncOpts)
require.Equalf(t, tt.expected, got, "appendWaitFlags() = %v, want %v", got, tt.expected) require.Equalf(t, tt.expected, got, "appendWaitFlags() = %v, want %v", got, tt.expected)
}) })
} }

File diff suppressed because it is too large Load Diff

View File

@ -170,6 +170,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
version *semver.Version version *semver.Version
defaults HelmSpec defaults HelmSpec
release *ReleaseSpec release *ReleaseSpec
syncOpts *SyncOpts
want []string want []string
wantErr string wantErr string
}{ }{
@ -455,6 +456,27 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
"--namespace", "test-namespace", "--namespace", "test-namespace",
}, },
}, },
{
name: "timeout-from-cli-flag",
defaults: HelmSpec{
Timeout: 123,
},
release: &ReleaseSpec{
Chart: "test/chart",
Version: "0.1",
Timeout: some(456),
Name: "test-charts",
Namespace: "test-namespace",
},
syncOpts: &SyncOpts{
Timeout: 789,
},
want: []string{
"--version", "0.1",
"--timeout", "789s",
"--namespace", "test-namespace",
},
},
{ {
name: "atomic", name: "atomic",
defaults: HelmSpec{ defaults: HelmSpec{
@ -737,7 +759,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
Version: tt.version, Version: tt.version,
} }
args, _, err := state.flagsForUpgrade(helm, tt.release, 0, nil) args, _, err := state.flagsForUpgrade(helm, tt.release, 0, tt.syncOpts)
if err != nil && tt.wantErr == "" { if err != nil && tt.wantErr == "" {
t.Errorf("unexpected error flagsForUpgrade: %v", err) t.Errorf("unexpected error flagsForUpgrade: %v", err)
} }
@ -1618,6 +1640,112 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) {
} }
} }
func TestHelmState_SyncReleasesAffectedReleasesWithReinstallIfForbidden(t *testing.T) {
no := false
tests := []struct {
name string
releases []ReleaseSpec
installed []bool
wantAffected exectest.Affected
}{
{
name: "2 new",
releases: []ReleaseSpec{
{
Name: "releaseNameFoo-forbidden",
Chart: "foo",
UpdateStrategy: "reinstallIfForbidden",
},
{
Name: "releaseNameBar-forbidden",
Chart: "foo",
UpdateStrategy: "reinstallIfForbidden",
},
},
wantAffected: exectest.Affected{
Upgraded: []*exectest.Release{
{Name: "releaseNameFoo-forbidden", Flags: []string{}},
{Name: "releaseNameBar-forbidden", Flags: []string{}},
},
Reinstalled: nil,
Deleted: nil,
Failed: nil,
},
},
{
name: "1 removed, 1 new, 1 reinstalled first new",
releases: []ReleaseSpec{
{
Name: "releaseNameFoo-forbidden",
Chart: "foo",
UpdateStrategy: "reinstallIfForbidden",
},
{
Name: "releaseNameBar",
Chart: "foo",
UpdateStrategy: "reinstallIfForbidden",
Installed: &no,
},
{
Name: "releaseNameFoo-forbidden",
Chart: "foo",
UpdateStrategy: "reinstallIfForbidden",
},
},
installed: []bool{true, true, true},
wantAffected: exectest.Affected{
Upgraded: []*exectest.Release{
{Name: "releaseNameFoo-forbidden", Flags: []string{}},
},
Reinstalled: []*exectest.Release{
{Name: "releaseNameFoo-forbidden", Flags: []string{}},
},
Deleted: []*exectest.Release{
{Name: "releaseNameBar", Flags: []string{}},
},
Failed: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
state := &HelmState{
ReleaseSetSpec: ReleaseSetSpec{
Releases: tt.releases,
},
logger: logger,
valsRuntime: valsRuntime,
RenderedValues: map[string]any{},
}
helm := &exectest.Helm{
Lists: map[exectest.ListKey]string{},
}
//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: "--uninstalling --deployed --failed --pending"}] = release.Name
}
}
affectedReleases := AffectedReleases{}
if err := state.SyncReleases(&affectedReleases, helm, []string{}, 1); err != nil {
if !testEq(affectedReleases.Failed, tt.wantAffected.Failed) {
t.Errorf("HelmState.SyncReleases() error failed for [%s] = %v, want %v", tt.name, affectedReleases.Failed, tt.wantAffected.Failed)
} //else expected error
}
if !testEq(affectedReleases.Upgraded, tt.wantAffected.Upgraded) {
t.Errorf("HelmState.SyncReleases() upgrade failed for [%s] = %v, want %v", tt.name, affectedReleases.Upgraded, tt.wantAffected.Upgraded)
}
if !testEq(affectedReleases.Reinstalled, tt.wantAffected.Reinstalled) {
t.Errorf("HelmState.SyncReleases() reinstalled failed for [%s] = %v, want %v", tt.name, affectedReleases.Reinstalled, tt.wantAffected.Reinstalled)
}
if !testEq(affectedReleases.Deleted, tt.wantAffected.Deleted) {
t.Errorf("HelmState.SyncReleases() deleted failed for [%s] = %v, want %v", tt.name, affectedReleases.Deleted, tt.wantAffected.Deleted)
}
})
}
}
func testEq(a []*ReleaseSpec, b []*exectest.Release) bool { func testEq(a []*ReleaseSpec, b []*exectest.Release) bool {
// If one is nil, the other must also be nil. // If one is nil, the other must also be nil.
if (a == nil) != (b == nil) { if (a == nil) != (b == nil) {
@ -1836,14 +1964,19 @@ func TestHelmState_DiffReleases(t *testing.T) {
} }
func TestHelmState_DiffFlags(t *testing.T) { func TestHelmState_DiffFlags(t *testing.T) {
enable := true
disable := false
tests := []struct { tests := []struct {
name string name string
defaults HelmSpec
releases []ReleaseSpec releases []ReleaseSpec
helm *exectest.Helm helm *exectest.Helm
wantDiffFlags []string wantDiffFlags []string
}{ }{
{ {
name: "release with api version and kubeversion", name: "release with api version and kubeversion",
defaults: HelmSpec{},
releases: []ReleaseSpec{ releases: []ReleaseSpec{
{ {
Name: "releaseName", Name: "releaseName",
@ -1856,7 +1989,8 @@ func TestHelmState_DiffFlags(t *testing.T) {
wantDiffFlags: []string{"--api-versions", "helmfile.test/v1", "--api-versions", "helmfile.test/v2", "--kube-version", "1.21"}, wantDiffFlags: []string{"--api-versions", "helmfile.test/v1", "--api-versions", "helmfile.test/v2", "--kube-version", "1.21"},
}, },
{ {
name: "release with kubeversion and plain http which is ignored", name: "release with kubeversion and plain http which is ignored",
defaults: HelmSpec{},
releases: []ReleaseSpec{ releases: []ReleaseSpec{
{ {
Name: "releaseName", Name: "releaseName",
@ -1868,13 +2002,52 @@ func TestHelmState_DiffFlags(t *testing.T) {
helm: &exectest.Helm{}, helm: &exectest.Helm{},
wantDiffFlags: []string{"--kube-version", "1.21"}, wantDiffFlags: []string{"--kube-version", "1.21"},
}, },
{
name: "release with enable-dns",
defaults: HelmSpec{EnableDNS: false},
releases: []ReleaseSpec{
{
Name: "releaseName",
Chart: "foo",
EnableDNS: &enable,
},
},
helm: &exectest.Helm{},
wantDiffFlags: []string{"--enable-dns"},
},
{
name: "release with disable-dns override",
defaults: HelmSpec{EnableDNS: true},
releases: []ReleaseSpec{
{
Name: "releaseName",
Chart: "foo",
EnableDNS: &disable,
},
},
helm: &exectest.Helm{},
wantDiffFlags: nil,
},
{
name: "release with enable-dns from default",
defaults: HelmSpec{EnableDNS: true},
releases: []ReleaseSpec{
{
Name: "releaseName",
Chart: "foo",
},
},
helm: &exectest.Helm{},
wantDiffFlags: []string{"--enable-dns"},
},
} }
for i := range tests { for i := range tests {
tt := tests[i] tt := tests[i]
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
state := &HelmState{ state := &HelmState{
ReleaseSetSpec: ReleaseSetSpec{ ReleaseSetSpec: ReleaseSetSpec{
Releases: tt.releases, Releases: tt.releases,
HelmDefaults: tt.defaults,
}, },
logger: logger, logger: logger,
valsRuntime: valsRuntime, valsRuntime: valsRuntime,
@ -4571,3 +4744,58 @@ func TestPrepareSyncReleases_ValueControlReleaseOverride(t *testing.T) {
require.Equal(t, tt.flags, r.flags, "Wrong value control flag for release %s", r.release.Name) require.Equal(t, tt.flags, r.flags, "Wrong value control flag for release %s", r.release.Name)
} }
} }
func TestChartCacheKey(t *testing.T) {
st := &HelmState{}
// Test case 1: release with version
release1 := &ReleaseSpec{
Chart: "stable/nginx",
Version: "1.2.3",
}
key1 := st.getChartCacheKey(release1)
expected1 := ChartCacheKey{Chart: "stable/nginx", Version: "1.2.3"}
if key1 != expected1 {
t.Errorf("Expected %+v, got %+v", expected1, key1)
}
// Test case 2: release without version
release2 := &ReleaseSpec{
Chart: "stable/nginx",
}
key2 := st.getChartCacheKey(release2)
expected2 := ChartCacheKey{Chart: "stable/nginx", Version: ""}
if key2 != expected2 {
t.Errorf("Expected %+v, got %+v", expected2, key2)
}
}
func TestChartCache(t *testing.T) {
st := &HelmState{}
// Create a test key
key := ChartCacheKey{Chart: "stable/test", Version: "1.0.0"}
path := "/tmp/test-chart"
// Initially, chart should not be in cache
_, exists := st.checkChartCache(key)
if exists {
t.Error("Chart should not be in cache initially")
}
// Add to cache
st.addToChartCache(key, path)
// Now chart should be in cache
cachedPath, exists := st.checkChartCache(key)
if !exists {
t.Error("Chart should be in cache after adding")
}
if cachedPath != path {
t.Errorf("Expected path %s, got %s", path, cachedPath)
}
}

View File

@ -38,39 +38,39 @@ func TestGenerateID(t *testing.T) {
run(testcase{ run(testcase{
subject: "baseline", subject: "baseline",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
want: "foo-values-57ff559cd5", want: "foo-values-67dc97cbcb",
}) })
run(testcase{ run(testcase{
subject: "different bytes content", subject: "different bytes content",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
data: []byte(`{"k":"v"}`), data: []byte(`{"k":"v"}`),
want: "foo-values-75cd785b8b", want: "foo-values-75d7c4758c",
}) })
run(testcase{ run(testcase{
subject: "different map content", subject: "different map content",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
data: map[string]any{"k": "v"}, data: map[string]any{"k": "v"},
want: "foo-values-5b44fdc768", want: "foo-values-685f8cf685",
}) })
run(testcase{ run(testcase{
subject: "different chart", subject: "different chart",
release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"}, release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"},
want: "foo-values-6ddb5db94d", want: "foo-values-75597d9c57",
}) })
run(testcase{ run(testcase{
subject: "different name", subject: "different name",
release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"}, release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"},
want: "bar-values-79bbb8df74", want: "bar-values-7b77df65ff",
}) })
run(testcase{ run(testcase{
subject: "specific ns", subject: "specific ns",
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"}, release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"},
want: "myns-foo-values-c9457ccfb", want: "myns-foo-values-85f979545c",
}) })
for id, n := range ids { for id, n := range ids {

View File

@ -358,11 +358,11 @@ func testFromYaml(t *testing.T, GoYamlV3 bool) {
} }
func TestFromYaml(t *testing.T) { func TestFromYaml(t *testing.T) {
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) { t.Run("with go.yaml.in/yaml/v2", func(t *testing.T) {
testFromYaml(t, true) testFromYaml(t, true)
}) })
t.Run("with gopkg.in/yaml.v3", func(t *testing.T) { t.Run("with go.yaml.in/yaml/v3", func(t *testing.T) {
testFromYaml(t, false) testFromYaml(t, false)
}) })
} }

View File

@ -4,8 +4,8 @@ import (
"bytes" "bytes"
"io" "io"
v2 "gopkg.in/yaml.v2" v2 "go.yaml.in/yaml/v2"
v3 "gopkg.in/yaml.v3" v3 "go.yaml.in/yaml/v3"
"github.com/helmfile/helmfile/pkg/runtime" "github.com/helmfile/helmfile/pkg/runtime"
) )

View File

@ -13,9 +13,9 @@ func testYamlMarshal(t *testing.T, GoYamlV3 bool) {
var yamlLibraryName string var yamlLibraryName string
if GoYamlV3 { if GoYamlV3 {
yamlLibraryName = "gopkg.in/yaml.v3" yamlLibraryName = "go.yaml.in/yaml/v3"
} else { } else {
yamlLibraryName = "gopkg.in/yaml.v2" yamlLibraryName = "go.yaml.in/yaml/v2"
} }
v := runtime.GoYamlV3 v := runtime.GoYamlV3
@ -49,8 +49,8 @@ func testYamlMarshal(t *testing.T, GoYamlV3 bool) {
Annotation: "on", Annotation: "on",
}}, }},
expected: map[string]string{ expected: map[string]string{
"gopkg.in/yaml.v2": "name: John\ninfo:\n- age: 20\n address: New York\n annotation: \"on\"\n", "go.yaml.in/yaml/v2": "name: John\ninfo:\n- age: 20\n address: New York\n annotation: \"on\"\n",
"gopkg.in/yaml.v3": "name: John\ninfo:\n - age: 20\n address: New York\n annotation: \"on\"\n", "go.yaml.in/yaml/v3": "name: John\ninfo:\n - age: 20\n address: New York\n annotation: \"on\"\n",
}, },
}, },
} }
@ -63,11 +63,11 @@ func testYamlMarshal(t *testing.T, GoYamlV3 bool) {
} }
func TestYamlMarshal(t *testing.T) { func TestYamlMarshal(t *testing.T) {
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) { t.Run("with go.yaml.in/yaml/v2", func(t *testing.T) {
testYamlMarshal(t, true) testYamlMarshal(t, true)
}) })
t.Run("with gopkg.in/yaml.v3", func(t *testing.T) { t.Run("with go.yaml.in/yaml/v3", func(t *testing.T) {
testYamlMarshal(t, false) testYamlMarshal(t, false)
}) })
} }

View File

@ -30,12 +30,6 @@ var (
helmShortVersionRegex = regexp.MustCompile(`v\d+\.\d+\.\d+\+[a-z0-9]+`) helmShortVersionRegex = regexp.MustCompile(`v\d+\.\d+\.\d+\+[a-z0-9]+`)
) )
type ociChart struct {
name string
version string
digest string
}
type Config struct { type Config struct {
LocalDockerRegistry struct { LocalDockerRegistry struct {
Enabled bool `yaml:"enabled"` Enabled bool `yaml:"enabled"`
@ -58,11 +52,11 @@ func (f fakeInit) Force() bool {
} }
func TestHelmfileTemplateWithBuildCommand(t *testing.T) { func TestHelmfileTemplateWithBuildCommand(t *testing.T) {
t.Run("with gopkg.in/yaml.v3", func(t *testing.T) { t.Run("with go.yaml.in/yaml/v3", func(t *testing.T) {
testHelmfileTemplateWithBuildCommand(t, true) testHelmfileTemplateWithBuildCommand(t, true)
}) })
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) { t.Run("with go.yaml.in/yaml/v2", func(t *testing.T) {
testHelmfileTemplateWithBuildCommand(t, false) testHelmfileTemplateWithBuildCommand(t, false)
}) })
} }
@ -166,8 +160,6 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
} }
}) })
// ociCharts holds a list of chart name, version and digest distributed by local oci registry.
ociCharts := []ociChart{}
// If localDockerRegistry.enabled is set to `true`, // If localDockerRegistry.enabled is set to `true`,
// run the docker registry v2 and push the test charts to the registry // run the docker registry v2 and push the test charts to the registry
// so that it can be accessed by helm and helmfile as a oci registry based chart repository. // so that it can be accessed by helm and helmfile as a oci registry based chart repository.
@ -201,16 +193,9 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
if !c.IsDir() { if !c.IsDir() {
t.Fatalf("%s is not a directory", c) t.Fatalf("%s is not a directory", c)
} }
chartName, chartVersion := execHelmShowChart(t, chartPath)
tgzFile := execHelmPackage(t, chartPath) tgzFile := execHelmPackage(t, chartPath)
chartDigest, err := execHelmPush(t, tgzFile, fmt.Sprintf("oci://localhost:%d/myrepo", hostPort)) _, err := execHelmPush(t, tgzFile, fmt.Sprintf("oci://localhost:%d/myrepo", hostPort))
require.NoError(t, err, "Unable to run helm push to local registry: %v", err) require.NoError(t, err, "Unable to run helm push to local registry: %v", err)
ociCharts = append(ociCharts, ociChart{
name: chartName,
version: chartVersion,
digest: chartDigest,
})
} }
} }
@ -221,7 +206,7 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
helmfileCacheHome := filepath.Join(tmpDir, "helmfile_cache") helmfileCacheHome := filepath.Join(tmpDir, "helmfile_cache")
// HELM_CONFIG_HOME contains the registry auth file (registry.json) and the index of all the repos added via helm-repo-add (repositories.yaml). // HELM_CONFIG_HOME contains the registry auth file (registry.json) and the index of all the repos added via helm-repo-add (repositories.yaml).
helmConfigHome := filepath.Join(tmpDir, "helm_config") helmConfigHome := filepath.Join(tmpDir, "helm_config")
t.Logf("Using HELM_CACHE_HOME=%s, HELMFILE_CACHE_HOME=%s, HELM_CONFIG_HOME=%s", helmCacheHome, helmfileCacheHome, helmConfigHome) t.Logf("Using HELM_CACHE_HOME=%s, HELMFILE_CACHE_HOME=%s, HELM_CONFIG_HOME=%s, WD=%s", helmCacheHome, helmfileCacheHome, helmConfigHome, wd)
inputFile := filepath.Join(testdataDir, name, "input.yaml.gotmpl") inputFile := filepath.Join(testdataDir, name, "input.yaml.gotmpl")
outputFile := "" outputFile := ""
@ -230,6 +215,7 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
} else { } else {
outputFile = filepath.Join(testdataDir, name, "gopkg.in-yaml.v2-output.yaml") outputFile = filepath.Join(testdataDir, name, "gopkg.in-yaml.v2-output.yaml")
} }
expectedOutputFile := filepath.Join(testdataDir, name, "output.yaml")
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel() defer cancel()
@ -262,15 +248,8 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
gotStr = chartGitFullPathRegex.ReplaceAllString(gotStr, `chart=$$GoGetterPath`) gotStr = chartGitFullPathRegex.ReplaceAllString(gotStr, `chart=$$GoGetterPath`)
// Replace helm version with $HelmVersion // Replace helm version with $HelmVersion
gotStr = helmShortVersionRegex.ReplaceAllString(gotStr, `$$HelmVersion`) gotStr = helmShortVersionRegex.ReplaceAllString(gotStr, `$$HelmVersion`)
// Replace all occurrences of HELMFILE_CACHE_HOME with /home/runner/.cache/helmfile
// for stable test result
gotStr = strings.ReplaceAll(gotStr, helmfileCacheHome, "/home/runner/.cache/helmfile")
// OCI based helm charts are pulled and exported under temporary directory.
// We are not sure the exact name of the temporary directory generated by helmfile,
// so redact its base directory name with $TMP.
if config.LocalDockerRegistry.Enabled { if config.LocalDockerRegistry.Enabled {
var releaseName, chartPath string
sc := bufio.NewScanner(strings.NewReader(gotStr)) sc := bufio.NewScanner(strings.NewReader(gotStr))
for sc.Scan() { for sc.Scan() {
if !strings.HasPrefix(sc.Text(), "Templating ") { if !strings.HasPrefix(sc.Text(), "Templating ") {
@ -281,28 +260,20 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
if len(releaseChartParts) != 2 { if len(releaseChartParts) != 2 {
t.Fatal("Found unexpected log output of templating oci based helm chart, want=\"Templating release=<release_name>, chart=<chart_name>\"") t.Fatal("Found unexpected log output of templating oci based helm chart, want=\"Templating release=<release_name>, chart=<chart_name>\"")
} }
releaseNamePart, chartPathPart := releaseChartParts[0], releaseChartParts[1]
releaseName = strings.TrimPrefix(releaseNamePart, "release=")
chartPath = chartPathPart
}
for _, ociChart := range ociCharts {
chartPathWithoutTempDirBase := fmt.Sprintf("/%s/%s/%s/%s", releaseName, ociChart.name, ociChart.version, ociChart.name)
var chartPathBase string
if strings.HasSuffix(chartPath, chartPathWithoutTempDirBase) {
chartPathBase = strings.TrimSuffix(chartPath, chartPathWithoutTempDirBase)
}
if len(chartPathBase) != 0 {
gotStr = strings.ReplaceAll(gotStr, chartPathBase, "chart=$TMP")
}
gotStr = strings.ReplaceAll(gotStr, fmt.Sprintf("Digest: %s", ociChart.digest), "Digest: $DIGEST")
} }
} }
gotStr = strings.ReplaceAll(gotStr, helmfileCacheHome, "$HELMFILE_CACHE_HOME")
gotStr = strings.ReplaceAll(gotStr, wd, "__workingdir__")
if stat, _ := os.Stat(outputFile); stat != nil { if stat, _ := os.Stat(outputFile); stat != nil {
want, err := os.ReadFile(outputFile) want, err := os.ReadFile(outputFile)
wantStr := strings.ReplaceAll(string(want), "__workingdir__", wd)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, wantStr, gotStr) require.Equal(t, string(want), gotStr)
} else if stat, _ := os.Stat(expectedOutputFile); stat != nil {
want, err := os.ReadFile(expectedOutputFile)
require.NoError(t, err)
require.Equal(t, string(want), gotStr)
} else { } else {
// To update the test golden image(output.yaml), just remove it and rerun this test. // To update the test golden image(output.yaml), just remove it and rerun this test.
// We automatically capture the output to `output.yaml` in the test case directory // We automatically capture the output to `output.yaml` in the test case directory
@ -325,23 +296,6 @@ func execDocker(t *testing.T, args ...string) {
} }
} }
func execHelmShowChart(t *testing.T, localChart string) (string, string) {
t.Helper()
name, version := "", ""
out := execHelm(t, "show", "chart", localChart)
sc := bufio.NewScanner(strings.NewReader(out))
for sc.Scan() {
if strings.HasPrefix(sc.Text(), "name:") {
name = strings.TrimPrefix(sc.Text(), "name: ")
}
if strings.HasPrefix(sc.Text(), "version:") {
version = strings.TrimPrefix(sc.Text(), "version: ")
}
}
return name, version
}
func execHelmPackage(t *testing.T, localChart string) string { func execHelmPackage(t *testing.T, localChart string) string {
t.Helper() t.Helper()

View File

@ -1,5 +1,5 @@
Pulling localhost:5001/myrepo/raw:0.1.0 Pulling localhost:5001/myrepo/raw:0.1.0
Templating release=foo, chart=$TMP/foo/raw/0.1.0/raw Templating release=foo, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
--- ---
# Source: raw/templates/resources.yaml # Source: raw/templates/resources.yaml
apiVersion: v1 apiVersion: v1

View File

@ -0,0 +1,6 @@
localDockerRegistry:
enabled: true
port: 5001
chartifyTempDir: temp2
helmfileArgs:
- template

View File

@ -0,0 +1,22 @@
releases:
- name: foo
chart: oci://localhost:5001/myrepo/raw
version: 0.1.0
values: &oci_chart_pull_direct
- templates:
- |
apiVersion: v1
kind: ConfigMap
metadata:
name: {{`{{ .Release.Name }}`}}
namespace: {{`{{ .Release.Namespace }}`}}
annotations:
chart-version: {{`{{ .Chart.Version }}`}}
data:
values: {{`{{ .Release.Name }}`}}
- name: bar
chart: oci://localhost:5001/myrepo/raw
version: 0.1.0
namespace: ns2
values: *oci_chart_pull_direct

View File

@ -0,0 +1,27 @@
Pulling localhost:5001/myrepo/raw:0.1.0
Templating release=foo, chart=$HELMFILE_CACHE_HOME/oci__localhost_5001/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: foo
namespace: default
annotations:
chart-version: 0.1.0
data:
values: foo
Templating release=bar, chart=$HELMFILE_CACHE_HOME/oci__localhost_5001/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: bar
namespace: ns2
annotations:
chart-version: 0.1.0
data:
values: bar

View File

@ -0,0 +1,9 @@
# Templating two versions of the same chart with only one pulling of each version
localDockerRegistry:
enabled: true
port: 5001
chartifyTempDir: temp3
helmfileArgs:
# Prevent releases from racing and randomizing the log
- --concurrency=1
- template

View File

@ -0,0 +1,43 @@
repositories:
- name: myrepo
url: localhost:5001/myrepo
oci: true
releases:
- name: foo
chart: myrepo/raw
version: 0.1.0
values: &oci_chart_pull_once_values
- templates:
- |
apiVersion: v1
kind: ConfigMap
metadata:
name: {{`{{ .Release.Name }}`}}
namespace: {{`{{ .Release.Namespace }}`}}
annotations:
chart-version: {{`{{ .Chart.Version }}`}}
data:
values: {{`{{ .Release.Name }}`}}
- name: bar
chart: myrepo/raw
version: 0.1.0
values: *oci_chart_pull_once_values
- name: release-no-default-ns
chart: myrepo/raw
version: 0.1.0
namespace: no-default
values: *oci_chart_pull_once_values
- name: second-version-of-chart
chart: myrepo/raw
version: 0.0.1
namespace: foobar
values: *oci_chart_pull_once_values
- name: first-release-version-of-chart
chart: myrepo/raw
version: 0.1.0
values: *oci_chart_pull_once_values

View File

@ -0,0 +1,67 @@
Pulling localhost:5001/myrepo/raw:0.1.0
Pulling localhost:5001/myrepo/raw:0.0.1
Templating release=foo, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: foo
namespace: default
annotations:
chart-version: 0.1.0
data:
values: foo
Templating release=bar, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: bar
namespace: default
annotations:
chart-version: 0.1.0
data:
values: bar
Templating release=release-no-default-ns, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-no-default-ns
namespace: no-default
annotations:
chart-version: 0.1.0
data:
values: release-no-default-ns
Templating release=second-version-of-chart, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.0.1/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: second-version-of-chart
namespace: foobar
annotations:
chart-version: 0.0.1
data:
values: second-version-of-chart
Templating release=first-release-version-of-chart, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: first-release-version-of-chart
namespace: default
annotations:
chart-version: 0.1.0
data:
values: first-release-version-of-chart

View File

@ -0,0 +1,7 @@
# Templating few releases with the same chart\version and only one pulling
localDockerRegistry:
enabled: true
port: 5001
chartifyTempDir: temp3
helmfileArgs:
- template

View File

@ -0,0 +1,23 @@
repositories:
- name: myrepo
url: localhost:5001/myrepo
oci: true
releases:
{{- range $i := until 5 }}
- name: release-{{ $i }}
chart: myrepo/raw
version: 0.1.0
values:
- templates:
- |
apiVersion: v1
kind: ConfigMap
metadata:
name: {{`{{ .Release.Name }}`}}
namespace: {{`{{ .Release.Namespace }}`}}
annotations:
chart-version: {{`{{ .Chart.Version }}`}}
data:
values: {{`{{ .Release.Name }}`}}
{{- end }}

View File

@ -0,0 +1,66 @@
Pulling localhost:5001/myrepo/raw:0.1.0
Templating release=release-0, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-0
namespace: default
annotations:
chart-version: 0.1.0
data:
values: release-0
Templating release=release-1, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-1
namespace: default
annotations:
chart-version: 0.1.0
data:
values: release-1
Templating release=release-2, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-2
namespace: default
annotations:
chart-version: 0.1.0
data:
values: release-2
Templating release=release-3, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-3
namespace: default
annotations:
chart-version: 0.1.0
data:
values: release-3
Templating release=release-4, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
---
# Source: raw/templates/resources.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: release-4
namespace: default
annotations:
chart-version: 0.1.0
data:
values: release-4

View File

@ -27,7 +27,7 @@ export HELM_DATA_HOME="${helm_dir}/data"
export HELM_HOME="${HELM_DATA_HOME}" export HELM_HOME="${HELM_DATA_HOME}"
export HELM_PLUGINS="${HELM_DATA_HOME}/plugins" export HELM_PLUGINS="${HELM_DATA_HOME}/plugins"
export HELM_CONFIG_HOME="${helm_dir}/config" export HELM_CONFIG_HOME="${helm_dir}/config"
HELM_DIFF_VERSION="${HELM_DIFF_VERSION:-3.12.3}" HELM_DIFF_VERSION="${HELM_DIFF_VERSION:-3.12.5}"
HELM_GIT_VERSION="${HELM_GIT_VERSION:-1.3.0}" HELM_GIT_VERSION="${HELM_GIT_VERSION:-1.3.0}"
HELM_SECRETS_VERSION="${HELM_SECRETS_VERSION:-3.15.0}" HELM_SECRETS_VERSION="${HELM_SECRETS_VERSION:-3.15.0}"
export GNUPGHOME="${PWD}/${dir}/.gnupg" export GNUPGHOME="${PWD}/${dir}/.gnupg"
@ -80,6 +80,7 @@ ${kubectl} create namespace ${test_ns} || fail "Could not create namespace ${tes
# TEST CASES---------------------------------------------------------------------------------------------------------- # TEST CASES----------------------------------------------------------------------------------------------------------
. ${dir}/test-cases/fetch-forl-local-chart.sh
. ${dir}/test-cases/suppress-output-line-regex.sh . ${dir}/test-cases/suppress-output-line-regex.sh
. ${dir}/test-cases/chartify-jsonPatches-and-strategicMergePatches.sh . ${dir}/test-cases/chartify-jsonPatches-and-strategicMergePatches.sh
. ${dir}/test-cases/include-template-func.sh . ${dir}/test-cases/include-template-func.sh

View File

@ -0,0 +1,15 @@
fetch_forl_local_chart_input_dir="${cases_dir}/fetch-forl-local-chart/input"
fetch_forl_local_chart_tmp=$(mktemp -d)
case_title="fetch for local chart"
test_start "$case_title"
info "Comparing fetch-forl-local-chart diff log #$i"
${helmfile} -f ${fetch_forl_local_chart_input_dir}/helmfile.yaml.gotmpl fetch --output-dir ${fetch_forl_local_chart_tmp} || fail "\"helmfile fetch\" shouldn't fail"
cat ${fetch_forl_local_chart_tmp}/helmfile-tests/local-chart/raw/latest/Chart.yaml || fail "Chart.yaml should exist in the fetched local chart directory"
cat ${fetch_forl_local_chart_tmp}/helmfile-tests/local-chart/raw/latest/templates/resources.yaml || fail "templates/resources.yaml should exist in the fetched local chart directory"
echo code=$?
test_pass "$case_title"

View File

@ -0,0 +1,4 @@
releases:
- name: local-chart
chart: ../../../charts/raw
namespace: local-chart