mirror of
https://github.com/helmfile/helmfile.git
synced 2026-10-22 14:32:27 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
954673d742 | ||
|
|
942b9a64c0 | ||
|
|
b176408eb2 | ||
|
|
ff81b2f279 | ||
|
|
8db03f979c | ||
|
|
988c218096 | ||
|
|
028bcc51dc | ||
|
|
5dd65e8d9a | ||
|
|
134d5beba2 | ||
|
|
87d04f0202 | ||
|
|
2d7c8e7525 | ||
|
|
0fad9f0544 | ||
|
|
832dcf47a5 | ||
|
|
1d2c8b8a3a | ||
|
|
0482ba3b6f | ||
|
|
9d2c0d4285 | ||
|
|
efd26f288a | ||
|
|
5f1698d0ed | ||
|
|
2ecf5780f3 | ||
|
|
3e6542e341 | ||
|
|
d3daea3137 | ||
|
|
19d79427a1 | ||
|
|
cd0ecc5263 | ||
|
|
dc6c59dc14 | ||
|
|
0fc0869671 | ||
|
|
5ca7ce15bc | ||
|
|
6b4b76e2bc | ||
|
|
7b11ce851a | ||
|
|
c575587e1f | ||
|
|
f2dfa7ce64 | ||
|
|
41cd1fe4a6 | ||
|
|
9e54af5c2c | ||
|
|
8a43e8f782 | ||
|
|
94e01b79e0 | ||
|
|
85e77982bf | ||
|
|
b42e847154 | ||
|
|
14e2b9ed93 | ||
|
|
90a41222e6 | ||
|
|
a9aa7af572 | ||
|
|
738409ee0e | ||
|
|
ec87a39015 | ||
|
|
ab1c118022 | ||
|
|
c8c444debc | ||
|
|
9a03d79a84 |
@@ -95,7 +95,7 @@ jobs:
|
||||
- run:
|
||||
name: Install helm
|
||||
environment:
|
||||
HELM_VERSION: v2.16.1
|
||||
HELM_VERSION: v2.16.12
|
||||
command: |
|
||||
HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
curl -Lo ${HELM_FILENAME} "https://kubernetes-helm.storage.googleapis.com/${HELM_FILENAME}"
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
- run:
|
||||
name: Install helm
|
||||
environment:
|
||||
HELM_VERSION: v3.0.0
|
||||
HELM_VERSION: v3.3.3
|
||||
command: |
|
||||
HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
curl -Lo ${HELM_FILENAME} "https://get.helm.sh/${HELM_FILENAME}"
|
||||
|
||||
+2
-2
@@ -11,10 +11,10 @@ FROM alpine:3.11
|
||||
|
||||
RUN apk add --no-cache ca-certificates git bash curl jq
|
||||
|
||||
ARG HELM_VERSION=v2.16.1
|
||||
ARG HELM_VERSION=v2.16.12
|
||||
ARG HELM_LOCATION="https://kubernetes-helm.storage.googleapis.com"
|
||||
ARG HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
ARG HELM_SHA256="7eebaaa2da4734242bbcdced62cc32ba8c7164a18792c8acdf16c77abffce202"
|
||||
ARG HELM_SHA256="756ab375314329b66b452c0f9d569f74b0760141670217c07b79890ad314c214"
|
||||
RUN wget ${HELM_LOCATION}/${HELM_FILENAME} && \
|
||||
echo Verifying ${HELM_FILENAME}... && \
|
||||
sha256sum ${HELM_FILENAME} | grep -q "${HELM_SHA256}" && \
|
||||
|
||||
+3
-3
@@ -11,10 +11,10 @@ FROM alpine:3.11
|
||||
|
||||
RUN apk add --no-cache ca-certificates git bash curl jq
|
||||
|
||||
ARG HELM_VERSION=v3.2.1
|
||||
ARG HELM_VERSION=v3.3.3
|
||||
ARG HELM_LOCATION="https://get.helm.sh"
|
||||
ARG HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
ARG HELM_SHA256="018f9908cb950701a5d59e757653a790c66d8eda288625dbb185354ca6f41f6b"
|
||||
ARG HELM_SHA256="246d58b6b353e63ae8627415a7340089015e3eb542ff7b5ce124b0b1409369cc"
|
||||
RUN wget ${HELM_LOCATION}/${HELM_FILENAME} && \
|
||||
echo Verifying ${HELM_FILENAME}... && \
|
||||
sha256sum ${HELM_FILENAME} | grep -q "${HELM_SHA256}" && \
|
||||
@@ -32,7 +32,7 @@ RUN curl --retry 3 --retry-connrefused -LO "https://storage.googleapis.com/kuber
|
||||
chmod +x kubectl && \
|
||||
mv kubectl /usr/local/bin/kubectl
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.0.0-rc.7 && \
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.1.3 && \
|
||||
helm plugin install https://github.com/futuresimple/helm-secrets && \
|
||||
helm plugin install https://github.com/hypnoglow/helm-s3.git && \
|
||||
helm plugin install https://github.com/aslafy-z/helm-git.git
|
||||
|
||||
@@ -103,6 +103,9 @@ helmDefaults:
|
||||
# when using helm 3.2+, automatically create release namespaces if they do not exist (default true)
|
||||
createNamespace: true
|
||||
|
||||
# these labels will be applied to all releases in a Helmfile. Useful in templating if you have a helmfile per environment or customer and don't want to copy the same label to each release
|
||||
commonLabels:
|
||||
hello: world
|
||||
|
||||
# The desired states of Helm releases.
|
||||
#
|
||||
@@ -298,6 +301,20 @@ apiVersions:
|
||||
|
||||
Helmfile uses [Go templates](https://godoc.org/text/template) for templating your helmfile.yaml. While go ships several built-in functions, we have added all of the functions in the [Sprig library](https://godoc.org/github.com/Masterminds/sprig).
|
||||
|
||||
We also added the following functions:
|
||||
|
||||
- `requiredEnv`
|
||||
- `exec`
|
||||
- `readFile`
|
||||
- `toYaml`
|
||||
- `fromYaml`
|
||||
- `setValueAtPath`
|
||||
- `get` (Sprig's original `get` is available as `sprigGet`)
|
||||
- `tpl`
|
||||
- `required`
|
||||
- `fetchSecretValue`
|
||||
- `expandSecretRefs`
|
||||
|
||||
We also added one special template function: `requiredEnv`.
|
||||
The `requiredEnv` function allows you to declare a particular environment variable as required for template rendering.
|
||||
If the environment variable is unset or empty, the template rendering will fail with an error message.
|
||||
@@ -420,6 +437,9 @@ GLOBAL OPTIONS:
|
||||
--interactive, -i Request confirmation before attempting to modify clusters
|
||||
--help, -h show help
|
||||
--version, -v print the version
|
||||
|
||||
Environment variables:
|
||||
HELMFILE_ENVIRONMENT specify the environment name, the command line option '-e' have precedence
|
||||
```
|
||||
|
||||
### sync
|
||||
@@ -519,6 +539,41 @@ The `selector` parameter can be specified multiple times. Each parameter is reso
|
||||
|
||||
In addition to user supplied labels, the name, the namespace, and the chart are available to be used as selectors. The chart will just be the chart name excluding the repository (Example `stable/filebeat` would be selected using `--selector chart=filebeat`).
|
||||
|
||||
`commonLabels` can be used when you want to apply the same label to all releases and use [templating](##Templates) based on that.
|
||||
For instance, you install a number of charts on every customer but need to provide different values file per customer.
|
||||
|
||||
templates/common.yaml:
|
||||
|
||||
```
|
||||
templates:
|
||||
nginx: &nginx
|
||||
name: nginx
|
||||
chart: stable/nginx-ingress
|
||||
values:
|
||||
- ../values/common/{{ .Release.Name }}.yaml
|
||||
- ../values/{{ .Release.Labels.customer }}/{{ .Release.Name }}.yaml
|
||||
|
||||
cert-manager: &cert-manager
|
||||
name: cert-manager
|
||||
chart: jetstack/cert-manager
|
||||
values:
|
||||
- ../values/common/{{ .Release.Name }}.yaml
|
||||
- ../values/{{ .Release.Labels.customer }}/{{ .Release.Name }}.yaml
|
||||
```
|
||||
|
||||
helmfile.yaml:
|
||||
|
||||
```
|
||||
{{ readFile "templates/common.yaml" }}
|
||||
|
||||
commonLabels:
|
||||
customer: company
|
||||
|
||||
releases:
|
||||
- <<: *nginx
|
||||
- <<: *cert-manager
|
||||
```
|
||||
|
||||
## Templates
|
||||
|
||||
You can use go's text/template expressions in `helmfile.yaml` and `values.yaml.gotmpl` (templated helm values files). `values.yaml` references will be used verbatim. In other words:
|
||||
@@ -1023,7 +1078,7 @@ hooks:
|
||||
- events: ["prepare", "cleanup"]
|
||||
showlogs: true
|
||||
command: "echo"
|
||||
args: ["{{`{{.Environment.Name}}`}}", "{{`{{.Release.Name}}`}}", "{{`{{.HelmfileCommand}}`}}\
|
||||
args: ["{{`{{.Environment.Name}}`}}", "{{`{{.HelmfileCommand}}`}}\
|
||||
"]
|
||||
```
|
||||
|
||||
@@ -1166,9 +1221,29 @@ For 1, you run `helmfile apply` on CI to deploy ArgoCD and the infrastructure co
|
||||
|
||||
> helmfile config for this phase often reside within the same directory as your Terraform project. So connecting the two with [terraform-provider-helmfile](https://github.com/mumoshu/terraform-provider-helmfile) may be helpful
|
||||
|
||||
For 2, another app-centric CI or bot should run `helmfile template --output-dir-template gitops//{{.Release.Name}} && cd gitops && git add . && git commit && git push` to render/commit manifests,
|
||||
For 2, another app-centric CI or bot should render/commit manifests by running:
|
||||
|
||||
```
|
||||
helmfile template --output-dir-template $(pwd)/gitops//{{.Release.Name}}
|
||||
cd gitops
|
||||
git add .
|
||||
git commit -m 'some message'
|
||||
git push origin $BRANCH
|
||||
```
|
||||
|
||||
> Note that `$(pwd)` is necessary when `hemlfile.yaml` has one or more sub-helmfiles in nested directories,
|
||||
> because setting a relative file path in `--output-dir` or `--output-dir-template` results in each sub-helmfile render
|
||||
> to the directory relative to the specified path.
|
||||
|
||||
so that they can be deployed by Argo CD as usual.
|
||||
|
||||
|
||||
The CI or bot can optionally submit a PR to be review by human, running:
|
||||
|
||||
```
|
||||
hub pull-request -b main -l gitops -m 'some description'
|
||||
```
|
||||
|
||||
Recommendations:
|
||||
|
||||
- Do create ArgoCD `Application` custom resource per Helm/Helmfile release, each point to respective sub-directory generated by `helmfile template --output-dir-template`
|
||||
|
||||
@@ -11,8 +11,10 @@ information to this file.
|
||||
|---|---|---|---|---|
|
||||
| [reddit.com](https://www.reddit.com/) | production | [GitHub issue](https://github.com/roboll/helmfile/issues/96#issue-312012249), [Talk](https://www.slideshare.net/GregoryTaylor11/helm-at-reddit-from-local-dev-staging-to-production) | San Francisco, CA | April 2018 |
|
||||
| [ricardo.ch](https://www.ricardo.ch/) | production | We're deploying our complete application platform using Helmfile. | Zug, Switzerland | April 2018 |
|
||||
| [Hellofresh](https://engineering.hellofresh.com/) | production | We've been using helmfile in production since April 2018 for deploying all our infrastructure applications | Berlin, Germany | April 2018 |
|
||||
| [Cherre](https://cherre.com/) | production | We have no public posts about using Helmfile, but we have been using it for a long time now | New York, NY | October 2018 |
|
||||
| [Sight Machine](https://sightmachine.com/) | production | We don't have anything publicly posted about it, but have been using it for quite a while in production. | San Francisco, CA and Ann Arbor, MI | December 2018 |
|
||||
| [TailorMed](https://tailormed.co/) | production | We're deploying all of our platform and all K8s resources using Helmfile. The power of Helmfile are more then amazing. Thank you! | Tel-Aviv, Israel | September 2020 |
|
||||
| [VSHN – The DevOps Company](https://vshn.ch) | production | | Zurich, Switzerland | March 2019 |
|
||||
| [Vlocity](https://vlocity.com/) | proof-of-concept | | Melbourne, Australia | March 2019 |
|
||||
| [transit](https://transit.app/) | production | [Blog post](https://medium.com/@naseem_60378/helmfile-its-like-a-helm-for-your-helm-74a908581599). | Montreal, Canada | March 2019 |
|
||||
|
||||
@@ -4,9 +4,8 @@ go 1.14
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go v35.0.0+incompatible // indirect
|
||||
github.com/Masterminds/goutils v1.1.0 // indirect
|
||||
github.com/Masterminds/semver v1.4.2
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||
github.com/Masterminds/sprig/v3 v3.1.0
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a
|
||||
github.com/go-test/deep v1.0.3
|
||||
github.com/golang/mock v1.4.3
|
||||
@@ -16,23 +15,21 @@ require (
|
||||
github.com/hashicorp/go-retryablehttp v0.6.3 // indirect
|
||||
github.com/hashicorp/go-version v1.2.0
|
||||
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c // indirect
|
||||
github.com/huandu/xstrings v1.2.0 // indirect
|
||||
github.com/imdario/mergo v0.3.9
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
github.com/pierrec/lz4 v2.3.0+incompatible // indirect
|
||||
github.com/r3labs/diff v0.0.0-20190801153147-a71de73c46ad
|
||||
github.com/spf13/cobra v0.0.3
|
||||
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
|
||||
github.com/urfave/cli v1.20.0
|
||||
github.com/variantdev/chartify v0.3.11
|
||||
github.com/variantdev/chartify v0.4.2
|
||||
github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363
|
||||
github.com/variantdev/vals v0.10.0
|
||||
github.com/variantdev/vals v0.10.2
|
||||
go.uber.org/multierr v1.1.0
|
||||
go.uber.org/zap v1.9.1
|
||||
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf // indirect
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||
gopkg.in/square/go-jose.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
gotest.tools v2.2.0+incompatible
|
||||
gotest.tools/v3 v3.0.3-0.20200410202438-4e4a41b7851a
|
||||
k8s.io/apimachinery v0.0.0-20190409092423-760d1845f48b
|
||||
|
||||
@@ -114,8 +114,12 @@ github.com/Masterminds/semver v1.4.1 h1:CaDA1wAoM3rj9sAFyyZP37LloExUzxFGYt+DqJ87
|
||||
github.com/Masterminds/semver v1.4.1/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
|
||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
|
||||
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TNDYD9Y=
|
||||
github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA=
|
||||
github.com/Microsoft/go-winio v0.4.13 h1:Hmi80lzZuI/CaYmlJp/b+FjZdRZhKu9c2mDVqKlLWVs=
|
||||
github.com/Microsoft/go-winio v0.4.13/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||
@@ -264,6 +268,8 @@ github.com/fujiwara/tfstate-lookup v0.0.4 h1:NGH9y+U4RDO3ooMYjK+fq1iYAkrBfCNZbZw
|
||||
github.com/fujiwara/tfstate-lookup v0.0.4/go.mod h1:kluvnw25uJ8bszZldMoewWxylt7QFvuzTGN+Rr911qs=
|
||||
github.com/fujiwara/tfstate-lookup v0.0.9 h1:U/iAQusJBJ5EsxdU48rZGaUSMiZmK8cTCkyCJpiFnMw=
|
||||
github.com/fujiwara/tfstate-lookup v0.0.9/go.mod h1:08o5Rm5pKzvIxoZe3D0NIT8AHYBqTzyUU03BZ/c0IKA=
|
||||
github.com/fujiwara/tfstate-lookup v0.0.12 h1:Ax8Xws/BvXRO2NH13QqGZJtk6X5aITJCSBdlaWqMLwU=
|
||||
github.com/fujiwara/tfstate-lookup v0.0.12/go.mod h1:08o5Rm5pKzvIxoZe3D0NIT8AHYBqTzyUU03BZ/c0IKA=
|
||||
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU=
|
||||
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
|
||||
github.com/gammazero/deque v0.0.0-20190130191400-2afb3858e9c7 h1:D2LrfOPgGHQprIxmsTpxtzhpmF66HoM6rXSmcqaX7h8=
|
||||
@@ -358,6 +364,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
|
||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
||||
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
@@ -516,9 +524,15 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0=
|
||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
||||
github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
|
||||
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
|
||||
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4 h1:3K3KcD4S6/Y2hevi70EzUTNKOS3cryQyhUnkjE6Tz0w=
|
||||
github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
|
||||
@@ -761,6 +775,8 @@ github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:s
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
@@ -777,6 +793,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.0 h1:DMOzIV76tmoDNE9pX6RSN0aDtCYeCg5VueieJaAo1uw=
|
||||
github.com/stretchr/testify v1.5.0/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939 h1:BhIUXV2ySTLrKgh/Hnts+QTQlIbWtomXt3LMdzME0A0=
|
||||
@@ -827,6 +844,12 @@ github.com/variantdev/chartify v0.3.10 h1:hi/9z0GPPOKAubemWhJwwsnYJnCKKdIZyo1ouO
|
||||
github.com/variantdev/chartify v0.3.10/go.mod h1:0tw+4doFHsNnhttYx7I9Pv/dsZ82BD4UuTV9saBOcfw=
|
||||
github.com/variantdev/chartify v0.3.11 h1:+jknp9CQ4kvgVMifuwxuu8/5UAVajuNrPBLWnTCTNpA=
|
||||
github.com/variantdev/chartify v0.3.11/go.mod h1:0tw+4doFHsNnhttYx7I9Pv/dsZ82BD4UuTV9saBOcfw=
|
||||
github.com/variantdev/chartify v0.4.0 h1:Ze+gfykTqFuQgfH2w6/ZqMwsrGrRU8yIsW8M+e7jAHg=
|
||||
github.com/variantdev/chartify v0.4.0/go.mod h1:Fr4oPNJ4b19knlovn0wBoU2+MZxQdBacmBs30wwgAFk=
|
||||
github.com/variantdev/chartify v0.4.1 h1:0a8Ipf7n65mbQaAAfI9JEbdDauMnwKs0Gdq/yu5jI7I=
|
||||
github.com/variantdev/chartify v0.4.1/go.mod h1:Fr4oPNJ4b19knlovn0wBoU2+MZxQdBacmBs30wwgAFk=
|
||||
github.com/variantdev/chartify v0.4.2 h1:Vw647JysxJ4kz+6vOxOmrctRoTTf5qmpleB6hTQEjUk=
|
||||
github.com/variantdev/chartify v0.4.2/go.mod h1:Fr4oPNJ4b19knlovn0wBoU2+MZxQdBacmBs30wwgAFk=
|
||||
github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363 h1:KrfQBEUn+wEOQ/6UIfoqRDvn+Q/wZridQ7t0G1vQqKE=
|
||||
github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363/go.mod h1:pH1TQsNSLj2uxMo9NNl9zdGy01Wtn+/2MT96BrKmVyE=
|
||||
github.com/variantdev/vals v0.4.0 h1:O1O7/sWhlvozcY2DjZBzlE1notxwVo6UBT1+w7HsO/k=
|
||||
@@ -841,6 +864,10 @@ github.com/variantdev/vals v0.9.3 h1:l9AT8UgvgU1GBADg5ZPssDVkZTkRr9SaVjvinVjQebk
|
||||
github.com/variantdev/vals v0.9.3/go.mod h1:tAQZUHZAYfgNJJ3BzmUDXeN+r6RLk0HQPSQ+gdYSP5I=
|
||||
github.com/variantdev/vals v0.10.0 h1:2diYujperePiJGQbQx5TEjWzhM0J7WM5+8UZnPXiaOc=
|
||||
github.com/variantdev/vals v0.10.0/go.mod h1:tAQZUHZAYfgNJJ3BzmUDXeN+r6RLk0HQPSQ+gdYSP5I=
|
||||
github.com/variantdev/vals v0.10.1 h1:yJHcElbczPP4adoR8FQloyV9kvnO5zdDzziRBq71C3Q=
|
||||
github.com/variantdev/vals v0.10.1/go.mod h1:tAQZUHZAYfgNJJ3BzmUDXeN+r6RLk0HQPSQ+gdYSP5I=
|
||||
github.com/variantdev/vals v0.10.2 h1:2ZtEV1BcED2WkfL8pZd0kO4HGpow6faIuFSz+c8zXoM=
|
||||
github.com/variantdev/vals v0.10.2/go.mod h1:0KbR+woANJnnZakBreV1s8CBsmAY0O2mZNQBqilsU00=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
|
||||
@@ -883,6 +910,8 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf h1:fnPsqIDRbCSgumaMCRpoIoF2s4qxv0xSSS0BVZUE/ss=
|
||||
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8=
|
||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -1077,6 +1106,8 @@ google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.21.0 h1:zS+Q/CJJnVlXpXQVIz+lH0ZT2lBuT2ac7XD8Y/3w6hY=
|
||||
google.golang.org/api v0.26.0 h1:VJZ8h6E8ip82FRpQl848c5vAadxlTXrUh8RzQzSRm08=
|
||||
google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w=
|
||||
google.golang.org/api v0.31.0 h1:1w5Sz/puhxFo9lTtip2n47k7toB/U2nCqOKNHd3Yrbo=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@@ -1167,6 +1198,8 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22 h1:0efs3hwEZhFKsCoP8l6dDB1AZWMgnEl3yWXWRZTOaEA=
|
||||
gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d h1:LCPbGQ34PMrwad11aMZ+dbz5SAsq/0ySjRwQ8I9Qwd8=
|
||||
|
||||
@@ -257,6 +257,36 @@ func main() {
|
||||
return run.Template(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "write-values",
|
||||
Usage: "write values files for releases. Similar to `helmfile template`, write values files instead of manifests.",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringSliceFlag{
|
||||
Name: "set",
|
||||
Usage: "additional values to be merged into the command",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "values",
|
||||
Usage: "additional value files to be merged into the command",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "output-file-template",
|
||||
Usage: "go text template for generating the output file. Default: {{ .State.BaseName }}-{{ .State.AbsPathSHA1 }}/{{ .Release.Name}}.yaml",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent downloads of release charts",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: "skip running `helm repo update` and `helm dependency build`",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.WriteValues(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "lint",
|
||||
Usage: "lint charts from state file (helm lint)",
|
||||
@@ -467,7 +497,12 @@ func main() {
|
||||
{
|
||||
Name: "build",
|
||||
Usage: "output compiled helmfile state(s) as YAML",
|
||||
Flags: []cli.Flag{},
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "embed-values",
|
||||
Usage: "Read all the values files for every release and embed into the output helmfile.yaml",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.PrintState(c)
|
||||
}),
|
||||
@@ -569,6 +604,10 @@ func (c configImpl) OutputDirTemplate() string {
|
||||
return c.c.String("output-dir-template")
|
||||
}
|
||||
|
||||
func (c configImpl) OutputFileTemplate() string {
|
||||
return c.c.String("output-file-template")
|
||||
}
|
||||
|
||||
func (c configImpl) Validate() bool {
|
||||
return c.c.Bool("validate")
|
||||
}
|
||||
@@ -674,6 +713,10 @@ func (c configImpl) Context() int {
|
||||
return c.c.Int("context")
|
||||
}
|
||||
|
||||
func (c configImpl) EmbedValues() bool {
|
||||
return c.c.Bool("embed-values")
|
||||
}
|
||||
|
||||
func (c configImpl) Logger() *zap.SugaredLogger {
|
||||
return c.c.App.Metadata["logger"].(*zap.SugaredLogger)
|
||||
}
|
||||
@@ -681,7 +724,10 @@ func (c configImpl) Logger() *zap.SugaredLogger {
|
||||
func (c configImpl) Env() string {
|
||||
env := c.c.GlobalString("environment")
|
||||
if env == "" {
|
||||
env = state.DefaultEnv
|
||||
env = os.Getenv("HELMFILE_ENVIRONMENT")
|
||||
if env == "" {
|
||||
env = state.DefaultEnv
|
||||
}
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
+158
-18
@@ -103,7 +103,10 @@ func Init(app *App) *App {
|
||||
|
||||
func (a *App) Deps(c DepsConfigProvider) error {
|
||||
return a.ForEachState(func(run *Run) (_ bool, errs []error) {
|
||||
prepErr := run.withPreparedCharts(false, c.SkipRepos(), "deps", func() {
|
||||
prepErr := run.withPreparedCharts("deps", state.ChartPrepareOptions{
|
||||
SkipRepos: c.SkipRepos(),
|
||||
SkipResolve: true,
|
||||
}, func() {
|
||||
errs = run.Deps(c)
|
||||
})
|
||||
|
||||
@@ -119,7 +122,7 @@ func (a *App) Repos(c ReposConfigProvider) error {
|
||||
return a.ForEachState(func(run *Run) (_ bool, errs []error) {
|
||||
var reposErr error
|
||||
|
||||
err := run.withPreparedCharts(false, true, "repos", func() {
|
||||
err := run.withPreparedCharts("repos", state.ChartPrepareOptions{SkipRepos: true}, func() {
|
||||
reposErr = run.Repos(c)
|
||||
})
|
||||
|
||||
@@ -137,7 +140,7 @@ func (a *App) Repos(c ReposConfigProvider) error {
|
||||
|
||||
func (a *App) DeprecatedSyncCharts(c DeprecatedChartsConfigProvider) error {
|
||||
return a.ForEachState(func(run *Run) (_ bool, errs []error) {
|
||||
err := run.withPreparedCharts(false, true, "charts", func() {
|
||||
err := run.withPreparedCharts("charts", state.ChartPrepareOptions{SkipRepos: true}, func() {
|
||||
errs = run.DeprecatedSyncCharts(c)
|
||||
})
|
||||
|
||||
@@ -163,7 +166,7 @@ func (a *App) Diff(c DiffConfigProvider) error {
|
||||
|
||||
var errs []error
|
||||
|
||||
prepErr := run.withPreparedCharts(false, c.SkipDeps(), "diff", func() {
|
||||
prepErr := run.withPreparedCharts("diff", state.ChartPrepareOptions{SkipRepos: c.SkipDeps()}, func() {
|
||||
msg, matched, affected, errs = a.diff(run, c)
|
||||
})
|
||||
|
||||
@@ -218,7 +221,10 @@ func (a *App) Template(c TemplateConfigProvider) error {
|
||||
return a.ForEachState(func(run *Run) (ok bool, errs []error) {
|
||||
// `helm template` in helm v2 does not support local chart.
|
||||
// So, we set forceDownload=true for helm v2 only
|
||||
prepErr := run.withPreparedCharts(!run.helm.IsHelm3(), c.SkipDeps(), "template", func() {
|
||||
prepErr := run.withPreparedCharts("template", state.ChartPrepareOptions{
|
||||
ForceDownload: !run.helm.IsHelm3(),
|
||||
SkipRepos: c.SkipDeps(),
|
||||
}, func() {
|
||||
ok, errs = a.template(run, c)
|
||||
})
|
||||
|
||||
@@ -230,10 +236,32 @@ func (a *App) Template(c TemplateConfigProvider) error {
|
||||
}, SetFilter(true))
|
||||
}
|
||||
|
||||
func (a *App) WriteValues(c WriteValuesConfigProvider) error {
|
||||
return a.ForEachState(func(run *Run) (ok bool, errs []error) {
|
||||
// `helm template` in helm v2 does not support local chart.
|
||||
// So, we set forceDownload=true for helm v2 only
|
||||
prepErr := run.withPreparedCharts("write-values", state.ChartPrepareOptions{
|
||||
ForceDownload: !run.helm.IsHelm3(),
|
||||
SkipRepos: c.SkipDeps(),
|
||||
}, func() {
|
||||
ok, errs = a.writeValues(run, c)
|
||||
})
|
||||
|
||||
if prepErr != nil {
|
||||
errs = append(errs, prepErr)
|
||||
}
|
||||
|
||||
return
|
||||
}, SetFilter(true))
|
||||
}
|
||||
|
||||
func (a *App) Lint(c LintConfigProvider) error {
|
||||
return a.ForEachState(func(run *Run) (_ bool, errs []error) {
|
||||
// `helm lint` on helm v2 and v3 does not support remote charts, that we need to set `forceDownload=true` here
|
||||
prepErr := run.withPreparedCharts(true, c.SkipDeps(), "lint", func() {
|
||||
prepErr := run.withPreparedCharts("lint", state.ChartPrepareOptions{
|
||||
ForceDownload: true,
|
||||
SkipRepos: c.SkipDeps(),
|
||||
}, func() {
|
||||
errs = run.Lint(c)
|
||||
})
|
||||
|
||||
@@ -247,7 +275,9 @@ func (a *App) Lint(c LintConfigProvider) error {
|
||||
|
||||
func (a *App) Sync(c SyncConfigProvider) error {
|
||||
return a.ForEachState(func(run *Run) (ok bool, errs []error) {
|
||||
prepErr := run.withPreparedCharts(false, c.SkipDeps(), "sync", func() {
|
||||
prepErr := run.withPreparedCharts("sync", state.ChartPrepareOptions{
|
||||
SkipRepos: c.SkipDeps(),
|
||||
}, func() {
|
||||
ok, errs = a.sync(run, c)
|
||||
})
|
||||
|
||||
@@ -269,7 +299,9 @@ func (a *App) Apply(c ApplyConfigProvider) error {
|
||||
opts = append(opts, SetRetainValuesFiles(c.RetainValuesFiles()))
|
||||
|
||||
err := a.ForEachState(func(run *Run) (ok bool, errs []error) {
|
||||
prepErr := run.withPreparedCharts(false, c.SkipDeps(), "apply", func() {
|
||||
prepErr := run.withPreparedCharts("apply", state.ChartPrepareOptions{
|
||||
SkipRepos: c.SkipDeps(),
|
||||
}, func() {
|
||||
matched, updated, es := a.apply(run, c)
|
||||
|
||||
mut.Lock()
|
||||
@@ -301,7 +333,9 @@ func (a *App) Apply(c ApplyConfigProvider) error {
|
||||
|
||||
func (a *App) Status(c StatusesConfigProvider) error {
|
||||
return a.ForEachState(func(run *Run) (_ bool, errs []error) {
|
||||
err := run.withPreparedCharts(false, true, "status", func() {
|
||||
err := run.withPreparedCharts("status", state.ChartPrepareOptions{
|
||||
SkipRepos: true,
|
||||
}, func() {
|
||||
errs = run.Status(c)
|
||||
})
|
||||
|
||||
@@ -315,7 +349,9 @@ func (a *App) Status(c StatusesConfigProvider) error {
|
||||
|
||||
func (a *App) Delete(c DeleteConfigProvider) error {
|
||||
return a.ForEachState(func(run *Run) (ok bool, errs []error) {
|
||||
err := run.withPreparedCharts(false, true, "delete", func() {
|
||||
err := run.withPreparedCharts("delete", state.ChartPrepareOptions{
|
||||
SkipRepos: true,
|
||||
}, func() {
|
||||
ok, errs = a.delete(run, c.Purge(), c)
|
||||
})
|
||||
|
||||
@@ -329,7 +365,9 @@ func (a *App) Delete(c DeleteConfigProvider) error {
|
||||
|
||||
func (a *App) Destroy(c DestroyConfigProvider) error {
|
||||
return a.ForEachState(func(run *Run) (ok bool, errs []error) {
|
||||
err := run.withPreparedCharts(false, true, "destroy", func() {
|
||||
err := run.withPreparedCharts("destroy", state.ChartPrepareOptions{
|
||||
SkipRepos: true,
|
||||
}, func() {
|
||||
ok, errs = a.delete(run, true, c)
|
||||
})
|
||||
|
||||
@@ -349,8 +387,10 @@ func (a *App) Test(c TestConfigProvider) error {
|
||||
"or set helm.sh/hook-delete-policy\n")
|
||||
}
|
||||
|
||||
err := run.withPreparedCharts(false, true, "test", func() {
|
||||
errs = run.Test(c)
|
||||
err := run.withPreparedCharts("test", state.ChartPrepareOptions{
|
||||
SkipRepos: true,
|
||||
}, func() {
|
||||
errs = a.test(run, c)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -363,13 +403,38 @@ func (a *App) Test(c TestConfigProvider) error {
|
||||
|
||||
func (a *App) PrintState(c StateConfigProvider) error {
|
||||
return a.ForEachState(func(run *Run) (_ bool, errs []error) {
|
||||
err := run.withPreparedCharts(false, true, "build", func() {
|
||||
state, err := run.state.ToYaml()
|
||||
err := run.withPreparedCharts("build", state.ChartPrepareOptions{
|
||||
SkipRepos: true,
|
||||
}, func() {
|
||||
if c.EmbedValues() {
|
||||
for i := range run.state.Releases {
|
||||
r := run.state.Releases[i]
|
||||
|
||||
values, err := run.state.LoadYAMLForEmbedding(&r, r.Values, r.MissingFileHandler, r.ValuesPathPrefix)
|
||||
if err != nil {
|
||||
errs = []error{err}
|
||||
return
|
||||
}
|
||||
|
||||
run.state.Releases[i].Values = values
|
||||
|
||||
secrets, err := run.state.LoadYAMLForEmbedding(&r, r.Secrets, r.MissingFileHandler, r.ValuesPathPrefix)
|
||||
if err != nil {
|
||||
errs = []error{err}
|
||||
return
|
||||
}
|
||||
|
||||
run.state.Releases[i].Secrets = secrets
|
||||
}
|
||||
}
|
||||
|
||||
stateYaml, err := run.state.ToYaml()
|
||||
if err != nil {
|
||||
errs = []error{err}
|
||||
return
|
||||
}
|
||||
fmt.Printf("---\n# Source: %s\n\n%+v", run.state.FilePath, state)
|
||||
|
||||
fmt.Printf("---\n# Source: %s\n\n%+v", run.state.FilePath, stateYaml)
|
||||
|
||||
errs = []error{}
|
||||
})
|
||||
@@ -386,15 +451,32 @@ func (a *App) ListReleases(c ListConfigProvider) error {
|
||||
var releases []*HelmRelease
|
||||
|
||||
err := a.ForEachState(func(run *Run) (_ bool, errs []error) {
|
||||
err := run.withPreparedCharts(false, true, "list", func() {
|
||||
err := run.withPreparedCharts("list", state.ChartPrepareOptions{
|
||||
SkipRepos: true,
|
||||
}, func() {
|
||||
|
||||
//var releases m
|
||||
for _, r := range run.state.Releases {
|
||||
labels := ""
|
||||
for k, v := range r.Labels {
|
||||
if r.Labels == nil {
|
||||
r.Labels = map[string]string{}
|
||||
}
|
||||
for k, v := range run.state.CommonLabels {
|
||||
r.Labels[k] = v
|
||||
}
|
||||
|
||||
var keys []string
|
||||
for k, _ := range r.Labels {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
v := r.Labels[k]
|
||||
labels = fmt.Sprintf("%s,%s:%s", labels, k, v)
|
||||
}
|
||||
labels = strings.Trim(labels, ",")
|
||||
|
||||
installed := r.Installed == nil || *r.Installed
|
||||
releases = append(releases, &HelmRelease{
|
||||
Name: r.Name,
|
||||
@@ -1355,6 +1437,64 @@ func (a *App) template(r *Run, c TemplateConfigProvider) (bool, []error) {
|
||||
return true, errs
|
||||
}
|
||||
|
||||
func (a *App) writeValues(r *Run, c WriteValuesConfigProvider) (bool, []error) {
|
||||
st := r.state
|
||||
helm := r.helm
|
||||
|
||||
allReleases := st.GetReleasesWithOverrides()
|
||||
|
||||
toRender, err := a.getSelectedReleases(r)
|
||||
if err != nil {
|
||||
return false, []error{err}
|
||||
}
|
||||
if len(toRender) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Do build deps and prepare only on selected releases so that we won't waste time
|
||||
// on running various helm commands on unnecessary releases
|
||||
st.Releases = toRender
|
||||
|
||||
releasesToWrite := map[string]state.ReleaseSpec{}
|
||||
for _, r := range toRender {
|
||||
id := state.ReleaseToID(&r)
|
||||
if r.Installed != nil && !*r.Installed {
|
||||
continue
|
||||
}
|
||||
releasesToWrite[id] = r
|
||||
}
|
||||
|
||||
var errs []error
|
||||
|
||||
// Traverse DAG of all the releases so that we don't suffer from false-positive missing dependencies
|
||||
st.Releases = allReleases
|
||||
|
||||
if len(releasesToWrite) > 0 {
|
||||
_, writeErrs := withDAG(st, helm, a.Logger, false, a.Wrap(func(subst *state.HelmState, helm helmexec.Interface) []error {
|
||||
var rs []state.ReleaseSpec
|
||||
|
||||
for _, r := range subst.Releases {
|
||||
if r2, ok := releasesToWrite[state.ReleaseToID(&r)]; ok {
|
||||
rs = append(rs, r2)
|
||||
}
|
||||
}
|
||||
|
||||
subst.Releases = rs
|
||||
|
||||
opts := &state.WriteValuesOpts{
|
||||
Set: c.Set(),
|
||||
OutputFileTemplate: c.OutputFileTemplate(),
|
||||
}
|
||||
return subst.WriteReleasesValues(helm, c.Values(), opts)
|
||||
}))
|
||||
|
||||
if writeErrs != nil && len(writeErrs) > 0 {
|
||||
errs = append(errs, writeErrs...)
|
||||
}
|
||||
}
|
||||
return true, errs
|
||||
}
|
||||
|
||||
func fileExistsAt(path string) bool {
|
||||
fileInfo, err := os.Stat(path)
|
||||
return err == nil && fileInfo.Mode().IsRegular()
|
||||
|
||||
+18
-8
@@ -4,8 +4,6 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/roboll/helmfile/pkg/remote"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
@@ -17,6 +15,9 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/roboll/helmfile/pkg/remote"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/exectest"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
@@ -746,6 +747,8 @@ func runFilterSubHelmFilesTests(testcases []struct {
|
||||
expectErr bool
|
||||
errMsg string
|
||||
}, files map[string]string, t *testing.T, testName string) {
|
||||
t.Helper()
|
||||
|
||||
for _, testcase := range testcases {
|
||||
actual := []string{}
|
||||
|
||||
@@ -2271,6 +2274,10 @@ func (c configImpl) Concurrency() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (c configImpl) EmbedValues() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (c configImpl) Output() string {
|
||||
return c.output
|
||||
}
|
||||
@@ -2455,7 +2462,7 @@ func (helm *mockHelmExec) GetVersion() helmexec.Version {
|
||||
return helmexec.Version{}
|
||||
}
|
||||
|
||||
func (helm *mockHelmExec) IsVersionAtLeast(major int, minor int) bool {
|
||||
func (helm *mockHelmExec) IsVersionAtLeast(major int, minor int, patch int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -2490,6 +2497,7 @@ releases:
|
||||
|
||||
var wantRepos = []mockRepo{
|
||||
{Name: "stable"},
|
||||
{Name: "stable2"},
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
@@ -4050,6 +4058,8 @@ releases:
|
||||
func TestList(t *testing.T) {
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.d/first.yaml": `
|
||||
commonLabels:
|
||||
common: label
|
||||
releases:
|
||||
- name: myrelease1
|
||||
chart: mychart1
|
||||
@@ -4093,11 +4103,11 @@ releases:
|
||||
assert.NilError(t, err)
|
||||
})
|
||||
|
||||
expected := `NAME NAMESPACE ENABLED LABELS
|
||||
myrelease1 false id:myrelease1
|
||||
myrelease2 true
|
||||
myrelease3 true
|
||||
myrelease4 true id:myrelease1
|
||||
expected := `NAME NAMESPACE ENABLED LABELS
|
||||
myrelease1 false common:label,id:myrelease1
|
||||
myrelease2 true common:label
|
||||
myrelease3 true
|
||||
myrelease4 true id:myrelease1
|
||||
`
|
||||
assert.Equal(t, expected, out)
|
||||
}
|
||||
|
||||
@@ -137,6 +137,13 @@ type TemplateConfigProvider interface {
|
||||
concurrencyConfig
|
||||
}
|
||||
|
||||
type WriteValuesConfigProvider interface {
|
||||
Values() []string
|
||||
Set() []string
|
||||
OutputFileTemplate() string
|
||||
SkipDeps() bool
|
||||
}
|
||||
|
||||
type StatusesConfigProvider interface {
|
||||
Args() string
|
||||
|
||||
@@ -144,6 +151,7 @@ type StatusesConfigProvider interface {
|
||||
}
|
||||
|
||||
type StateConfigProvider interface {
|
||||
EmbedValues() bool
|
||||
}
|
||||
|
||||
type concurrencyConfig interface {
|
||||
|
||||
@@ -4,16 +4,14 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/remote"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/roboll/helmfile/pkg/environment"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/remote"
|
||||
"github.com/roboll/helmfile/pkg/state"
|
||||
"github.com/variantdev/vals"
|
||||
"go.uber.org/zap"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -68,11 +66,7 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e
|
||||
}
|
||||
|
||||
if opts.Reverse {
|
||||
rev := func(i, j int) bool {
|
||||
return j < i
|
||||
}
|
||||
sort.Slice(st.Releases, rev)
|
||||
sort.Slice(st.Helmfiles, rev)
|
||||
st.Reverse()
|
||||
}
|
||||
|
||||
if ld.overrideKubeContext != "" {
|
||||
@@ -222,7 +216,7 @@ func (ld *desiredStateLoader) renderAndLoad(env, overrodeEnv *environment.Enviro
|
||||
if finalState == nil {
|
||||
finalState = currentState
|
||||
} else {
|
||||
if err := mergo.Merge(finalState, currentState, mergo.WithOverride); err != nil {
|
||||
if err := mergo.Merge(&finalState.ReleaseSetSpec, ¤tState.ReleaseSetSpec, mergo.WithOverride); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,8 +156,8 @@ anotherbackend 4 Fri Nov 1 08:40:07 2019 DEPLOYED anotherbackend-3.1.0
|
||||
{Name: "backend-v2", Flags: []string{}},
|
||||
{Name: "backend-v1", Flags: []string{}},
|
||||
{Name: "anotherbackend", Flags: []string{}},
|
||||
{Name: "database", Flags: []string{}},
|
||||
{Name: "servicemesh", Flags: []string{}},
|
||||
{Name: "database", Flags: []string{}},
|
||||
{Name: "front-proxy", Flags: []string{}},
|
||||
{Name: "logging", Flags: []string{}},
|
||||
},
|
||||
@@ -291,7 +291,7 @@ GROUP RELEASES
|
||||
1 frontend-v3, frontend-v2, frontend-v1
|
||||
2 backend-v2, backend-v1
|
||||
3 anotherbackend
|
||||
4 database, servicemesh
|
||||
4 servicemesh, database
|
||||
5 front-proxy, logging
|
||||
|
||||
processing releases in group 1/5: frontend-v3, frontend-v2, frontend-v1
|
||||
@@ -303,9 +303,9 @@ release "backend-v2" processed
|
||||
release "backend-v1" processed
|
||||
processing releases in group 3/5: anotherbackend
|
||||
release "anotherbackend" processed
|
||||
processing releases in group 4/5: database, servicemesh
|
||||
release "database" processed
|
||||
processing releases in group 4/5: servicemesh, database
|
||||
release "servicemesh" processed
|
||||
release "database" processed
|
||||
processing releases in group 5/5: front-proxy, logging
|
||||
release "front-proxy" processed
|
||||
release "logging" processed
|
||||
@@ -318,8 +318,8 @@ frontend-v1
|
||||
backend-v2
|
||||
backend-v1
|
||||
anotherbackend
|
||||
database
|
||||
servicemesh
|
||||
database
|
||||
front-proxy
|
||||
logging
|
||||
`,
|
||||
|
||||
@@ -97,7 +97,7 @@ func (helm *noCallHelmExec) GetVersion() helmexec.Version {
|
||||
return helmexec.Version{}
|
||||
}
|
||||
|
||||
func (helm *noCallHelmExec) IsVersionAtLeast(major int, minor int) bool {
|
||||
func (helm *noCallHelmExec) IsVersionAtLeast(major int, minor int, patch int) bool {
|
||||
helm.doPanic()
|
||||
return false
|
||||
}
|
||||
|
||||
+20
-5
@@ -36,12 +36,12 @@ func (r *Run) askForConfirmation(msg string) bool {
|
||||
return AskForConfirmation(msg)
|
||||
}
|
||||
|
||||
func (r *Run) withPreparedCharts(forceDownload, skipRepos bool, helmfileCommand string, f func()) error {
|
||||
func (r *Run) withPreparedCharts(helmfileCommand string, opts state.ChartPrepareOptions, f func()) error {
|
||||
if r.ReleaseToChart != nil {
|
||||
panic("Run.PrepareCharts can be called only once")
|
||||
}
|
||||
|
||||
if !skipRepos {
|
||||
if !opts.SkipRepos {
|
||||
ctx := r.ctx
|
||||
if err := ctx.SyncReposOnce(r.state, r.helm); err != nil {
|
||||
return err
|
||||
@@ -59,7 +59,7 @@ func (r *Run) withPreparedCharts(forceDownload, skipRepos bool, helmfileCommand
|
||||
return err
|
||||
}
|
||||
|
||||
releaseToChart, errs := r.state.PrepareCharts(r.helm, dir, 2, helmfileCommand, forceDownload, skipRepos)
|
||||
releaseToChart, errs := r.state.PrepareCharts(r.helm, dir, 2, helmfileCommand, opts)
|
||||
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("%v", errs)
|
||||
@@ -161,14 +161,29 @@ func (a *App) diff(r *Run, c DiffConfigProvider) (*string, bool, bool, []error)
|
||||
return infoMsg, true, len(deleted) > 0 || len(updated) > 0, errs
|
||||
}
|
||||
|
||||
func (r *Run) Test(c TestConfigProvider) []error {
|
||||
func (a *App) test(r *Run, c TestConfigProvider) []error {
|
||||
cleanup := c.Cleanup()
|
||||
timeout := c.Timeout()
|
||||
concurrency := c.Concurrency()
|
||||
|
||||
st := r.state
|
||||
|
||||
toTest, err := a.getSelectedReleases(r)
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
|
||||
if len(toTest) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do test only on selected releases, because that's what the user intended
|
||||
// with conditions and selectors
|
||||
st.Releases = toTest
|
||||
|
||||
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||
|
||||
return r.state.TestReleases(r.helm, cleanup, timeout, concurrency)
|
||||
return st.TestReleases(r.helm, cleanup, timeout, concurrency)
|
||||
}
|
||||
|
||||
func (r *Run) Lint(c LintConfigProvider) []error {
|
||||
|
||||
@@ -10,7 +10,11 @@ func TestGetArgs(t *testing.T) {
|
||||
args := "--timeout=3600 --set app1.bootstrap=true --set app2.bootstrap=false --tiller-namespace ns"
|
||||
defaultArgs := []string{"--recreate-pods", "--force"}
|
||||
Helmdefaults := state.HelmSpec{KubeContext: "test", TillerNamespace: "test-namespace", Args: defaultArgs}
|
||||
testState := &state.HelmState{HelmDefaults: Helmdefaults}
|
||||
testState := &state.HelmState{
|
||||
ReleaseSetSpec: state.ReleaseSetSpec{
|
||||
HelmDefaults: Helmdefaults,
|
||||
},
|
||||
}
|
||||
receivedArgs := GetArgs(args, testState)
|
||||
|
||||
expectedOutput := "--timeout=3600 --set app1.bootstrap=true --set app2.bootstrap=false --tiller-namespace ns --recreate-pods --force"
|
||||
@@ -24,7 +28,11 @@ func Test2(t *testing.T) {
|
||||
args := "--timeout=3600 --set app1.bootstrap=true --set app2.bootstrap=false,app3.bootstrap=true --tiller-namespace ns"
|
||||
defaultArgs := []string{"--recreate-pods", "--force"}
|
||||
Helmdefaults := state.HelmSpec{KubeContext: "test", TillerNamespace: "test-namespace", Args: defaultArgs}
|
||||
testState := &state.HelmState{HelmDefaults: Helmdefaults}
|
||||
testState := &state.HelmState{
|
||||
ReleaseSetSpec: state.ReleaseSetSpec{
|
||||
HelmDefaults: Helmdefaults,
|
||||
},
|
||||
}
|
||||
receivedArgs := GetArgs(args, testState)
|
||||
|
||||
expectedOutput := "--timeout=3600 --set app1.bootstrap=true --set app2.bootstrap=false,app3.bootstrap=true --tiller-namespace ns --recreate-pods --force"
|
||||
|
||||
+2
-1
@@ -41,7 +41,8 @@ type Bus struct {
|
||||
func (bus *Bus) Trigger(evt string, evtErr error, context map[string]interface{}) (bool, error) {
|
||||
if bus.Runner == nil {
|
||||
bus.Runner = helmexec.ShellRunner{
|
||||
Dir: bus.BasePath,
|
||||
Dir: bus.BasePath,
|
||||
Logger: bus.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -170,12 +170,12 @@ func (helm *Helm) GetVersion() helmexec.Version {
|
||||
return helmexec.Version{}
|
||||
}
|
||||
|
||||
func (helm *Helm) IsVersionAtLeast(major int, minor int) bool {
|
||||
func (helm *Helm) IsVersionAtLeast(major int, minor int, patch int) bool {
|
||||
if helm.Version == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return helm.Version.Major >= major && minor >= helm.Version.Minor
|
||||
return helm.Version.Major >= major && minor >= helm.Version.Minor && patch >= helm.Version.Patch
|
||||
}
|
||||
|
||||
func (helm *Helm) sync(m *sync.Mutex, f func()) {
|
||||
|
||||
+37
-30
@@ -48,40 +48,28 @@ func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger {
|
||||
return zap.New(core).Sugar()
|
||||
}
|
||||
|
||||
func getHelmVersion(helmBinary string, logger *zap.SugaredLogger, runner Runner) Version {
|
||||
// versionRegex matches versions like v1.1.1 and v1.1
|
||||
var versionRegex = regexp.MustCompile("v(?P<major>\\d+)\\.(?P<minor>\\d+)(?:\\.(?P<patch>\\d+))?")
|
||||
|
||||
// Autodetect from `helm verison`
|
||||
bytes, err := runner.Execute(helmBinary, []string{"version", "--client", "--short"}, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
func parseHelmVersion(versionStr string) (Version, error) {
|
||||
if len(versionStr) == 0 {
|
||||
return Version{}, nil
|
||||
}
|
||||
|
||||
if bytes == nil || len(bytes) == 0 {
|
||||
return Version{}
|
||||
matches := versionRegex.FindStringSubmatch(versionStr)
|
||||
if len(matches) == 0 {
|
||||
return Version{}, fmt.Errorf("error parsing helm verion '%s'", versionStr)
|
||||
}
|
||||
|
||||
re := regexp.MustCompile("v(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)")
|
||||
matches := re.FindStringSubmatch(string(bytes))
|
||||
|
||||
result := make(map[string]string)
|
||||
for i, name := range re.SubexpNames() {
|
||||
for i, name := range versionRegex.SubexpNames() {
|
||||
result[name] = matches[i]
|
||||
}
|
||||
|
||||
major, err := strconv.Atoi(result["major"])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
minor, err := strconv.Atoi(result["minor"])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
patch, err := strconv.Atoi(result["patch"])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// We ignore errors because regex matches only integers
|
||||
// If any of the parts does not exist - default "0" will be used
|
||||
major, _ := strconv.Atoi(result["major"])
|
||||
minor, _ := strconv.Atoi(result["minor"])
|
||||
patch, _ := strconv.Atoi(result["patch"])
|
||||
|
||||
// Support explicit helm3 opt-in via environment variable
|
||||
if os.Getenv("HELMFILE_HELM3") != "" && major < 3 {
|
||||
@@ -89,21 +77,37 @@ func getHelmVersion(helmBinary string, logger *zap.SugaredLogger, runner Runner)
|
||||
Major: 3,
|
||||
Minor: 0,
|
||||
Patch: 0,
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
return Version{
|
||||
Major: major,
|
||||
Minor: minor,
|
||||
Patch: patch,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getHelmVersion(helmBinary string, runner Runner) (Version, error) {
|
||||
|
||||
// Autodetect from `helm verison`
|
||||
bytes, err := runner.Execute(helmBinary, []string{"version", "--client", "--short"}, nil)
|
||||
if err != nil {
|
||||
return Version{}, fmt.Errorf("error determining helm version: %w", err)
|
||||
}
|
||||
|
||||
return parseHelmVersion(string(bytes))
|
||||
}
|
||||
|
||||
// New for running helm commands
|
||||
func New(helmBinary string, logger *zap.SugaredLogger, kubeContext string, runner Runner) *execer {
|
||||
// TODO: proper error handling
|
||||
version, err := getHelmVersion(helmBinary, runner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &execer{
|
||||
helmBinary: helmBinary,
|
||||
version: getHelmVersion(helmBinary, logger, runner),
|
||||
version: version,
|
||||
logger: logger,
|
||||
kubeContext: kubeContext,
|
||||
runner: runner,
|
||||
@@ -126,6 +130,9 @@ func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, usernam
|
||||
return fmt.Errorf("empty field name")
|
||||
}
|
||||
args = append(args, "repo", "add", name, repository)
|
||||
if helm.IsHelm3() && helm.IsVersionAtLeast(3, 3, 2) {
|
||||
args = append(args, "--force-update")
|
||||
}
|
||||
if certfile != "" && keyfile != "" {
|
||||
args = append(args, "--cert-file", certfile, "--key-file", keyfile)
|
||||
}
|
||||
@@ -394,6 +401,6 @@ func (helm *execer) GetVersion() Version {
|
||||
return helm.version
|
||||
}
|
||||
|
||||
func (helm *execer) IsVersionAtLeast(major int, minor int) bool {
|
||||
return helm.version.Major >= major && helm.version.Minor >= minor
|
||||
func (helm *execer) IsVersionAtLeast(major int, minor int, patch int) bool {
|
||||
return helm.version.Major >= major && helm.version.Minor >= minor && helm.version.Patch >= patch
|
||||
}
|
||||
|
||||
@@ -557,16 +557,63 @@ func Test_GetVersion(t *testing.T) {
|
||||
func Test_IsVersionAtLeast(t *testing.T) {
|
||||
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")}
|
||||
helm := New("helm", NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
|
||||
if !helm.IsVersionAtLeast(2, 1) {
|
||||
if !helm.IsVersionAtLeast(2, 1, 0) {
|
||||
t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1")
|
||||
}
|
||||
|
||||
if helm.IsVersionAtLeast(2, 19) {
|
||||
if helm.IsVersionAtLeast(2, 19, 0) {
|
||||
t.Error("helmexec.IsVersionAtLeast - 2.16.1 is atleast 2.19")
|
||||
}
|
||||
|
||||
if helm.IsVersionAtLeast(3, 2) {
|
||||
if helm.IsVersionAtLeast(3, 2, 0) {
|
||||
t.Error("helmexec.IsVersionAtLeast - 2.16.1 is atleast 3.2")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_parseHelmVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
ver string
|
||||
want Version
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
ver: "v1.2.3",
|
||||
want: Version{
|
||||
Major: 1,
|
||||
Minor: 2,
|
||||
Patch: 3,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
ver: "v1.2",
|
||||
want: Version{
|
||||
Major: 1,
|
||||
Minor: 2,
|
||||
Patch: 0,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
ver: "v1",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
ver: "1.1.1",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.ver, func(t *testing.T) {
|
||||
got, err := parseHelmVersion(tt.ver)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("parseHelmVersion() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("parseHelmVersion() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ type Interface interface {
|
||||
DecryptSecret(context HelmContext, name string, flags ...string) (string, error)
|
||||
IsHelm3() bool
|
||||
GetVersion() Version
|
||||
IsVersionAtLeast(major int, minor int) bool
|
||||
IsVersionAtLeast(major int, minor int, patch int) bool
|
||||
}
|
||||
|
||||
type DependencyUpdater interface {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package helmexec
|
||||
|
||||
import "math/rand"
|
||||
|
||||
var executionIDComponents = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
|
||||
func newExecutionID() string {
|
||||
b := make([]rune, 5)
|
||||
for i := range b {
|
||||
b[i] = executionIDComponents[rand.Intn(len(executionIDComponents))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package helmexec
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type logWriterGenerator struct {
|
||||
log *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func (g logWriterGenerator) Writer(prefix string) *logWriter {
|
||||
return &logWriter{
|
||||
log: g.log,
|
||||
prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
type logWriter struct {
|
||||
log *zap.SugaredLogger
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (w *logWriter) Write(p []byte) (int, error) {
|
||||
w.log.Debugf("%s%s", w.prefix, strings.TrimSpace(string(p)))
|
||||
return len(p), nil
|
||||
}
|
||||
+30
-6
@@ -4,12 +4,14 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -34,21 +36,34 @@ func (shell ShellRunner) Execute(cmd string, args []string, env map[string]strin
|
||||
preparedCmd := exec.Command(cmd, args...)
|
||||
preparedCmd.Dir = shell.Dir
|
||||
preparedCmd.Env = mergeEnv(os.Environ(), env)
|
||||
return Output(preparedCmd)
|
||||
return Output(preparedCmd, &logWriterGenerator{
|
||||
log: shell.Logger,
|
||||
})
|
||||
}
|
||||
|
||||
func Output(c *exec.Cmd) ([]byte, error) {
|
||||
func Output(c *exec.Cmd, logWriterGenerators ...*logWriterGenerator) ([]byte, error) {
|
||||
if c.Stdout != nil {
|
||||
return nil, errors.New("exec: Stdout already set")
|
||||
}
|
||||
if c.Stderr != nil {
|
||||
return nil, errors.New("exec: Stderr already set")
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
var combined bytes.Buffer
|
||||
c.Stdout = io.MultiWriter(&stdout, &combined)
|
||||
c.Stderr = io.MultiWriter(&stderr, &combined)
|
||||
|
||||
var logWriters []io.Writer
|
||||
|
||||
id := newExecutionID()
|
||||
for _, g := range logWriterGenerators {
|
||||
logPrefix := fmt.Sprintf("%s:%s> ", filepath.Base(c.Path), id)
|
||||
logWriters = append(logWriters, g.Writer(logPrefix))
|
||||
}
|
||||
|
||||
c.Stdout = io.MultiWriter(append([]io.Writer{&stdout, &combined}, logWriters...)...)
|
||||
c.Stderr = io.MultiWriter(append([]io.Writer{&stderr, &combined}, logWriters...)...)
|
||||
|
||||
err := c.Run()
|
||||
|
||||
if err != nil {
|
||||
@@ -92,7 +107,16 @@ func env2map(env []string) map[string]string {
|
||||
wanted := map[string]string{}
|
||||
for _, cur := range env {
|
||||
pair := strings.SplitN(cur, "=", 2)
|
||||
wanted[pair[0]] = pair[1]
|
||||
|
||||
var v string
|
||||
|
||||
// An environment can completely miss `=` and the right side.
|
||||
// If we didn't deal with that, this may fail due to an index-out-of-range error
|
||||
if len(pair) > 1 {
|
||||
v = pair[1]
|
||||
}
|
||||
|
||||
wanted[pair[0]] = v
|
||||
}
|
||||
return wanted
|
||||
}
|
||||
|
||||
+102
-3
@@ -100,9 +100,15 @@ bar: {{ readFile "bar.txt" }}
|
||||
}
|
||||
|
||||
valuesFile := "/example/path/to/values.yaml.gotmpl"
|
||||
valuesContent := []byte(`env: {{ .Environment.Name }}`)
|
||||
valuesContent := []byte(`env: {{ .Environment.Name }}
|
||||
releaseName: {{ .Release.Name }}
|
||||
releaseNamespace: {{ .Release.Namespace }}
|
||||
`)
|
||||
|
||||
expectedValues := `env: production`
|
||||
expectedValues := `env: production
|
||||
releaseName: myrelease
|
||||
releaseNamespace: mynamespace
|
||||
`
|
||||
|
||||
testFs := testhelper.NewTestFs(map[string]string{
|
||||
fooYamlFile: string(fooYamlContent),
|
||||
@@ -124,7 +130,98 @@ bar: {{ readFile "bar.txt" }}
|
||||
t.Errorf("unexpected environment values: expected=%v, actual=%v", expected, actual)
|
||||
}
|
||||
|
||||
actualValuesData, err := state.RenderValuesFileToBytes(valuesFile)
|
||||
release := state.Releases[0]
|
||||
|
||||
state.ApplyOverrides(&release)
|
||||
|
||||
actualValuesData, err := state.RenderReleaseValuesFileToBytes(&release, valuesFile)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
actualValues := string(actualValuesData)
|
||||
|
||||
if !reflect.DeepEqual(expectedValues, actualValues) {
|
||||
t.Errorf("unexpected values: expected=%v, actual=%v", expectedValues, actualValues)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFromYaml_OverrideNamespace(t *testing.T) {
|
||||
yamlFile := "/example/path/to/helmfile.yaml"
|
||||
yamlContent := []byte(`environments:
|
||||
production:
|
||||
values:
|
||||
- foo.yaml
|
||||
- bar.yaml.gotmpl
|
||||
|
||||
# A.k.a helmfile apply --namespace myns
|
||||
namespace: myns
|
||||
|
||||
releases:
|
||||
- name: myrelease
|
||||
namespace: mynamespace
|
||||
chart: mychart
|
||||
values:
|
||||
- values.yaml.gotmpl
|
||||
`)
|
||||
|
||||
fooYamlFile := "/example/path/to/foo.yaml"
|
||||
fooYamlContent := []byte(`foo: foo
|
||||
# As this file doesn't have an file extension ".gotmpl", this template expression should not be evaluated
|
||||
baz: "{{ readFile \"baz.txt\" }}"`)
|
||||
|
||||
barYamlFile := "/example/path/to/bar.yaml.gotmpl"
|
||||
barYamlContent := []byte(`foo: FOO
|
||||
bar: {{ readFile "bar.txt" }}
|
||||
`)
|
||||
|
||||
barTextFile := "/example/path/to/bar.txt"
|
||||
barTextContent := []byte("BAR")
|
||||
|
||||
expected := map[string]interface{}{
|
||||
"foo": "FOO",
|
||||
"bar": "BAR",
|
||||
// As the file doesn't have an file extension ".gotmpl", this template expression should not be evaluated
|
||||
"baz": "{{ readFile \"baz.txt\" }}",
|
||||
}
|
||||
|
||||
valuesFile := "/example/path/to/values.yaml.gotmpl"
|
||||
valuesContent := []byte(`env: {{ .Environment.Name }}
|
||||
releaseName: {{ .Release.Name }}
|
||||
releaseNamespace: {{ .Release.Namespace }}
|
||||
overrideNamespace: {{ .Namespace }}
|
||||
`)
|
||||
|
||||
expectedValues := `env: production
|
||||
releaseName: myrelease
|
||||
releaseNamespace: myns
|
||||
overrideNamespace: myns
|
||||
`
|
||||
|
||||
testFs := testhelper.NewTestFs(map[string]string{
|
||||
fooYamlFile: string(fooYamlContent),
|
||||
barYamlFile: string(barYamlContent),
|
||||
barTextFile: string(barTextContent),
|
||||
valuesFile: string(valuesContent),
|
||||
})
|
||||
testFs.Cwd = "/example/path/to"
|
||||
|
||||
r := remote.NewRemote(logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt)
|
||||
state, err := NewCreator(logger, testFs.ReadFile, testFs.FileExists, testFs.Abs, testFs.Glob, nil, nil, "", r).
|
||||
ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
actual := state.Env.Values
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("unexpected environment values: expected=%v, actual=%v", expected, actual)
|
||||
}
|
||||
|
||||
release := state.Releases[0]
|
||||
|
||||
state.ApplyOverrides(&release)
|
||||
|
||||
actualValuesData, err := state.RenderReleaseValuesFileToBytes(&release, valuesFile)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -250,6 +347,8 @@ func TestReadFromYaml_FilterNegatives(t *testing.T) {
|
||||
[]bool{false, true, false}},
|
||||
{LabelFilter{negativeLabels: [][]string{[]string{"stage", "pre"}, []string{"stage", "post"}}},
|
||||
[]bool{false, false, true}},
|
||||
{LabelFilter{negativeLabels: [][]string{[]string{"foo", "bar"}}},
|
||||
[]bool{false, true, true}},
|
||||
}
|
||||
state, err := createFromYaml(yamlContent, yamlFile, DefaultEnv, logger)
|
||||
if err != nil {
|
||||
|
||||
+8
-2
@@ -114,7 +114,7 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
|
||||
|
||||
jsonPatches := release.JSONPatches
|
||||
if len(jsonPatches) > 0 {
|
||||
generatedFiles, err := st.generateTemporaryValuesFiles(jsonPatches, release.MissingFileHandler)
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, jsonPatches, release.MissingFileHandler)
|
||||
if err != nil {
|
||||
return nil, clean, err
|
||||
}
|
||||
@@ -130,7 +130,7 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
|
||||
|
||||
strategicMergePatches := release.StrategicMergePatches
|
||||
if len(strategicMergePatches) > 0 {
|
||||
generatedFiles, err := st.generateTemporaryValuesFiles(strategicMergePatches, release.MissingFileHandler)
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, strategicMergePatches, release.MissingFileHandler)
|
||||
if err != nil {
|
||||
return nil, clean, err
|
||||
}
|
||||
@@ -144,6 +144,12 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
|
||||
shouldRun = true
|
||||
}
|
||||
|
||||
if release.ForceNamespace != "" {
|
||||
chartify.Opts.OverrideNamespace = release.ForceNamespace
|
||||
|
||||
shouldRun = true
|
||||
}
|
||||
|
||||
if shouldRun {
|
||||
generatedFiles, err := st.generateValuesFiles(helm, release, workerIndex)
|
||||
if err != nil {
|
||||
|
||||
@@ -133,12 +133,15 @@ func (r ReleaseSpec) ExecuteTemplateExpressions(renderer *tmpl.FileRenderer) (*R
|
||||
}
|
||||
}
|
||||
|
||||
for i, ts := range result.Secrets {
|
||||
s, err := renderer.RenderTemplateContentToBuffer([]byte(ts))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".secrets[%d] = \"%s\": %v", r.Name, i, ts, err)
|
||||
for i, t := range result.Secrets {
|
||||
switch ts := t.(type) {
|
||||
case string:
|
||||
s, err := renderer.RenderTemplateContentToBuffer([]byte(ts))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed executing template expressions in release \"%s\".secrets[%d] = \"%s\": %v", r.Name, i, ts, err)
|
||||
}
|
||||
result.Secrets[i] = s.String()
|
||||
}
|
||||
result.Secrets[i] = s.String()
|
||||
}
|
||||
|
||||
if len(result.SetValuesTemplate) > 0 {
|
||||
|
||||
@@ -38,7 +38,7 @@ func (l LabelFilter) Match(r ReleaseSpec) bool {
|
||||
k := element[0]
|
||||
v := element[1]
|
||||
if rVal, ok := r.Labels[k]; !ok {
|
||||
return true
|
||||
|
||||
} else if rVal == v {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"gopkg.in/yaml.v2"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSelectReleasesWithOverrides(t *testing.T) {
|
||||
type testcase struct {
|
||||
subject string
|
||||
selector []string
|
||||
want []string
|
||||
}
|
||||
|
||||
testcases := []testcase{
|
||||
{
|
||||
subject: "multiple OR selectors (nillable label first)",
|
||||
selector: []string{"type=bar", "name=nolabel2", "name=nolabel1"},
|
||||
want: []string{"nolabel1", "nolabel2", "foo"},
|
||||
},
|
||||
{
|
||||
subject: "multiple OR selectors (non-nillable label first)",
|
||||
selector: []string{"name=foo", "type!=bar"},
|
||||
want: []string{"nolabel1", "nolabel2", "foo"},
|
||||
},
|
||||
{
|
||||
subject: "multiple AND conditions (nillable label first)",
|
||||
selector: []string{"type!=bar,name!=nolabel2"},
|
||||
want: []string{"nolabel1"},
|
||||
},
|
||||
{
|
||||
subject: "multiple AND conditions (non-nillable label first)",
|
||||
selector: []string{"name!=nolabel2,type!=bar"},
|
||||
want: []string{"nolabel1"},
|
||||
},
|
||||
{
|
||||
subject: "inequality on nillable label",
|
||||
selector: []string{"type!=bar"},
|
||||
want: []string{"nolabel1", "nolabel2"},
|
||||
},
|
||||
{
|
||||
subject: "equality on nillable label",
|
||||
selector: []string{"type=bar"},
|
||||
want: []string{"foo"},
|
||||
},
|
||||
{
|
||||
subject: "inequality on non-nillable label",
|
||||
selector: []string{"name!=nolabel1"},
|
||||
want: []string{"nolabel2", "foo"},
|
||||
},
|
||||
{
|
||||
subject: "equality on non-nillable label",
|
||||
selector: []string{"name=nolabel1"},
|
||||
want: []string{"nolabel1"},
|
||||
},
|
||||
}
|
||||
|
||||
example := []byte(`releases:
|
||||
- name: nolabel1
|
||||
namespace: kube-system
|
||||
chart: stable/nolabel
|
||||
- name: nolabel2
|
||||
namespace: default
|
||||
chart: stable/nolabel
|
||||
- name: foo
|
||||
namespace: kube-system
|
||||
chart: stable/foo
|
||||
labels:
|
||||
type: bar
|
||||
`)
|
||||
|
||||
var state HelmState
|
||||
|
||||
if err := yaml.Unmarshal(example, &state); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
state.Selectors = tc.selector
|
||||
|
||||
rs, err := state.GetSelectedReleasesWithOverrides()
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v", tc.selector, tc.subject, err)
|
||||
}
|
||||
|
||||
var got []string
|
||||
|
||||
for _, r := range rs {
|
||||
got = append(got, r.Name)
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.want, got); d != "" {
|
||||
t.Errorf("%s %s: %s", tc.selector, tc.subject, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
+486
-59
@@ -18,6 +18,9 @@ import (
|
||||
"sync"
|
||||
"text/template"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/variantdev/chartify"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/environment"
|
||||
@@ -39,11 +42,7 @@ const (
|
||||
EmptyTimeout = -1
|
||||
)
|
||||
|
||||
// HelmState structure for the helmfile
|
||||
type HelmState struct {
|
||||
basePath string
|
||||
FilePath string
|
||||
|
||||
type ReleaseSetSpec struct {
|
||||
DefaultHelmBinary string `yaml:"helmBinary,omitempty"`
|
||||
|
||||
// DefaultValues is the default values to be overrode by environment values and command-line overrides
|
||||
@@ -58,6 +57,7 @@ type HelmState struct {
|
||||
DeprecatedReleases []ReleaseSpec `yaml:"charts,omitempty"`
|
||||
OverrideNamespace string `yaml:"namespace,omitempty"`
|
||||
Repositories []RepositorySpec `yaml:"repositories,omitempty"`
|
||||
CommonLabels map[string]string `yaml:"commonLabels,omitempty"`
|
||||
Releases []ReleaseSpec `yaml:"releases,omitempty"`
|
||||
Selectors []string `yaml:"-"`
|
||||
ApiVersions []string `yaml:"apiVersions,omitempty"`
|
||||
@@ -69,6 +69,19 @@ type HelmState struct {
|
||||
|
||||
Env environment.Environment `yaml:"-"`
|
||||
|
||||
// If set to "Error", return an error when a subhelmfile points to a
|
||||
// non-existent path. The default behavior is to print a warning. Note the
|
||||
// differing default compared to other MissingFileHandlers.
|
||||
MissingFileHandler string `yaml:"missingFileHandler"`
|
||||
}
|
||||
|
||||
// HelmState structure for the helmfile
|
||||
type HelmState struct {
|
||||
basePath string
|
||||
FilePath string
|
||||
|
||||
ReleaseSetSpec `yaml:",inline"`
|
||||
|
||||
logger *zap.SugaredLogger
|
||||
|
||||
readFile func(string) ([]byte, error)
|
||||
@@ -80,11 +93,6 @@ type HelmState struct {
|
||||
|
||||
runner helmexec.Runner
|
||||
valsRuntime vals.Evaluator
|
||||
|
||||
// If set to "Error", return an error when a subhelmfile points to a
|
||||
// non-existent path. The default behavior is to print a warning. Note the
|
||||
// differing default compared to other MissingFileHandlers.
|
||||
MissingFileHandler string `yaml:"missingFileHandler"`
|
||||
}
|
||||
|
||||
// SubHelmfileSpec defines the subhelmfile path and options
|
||||
@@ -209,7 +217,7 @@ type ReleaseSpec struct {
|
||||
Namespace string `yaml:"namespace,omitempty"`
|
||||
Labels map[string]string `yaml:"labels,omitempty"`
|
||||
Values []interface{} `yaml:"values,omitempty"`
|
||||
Secrets []string `yaml:"secrets,omitempty"`
|
||||
Secrets []interface{} `yaml:"secrets,omitempty"`
|
||||
SetValues []SetValue `yaml:"set,omitempty"`
|
||||
|
||||
ValuesTemplate []interface{} `yaml:"valuesTemplate,omitempty"`
|
||||
@@ -251,6 +259,13 @@ type ReleaseSpec struct {
|
||||
// Without this, any error in url parsing result in silently falling-back to normal process of treating `chart:` as the regular
|
||||
// helm chart name.
|
||||
ForceGoGetter bool `yaml:"forceGoGetter,omitempty"`
|
||||
|
||||
// ForceNamespace is an experimental feature to set metadata.namespace in every K8s resource rendered by the chart,
|
||||
// regardless of the template, even when it doesn't have `namespace: {{ .Namespace | quote }}`.
|
||||
// This is only needed when you can't FIX your chart to have `namespace: {{ .Namespace }}` AND you're using `helmfile template`.
|
||||
// In standard use-cases, `Namespace` should be sufficient.
|
||||
// Use this only when you know what you want to do!
|
||||
ForceNamespace string `yaml:"forceNamespace"`
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
@@ -330,15 +345,10 @@ func (st *HelmState) getRepositoriesToSync() (map[string]bool, error) {
|
||||
}
|
||||
|
||||
func (st *HelmState) SyncRepos(helm RepoUpdater, shouldSkip map[string]bool) ([]string, error) {
|
||||
shouldUpdate, err := st.getRepositoriesToSync()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var updated []string
|
||||
|
||||
for _, repo := range st.Repositories {
|
||||
if !shouldUpdate[repo.Name] || shouldSkip[repo.Name] {
|
||||
if shouldSkip[repo.Name] {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -524,6 +534,11 @@ func (o *SyncOpts) Apply(opts *SyncOpts) {
|
||||
func ReleaseToID(r *ReleaseSpec) string {
|
||||
var id string
|
||||
|
||||
kc := r.KubeContext
|
||||
if kc != "" {
|
||||
id += kc + "/"
|
||||
}
|
||||
|
||||
tns := r.TillerNamespace
|
||||
if tns != "" {
|
||||
id += tns + "/"
|
||||
@@ -797,6 +812,21 @@ func releasesNeedCharts(releases []ReleaseSpec) []ReleaseSpec {
|
||||
return result
|
||||
}
|
||||
|
||||
type ChartPrepareOptions struct {
|
||||
ForceDownload bool
|
||||
SkipRepos bool
|
||||
SkipResolve bool
|
||||
}
|
||||
|
||||
type chartPrepareResult struct {
|
||||
releaseName string
|
||||
chartName string
|
||||
chartPath string
|
||||
err error
|
||||
buildDeps bool
|
||||
chartFetchedByGoGetter bool
|
||||
}
|
||||
|
||||
// PrepareCharts creates temporary directories of charts.
|
||||
//
|
||||
// Each resulting "chart" can be one of the followings:
|
||||
@@ -811,20 +841,15 @@ func releasesNeedCharts(releases []ReleaseSpec) []ReleaseSpec {
|
||||
// Otheriwse, if a chart is not a helm chart, it will call "chartify" to turn it into a chart.
|
||||
//
|
||||
// If exists, it will also patch resources by json patches, strategic-merge patches, and injectors.
|
||||
func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurrency int, helmfileCommand string, forceDownload, skipDeps bool) (map[string]string, []error) {
|
||||
func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurrency int, helmfileCommand string, opts ChartPrepareOptions) (map[string]string, []error) {
|
||||
releases := releasesNeedCharts(st.Releases)
|
||||
|
||||
temp := make(map[string]string, len(releases))
|
||||
type downloadResults struct {
|
||||
releaseName string
|
||||
chartPath string
|
||||
err error
|
||||
}
|
||||
|
||||
errs := []error{}
|
||||
|
||||
jobQueue := make(chan *ReleaseSpec, len(releases))
|
||||
results := make(chan *downloadResults, len(releases))
|
||||
results := make(chan *chartPrepareResult, len(releases))
|
||||
|
||||
var helm3 bool
|
||||
|
||||
@@ -832,11 +857,15 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
|
||||
helm3 = helm.IsHelm3()
|
||||
}
|
||||
|
||||
updated, err := st.ResolveDeps()
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
if !opts.SkipResolve {
|
||||
updated, err := st.ResolveDeps()
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
*st = *updated
|
||||
}
|
||||
*st = *updated
|
||||
|
||||
var builds []*chartPrepareResult
|
||||
|
||||
st.scatterGather(
|
||||
concurrency,
|
||||
@@ -855,7 +884,7 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
|
||||
// If it wasn't called here, Helmfile can end up an issue like
|
||||
// https://github.com/roboll/helmfile/issues/1328
|
||||
if _, err := st.triggerPrepareEvent(release, helmfileCommand); err != nil {
|
||||
results <- &downloadResults{err: err}
|
||||
results <- &chartPrepareResult{err: err}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -863,17 +892,21 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
|
||||
|
||||
chartPath, err := st.goGetterChart(chartName, release.Directory, release.ForceGoGetter)
|
||||
if err != nil {
|
||||
results <- &downloadResults{err: fmt.Errorf("release %q: %w", release.Name, err)}
|
||||
results <- &chartPrepareResult{err: fmt.Errorf("release %q: %w", release.Name, err)}
|
||||
return
|
||||
}
|
||||
|
||||
chartFetchedByGoGetter := chartPath != chartName
|
||||
|
||||
chartification, clean, err := st.PrepareChartify(helm, release, chartPath, workerIndex)
|
||||
defer clean()
|
||||
if err != nil {
|
||||
results <- &downloadResults{err: err}
|
||||
results <- &chartPrepareResult{err: err}
|
||||
return
|
||||
}
|
||||
|
||||
var buildDeps bool
|
||||
|
||||
if chartification != nil {
|
||||
c := chartify.New(
|
||||
chartify.HelmBin(st.DefaultHelmBinary),
|
||||
@@ -882,28 +915,51 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
|
||||
|
||||
out, err := c.Chartify(release.Name, chartPath, chartify.WithChartifyOpts(chartification.Opts))
|
||||
if err != nil {
|
||||
results <- &downloadResults{err: err}
|
||||
results <- &chartPrepareResult{err: err}
|
||||
return
|
||||
} else {
|
||||
// TODO Chartify
|
||||
chartPath = out
|
||||
}
|
||||
} else if !forceDownload {
|
||||
} else if pathExists(normalizeChart(st.basePath, chartPath)) {
|
||||
// At this point, we are sure that chartPath is a local directory containing either:
|
||||
// - A remote chart fetched by go-getter or
|
||||
// - A local chart
|
||||
//
|
||||
// The chart may have Chart.yaml(and requirements.yaml for Helm 2), and optionally Chart.lock/requirements.lock,
|
||||
// but no `charts/` directory populated at all, or a subet of chart dependencies are missing in the directory.
|
||||
//
|
||||
// In such situation, Helm fails with an error like:
|
||||
// Error: found in Chart.yaml, but missing in charts/ directory: cert-manager, prometheus, postgresql, gitlab-runner, grafana, redis
|
||||
//
|
||||
// (See also https://github.com/roboll/helmfile/issues/1401#issuecomment-670854495)
|
||||
//
|
||||
// To avoid it, we need to call a `helm dep build` command on the chart.
|
||||
// But the command may consistently fail when an outdated Chart.lock exists.
|
||||
//
|
||||
// (I've mentioned about such case in https://github.com/roboll/helmfile/pull/1400.)
|
||||
//
|
||||
// Trying to run `helm dep build` on the chart regardless of if it's from local or remote is
|
||||
// problematic, as usually the user would have no way to fix the remote chart on their own.
|
||||
//
|
||||
// Given that, we always run `helm dep build` on the chart here, but tolerate any error caused by it
|
||||
// for a remote chart, so that the user can notice/fix the issue in a local chart while
|
||||
// a broken remote chart won't completely block their job.
|
||||
chartPath = normalizeChart(st.basePath, chartPath)
|
||||
|
||||
buildDeps = !opts.SkipRepos
|
||||
} else if !opts.ForceDownload {
|
||||
// At this point, we are sure that either:
|
||||
// 1. It is a local chart and we can use it in later process (helm upgrade/template/lint/etc)
|
||||
// without any modification, or
|
||||
// 2. It is a remote chart which can be safely handed over to helm,
|
||||
// because the version of Helm used in this transaction support downloading the chart instead,
|
||||
// and we don't need any modification to the chart
|
||||
} else if pathExists(normalizeChart(st.basePath, chartPath)) {
|
||||
chartPath = normalizeChart(st.basePath, chartPath)
|
||||
|
||||
if !skipDeps {
|
||||
if err := helm.BuildDeps(release.Name, chartPath); err != nil {
|
||||
results <- &downloadResults{err: err}
|
||||
return
|
||||
}
|
||||
}
|
||||
// because the version of Helm used in this transaction (helm v3 or greater) support downloading
|
||||
// the chart instead, AND we don't need any modification to the chart
|
||||
//
|
||||
// Also see HelmState.chartVersionFlags(). For `helmfile template`, it's called before `helm template`
|
||||
// only on helm v3.
|
||||
// For helm 2, we `helm fetch` with the version flags and call `helm template`
|
||||
// WITHOUT the version flags.
|
||||
} else {
|
||||
pathElems := []string{
|
||||
dir,
|
||||
@@ -917,6 +973,10 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
|
||||
pathElems = append(pathElems, release.Namespace)
|
||||
}
|
||||
|
||||
if release.KubeContext != "" {
|
||||
pathElems = append(pathElems, release.KubeContext)
|
||||
}
|
||||
|
||||
chartVersion := "latest"
|
||||
if release.Version != "" {
|
||||
chartVersion = release.Version
|
||||
@@ -931,7 +991,7 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
|
||||
fetchFlags := st.chartVersionFlags(release)
|
||||
fetchFlags = append(fetchFlags, "--untar", "--untardir", chartPath)
|
||||
if err := helm.Fetch(chartName, fetchFlags...); err != nil {
|
||||
results <- &downloadResults{err: err}
|
||||
results <- &chartPrepareResult{err: err}
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -943,7 +1003,13 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
|
||||
}
|
||||
}
|
||||
|
||||
results <- &downloadResults{releaseName: release.Name, chartPath: chartPath}
|
||||
results <- &chartPrepareResult{
|
||||
releaseName: release.Name,
|
||||
chartName: chartName,
|
||||
chartPath: chartPath,
|
||||
buildDeps: buildDeps,
|
||||
chartFetchedByGoGetter: chartFetchedByGoGetter,
|
||||
}
|
||||
}
|
||||
},
|
||||
func() {
|
||||
@@ -956,6 +1022,10 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
|
||||
return
|
||||
}
|
||||
temp[downloadRes.releaseName] = downloadRes.chartPath
|
||||
|
||||
if downloadRes.buildDeps {
|
||||
builds = append(builds, downloadRes)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -963,9 +1033,104 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
|
||||
if len(errs) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
if len(builds) > 0 {
|
||||
if err := st.runHelmDepBuilds(helm, concurrency, builds); err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
}
|
||||
|
||||
return temp, nil
|
||||
}
|
||||
|
||||
func (st *HelmState) runHelmDepBuilds(helm helmexec.Interface, concurrency int, builds []*chartPrepareResult) error {
|
||||
type buildOutput struct {
|
||||
diagnostic string
|
||||
}
|
||||
|
||||
buildQueue := make(chan *chartPrepareResult)
|
||||
|
||||
buildOutputQueue := make(chan *buildOutput)
|
||||
|
||||
buildWorkers := &errgroup.Group{}
|
||||
for i := 0; i < concurrency; i++ {
|
||||
buildWorkers.Go(func() error {
|
||||
for r := range buildQueue {
|
||||
out, err := func() (*buildOutput, error) {
|
||||
if err := helm.BuildDeps(r.releaseName, r.chartPath); err != nil {
|
||||
if r.chartFetchedByGoGetter {
|
||||
return &buildOutput{
|
||||
diagnostic: fmt.Sprintf(
|
||||
"WARN: `helm dep build` failed. While processing release %q, Helmfile observed that remote chart %q fetched by go-getter is seemingly broken. "+
|
||||
"One of well-known causes of this is that the chart has outdated Chart.lock, which needs the chart maintainer needs to run `helm dep up`. "+
|
||||
"Helmfile is tolerating the error to avoid blocking you until the remote chart gets fixed. "+
|
||||
"But this may result in any failure later if the chart is broken badly. FYI, the tolerated error was: %v",
|
||||
r.releaseName,
|
||||
r.chartName,
|
||||
err.Error(),
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if out != nil {
|
||||
buildOutputQueue <- out
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
diagnosticsAggregator := &sync.WaitGroup{}
|
||||
diagnosticsAggregator.Add(1)
|
||||
go func() {
|
||||
var diagnostics []string
|
||||
for r := range buildOutputQueue {
|
||||
if d := r.diagnostic; d != "" {
|
||||
diagnostics = append(diagnostics, d)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(diagnostics)
|
||||
for _, d := range diagnostics {
|
||||
st.logger.Warn(d)
|
||||
}
|
||||
|
||||
diagnosticsAggregator.Done()
|
||||
}()
|
||||
|
||||
built := map[string]bool{}
|
||||
|
||||
for _, b := range builds {
|
||||
// `helm dep build` fails when it was run concurrency on the same chart.
|
||||
// To avoid that, we run `helm dep build` only once per each local chart.
|
||||
//
|
||||
// See https://github.com/roboll/helmfile/issues/1438
|
||||
if !built[b.chartPath] {
|
||||
built[b.chartPath] = true
|
||||
buildQueue <- b
|
||||
}
|
||||
}
|
||||
close(buildQueue)
|
||||
|
||||
if err := buildWorkers.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
close(buildOutputQueue)
|
||||
|
||||
diagnosticsAggregator.Wait()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type TemplateOpts struct {
|
||||
Set []string
|
||||
OutputDirTemplate string
|
||||
@@ -1066,6 +1231,103 @@ func (st *HelmState) TemplateReleases(helm helmexec.Interface, outputDir string,
|
||||
return nil
|
||||
}
|
||||
|
||||
type WriteValuesOpts struct {
|
||||
Set []string
|
||||
OutputFileTemplate string
|
||||
}
|
||||
|
||||
type WriteValuesOpt interface{ Apply(*WriteValuesOpts) }
|
||||
|
||||
func (o *WriteValuesOpts) Apply(opts *WriteValuesOpts) {
|
||||
*opts = *o
|
||||
}
|
||||
|
||||
// WriteReleasesValues writes values files for releases
|
||||
func (st *HelmState) WriteReleasesValues(helm helmexec.Interface, additionalValues []string, opt ...WriteValuesOpt) []error {
|
||||
opts := &WriteValuesOpts{}
|
||||
for _, o := range opt {
|
||||
o.Apply(opts)
|
||||
}
|
||||
|
||||
for i := range st.Releases {
|
||||
release := &st.Releases[i]
|
||||
|
||||
if !release.Desired() {
|
||||
continue
|
||||
}
|
||||
|
||||
st.ApplyOverrides(release)
|
||||
|
||||
generatedFiles, err := st.generateValuesFiles(helm, release, i)
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
st.removeFiles(generatedFiles)
|
||||
}()
|
||||
|
||||
for _, value := range additionalValues {
|
||||
valfile, err := filepath.Abs(value)
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
|
||||
if _, err := os.Stat(valfile); os.IsNotExist(err) {
|
||||
return []error{err}
|
||||
}
|
||||
generatedFiles = append(generatedFiles, valfile)
|
||||
}
|
||||
|
||||
outputValuesFile, err := st.GenerateOutputFilePath(release, opts.OutputFileTemplate)
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(outputValuesFile), 0755); err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
|
||||
st.logger.Infof("Writing values file %s", outputValuesFile)
|
||||
|
||||
merged := map[string]interface{}{}
|
||||
|
||||
for _, f := range generatedFiles {
|
||||
src := map[string]interface{}{}
|
||||
|
||||
srcBytes, err := st.readFile(f)
|
||||
if err != nil {
|
||||
return []error{fmt.Errorf("reading %s: %w", f, err)}
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(srcBytes, &src); err != nil {
|
||||
return []error{fmt.Errorf("unmarshalling yaml %s: %w", f, err)}
|
||||
}
|
||||
|
||||
if err := mergo.Merge(&merged, &src, mergo.WithOverride, mergo.WithOverwriteWithEmptyValue); err != nil {
|
||||
return []error{fmt.Errorf("merging %s: %w", f, err)}
|
||||
}
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
y := yaml.NewEncoder(&buf)
|
||||
if err := y.Encode(merged); err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(outputValuesFile, buf.Bytes(), 0644); err != nil {
|
||||
return []error{fmt.Errorf("writing values file %s: %w", outputValuesFile, err)}
|
||||
}
|
||||
|
||||
if _, err := st.TriggerCleanupEvent(release, "write-values"); err != nil {
|
||||
st.logger.Warnf("warn: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type LintOpts struct {
|
||||
Set []string
|
||||
}
|
||||
@@ -1487,14 +1749,14 @@ func (st *HelmState) SelectReleasesWithOverrides() ([]Release, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rs, err := markExcludedReleases(st.GetReleasesWithOverrides(), st.Selectors, values)
|
||||
rs, err := markExcludedReleases(st.GetReleasesWithOverrides(), st.Selectors, st.CommonLabels, values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func markExcludedReleases(releases []ReleaseSpec, selectors []string, values map[string]interface{}) ([]Release, error) {
|
||||
func markExcludedReleases(releases []ReleaseSpec, selectors []string, commonLabels map[string]string, values map[string]interface{}) ([]Release, error) {
|
||||
var filteredReleases []Release
|
||||
filters := []ReleaseFilter{}
|
||||
for _, label := range selectors {
|
||||
@@ -1514,6 +1776,10 @@ func markExcludedReleases(releases []ReleaseSpec, selectors []string, values map
|
||||
// Strip off just the last portion for the name stable/newrelic would give newrelic
|
||||
chartSplit := strings.Split(r.Chart, "/")
|
||||
r.Labels["chart"] = chartSplit[len(chartSplit)-1]
|
||||
//Merge CommonLabels into release labels
|
||||
for k, v := range commonLabels {
|
||||
r.Labels[k] = v
|
||||
}
|
||||
var filterMatch bool
|
||||
for _, f := range filters {
|
||||
if r.Labels == nil {
|
||||
@@ -1619,7 +1885,12 @@ func (st *HelmState) triggerReleaseEvent(evt string, evtErr error, r *ReleaseSpe
|
||||
Logger: st.logger,
|
||||
ReadFile: st.readFile,
|
||||
}
|
||||
vals, err := st.Values()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"Values": vals,
|
||||
"Release": r,
|
||||
"HelmfileCommand": helmfileCmd,
|
||||
}
|
||||
@@ -1798,7 +2069,7 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp
|
||||
|
||||
if release.CreateNamespace != nil && *release.CreateNamespace ||
|
||||
release.CreateNamespace == nil && (st.HelmDefaults.CreateNamespace == nil || *st.HelmDefaults.CreateNamespace) {
|
||||
if helm.IsVersionAtLeast(3, 2) {
|
||||
if helm.IsVersionAtLeast(3, 2, 0) {
|
||||
flags = append(flags, "--create-namespace")
|
||||
} else if release.CreateNamespace != nil || st.HelmDefaults.CreateNamespace != nil {
|
||||
// createNamespace was set explicitly, but not running supported version of helm - error
|
||||
@@ -1827,7 +2098,16 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp
|
||||
}
|
||||
|
||||
func (st *HelmState) flagsForTemplate(helm helmexec.Interface, release *ReleaseSpec, workerIndex int) ([]string, []string, error) {
|
||||
flags := st.chartVersionFlags(release)
|
||||
var flags []string
|
||||
|
||||
// `helm template` in helm v2 does not support `--version` flag. So we fetch with the version flag and then template
|
||||
// without the flag. See PrepareCharts function to see the Helmfile implementation of chart fetching.
|
||||
//
|
||||
// `helm template` in helm v3 supports `--version` and it automatically fetches the remote chart to template,
|
||||
// so we skip fetching on helmfile-side and let helm fetch it.
|
||||
if helm.IsHelm3() {
|
||||
flags = st.chartVersionFlags(release)
|
||||
}
|
||||
|
||||
var err error
|
||||
flags, err = st.appendHelmXFlags(flags, release)
|
||||
@@ -1928,8 +2208,14 @@ func (st *HelmState) flagsForLint(helm helmexec.Interface, release *ReleaseSpec,
|
||||
return flags, files, nil
|
||||
}
|
||||
|
||||
func (st *HelmState) RenderValuesFileToBytes(path string) ([]byte, error) {
|
||||
r := tmpl.NewFileRenderer(st.readFile, filepath.Dir(path), st.valuesFileTemplateData())
|
||||
func (st *HelmState) RenderReleaseValuesFileToBytes(release *ReleaseSpec, path string) ([]byte, error) {
|
||||
vals, err := st.Values()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
templateData := st.createReleaseTemplateData(release, vals)
|
||||
|
||||
r := tmpl.NewFileRenderer(st.readFile, filepath.Dir(path), templateData)
|
||||
rawBytes, err := r.RenderToBytes(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -2008,7 +2294,7 @@ func (st *HelmState) removeFiles(files []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (st *HelmState) generateTemporaryValuesFiles(values []interface{}, missingFileHandler *string) ([]string, error) {
|
||||
func (st *HelmState) generateTemporaryReleaseValuesFiles(release *ReleaseSpec, values []interface{}, missingFileHandler *string) ([]string, error) {
|
||||
generatedFiles := []string{}
|
||||
|
||||
for _, value := range values {
|
||||
@@ -2027,7 +2313,7 @@ func (st *HelmState) generateTemporaryValuesFiles(values []interface{}, missingF
|
||||
}
|
||||
path := paths[0]
|
||||
|
||||
yamlBytes, err := st.RenderValuesFileToBytes(path)
|
||||
yamlBytes, err := st.RenderReleaseValuesFileToBytes(release, path)
|
||||
if err != nil {
|
||||
return generatedFiles, fmt.Errorf("failed to render values files \"%s\": %v", typedValue, err)
|
||||
}
|
||||
@@ -2084,7 +2370,7 @@ func (st *HelmState) generateVanillaValuesFiles(release *ReleaseSpec) ([]string,
|
||||
return nil, fmt.Errorf("Failed to render values in %s for release %s: type %T isn't supported", st.FilePath, release.Name, valuesMapSecretsRendered["values"])
|
||||
}
|
||||
|
||||
generatedFiles, err := st.generateTemporaryValuesFiles(valuesSecretsRendered, release.MissingFileHandler)
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, valuesSecretsRendered, release.MissingFileHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2095,11 +2381,41 @@ func (st *HelmState) generateVanillaValuesFiles(release *ReleaseSpec) ([]string,
|
||||
func (st *HelmState) generateSecretValuesFiles(helm helmexec.Interface, release *ReleaseSpec, workerIndex int) ([]string, error) {
|
||||
var generatedFiles []string
|
||||
|
||||
for _, value := range release.Secrets {
|
||||
paths, skip, err := st.storage().resolveFile(release.MissingFileHandler, "secrets", release.ValuesPathPrefix+value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
for _, v := range release.Secrets {
|
||||
var (
|
||||
paths []string
|
||||
skip bool
|
||||
err error
|
||||
)
|
||||
|
||||
switch value := v.(type) {
|
||||
case string:
|
||||
paths, skip, err = st.storage().resolveFile(release.MissingFileHandler, "secrets", release.ValuesPathPrefix+value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
bs, err := yaml.Marshal(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path, err := ioutil.TempFile(os.TempDir(), "helmfile-embdedded-secrets-*.yaml.enc")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = path.Close()
|
||||
defer func() {
|
||||
_ = os.Remove(path.Name())
|
||||
}()
|
||||
|
||||
if err := ioutil.WriteFile(path.Name(), bs, 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
paths = []string{path.Name()}
|
||||
}
|
||||
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
@@ -2372,6 +2688,68 @@ func (st *HelmState) GenerateOutputDir(outputDir string, release *ReleaseSpec, o
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func (st *HelmState) GenerateOutputFilePath(release *ReleaseSpec, outputFileTemplate string) (string, error) {
|
||||
// get absolute path of state file to generate a hash
|
||||
// use this hash to write helm output in a specific directory by state file and release name
|
||||
// ie. in a directory named stateFileName-stateFileHash-releaseName
|
||||
stateAbsPath, err := filepath.Abs(st.FilePath)
|
||||
if err != nil {
|
||||
return stateAbsPath, err
|
||||
}
|
||||
|
||||
hasher := sha1.New()
|
||||
io.WriteString(hasher, stateAbsPath)
|
||||
|
||||
var stateFileExtension = filepath.Ext(st.FilePath)
|
||||
var stateFileName = st.FilePath[0 : len(st.FilePath)-len(stateFileExtension)]
|
||||
|
||||
sha1sum := hex.EncodeToString(hasher.Sum(nil))[:8]
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString(stateFileName)
|
||||
sb.WriteString("-")
|
||||
sb.WriteString(sha1sum)
|
||||
sb.WriteString("-")
|
||||
sb.WriteString(release.Name)
|
||||
|
||||
if outputFileTemplate == "" {
|
||||
outputFileTemplate = filepath.Join("{{ .State.BaseName }}-{{ .State.AbsPathSHA1 }}", "{{ .Release.Name}}.yaml")
|
||||
}
|
||||
|
||||
t, err := template.New("output-file").Parse(outputFileTemplate)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parsing output-file templmate")
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
type state struct {
|
||||
BaseName string
|
||||
Path string
|
||||
AbsPath string
|
||||
AbsPathSHA1 string
|
||||
}
|
||||
|
||||
data := struct {
|
||||
State state
|
||||
Release *ReleaseSpec
|
||||
}{
|
||||
State: state{
|
||||
BaseName: stateFileName,
|
||||
Path: st.FilePath,
|
||||
AbsPath: stateAbsPath,
|
||||
AbsPathSHA1: sha1sum,
|
||||
},
|
||||
Release: release,
|
||||
}
|
||||
|
||||
if err := t.Execute(buf, data); err != nil {
|
||||
return "", fmt.Errorf("executing output-file template: %w", err)
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func (st *HelmState) ToYaml() (string, error) {
|
||||
if result, err := yaml.Marshal(st); err != nil {
|
||||
return "", err
|
||||
@@ -2379,3 +2757,52 @@ func (st *HelmState) ToYaml() (string, error) {
|
||||
return string(result), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (st *HelmState) LoadYAMLForEmbedding(release *ReleaseSpec, entries []interface{}, missingFileHandler *string, pathPrefix string) ([]interface{}, error) {
|
||||
var result []interface{}
|
||||
|
||||
for _, v := range entries {
|
||||
switch t := v.(type) {
|
||||
case string:
|
||||
var values map[string]interface{}
|
||||
|
||||
paths, skip, err := st.storage().resolveFile(missingFileHandler, "values", pathPrefix+t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(paths) > 1 {
|
||||
return nil, fmt.Errorf("glob patterns in release values and secrets is not supported yet. please submit a feature request if necessary")
|
||||
}
|
||||
yamlOrTemplatePath := paths[0]
|
||||
|
||||
yamlBytes, err := st.RenderReleaseValuesFileToBytes(release, yamlOrTemplatePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to render values files \"%s\": %v", t, err)
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(yamlBytes, &values); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, values)
|
||||
default:
|
||||
result = append(result, v)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (st *HelmState) Reverse() {
|
||||
for i, j := 0, len(st.Releases)-1; i < j; i, j = i+1, j-1 {
|
||||
st.Releases[i], st.Releases[j] = st.Releases[j], st.Releases[i]
|
||||
}
|
||||
|
||||
for i, j := 0, len(st.Helmfiles)-1; i < j; i, j = i+1, j-1 {
|
||||
st.Helmfiles[i], st.Helmfiles[j] = st.Helmfiles[j], st.Helmfiles[i]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,19 +12,17 @@ func (st *HelmState) Values() (map[string]interface{}, error) {
|
||||
return st.Env.GetMergedValues()
|
||||
}
|
||||
|
||||
func (st *HelmState) mustLoadVals() map[string]interface{} {
|
||||
vals, err := st.Values()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
func (st *HelmState) valuesFileTemplateData() EnvironmentTemplateData {
|
||||
return EnvironmentTemplateData{
|
||||
func (st *HelmState) createReleaseTemplateData(release *ReleaseSpec, vals map[string]interface{}) releaseTemplateData {
|
||||
return releaseTemplateData{
|
||||
Environment: st.Env,
|
||||
Namespace: st.OverrideNamespace,
|
||||
Values: st.mustLoadVals(),
|
||||
Values: vals,
|
||||
Release: releaseTemplateDataRelease{
|
||||
Name: release.Name,
|
||||
Chart: release.Chart,
|
||||
Namespace: release.Namespace,
|
||||
Labels: release.Labels,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,13 +84,15 @@ func (st *HelmState) ExecuteTemplates() (*HelmState, error) {
|
||||
}
|
||||
|
||||
for i, rt := range st.Releases {
|
||||
if rt.Labels == nil {
|
||||
rt.Labels = map[string]string{}
|
||||
}
|
||||
for k, v := range st.CommonLabels {
|
||||
rt.Labels[k] = v
|
||||
}
|
||||
successFlag := false
|
||||
for it, prev := 0, &rt; it < 6; it++ {
|
||||
tmplData := releaseTemplateData{
|
||||
Environment: st.Env,
|
||||
Release: *prev,
|
||||
Values: vals,
|
||||
}
|
||||
tmplData := st.createReleaseTemplateData(prev, vals)
|
||||
renderer := tmpl.NewFileRenderer(st.readFile, st.basePath, tmplData)
|
||||
r, err := rt.ExecuteTemplateExpressions(renderer)
|
||||
if err != nil {
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestHelmState_executeTemplates(t *testing.T) {
|
||||
Name: "test-app",
|
||||
Namespace: "test-namespace-{{ .Release.Name }}",
|
||||
ValuesTemplate: []interface{}{"config/{{ .Environment.Name }}/{{ .Release.Name }}/values.yaml"},
|
||||
Secrets: []string{"config/{{ .Environment.Name }}/{{ .Release.Name }}/secrets.yaml"},
|
||||
Secrets: []interface{}{"config/{{ .Environment.Name }}/{{ .Release.Name }}/secrets.yaml"},
|
||||
Labels: map[string]string{"id": "{{ .Release.Name }}"},
|
||||
},
|
||||
want: ReleaseSpec{
|
||||
@@ -45,7 +45,7 @@ func TestHelmState_executeTemplates(t *testing.T) {
|
||||
Name: "test-app",
|
||||
Namespace: "test-namespace-test-app",
|
||||
Values: []interface{}{"config/test_env/test-app/values.yaml"},
|
||||
Secrets: []string{"config/test_env/test-app/secrets.yaml"},
|
||||
Secrets: []interface{}{"config/test_env/test-app/secrets.yaml"},
|
||||
Labels: map[string]string{"id": "test-app"},
|
||||
},
|
||||
},
|
||||
@@ -135,14 +135,16 @@ func TestHelmState_executeTemplates(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
basePath: ".",
|
||||
HelmDefaults: HelmSpec{
|
||||
KubeContext: "test_context",
|
||||
},
|
||||
Env: environment.Environment{Name: "test_env"},
|
||||
OverrideNamespace: "test-namespace_",
|
||||
Repositories: nil,
|
||||
Releases: []ReleaseSpec{
|
||||
tt.input,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
HelmDefaults: HelmSpec{
|
||||
KubeContext: "test_context",
|
||||
},
|
||||
Env: environment.Environment{Name: "test_env"},
|
||||
OverrideNamespace: "test-namespace_",
|
||||
Repositories: nil,
|
||||
Releases: []ReleaseSpec{
|
||||
tt.input,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -235,14 +237,16 @@ func TestHelmState_recursiveRefsTemplates(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
basePath: ".",
|
||||
HelmDefaults: HelmSpec{
|
||||
KubeContext: "test_context",
|
||||
},
|
||||
Env: environment.Environment{Name: "test_env"},
|
||||
OverrideNamespace: "test-namespace_",
|
||||
Repositories: nil,
|
||||
Releases: []ReleaseSpec{
|
||||
tt.input,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
HelmDefaults: HelmSpec{
|
||||
KubeContext: "test_context",
|
||||
},
|
||||
Env: environment.Environment{Name: "test_env"},
|
||||
OverrideNamespace: "test-namespace_",
|
||||
Repositories: nil,
|
||||
Releases: []ReleaseSpec{
|
||||
tt.input,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+154
-84
@@ -127,12 +127,14 @@ func TestHelmState_applyDefaultsTo(t *testing.T) {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
basePath: tt.fields.BaseChartPath,
|
||||
DeprecatedContext: tt.fields.Context,
|
||||
DeprecatedReleases: tt.fields.DeprecatedReleases,
|
||||
OverrideNamespace: tt.fields.Namespace,
|
||||
Repositories: tt.fields.Repositories,
|
||||
Releases: tt.fields.Releases,
|
||||
basePath: tt.fields.BaseChartPath,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
DeprecatedContext: tt.fields.Context,
|
||||
DeprecatedReleases: tt.fields.DeprecatedReleases,
|
||||
OverrideNamespace: tt.fields.Namespace,
|
||||
Repositories: tt.fields.Repositories,
|
||||
Releases: tt.fields.Releases,
|
||||
},
|
||||
}
|
||||
if state.ApplyOverrides(&tt.args.spec); !reflect.DeepEqual(tt.args.spec, tt.want) {
|
||||
t.Errorf("HelmState.ApplyOverrides() = %v, want %v", tt.args.spec, tt.want)
|
||||
@@ -695,11 +697,13 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
basePath: "./",
|
||||
DeprecatedContext: "default",
|
||||
Releases: []ReleaseSpec{*tt.release},
|
||||
HelmDefaults: tt.defaults,
|
||||
valsRuntime: valsRuntime,
|
||||
basePath: "./",
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
DeprecatedContext: "default",
|
||||
Releases: []ReleaseSpec{*tt.release},
|
||||
HelmDefaults: tt.defaults,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}
|
||||
helm := &exectest.Helm{
|
||||
Version: tt.version,
|
||||
@@ -771,11 +775,11 @@ func Test_isLocalChart(t *testing.T) {
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "local chart in 3-level deep dir",
|
||||
name: "remote chart in 3-level deep dir (e.g. ChartCenter)",
|
||||
args: args{
|
||||
chart: "foo/bar/baz",
|
||||
chart: "center/bar/baz",
|
||||
},
|
||||
want: true,
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for i := range tests {
|
||||
@@ -814,6 +818,14 @@ func Test_normalizeChart(t *testing.T) {
|
||||
},
|
||||
want: "remote/app",
|
||||
},
|
||||
{
|
||||
name: "chartcenter repo path",
|
||||
args: args{
|
||||
basePath: "/Users/jane/code/deploy/charts",
|
||||
chart: "center/stable/myapp",
|
||||
},
|
||||
want: "center/stable/myapp",
|
||||
},
|
||||
{
|
||||
name: "construct local chart path, parent dir",
|
||||
args: args{
|
||||
@@ -921,7 +933,9 @@ func TestHelmState_SyncRepos(t *testing.T) {
|
||||
}
|
||||
}
|
||||
state := &HelmState{
|
||||
Repositories: tt.repos,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Repositories: tt.repos,
|
||||
},
|
||||
}
|
||||
if _, _ = state.SyncRepos(tt.helm, map[string]bool{}); !reflect.DeepEqual(tt.helm.Repo, tt.want) {
|
||||
t.Errorf("HelmState.SyncRepos() for [%s] = %v, want %v", tt.name, tt.helm.Repo, tt.want)
|
||||
@@ -1032,7 +1046,9 @@ func TestHelmState_SyncReleases(t *testing.T) {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
Releases: tt.releases,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
}
|
||||
@@ -1135,8 +1151,10 @@ func TestHelmState_SyncReleases_MissingValuesFileForUndesiredRelease(t *testing.
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
basePath: ".",
|
||||
Releases: []ReleaseSpec{tt.release},
|
||||
basePath: ".",
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: []ReleaseSpec{tt.release},
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
}
|
||||
@@ -1281,7 +1299,9 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
Releases: tt.releases,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
}
|
||||
@@ -1383,7 +1403,9 @@ func TestGetDeployedVersion(t *testing.T) {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
Releases: []ReleaseSpec{tt.release},
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: []ReleaseSpec{tt.release},
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
}
|
||||
@@ -1510,7 +1532,9 @@ func TestHelmState_DiffReleases(t *testing.T) {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
Releases: tt.releases,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
}
|
||||
@@ -1582,7 +1606,9 @@ func TestHelmState_SyncReleasesCleanup(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
numRemovedFiles := 0
|
||||
state := &HelmState{
|
||||
Releases: tt.releases,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
removeFile: func(f string) error {
|
||||
@@ -1666,7 +1692,9 @@ func TestHelmState_DiffReleasesCleanup(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
numRemovedFiles := 0
|
||||
state := &HelmState{
|
||||
Releases: tt.releases,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
removeFile: func(f string) error {
|
||||
@@ -1728,35 +1756,37 @@ generated: 2019-05-16T15:42:45.50486+09:00
|
||||
state := &HelmState{
|
||||
basePath: "/src",
|
||||
FilePath: "/src/helmfile.yaml",
|
||||
Releases: []ReleaseSpec{
|
||||
{
|
||||
Chart: "./..",
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: []ReleaseSpec{
|
||||
{
|
||||
Chart: "./..",
|
||||
},
|
||||
{
|
||||
Chart: "../examples",
|
||||
},
|
||||
{
|
||||
Chart: "../../helmfile",
|
||||
},
|
||||
{
|
||||
Chart: "published",
|
||||
},
|
||||
{
|
||||
Chart: "published/deeper",
|
||||
},
|
||||
{
|
||||
Chart: "stable/envoy",
|
||||
Version: "1.5.0",
|
||||
},
|
||||
{
|
||||
Chart: "stable/envoy",
|
||||
Version: "1.4.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Chart: "../examples",
|
||||
},
|
||||
{
|
||||
Chart: "../../helmfile",
|
||||
},
|
||||
{
|
||||
Chart: "published",
|
||||
},
|
||||
{
|
||||
Chart: "published/deeper",
|
||||
},
|
||||
{
|
||||
Chart: "stable/envoy",
|
||||
Version: "1.5.0",
|
||||
},
|
||||
{
|
||||
Chart: "stable/envoy",
|
||||
Version: "1.4.0",
|
||||
},
|
||||
},
|
||||
Repositories: []RepositorySpec{
|
||||
{
|
||||
Name: "stable",
|
||||
URL: "https://kubernetes-charts.storage.googleapis.com",
|
||||
Repositories: []RepositorySpec{
|
||||
{
|
||||
Name: "stable",
|
||||
URL: "https://kubernetes-charts.storage.googleapis.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
tempDir: tempDir,
|
||||
@@ -1790,30 +1820,32 @@ func TestHelmState_ResolveDeps_NoLockFile(t *testing.T) {
|
||||
state := &HelmState{
|
||||
basePath: "/src",
|
||||
FilePath: "/src/helmfile.yaml",
|
||||
Releases: []ReleaseSpec{
|
||||
{
|
||||
Chart: "./..",
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: []ReleaseSpec{
|
||||
{
|
||||
Chart: "./..",
|
||||
},
|
||||
{
|
||||
Chart: "../examples",
|
||||
},
|
||||
{
|
||||
Chart: "../../helmfile",
|
||||
},
|
||||
{
|
||||
Chart: "published",
|
||||
},
|
||||
{
|
||||
Chart: "published/deeper",
|
||||
},
|
||||
{
|
||||
Chart: "stable/envoy",
|
||||
},
|
||||
},
|
||||
{
|
||||
Chart: "../examples",
|
||||
},
|
||||
{
|
||||
Chart: "../../helmfile",
|
||||
},
|
||||
{
|
||||
Chart: "published",
|
||||
},
|
||||
{
|
||||
Chart: "published/deeper",
|
||||
},
|
||||
{
|
||||
Chart: "stable/envoy",
|
||||
},
|
||||
},
|
||||
Repositories: []RepositorySpec{
|
||||
{
|
||||
Name: "stable",
|
||||
URL: "https://kubernetes-charts.storage.googleapis.com",
|
||||
Repositories: []RepositorySpec{
|
||||
{
|
||||
Name: "stable",
|
||||
URL: "https://kubernetes-charts.storage.googleapis.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
logger: logger,
|
||||
@@ -1906,8 +1938,10 @@ func TestHelmState_ReleaseStatuses(t *testing.T) {
|
||||
tt := tests[i]
|
||||
f := func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
Releases: tt.releases,
|
||||
logger: logger,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
},
|
||||
logger: logger,
|
||||
fileExists: func(f string) (bool, error) {
|
||||
if f != "foo.yaml" {
|
||||
return false, fmt.Errorf("unexpected file: %s", f)
|
||||
@@ -1991,8 +2025,10 @@ func TestHelmState_TestReleasesNoCleanUp(t *testing.T) {
|
||||
tt := tests[i]
|
||||
f := func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
Releases: tt.releases,
|
||||
logger: logger,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
},
|
||||
logger: logger,
|
||||
}
|
||||
errs := state.TestReleases(tt.helm, tt.cleanup, 1, 1)
|
||||
if (errs != nil) != tt.wantErr {
|
||||
@@ -2042,8 +2078,10 @@ func TestHelmState_NoReleaseMatched(t *testing.T) {
|
||||
tt := tests[i]
|
||||
f := func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
Releases: releases,
|
||||
logger: logger,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: releases,
|
||||
},
|
||||
logger: logger,
|
||||
}
|
||||
state.Selectors = []string{tt.labels}
|
||||
errs := state.FilterReleases()
|
||||
@@ -2208,11 +2246,13 @@ func TestHelmState_Delete(t *testing.T) {
|
||||
release,
|
||||
}
|
||||
state := &HelmState{
|
||||
HelmDefaults: HelmSpec{
|
||||
KubeContext: tt.defKubeContext,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
HelmDefaults: HelmSpec{
|
||||
KubeContext: tt.defKubeContext,
|
||||
},
|
||||
Releases: releases,
|
||||
},
|
||||
Releases: releases,
|
||||
logger: logger,
|
||||
logger: logger,
|
||||
}
|
||||
helm := &exectest.Helm{
|
||||
Lists: map[exectest.ListKey]string{},
|
||||
@@ -2235,3 +2275,33 @@ func TestHelmState_Delete(t *testing.T) {
|
||||
t.Run(tt.name, f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReverse(t *testing.T) {
|
||||
num := 8
|
||||
st := &HelmState{}
|
||||
|
||||
for i := 0; i < num; i++ {
|
||||
name := fmt.Sprintf("%d", i)
|
||||
st.Helmfiles = append(st.Helmfiles, SubHelmfileSpec{
|
||||
Path: name,
|
||||
})
|
||||
st.Releases = append(st.Releases, ReleaseSpec{
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
|
||||
st.Reverse()
|
||||
|
||||
for i := 0; i < num; i++ {
|
||||
j := num - 1 - i
|
||||
want := fmt.Sprintf("%d", j)
|
||||
|
||||
if got := st.Helmfiles[i].Path; got != want {
|
||||
t.Errorf("sub-helmfile at %d has incorrect path: want %q, got %q", i, want, got)
|
||||
}
|
||||
|
||||
if got := st.Releases[i].Name; got != want {
|
||||
t.Errorf("release at %d has incorrect name: want %q, got %q", i, want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-3
@@ -19,12 +19,32 @@ type EnvironmentTemplateData struct {
|
||||
Values map[string]interface{}
|
||||
}
|
||||
|
||||
// releaseTemplateData provides variables accessible while executing golang text/template expressions in releases of a helmfile YAML file
|
||||
// releaseTemplateData provides variables accessible while executing golang text/template expressions in release templates
|
||||
// and release values templates within a Helmfile YAML file.
|
||||
type releaseTemplateData struct {
|
||||
// Environment is accessible as `.Environment` from any template expression executed by the renderer
|
||||
Environment environment.Environment
|
||||
// Release is accessible as `.Release` from any template expression executed by the renderer
|
||||
Release ReleaseSpec
|
||||
// Release is accessible as `.Release` from any template expression executed by the renderer.
|
||||
// It contains a subset of ReleaseSpec that is known to be useful to dynamically render values.
|
||||
Release releaseTemplateDataRelease
|
||||
// Values is accessible as `.Values` and it contains default state values overrode by environment values and override values.
|
||||
Values map[string]interface{}
|
||||
// Namespace is HelmState.OverrideNamespace.
|
||||
// You should better use Release.Namespace as it might work as you'd expect even if OverrideNamespace is not set.
|
||||
// See releaseTemplateDataRelease.Namespace for more information.
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type releaseTemplateDataRelease struct {
|
||||
// Name is basically ReleaseSpec.Name exposed to the template
|
||||
Name string
|
||||
|
||||
// Namespace is HelmState.OverrideNamespace, or if it's empty, ReleaseSpec.Namespace.
|
||||
Namespace string
|
||||
|
||||
// Labels is ReleaseSpec.Labels
|
||||
Labels map[string]string
|
||||
|
||||
// Chart is ReleaseSpec.Chart
|
||||
Chart string
|
||||
}
|
||||
|
||||
+9
-3
@@ -21,7 +21,8 @@ func isLocalChart(chart string) bool {
|
||||
return chart == "" ||
|
||||
chart[0] == '/' ||
|
||||
strings.Index(chart, "/") == -1 ||
|
||||
len(strings.Split(chart, "/")) != 2
|
||||
(len(strings.Split(chart, "/")) != 2 &&
|
||||
len(strings.Split(chart, "/")) != 3)
|
||||
}
|
||||
|
||||
func resolveRemoteChart(repoAndChart string) (string, string, bool) {
|
||||
@@ -29,8 +30,13 @@ func resolveRemoteChart(repoAndChart string) (string, string, bool) {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
parts := strings.Split(repoAndChart, "/")
|
||||
if len(parts) != 2 {
|
||||
uriLike := strings.Index(repoAndChart, "://") > -1
|
||||
if uriLike {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
parts := strings.SplitN(repoAndChart, "/", 2)
|
||||
if len(parts) < 2 {
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
|
||||
+10
-4
@@ -15,13 +15,17 @@ func TestIsLocalChart(t *testing.T) {
|
||||
input: "stable/mysql",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
input: "center/stable/mysql",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
input: "./charts/myapp",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
input: "charts/mysubsystem/myapp",
|
||||
expected: true,
|
||||
input: "center/stable/myapp",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
input: "./charts/mysubsystem/myapp",
|
||||
@@ -77,8 +81,10 @@ func TestResolveRemortChart(t *testing.T) {
|
||||
remote: false,
|
||||
},
|
||||
{
|
||||
input: "charts/mysubsystem/myapp",
|
||||
remote: false,
|
||||
input: "center/stable/myapp",
|
||||
repo: "center",
|
||||
chart: "stable/myapp",
|
||||
remote: true,
|
||||
},
|
||||
{
|
||||
input: "./charts/mysubsystem/myapp",
|
||||
|
||||
@@ -2,15 +2,31 @@ package tmpl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/Masterminds/sprig"
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func (c *Context) newTemplate() *template.Template {
|
||||
aliased := template.FuncMap{}
|
||||
|
||||
aliases := map[string]string{
|
||||
"get": "sprigGet",
|
||||
}
|
||||
|
||||
funcMap := sprig.TxtFuncMap()
|
||||
|
||||
for orig, alias := range aliases {
|
||||
aliased[alias] = funcMap[orig]
|
||||
}
|
||||
|
||||
for name, f := range c.createFuncMap() {
|
||||
funcMap[name] = f
|
||||
}
|
||||
|
||||
for name, f := range aliased {
|
||||
funcMap[name] = f
|
||||
}
|
||||
|
||||
tmpl := template.New("stringTemplate").Funcs(funcMap)
|
||||
if c.preRender {
|
||||
tmpl = tmpl.Option("missingkey=zero")
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package tmpl
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMergeOverwrite(t *testing.T) {
|
||||
ctx := &Context{}
|
||||
buf, err := ctx.RenderTemplateToBuffer(`
|
||||
{{- $v1 := dict "bool" true "int" 2 "str" "v1" "str2" "v1" -}}
|
||||
{{- $v2 := dict "bool" false "int" 0 "str" "v2" "str2" "" -}}
|
||||
{{- $mo1 := mergeOverwrite (dict) $v1 $v2 }}
|
||||
{{- $mo1 -}}
|
||||
`)
|
||||
expected := "map[bool:false int:0 str:v2 str2:]"
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
actual := buf.String()
|
||||
if actual != expected {
|
||||
t.Errorf("unexpected result: expected=%v, actual=%v", expected, actual)
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,8 @@ environments:
|
||||
repositories:
|
||||
- name: stable
|
||||
url: https://kubernetes-charts.storage.googleapis.com/
|
||||
- name: incubator
|
||||
url: https://kubernetes-charts-incubator.storage.googleapis.com
|
||||
- name: center
|
||||
url: https://repo.chartcenter.io
|
||||
|
||||
helmDefaults:
|
||||
kubeContext: minikube
|
||||
@@ -19,9 +19,17 @@ releases:
|
||||
set:
|
||||
- name: ingress.enabled
|
||||
value: false
|
||||
hooks:
|
||||
- events: ["prepare", "cleanup"]
|
||||
showlogs: true
|
||||
command: "echo"
|
||||
args:
|
||||
- "{{`{{.Environment.Name}}`}}"
|
||||
- "{{`{{.Release.Name}}`}}"
|
||||
- "{{`{{.HelmfileCommand}}`}}"
|
||||
|
||||
- name: raw
|
||||
chart: incubator/raw
|
||||
chart: center/incubator/raw
|
||||
values:
|
||||
- mysecret: {{ .Environment.Values.mysecret }}
|
||||
- values.yaml
|
||||
|
||||
Reference in New Issue
Block a user