mirror of
https://github.com/helmfile/helmfile.git
synced 2026-10-22 14:32:27 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c623730374 | ||
|
|
72e71608b1 | ||
|
|
0d4adfedab | ||
|
|
1a109f7400 | ||
|
|
97bab23e0c | ||
|
|
c881544255 | ||
|
|
92ba347d6f | ||
|
|
f502a0a779 | ||
|
|
77241607fa | ||
|
|
f38958d6d7 | ||
|
|
212e6acf23 | ||
|
|
efa404e275 | ||
|
|
794ab9fc66 | ||
|
|
48bd5c0979 | ||
|
|
e39a0b3639 | ||
|
|
58954109fb | ||
|
|
cb7fa6bf63 | ||
|
|
44fb68b7e0 | ||
|
|
ded0f1049a | ||
|
|
08db073958 | ||
|
|
de8644a504 | ||
|
|
f28ad5af23 | ||
|
|
77ee239db2 | ||
|
|
d9e15754b7 | ||
|
|
dabd7ad99f | ||
|
|
204f78c8ff | ||
|
|
59b91a45a0 | ||
|
|
5a3bd7d649 | ||
|
|
ce6a621414 | ||
|
|
20f6c68108 | ||
|
|
3367822e89 | ||
|
|
8702639510 | ||
|
|
41cbc25c35 | ||
|
|
83189dbad3 | ||
|
|
28ade19419 | ||
|
|
378ae4ad72 | ||
|
|
30e0356ad6 | ||
|
|
eabda4cf28 | ||
|
|
5617b593c8 | ||
|
|
5d43b30a7c | ||
|
|
d6db4b53e7 | ||
|
|
368dd13e16 | ||
|
|
ae942c5288 | ||
|
|
a111e89b27 | ||
|
|
589b26aaad | ||
|
|
1d7608f42c | ||
|
|
efad26641d | ||
|
|
81b04fcb8b | ||
|
|
74cd2bc147 | ||
|
|
2ff06a8abd | ||
|
|
d703e17239 | ||
|
|
3bfcc1ea42 | ||
|
|
c65bdff62c | ||
|
|
f614e8b1bf | ||
|
|
85accf7330 | ||
|
|
a161796dc4 | ||
|
|
261367e7e9 | ||
|
|
5cd0afcfa0 | ||
|
|
1748cbfcea | ||
|
|
200cae2a68 | ||
|
|
b1b7831a90 | ||
|
|
53c6d2f988 | ||
|
|
faa74962e6 | ||
|
|
28cf300ef2 | ||
|
|
e9d1c8534c | ||
|
|
aafe74a3e1 | ||
|
|
db9d31ac06 | ||
|
|
4a9d753441 | ||
|
|
4efaa656f9 | ||
|
|
59f4043fc5 | ||
|
|
d04cd1b1a7 | ||
|
|
781c4e9ebe | ||
|
|
0c9934c357 | ||
|
|
2983f5cd28 | ||
|
|
7610d8d3c6 | ||
|
|
5cd853b773 | ||
|
|
f1c07879ad | ||
|
|
d1a2435ce1 | ||
|
|
45b8eb6942 | ||
|
|
17a969ce83 | ||
|
|
4f02196dad | ||
|
|
85f19b6abd | ||
|
|
42138d88bf | ||
|
|
2ef09d45ee | ||
|
|
9b2a770dbe | ||
|
|
6085730a91 | ||
|
|
94a8658635 | ||
|
|
2618cfb38b | ||
|
|
fcf9a7273f | ||
|
|
5ce755e4d8 | ||
|
|
b7d6639b25 | ||
|
|
95759958b5 | ||
|
|
8ed8a9eab3 | ||
|
|
14a83e7cbe | ||
|
|
bf9f36b2bb | ||
|
|
3215eaf710 | ||
|
|
927bd30fa0 | ||
|
|
247ae9108c | ||
|
|
97f0e59cfd | ||
|
|
56405e7445 | ||
|
|
42c97cd919 | ||
|
|
65317e96f6 | ||
|
|
453b498ccb | ||
|
|
19cbea49f7 | ||
|
|
bd3838c059 | ||
|
|
f24b61f100 | ||
|
|
257c1f62d2 | ||
|
|
4e1ecb5890 | ||
|
|
ad5fba56e0 | ||
|
|
0ad62b504a | ||
|
|
8487970a2e | ||
|
|
8212b630ff | ||
|
|
2a71640095 | ||
|
|
33880dab77 | ||
|
|
87fcd83275 | ||
|
|
600721ca09 | ||
|
|
f7a1d09be7 | ||
|
|
e5d982ed33 | ||
|
|
c815d0192a | ||
|
|
9569d73e2f | ||
|
|
dd5bc46ead | ||
|
|
fc3567ddda | ||
|
|
9b64d65d94 | ||
|
|
54eb73b423 | ||
|
|
4c0987a618 | ||
|
|
0637973e40 | ||
|
|
3690bde94c | ||
|
|
b92ff2baff | ||
|
|
afe82f6453 | ||
|
|
1c7b872476 | ||
|
|
28e7ebb4a4 | ||
|
|
1ef9b29f6d | ||
|
|
b910591e1d | ||
|
|
d807510dd7 | ||
|
|
4e485219d7 | ||
|
|
b0e042988c | ||
|
|
f877de7778 | ||
|
|
655d5934a2 | ||
|
|
f6bf885fb7 | ||
|
|
2857382006 | ||
|
|
972667f115 | ||
|
|
3c88145329 | ||
|
|
709913b5ea | ||
|
|
88884b68dc | ||
|
|
5a15b65b94 | ||
|
|
3899680672 | ||
|
|
6b86408500 | ||
|
|
bdbaa00628 | ||
|
|
87e86dcfd2 | ||
|
|
995b201e47 | ||
|
|
0663831dd5 | ||
|
|
afb2653452 | ||
|
|
9ec4a85821 | ||
|
|
4d868794fd | ||
|
|
1aca099457 | ||
|
|
f5d098876a | ||
|
|
080080f57f | ||
|
|
cd4afc3269 | ||
|
|
8f8669778c | ||
|
|
563fce4adf | ||
|
|
b287101625 | ||
|
|
ab9fb2c9dc | ||
|
|
c170b5a621 | ||
|
|
d9286ed8f6 | ||
|
|
3018e82902 | ||
|
|
34acf14d7c | ||
|
|
5d8eba9b29 | ||
|
|
b284b7be83 | ||
|
|
b293ac1f83 | ||
|
|
8d9d635b8b | ||
|
|
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 | ||
|
|
0ef7e65f02 | ||
|
|
61b61d3009 | ||
|
|
2710cb382f | ||
|
|
4d7fcd846e | ||
|
|
b85243a6b4 | ||
|
|
53c3fe9f37 | ||
|
|
2ce57ba2ee | ||
|
|
cc9b7f3942 | ||
|
|
85a2024669 | ||
|
|
87573089e8 | ||
|
|
4fb47a833a | ||
|
|
9cffef50b0 | ||
|
|
b4857937fb | ||
|
|
1e260e4a5e | ||
|
|
ee8ea507a6 | ||
|
|
b5830a3011 | ||
|
|
4fde6e13db | ||
|
|
a5e790caf1 | ||
|
|
1e956ae8a5 | ||
|
|
1671b8ba7d | ||
|
|
608ba19c3c | ||
|
|
daf7d3b876 | ||
|
|
34f6d4d568 | ||
|
|
1784b02d03 | ||
|
|
68c51d8c16 | ||
|
|
4bbb1699a3 | ||
|
|
753f65a56d | ||
|
|
43adbeaaca | ||
|
|
df6489ab43 | ||
|
|
31d0fce6fd | ||
|
|
cd427d9714 | ||
|
|
6d4f0984b6 | ||
|
|
ac40eec423 | ||
|
|
8de9b70b33 | ||
|
|
face92536c | ||
|
|
3a2a460fe7 | ||
|
|
f16d96bc8f | ||
|
|
0f86cc9b87 | ||
|
|
ca844aa044 | ||
|
|
604af3f1d2 | ||
|
|
28e13c3a18 | ||
|
|
7db31846cf |
@@ -0,0 +1,77 @@
|
||||
HELM_VERSION ?= v3.5.3
|
||||
HELM2_VERSION ?= v2.17.0
|
||||
KUSTOMIZE_VERSION ?= v3.8.8
|
||||
K8S_VERSION ?= v1.13.12
|
||||
MINIKUBE_VERSION ?= v0.30.0
|
||||
SOPS_VERSION ?= v3.6.1
|
||||
|
||||
# ---
|
||||
CHANGE_MINIKUBE_NONE_USER ?= true
|
||||
MINIKUBE_WANTUPDATENOTIFICATION ?= false
|
||||
MINIKUBE_WANTREPORTERRORPROMPT ?= false
|
||||
|
||||
VAULT_ADDR := http://127.0.0.1:8200
|
||||
VAULT_TOKEN := toor
|
||||
|
||||
tmp := $(shell mktemp -d)
|
||||
HELM_FILENAME := helm-${HELM_VERSION}-linux-amd64.tar.gz
|
||||
HELM2_FILENAME := helm-${HELM2_VERSION}-linux-amd64.tar.gz
|
||||
KUSTOMIZE_FILENAME := kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz
|
||||
|
||||
|
||||
all: vault sops helm kustomize minikube/destroy minikube
|
||||
|
||||
helm:
|
||||
curl -sSLo $(tmp)/${HELM_FILENAME} "https://get.helm.sh/${HELM_FILENAME}"
|
||||
tar zxf $(tmp)/${HELM_FILENAME} --directory ${tmp} linux-amd64/helm
|
||||
chmod +x ${tmp}/linux-amd64/helm
|
||||
sudo mv ${tmp}/linux-amd64/helm /usr/local/bin/
|
||||
.PHONY: helm
|
||||
|
||||
helm2:
|
||||
curl -sSLo $(tmp)/${HELM2_FILENAME} "https://kubernetes-helm.storage.googleapis.com/${HELM2_FILENAME}"
|
||||
tar zxf $(tmp)/${HELM2_FILENAME} --directory ${tmp} linux-amd64/helm
|
||||
chmod +x ${tmp}/linux-amd64/helm
|
||||
sudo mv ${tmp}/linux-amd64/helm /usr/local/bin/
|
||||
.PHONY: helm2
|
||||
|
||||
kustomize:
|
||||
curl -sSLo $(tmp)/${KUSTOMIZE_FILENAME} "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/${KUSTOMIZE_FILENAME}"
|
||||
tar zxf $(tmp)/${KUSTOMIZE_FILENAME} --directory ${tmp} kustomize
|
||||
chmod +x ${tmp}/kustomize
|
||||
sudo mv ${tmp}/kustomize /usr/local/bin/
|
||||
.PHONY: kustomize
|
||||
|
||||
minikube/destroy:
|
||||
sudo -E minikube delete || true
|
||||
sudo -E rm -rf /etc/kubernetes || true
|
||||
sudo -E rm -rf $$HOME/.minikube/* || true
|
||||
.PHONY: minikube/destroy
|
||||
.EXPORT_ALL_VARIABLES: minikube/destroy
|
||||
|
||||
minikube:
|
||||
curl -sSLo ${tmp}/kubectl https://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl
|
||||
chmod +x ${tmp}/kubectl && sudo mv ${tmp}/kubectl /usr/local/bin/
|
||||
curl -sSLo ${tmp}/minikube https://storage.googleapis.com/minikube/releases/${MINIKUBE_VERSION}/minikube-linux-amd64
|
||||
chmod +x ${tmp}/minikube && sudo mv ${tmp}/minikube /usr/local/bin/
|
||||
sudo -E minikube delete || true
|
||||
sudo -E rm -rf /etc/kubernetes || true
|
||||
sudo -E rm -rf $$HOME/.minikube/* || true
|
||||
sudo -E minikube start --vm-driver=none --kubernetes-version=${K8S_VERSION}
|
||||
sudo -E minikube update-context
|
||||
kubectl wait node/minikube --for=condition=Ready
|
||||
.PHONY: minikube
|
||||
.EXPORT_ALL_VARIABLES: minikube
|
||||
|
||||
vault:
|
||||
docker kill $$(docker ps -a --filter "name=vault" -q) || true
|
||||
docker run -d -p8200:8200 --rm --name vault vault:1.2.0 server -dev -dev-root-token-id=toor
|
||||
docker run --rm --network="host" -e VAULT_ADDR=$$VAULT_ADDR -e VAULT_TOKEN=$$VAULT_TOKEN vault:1.2.0 secrets enable -path=sops transit
|
||||
docker run --rm --network="host" -e VAULT_ADDR=$$VAULT_ADDR -e VAULT_TOKEN=$$VAULT_TOKEN vault:1.2.0 write sops/keys/key type=rsa-4096
|
||||
.PHONY: vault
|
||||
|
||||
sops:
|
||||
curl -sSLo $(tmp)/sops "https://github.com/mozilla/sops/releases/download/${SOPS_VERSION}/sops-${SOPS_VERSION}.linux"
|
||||
chmod +x $(tmp)/sops
|
||||
sudo mv ${tmp}/sops /usr/local/bin/
|
||||
.PHONY: sops
|
||||
+36
-85
@@ -1,10 +1,10 @@
|
||||
version: 2
|
||||
version: 2.1
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.14.2
|
||||
- image: circleci/golang:1.16.3
|
||||
environment:
|
||||
GOPATH: /go
|
||||
working_directory: /home/circleci/workspace/helmfile
|
||||
@@ -16,6 +16,7 @@ jobs:
|
||||
- go-mod-cache-v1-
|
||||
- run: go env
|
||||
- run: make build
|
||||
- run: make build-test-tools
|
||||
- save_cache:
|
||||
key: go-mod-cache-v1-{{ checksum "./go.sum" }}
|
||||
paths:
|
||||
@@ -26,7 +27,7 @@ jobs:
|
||||
- .
|
||||
image:
|
||||
docker:
|
||||
- image: circleci/golang:1.14.2
|
||||
- image: circleci/golang:1.16.3
|
||||
working_directory: /home/circleci/workspace/helmfile
|
||||
steps:
|
||||
- checkout
|
||||
@@ -43,7 +44,7 @@ jobs:
|
||||
|
||||
image_helm3:
|
||||
docker:
|
||||
- image: circleci/golang:1.14.2
|
||||
- image: circleci/golang:1.16.3
|
||||
working_directory: /home/circleci/workspace/helmfile
|
||||
steps:
|
||||
- checkout
|
||||
@@ -62,7 +63,7 @@ jobs:
|
||||
environment:
|
||||
GOPATH: /go
|
||||
docker:
|
||||
- image: circleci/golang:1.14.2
|
||||
- image: circleci/golang:1.16.3
|
||||
working_directory: /home/circleci/workspace/helmfile
|
||||
steps:
|
||||
- checkout
|
||||
@@ -77,99 +78,49 @@ jobs:
|
||||
# thanks to https://raw.githubusercontent.com/weaveworks/launcher/master/.circleci/config.yml
|
||||
integration_tests:
|
||||
machine:
|
||||
image: circleci/classic:201808-01
|
||||
environment:
|
||||
CHANGE_MINIKUBE_NONE_USER: true
|
||||
MINIKUBE_WANTUPDATENOTIFICATION: false
|
||||
MINIKUBE_WANTREPORTERRORPROMPT: false
|
||||
image: ubuntu-2004:202010-01
|
||||
parameters:
|
||||
helm-version:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- run: mkdir ~/build
|
||||
- attach_workspace:
|
||||
at: ~/build
|
||||
- run: cp ~/build/helmfile ~/project/helmfile
|
||||
- run:
|
||||
name: Install helm
|
||||
environment:
|
||||
HELM_VERSION: v2.16.1
|
||||
name: Install test dependencies
|
||||
command: |
|
||||
HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
curl -Lo ${HELM_FILENAME} "https://kubernetes-helm.storage.googleapis.com/${HELM_FILENAME}"
|
||||
tar zxf ${HELM_FILENAME} linux-amd64/helm
|
||||
chmod +x linux-amd64/helm
|
||||
sudo mv linux-amd64/helm /usr/local/bin/
|
||||
- run:
|
||||
name: Deploy minikube
|
||||
environment:
|
||||
CHANGE_MINIKUBE_NONE_USER: true
|
||||
K8S_VERSION: v1.12.3
|
||||
MINIKUBE_VERSION: v0.30.0
|
||||
command: |
|
||||
curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl
|
||||
chmod +x kubectl && sudo mv kubectl /usr/local/bin/
|
||||
curl -Lo minikube https://storage.googleapis.com/minikube/releases/${MINIKUBE_VERSION}/minikube-linux-amd64
|
||||
chmod +x minikube && sudo mv minikube /usr/local/bin/
|
||||
sudo -E minikube start --vm-driver=none --kubernetes-version=${K8S_VERSION}
|
||||
sudo -E minikube update-context
|
||||
- run:
|
||||
name: Wait for nodes to become ready
|
||||
command: JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'; until kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 1; done
|
||||
cp ~/build/helmfile ~/project/helmfile
|
||||
cp ~/build/diff-yamls ~/project/diff-yamls
|
||||
cp ~/build/yamldiff ~/project/yamldiff
|
||||
if [[ "<< parameters.helm-version >>" == v3* ]]
|
||||
then
|
||||
make -C .circleci helm
|
||||
else
|
||||
make -C .circleci helm2
|
||||
fi
|
||||
make -C .circleci vault
|
||||
make -C .circleci sops
|
||||
make -C .circleci kustomize
|
||||
make -C .circleci minikube
|
||||
- run:
|
||||
name: Execute integration tests
|
||||
environment:
|
||||
TERM: "xterm"
|
||||
command: |
|
||||
export TERM=xterm
|
||||
make integration
|
||||
|
||||
integration_tests_helm3:
|
||||
machine:
|
||||
image: circleci/classic:201808-01
|
||||
environment:
|
||||
CHANGE_MINIKUBE_NONE_USER: true
|
||||
MINIKUBE_WANTUPDATENOTIFICATION: false
|
||||
MINIKUBE_WANTREPORTERRORPROMPT: false
|
||||
steps:
|
||||
- checkout
|
||||
- run: mkdir ~/build
|
||||
- attach_workspace:
|
||||
at: ~/build
|
||||
- run: cp ~/build/helmfile ~/project/helmfile
|
||||
- run:
|
||||
name: Install helm
|
||||
environment:
|
||||
HELM_VERSION: v3.0.0
|
||||
command: |
|
||||
HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
curl -Lo ${HELM_FILENAME} "https://get.helm.sh/${HELM_FILENAME}"
|
||||
tar zxf ${HELM_FILENAME} linux-amd64/helm
|
||||
chmod +x linux-amd64/helm
|
||||
sudo mv linux-amd64/helm /usr/local/bin/
|
||||
- run:
|
||||
name: Deploy minikube
|
||||
environment:
|
||||
CHANGE_MINIKUBE_NONE_USER: true
|
||||
K8S_VERSION: v1.12.3
|
||||
MINIKUBE_VERSION: v0.30.0
|
||||
command: |
|
||||
curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl
|
||||
chmod +x kubectl && sudo mv kubectl /usr/local/bin/
|
||||
curl -Lo minikube https://storage.googleapis.com/minikube/releases/${MINIKUBE_VERSION}/minikube-linux-amd64
|
||||
chmod +x minikube && sudo mv minikube /usr/local/bin/
|
||||
sudo -E minikube start --vm-driver=none --kubernetes-version=${K8S_VERSION}
|
||||
sudo -E minikube update-context
|
||||
- run:
|
||||
name: Wait for nodes to become ready
|
||||
command: JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'; until kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 1; done
|
||||
- run:
|
||||
name: Execute integration tests
|
||||
command: |
|
||||
export TERM=xterm
|
||||
HELMFILE_HELM3=1 make integration
|
||||
if [[ "<< parameters.helm-version >>" == v3* ]]
|
||||
then
|
||||
HELMFILE_HELM3=1 make integration
|
||||
else
|
||||
make integration
|
||||
fi
|
||||
|
||||
# GITHUB_TOKEN env var must be setup in circleci console
|
||||
|
||||
release:
|
||||
docker:
|
||||
- image: circleci/golang:1.14.2
|
||||
- image: circleci/golang:1.16.3
|
||||
working_directory: /home/circleci/workspace/helmfile
|
||||
steps:
|
||||
- checkout
|
||||
@@ -196,9 +147,9 @@ workflows:
|
||||
- integration_tests:
|
||||
requires:
|
||||
- build
|
||||
- integration_tests_helm3:
|
||||
requires:
|
||||
- build
|
||||
matrix:
|
||||
parameters:
|
||||
helm-version: ["v2.17.0", "v3.4.2", "v3.5.4"]
|
||||
- release:
|
||||
filters:
|
||||
branches:
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
root = true
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
@@ -2,4 +2,11 @@ dist/
|
||||
.idea/
|
||||
helmfile
|
||||
helmfile.lock
|
||||
/diff-yamls
|
||||
/yamldiff
|
||||
test/integration/tmp
|
||||
vendor/
|
||||
*.log
|
||||
.vagrant/
|
||||
*.lock
|
||||
test/integration/.gnupg/
|
||||
|
||||
+13
-9
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.14.2-alpine3.11 as builder
|
||||
FROM golang:1.16.3-alpine3.13 as builder
|
||||
|
||||
RUN apk add --no-cache make git
|
||||
WORKDIR /workspace/helmfile
|
||||
@@ -11,11 +11,12 @@ FROM alpine:3.11
|
||||
|
||||
RUN apk add --no-cache ca-certificates git bash curl jq
|
||||
|
||||
ARG HELM_VERSION=v2.16.1
|
||||
ARG HELM_VERSION="v2.17.0"
|
||||
ARG HELM_LOCATION="https://kubernetes-helm.storage.googleapis.com"
|
||||
ARG HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
ARG HELM_SHA256="7eebaaa2da4734242bbcdced62cc32ba8c7164a18792c8acdf16c77abffce202"
|
||||
RUN wget ${HELM_LOCATION}/${HELM_FILENAME} && \
|
||||
ARG HELM_SHA256="f3bec3c7c55f6a9eb9e6586b8c503f370af92fe987fcbf741f37707606d70296"
|
||||
RUN set -x && \
|
||||
wget ${HELM_LOCATION}/${HELM_FILENAME} && \
|
||||
echo Verifying ${HELM_FILENAME}... && \
|
||||
sha256sum ${HELM_FILENAME} | grep -q "${HELM_SHA256}" && \
|
||||
echo Extracting ${HELM_FILENAME}... && \
|
||||
@@ -26,14 +27,17 @@ RUN wget ${HELM_LOCATION}/${HELM_FILENAME} && \
|
||||
# using the install documentation found at https://kubernetes.io/docs/tasks/tools/install-kubectl/
|
||||
# for now but in a future version of alpine (in the testing version at the time of writing)
|
||||
# we should be able to install using apk add.
|
||||
ENV KUBECTL_VERSION="v1.14.5"
|
||||
ENV KUBECTL_SHA256="26681319de56820a8467c9407e9203d5b15fb010ffc75ac5b99c9945ad0bd28c"
|
||||
RUN curl --retry 3 -LO "https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \
|
||||
# the sha256 sum can be found at https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl.sha256
|
||||
# maybe a good idea to automate in the future?
|
||||
ENV KUBECTL_VERSION="v1.18.9"
|
||||
ENV KUBECTL_SHA256="6a68756a2d3d04b4d0f52b00de6493ba2c1fcb28b32f3e4a0e99b3d9f6c4e8ed"
|
||||
RUN set -x & \
|
||||
curl --retry 5 --retry-connrefused -LO "https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \
|
||||
sha256sum kubectl | grep ${KUBECTL_SHA256} && \
|
||||
chmod +x kubectl && \
|
||||
mv kubectl /usr/local/bin/kubectl
|
||||
|
||||
RUN ["helm", "init", "--client-only"]
|
||||
RUN ["helm", "init", "--client-only", "--stable-repo-url", "https://charts.helm.sh/stable"]
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff && \
|
||||
helm plugin install https://github.com/futuresimple/helm-secrets && \
|
||||
helm plugin install https://github.com/hypnoglow/helm-s3.git && \
|
||||
@@ -42,4 +46,4 @@ RUN helm plugin install https://github.com/databus23/helm-diff && \
|
||||
|
||||
COPY --from=builder /workspace/helmfile/dist/helmfile_linux_amd64 /usr/local/bin/helmfile
|
||||
|
||||
CMD ["/usr/local/bin/helmfile"]
|
||||
CMD ["/usr/local/bin/helmfile", "--help"]
|
||||
|
||||
+24
-11
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.14.2-alpine3.11 as builder
|
||||
FROM golang:1.16.3-alpine3.13 as builder
|
||||
|
||||
RUN apk add --no-cache make git
|
||||
WORKDIR /workspace/helmfile
|
||||
@@ -11,11 +11,12 @@ FROM alpine:3.11
|
||||
|
||||
RUN apk add --no-cache ca-certificates git bash curl jq
|
||||
|
||||
ARG HELM_VERSION=v3.2.1
|
||||
ARG HELM_VERSION="v3.5.3"
|
||||
ARG HELM_LOCATION="https://get.helm.sh"
|
||||
ARG HELM_FILENAME="helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
ARG HELM_SHA256="018f9908cb950701a5d59e757653a790c66d8eda288625dbb185354ca6f41f6b"
|
||||
RUN wget ${HELM_LOCATION}/${HELM_FILENAME} && \
|
||||
ARG HELM_SHA256="2170a1a644a9e0b863f00c17b761ce33d4323da64fc74562a3a6df2abbf6cd70"
|
||||
RUN set -x && \
|
||||
wget ${HELM_LOCATION}/${HELM_FILENAME} && \
|
||||
echo Verifying ${HELM_FILENAME}... && \
|
||||
sha256sum ${HELM_FILENAME} | grep -q "${HELM_SHA256}" && \
|
||||
echo Extracting ${HELM_FILENAME}... && \
|
||||
@@ -25,17 +26,29 @@ RUN wget ${HELM_LOCATION}/${HELM_FILENAME} && \
|
||||
# using the install documentation found at https://kubernetes.io/docs/tasks/tools/install-kubectl/
|
||||
# for now but in a future version of alpine (in the testing version at the time of writing)
|
||||
# we should be able to install using apk add.
|
||||
ENV KUBECTL_VERSION="v1.14.5"
|
||||
ENV KUBECTL_SHA256="26681319de56820a8467c9407e9203d5b15fb010ffc75ac5b99c9945ad0bd28c"
|
||||
RUN curl --retry 3 -LO "https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \
|
||||
# the sha256 sum can be found at https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl.sha256
|
||||
# maybe a good idea to automate in the future?
|
||||
ENV KUBECTL_VERSION="v1.18.9"
|
||||
ENV KUBECTL_SHA256="6a68756a2d3d04b4d0f52b00de6493ba2c1fcb28b32f3e4a0e99b3d9f6c4e8ed"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO "https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" && \
|
||||
sha256sum kubectl | grep ${KUBECTL_SHA256} && \
|
||||
chmod +x kubectl && \
|
||||
mv kubectl /usr/local/bin/kubectl
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.0.0-rc.7 && \
|
||||
helm plugin install https://github.com/futuresimple/helm-secrets && \
|
||||
helm plugin install https://github.com/hypnoglow/helm-s3.git && \
|
||||
helm plugin install https://github.com/aslafy-z/helm-git.git
|
||||
ENV KUSTOMIZE_VERSION="v3.8.8"
|
||||
ENV KUSTOMIZE_SHA256="175938206f23956ec18dac3da0816ea5b5b485a8493a839da278faac82e3c303"
|
||||
RUN set -x && \
|
||||
curl --retry 5 --retry-connrefused -LO https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \
|
||||
sha256sum kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz | grep ${KUSTOMIZE_SHA256} && \
|
||||
tar zxf kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \
|
||||
rm kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz && \
|
||||
mv kustomize /usr/local/bin/kustomize
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.1.3 && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets --version v3.5.0 && \
|
||||
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.10.0 && \
|
||||
helm plugin install https://github.com/aslafy-z/helm-git.git --version v0.10.0
|
||||
|
||||
COPY --from=builder /workspace/helmfile/dist/helmfile_linux_amd64 /usr/local/bin/helmfile
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ ORG ?= $(shell basename $(realpath ..))
|
||||
PKGS := $(shell go list ./... | grep -v /vendor/)
|
||||
|
||||
build:
|
||||
go build ${TARGETS}
|
||||
go build -ldflags '-X github.com/roboll/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
|
||||
.PHONY: build
|
||||
|
||||
generate:
|
||||
@@ -17,6 +17,11 @@ check:
|
||||
go vet ${PKGS}
|
||||
.PHONY: check
|
||||
|
||||
build-test-tools:
|
||||
go build test/diff-yamls/diff-yamls.go
|
||||
go build test/yamldiff/yamldiff.go
|
||||
.PHONY: build-test-tools
|
||||
|
||||
test:
|
||||
go test -v ${PKGS} -cover -race -p=1
|
||||
.PHONY: test
|
||||
@@ -25,12 +30,19 @@ integration:
|
||||
bash test/integration/run.sh
|
||||
.PHONY: integration
|
||||
|
||||
integration/vagrant:
|
||||
$(MAKE) build GOOS=linux GOARCH=amd64
|
||||
$(MAKE) build-test-tools GOOS=linux GOARCH=amd64
|
||||
vagrant up
|
||||
vagrant ssh -c 'HELMFILE_HELM3=1 make -C /vagrant integration'
|
||||
.PHONY: integration/vagrant
|
||||
|
||||
cross:
|
||||
env CGO_ENABLED=0 gox -os '!openbsd !freebsd !netbsd' -arch '!arm !mips !mipsle !mips64 !mips64le !s390x' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X github.com/roboll/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
|
||||
env CGO_ENABLED=0 gox -os 'windows darwin linux' -arch '386 amd64 arm64' -osarch '!darwin/arm64 !darwin/386' -output "dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags '-X github.com/roboll/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
|
||||
.PHONY: cross
|
||||
|
||||
static-linux:
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOFLAGS=-mod=vendor go build -o "dist/helmfile_linux_amd64" -ldflags '-X github.com/roboll/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
|
||||
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOFLAGS=-mod=readonly go build -o "dist/helmfile_linux_amd64" -ldflags '-X github.com/roboll/helmfile/pkg/app/version.Version=${TAG}' ${TARGETS}
|
||||
.PHONY: static-linux
|
||||
|
||||
install:
|
||||
@@ -59,7 +71,6 @@ run: image
|
||||
push: image
|
||||
docker push quay.io/${ORG}/helmfile:${TAG}
|
||||
|
||||
|
||||
image/helm3:
|
||||
docker build -f Dockerfile.helm3 -t quay.io/${ORG}/helmfile:helm3-${TAG} .
|
||||
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
# Paths Overivew
|
||||
Using manifest files in conjunction with command line argument can be a bit confusing.
|
||||
# Paths Overivew
|
||||
|
||||
A few rules to clear up this ambiguity:
|
||||
Using manifest files in conjunction with command line argument can be a bit confusing.
|
||||
|
||||
A few rules to clear up this ambiguity:
|
||||
|
||||
- Absolute paths are always resolved as absolute paths
|
||||
- Relative paths referenced *in* the helmfile manifest itself are relative to that manifest
|
||||
- Relative paths referenced on the command line are relative to the current working directory the user is in
|
||||
|
||||
### Examples
|
||||
There are several examples that we can go through in the `/examples` folder which demonstrate this.
|
||||
|
||||
There are several examples that we can go through in the [`/examples`](examples) folder which demonstrate this.
|
||||
|
||||
**Local Execution**
|
||||
|
||||
This is an example of a Helmfile manifest referencing a local value directly.
|
||||
This is an example of a Helmfile manifest referencing a local value directly.
|
||||
|
||||
Indirect:
|
||||
```
|
||||
helmfile -f examples/deployments/local/charts.yaml sync
|
||||
```
|
||||
|
||||
Direct:
|
||||
Direct:
|
||||
```
|
||||
cd examples/deployments/local/
|
||||
helmfile sync
|
||||
@@ -27,14 +29,14 @@ helmfile sync
|
||||
|
||||
**Relative Paths in Helmfile**
|
||||
|
||||
This is an example of a Helmfile manifest using relative paths for values.
|
||||
This is an example of a Helmfile manifest using relative paths for values.
|
||||
|
||||
Indirect:
|
||||
```
|
||||
helmfile -f examples/deployments/dev/charts.yaml sync
|
||||
```
|
||||
|
||||
Direct:
|
||||
Direct:
|
||||
```
|
||||
cd examples/deployments/dev/
|
||||
helmfile sync
|
||||
@@ -42,7 +44,7 @@ helmfile sync
|
||||
|
||||
**Relative Paths in Helmfile w/ --values overrides**
|
||||
|
||||
This is an example of a Helmfile manifest using relative paths for values including an additional `--values` from the command line.
|
||||
This is an example of a Helmfile manifest using relative paths for values including an additional `--values` from the command line.
|
||||
|
||||
NOTE: The `--values` is resolved relative to the CWD of the terminal *not* the Helmfile manifest. You can see this with the `replicas` being adjusted to 3 now for the deployment.
|
||||
|
||||
@@ -51,7 +53,7 @@ Indirect:
|
||||
helmfile -f examples/deployments/dev/charts.yaml sync --values values/replica-values.yaml
|
||||
```
|
||||
|
||||
Direct:
|
||||
Direct:
|
||||
```
|
||||
cd examples/deployments/dev/
|
||||
helmfile sync --values ../../values/replica-values.yaml
|
||||
|
||||
@@ -47,20 +47,21 @@ The default name for a helmfile is `helmfile.yaml`:
|
||||
repositories:
|
||||
# To use official "stable" charts a.k.a https://github.com/helm/charts/tree/master/stable
|
||||
- name: stable
|
||||
url: https://kubernetes-charts.storage.googleapis.com
|
||||
url: https://charts.helm.sh/stable
|
||||
# To use official "incubator" charts a.k.a https://github.com/helm/charts/tree/master/incubator
|
||||
- name: incubator
|
||||
url: https://kubernetes-charts-incubator.storage.googleapis.com
|
||||
url: https://charts.helm.sh/incubator
|
||||
# helm-git powered repository: You can treat any Git repository as a charts repository
|
||||
- name: polaris
|
||||
url: git+https://github.com/reactiveops/polaris@deploy/helm?ref=master
|
||||
# Advanced configuration: You can setup basic or tls auth
|
||||
# Advanced configuration: You can setup basic or tls auth and optionally enable helm OCI integration
|
||||
- name: roboll
|
||||
url: http://roboll.io/charts
|
||||
certFile: optional_client_cert
|
||||
keyFile: optional_client_key
|
||||
username: optional_username
|
||||
password: optional_password
|
||||
oci: true
|
||||
# Advanced configuration: You can use a ca bundle to use an https repo
|
||||
# with a self-signed certificate
|
||||
- name: insecure
|
||||
@@ -69,6 +70,9 @@ repositories:
|
||||
|
||||
# context: kube-context # this directive is deprecated, please consider using helmDefaults.kubeContext
|
||||
|
||||
# Path to alternative helm binary (--helm-binary)
|
||||
helmBinary: path/to/helm3
|
||||
|
||||
# Default values to set for args along with dedicated keys that can be set by contributors, cli args take precedence over these.
|
||||
# In other words, unset values results in no flags passed to helm.
|
||||
# See the helm usage (helm SUBCOMMAND -h) for more info on default values when those flags aren't provided.
|
||||
@@ -84,6 +88,8 @@ helmDefaults:
|
||||
verify: true
|
||||
# wait for k8s resources via --wait. (default false)
|
||||
wait: true
|
||||
# if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout (default false, Implemented in Helm3.5)
|
||||
waitForJobs: true
|
||||
# time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300)
|
||||
timeout: 600
|
||||
# performs pods restart for the resource if applicable (default false)
|
||||
@@ -102,7 +108,15 @@ helmDefaults:
|
||||
historyMax: 10
|
||||
# when using helm 3.2+, automatically create release namespaces if they do not exist (default true)
|
||||
createNamespace: true
|
||||
# if used with charts museum allows to pull unstable charts for deployment, for example: if 1.2.3 and 1.2.4-dev versions exist and set to true, 1.2.4-dev will be pulled (default false)
|
||||
devel: true
|
||||
# When set to `true`, skips running `helm dep up` and `helm dep build` on this release's chart.
|
||||
# Useful when the chart is broken, like seen in https://github.com/roboll/helmfile/issues/1547
|
||||
skipDeps: false
|
||||
|
||||
# these labels will be applied to all releases in a Helmfile. Useful in templating if you have a helmfile per environment or customer and don't want to copy the same label to each release
|
||||
commonLabels:
|
||||
hello: world
|
||||
|
||||
# The desired states of Helm releases.
|
||||
#
|
||||
@@ -157,9 +171,10 @@ releases:
|
||||
# will attempt to decrypt it using helm-secrets plugin
|
||||
secrets:
|
||||
- vault_secret.yaml
|
||||
# Override helmDefaults options for verify, wait, timeout, recreatePods and force.
|
||||
# Override helmDefaults options for verify, wait, waitForJobs, timeout, recreatePods and force.
|
||||
verify: true
|
||||
wait: true
|
||||
waitForJobs: true
|
||||
timeout: 60
|
||||
recreatePods: true
|
||||
force: false
|
||||
@@ -186,8 +201,23 @@ releases:
|
||||
# See https://github.com/roboll/helmfile/issues/642
|
||||
# (default "", which means the standard kubeconfig, either ~/kubeconfig or the file pointed by $KUBECONFIG environment variable)
|
||||
kubeContext: kube-context
|
||||
# passes --disable-validation to helm 3 diff plugin, this requires diff plugin >= 3.1.2
|
||||
# It may be helpful to deploy charts with helm api v1 CRDS
|
||||
# https://github.com/roboll/helmfile/pull/1373
|
||||
disableValidation: false
|
||||
# passes --disable-validation to helm 3 diff plugin, this requires diff plugin >= 3.1.2
|
||||
# It is useful when any release contains custom resources for CRDs that is not yet installed onto the cluster.
|
||||
# https://github.com/roboll/helmfile/pull/1618
|
||||
disableValidationOnInstall: false
|
||||
# passes --disable-openapi-validation to helm 3 diff plugin, this requires diff plugin >= 3.1.2
|
||||
# It may be helpful to deploy charts with helm api v1 CRDS
|
||||
# https://github.com/roboll/helmfile/pull/1373
|
||||
disableOpenApiValidation: false
|
||||
# limit the maximum number of revisions saved per release. Use 0 for no limit (default 10)
|
||||
historyMax: 10
|
||||
# When set to `true`, skips running `helm dep up` and `helm dep build` on this release's chart.
|
||||
# Useful when the chart is broken, like seen in https://github.com/roboll/helmfile/issues/1547
|
||||
skipDeps: false
|
||||
|
||||
# Local chart example
|
||||
- name: grafana # name of this release
|
||||
@@ -268,6 +298,8 @@ environments:
|
||||
# Use "Warn", "Info", or "Debug" if you want helmfile to not fail when a values file is missing, while just leaving
|
||||
# a message about the missing file at the log-level.
|
||||
missingFileHandler: Error
|
||||
# kubeContext to use for this environment
|
||||
kubeContext: kube-context
|
||||
|
||||
#
|
||||
# Advanced Configuration: Layering
|
||||
@@ -298,6 +330,20 @@ apiVersions:
|
||||
|
||||
Helmfile uses [Go templates](https://godoc.org/text/template) for templating your helmfile.yaml. While go ships several built-in functions, we have added all of the functions in the [Sprig library](https://godoc.org/github.com/Masterminds/sprig).
|
||||
|
||||
We also added the following functions:
|
||||
|
||||
- `requiredEnv`
|
||||
- `exec`
|
||||
- `readFile`
|
||||
- `toYaml`
|
||||
- `fromYaml`
|
||||
- `setValueAtPath`
|
||||
- `get` (Sprig's original `get` is available as `sprigGet`)
|
||||
- `tpl`
|
||||
- `required`
|
||||
- `fetchSecretValue`
|
||||
- `expandSecretRefs`
|
||||
|
||||
We also added one special template function: `requiredEnv`.
|
||||
The `requiredEnv` function allows you to declare a particular environment variable as required for template rendering.
|
||||
If the environment variable is unset or empty, the template rendering will fail with an error message.
|
||||
@@ -334,15 +380,44 @@ releases:
|
||||
|
||||
If you wish to treat your enviroment variables as strings always, even if they are boolean or numeric values you can use `{{ env "ENV_NAME" | quote }}` or `"{{ env "ENV_NAME" }}"`. These approaches also work with `requiredEnv`.
|
||||
|
||||
## installation
|
||||
## Installation
|
||||
|
||||
- download one of [releases](https://github.com/roboll/helmfile/releases) or
|
||||
- run as a [container](https://quay.io/roboll/helmfile) or
|
||||
- install from [AUR](https://aur.archlinux.org/packages/kubernetes-helmfile-bin/) for Archlinux or
|
||||
- [run as a container](#running-as-a-container) or
|
||||
- Archlinux: install via `pacman -S helmfile` or from [AUR](https://aur.archlinux.org/packages/kubernetes-helmfile-bin/) or
|
||||
- openSUSE: install via `zypper in helmfile` assuming you are on Tumbleweed; if you are on Leap you must add the [kubic](https://download.opensuse.org/repositories/devel:/kubic/) repo for your distribution version once before that command, e.g. `zypper ar https://download.opensuse.org/repositories/devel:/kubic/openSUSE_Leap_\$releasever kubic`, or
|
||||
- Windows (using [scoop](https://scoop.sh/)): `scoop install helmfile`
|
||||
- macOS (using [homebrew](https://brew.sh/)): `brew install helmfile`
|
||||
|
||||
## getting started
|
||||
### Running as a container
|
||||
|
||||
The [Helmfile Docker images are available in Quay](https://quay.io/roboll/helmfile). There is no `latest` tag, since the `0.x` versions can contain breaking changes, so make sure you pick the right tag. Example using `helmfile 0.135.0`:
|
||||
|
||||
```sh-session
|
||||
# helm 2
|
||||
$ docker run --rm --net=host -v "${HOME}/.kube:/root/.kube" -v "${HOME}/.helm:/root/.helm" -v "${PWD}:/wd" --workdir /wd quay.io/roboll/helmfile:v0.135.0 helmfile sync
|
||||
|
||||
# helm 3
|
||||
$ docker run --rm --net=host -v "${HOME}/.kube:/root/.kube" -v "${HOME}/.config/helm:/root/.config/helm" -v "${PWD}:/wd" --workdir /wd quay.io/roboll/helmfile:helm3-v0.135.0 helmfile sync
|
||||
```
|
||||
|
||||
You can also use shims to make calling the binaries easier:
|
||||
|
||||
```sh-session
|
||||
# helm 2
|
||||
$ printf '%s\n' '#!/bin/sh' 'docker run --rm --net=host -v "${HOME}/.kube:/root/.kube" -v "${HOME}/.helm:/root/.helm" -v "${PWD}:/wd" --workdir /wd quay.io/roboll/helmfile:v0.135.0 helmfile "$@"' |
|
||||
tee helmfile
|
||||
$ chmod +x helmfile
|
||||
$ ./helmfile sync
|
||||
|
||||
# helm 3
|
||||
$ printf '%s\n' '#!/bin/sh' 'docker run --rm --net=host -v "${HOME}/.kube:/root/.kube" -v "${HOME}/.config/helm:/root/.config/helm" -v "${PWD}:/wd" --workdir /wd quay.io/roboll/helmfile:helm3-v0.135.0 helmfile "$@"' |
|
||||
tee helmfile
|
||||
$ chmod +x helmfile
|
||||
$ ./helmfile sync
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
Let's start with a simple `helmfile` and gradually improve it to fit your use-case!
|
||||
|
||||
@@ -372,7 +447,7 @@ Iterate on the `helmfile.yaml` by referencing:
|
||||
- [CLI reference](#cli-reference).
|
||||
- [Helmfile Best Practices Guide](https://github.com/roboll/helmfile/blob/master/docs/writing-helmfile.md)
|
||||
|
||||
## cli reference
|
||||
## CLI Reference
|
||||
|
||||
```
|
||||
NAME:
|
||||
@@ -382,33 +457,37 @@ USAGE:
|
||||
helmfile [global options] command [command options] [arguments...]
|
||||
|
||||
VERSION:
|
||||
v0.92.1
|
||||
v0.138.6
|
||||
|
||||
COMMANDS:
|
||||
deps update charts based on the contents of requirements.yaml
|
||||
repos sync repositories from state file (helm repo add && helm repo update)
|
||||
charts DEPRECATED: sync releases from state file (helm upgrade --install)
|
||||
diff diff releases from state file against env (helm diff)
|
||||
template template releases from state file against env (helm template)
|
||||
lint lint charts from state file (helm lint)
|
||||
sync sync all resources from state file (repos, releases and chart deps)
|
||||
apply apply all resources from state file only when there are changes
|
||||
status retrieve status of releases in state file
|
||||
delete DEPRECATED: delete releases from state file (helm delete)
|
||||
destroy deletes and then purges releases
|
||||
test test releases from state file (helm test)
|
||||
build output compiled helmfile state(s) as YAML
|
||||
list list releases defined in state file
|
||||
help, h Shows a list of commands or help for one command
|
||||
deps update charts based on their requirements
|
||||
repos sync repositories from state file (helm repo add && helm repo update)
|
||||
charts DEPRECATED: sync releases from state file (helm upgrade --install)
|
||||
diff diff releases from state file against env (helm diff)
|
||||
template template releases from state file against env (helm template)
|
||||
write-values write values files for releases. Similar to `helmfile template`, write values files instead of manifests.
|
||||
lint lint charts from state file (helm lint)
|
||||
sync sync all resources from state file (repos, releases and chart deps)
|
||||
apply apply all resources from state file only when there are changes
|
||||
status retrieve status of releases in state file
|
||||
delete DEPRECATED: delete releases from state file (helm delete)
|
||||
destroy deletes and then purges releases
|
||||
test test releases from state file (helm test)
|
||||
build output compiled helmfile state(s) as YAML
|
||||
list list releases defined in state file
|
||||
fetch fetch charts from state file
|
||||
version Show the version for Helmfile.
|
||||
help, h Shows a list of commands or help for one command
|
||||
|
||||
GLOBAL OPTIONS:
|
||||
--helm-binary value, -b value path to helm binary (default: "helm")
|
||||
--file helmfile.yaml, -f helmfile.yaml load config from file or directory. defaults to helmfile.yaml or `helmfile.d`(means `helmfile.d/*.yaml`) in this preference
|
||||
--environment default, -e default specify the environment name. defaults to default
|
||||
--environment value, -e value specify the environment name. defaults to "default"
|
||||
--state-values-set value set state values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
|
||||
--state-values-file value specify state values in a YAML file
|
||||
--quiet, -q Silence output. Equivalent to log-level warn
|
||||
--kube-context value Set kubectl context. Uses current context by default
|
||||
--debug Enable verbose output for Helm and set log-level to debug, this disables --quiet/-q effect
|
||||
--no-color Output without color
|
||||
--log-level value Set log level, default info
|
||||
--namespace value, -n value Set namespace. Uses the namespace set in the context by default, and is available in templates as {{ .Namespace }}
|
||||
@@ -462,11 +541,11 @@ An expected use-case of `apply` is to schedule it to run periodically, so that y
|
||||
|
||||
### destroy
|
||||
|
||||
The `helmfile destroy` sub-command deletes and purges all the releases defined in the manifests.
|
||||
The `helmfile destroy` sub-command uninstalls and purges all the releases defined in the manifests.
|
||||
|
||||
`helmfile --interactive destroy` instructs Helmfile to request your confirmation before actually deleting releases.
|
||||
|
||||
`destroy` basically runs `helm delete --purge` on all the targeted releases. If you don't want purging, use `helmfile delete` instead.
|
||||
`destroy` basically runs `helm uninstall --purge` on all the targeted releases. If you don't want purging, use `helmfile delete` instead.
|
||||
|
||||
### delete (DEPRECATED)
|
||||
|
||||
@@ -476,13 +555,12 @@ The `helmfile delete` sub-command deletes all the releases defined in the manife
|
||||
|
||||
Note that `delete` doesn't purge releases. So `helmfile delete && helmfile sync` results in sync failed due to that releases names are not deleted but preserved for future references. If you really want to remove releases for reuse, add `--purge` flag to run it like `helmfile delete --purge`.
|
||||
|
||||
|
||||
### secrets
|
||||
|
||||
The `secrets` parameter in a `helmfile.yaml` causes the [helm-secrets](https://github.com/futuresimple/helm-secrets) plugin to be executed to decrypt the file.
|
||||
The `secrets` parameter in a `helmfile.yaml` causes the [helm-secrets](https://github.com/jkroepke/helm-secrets) plugin to be executed to decrypt the file.
|
||||
|
||||
To supply the secret functionality Helmfile needs the `helm secrets` plugin installed. For Helm 2.3+
|
||||
you should be able to simply execute `helm plugin install https://github.com/futuresimple/helm-secrets
|
||||
you should be able to simply execute `helm plugin install https://github.com/jkroepke/helm-secrets
|
||||
`.
|
||||
|
||||
### test
|
||||
@@ -495,7 +573,13 @@ Use `--cleanup` to delete pods upon completion.
|
||||
|
||||
The `helmfile lint` sub-command runs a `helm lint` across all of the charts/releases defined in the manifest. Non local charts will be fetched into a temporary folder which will be deleted once the task is completed.
|
||||
|
||||
### fetch
|
||||
|
||||
The `helmfile fetch` sub-command downloads or copies local charts to a local directory for debug purpose. The local directory
|
||||
must be specified with `--output-dir`.
|
||||
|
||||
## Paths Overview
|
||||
|
||||
Using manifest files in conjunction with command line argument can be a bit confusing.
|
||||
|
||||
A few rules to clear up this ambiguity:
|
||||
@@ -504,9 +588,10 @@ A few rules to clear up this ambiguity:
|
||||
- Relative paths referenced *in* the Helmfile manifest itself are relative to that manifest
|
||||
- Relative paths referenced on the command line are relative to the current working directory the user is in
|
||||
|
||||
For additional context, take a look at [paths examples](PATHS.md)
|
||||
For additional context, take a look at [paths examples](PATHS.md).
|
||||
|
||||
## Labels Overview
|
||||
|
||||
A selector can be used to only target a subset of releases when running Helmfile. This is useful for large helmfiles with releases that are logically grouped together.
|
||||
|
||||
Labels are simple key value pairs that are an optional field of the release spec. When selecting by label, the search can be inverted. `tier!=backend` would match all releases that do NOT have the `tier: backend` label. `tier=fronted` would only match releases with the `tier: frontend` label.
|
||||
@@ -515,10 +600,45 @@ Multiple labels can be specified using `,` as a separator. A release must match
|
||||
|
||||
The `selector` parameter can be specified multiple times. Each parameter is resolved independently so a release that matches any parameter will be used.
|
||||
|
||||
`--selector tier=frontend --selector tier=backend` will select all the charts
|
||||
`--selector tier=frontend --selector tier=backend` will select all the charts.
|
||||
|
||||
In addition to user supplied labels, the name, the namespace, and the chart are available to be used as selectors. The chart will just be the chart name excluding the repository (Example `stable/filebeat` would be selected using `--selector chart=filebeat`).
|
||||
|
||||
`commonLabels` can be used when you want to apply the same label to all releases and use [templating](##Templates) based on that.
|
||||
For instance, you install a number of charts on every customer but need to provide different values file per customer.
|
||||
|
||||
templates/common.yaml:
|
||||
|
||||
```yaml
|
||||
templates:
|
||||
nginx: &nginx
|
||||
name: nginx
|
||||
chart: stable/nginx-ingress
|
||||
values:
|
||||
- ../values/common/{{ .Release.Name }}.yaml
|
||||
- ../values/{{ .Release.Labels.customer }}/{{ .Release.Name }}.yaml
|
||||
|
||||
cert-manager: &cert-manager
|
||||
name: cert-manager
|
||||
chart: jetstack/cert-manager
|
||||
values:
|
||||
- ../values/common/{{ .Release.Name }}.yaml
|
||||
- ../values/{{ .Release.Labels.customer }}/{{ .Release.Name }}.yaml
|
||||
```
|
||||
|
||||
helmfile.yaml:
|
||||
|
||||
```yaml
|
||||
{{ readFile "templates/common.yaml" }}
|
||||
|
||||
commonLabels:
|
||||
customer: company
|
||||
|
||||
releases:
|
||||
- <<: *nginx
|
||||
- <<: *cert-manager
|
||||
```
|
||||
|
||||
## Templates
|
||||
|
||||
You can use go's text/template expressions in `helmfile.yaml` and `values.yaml.gotmpl` (templated helm values files). `values.yaml` references will be used verbatim. In other words:
|
||||
@@ -532,6 +652,7 @@ In addition to built-in ones, the following custom template functions are availa
|
||||
- `fromYaml` reads a golang string and generates a map
|
||||
- `setValueAtPath PATH NEW_VALUE` traverses a golang map, replaces the value at the PATH with NEW_VALUE
|
||||
- `toYaml` marshals a map into a string
|
||||
- `get` returns the value of the specified key if present in the `.Values` object, otherwise will return the default value defined in the function
|
||||
|
||||
### Values Files Templates
|
||||
|
||||
@@ -560,7 +681,7 @@ foo:
|
||||
bar: ""
|
||||
```
|
||||
|
||||
The resulting, temporary values.yaml that is generated from `values.yaml.tpl` would become:
|
||||
The resulting, temporary values.yaml that is generated from `values.yaml.gotmpl` would become:
|
||||
|
||||
```yaml
|
||||
foo:
|
||||
@@ -632,6 +753,8 @@ environments:
|
||||
default:
|
||||
production:
|
||||
|
||||
---
|
||||
|
||||
releases:
|
||||
- name: newrelic-agent
|
||||
installed: {{ eq .Environment.Name "production" | toYaml }}
|
||||
@@ -655,6 +778,8 @@ environments:
|
||||
values:
|
||||
- production.yaml
|
||||
|
||||
---
|
||||
|
||||
releases:
|
||||
- name: myapp
|
||||
values:
|
||||
@@ -671,11 +796,11 @@ releaseName: prod
|
||||
`values.yaml.gotmpl`
|
||||
|
||||
```yaml
|
||||
domain: {{ .Values | getOrNil "my.domain" | default "dev.example.com" }}
|
||||
domain: {{ .Values | get "domain" "dev.example.com" }}
|
||||
```
|
||||
|
||||
`helmfile sync` installs `myapp` with the value `domain=dev.example.com`,
|
||||
whereas `helmfile --environment production sync` installs the app with the value `domain=production.example.com`.
|
||||
whereas `helmfile --environment production sync` installs the app with the value `domain=prod.example.com`.
|
||||
|
||||
For even more flexibility, you can now use values declared in the `environments:` section in other parts of your helmfiles:
|
||||
|
||||
@@ -697,6 +822,8 @@ environments:
|
||||
- production.yaml # bare .yaml file, content will be used verbatim
|
||||
- other.yaml.gotmpl # template directives with potential side-effects like `exec` and `readFile` will be honoured
|
||||
|
||||
---
|
||||
|
||||
releases:
|
||||
- name: myapp-{{ .Values.releaseName }} # release name will be one of `dev` or `prod` depending on selected environment
|
||||
values:
|
||||
@@ -707,7 +834,7 @@ releases:
|
||||
...
|
||||
```
|
||||
|
||||
### Note
|
||||
### Note on Environment.Values vs Values
|
||||
|
||||
The `{{ .Values.foo }}` syntax is the recommended way of using environment values.
|
||||
|
||||
@@ -716,13 +843,36 @@ This is still working but is **deprecated** and the new `{{ .Values.foo }}` synt
|
||||
|
||||
You can read more infos about the feature proposal [here](https://github.com/roboll/helmfile/issues/640).
|
||||
|
||||
### Loading remote environment values files
|
||||
|
||||
Since #1296 and Helmfile v0.118.8, you can use `go-getter`-style URLs to refer to remote values files:
|
||||
|
||||
```yaml
|
||||
environments:
|
||||
cluster-azure-us-west:
|
||||
values:
|
||||
- git::https://git.company.org/helmfiles/global/azure.yaml?ref=master
|
||||
- git::https://git.company.org/helmfiles/global/us-west.yaml?ref=master
|
||||
cluster-gcp-europe-west:
|
||||
values:
|
||||
- git::https://git.company.org/helmfiles/global/gcp.yaml?ref=master
|
||||
- git::https://git.company.org/helmfiles/global/europe-west.yaml?ref=master
|
||||
|
||||
---
|
||||
|
||||
releases:
|
||||
- ...
|
||||
```
|
||||
|
||||
This is particularly useful when you co-locate helmfiles within your project repo but want to reuse the definitions in a global repo.
|
||||
|
||||
## Environment Secrets
|
||||
|
||||
Environment Secrets (not to be confused with Kubernetes Secrets) are encrypted versions of `Environment Values`.
|
||||
You can list any number of `secrets.yaml` files created using `helm secrets` or `sops`, so that
|
||||
Helmfile could automatically decrypt and merge the secrets into the environment values.
|
||||
|
||||
First you must have the [helm-secrets](https://github.com/futuresimple/helm-secrets) plugin installed along with a
|
||||
First you must have the [helm-secrets](https://github.com/jkroepke/helm-secrets) plugin installed along with a
|
||||
`.sops.yaml` file to configure the method of encryption (this can be in the same directory as your helmfile or
|
||||
in the sub-directory containing your secrets files).
|
||||
|
||||
@@ -740,6 +890,8 @@ environments:
|
||||
secrets:
|
||||
- environments/production/secrets.yaml
|
||||
|
||||
---
|
||||
|
||||
releases:
|
||||
- name: myapp
|
||||
chart: mychart
|
||||
@@ -771,6 +923,8 @@ releases:
|
||||
- [TILLER_NAMESPACE/][NAMESPACE/]anotherelease
|
||||
```
|
||||
|
||||
Be aware that you have to specify the namespace name if you configured one for the release(s).
|
||||
|
||||
All the releases listed under `needs` are installed before(or deleted after) the release itself.
|
||||
|
||||
For the following example, `helmfile [sync|apply]` installs releases in this order:
|
||||
@@ -905,14 +1059,14 @@ that is accessible by running a command:
|
||||
|
||||
A usual usage of `exec` would look like this:
|
||||
|
||||
```
|
||||
```yaml
|
||||
mysetting: |
|
||||
{{ exec "./mycmd" (list "arg1" "arg2" "--flag1") | indent 2 }}
|
||||
```
|
||||
|
||||
Or even with a pipeline:
|
||||
|
||||
```
|
||||
```yaml
|
||||
mysetting: |
|
||||
{{ yourinput | exec "./mycmd-consume-stdin" (list "arg1" "arg2") | indent 2 }}
|
||||
```
|
||||
@@ -935,20 +1089,30 @@ Currently supported `events` are:
|
||||
|
||||
- `prepare`
|
||||
- `presync`
|
||||
- `preuninstall`
|
||||
- `postuninstall`
|
||||
- `postsync`
|
||||
- `cleanup`
|
||||
|
||||
Hooks associated to `prepare` events are triggered after each release in your helmfile is loaded from YAML, before execution.
|
||||
`prepare` hooks are triggered on the release as long as it is not excluded by the helmfile selector(e.g. `helmfile -l key=value`).
|
||||
|
||||
Hooks associated to `cleanup` events are triggered after each release is processed.
|
||||
Hooks associated to `presync` events are triggered before each release is applied to the remote cluster.
|
||||
This is the ideal event to execute any commands that may mutate the cluster state as it will not be run for read-only operations like `lint`, `diff` or `template`.
|
||||
|
||||
Hooks associated to `presync` events are triggered before each release is applied to the remote cluster. This is the ideal event to execute any commands that may mutate the cluster state as it will not be run for read-only operations like `lint`, `diff` or `template`.
|
||||
`preuninstall` hooks are triggered immediately before a release is uninstalled as part of `helmfile apply`, `helmfile sync`, `helmfile delete`, and `helmfile destroy`.
|
||||
|
||||
Hooks associated to `postsync` events are triggered after each release is applied to the remote cluster. This is the ideal event to execute any commands that may mutate the cluster state as it will not be run for read-only operations like `lint`, `diff` or `template`.
|
||||
`postuninstall` hooks are triggered immediately after successful uninstall of a release while running `helmfile apply`, `helmfile sync`, `helmfile delete`, `helmfile destroy`.
|
||||
|
||||
`postsync` hooks are triggered after each release is synced(installed, updated, or uninstalled) to/from the cluster, regardless of the sync was successful or not.
|
||||
This is the ideal place to execute any commands that may mutate the cluster state as it will not be run for read-only operations like `lint`, `diff` or `template`.
|
||||
|
||||
`cleanup` hooks are triggered after each release is processed.
|
||||
This is the counterpart to `prepare`, as any release on which `prepare` has been triggered gets `cleanup` triggered as well.
|
||||
|
||||
The following is an example hook that just prints the contextual information provided to hook:
|
||||
|
||||
```
|
||||
```yaml
|
||||
releases:
|
||||
- name: myapp
|
||||
chart: mychart
|
||||
@@ -979,9 +1143,57 @@ Now, replace `echo` with any command you like, and rewrite `args` that actually
|
||||
- linting
|
||||
- testing
|
||||
|
||||
Hooks expose additional template expressions:
|
||||
|
||||
`.Event.Name` is the name of the hook event.
|
||||
|
||||
`.Event.Error` is the error generated by a failed release, exposed for `posysync` hooks only when a release fails, otherwise its value is `nil`.
|
||||
|
||||
You can use the hooks event expressions to send notifications to platforms such as `Slack`, `MS Teams`, etc.
|
||||
|
||||
The following example passes arguments to a script which sends a notification:
|
||||
|
||||
```yaml
|
||||
releases:
|
||||
- name: myapp
|
||||
chart: mychart
|
||||
# *snip*
|
||||
hooks:
|
||||
- events:
|
||||
- presync
|
||||
- postsync
|
||||
showlogs: true
|
||||
command: notify.sh
|
||||
args:
|
||||
- --event
|
||||
- '{{`{{ .Event.Name }}`}}'
|
||||
- --status
|
||||
- '{{`{{ if .Event.Error }}failure{{ else }}success{{ end }}`}}'
|
||||
- --environment
|
||||
- '{{`{{ .Environment.Name }}`}}'
|
||||
- --namespace
|
||||
- '{{`{{ .Release.Namespace }}`}}'
|
||||
- --release
|
||||
- '{{`{{ .Release.Name }}`}}'
|
||||
```
|
||||
|
||||
For templating, imagine that you created a hook that generates a helm chart on-the-fly by running an external tool like ksonnet, kustomize, or your own template engine.
|
||||
It will allow you to write your helm releases with any language you like, while still leveraging goodies provided by helm.
|
||||
|
||||
### Global Hooks
|
||||
|
||||
In contrast to the per release hooks mentioned above these are run only once at the very beginning and end of the execution of a helmfile command and only the `prepare` and `cleanup` hooks are available respectively.
|
||||
|
||||
They use the same syntax as per release hooks, but at the top level of your helmfile:
|
||||
```yaml
|
||||
hooks:
|
||||
- events: ["prepare", "cleanup"]
|
||||
showlogs: true
|
||||
command: "echo"
|
||||
args: ["{{`{{.Environment.Name}}`}}", "{{`{{.HelmfileCommand}}`}}\
|
||||
"]
|
||||
```
|
||||
|
||||
### Helmfile + Kustomize
|
||||
|
||||
Do you prefer `kustomize` to write and organize your Kubernetes apps, but still want to leverage helm's useful features
|
||||
@@ -1044,9 +1256,9 @@ We also have dedicated documentation on the following topics which might interes
|
||||
|
||||
Or join our friendly slack community in the [`#helmfile`](https://slack.sweetops.com) channel to ask questions and get help. Check out our [slack archive](https://archive.sweetops.com/helmfile/) for good examples of how others are using it.
|
||||
|
||||
## Using env files
|
||||
## Using .env files
|
||||
|
||||
Helmfile itself doesn't have an ability to load env files. But you can write some bash script to achieve the goal:
|
||||
Helmfile itself doesn't have an ability to load .env files. But you can write some bash script to achieve the goal:
|
||||
|
||||
```console
|
||||
set -a; . .env; set +a; helmfile sync
|
||||
@@ -1054,7 +1266,7 @@ set -a; . .env; set +a; helmfile sync
|
||||
|
||||
Please see #203 for more context.
|
||||
|
||||
## Running helmfile interactively
|
||||
## Running Helmfile interactively
|
||||
|
||||
`helmfile --interactive [apply|destroy]` requests confirmation from you before actually modifying your cluster.
|
||||
|
||||
@@ -1068,14 +1280,79 @@ Once you download all required charts into your machine, you can run `helmfile c
|
||||
It basically run only `helm upgrade --install` with your already-downloaded charts, hence no Internet connection is required.
|
||||
See #155 for more information on this topic.
|
||||
|
||||
## Experimental features
|
||||
## Experimental Features
|
||||
|
||||
Some experimental features may be available for testing in perspective of being (or not) included in a future release.
|
||||
Those features are set using the environment variable `HELMFILE_EXPERIMENTAL`. Here is the current experimental feature :
|
||||
* `explicit-selector-inheritance` : remove today implicit cli selectors inheritance for composed helmfiles, see [composition selector](#selectors)
|
||||
|
||||
If you want to enable all experimental features set the env var to `HELMFILE_EXPERIMENTAL=true`
|
||||
|
||||
## Azure ACR integration
|
||||
## `bash` and `zsh` completion
|
||||
|
||||
Copy `autocomplete/helmfile_bash_autocomplete` or `autocomplete/helmfile_zsh_autocomplete` (depending on your shell of choice) to directory where you keep other shell completion scripts to make sure it is sourced.
|
||||
|
||||
## Examples
|
||||
|
||||
For more examples, see the [examples/README.md](https://github.com/roboll/helmfile/blob/master/examples/README.md) or the [`helmfile`](https://github.com/cloudposse/helmfiles/tree/master/releases) distribution by [Cloud Posse](https://github.com/cloudposse/).
|
||||
|
||||
## Integrations
|
||||
|
||||
- [renovate](https://github.com/renovatebot/renovate) automates chart version updates. See [this PR for more information](https://github.com/renovatebot/renovate/pull/5257).
|
||||
- For updating container image tags and git tags embedded within helmfile.yaml and values, you can use [renovate's regexManager](https://docs.renovatebot.com/modules/manager/regex/). Please see [this comment in the renovate repository](https://github.com/renovatebot/renovate/issues/6130#issuecomment-624061289) for more information.
|
||||
- [ArgoCD Integration](#argocd-integration)
|
||||
- [Azure ACR Integration](#azure-acr-integration)
|
||||
|
||||
### ArgoCD Integration
|
||||
|
||||
Use [ArgoCD](https://argoproj.github.io/argo-cd/) with `helmfile template` for GitOps.
|
||||
|
||||
ArgoCD has support for kustomize/manifests/helm chart by itself. Why bother with Helmfile?
|
||||
|
||||
The reasons may vary:
|
||||
|
||||
1. You do want to manage applications with ArgoCD, while letting Helmfile manage infrastructure-related components like Calico/Cilium/WeaveNet, Linkerd/Istio, and ArgoCD itself.
|
||||
- This way, any application deployed by ArgoCD has access to all the infrastructure.
|
||||
- Of course, you can use ArgoCD's [Sync Waves and Phases](https://argoproj.github.io/argo-cd/user-guide/sync-waves/) for ordering the infrastructure and application installations. But it may be difficult to separate the concern between the infrastructure and apps and annotate K8s resources consistently when you have different teams for managing infra and apps.
|
||||
2. You want to review the exact K8s manifests being applied on pull-request time, before ArgoCD syncs.
|
||||
- This is often better than using a kind of `HelmRelease` custom resources that obfuscates exactly what manifests are being applied, which makes reviewing harder.
|
||||
3. Use Helmfile as the single-pane of glass for all the K8s resources deployed to your cluster(s).
|
||||
- Helmfile can reduce repetition in K8s manifests across ArgoCD application
|
||||
|
||||
For 1, you run `helmfile apply` on CI to deploy ArgoCD and the infrastructure components.
|
||||
|
||||
> helmfile config for this phase often reside within the same directory as your Terraform project. So connecting the two with [terraform-provider-helmfile](https://github.com/mumoshu/terraform-provider-helmfile) may be helpful
|
||||
|
||||
For 2, another app-centric CI or bot should render/commit manifests by running:
|
||||
|
||||
```
|
||||
helmfile template --output-dir-template $(pwd)/gitops//{{.Release.Name}}
|
||||
cd gitops
|
||||
git add .
|
||||
git commit -m 'some message'
|
||||
git push origin $BRANCH
|
||||
```
|
||||
|
||||
> Note that `$(pwd)` is necessary when `hemlfile.yaml` has one or more sub-helmfiles in nested directories,
|
||||
> because setting a relative file path in `--output-dir` or `--output-dir-template` results in each sub-helmfile render
|
||||
> to the directory relative to the specified path.
|
||||
|
||||
so that they can be deployed by Argo CD as usual.
|
||||
|
||||
|
||||
The CI or bot can optionally submit a PR to be review by human, running:
|
||||
|
||||
```
|
||||
hub pull-request -b main -l gitops -m 'some description'
|
||||
```
|
||||
|
||||
Recommendations:
|
||||
|
||||
- Do create ArgoCD `Application` custom resource per Helm/Helmfile release, each point to respective sub-directory generated by `helmfile template --output-dir-template`
|
||||
- If you don't directly push it to the main Git branch and instead go through a pull-request, do lint rendered manifests on your CI, so that you can catch easy mistakes earlier/before ArgoCD finally deploys it
|
||||
- See [this ArgoCD issue](https://github.com/argoproj/argo-cd/issues/2143#issuecomment-570478329) for why you may want this, and see [this helmfile issue](https://github.com/roboll/helmfile/pull/1357) for how `--output-dir-template` works.
|
||||
|
||||
### Azure ACR Integration
|
||||
|
||||
Azure offers helm repository [support for Azure Container Registry](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-helm-repos) as a preview feature.
|
||||
|
||||
@@ -1085,17 +1362,44 @@ To use `helmfile` with ACR, on the other hand, you must either include a usernam
|
||||
|
||||
An ACR repository definition in `helmfile.yaml` looks like this:
|
||||
|
||||
```
|
||||
```yaml
|
||||
repositories:
|
||||
- name: <MyRegistry>
|
||||
url: https://<MyRegistry>.azurecr.io/helm/v1/repo
|
||||
```
|
||||
|
||||
## Examples
|
||||
## OCI Registries
|
||||
|
||||
For more examples, see the [examples/README.md](https://github.com/roboll/helmfile/blob/master/examples/README.md) or the [`helmfile`](https://github.com/cloudposse/helmfiles/tree/master/releases) distribution by [Cloud Posse](https://github.com/cloudposse/).
|
||||
In order to use OCI chart registries firstly they must be marked in the repository list as OCI enabled, e.g.
|
||||
|
||||
# Attribution
|
||||
```yaml
|
||||
repositories:
|
||||
- name: myOCIRegistry
|
||||
url: myregistry.azurecr.io
|
||||
oci: true
|
||||
```
|
||||
|
||||
It is important not to include a scheme for the URL as helm requires that these are not present for OCI registries
|
||||
|
||||
Secondly the credentials for the OCI registry can either be specified within `helmfile.yaml` similar to
|
||||
|
||||
```yaml
|
||||
repositories:
|
||||
- name: myOCIRegistry
|
||||
url: myregistry.azurecr.io
|
||||
oci: true
|
||||
username: spongebob
|
||||
password: squarepants
|
||||
```
|
||||
|
||||
or for CI scenarios these can be sourced from the environment with the format `<registryName>_USERNAME` and `<registryName_PASSWORD>`, e.g.
|
||||
|
||||
```shell
|
||||
export MYOCIREGISTRY_USERNAME=spongebob
|
||||
export MYOCIREGISTRY_PASSWORD=squarepants
|
||||
```
|
||||
|
||||
## Attribution
|
||||
|
||||
We use:
|
||||
|
||||
|
||||
@@ -11,7 +11,10 @@ information to this file.
|
||||
|---|---|---|---|---|
|
||||
| [reddit.com](https://www.reddit.com/) | production | [GitHub issue](https://github.com/roboll/helmfile/issues/96#issue-312012249), [Talk](https://www.slideshare.net/GregoryTaylor11/helm-at-reddit-from-local-dev-staging-to-production) | San Francisco, CA | April 2018 |
|
||||
| [ricardo.ch](https://www.ricardo.ch/) | production | We're deploying our complete application platform using Helmfile. | Zug, Switzerland | April 2018 |
|
||||
| [Hellofresh](https://engineering.hellofresh.com/) | production | We've been using helmfile in production since April 2018 for deploying all our infrastructure applications | Berlin, Germany | April 2018 |
|
||||
| [Cherre](https://cherre.com/) | production | We have no public posts about using Helmfile, but we have been using it for a long time now | New York, NY | October 2018 |
|
||||
| [Sight Machine](https://sightmachine.com/) | production | We don't have anything publicly posted about it, but have been using it for quite a while in production. | San Francisco, CA and Ann Arbor, MI | December 2018 |
|
||||
| [TailorMed](https://tailormed.co/) | production | We're deploying all of our platform and all K8s resources using Helmfile. The power of Helmfile are more then amazing. Thank you! | Tel-Aviv, Israel | September 2020 |
|
||||
| [VSHN – The DevOps Company](https://vshn.ch) | production | | Zurich, Switzerland | March 2019 |
|
||||
| [Vlocity](https://vlocity.com/) | proof-of-concept | | Melbourne, Australia | March 2019 |
|
||||
| [transit](https://transit.app/) | production | [Blog post](https://medium.com/@naseem_60378/helmfile-its-like-a-helm-for-your-helm-74a908581599). | Montreal, Canada | March 2019 |
|
||||
@@ -20,4 +23,11 @@ information to this file.
|
||||
| [kloeckner-i](https://www.kloeckner-i.com/) | production | We are deploying our standard tools via helmfile to all our clusters. | Berlin, Germany | September 2019 |
|
||||
| [American Express](https://www.americanexpress.com) | proof-of-concept | Orchestration of both internal cluster workloads and local developer environments. | London, GB | January 2020 |
|
||||
| [Sportradar](https://www.sportradar.com) | production | Since mid-2019, we've been deploying our core infrastructure and several application stacks with Helmfile. | St. Gallen, Switzerland | March 2020 |
|
||||
| [PedidosYa](https://www.pedidosya.com) | production | | Montevideo, Uruguay | June 2020 |
|
||||
| [Jenkins OSS](https://jenkins.io) | production | [jenkins-infra/charts](https://github.com/jenkins-infra/charts) | * | July 2020 |
|
||||
| [SettleMint](https://settlemint.com) | production | The SettleMint platform allows enterprises to spin up k8s clusters and deploy production grade blockchain networks and additional services. Helmfile is in charge of deploying these networks and services on demand out of the self service management ui. | Belgium, Singapore, UAE, India | October 2020 |
|
||||
| [AutoTrader (UK)](https://www.autotrader.co.uk) | production | We've used Helmfile for 2+ years to deploy 400+ services | UK | October 2020 |
|
||||
| [Trend Micro](https://www.trendmicro.com) | production | We manage 9 k8s clusters in a mono git repository with Helmfile | Taipei, Taiwan | December 2019 |
|
||||
| [William Hill](https://www.williamhill.com) | production | We have standardised on Helmfile for managing both our application services and our Kubernetes platform components. | International | March 2021 |
|
||||
| [The Hyve](https://www.thehyve.nl) | production | We've been using Helmfile since 2019 to deploy [Radar-base applications](https://github.com/RADAR-base/RADAR-Kubernetes). [Blog Post](https://www.thehyve.nl/articles/kubernetes-added-to-radar-base) | Utrecht, Netherlands | April 2021 |
|
||||
<!-- TABLE_END -->
|
||||
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "ubuntu/focal64"
|
||||
config.vm.hostname = "minikube.box"
|
||||
config.vm.provision :shell, privileged: false,
|
||||
inline: <<-EOS
|
||||
set -e
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y make docker.io
|
||||
sudo systemctl start docker
|
||||
sudo usermod -G docker $USER
|
||||
cd /vagrant/.circleci
|
||||
make all
|
||||
EOS
|
||||
|
||||
config.vm.provider "virtualbox" do |v|
|
||||
v.memory = 2048
|
||||
v.cpus = 2
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
#! /bin/bash
|
||||
|
||||
_helmfile_bash_autocomplete() {
|
||||
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
|
||||
local cur opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
|
||||
else
|
||||
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
|
||||
fi
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
complete -o bashdefault -o default -o nospace -F _helmfile_bash_autocomplete helmfile
|
||||
@@ -0,0 +1,23 @@
|
||||
#compdef helmfile
|
||||
|
||||
_helmfile_zsh_autocomplete() {
|
||||
|
||||
local -a opts
|
||||
local cur
|
||||
cur=${words[-1]}
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}")
|
||||
else
|
||||
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
|
||||
fi
|
||||
|
||||
if [[ "${opts[1]}" != "" ]]; then
|
||||
_describe 'values' opts
|
||||
else
|
||||
_files
|
||||
fi
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
compdef _helmfile_zsh_autocomplete helmfile
|
||||
+165
-3
@@ -1,6 +1,9 @@
|
||||
## Advanced Features
|
||||
|
||||
- [Import Configuration Parameters into Helmfile](#import-configuration-parameters-into-helmfile)
|
||||
- [Deploy Kustomization with Helmfile](#deploy-kustomizations-with-helmfile)
|
||||
- [Adhoc Kustomization of Helm Charts](#adhoc-kustomization-of-helm-charts)
|
||||
- [Adding dependencies without forking the chart](#adding-dependencies-without-forking-the-chart)
|
||||
|
||||
### Import Configuration Parameters into Helmfile
|
||||
|
||||
@@ -35,7 +38,7 @@ $ somehow_generate_chart_yaml ${TMPCHART}/Chart.yaml
|
||||
|
||||
$ TMPKUSTOMIZATION=/tmp/sometmpdir2
|
||||
$ somehow_generate_temp_kustomization_yaml ${TMPKUSTOMIZATION}/kustomization.yaml
|
||||
$ kustomize build ${TMPKUSTOMIZATION}/kustomization.yaml > ${TMPCHART}/templates/all.yaml
|
||||
$ kustomize build ${TMPKUSTOMIZATION}/kustomization.yaml > ${TMPCHART}/templates/all.yaml
|
||||
```
|
||||
|
||||
Let's say you have a `helmfile.yaml` that looks like the below:
|
||||
@@ -57,7 +60,7 @@ bases:
|
||||
|
||||
Followed by the below steps:
|
||||
|
||||
- Running `kustomize edit set image $IMAGE` for every `$IMAGE` generated from your values.yaml
|
||||
- Running `kustomize edit set image $IMAGE` for every `$IMAGE` generated from your values.yaml
|
||||
- Running `kustomize edit set nameprefix $NAMEPREFIX` with the nameprefix specified in your values.yaml
|
||||
- Running `kustomize edit set namesuffix $NAMESUFFIX` with the namesuffix specified in your values.yaml
|
||||
- Running `kustomize edit set namespace $NS` with the namespace specified in your values.yaml
|
||||
@@ -92,7 +95,17 @@ After all, Helmfile just installs the temporary chart like standard charts, whic
|
||||
|
||||
Please also see [test/advanced/helmfile.yaml](https://github.com/roboll/helmfile/tree/master/test/advanced/helmfile.yaml) for an example of kustomization support and more.
|
||||
|
||||
## Adhoc Customization of Helm charts
|
||||
### Adhoc Kustomization of Helm charts
|
||||
|
||||
With Helmfile's integration with Helmfile, not only deploying Kustomization as a Helm chart, you can kustomize charts before installation.
|
||||
|
||||
Currently, Helmfile allows you to set the following fields for kustomizing the chart:
|
||||
|
||||
- [`releases[].strategicMergePatches`](#strategicmergepatches)
|
||||
- `releases[].jsonPatches`
|
||||
- [`releases[].transformers`](#transformers)
|
||||
|
||||
#### `strategicMergePatches`
|
||||
|
||||
You can add/update any Kubernetes resource field rendered from a Helm chart by specifying `releases[].strategicMergePatches`:
|
||||
|
||||
@@ -135,3 +148,152 @@ Please note that the second `data` field `bar` is coming from the strategic-merg
|
||||
There's also `releases[].jsonPatches` that works similarly to `strategicMergePatches` but has additional capability to remove fields.
|
||||
|
||||
Please also see [test/advanced/helmfile.yaml](https://github.com/roboll/helmfile/tree/master/test/advanced/helmfile.yaml) for an example of patching support and more.
|
||||
|
||||
#### `transformers`
|
||||
|
||||
You can set `transformers` to apply [Kustomize's transformers](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/configureBuiltinPlugin.md#configuring-the-builtin-plugins-instead).
|
||||
|
||||
Each item can be a path to a YAML or go template file, or an embedded transformer declaration as a YAML hash.
|
||||
|
||||
It's often used to add common labels and annotations to your resources.
|
||||
|
||||
In the below example. we add common annotations and labels every resource rendered from the `aws-load-balancer-controller` chart:
|
||||
|
||||
```yaml
|
||||
releases:
|
||||
- name: "aws-load-balancer-controller"
|
||||
namespace: "kube-system"
|
||||
forceNamespace: "kube-system"
|
||||
chart: "center/aws/aws-load-balancer-controller"
|
||||
transformers:
|
||||
- apiVersion: builtin
|
||||
kind: AnnotationsTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
annotations:
|
||||
area: 51
|
||||
greeting: take me to your leader
|
||||
fieldSpecs:
|
||||
- path: metadata/annotations
|
||||
create: true
|
||||
- apiVersion: builtin
|
||||
kind: LabelTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
labels:
|
||||
foo: bar
|
||||
fieldSpecs:
|
||||
- path: metadata/labels
|
||||
create: true
|
||||
```
|
||||
|
||||
As explained earlier, `transformers` can be not only a list of embedded transformers, but also YAML or go template files, or a mix of those three kinds.
|
||||
|
||||
```yaml
|
||||
transformers:
|
||||
# Embedded transformer
|
||||
- apiVersion: builtin
|
||||
kind: AnnotationsTransformer
|
||||
metadata:
|
||||
name: notImportantHere
|
||||
annotations:
|
||||
area: 51
|
||||
greeting: take me to your leader
|
||||
fieldSpecs:
|
||||
- path: metadata/annotations
|
||||
create: true
|
||||
# YAML file
|
||||
- path/to/transformer.yaml
|
||||
# Go template
|
||||
# The same set of template parameters as release values files templates is available.
|
||||
- path/to/transformer.yaml.gotmpl
|
||||
```
|
||||
|
||||
Please see https://github.com/kubernetes-sigs/kustomize/blob/master/examples/configureBuiltinPlugin.md#configuring-the-builtin-plugins-instead for more information on how to declare transformers.
|
||||
|
||||
### Adding dependencies without forking the chart
|
||||
|
||||
With Helmfile, you can add chart dependencies to a Helm chart without forking it.
|
||||
|
||||
An example `helmfile.yaml` that adds a `stable/envoy` dependency to the release `foo` looks like the below:
|
||||
|
||||
```
|
||||
repositories:
|
||||
- name: stable
|
||||
url: https://charts.helm.sh/stable
|
||||
|
||||
releases:
|
||||
- name: foo
|
||||
chart: ./path/to/foo
|
||||
dependencies:
|
||||
- chart: stable/envoy
|
||||
version: 1.5
|
||||
```
|
||||
|
||||
When Helmfile encounters `releases[].dependencies`, it creates a another temporary chart from `./path/to/foo` and adds the following `dependencies` to the `Chart.yaml`, so that you don't need to fork the chart.
|
||||
|
||||
```
|
||||
dependencies:
|
||||
- name: envoy
|
||||
repo: https://charts.helm.sh/stable
|
||||
condition: envoy.enabled
|
||||
```
|
||||
|
||||
A Helm chart can have two or more dependencies for the same chart with different `alias`es. To give your dependency an `alias`, defien it like you would do in a standard `Chart.yaml`:
|
||||
|
||||
```
|
||||
repositories:
|
||||
- name: stable
|
||||
url: https://charts.helm.sh/stable
|
||||
|
||||
releases:
|
||||
- name: foo
|
||||
chart: ./path/to/foo
|
||||
dependencies:
|
||||
- chart: stable/envoy
|
||||
version: 1.5
|
||||
alias: bar
|
||||
- chart: stable/envoy
|
||||
version: 1.5
|
||||
alias: baz
|
||||
```
|
||||
|
||||
which will tweaks the temporary chart's `Chart.yaml` to have:
|
||||
|
||||
|
||||
```
|
||||
dependencies:
|
||||
- alias: bar
|
||||
name: envoy
|
||||
repo: https://charts.helm.sh/stable
|
||||
condition: bar.enabled
|
||||
- alias: baz
|
||||
name: envoy
|
||||
repo: https://charts.helm.sh/stable
|
||||
condition: baz.enabled
|
||||
```
|
||||
|
||||
Please see #649 for more context around this feature.
|
||||
|
||||
After the support for adhoc dependency to local chart (#1765),
|
||||
you can even write local file paths relative to `helmfile.yaml` in `chart`:
|
||||
|
||||
```
|
||||
releases:
|
||||
- name: foo
|
||||
chart: ./path/to/foo
|
||||
dependencies:
|
||||
- chart: ./path/to/bar
|
||||
```
|
||||
|
||||
Internally, Helmfile creates another temporary chart from the local chart `./path/to/foo`, and modifies the chart's `Chart.yaml` dependencies to look like:
|
||||
|
||||
```
|
||||
dependencies:
|
||||
- alias: bar
|
||||
name: bar
|
||||
repo: file:///abs/path/to/bar
|
||||
condition: bar.enabled
|
||||
```
|
||||
|
||||
Please read https://github.com/roboll/helmfile/issues/1762#issuecomment-816341251 for more details.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Secrets
|
||||
|
||||
helmfile can handle secrets using [helm-secrets](https://github.com/zendesk/helm-secrets) plugin or using remote secrets storage
|
||||
helmfile can handle secrets using [helm-secrets](https://github.com/jkroepke/helm-secrets) plugin or using remote secrets storage
|
||||
(everything that package [vals](https://github.com/variantdev/vals) can handle vault, AWS SSM etc)
|
||||
This section will describe the second use case.
|
||||
|
||||
|
||||
@@ -2,6 +2,23 @@
|
||||
|
||||
This guide covers the Helmfile’s considered patterns for writing advanced helmfiles. It focuses on how helmfile should be structured and executed.
|
||||
|
||||
## Helmfile .Values vs Helm .Values
|
||||
|
||||
Templating engine of Helmfile uses the same pipeline name `.Values` as Helm, so in some use-cases `.Values` of Helmfile and
|
||||
Helm can be seen in the same file. To distinguish these two kinds of `.Values`, Helmfile provides an alias `.StateValues`
|
||||
for its `.Values`.
|
||||
|
||||
```
|
||||
app:
|
||||
project: {{.Environmont.Name}}-{{.StateValues.project}} # Same as {{.Environmont.Name}}-{{.Values.project}}
|
||||
|
||||
{{`
|
||||
extraEnvVars:
|
||||
- name: APP_PROJECT
|
||||
value: {{.Values.app.project}}
|
||||
`}}
|
||||
```
|
||||
|
||||
## Missing keys and Default values
|
||||
|
||||
helmfile tries its best to inform users for noticing potential mistakes.
|
||||
@@ -14,10 +31,10 @@ That is, the following example let `helmfile` fail when you have no `eventApi.re
|
||||
{{ .Values.eventApi.replicas | default 1 }}
|
||||
```
|
||||
|
||||
In case it isn't a mistake and you do want to allow missing keys, use the `getOrNil` template function:
|
||||
In case it isn't a mistake and you do want to allow missing keys, use the `get` template function:
|
||||
|
||||
```
|
||||
{{ .Values | getOrNil "eventApi.replicas" }}
|
||||
{{ .Values | get "eventApi.replicas" nil }}
|
||||
```
|
||||
|
||||
This result in printing `<no value` in your template, that may or may not result in a failure.
|
||||
@@ -25,7 +42,7 @@ This result in printing `<no value` in your template, that may or may not result
|
||||
If you want a kind of default values that is used when a missing key was referenced, use `default` like:
|
||||
|
||||
```
|
||||
{{ .Values | getOrNil "eventApi.replicas" | default 1 }}
|
||||
{{ .Values | get "eventApi.replicas" 1 }}
|
||||
```
|
||||
|
||||
Now, you get `1` when there is no `eventApi.replicas` defined in environment values.
|
||||
@@ -91,7 +108,7 @@ releases:
|
||||
|
||||
Release Templating supports the following parts of release definition:
|
||||
- basic fields: `name`, `namespace`, `chart`, `version`
|
||||
- boolean fields: `installed`, `wait`, `tillerless`, `verify` by the means of additional text
|
||||
- boolean fields: `installed`, `wait`, `waitForJobs`, `tillerless`, `verify` by the means of additional text
|
||||
fields designed for templating only: `installedTemplate`, `waitTemplate`, `tillerlessTemplate`, `verifyTemplate`
|
||||
```yaml
|
||||
# ...
|
||||
@@ -135,13 +152,13 @@ Please note, that it is not possible to layer `values` sections. If `values` is
|
||||
|
||||
> See **Layering State Template Files** if you're layering templates.
|
||||
|
||||
You may occasionally end up with many helmfiles that shares common parts like which repositories to use, and whichi release to be bundled by default.
|
||||
You may occasionally end up with many helmfiles that shares common parts like which repositories to use, and which release to be bundled by default.
|
||||
|
||||
Use Layering to extract the common parts into a dedicated *library helmfile*s, so that each helmfile becomes DRY.
|
||||
|
||||
Let's assume that your `helmfile.yaml` looks like:
|
||||
|
||||
```
|
||||
```yaml
|
||||
bases:
|
||||
- environments.yaml
|
||||
|
||||
@@ -314,7 +331,7 @@ helmDefaults:
|
||||
force: true
|
||||
```
|
||||
|
||||
Each go template is rendered in the context where `.Values` is inherited from the previous part.
|
||||
Each go template is rendered in the context where `.Values` is inherited from the previous part.
|
||||
|
||||
So in `mydefaults.yaml.gotmpl`, both `.Values.kubeContext` and `.Values.wait` are valid as they do exist in the environment values inherited from the previous part(=the first part) of your `helmfile.yaml.gotmpl`, and therefore the template is rendered to:
|
||||
|
||||
@@ -356,3 +373,42 @@ releases:
|
||||
repository: "nginx"
|
||||
tag: "latest"
|
||||
```
|
||||
|
||||
## Re-using environment state in sub-helmfiles
|
||||
|
||||
Do you want to decouple the environment state loading from the sub-helmfiles and load it only once?
|
||||
|
||||
This example shows how to do this:
|
||||
|
||||
```yaml
|
||||
environments:
|
||||
stage:
|
||||
values:
|
||||
- env/stage.yaml
|
||||
prod:
|
||||
values:
|
||||
- env/prod.yaml
|
||||
---
|
||||
|
||||
helmfiles:
|
||||
- path: releases/myrelease/helmfile.yaml
|
||||
values:
|
||||
- {{ toYaml .Values | nindent 4 }}
|
||||
# pass the current state values to the sub-helmfile
|
||||
# add other values to use overlay logic here
|
||||
```
|
||||
|
||||
and `releases/myrelease/helmfile.yaml` is as DRY as
|
||||
|
||||
```yaml
|
||||
releases:
|
||||
- name: mychart-{{ .Values.myrelease.myname }}
|
||||
installed: {{ .Values | get "myrelease.enabled" false }}
|
||||
chart: mychart
|
||||
version: {{ .Values.myrelease.version }}
|
||||
labels:
|
||||
chart: mychart
|
||||
values:
|
||||
- values.yaml.gotmpl
|
||||
# templated values would also inherit the values passed from upstream
|
||||
```
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ You would then start a database migration job by executing:
|
||||
|
||||
```console
|
||||
# Start a database migration for the prod environment
|
||||
$ RAILS_ENV=prod helmfile sync --selector job=dbmigrator
|
||||
$ RAILS_ENV=prod helmfile --selector job=dbmigrator sync
|
||||
|
||||
# Tail log until you are satisfied
|
||||
$ kubectl logs -l job=dbmigrator
|
||||
|
||||
@@ -4,35 +4,40 @@ go 1.14
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go v35.0.0+incompatible // indirect
|
||||
github.com/Masterminds/goutils v1.1.0 // indirect
|
||||
github.com/Masterminds/semver v1.4.2
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||
github.com/Masterminds/semver/v3 v3.1.0
|
||||
github.com/Masterminds/sprig/v3 v3.1.0
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a
|
||||
github.com/go-test/deep v1.0.3
|
||||
github.com/golang/mock v1.4.3
|
||||
github.com/google/go-cmp v0.4.0
|
||||
github.com/gosuri/uitable v0.0.3
|
||||
github.com/hashicorp/go-getter v1.3.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/frankban/quicktest v1.11.2 // indirect
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
|
||||
github.com/go-test/deep v1.0.7
|
||||
github.com/golang/mock v1.5.0
|
||||
github.com/google/go-cmp v0.5.5
|
||||
github.com/gosuri/uitable v0.0.4
|
||||
github.com/hashicorp/go-getter v1.5.0
|
||||
github.com/hashicorp/go-retryablehttp v0.6.3 // indirect
|
||||
github.com/hashicorp/go-version v1.2.0
|
||||
github.com/hashicorp/go-version v1.2.1
|
||||
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c // indirect
|
||||
github.com/huandu/xstrings v1.2.0 // indirect
|
||||
github.com/imdario/mergo v0.3.9
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/jessevdk/go-flags v1.4.0
|
||||
github.com/kylelemons/godebug v1.1.0
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||
github.com/mattn/go-isatty v0.0.12
|
||||
github.com/pierrec/lz4 v2.3.0+incompatible // indirect
|
||||
github.com/r3labs/diff v0.0.0-20190801153147-a71de73c46ad
|
||||
github.com/r3labs/diff v1.1.0
|
||||
github.com/spf13/cobra v1.1.1
|
||||
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
|
||||
github.com/urfave/cli v1.20.0
|
||||
github.com/variantdev/chartify v0.3.7
|
||||
github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363
|
||||
github.com/variantdev/vals v0.4.1-0.20200501114609-9cebe482281c
|
||||
go.mozilla.org/sops v0.0.0-20190912205235-14a22d7a7060 // indirect
|
||||
go.uber.org/multierr v1.1.0
|
||||
go.uber.org/zap v1.9.1
|
||||
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf // indirect
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
|
||||
github.com/urfave/cli v1.22.5
|
||||
github.com/variantdev/chartify v0.8.9
|
||||
github.com/variantdev/dag v1.0.0
|
||||
github.com/variantdev/vals v0.14.0
|
||||
go.uber.org/multierr v1.6.0
|
||||
go.uber.org/zap v1.16.0
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
gopkg.in/square/go-jose.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
gotest.tools v2.2.0+incompatible
|
||||
gotest.tools/v3 v3.0.3-0.20200410202438-4e4a41b7851a
|
||||
gotest.tools/v3 v3.0.3
|
||||
k8s.io/apimachinery v0.19.3
|
||||
)
|
||||
|
||||
@@ -5,9 +5,8 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/app/version"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/app"
|
||||
"github.com/roboll/helmfile/pkg/app/version"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/maputil"
|
||||
"github.com/roboll/helmfile/pkg/state"
|
||||
@@ -42,6 +41,7 @@ func main() {
|
||||
cliApp.Name = "helmfile"
|
||||
cliApp.Usage = ""
|
||||
cliApp.Version = version.Version
|
||||
cliApp.EnableBashCompletion = true
|
||||
cliApp.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "helm-binary, b",
|
||||
@@ -54,7 +54,7 @@ func main() {
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "environment, e",
|
||||
Usage: "specify the environment name. defaults to `default`",
|
||||
Usage: `specify the environment name. defaults to "default"`,
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "state-values-set",
|
||||
@@ -88,6 +88,10 @@ func main() {
|
||||
Name: "namespace, n",
|
||||
Usage: "Set namespace. Uses the namespace set in the context by default, and is available in templates as {{ .Namespace }}",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "chart, c",
|
||||
Usage: "Set chart. Uses the chart set in release by default, and is available in template as {{ .Chart }}",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "selector, l",
|
||||
Usage: `Only run using the releases that match labels. Labels can take the form of foo=bar or foo!=bar.
|
||||
@@ -109,7 +113,7 @@ func main() {
|
||||
cliApp.Commands = []cli.Command{
|
||||
{
|
||||
Name: "deps",
|
||||
Usage: "update charts based on the contents of requirements.yaml",
|
||||
Usage: "update charts based on their requirements",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
@@ -118,11 +122,11 @@ func main() {
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-repos",
|
||||
Usage: "skip running `helm repo update` before running `helm dependency build`",
|
||||
Usage: `skip running "helm repo update" before running "helm dependency build"`,
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Deps(c)
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.Deps(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -135,8 +139,8 @@ func main() {
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Repos(c)
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.Repos(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -162,8 +166,8 @@ func main() {
|
||||
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.DeprecatedSyncCharts(c)
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.DeprecatedSyncCharts(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -185,7 +189,7 @@ func main() {
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: "skip running `helm repo update` and `helm dependency build`",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "detailed-exitcode",
|
||||
@@ -195,10 +199,26 @@ func main() {
|
||||
Name: "include-tests",
|
||||
Usage: "enable the diffing of the helm test hooks",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "skip-needs",
|
||||
Usage: `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided. Defaults to true when --include-needs is not provided`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "include-needs",
|
||||
Usage: `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-diff-on-install",
|
||||
Usage: "Skips running helm-diff on releases being newly installed on this apply. Useful when the release manifests are too huge to be reviewed, or it's too time-consuming to diff at all",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "suppress-secrets",
|
||||
Usage: "suppress secrets in the output. highly recommended to specify on CI/CD use-cases",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "show-secrets",
|
||||
Usage: "do not redact secret values in the output. should be used for debug purpose only",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
@@ -209,9 +229,14 @@ func main() {
|
||||
Value: 0,
|
||||
Usage: "output NUM lines of context around changes",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "output",
|
||||
Value: "",
|
||||
Usage: "output format for diff plugin",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Diff(c)
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.Diff(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -235,6 +260,10 @@ func main() {
|
||||
Name: "output-dir",
|
||||
Usage: "output directory to pass to helm template (helm template --output-dir)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "output-dir-template",
|
||||
Usage: "go text template for generating the output directory. Default: {{ .OutputDir }}/{{ .State.BaseName }}-{{ .State.AbsPathSHA1 }}-{{ .Release.Name}}",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
@@ -244,13 +273,55 @@ func main() {
|
||||
Name: "validate",
|
||||
Usage: "validate your manifests against the Kubernetes cluster you are currently pointing at",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "include-crds",
|
||||
Usage: "include CRDs in the templated output",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "include-needs",
|
||||
Usage: `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: "skip running `helm repo update` and `helm dependency build`",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-cleanup",
|
||||
Usage: "Stop cleaning up temporary values generated by helmfile and helm-secrets. Useful for debugging. Don't use in production for security",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Template(c)
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.Template(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "write-values",
|
||||
Usage: "write values files for releases. Similar to `helmfile template`, write values files instead of manifests.",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringSliceFlag{
|
||||
Name: "set",
|
||||
Usage: "additional values to be merged into the command",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "values",
|
||||
Usage: "additional value files to be merged into the command",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "output-file-template",
|
||||
Usage: "go text template for generating the output file. Default: {{ .State.BaseName }}-{{ .State.AbsPathSHA1 }}/{{ .Release.Name}}.yaml",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent downloads of release charts",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
},
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.WriteValues(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -277,11 +348,33 @@ func main() {
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: "skip running `helm repo update` and `helm dependency build`",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Lint(c)
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.Lint(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "fetch",
|
||||
Usage: "fetch charts from state file",
|
||||
Flags: []cli.Flag{
|
||||
cli.IntFlag{
|
||||
Name: "concurrency",
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent downloads of release charts",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "output-dir",
|
||||
Usage: "directory to store charts (default: temporary directory which is deleted when the command terminates)",
|
||||
},
|
||||
},
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.Fetch(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -306,13 +399,33 @@ func main() {
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-crds",
|
||||
Usage: "if set, no CRDs will be installed on sync. By default, CRDs are installed if not already present",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: "skip running `helm repo update` and `helm dependency build`",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "skip-needs",
|
||||
Usage: `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided. Defaults to true when --include-needs is not provided`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "include-needs",
|
||||
Usage: `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "wait",
|
||||
Usage: `Override helmDefaults.wait setting "helm upgrade --install --wait"`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "wait-for-jobs",
|
||||
Usage: `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`,
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Sync(c)
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.Sync(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -337,6 +450,11 @@ func main() {
|
||||
Value: 0,
|
||||
Usage: "output NUM lines of context around changes",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "output",
|
||||
Value: "",
|
||||
Usage: "output format for diff plugin",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "detailed-exitcode",
|
||||
Usage: "return a non-zero exit code 2 instead of 0 when there were changes detected AND the changes are synced successfully",
|
||||
@@ -348,7 +466,27 @@ func main() {
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "retain-values-files",
|
||||
Usage: "Stop cleaning up values files passed to Helm. Together with --log-level=debug, you can manually rerun helm commands as Helmfile did for debugging purpose",
|
||||
Usage: "DEPRECATED: Use skip-cleanup instead",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-cleanup",
|
||||
Usage: "Stop cleaning up temporary values generated by helmfile and helm-secrets. Useful for debugging. Don't use in production for security",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-crds",
|
||||
Usage: "if set, no CRDs will be installed on sync. By default, CRDs are installed if not already present",
|
||||
},
|
||||
cli.BoolTFlag{
|
||||
Name: "skip-needs",
|
||||
Usage: `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided. Defaults to true when --include-needs is not provided`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "include-needs",
|
||||
Usage: `automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-diff-on-install",
|
||||
Usage: "Skips running helm-diff on releases being newly installed on this apply. Useful when the release manifests are too huge to be reviewed, or it's too time-consuming to diff at all",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "include-tests",
|
||||
@@ -358,17 +496,29 @@ func main() {
|
||||
Name: "suppress-secrets",
|
||||
Usage: "suppress secrets in the diff output. highly recommended to specify on CI/CD use-cases",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "show-secrets",
|
||||
Usage: "do not redact secret values in the diff output. should be used for debug purpose only",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "suppress-diff",
|
||||
Usage: "suppress diff in the output. Usable in new installs",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: "skip running `helm repo update` and `helm dependency build`",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "wait",
|
||||
Usage: `Override helmDefaults.wait setting "helm upgrade --install --wait"`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "wait-for-jobs",
|
||||
Usage: `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`,
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Apply(c)
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.Apply(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -386,8 +536,8 @@ func main() {
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Status(c)
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.Status(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -408,9 +558,13 @@ func main() {
|
||||
Name: "purge",
|
||||
Usage: "purge releases i.e. free release names and histories",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Delete(c)
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.Delete(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -427,9 +581,13 @@ func main() {
|
||||
Value: "",
|
||||
Usage: "pass args to helm exec",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Destroy(c)
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.Destroy(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -440,6 +598,10 @@ func main() {
|
||||
Name: "cleanup",
|
||||
Usage: "delete test pods upon completion",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "logs",
|
||||
Usage: "Dump the logs from test pods (this runs after all tests are complete, but before any cleanup)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "args",
|
||||
Value: "",
|
||||
@@ -455,17 +617,26 @@ func main() {
|
||||
Value: 0,
|
||||
Usage: "maximum number of concurrent helm processes to run, 0 is unlimited",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-deps",
|
||||
Usage: `skip running "helm repo update" and "helm dependency build"`,
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.Test(c)
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.Test(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "build",
|
||||
Usage: "output compiled helmfile state(s) as YAML",
|
||||
Flags: []cli.Flag{},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.PrintState(c)
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "embed-values",
|
||||
Usage: "Read all the values files for every release and embed into the output helmfile.yaml",
|
||||
},
|
||||
},
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.PrintState(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -477,11 +648,24 @@ func main() {
|
||||
Value: "",
|
||||
Usage: "output releases list as a json string",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "keep-temp-dir",
|
||||
Usage: "Keep temporary directory",
|
||||
},
|
||||
},
|
||||
Action: action(func(run *app.App, c configImpl) error {
|
||||
return run.ListReleases(c)
|
||||
Action: action(func(a *app.App, c configImpl) error {
|
||||
return a.ListReleases(c)
|
||||
}),
|
||||
},
|
||||
{
|
||||
Name: "version",
|
||||
Usage: "Show the version for Helmfile.",
|
||||
ArgsUsage: "[command]",
|
||||
Action: func(c *cli.Context) error {
|
||||
cli.ShowVersion(c)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := cliApp.Run(os.Args)
|
||||
@@ -534,6 +718,14 @@ func (c configImpl) SkipRepos() bool {
|
||||
return c.c.Bool("skip-repos")
|
||||
}
|
||||
|
||||
func (c configImpl) Wait() bool {
|
||||
return c.c.Bool("wait")
|
||||
}
|
||||
|
||||
func (c configImpl) WaitForJobs() bool {
|
||||
return c.c.Bool("wait-for-jobs")
|
||||
}
|
||||
|
||||
func (c configImpl) Values() []string {
|
||||
return c.c.StringSlice("values")
|
||||
}
|
||||
@@ -552,6 +744,14 @@ func (c configImpl) OutputDir() string {
|
||||
return c.c.String("output-dir")
|
||||
}
|
||||
|
||||
func (c configImpl) OutputDirTemplate() string {
|
||||
return c.c.String("output-dir-template")
|
||||
}
|
||||
|
||||
func (c configImpl) OutputFileTemplate() string {
|
||||
return c.c.String("output-file-template")
|
||||
}
|
||||
|
||||
func (c configImpl) Validate() bool {
|
||||
return c.c.Bool("validate")
|
||||
}
|
||||
@@ -564,6 +764,18 @@ func (c configImpl) HasCommandName(name string) bool {
|
||||
return c.c.Command.HasName(name)
|
||||
}
|
||||
|
||||
func (c configImpl) SkipNeeds() bool {
|
||||
if !c.IncludeNeeds() {
|
||||
return c.c.Bool("skip-needs")
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c configImpl) IncludeNeeds() bool {
|
||||
return c.c.Bool("include-needs")
|
||||
}
|
||||
|
||||
// DiffConfig
|
||||
|
||||
func (c configImpl) SkipDeps() bool {
|
||||
@@ -586,6 +798,10 @@ func (c configImpl) SuppressSecrets() bool {
|
||||
return c.c.Bool("suppress-secrets")
|
||||
}
|
||||
|
||||
func (c configImpl) ShowSecrets() bool {
|
||||
return c.c.Bool("show-secrets")
|
||||
}
|
||||
|
||||
func (c configImpl) SuppressDiff() bool {
|
||||
return c.c.Bool("suppress-diff")
|
||||
}
|
||||
@@ -602,6 +818,10 @@ func (c configImpl) Cleanup() bool {
|
||||
return c.c.Bool("cleanup")
|
||||
}
|
||||
|
||||
func (c configImpl) Logs() bool {
|
||||
return c.c.Bool("logs")
|
||||
}
|
||||
|
||||
func (c configImpl) Timeout() int {
|
||||
if !c.c.IsSet("timeout") {
|
||||
return state.EmptyTimeout
|
||||
@@ -615,6 +835,10 @@ func (c configImpl) Output() string {
|
||||
return c.c.String("output")
|
||||
}
|
||||
|
||||
func (c configImpl) KeepTempDir() bool {
|
||||
return c.c.Bool("keep-temp-dir")
|
||||
}
|
||||
|
||||
// GlobalConfig
|
||||
|
||||
func (c configImpl) HelmBinary() string {
|
||||
@@ -629,6 +853,10 @@ func (c configImpl) Namespace() string {
|
||||
return c.c.GlobalString("namespace")
|
||||
}
|
||||
|
||||
func (c configImpl) Chart() string {
|
||||
return c.c.GlobalString("chart")
|
||||
}
|
||||
|
||||
func (c configImpl) FileOrDir() string {
|
||||
return c.c.GlobalString("file")
|
||||
}
|
||||
@@ -657,6 +885,30 @@ func (c configImpl) Context() int {
|
||||
return c.c.Int("context")
|
||||
}
|
||||
|
||||
func (c configImpl) DiffOutput() string {
|
||||
return c.c.String("output")
|
||||
}
|
||||
|
||||
func (c configImpl) SkipCleanup() bool {
|
||||
return c.c.Bool("skip-cleanup")
|
||||
}
|
||||
|
||||
func (c configImpl) SkipCRDs() bool {
|
||||
return c.c.Bool("skip-crds")
|
||||
}
|
||||
|
||||
func (c configImpl) SkipDiffOnInstall() bool {
|
||||
return c.c.Bool("skip-diff-on-install")
|
||||
}
|
||||
|
||||
func (c configImpl) EmbedValues() bool {
|
||||
return c.c.Bool("embed-values")
|
||||
}
|
||||
|
||||
func (c configImpl) IncludeCRDs() bool {
|
||||
return c.c.Bool("include-crds")
|
||||
}
|
||||
|
||||
func (c configImpl) Logger() *zap.SugaredLogger {
|
||||
return c.c.App.Metadata["logger"].(*zap.SugaredLogger)
|
||||
}
|
||||
@@ -664,7 +916,10 @@ func (c configImpl) Logger() *zap.SugaredLogger {
|
||||
func (c configImpl) Env() string {
|
||||
env := c.c.GlobalString("environment")
|
||||
if env == "" {
|
||||
env = state.DefaultEnv
|
||||
env = os.Getenv("HELMFILE_ENVIRONMENT")
|
||||
if env == "" {
|
||||
env = state.DefaultEnv
|
||||
}
|
||||
}
|
||||
return env
|
||||
}
|
||||
@@ -695,6 +950,8 @@ func toCliError(c *cli.Context, err error) error {
|
||||
noMatchingExitCode = 0
|
||||
}
|
||||
return cli.NewExitError(e.Error(), noMatchingExitCode)
|
||||
case *app.MultiError:
|
||||
return cli.NewExitError(e.Error(), 1)
|
||||
case *app.Error:
|
||||
return cli.NewExitError(e.Error(), e.Code())
|
||||
default:
|
||||
|
||||
+801
-201
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,790 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/roboll/helmfile/pkg/exectest"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/testhelper"
|
||||
"github.com/variantdev/vals"
|
||||
)
|
||||
|
||||
func TestSync(t *testing.T) {
|
||||
type fields struct {
|
||||
skipNeeds bool
|
||||
includeNeeds bool
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
fields fields
|
||||
ns string
|
||||
concurrency int
|
||||
skipDiffOnInstall bool
|
||||
error string
|
||||
files map[string]string
|
||||
selectors []string
|
||||
lists map[exectest.ListKey]string
|
||||
upgraded []exectest.Release
|
||||
deleted []exectest.Release
|
||||
log string
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
wantUpgrades := tc.upgraded
|
||||
wantDeletes := tc.deleted
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
Lists: tc.lists,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
}
|
||||
|
||||
bs := &bytes.Buffer{}
|
||||
|
||||
func() {
|
||||
t.Helper()
|
||||
|
||||
logReader, logWriter := io.Pipe()
|
||||
|
||||
logFlushed := &sync.WaitGroup{}
|
||||
// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
|
||||
logFlushed.Add(1)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(logReader)
|
||||
for scanner.Scan() {
|
||||
bs.Write(scanner.Bytes())
|
||||
bs.WriteString("\n")
|
||||
}
|
||||
logFlushed.Done()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
// This is here to avoid data-trace on bytes buffer `bs` to capture logs
|
||||
if err := logWriter.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logFlushed.Wait()
|
||||
}()
|
||||
|
||||
logger := helmexec.NewLogger(logWriter, "debug")
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
glob: filepath.Glob,
|
||||
abs: filepath.Abs,
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, tc.files)
|
||||
|
||||
if tc.ns != "" {
|
||||
app.Namespace = tc.ns
|
||||
}
|
||||
|
||||
if tc.selectors != nil {
|
||||
app.Selectors = tc.selectors
|
||||
}
|
||||
|
||||
syncErr := app.Sync(applyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: tc.concurrency,
|
||||
logger: logger,
|
||||
skipDiffOnInstall: tc.skipDiffOnInstall,
|
||||
skipNeeds: tc.fields.skipNeeds,
|
||||
includeNeeds: tc.fields.includeNeeds,
|
||||
})
|
||||
|
||||
var gotErr string
|
||||
if syncErr != nil {
|
||||
gotErr = syncErr.Error()
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.error, gotErr); d != "" {
|
||||
t.Fatalf("unexpected error: want (-), got (+): %s", d)
|
||||
}
|
||||
|
||||
if len(wantUpgrades) > len(helm.Releases) {
|
||||
t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades))
|
||||
}
|
||||
|
||||
for relIdx := range wantUpgrades {
|
||||
if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantUpgrades[relIdx].Flags {
|
||||
if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(wantDeletes) > len(helm.Deleted) {
|
||||
t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes))
|
||||
}
|
||||
|
||||
for relIdx := range wantDeletes {
|
||||
if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantDeletes[relIdx].Flags {
|
||||
if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if tc.log != "" {
|
||||
actual := bs.String()
|
||||
|
||||
diff, exists := testhelper.Diff(tc.log, actual, 3)
|
||||
if exists {
|
||||
t.Errorf("unexpected log:\nDIFF\n%s\nEOD", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("skip-needs=true", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: true,
|
||||
},
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "external-secrets", Flags: []string{"--kube-context", "default", "--namespace", "default"}},
|
||||
{Name: "my-release", Flags: []string{"--kube-context", "default", "--namespace", "default"}},
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) matching app=test found in helmfile.yaml
|
||||
|
||||
Affected releases are:
|
||||
external-secrets (incubator/raw) UPDATED
|
||||
my-release (incubator/raw) UPDATED
|
||||
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default/default/external-secrets
|
||||
2 default/default/my-release
|
||||
|
||||
processing releases in group 1/2: default/default/external-secrets
|
||||
getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
|
||||
processing releases in group 2/2: default/default/my-release
|
||||
getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
|
||||
|
||||
UPDATED RELEASES:
|
||||
NAME CHART VERSION
|
||||
external-secrets incubator/raw
|
||||
my-release incubator/raw
|
||||
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=false include-needs=true", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) matching app=test found in helmfile.yaml
|
||||
|
||||
Affected releases are:
|
||||
external-secrets (incubator/raw) UPDATED
|
||||
kubernetes-external-secrets (incubator/raw) UPDATED
|
||||
my-release (incubator/raw) UPDATED
|
||||
|
||||
processing 3 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default/kube-system/kubernetes-external-secrets
|
||||
2 default/default/external-secrets
|
||||
3 default/default/my-release
|
||||
|
||||
processing releases in group 1/3: default/kube-system/kubernetes-external-secrets
|
||||
getting deployed release version failed:unexpected list key: {^kubernetes-external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
|
||||
processing releases in group 2/3: default/default/external-secrets
|
||||
getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
|
||||
processing releases in group 3/3: default/default/my-release
|
||||
getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
|
||||
|
||||
UPDATED RELEASES:
|
||||
NAME CHART VERSION
|
||||
kubernetes-external-secrets incubator/raw
|
||||
external-secrets incubator/raw
|
||||
my-release incubator/raw
|
||||
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=false include-needs=true with installed but disabled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
installed: false
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
// delete frontend-v1 and backend-v1
|
||||
exectest.ListKey{Filter: "^kubernetes-external-secrets$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
^kubernetes-external-secrets$ 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7: installed: false
|
||||
8:
|
||||
9: - name: external-secrets
|
||||
10: chart: incubator/raw
|
||||
11: namespace: default
|
||||
12: labels:
|
||||
13: app: test
|
||||
14: needs:
|
||||
15: - kube-system/kubernetes-external-secrets
|
||||
16:
|
||||
17: - name: my-release
|
||||
18: chart: incubator/raw
|
||||
19: namespace: default
|
||||
20: labels:
|
||||
21: app: test
|
||||
22: needs:
|
||||
23: - default/external-secrets
|
||||
24:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7: installed: false
|
||||
8:
|
||||
9: - name: external-secrets
|
||||
10: chart: incubator/raw
|
||||
11: namespace: default
|
||||
12: labels:
|
||||
13: app: test
|
||||
14: needs:
|
||||
15: - kube-system/kubernetes-external-secrets
|
||||
16:
|
||||
17: - name: my-release
|
||||
18: chart: incubator/raw
|
||||
19: namespace: default
|
||||
20: labels:
|
||||
21: app: test
|
||||
22: needs:
|
||||
23: - default/external-secrets
|
||||
24:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) matching app=test found in helmfile.yaml
|
||||
|
||||
Affected releases are:
|
||||
external-secrets (incubator/raw) UPDATED
|
||||
kubernetes-external-secrets (incubator/raw) DELETED
|
||||
my-release (incubator/raw) UPDATED
|
||||
|
||||
processing 1 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default/kube-system/kubernetes-external-secrets
|
||||
|
||||
processing releases in group 1/1: default/kube-system/kubernetes-external-secrets
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default/default/external-secrets
|
||||
2 default/default/my-release
|
||||
|
||||
processing releases in group 1/2: default/default/external-secrets
|
||||
getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
|
||||
processing releases in group 2/2: default/default/my-release
|
||||
getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
|
||||
|
||||
UPDATED RELEASES:
|
||||
NAME CHART VERSION
|
||||
external-secrets incubator/raw
|
||||
my-release incubator/raw
|
||||
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
kubernetes-external-secrets
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("skip-needs=false include-needs=true with not installed and disabled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
fields: fields{
|
||||
skipNeeds: false,
|
||||
includeNeeds: true,
|
||||
},
|
||||
error: ``,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
installed: false
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
upgraded: []exectest.Release{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
// delete frontend-v1 and backend-v1
|
||||
exectest.ListKey{Filter: "^kubernetes-external-secrets$", Flags: helmV2ListFlags}: ``,
|
||||
},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7: installed: false
|
||||
8:
|
||||
9: - name: external-secrets
|
||||
10: chart: incubator/raw
|
||||
11: namespace: default
|
||||
12: labels:
|
||||
13: app: test
|
||||
14: needs:
|
||||
15: - kube-system/kubernetes-external-secrets
|
||||
16:
|
||||
17: - name: my-release
|
||||
18: chart: incubator/raw
|
||||
19: namespace: default
|
||||
20: labels:
|
||||
21: app: test
|
||||
22: needs:
|
||||
23: - default/external-secrets
|
||||
24:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7: installed: false
|
||||
8:
|
||||
9: - name: external-secrets
|
||||
10: chart: incubator/raw
|
||||
11: namespace: default
|
||||
12: labels:
|
||||
13: app: test
|
||||
14: needs:
|
||||
15: - kube-system/kubernetes-external-secrets
|
||||
16:
|
||||
17: - name: my-release
|
||||
18: chart: incubator/raw
|
||||
19: namespace: default
|
||||
20: labels:
|
||||
21: app: test
|
||||
22: needs:
|
||||
23: - default/external-secrets
|
||||
24:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) matching app=test found in helmfile.yaml
|
||||
|
||||
Affected releases are:
|
||||
external-secrets (incubator/raw) UPDATED
|
||||
my-release (incubator/raw) UPDATED
|
||||
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default/default/external-secrets
|
||||
2 default/default/my-release
|
||||
|
||||
processing releases in group 1/2: default/default/external-secrets
|
||||
getting deployed release version failed:unexpected list key: {^external-secrets$ --kube-contextdefault--deleting--deployed--failed--pending}
|
||||
processing releases in group 2/2: default/default/my-release
|
||||
getting deployed release version failed:unexpected list key: {^my-release$ --kube-contextdefault--deleting--deployed--failed--pending}
|
||||
|
||||
UPDATED RELEASES:
|
||||
NAME CHART VERSION
|
||||
external-secrets incubator/raw
|
||||
my-release incubator/raw
|
||||
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("bad --selector", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test_non_existent"},
|
||||
upgraded: []exectest.Release{},
|
||||
error: "err: no releases found that matches specified selector(app=test_non_existent) and environment(default), in any helmfile",
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
0 release(s) matching app=test_non_existent found in helmfile.yaml
|
||||
|
||||
`,
|
||||
})
|
||||
})
|
||||
}
|
||||
+1023
-319
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ type ConfigProvider interface {
|
||||
FileOrDir() string
|
||||
KubeContext() string
|
||||
Namespace() string
|
||||
Chart() string
|
||||
Selectors() []string
|
||||
StateValuesSet() map[string]interface{}
|
||||
StateValuesFiles() []string
|
||||
@@ -38,19 +39,29 @@ type ApplyConfigProvider interface {
|
||||
|
||||
Values() []string
|
||||
Set() []string
|
||||
SkipCRDs() bool
|
||||
SkipDeps() bool
|
||||
Wait() bool
|
||||
WaitForJobs() bool
|
||||
|
||||
IncludeTests() bool
|
||||
|
||||
SuppressSecrets() bool
|
||||
ShowSecrets() bool
|
||||
SuppressDiff() bool
|
||||
|
||||
DetailedExitcode() bool
|
||||
|
||||
NoColor() bool
|
||||
Context() int
|
||||
DiffOutput() string
|
||||
|
||||
RetainValuesFiles() bool
|
||||
SkipCleanup() bool
|
||||
SkipDiffOnInstall() bool
|
||||
|
||||
SkipNeeds() bool
|
||||
IncludeNeeds() bool
|
||||
|
||||
concurrencyConfig
|
||||
interactive
|
||||
@@ -62,7 +73,13 @@ type SyncConfigProvider interface {
|
||||
|
||||
Values() []string
|
||||
Set() []string
|
||||
SkipCRDs() bool
|
||||
SkipDeps() bool
|
||||
Wait() bool
|
||||
WaitForJobs() bool
|
||||
|
||||
SkipNeeds() bool
|
||||
IncludeNeeds() bool
|
||||
|
||||
concurrencyConfig
|
||||
loggingConfig
|
||||
@@ -73,16 +90,23 @@ type DiffConfigProvider interface {
|
||||
|
||||
Values() []string
|
||||
Set() []string
|
||||
SkipCRDs() bool
|
||||
SkipDeps() bool
|
||||
|
||||
IncludeTests() bool
|
||||
|
||||
SuppressSecrets() bool
|
||||
ShowSecrets() bool
|
||||
SuppressDiff() bool
|
||||
SkipDiffOnInstall() bool
|
||||
|
||||
SkipNeeds() bool
|
||||
IncludeNeeds() bool
|
||||
|
||||
DetailedExitcode() bool
|
||||
NoColor() bool
|
||||
Context() int
|
||||
DiffOutput() string
|
||||
|
||||
concurrencyConfig
|
||||
}
|
||||
@@ -91,6 +115,7 @@ type DeleteConfigProvider interface {
|
||||
Args() string
|
||||
|
||||
Purge() bool
|
||||
SkipDeps() bool
|
||||
|
||||
interactive
|
||||
loggingConfig
|
||||
@@ -100,6 +125,8 @@ type DeleteConfigProvider interface {
|
||||
type DestroyConfigProvider interface {
|
||||
Args() string
|
||||
|
||||
SkipDeps() bool
|
||||
|
||||
interactive
|
||||
loggingConfig
|
||||
concurrencyConfig
|
||||
@@ -108,8 +135,10 @@ type DestroyConfigProvider interface {
|
||||
type TestConfigProvider interface {
|
||||
Args() string
|
||||
|
||||
SkipDeps() bool
|
||||
Timeout() int
|
||||
Cleanup() bool
|
||||
Logs() bool
|
||||
|
||||
concurrencyConfig
|
||||
}
|
||||
@@ -124,18 +153,36 @@ type LintConfigProvider interface {
|
||||
concurrencyConfig
|
||||
}
|
||||
|
||||
type FetchConfigProvider interface {
|
||||
SkipDeps() bool
|
||||
OutputDir() string
|
||||
|
||||
concurrencyConfig
|
||||
}
|
||||
|
||||
type TemplateConfigProvider interface {
|
||||
Args() string
|
||||
|
||||
Values() []string
|
||||
Set() []string
|
||||
OutputDirTemplate() string
|
||||
Validate() bool
|
||||
SkipDeps() bool
|
||||
SkipCleanup() bool
|
||||
OutputDir() string
|
||||
IncludeCRDs() bool
|
||||
IncludeNeeds() bool
|
||||
|
||||
concurrencyConfig
|
||||
}
|
||||
|
||||
type WriteValuesConfigProvider interface {
|
||||
Values() []string
|
||||
Set() []string
|
||||
OutputFileTemplate() string
|
||||
SkipDeps() bool
|
||||
}
|
||||
|
||||
type StatusesConfigProvider interface {
|
||||
Args() string
|
||||
|
||||
@@ -143,6 +190,7 @@ type StatusesConfigProvider interface {
|
||||
}
|
||||
|
||||
type StateConfigProvider interface {
|
||||
EmbedValues() bool
|
||||
}
|
||||
|
||||
type concurrencyConfig interface {
|
||||
|
||||
+16
-24
@@ -5,40 +5,32 @@ import (
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
updatedRepos map[string]struct{}
|
||||
updatedRepos map[string]bool
|
||||
updatedReposV2 map[string]bool
|
||||
}
|
||||
|
||||
func NewContext() Context {
|
||||
return Context{
|
||||
updatedRepos: map[string]struct{}{},
|
||||
updatedRepos: map[string]bool{},
|
||||
updatedReposV2: map[string]bool{},
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx Context) SyncReposOnce(st *state.HelmState, helm state.RepoUpdater) []error {
|
||||
var errs []error
|
||||
func (ctx Context) SyncReposOnce(st *state.HelmState, helm state.RepoUpdater) error {
|
||||
var (
|
||||
updated []string
|
||||
err error
|
||||
)
|
||||
|
||||
hasInstalled := false
|
||||
for _, release := range st.Releases {
|
||||
hasInstalled = hasInstalled || release.Installed == nil || *release.Installed
|
||||
if helm.IsHelm3() {
|
||||
updated, err = st.SyncRepos(helm, ctx.updatedRepos)
|
||||
} else {
|
||||
updated, err = st.SyncRepos(helm, ctx.updatedReposV2)
|
||||
}
|
||||
|
||||
if !hasInstalled {
|
||||
return errs
|
||||
for _, r := range updated {
|
||||
ctx.updatedRepos[r] = true
|
||||
}
|
||||
|
||||
allUpdated := true
|
||||
for _, r := range st.Repositories {
|
||||
_, exists := ctx.updatedRepos[r.Name]
|
||||
allUpdated = allUpdated && exists
|
||||
}
|
||||
|
||||
if !allUpdated {
|
||||
errs = st.SyncRepos(helm)
|
||||
|
||||
for _, r := range st.Repositories {
|
||||
ctx.updatedRepos[r.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/roboll/helmfile/pkg/environment"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/remote"
|
||||
"github.com/roboll/helmfile/pkg/state"
|
||||
"github.com/variantdev/vals"
|
||||
"go.uber.org/zap"
|
||||
@@ -25,13 +25,17 @@ type desiredStateLoader struct {
|
||||
|
||||
env string
|
||||
namespace string
|
||||
chart string
|
||||
|
||||
readFile func(string) ([]byte, error)
|
||||
fileExists func(string) (bool, error)
|
||||
abs func(string) (string, error)
|
||||
glob func(string) ([]string, error)
|
||||
getHelm func(*state.HelmState) helmexec.Interface
|
||||
readFile func(string) ([]byte, error)
|
||||
deleteFile func(string) error
|
||||
fileExists func(string) (bool, error)
|
||||
abs func(string) (string, error)
|
||||
glob func(string) ([]string, error)
|
||||
directoryExistsAt func(string) bool
|
||||
getHelm func(*state.HelmState) helmexec.Interface
|
||||
|
||||
remote *remote.Remote
|
||||
logger *zap.SugaredLogger
|
||||
valsRuntime vals.Evaluator
|
||||
}
|
||||
@@ -46,7 +50,7 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e
|
||||
return nil, fmt.Errorf("bug: opts.CalleePath was nil: f=%s, opts=%v", f, opts)
|
||||
}
|
||||
storage := state.NewStorage(opts.CalleePath, ld.logger, ld.glob)
|
||||
envld := state.NewEnvironmentValuesLoader(storage, ld.readFile, ld.logger)
|
||||
envld := state.NewEnvironmentValuesLoader(storage, ld.readFile, ld.logger, ld.remote)
|
||||
handler := state.MissingFileHandlerError
|
||||
vals, err := envld.LoadEnvironmentValues(&handler, args)
|
||||
if err != nil {
|
||||
@@ -65,17 +69,16 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e
|
||||
}
|
||||
|
||||
if opts.Reverse {
|
||||
rev := func(i, j int) bool {
|
||||
return j < i
|
||||
}
|
||||
sort.Slice(st.Releases, rev)
|
||||
sort.Slice(st.Helmfiles, rev)
|
||||
st.Reverse()
|
||||
}
|
||||
|
||||
if ld.overrideKubeContext != "" {
|
||||
if st.HelmDefaults.KubeContext != "" {
|
||||
return nil, errors.New("err: Cannot use option --kube-context and set attribute helmDefaults.kubeContext.")
|
||||
if st.OverrideKubeContext != "" {
|
||||
return nil, errors.New("err: Cannot use option --kube-context and set attribute kubeContext.")
|
||||
}
|
||||
st.OverrideKubeContext = ld.overrideKubeContext
|
||||
// HelmDefaults.KubeContext is also overriden in here
|
||||
// to set default release value properly.
|
||||
st.HelmDefaults.KubeContext = ld.overrideKubeContext
|
||||
}
|
||||
|
||||
@@ -86,6 +89,13 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e
|
||||
st.OverrideNamespace = ld.namespace
|
||||
}
|
||||
|
||||
if ld.chart != "" {
|
||||
if st.OverrideChart != "" {
|
||||
return nil, errors.New("err: Cannot use option --chart and set attribute chart.")
|
||||
}
|
||||
st.OverrideChart = ld.chart
|
||||
}
|
||||
|
||||
return st, nil
|
||||
}
|
||||
|
||||
@@ -147,7 +157,8 @@ func (ld *desiredStateLoader) loadFileWithOverrides(inheritedEnv, overrodeEnv *e
|
||||
}
|
||||
|
||||
func (a *desiredStateLoader) underlying() *state.StateCreator {
|
||||
c := state.NewCreator(a.logger, a.readFile, a.fileExists, a.abs, a.glob, a.valsRuntime, a.getHelm, a.overrideHelmBinary)
|
||||
c := state.NewCreator(a.logger, a.readFile, a.fileExists, a.abs, a.glob, a.directoryExistsAt, a.valsRuntime, a.getHelm, a.overrideHelmBinary, a.remote)
|
||||
c.DeleteFile = a.deleteFile
|
||||
c.LoadFile = a.loadFile
|
||||
return c
|
||||
}
|
||||
@@ -218,9 +229,11 @@ func (ld *desiredStateLoader) renderAndLoad(env, overrodeEnv *environment.Enviro
|
||||
if finalState == nil {
|
||||
finalState = currentState
|
||||
} else {
|
||||
if err := mergo.Merge(finalState, currentState, mergo.WithOverride); err != nil {
|
||||
if err := mergo.Merge(&finalState.ReleaseSetSpec, ¤tState.ReleaseSetSpec, mergo.WithOverride); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
finalState.RenderedValues = currentState.RenderedValues
|
||||
}
|
||||
|
||||
env = &finalState.Env
|
||||
|
||||
@@ -0,0 +1,765 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/exectest"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/testhelper"
|
||||
"github.com/variantdev/vals"
|
||||
)
|
||||
|
||||
func TestDestroy_2(t *testing.T) {
|
||||
type testcase struct {
|
||||
helm3 bool
|
||||
ns string
|
||||
concurrency int
|
||||
error string
|
||||
files map[string]string
|
||||
selectors []string
|
||||
lists map[exectest.ListKey]string
|
||||
diffs map[exectest.DiffKey]error
|
||||
upgraded []exectest.Release
|
||||
deleted []exectest.Release
|
||||
log string
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
wantUpgrades := tc.upgraded
|
||||
wantDeletes := tc.deleted
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
Helm3: tc.helm3,
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
Lists: tc.lists,
|
||||
Diffs: tc.diffs,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
}
|
||||
|
||||
bs := &bytes.Buffer{}
|
||||
|
||||
func() {
|
||||
t.Helper()
|
||||
|
||||
logReader, logWriter := io.Pipe()
|
||||
|
||||
logFlushed := &sync.WaitGroup{}
|
||||
// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
|
||||
logFlushed.Add(1)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(logReader)
|
||||
for scanner.Scan() {
|
||||
bs.Write(scanner.Bytes())
|
||||
bs.WriteString("\n")
|
||||
}
|
||||
logFlushed.Done()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
// This is here to avoid data-trace on bytes buffer `bs` to capture logs
|
||||
if err := logWriter.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logFlushed.Wait()
|
||||
}()
|
||||
|
||||
logger := helmexec.NewLogger(logWriter, "debug")
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
glob: filepath.Glob,
|
||||
abs: filepath.Abs,
|
||||
OverrideKubeContext: "",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", ""): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, tc.files)
|
||||
|
||||
if tc.ns != "" {
|
||||
app.Namespace = tc.ns
|
||||
}
|
||||
|
||||
if tc.selectors != nil {
|
||||
app.Selectors = tc.selectors
|
||||
}
|
||||
|
||||
destroyErr := app.Destroy(destroyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: tc.concurrency,
|
||||
logger: logger,
|
||||
})
|
||||
|
||||
if tc.error == "" && destroyErr != nil {
|
||||
t.Fatalf("unexpected error: %v", destroyErr)
|
||||
} else if tc.error != "" && destroyErr == nil {
|
||||
t.Fatal("expected error did not occur")
|
||||
} else if tc.error != "" && destroyErr != nil && tc.error != destroyErr.Error() {
|
||||
t.Fatalf("invalid error: expected %q, got %q", tc.error, destroyErr.Error())
|
||||
}
|
||||
|
||||
if len(wantUpgrades) > len(helm.Releases) {
|
||||
t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades))
|
||||
}
|
||||
|
||||
for relIdx := range wantUpgrades {
|
||||
if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantUpgrades[relIdx].Flags {
|
||||
if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(wantDeletes) > len(helm.Deleted) {
|
||||
t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes))
|
||||
}
|
||||
|
||||
for relIdx := range wantDeletes {
|
||||
if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantDeletes[relIdx].Flags {
|
||||
if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if tc.log != "" {
|
||||
actual := bs.String()
|
||||
|
||||
diff, exists := testhelper.Diff(tc.log, actual, 3)
|
||||
if exists {
|
||||
t.Errorf("unexpected log:\nDIFF\n%s\nEOD", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: database
|
||||
chart: charts/mysql
|
||||
needs:
|
||||
- logging
|
||||
- name: frontend-v1
|
||||
chart: charts/frontend
|
||||
installed: false
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- backend-v1
|
||||
- name: frontend-v2
|
||||
chart: charts/frontend
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- backend-v2
|
||||
- name: frontend-v3
|
||||
chart: charts/frontend
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- backend-v2
|
||||
- name: backend-v1
|
||||
chart: charts/backend
|
||||
installed: false
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- database
|
||||
- anotherbackend
|
||||
- name: backend-v2
|
||||
chart: charts/backend
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- database
|
||||
- anotherbackend
|
||||
- name: anotherbackend
|
||||
chart: charts/anotherbackend
|
||||
needs:
|
||||
- servicemesh
|
||||
- logging
|
||||
- database
|
||||
- name: servicemesh
|
||||
chart: charts/istio
|
||||
needs:
|
||||
- logging
|
||||
- name: logging
|
||||
chart: charts/fluent-bit
|
||||
- name: front-proxy
|
||||
chart: stable/envoy
|
||||
`,
|
||||
}
|
||||
|
||||
filesForTwoReleases := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: backend-v1
|
||||
chart: charts/backend
|
||||
installed: false
|
||||
- name: frontend-v1
|
||||
chart: charts/frontend
|
||||
needs:
|
||||
- backend-v1
|
||||
`,
|
||||
}
|
||||
|
||||
t.Run("smoke", func(t *testing.T) {
|
||||
//
|
||||
// complex test cases for smoke testing
|
||||
//
|
||||
check(t, testcase{
|
||||
files: files,
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
exectest.ListKey{Filter: "^frontend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^frontend-v3$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v3 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
exectest.ListKey{Filter: "^backend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
backend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^logging$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
logging 4 Fri Nov 1 08:40:07 2019 DEPLOYED fluent-bit-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^front-proxy$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
front-proxy 4 Fri Nov 1 08:40:07 2019 DEPLOYED envoy-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^servicemesh$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
servicemesh 4 Fri Nov 1 08:40:07 2019 DEPLOYED istio-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^database$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
database 4 Fri Nov 1 08:40:07 2019 DEPLOYED mysql-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^anotherbackend$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
anotherbackend 4 Fri Nov 1 08:40:07 2019 DEPLOYED anotherbackend-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// Disable concurrency to avoid in-deterministic result
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{
|
||||
{Name: "frontend-v3", Flags: []string{}},
|
||||
{Name: "frontend-v2", Flags: []string{}},
|
||||
{Name: "frontend-v1", Flags: []string{}},
|
||||
{Name: "backend-v2", Flags: []string{}},
|
||||
{Name: "backend-v1", Flags: []string{}},
|
||||
{Name: "anotherbackend", Flags: []string{}},
|
||||
{Name: "servicemesh", Flags: []string{}},
|
||||
{Name: "database", Flags: []string{}},
|
||||
{Name: "front-proxy", Flags: []string{}},
|
||||
{Name: "logging", Flags: []string{}},
|
||||
},
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: database
|
||||
3: chart: charts/mysql
|
||||
4: needs:
|
||||
5: - logging
|
||||
6: - name: frontend-v1
|
||||
7: chart: charts/frontend
|
||||
8: installed: false
|
||||
9: needs:
|
||||
10: - servicemesh
|
||||
11: - logging
|
||||
12: - backend-v1
|
||||
13: - name: frontend-v2
|
||||
14: chart: charts/frontend
|
||||
15: needs:
|
||||
16: - servicemesh
|
||||
17: - logging
|
||||
18: - backend-v2
|
||||
19: - name: frontend-v3
|
||||
20: chart: charts/frontend
|
||||
21: needs:
|
||||
22: - servicemesh
|
||||
23: - logging
|
||||
24: - backend-v2
|
||||
25: - name: backend-v1
|
||||
26: chart: charts/backend
|
||||
27: installed: false
|
||||
28: needs:
|
||||
29: - servicemesh
|
||||
30: - logging
|
||||
31: - database
|
||||
32: - anotherbackend
|
||||
33: - name: backend-v2
|
||||
34: chart: charts/backend
|
||||
35: needs:
|
||||
36: - servicemesh
|
||||
37: - logging
|
||||
38: - database
|
||||
39: - anotherbackend
|
||||
40: - name: anotherbackend
|
||||
41: chart: charts/anotherbackend
|
||||
42: needs:
|
||||
43: - servicemesh
|
||||
44: - logging
|
||||
45: - database
|
||||
46: - name: servicemesh
|
||||
47: chart: charts/istio
|
||||
48: needs:
|
||||
49: - logging
|
||||
50: - name: logging
|
||||
51: chart: charts/fluent-bit
|
||||
52: - name: front-proxy
|
||||
53: chart: stable/envoy
|
||||
54:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: database
|
||||
3: chart: charts/mysql
|
||||
4: needs:
|
||||
5: - logging
|
||||
6: - name: frontend-v1
|
||||
7: chart: charts/frontend
|
||||
8: installed: false
|
||||
9: needs:
|
||||
10: - servicemesh
|
||||
11: - logging
|
||||
12: - backend-v1
|
||||
13: - name: frontend-v2
|
||||
14: chart: charts/frontend
|
||||
15: needs:
|
||||
16: - servicemesh
|
||||
17: - logging
|
||||
18: - backend-v2
|
||||
19: - name: frontend-v3
|
||||
20: chart: charts/frontend
|
||||
21: needs:
|
||||
22: - servicemesh
|
||||
23: - logging
|
||||
24: - backend-v2
|
||||
25: - name: backend-v1
|
||||
26: chart: charts/backend
|
||||
27: installed: false
|
||||
28: needs:
|
||||
29: - servicemesh
|
||||
30: - logging
|
||||
31: - database
|
||||
32: - anotherbackend
|
||||
33: - name: backend-v2
|
||||
34: chart: charts/backend
|
||||
35: needs:
|
||||
36: - servicemesh
|
||||
37: - logging
|
||||
38: - database
|
||||
39: - anotherbackend
|
||||
40: - name: anotherbackend
|
||||
41: chart: charts/anotherbackend
|
||||
42: needs:
|
||||
43: - servicemesh
|
||||
44: - logging
|
||||
45: - database
|
||||
46: - name: servicemesh
|
||||
47: chart: charts/istio
|
||||
48: needs:
|
||||
49: - logging
|
||||
50: - name: logging
|
||||
51: chart: charts/fluent-bit
|
||||
52: - name: front-proxy
|
||||
53: chart: stable/envoy
|
||||
54:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
10 release(s) found in helmfile.yaml
|
||||
|
||||
processing 5 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 frontend-v3, frontend-v2, frontend-v1
|
||||
2 backend-v2, backend-v1
|
||||
3 anotherbackend
|
||||
4 servicemesh, database
|
||||
5 front-proxy, logging
|
||||
|
||||
processing releases in group 1/5: frontend-v3, frontend-v2, frontend-v1
|
||||
release "frontend-v3" processed
|
||||
release "frontend-v2" processed
|
||||
release "frontend-v1" processed
|
||||
processing releases in group 2/5: backend-v2, backend-v1
|
||||
release "backend-v2" processed
|
||||
release "backend-v1" processed
|
||||
processing releases in group 3/5: anotherbackend
|
||||
release "anotherbackend" processed
|
||||
processing releases in group 4/5: servicemesh, database
|
||||
release "servicemesh" processed
|
||||
release "database" processed
|
||||
processing releases in group 5/5: front-proxy, logging
|
||||
release "front-proxy" processed
|
||||
release "logging" processed
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
frontend-v3
|
||||
frontend-v2
|
||||
frontend-v1
|
||||
backend-v2
|
||||
backend-v1
|
||||
anotherbackend
|
||||
servicemesh
|
||||
database
|
||||
front-proxy
|
||||
logging
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("destroy only one release with selector", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: files,
|
||||
selectors: []string{"name=logging"},
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
exectest.ListKey{Filter: "^frontend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^frontend-v3$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v3 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
exectest.ListKey{Filter: "^backend-v2$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
backend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^logging$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
logging 4 Fri Nov 1 08:40:07 2019 DEPLOYED fluent-bit-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^front-proxy$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
front-proxy 4 Fri Nov 1 08:40:07 2019 DEPLOYED envoy-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^servicemesh$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
servicemesh 4 Fri Nov 1 08:40:07 2019 DEPLOYED istio-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^database$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
database 4 Fri Nov 1 08:40:07 2019 DEPLOYED mysql-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^anotherbackend$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
anotherbackend 4 Fri Nov 1 08:40:07 2019 DEPLOYED anotherbackend-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// Disable concurrency to avoid in-deterministic result
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{
|
||||
{Name: "logging", Flags: []string{}},
|
||||
},
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: database
|
||||
3: chart: charts/mysql
|
||||
4: needs:
|
||||
5: - logging
|
||||
6: - name: frontend-v1
|
||||
7: chart: charts/frontend
|
||||
8: installed: false
|
||||
9: needs:
|
||||
10: - servicemesh
|
||||
11: - logging
|
||||
12: - backend-v1
|
||||
13: - name: frontend-v2
|
||||
14: chart: charts/frontend
|
||||
15: needs:
|
||||
16: - servicemesh
|
||||
17: - logging
|
||||
18: - backend-v2
|
||||
19: - name: frontend-v3
|
||||
20: chart: charts/frontend
|
||||
21: needs:
|
||||
22: - servicemesh
|
||||
23: - logging
|
||||
24: - backend-v2
|
||||
25: - name: backend-v1
|
||||
26: chart: charts/backend
|
||||
27: installed: false
|
||||
28: needs:
|
||||
29: - servicemesh
|
||||
30: - logging
|
||||
31: - database
|
||||
32: - anotherbackend
|
||||
33: - name: backend-v2
|
||||
34: chart: charts/backend
|
||||
35: needs:
|
||||
36: - servicemesh
|
||||
37: - logging
|
||||
38: - database
|
||||
39: - anotherbackend
|
||||
40: - name: anotherbackend
|
||||
41: chart: charts/anotherbackend
|
||||
42: needs:
|
||||
43: - servicemesh
|
||||
44: - logging
|
||||
45: - database
|
||||
46: - name: servicemesh
|
||||
47: chart: charts/istio
|
||||
48: needs:
|
||||
49: - logging
|
||||
50: - name: logging
|
||||
51: chart: charts/fluent-bit
|
||||
52: - name: front-proxy
|
||||
53: chart: stable/envoy
|
||||
54:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: database
|
||||
3: chart: charts/mysql
|
||||
4: needs:
|
||||
5: - logging
|
||||
6: - name: frontend-v1
|
||||
7: chart: charts/frontend
|
||||
8: installed: false
|
||||
9: needs:
|
||||
10: - servicemesh
|
||||
11: - logging
|
||||
12: - backend-v1
|
||||
13: - name: frontend-v2
|
||||
14: chart: charts/frontend
|
||||
15: needs:
|
||||
16: - servicemesh
|
||||
17: - logging
|
||||
18: - backend-v2
|
||||
19: - name: frontend-v3
|
||||
20: chart: charts/frontend
|
||||
21: needs:
|
||||
22: - servicemesh
|
||||
23: - logging
|
||||
24: - backend-v2
|
||||
25: - name: backend-v1
|
||||
26: chart: charts/backend
|
||||
27: installed: false
|
||||
28: needs:
|
||||
29: - servicemesh
|
||||
30: - logging
|
||||
31: - database
|
||||
32: - anotherbackend
|
||||
33: - name: backend-v2
|
||||
34: chart: charts/backend
|
||||
35: needs:
|
||||
36: - servicemesh
|
||||
37: - logging
|
||||
38: - database
|
||||
39: - anotherbackend
|
||||
40: - name: anotherbackend
|
||||
41: chart: charts/anotherbackend
|
||||
42: needs:
|
||||
43: - servicemesh
|
||||
44: - logging
|
||||
45: - database
|
||||
46: - name: servicemesh
|
||||
47: chart: charts/istio
|
||||
48: needs:
|
||||
49: - logging
|
||||
50: - name: logging
|
||||
51: chart: charts/fluent-bit
|
||||
52: - name: front-proxy
|
||||
53: chart: stable/envoy
|
||||
54:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
1 release(s) matching name=logging found in helmfile.yaml
|
||||
|
||||
processing 1 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 logging
|
||||
|
||||
processing releases in group 1/1: logging
|
||||
release "logging" processed
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
logging
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("destroy installed but disabled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: filesForTwoReleases,
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
},
|
||||
// Disable concurrency to avoid in-deterministic result
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{
|
||||
{Name: "frontend-v1", Flags: []string{}},
|
||||
},
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) found in helmfile.yaml
|
||||
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 frontend-v1
|
||||
2 backend-v1
|
||||
|
||||
processing releases in group 1/2: frontend-v1
|
||||
release "frontend-v1" processed
|
||||
processing releases in group 2/2: backend-v1
|
||||
release "backend-v1" processed
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
frontend-v1
|
||||
backend-v1
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("helm3", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
helm3: true,
|
||||
files: filesForTwoReleases,
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV3ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV3ListFlagsWithoutKubeContext}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
},
|
||||
// Disable concurrency to avoid in-deterministic result
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{
|
||||
{Name: "frontend-v1", Flags: []string{}},
|
||||
},
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) found in helmfile.yaml
|
||||
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 frontend-v1
|
||||
2 backend-v1
|
||||
|
||||
processing releases in group 1/2: frontend-v1
|
||||
release "frontend-v1" processed
|
||||
processing releases in group 2/2: backend-v1
|
||||
release "backend-v1" processed
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
frontend-v1
|
||||
backend-v1
|
||||
`,
|
||||
})
|
||||
})
|
||||
}
|
||||
+514
-166
@@ -3,21 +3,30 @@ package app
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/exectest"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/testhelper"
|
||||
"github.com/variantdev/vals"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
helmV2ListFlags = "--kube-contextdefault--deleting--deployed--failed--pending"
|
||||
helmV2ListFlagsWithoutKubeContext = "--deleting--deployed--failed--pending"
|
||||
helmV3ListFlags = "--kube-contextdefault--uninstalling--deployed--failed--pending"
|
||||
helmV3ListFlagsWithoutKubeContext = "--uninstalling--deployed--failed--pending"
|
||||
)
|
||||
|
||||
type destroyConfig struct {
|
||||
args string
|
||||
concurrency int
|
||||
interactive bool
|
||||
skipDeps bool
|
||||
logger *zap.SugaredLogger
|
||||
}
|
||||
|
||||
@@ -37,10 +46,13 @@ func (d destroyConfig) Concurrency() int {
|
||||
return d.concurrency
|
||||
}
|
||||
|
||||
func (d destroyConfig) SkipDeps() bool {
|
||||
return d.skipDeps
|
||||
}
|
||||
|
||||
func TestDestroy(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
loc string
|
||||
type testcase struct {
|
||||
helm3 bool
|
||||
ns string
|
||||
concurrency int
|
||||
error string
|
||||
@@ -51,15 +63,137 @@ func TestDestroy(t *testing.T) {
|
||||
upgraded []exectest.Release
|
||||
deleted []exectest.Release
|
||||
log string
|
||||
}{
|
||||
//
|
||||
// complex test cases for smoke testing
|
||||
//
|
||||
{
|
||||
name: "smoke",
|
||||
loc: location(),
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
}
|
||||
|
||||
check := func(t *testing.T, tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
wantUpgrades := tc.upgraded
|
||||
wantDeletes := tc.deleted
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
Helm3: tc.helm3,
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
Lists: tc.lists,
|
||||
Diffs: tc.diffs,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
}
|
||||
|
||||
bs := &bytes.Buffer{}
|
||||
|
||||
func() {
|
||||
t.Helper()
|
||||
|
||||
logReader, logWriter := io.Pipe()
|
||||
|
||||
logFlushed := &sync.WaitGroup{}
|
||||
// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
|
||||
logFlushed.Add(1)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(logReader)
|
||||
for scanner.Scan() {
|
||||
bs.Write(scanner.Bytes())
|
||||
bs.WriteString("\n")
|
||||
}
|
||||
logFlushed.Done()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
// This is here to avoid data-trace on bytes buffer `bs` to capture logs
|
||||
if err := logWriter.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logFlushed.Wait()
|
||||
}()
|
||||
|
||||
logger := helmexec.NewLogger(logWriter, "debug")
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
glob: filepath.Glob,
|
||||
abs: filepath.Abs,
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, tc.files)
|
||||
|
||||
if tc.ns != "" {
|
||||
app.Namespace = tc.ns
|
||||
}
|
||||
|
||||
if tc.selectors != nil {
|
||||
app.Selectors = tc.selectors
|
||||
}
|
||||
|
||||
destroyErr := app.Destroy(destroyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: tc.concurrency,
|
||||
logger: logger,
|
||||
})
|
||||
|
||||
if tc.error == "" && destroyErr != nil {
|
||||
t.Fatalf("unexpected error: %v", destroyErr)
|
||||
} else if tc.error != "" && destroyErr == nil {
|
||||
t.Fatal("expected error did not occur")
|
||||
} else if tc.error != "" && destroyErr != nil && tc.error != destroyErr.Error() {
|
||||
t.Fatalf("invalid error: expected %q, got %q", tc.error, destroyErr.Error())
|
||||
}
|
||||
|
||||
if len(wantUpgrades) > len(helm.Releases) {
|
||||
t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades))
|
||||
}
|
||||
|
||||
for relIdx := range wantUpgrades {
|
||||
if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantUpgrades[relIdx].Flags {
|
||||
if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(wantDeletes) > len(helm.Deleted) {
|
||||
t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes))
|
||||
}
|
||||
|
||||
for relIdx := range wantDeletes {
|
||||
if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantDeletes[relIdx].Flags {
|
||||
if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if tc.log != "" {
|
||||
actual := bs.String()
|
||||
|
||||
diff, exists := testhelper.Diff(tc.log, actual, 3)
|
||||
if exists {
|
||||
t.Errorf("unexpected log:\nDIFF\n%s\nEOD", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: database
|
||||
chart: charts/mysql
|
||||
@@ -114,35 +248,55 @@ releases:
|
||||
- name: front-proxy
|
||||
chart: stable/envoy
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
filesForTwoReleases := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: backend-v1
|
||||
chart: charts/backend
|
||||
installed: false
|
||||
- name: frontend-v1
|
||||
chart: charts/frontend
|
||||
needs:
|
||||
- backend-v1
|
||||
`,
|
||||
}
|
||||
|
||||
t.Run("smoke", func(t *testing.T) {
|
||||
//
|
||||
// complex test cases for smoke testing
|
||||
//
|
||||
check(t, testcase{
|
||||
files: files,
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
exectest.ListKey{Filter: "^frontend-v1$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
exectest.ListKey{Filter: "^frontend-v2$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^frontend-v2$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^frontend-v3$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^frontend-v3$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v3 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^backend-v1$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
exectest.ListKey{Filter: "^backend-v2$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^backend-v2$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
backend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^logging$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^logging$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
logging 4 Fri Nov 1 08:40:07 2019 DEPLOYED fluent-bit-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^front-proxy$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^front-proxy$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
front-proxy 4 Fri Nov 1 08:40:07 2019 DEPLOYED envoy-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^servicemesh$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^servicemesh$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
servicemesh 4 Fri Nov 1 08:40:07 2019 DEPLOYED istio-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^database$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^database$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
database 4 Fri Nov 1 08:40:07 2019 DEPLOYED mysql-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^anotherbackend$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^anotherbackend$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
anotherbackend 4 Fri Nov 1 08:40:07 2019 DEPLOYED anotherbackend-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
@@ -156,8 +310,8 @@ anotherbackend 4 Fri Nov 1 08:40:07 2019 DEPLOYED anotherbackend-3.1.0
|
||||
{Name: "backend-v2", Flags: []string{}},
|
||||
{Name: "backend-v1", Flags: []string{}},
|
||||
{Name: "anotherbackend", Flags: []string{}},
|
||||
{Name: "database", Flags: []string{}},
|
||||
{Name: "servicemesh", Flags: []string{}},
|
||||
{Name: "database", Flags: []string{}},
|
||||
{Name: "front-proxy", Flags: []string{}},
|
||||
{Name: "logging", Flags: []string{}},
|
||||
},
|
||||
@@ -288,25 +442,25 @@ merged environment: &{default map[] map[]}
|
||||
|
||||
processing 5 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 frontend-v3, frontend-v2, frontend-v1
|
||||
2 backend-v2, backend-v1
|
||||
3 anotherbackend
|
||||
4 database, servicemesh
|
||||
5 front-proxy, logging
|
||||
1 default//frontend-v3, default//frontend-v2, default//frontend-v1
|
||||
2 default//backend-v2, default//backend-v1
|
||||
3 default//anotherbackend
|
||||
4 default//servicemesh, default//database
|
||||
5 default//front-proxy, default//logging
|
||||
|
||||
processing releases in group 1/5: frontend-v3, frontend-v2, frontend-v1
|
||||
processing releases in group 1/5: default//frontend-v3, default//frontend-v2, default//frontend-v1
|
||||
release "frontend-v3" processed
|
||||
release "frontend-v2" processed
|
||||
release "frontend-v1" processed
|
||||
processing releases in group 2/5: backend-v2, backend-v1
|
||||
processing releases in group 2/5: default//backend-v2, default//backend-v1
|
||||
release "backend-v2" processed
|
||||
release "backend-v1" processed
|
||||
processing releases in group 3/5: anotherbackend
|
||||
processing releases in group 3/5: default//anotherbackend
|
||||
release "anotherbackend" processed
|
||||
processing releases in group 4/5: database, servicemesh
|
||||
release "database" processed
|
||||
processing releases in group 4/5: default//servicemesh, default//database
|
||||
release "servicemesh" processed
|
||||
processing releases in group 5/5: front-proxy, logging
|
||||
release "database" processed
|
||||
processing releases in group 5/5: default//front-proxy, default//logging
|
||||
release "front-proxy" processed
|
||||
release "logging" processed
|
||||
|
||||
@@ -318,136 +472,330 @@ frontend-v1
|
||||
backend-v2
|
||||
backend-v1
|
||||
anotherbackend
|
||||
database
|
||||
servicemesh
|
||||
database
|
||||
front-proxy
|
||||
logging
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for i := range testcases {
|
||||
tc := testcases[i]
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
wantUpgrades := tc.upgraded
|
||||
wantDeletes := tc.deleted
|
||||
|
||||
var helm = &exectest.Helm{
|
||||
FailOnUnexpectedList: true,
|
||||
FailOnUnexpectedDiff: true,
|
||||
Lists: tc.lists,
|
||||
Diffs: tc.diffs,
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
}
|
||||
|
||||
bs := &bytes.Buffer{}
|
||||
|
||||
func() {
|
||||
logReader, logWriter := io.Pipe()
|
||||
|
||||
logFlushed := &sync.WaitGroup{}
|
||||
// Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()`
|
||||
logFlushed.Add(1)
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(logReader)
|
||||
for scanner.Scan() {
|
||||
bs.Write(scanner.Bytes())
|
||||
bs.WriteString("\n")
|
||||
}
|
||||
logFlushed.Done()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
// This is here to avoid data-trace on bytes buffer `bs` to capture logs
|
||||
if err := logWriter.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logFlushed.Wait()
|
||||
}()
|
||||
|
||||
logger := helmexec.NewLogger(logWriter, "debug")
|
||||
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error creating vals runtime: %v", err)
|
||||
}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
glob: filepath.Glob,
|
||||
abs: filepath.Abs,
|
||||
OverrideKubeContext: "default",
|
||||
Env: "default",
|
||||
Logger: logger,
|
||||
helms: map[helmKey]helmexec.Interface{
|
||||
createHelmKey("helm", "default"): helm,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}, tc.files)
|
||||
|
||||
if tc.ns != "" {
|
||||
app.Namespace = tc.ns
|
||||
}
|
||||
|
||||
if tc.selectors != nil {
|
||||
app.Selectors = tc.selectors
|
||||
}
|
||||
|
||||
destroyErr := app.Destroy(destroyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: tc.concurrency,
|
||||
logger: logger,
|
||||
})
|
||||
|
||||
if tc.error == "" && destroyErr != nil {
|
||||
t.Fatalf("unexpected error for data defined at %s: %v", tc.loc, destroyErr)
|
||||
} else if tc.error != "" && destroyErr == nil {
|
||||
t.Fatalf("expected error did not occur for data defined at %s", tc.loc)
|
||||
} else if tc.error != "" && destroyErr != nil && tc.error != destroyErr.Error() {
|
||||
t.Fatalf("invalid error: expected %q, got %q", tc.error, destroyErr.Error())
|
||||
}
|
||||
|
||||
if len(wantUpgrades) > len(helm.Releases) {
|
||||
t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades))
|
||||
}
|
||||
|
||||
for relIdx := range wantUpgrades {
|
||||
if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantUpgrades[relIdx].Flags {
|
||||
if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(wantDeletes) > len(helm.Deleted) {
|
||||
t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes))
|
||||
}
|
||||
|
||||
for relIdx := range wantDeletes {
|
||||
if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name {
|
||||
t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name)
|
||||
}
|
||||
for flagIdx := range wantDeletes[relIdx].Flags {
|
||||
if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if tc.log != "" {
|
||||
actual := bs.String()
|
||||
|
||||
diff, exists := testhelper.Diff(tc.log, actual, 3)
|
||||
if exists {
|
||||
t.Errorf("unexpected log for data defined %s:\nDIFF\n%s\nEOD", tc.loc, diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("destroy only one release with selector", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: files,
|
||||
selectors: []string{"name=logging"},
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
exectest.ListKey{Filter: "^frontend-v2$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^frontend-v3$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v3 4 Fri Nov 1 08:40:07 2019 DEPLOYED frontend-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
exectest.ListKey{Filter: "^backend-v2$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
backend-v2 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^logging$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
logging 4 Fri Nov 1 08:40:07 2019 DEPLOYED fluent-bit-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^front-proxy$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
front-proxy 4 Fri Nov 1 08:40:07 2019 DEPLOYED envoy-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^servicemesh$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
servicemesh 4 Fri Nov 1 08:40:07 2019 DEPLOYED istio-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^database$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
database 4 Fri Nov 1 08:40:07 2019 DEPLOYED mysql-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^anotherbackend$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
anotherbackend 4 Fri Nov 1 08:40:07 2019 DEPLOYED anotherbackend-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
// Disable concurrency to avoid in-deterministic result
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{
|
||||
{Name: "logging", Flags: []string{}},
|
||||
},
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: database
|
||||
3: chart: charts/mysql
|
||||
4: needs:
|
||||
5: - logging
|
||||
6: - name: frontend-v1
|
||||
7: chart: charts/frontend
|
||||
8: installed: false
|
||||
9: needs:
|
||||
10: - servicemesh
|
||||
11: - logging
|
||||
12: - backend-v1
|
||||
13: - name: frontend-v2
|
||||
14: chart: charts/frontend
|
||||
15: needs:
|
||||
16: - servicemesh
|
||||
17: - logging
|
||||
18: - backend-v2
|
||||
19: - name: frontend-v3
|
||||
20: chart: charts/frontend
|
||||
21: needs:
|
||||
22: - servicemesh
|
||||
23: - logging
|
||||
24: - backend-v2
|
||||
25: - name: backend-v1
|
||||
26: chart: charts/backend
|
||||
27: installed: false
|
||||
28: needs:
|
||||
29: - servicemesh
|
||||
30: - logging
|
||||
31: - database
|
||||
32: - anotherbackend
|
||||
33: - name: backend-v2
|
||||
34: chart: charts/backend
|
||||
35: needs:
|
||||
36: - servicemesh
|
||||
37: - logging
|
||||
38: - database
|
||||
39: - anotherbackend
|
||||
40: - name: anotherbackend
|
||||
41: chart: charts/anotherbackend
|
||||
42: needs:
|
||||
43: - servicemesh
|
||||
44: - logging
|
||||
45: - database
|
||||
46: - name: servicemesh
|
||||
47: chart: charts/istio
|
||||
48: needs:
|
||||
49: - logging
|
||||
50: - name: logging
|
||||
51: chart: charts/fluent-bit
|
||||
52: - name: front-proxy
|
||||
53: chart: stable/envoy
|
||||
54:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: database
|
||||
3: chart: charts/mysql
|
||||
4: needs:
|
||||
5: - logging
|
||||
6: - name: frontend-v1
|
||||
7: chart: charts/frontend
|
||||
8: installed: false
|
||||
9: needs:
|
||||
10: - servicemesh
|
||||
11: - logging
|
||||
12: - backend-v1
|
||||
13: - name: frontend-v2
|
||||
14: chart: charts/frontend
|
||||
15: needs:
|
||||
16: - servicemesh
|
||||
17: - logging
|
||||
18: - backend-v2
|
||||
19: - name: frontend-v3
|
||||
20: chart: charts/frontend
|
||||
21: needs:
|
||||
22: - servicemesh
|
||||
23: - logging
|
||||
24: - backend-v2
|
||||
25: - name: backend-v1
|
||||
26: chart: charts/backend
|
||||
27: installed: false
|
||||
28: needs:
|
||||
29: - servicemesh
|
||||
30: - logging
|
||||
31: - database
|
||||
32: - anotherbackend
|
||||
33: - name: backend-v2
|
||||
34: chart: charts/backend
|
||||
35: needs:
|
||||
36: - servicemesh
|
||||
37: - logging
|
||||
38: - database
|
||||
39: - anotherbackend
|
||||
40: - name: anotherbackend
|
||||
41: chart: charts/anotherbackend
|
||||
42: needs:
|
||||
43: - servicemesh
|
||||
44: - logging
|
||||
45: - database
|
||||
46: - name: servicemesh
|
||||
47: chart: charts/istio
|
||||
48: needs:
|
||||
49: - logging
|
||||
50: - name: logging
|
||||
51: chart: charts/fluent-bit
|
||||
52: - name: front-proxy
|
||||
53: chart: stable/envoy
|
||||
54:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
1 release(s) matching name=logging found in helmfile.yaml
|
||||
|
||||
processing 1 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default//logging
|
||||
|
||||
processing releases in group 1/1: default//logging
|
||||
release "logging" processed
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
logging
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("destroy installed but disabled release", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
files: filesForTwoReleases,
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
},
|
||||
// Disable concurrency to avoid in-deterministic result
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{
|
||||
{Name: "frontend-v1", Flags: []string{}},
|
||||
},
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) found in helmfile.yaml
|
||||
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default//frontend-v1
|
||||
2 default//backend-v1
|
||||
|
||||
processing releases in group 1/2: default//frontend-v1
|
||||
release "frontend-v1" processed
|
||||
processing releases in group 2/2: default//backend-v1
|
||||
release "backend-v1" processed
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
frontend-v1
|
||||
backend-v1
|
||||
`,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("helm3", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
helm3: true,
|
||||
files: filesForTwoReleases,
|
||||
diffs: map[exectest.DiffKey]error{},
|
||||
lists: map[exectest.ListKey]string{
|
||||
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV3ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV3ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
`,
|
||||
},
|
||||
// Disable concurrency to avoid in-deterministic result
|
||||
concurrency: 1,
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{
|
||||
{Name: "frontend-v1", Flags: []string{}},
|
||||
},
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: backend-v1
|
||||
3: chart: charts/backend
|
||||
4: installed: false
|
||||
5: - name: frontend-v1
|
||||
6: chart: charts/frontend
|
||||
7: needs:
|
||||
8: - backend-v1
|
||||
9:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) found in helmfile.yaml
|
||||
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default//frontend-v1
|
||||
2 default//backend-v1
|
||||
|
||||
processing releases in group 1/2: default//frontend-v1
|
||||
release "frontend-v1" processed
|
||||
processing releases in group 2/2: default//backend-v1
|
||||
release "backend-v1" processed
|
||||
|
||||
DELETED RELEASES:
|
||||
NAME
|
||||
frontend-v1
|
||||
backend-v1
|
||||
`,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+282
-66
@@ -3,15 +3,17 @@ package app
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/roboll/helmfile/pkg/exectest"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/testhelper"
|
||||
"github.com/variantdev/vals"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type diffConfig struct {
|
||||
@@ -20,15 +22,21 @@ type diffConfig struct {
|
||||
retainValuesFiles bool
|
||||
set []string
|
||||
validate bool
|
||||
skipCRDs bool
|
||||
skipDeps bool
|
||||
includeTests bool
|
||||
includeNeeds bool
|
||||
skipNeeds bool
|
||||
suppressSecrets bool
|
||||
showSecrets bool
|
||||
suppressDiff bool
|
||||
noColor bool
|
||||
context int
|
||||
diffOutput string
|
||||
concurrency int
|
||||
detailedExitcode bool
|
||||
interactive bool
|
||||
skipDiffOnInstall bool
|
||||
logger *zap.SugaredLogger
|
||||
}
|
||||
|
||||
@@ -48,6 +56,10 @@ func (a diffConfig) Validate() bool {
|
||||
return a.validate
|
||||
}
|
||||
|
||||
func (a diffConfig) SkipCRDs() bool {
|
||||
return a.skipCRDs
|
||||
}
|
||||
|
||||
func (a diffConfig) SkipDeps() bool {
|
||||
return a.skipDeps
|
||||
}
|
||||
@@ -56,10 +68,22 @@ func (a diffConfig) IncludeTests() bool {
|
||||
return a.includeTests
|
||||
}
|
||||
|
||||
func (a diffConfig) IncludeNeeds() bool {
|
||||
return a.includeNeeds
|
||||
}
|
||||
|
||||
func (a diffConfig) SkipNeeds() bool {
|
||||
return a.skipNeeds
|
||||
}
|
||||
|
||||
func (a diffConfig) SuppressSecrets() bool {
|
||||
return a.suppressSecrets
|
||||
}
|
||||
|
||||
func (a diffConfig) ShowSecrets() bool {
|
||||
return a.showSecrets
|
||||
}
|
||||
|
||||
func (a diffConfig) SuppressDiff() bool {
|
||||
return a.suppressDiff
|
||||
}
|
||||
@@ -72,6 +96,10 @@ func (a diffConfig) Context() int {
|
||||
return a.context
|
||||
}
|
||||
|
||||
func (a diffConfig) DiffOutput() string {
|
||||
return a.diffOutput
|
||||
}
|
||||
|
||||
func (a diffConfig) Concurrency() int {
|
||||
return a.concurrency
|
||||
}
|
||||
@@ -84,6 +112,10 @@ func (a diffConfig) Interactive() bool {
|
||||
return a.interactive
|
||||
}
|
||||
|
||||
func (a diffConfig) SkipDiffOnInstall() bool {
|
||||
return a.skipDiffOnInstall
|
||||
}
|
||||
|
||||
func (a diffConfig) Logger() *zap.SugaredLogger {
|
||||
return a.logger
|
||||
}
|
||||
@@ -93,20 +125,26 @@ func (a diffConfig) RetainValuesFiles() bool {
|
||||
}
|
||||
|
||||
func TestDiff(t *testing.T) {
|
||||
type flags struct {
|
||||
skipNeeds bool
|
||||
}
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
loc string
|
||||
ns string
|
||||
concurrency int
|
||||
detailedExitcode bool
|
||||
error string
|
||||
files map[string]string
|
||||
selectors []string
|
||||
lists map[exectest.ListKey]string
|
||||
diffs map[exectest.DiffKey]error
|
||||
upgraded []exectest.Release
|
||||
deleted []exectest.Release
|
||||
log string
|
||||
name string
|
||||
loc string
|
||||
ns string
|
||||
concurrency int
|
||||
skipDiffOnInstall bool
|
||||
detailedExitcode bool
|
||||
error string
|
||||
flags flags
|
||||
files map[string]string
|
||||
selectors []string
|
||||
lists map[exectest.ListKey]string
|
||||
diffs map[exectest.DiffKey]error
|
||||
upgraded []exectest.Release
|
||||
deleted []exectest.Release
|
||||
log string
|
||||
}{
|
||||
//
|
||||
// complex test cases for smoke testing
|
||||
@@ -172,7 +210,7 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
// noop on frontend-v2
|
||||
exectest.DiffKey{Name: "frontend-v2", Chart: "charts/frontend", Flags: "--kube-contextdefault--detailed-exitcode"}: nil,
|
||||
@@ -188,10 +226,10 @@ releases:
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
// delete frontend-v1 and backend-v1
|
||||
exectest.ListKey{Filter: "^frontend-v1$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^frontend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
frontend-v1 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^backend-v1$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^backend-v1$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
backend-v1 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
@@ -361,8 +399,8 @@ releases:
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: nil,
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault--deployed--failed--pending"}: ``,
|
||||
exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlags}: ``,
|
||||
exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
@@ -389,7 +427,7 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
@@ -461,7 +499,7 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
@@ -483,7 +521,7 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
@@ -506,7 +544,7 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
@@ -529,7 +567,7 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
@@ -553,7 +591,7 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
@@ -561,7 +599,7 @@ releases:
|
||||
upgraded: []exectest.Release{},
|
||||
},
|
||||
{
|
||||
name: "upgrade when ns2/bar needs ns1/foo",
|
||||
name: "helm3: upgrade when ns2/bar needs ns1/foo",
|
||||
loc: location(),
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
@@ -577,7 +615,7 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
@@ -596,7 +634,7 @@ releases:
|
||||
namespace: ns1
|
||||
tillerNamespace: tns1
|
||||
needs:
|
||||
- tns2/ns2/bar
|
||||
- tns2/bar
|
||||
- name: bar
|
||||
chart: mychart2
|
||||
namespace: ns2
|
||||
@@ -604,7 +642,7 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--tiller-namespacetns2--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--tiller-namespacetns1--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
@@ -612,7 +650,7 @@ releases:
|
||||
upgraded: []exectest.Release{},
|
||||
},
|
||||
{
|
||||
name: "upgrade when tns2/ns2/bar needs tns1/ns1/foo",
|
||||
name: "helm2: upgrade when tns2/bar needs tns1/foo",
|
||||
loc: location(),
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
@@ -622,7 +660,7 @@ releases:
|
||||
namespace: ns2
|
||||
tillerNamespace: tns2
|
||||
needs:
|
||||
- tns1/ns1/foo
|
||||
- tns1/foo
|
||||
- name: foo
|
||||
chart: mychart1
|
||||
namespace: ns1
|
||||
@@ -630,7 +668,7 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--tiller-namespacetns2--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--tiller-namespacetns1--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
@@ -649,7 +687,7 @@ first-pass rendering output of "helmfile.yaml.part.0":
|
||||
4: namespace: ns2
|
||||
5: tillerNamespace: tns2
|
||||
6: needs:
|
||||
7: - tns1/ns1/foo
|
||||
7: - tns1/foo
|
||||
8: - name: foo
|
||||
9: chart: mychart1
|
||||
10: namespace: ns1
|
||||
@@ -669,7 +707,7 @@ second-pass rendering result of "helmfile.yaml.part.0":
|
||||
4: namespace: ns2
|
||||
5: tillerNamespace: tns2
|
||||
6: needs:
|
||||
7: - tns1/ns1/foo
|
||||
7: - tns1/foo
|
||||
8: - name: foo
|
||||
9: chart: mychart1
|
||||
10: namespace: ns1
|
||||
@@ -679,6 +717,74 @@ second-pass rendering result of "helmfile.yaml.part.0":
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) found in helmfile.yaml
|
||||
|
||||
Affected releases are:
|
||||
bar (mychart2) UPDATED
|
||||
foo (mychart1) UPDATED
|
||||
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "helm3: upgrade when ns2/bar needs ns1/foo",
|
||||
loc: location(),
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: bar
|
||||
chart: mychart2
|
||||
namespace: ns2
|
||||
needs:
|
||||
- ns1/foo
|
||||
- name: foo
|
||||
chart: mychart1
|
||||
namespace: ns1
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
upgraded: []exectest.Release{},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: bar
|
||||
3: chart: mychart2
|
||||
4: namespace: ns2
|
||||
5: needs:
|
||||
6: - ns1/foo
|
||||
7: - name: foo
|
||||
8: chart: mychart1
|
||||
9: namespace: ns1
|
||||
10:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: bar
|
||||
3: chart: mychart2
|
||||
4: namespace: ns2
|
||||
5: needs:
|
||||
6: - ns1/foo
|
||||
7: - name: foo
|
||||
8: chart: mychart1
|
||||
9: namespace: ns1
|
||||
10:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) found in helmfile.yaml
|
||||
|
||||
Affected releases are:
|
||||
bar (mychart2) UPDATED
|
||||
foo (mychart1) UPDATED
|
||||
@@ -705,16 +811,16 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
@@ -737,16 +843,16 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
@@ -771,16 +877,16 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
@@ -803,16 +909,16 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
@@ -835,16 +941,16 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
@@ -867,16 +973,16 @@ releases:
|
||||
`,
|
||||
},
|
||||
detailedExitcode: true,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^foo$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default
|
||||
`,
|
||||
exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault--deployed--failed--pending"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
exectest.ListKey{Filter: "^bar$", Flags: helmV2ListFlags}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
@@ -888,8 +994,9 @@ bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 defau
|
||||
//
|
||||
{
|
||||
// see https://github.com/roboll/helmfile/issues/919#issuecomment-549831747
|
||||
name: "upgrades with good selector",
|
||||
loc: location(),
|
||||
name: "upgrades with good selector with --skip-needs=true",
|
||||
loc: location(),
|
||||
flags: flags{skipNeeds: true},
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
@@ -925,7 +1032,7 @@ releases:
|
||||
upgraded: []exectest.Release{},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
error: "Identified at least on change",
|
||||
error: "Identified at least one change",
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
@@ -993,6 +1100,112 @@ Affected releases are:
|
||||
external-secrets (incubator/raw) UPDATED
|
||||
my-release (incubator/raw) UPDATED
|
||||
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "upgrades with good selector with --skip-needs=false",
|
||||
loc: location(),
|
||||
flags: flags{skipNeeds: false},
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
{{ $mark := "a" }}
|
||||
|
||||
releases:
|
||||
- name: kubernetes-external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: kube-system
|
||||
|
||||
- name: external-secrets
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- kube-system/kubernetes-external-secrets
|
||||
|
||||
- name: my-release
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
labels:
|
||||
app: test
|
||||
needs:
|
||||
- default/external-secrets
|
||||
`,
|
||||
},
|
||||
selectors: []string{"app=test"},
|
||||
detailedExitcode: true,
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
exectest.DiffKey{Name: "external-secrets", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
exectest.DiffKey{Name: "my-release", Chart: "incubator/raw", Flags: "--kube-contextdefault--namespacedefault--detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
upgraded: []exectest.Release{},
|
||||
// as we check for log output, set concurrency to 1 to avoid non-deterministic test result
|
||||
concurrency: 1,
|
||||
error: `in ./helmfile.yaml: release "default/default/external-secrets" depends on "default/kube-system/kubernetes-external-secrets" which does not match the selectors. Please add a selector like "--selector name=kubernetes-external-secrets", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies`,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1:
|
||||
2:
|
||||
3: releases:
|
||||
4: - name: kubernetes-external-secrets
|
||||
5: chart: incubator/raw
|
||||
6: namespace: kube-system
|
||||
7:
|
||||
8: - name: external-secrets
|
||||
9: chart: incubator/raw
|
||||
10: namespace: default
|
||||
11: labels:
|
||||
12: app: test
|
||||
13: needs:
|
||||
14: - kube-system/kubernetes-external-secrets
|
||||
15:
|
||||
16: - name: my-release
|
||||
17: chart: incubator/raw
|
||||
18: namespace: default
|
||||
19: labels:
|
||||
20: app: test
|
||||
21: needs:
|
||||
22: - default/external-secrets
|
||||
23:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) matching app=test found in helmfile.yaml
|
||||
|
||||
err: release "default/default/external-secrets" depends on "default/kube-system/kubernetes-external-secrets" which does not match the selectors. Please add a selector like "--selector name=kubernetes-external-secrets", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -1124,7 +1337,7 @@ releases:
|
||||
upgraded: []exectest.Release{},
|
||||
deleted: []exectest.Release{},
|
||||
concurrency: 1,
|
||||
error: `in ./helmfile.yaml: "foo" depends on nonexistent release "bar"`,
|
||||
error: `in ./helmfile.yaml: release "default//foo" depends on "default//bar" which does not match the selectors. Please add a selector like "--selector name=bar", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies`,
|
||||
log: `processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
@@ -1160,7 +1373,7 @@ second-pass rendering result of "helmfile.yaml.part.0":
|
||||
merged environment: &{default map[] map[]}
|
||||
2 release(s) found in helmfile.yaml
|
||||
|
||||
err: "foo" depends on nonexistent release "bar"
|
||||
err: release "default//foo" depends on "default//bar" which does not match the selectors. Please add a selector like "--selector name=bar", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies
|
||||
`,
|
||||
},
|
||||
}
|
||||
@@ -1239,13 +1452,16 @@ err: "foo" depends on nonexistent release "bar"
|
||||
concurrency: tc.concurrency,
|
||||
logger: logger,
|
||||
detailedExitcode: tc.detailedExitcode,
|
||||
skipNeeds: tc.flags.skipNeeds,
|
||||
})
|
||||
if tc.error == "" && diffErr != nil {
|
||||
t.Fatalf("unexpected error for data defined at %s: %v", tc.loc, diffErr)
|
||||
} else if tc.error != "" && diffErr == nil {
|
||||
t.Fatalf("expected error did not occur for data defined at %s", tc.loc)
|
||||
} else if tc.error != "" && diffErr != nil && tc.error != diffErr.Error() {
|
||||
t.Fatalf("invalid error: expected %q, got %q", tc.error, diffErr.Error())
|
||||
|
||||
var diffErrStr string
|
||||
if diffErr != nil {
|
||||
diffErrStr = diffErr.Error()
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.error, diffErrStr); d != "" {
|
||||
t.Fatalf("invalid error: want (-), got (+): %s", d)
|
||||
}
|
||||
|
||||
if len(wantUpgrades) > len(helm.Releases) {
|
||||
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
|
||||
func FormatAsTable(releases []*HelmRelease) error {
|
||||
table := uitable.New()
|
||||
table.AddRow("NAME", "NAMESPACE", "ENABLED", "LABELS")
|
||||
table.AddRow("NAME", "NAMESPACE", "ENABLED", "LABELS", "CHART", "VERSION")
|
||||
|
||||
for _, r := range releases {
|
||||
table.AddRow(r.Name, r.Namespace, fmt.Sprintf("%t", r.Enabled), r.Labels)
|
||||
table.AddRow(r.Name, r.Namespace, fmt.Sprintf("%t", r.Enabled), r.Labels, r.Chart, r.Version)
|
||||
}
|
||||
|
||||
fmt.Println(table.String())
|
||||
|
||||
@@ -15,6 +15,8 @@ type LoadOpts struct {
|
||||
CalleePath string
|
||||
|
||||
Reverse bool
|
||||
|
||||
Filter bool
|
||||
}
|
||||
|
||||
func (o LoadOpts) DeepCopy() LoadOpts {
|
||||
|
||||
+14
-3
@@ -22,7 +22,14 @@ func (helm *noCallHelmExec) TemplateRelease(name, chart string, flags ...string)
|
||||
helm.doPanic()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (helm *noCallHelmExec) ChartPull(chart string, flags ...string) error {
|
||||
helm.doPanic()
|
||||
return nil
|
||||
}
|
||||
func (helm *noCallHelmExec) ChartExport(chart string, path string, flags ...string) error {
|
||||
helm.doPanic()
|
||||
return nil
|
||||
}
|
||||
func (helm *noCallHelmExec) UpdateDeps(chart string) error {
|
||||
helm.doPanic()
|
||||
return nil
|
||||
@@ -41,7 +48,7 @@ func (helm *noCallHelmExec) SetHelmBinary(bin string) {
|
||||
helm.doPanic()
|
||||
return
|
||||
}
|
||||
func (helm *noCallHelmExec) AddRepo(name, repository, cafile, certfile, keyfile, username, password string) error {
|
||||
func (helm *noCallHelmExec) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string) error {
|
||||
helm.doPanic()
|
||||
return nil
|
||||
}
|
||||
@@ -49,6 +56,10 @@ func (helm *noCallHelmExec) UpdateRepo() error {
|
||||
helm.doPanic()
|
||||
return nil
|
||||
}
|
||||
func (helm *noCallHelmExec) RegistryLogin(name string, username string, password string) error {
|
||||
helm.doPanic()
|
||||
return nil
|
||||
}
|
||||
func (helm *noCallHelmExec) SyncRelease(context helmexec.HelmContext, name, chart string, flags ...string) error {
|
||||
helm.doPanic()
|
||||
return nil
|
||||
@@ -97,7 +108,7 @@ func (helm *noCallHelmExec) GetVersion() helmexec.Version {
|
||||
return helmexec.Version{}
|
||||
}
|
||||
|
||||
func (helm *noCallHelmExec) IsVersionAtLeast(major int, minor int) bool {
|
||||
func (helm *noCallHelmExec) IsVersionAtLeast(versionStr string) bool {
|
||||
helm.doPanic()
|
||||
return false
|
||||
}
|
||||
|
||||
+46
-127
@@ -2,13 +2,14 @@ package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/roboll/helmfile/pkg/argparser"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/state"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/argparser"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/state"
|
||||
)
|
||||
|
||||
type Run struct {
|
||||
@@ -16,12 +17,16 @@ type Run struct {
|
||||
helm helmexec.Interface
|
||||
ctx Context
|
||||
|
||||
ReleaseToChart map[string]string
|
||||
ReleaseToChart map[state.PrepareChartKey]string
|
||||
|
||||
Ask func(string) bool
|
||||
}
|
||||
|
||||
func NewRun(st *state.HelmState, helm helmexec.Interface, ctx Context) *Run {
|
||||
if helm == nil {
|
||||
panic("Assertion failed: helmexec.Interface must not be nil")
|
||||
}
|
||||
|
||||
return &Run{state: st, helm: helm, ctx: ctx}
|
||||
}
|
||||
|
||||
@@ -32,34 +37,37 @@ func (r *Run) askForConfirmation(msg string) bool {
|
||||
return AskForConfirmation(msg)
|
||||
}
|
||||
|
||||
func (r *Run) withReposAndPreparedCharts(forceDownload bool, skipRepos bool, f func()) []error {
|
||||
if !skipRepos {
|
||||
ctx := r.ctx
|
||||
if errs := ctx.SyncReposOnce(r.state, r.helm); errs != nil && len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.withPreparedCharts(forceDownload, f); err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Run) withPreparedCharts(forceDownload bool, f func()) error {
|
||||
func (r *Run) withPreparedCharts(helmfileCommand string, opts state.ChartPrepareOptions, f func()) error {
|
||||
if r.ReleaseToChart != nil {
|
||||
panic("Run.PrepareCharts can be called only once")
|
||||
}
|
||||
|
||||
if !opts.SkipRepos {
|
||||
ctx := r.ctx
|
||||
if err := ctx.SyncReposOnce(r.state, r.helm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Create tmp directory and bail immediately if it fails
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
var dir string
|
||||
if len(opts.OutputDir) == 0 {
|
||||
tempDir, err := ioutil.TempDir("", "helmfile*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
dir = tempDir
|
||||
} else {
|
||||
dir = opts.OutputDir
|
||||
fmt.Printf("Charts will be downloaded to: %s\n", dir)
|
||||
}
|
||||
|
||||
if _, err := r.state.TriggerGlobalPrepareEvent(helmfileCommand); err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
releaseToChart, errs := state.PrepareCharts(r.helm, r.state, dir, 2, "template", forceDownload)
|
||||
releaseToChart, errs := r.state.PrepareCharts(r.helm, dir, 2, helmfileCommand, opts)
|
||||
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("%v", errs)
|
||||
@@ -68,14 +76,23 @@ func (r *Run) withPreparedCharts(forceDownload bool, f func()) error {
|
||||
for i := range r.state.Releases {
|
||||
rel := &r.state.Releases[i]
|
||||
|
||||
rel.Chart = releaseToChart[rel.Name]
|
||||
key := state.PrepareChartKey{
|
||||
Name: rel.Name,
|
||||
Namespace: rel.Namespace,
|
||||
KubeContext: rel.KubeContext,
|
||||
}
|
||||
if chart := releaseToChart[key]; chart != "" {
|
||||
rel.Chart = chart
|
||||
}
|
||||
}
|
||||
|
||||
r.ReleaseToChart = releaseToChart
|
||||
|
||||
f()
|
||||
|
||||
return nil
|
||||
_, err := r.state.TriggerGlobalCleanupEvent(helmfileCommand)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Run) Deps(c DepsConfigProvider) []error {
|
||||
@@ -84,7 +101,7 @@ func (r *Run) Deps(c DepsConfigProvider) []error {
|
||||
return r.state.UpdateDeps(r.helm)
|
||||
}
|
||||
|
||||
func (r *Run) Repos(c ReposConfigProvider) []error {
|
||||
func (r *Run) Repos(c ReposConfigProvider) error {
|
||||
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||
|
||||
return r.ctx.SyncReposOnce(r.state, r.helm)
|
||||
@@ -100,115 +117,17 @@ func (r *Run) DeprecatedSyncCharts(c DeprecatedChartsConfigProvider) []error {
|
||||
return errs
|
||||
}
|
||||
|
||||
func (r *Run) Status(c StatusesConfigProvider) []error {
|
||||
workers := c.Concurrency()
|
||||
|
||||
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||
|
||||
return r.state.ReleaseStatuses(r.helm, workers)
|
||||
}
|
||||
|
||||
func (r *Run) Diff(c DiffConfigProvider) (*string, bool, bool, []error) {
|
||||
func (r *Run) diff(triggerCleanupEvent bool, detailedExitCode bool, c DiffConfigProvider, diffOpts *state.DiffOpts) (*string, map[string]state.ReleaseSpec, map[string]state.ReleaseSpec, []error) {
|
||||
st := r.state
|
||||
helm := r.helm
|
||||
|
||||
allReleases := st.GetReleasesWithOverrides()
|
||||
|
||||
toDiff, err := st.GetSelectedReleasesWithOverrides()
|
||||
if err != nil {
|
||||
return nil, false, false, []error{err}
|
||||
}
|
||||
|
||||
if len(toDiff) == 0 {
|
||||
return nil, false, false, nil
|
||||
}
|
||||
|
||||
// Do build deps and prepare only on selected releases so that we won't waste time
|
||||
// on running various helm commands on unnecessary releases
|
||||
st.Releases = toDiff
|
||||
|
||||
if !c.SkipDeps() {
|
||||
if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
|
||||
return nil, false, false, errs
|
||||
}
|
||||
}
|
||||
if errs := st.PrepareReleases(helm, "diff"); errs != nil && len(errs) > 0 {
|
||||
return nil, false, false, errs
|
||||
}
|
||||
|
||||
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||
|
||||
opts := &state.DiffOpts{
|
||||
Context: c.Context(),
|
||||
NoColor: c.NoColor(),
|
||||
Set: c.Set(),
|
||||
}
|
||||
|
||||
// Validate all releases for missing `needs` targets
|
||||
st.Releases = allReleases
|
||||
|
||||
if _, err := st.PlanReleases(false); err != nil {
|
||||
return nil, false, false, []error{err}
|
||||
}
|
||||
|
||||
// Diff only targeted releases
|
||||
|
||||
st.Releases = toDiff
|
||||
|
||||
filtered := &Run{
|
||||
state: st,
|
||||
helm: r.helm,
|
||||
ctx: r.ctx,
|
||||
Ask: r.Ask,
|
||||
}
|
||||
|
||||
infoMsg, updated, deleted, errs := filtered.diff(true, c.DetailedExitcode(), c, opts)
|
||||
|
||||
return infoMsg, true, len(deleted) > 0 || len(updated) > 0, errs
|
||||
}
|
||||
|
||||
func (r *Run) Test(c TestConfigProvider) []error {
|
||||
cleanup := c.Cleanup()
|
||||
timeout := c.Timeout()
|
||||
concurrency := c.Concurrency()
|
||||
|
||||
r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...)
|
||||
|
||||
return r.state.TestReleases(r.helm, cleanup, timeout, concurrency)
|
||||
}
|
||||
|
||||
func (r *Run) Lint(c LintConfigProvider) []error {
|
||||
st := r.state
|
||||
helm := r.helm
|
||||
|
||||
values := c.Values()
|
||||
args := argparser.GetArgs(c.Args(), st)
|
||||
workers := c.Concurrency()
|
||||
if !c.SkipDeps() {
|
||||
if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
}
|
||||
if errs := st.PrepareReleases(helm, "lint"); errs != nil && len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
opts := &state.LintOpts{
|
||||
Set: c.Set(),
|
||||
}
|
||||
return st.LintReleases(helm, values, args, workers, opts)
|
||||
}
|
||||
|
||||
func (run *Run) diff(triggerCleanupEvent bool, detailedExitCode bool, c DiffConfigProvider, diffOpts *state.DiffOpts) (*string, map[string]state.ReleaseSpec, map[string]state.ReleaseSpec, []error) {
|
||||
st := run.state
|
||||
helm := run.helm
|
||||
|
||||
var changedReleases []state.ReleaseSpec
|
||||
var deletingReleases []state.ReleaseSpec
|
||||
var planningErrs []error
|
||||
|
||||
// TODO Better way to detect diff on only filtered releases
|
||||
{
|
||||
changedReleases, planningErrs = st.DiffReleases(helm, c.Values(), c.Concurrency(), detailedExitCode, c.IncludeTests(), c.SuppressSecrets(), c.SuppressDiff(), triggerCleanupEvent, diffOpts)
|
||||
changedReleases, planningErrs = st.DiffReleases(helm, c.Values(), c.Concurrency(), detailedExitCode, c.IncludeTests(), c.SuppressSecrets(), c.ShowSecrets(), c.SuppressDiff(), triggerCleanupEvent, diffOpts)
|
||||
|
||||
var err error
|
||||
deletingReleases, err = st.DetectReleasesToBeDeletedForSync(helm, st.Releases)
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func assertEqualsToSnapshot(t *testing.T, name string, data string) {
|
||||
type thisPkgLocator struct{}
|
||||
|
||||
t.Helper()
|
||||
|
||||
snapshotFileName := snapshotFileName(t, name)
|
||||
|
||||
if os.Getenv("HELMFILE_UPDATE_SNAPSHOT") != "" {
|
||||
update(t, snapshotFileName, []byte(data))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
wantData, err := ioutil.ReadFile(snapshotFileName)
|
||||
if err != nil {
|
||||
t.Fatalf(
|
||||
"Snapshot file %q does not exist. Rerun this test with `HELMFILE_UPDATE_SNAPSHOT=1 go test -v -run %s %s` to create the snapshot",
|
||||
snapshotFileName,
|
||||
t.Name(),
|
||||
reflect.TypeOf(thisPkgLocator{}).PkgPath(),
|
||||
)
|
||||
}
|
||||
|
||||
want := string(wantData)
|
||||
|
||||
if d := cmp.Diff(want, data); d != "" {
|
||||
t.Errorf("unexpected %s: want (-), got (+): %s", name, d)
|
||||
t.Errorf(
|
||||
"If you think this is due to the snapshot file being outdated, rerun this test with `HELMFILE_UPDATE_SNAPSHOT=1 go test -v -run %s %s` to update the snapshot",
|
||||
t.Name(),
|
||||
reflect.TypeOf(thisPkgLocator{}).PkgPath(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func update(t *testing.T, snapshotFileName string, data []byte) {
|
||||
t.Helper()
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(snapshotFileName), 0755); err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(snapshotFileName, data, 0644); err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func snapshotFileName(t *testing.T, name string) string {
|
||||
dir := filepath.Join(strings.Split(strings.ToLower(t.Name()), "/")...)
|
||||
|
||||
return filepath.Join("testdata", dir, name)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
processing file "helmfile.yaml" in directory "."
|
||||
first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode=<nil>
|
||||
first-pass uses: &{default map[] map[]}
|
||||
first-pass rendering output of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: foo
|
||||
3: chart: incubator/raw
|
||||
4: namespace: default
|
||||
5: labels:
|
||||
6: app: test
|
||||
7: component: raw
|
||||
8: index: '1'
|
||||
9:
|
||||
10: - name: foo
|
||||
11: chart: incubator/raw
|
||||
12: namespace: default
|
||||
13: labels:
|
||||
14: app: test
|
||||
15: component: raw
|
||||
16: index: '2'
|
||||
17:
|
||||
|
||||
first-pass produced: &{default map[] map[]}
|
||||
first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]}
|
||||
vals:
|
||||
map[]
|
||||
defaultVals:[]
|
||||
second-pass rendering result of "helmfile.yaml.part.0":
|
||||
0:
|
||||
1: releases:
|
||||
2: - name: foo
|
||||
3: chart: incubator/raw
|
||||
4: namespace: default
|
||||
5: labels:
|
||||
6: app: test
|
||||
7: component: raw
|
||||
8: index: '1'
|
||||
9:
|
||||
10: - name: foo
|
||||
11: chart: incubator/raw
|
||||
12: namespace: default
|
||||
13: labels:
|
||||
14: app: test
|
||||
15: component: raw
|
||||
16: index: '2'
|
||||
17:
|
||||
|
||||
merged environment: &{default map[] map[]}
|
||||
1 release(s) matching index=1 found in helmfile.yaml
|
||||
|
||||
Affected releases are:
|
||||
foo (incubator/raw) UPDATED
|
||||
|
||||
processing 1 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default/default/foo
|
||||
|
||||
processing releases in group 1/1: default/default/foo
|
||||
getting deployed release version failed:unexpected list key: {^foo$ --kube-contextdefault--deleting--deployed--failed--pending}
|
||||
|
||||
UPDATED RELEASES:
|
||||
NAME CHART VERSION
|
||||
foo incubator/raw
|
||||
|
||||
@@ -3,11 +3,12 @@ package app
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/roboll/helmfile/pkg/environment"
|
||||
"github.com/roboll/helmfile/pkg/state"
|
||||
"github.com/roboll/helmfile/pkg/tmpl"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func prependLineNumbers(text string) string {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/roboll/helmfile/pkg/remote"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -14,6 +15,8 @@ import (
|
||||
|
||||
func makeLoader(files map[string]string, env string) (*desiredStateLoader, *testhelper.TestFs) {
|
||||
testfs := testhelper.NewTestFs(files)
|
||||
logger := helmexec.NewLogger(os.Stdout, "debug")
|
||||
r := remote.NewRemote(logger, testfs.Cwd, testfs.ReadFile, testfs.DirectoryExistsAt, testfs.FileExistsAt)
|
||||
return &desiredStateLoader{
|
||||
env: env,
|
||||
namespace: "namespace",
|
||||
@@ -22,6 +25,7 @@ func makeLoader(files map[string]string, env string) (*desiredStateLoader, *test
|
||||
fileExists: testfs.FileExists,
|
||||
abs: testfs.Abs,
|
||||
glob: testfs.Glob,
|
||||
remote: r,
|
||||
}, testfs
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,11 @@ func TestGetArgs(t *testing.T) {
|
||||
args := "--timeout=3600 --set app1.bootstrap=true --set app2.bootstrap=false --tiller-namespace ns"
|
||||
defaultArgs := []string{"--recreate-pods", "--force"}
|
||||
Helmdefaults := state.HelmSpec{KubeContext: "test", TillerNamespace: "test-namespace", Args: defaultArgs}
|
||||
testState := &state.HelmState{HelmDefaults: Helmdefaults}
|
||||
testState := &state.HelmState{
|
||||
ReleaseSetSpec: state.ReleaseSetSpec{
|
||||
HelmDefaults: Helmdefaults,
|
||||
},
|
||||
}
|
||||
receivedArgs := GetArgs(args, testState)
|
||||
|
||||
expectedOutput := "--timeout=3600 --set app1.bootstrap=true --set app2.bootstrap=false --tiller-namespace ns --recreate-pods --force"
|
||||
@@ -24,7 +28,11 @@ func Test2(t *testing.T) {
|
||||
args := "--timeout=3600 --set app1.bootstrap=true --set app2.bootstrap=false,app3.bootstrap=true --tiller-namespace ns"
|
||||
defaultArgs := []string{"--recreate-pods", "--force"}
|
||||
Helmdefaults := state.HelmSpec{KubeContext: "test", TillerNamespace: "test-namespace", Args: defaultArgs}
|
||||
testState := &state.HelmState{HelmDefaults: Helmdefaults}
|
||||
testState := &state.HelmState{
|
||||
ReleaseSetSpec: state.ReleaseSetSpec{
|
||||
HelmDefaults: Helmdefaults,
|
||||
},
|
||||
}
|
||||
receivedArgs := GetArgs(args, testState)
|
||||
|
||||
expectedOutput := "--timeout=3600 --set app1.bootstrap=true --set app2.bootstrap=false,app3.bootstrap=true --tiller-namespace ns --recreate-pods --force"
|
||||
|
||||
+32
-9
@@ -2,7 +2,6 @@ package event
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/environment"
|
||||
@@ -12,11 +11,12 @@ import (
|
||||
)
|
||||
|
||||
type Hook struct {
|
||||
Name string `yaml:"name"`
|
||||
Events []string `yaml:"events"`
|
||||
Command string `yaml:"command"`
|
||||
Args []string `yaml:"args"`
|
||||
ShowLogs bool `yaml:"showlogs"`
|
||||
Name string `yaml:"name"`
|
||||
Events []string `yaml:"events"`
|
||||
Command string `yaml:"command"`
|
||||
Kubectl map[string]string `yaml:"kubectlApply,omitempty"`
|
||||
Args []string `yaml:"args"`
|
||||
ShowLogs bool `yaml:"showlogs"`
|
||||
}
|
||||
|
||||
type event struct {
|
||||
@@ -31,6 +31,7 @@ type Bus struct {
|
||||
BasePath string
|
||||
StateFilePath string
|
||||
Namespace string
|
||||
Chart string
|
||||
|
||||
Env environment.Environment
|
||||
|
||||
@@ -41,7 +42,8 @@ type Bus struct {
|
||||
func (bus *Bus) Trigger(evt string, evtErr error, context map[string]interface{}) (bool, error) {
|
||||
if bus.Runner == nil {
|
||||
bus.Runner = helmexec.ShellRunner{
|
||||
Dir: bus.BasePath,
|
||||
Dir: bus.BasePath,
|
||||
Logger: bus.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,10 +62,31 @@ func (bus *Bus) Trigger(evt string, evtErr error, context map[string]interface{}
|
||||
|
||||
name := hook.Name
|
||||
if name == "" {
|
||||
name = hook.Command
|
||||
if hook.Kubectl != nil {
|
||||
name = "kubectlApply"
|
||||
} else {
|
||||
name = hook.Command
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "%s: basePath=%s\n", bus.StateFilePath, bus.BasePath)
|
||||
if hook.Kubectl != nil {
|
||||
if hook.Command != "" {
|
||||
bus.Logger.Warnf("warn: ignoring command '%s' given within a kubectlApply hook", hook.Command)
|
||||
}
|
||||
hook.Command = "kubectl"
|
||||
if val, found := hook.Kubectl["filename"]; found {
|
||||
if _, found := hook.Kubectl["kustomize"]; found {
|
||||
return false, fmt.Errorf("hook[%s]: kustomize & filename cannot be used together", name)
|
||||
}
|
||||
hook.Args = append([]string{"apply", "-f"}, val)
|
||||
} else if val, found := hook.Kubectl["kustomize"]; found {
|
||||
hook.Args = append([]string{"apply", "-k"}, val)
|
||||
} else {
|
||||
return false, fmt.Errorf("hook[%s]: either kustomize or filename must be given", name)
|
||||
}
|
||||
}
|
||||
|
||||
bus.Logger.Debugf("hook[%s]: stateFilePath=%s, basePath=%s\n", name, bus.StateFilePath, bus.BasePath)
|
||||
|
||||
data := map[string]interface{}{
|
||||
"Environment": bus.Env,
|
||||
|
||||
+67
-6
@@ -2,6 +2,7 @@ package event
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
@@ -16,6 +17,10 @@ var logger = helmexec.NewLogger(os.Stdout, "warn")
|
||||
type runner struct {
|
||||
}
|
||||
|
||||
func (r *runner) ExecuteStdIn(cmd string, args []string, env map[string]string, stdin io.Reader) ([]byte, error) {
|
||||
return []byte(""), nil
|
||||
}
|
||||
|
||||
func (r *runner) Execute(cmd string, args []string, env map[string]string) ([]byte, error) {
|
||||
if cmd == "ng" {
|
||||
return nil, fmt.Errorf("cmd failed due to invalid cmd: %s", cmd)
|
||||
@@ -38,21 +43,21 @@ func TestTrigger(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
"okhook1",
|
||||
&Hook{"okhook1", []string{"foo"}, "ok", []string{}, true},
|
||||
&Hook{"okhook1", []string{"foo"}, "ok", nil, []string{}, true},
|
||||
"foo",
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"okhooké",
|
||||
&Hook{"okhook2", []string{"foo"}, "ok", []string{}, false},
|
||||
&Hook{"okhook2", []string{"foo"}, "ok", nil, []string{}, false},
|
||||
"foo",
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"missinghook1",
|
||||
&Hook{"okhook1", []string{"foo"}, "ok", []string{}, false},
|
||||
&Hook{"okhook1", []string{"foo"}, "ok", nil, []string{}, false},
|
||||
"bar",
|
||||
false,
|
||||
"",
|
||||
@@ -66,18 +71,74 @@ func TestTrigger(t *testing.T) {
|
||||
},
|
||||
{
|
||||
"nghook1",
|
||||
&Hook{"nghook1", []string{"foo"}, "ng", []string{}, false},
|
||||
&Hook{"nghook1", []string{"foo"}, "ng", nil, []string{}, false},
|
||||
"foo",
|
||||
false,
|
||||
"hook[nghook1]: command `ng` failed: cmd failed due to invalid cmd: ng",
|
||||
},
|
||||
{
|
||||
"nghook2",
|
||||
&Hook{"nghook2", []string{"foo"}, "ok", []string{"ng"}, false},
|
||||
&Hook{"nghook2", []string{"foo"}, "ok", nil, []string{"ng"}, false},
|
||||
"foo",
|
||||
false,
|
||||
"hook[nghook2]: command `ok` failed: cmd failed due to invalid arg: ng",
|
||||
},
|
||||
{
|
||||
"okkubeapply1",
|
||||
&Hook{"okkubeapply1", []string{"foo"}, "", map[string]string{"kustomize": "kustodir"}, []string{}, false},
|
||||
"foo",
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"okkubeapply2",
|
||||
&Hook{"okkubeapply2", []string{"foo"}, "", map[string]string{"filename": "resource.yaml"}, []string{}, false},
|
||||
"foo",
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"kokubeapply",
|
||||
&Hook{"kokubeapply", []string{"foo"}, "", map[string]string{"kustomize": "kustodir", "filename": "resource.yaml"}, []string{}, true},
|
||||
"foo",
|
||||
false,
|
||||
"hook[kokubeapply]: kustomize & filename cannot be used together",
|
||||
},
|
||||
{
|
||||
"kokubeapply2",
|
||||
&Hook{"kokubeapply2", []string{"foo"}, "", map[string]string{}, []string{}, true},
|
||||
"foo",
|
||||
false,
|
||||
"hook[kokubeapply2]: either kustomize or filename must be given",
|
||||
},
|
||||
{
|
||||
"kokubeapply3",
|
||||
&Hook{"", []string{"foo"}, "", map[string]string{}, []string{}, true},
|
||||
"foo",
|
||||
false,
|
||||
"hook[kubectlApply]: either kustomize or filename must be given",
|
||||
},
|
||||
{
|
||||
"warnkubeapply1",
|
||||
&Hook{"warnkubeapply1", []string{"foo"}, "ok", map[string]string{"filename": "resource.yaml"}, []string{}, true},
|
||||
"foo",
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"warnkubeapply2",
|
||||
&Hook{"warnkubeapply2", []string{"foo"}, "", map[string]string{"filename": "resource.yaml"}, []string{"ng"}, true},
|
||||
"foo",
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"warnkubeapply3",
|
||||
&Hook{"warnkubeapply3", []string{"foo"}, "ok", map[string]string{"filename": "resource.yaml"}, []string{"ng"}, true},
|
||||
"foo",
|
||||
true,
|
||||
"",
|
||||
},
|
||||
}
|
||||
readFile := func(filename string) ([]byte, error) {
|
||||
return nil, fmt.Errorf("unexpected call to readFile: %s", filename)
|
||||
@@ -112,7 +173,7 @@ func TestTrigger(t *testing.T) {
|
||||
|
||||
if c.expectedErr != "" {
|
||||
if err == nil {
|
||||
t.Error("error expected, but not occurred")
|
||||
t.Errorf("error expected for case \"%s\", but not occurred", c.name)
|
||||
} else if err.Error() != c.expectedErr {
|
||||
t.Errorf("unexpected error for case \"%s\": expected=%s, actual=%v", c.name, c.expectedErr, err)
|
||||
}
|
||||
|
||||
+23
-11
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
)
|
||||
|
||||
@@ -30,13 +31,15 @@ type Helm struct {
|
||||
Diffed []Release
|
||||
FailOnUnexpectedDiff bool
|
||||
FailOnUnexpectedList bool
|
||||
Version *helmexec.Version
|
||||
Version *semver.Version
|
||||
|
||||
UpdateDepsCallbacks map[string]func(string) error
|
||||
|
||||
DiffMutex *sync.Mutex
|
||||
ChartsMutex *sync.Mutex
|
||||
ReleasesMutex *sync.Mutex
|
||||
|
||||
Helm3 bool
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
@@ -81,13 +84,16 @@ func (helm *Helm) SetExtraArgs(args ...string) {
|
||||
func (helm *Helm) SetHelmBinary(bin string) {
|
||||
return
|
||||
}
|
||||
func (helm *Helm) AddRepo(name, repository, cafile, certfile, keyfile, username, password string) error {
|
||||
helm.Repo = []string{name, repository, cafile, certfile, keyfile, username, password}
|
||||
func (helm *Helm) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string) error {
|
||||
helm.Repo = []string{name, repository, cafile, certfile, keyfile, username, password, managed}
|
||||
return nil
|
||||
}
|
||||
func (helm *Helm) UpdateRepo() error {
|
||||
return nil
|
||||
}
|
||||
func (helm *Helm) RegistryLogin(name string, username string, password string) error {
|
||||
return nil
|
||||
}
|
||||
func (helm *Helm) SyncRelease(context helmexec.HelmContext, name, chart string, flags ...string) error {
|
||||
if strings.Contains(name, "error") {
|
||||
return errors.New("error")
|
||||
@@ -157,25 +163,31 @@ func (helm *Helm) Lint(name, chart string, flags ...string) error {
|
||||
func (helm *Helm) TemplateRelease(name, chart string, flags ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (helm *Helm) ChartPull(chart string, flags ...string) error {
|
||||
return nil
|
||||
}
|
||||
func (helm *Helm) ChartExport(chart string, path string, flags ...string) error {
|
||||
return nil
|
||||
}
|
||||
func (helm *Helm) IsHelm3() bool {
|
||||
return false
|
||||
return helm.Helm3
|
||||
}
|
||||
|
||||
func (helm *Helm) GetVersion() helmexec.Version {
|
||||
if helm.Version != nil {
|
||||
return *helm.Version
|
||||
return helmexec.Version{
|
||||
Major: int(helm.Version.Major()),
|
||||
Minor: int(helm.Version.Minor()),
|
||||
Patch: int(helm.Version.Patch()),
|
||||
}
|
||||
|
||||
return helmexec.Version{}
|
||||
}
|
||||
|
||||
func (helm *Helm) IsVersionAtLeast(major int, minor int) bool {
|
||||
func (helm *Helm) IsVersionAtLeast(versionStr string) bool {
|
||||
if helm.Version == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return helm.Version.Major >= major && minor >= helm.Version.Minor
|
||||
ver := semver.MustParse(versionStr)
|
||||
return helm.Version.Equal(ver) || helm.Version.GreaterThan(ver)
|
||||
}
|
||||
|
||||
func (helm *Helm) sync(m *sync.Mutex, f func()) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package helmexec
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
@@ -10,6 +11,7 @@ type HelmContext struct {
|
||||
TillerNamespace string
|
||||
HistoryMax int
|
||||
WorkerIndex int
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
func (context *HelmContext) GetTillerlessArgs(helm *execer) []string {
|
||||
|
||||
+213
-82
@@ -1,16 +1,17 @@
|
||||
package helmexec
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
@@ -18,17 +19,19 @@ import (
|
||||
type decryptedSecret struct {
|
||||
mutex sync.RWMutex
|
||||
bytes []byte
|
||||
err error
|
||||
}
|
||||
|
||||
type execer struct {
|
||||
helmBinary string
|
||||
version Version
|
||||
version semver.Version
|
||||
runner Runner
|
||||
logger *zap.SugaredLogger
|
||||
kubeContext string
|
||||
extra []string
|
||||
decryptedSecretMutex sync.Mutex
|
||||
decryptedSecrets map[string]*decryptedSecret
|
||||
writeTempFile func([]byte) (string, error)
|
||||
}
|
||||
|
||||
func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger {
|
||||
@@ -48,62 +51,48 @@ func NewLogger(writer io.Writer, logLevel string) *zap.SugaredLogger {
|
||||
return zap.New(core).Sugar()
|
||||
}
|
||||
|
||||
func getHelmVersion(helmBinary string, logger *zap.SugaredLogger, runner Runner) Version {
|
||||
func parseHelmVersion(versionStr string) (semver.Version, error) {
|
||||
if len(versionStr) == 0 {
|
||||
return semver.Version{}, nil
|
||||
}
|
||||
|
||||
// Autodetect from `helm verison`
|
||||
bytes, err := runner.Execute(helmBinary, []string{"version", "--client", "--short"}, nil)
|
||||
versionStr = strings.TrimLeft(versionStr, "Client: ")
|
||||
versionStr = strings.TrimRight(versionStr, "\n")
|
||||
|
||||
ver, err := semver.NewVersion(versionStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if bytes == nil || len(bytes) == 0 {
|
||||
return Version{}
|
||||
}
|
||||
|
||||
re := regexp.MustCompile("v(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)")
|
||||
matches := re.FindStringSubmatch(string(bytes))
|
||||
|
||||
result := make(map[string]string)
|
||||
for i, name := range re.SubexpNames() {
|
||||
result[name] = matches[i]
|
||||
}
|
||||
|
||||
major, err := strconv.Atoi(result["major"])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
minor, err := strconv.Atoi(result["minor"])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
patch, err := strconv.Atoi(result["patch"])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return semver.Version{}, fmt.Errorf("error parsing helm version '%s'", versionStr)
|
||||
}
|
||||
|
||||
// Support explicit helm3 opt-in via environment variable
|
||||
if os.Getenv("HELMFILE_HELM3") != "" && major < 3 {
|
||||
return Version{
|
||||
Major: 3,
|
||||
Minor: 0,
|
||||
Patch: 0,
|
||||
}
|
||||
if os.Getenv("HELMFILE_HELM3") != "" && ver.Major() < 3 {
|
||||
return *semver.MustParse("v3.0.0"), nil
|
||||
}
|
||||
|
||||
return Version{
|
||||
Major: major,
|
||||
Minor: minor,
|
||||
Patch: patch,
|
||||
return *ver, nil
|
||||
}
|
||||
|
||||
func getHelmVersion(helmBinary string, runner Runner) (semver.Version, error) {
|
||||
|
||||
// Autodetect from `helm version`
|
||||
outBytes, err := runner.Execute(helmBinary, []string{"version", "--client", "--short"}, nil)
|
||||
if err != nil {
|
||||
return semver.Version{}, fmt.Errorf("error determining helm version: %w", err)
|
||||
}
|
||||
|
||||
return parseHelmVersion(string(outBytes))
|
||||
}
|
||||
|
||||
// New for running helm commands
|
||||
func New(helmBinary string, logger *zap.SugaredLogger, kubeContext string, runner Runner) *execer {
|
||||
// TODO: proper error handling
|
||||
version, err := getHelmVersion(helmBinary, runner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &execer{
|
||||
helmBinary: helmBinary,
|
||||
version: getHelmVersion(helmBinary, logger, runner),
|
||||
version: version,
|
||||
logger: logger,
|
||||
kubeContext: kubeContext,
|
||||
runner: runner,
|
||||
@@ -119,24 +108,46 @@ func (helm *execer) SetHelmBinary(bin string) {
|
||||
helm.helmBinary = bin
|
||||
}
|
||||
|
||||
func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, username, password string) error {
|
||||
func (helm *execer) AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string) error {
|
||||
var args []string
|
||||
var out []byte
|
||||
var err error
|
||||
if name == "" && repository != "" {
|
||||
helm.logger.Infof("empty field name\n")
|
||||
return fmt.Errorf("empty field name")
|
||||
}
|
||||
args = append(args, "repo", "add", name, repository)
|
||||
if certfile != "" && keyfile != "" {
|
||||
args = append(args, "--cert-file", certfile, "--key-file", keyfile)
|
||||
switch managed {
|
||||
case "acr":
|
||||
helm.logger.Infof("Adding repo %v (acr)", name)
|
||||
out, err = helm.azcli(name)
|
||||
case "":
|
||||
args = append(args, "repo", "add", name, repository)
|
||||
|
||||
// See https://github.com/helm/helm/pull/8777
|
||||
if cons, err := semver.NewConstraint(">= 3.3.2"); err == nil {
|
||||
if cons.Check(&helm.version) {
|
||||
args = append(args, "--force-update")
|
||||
}
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if certfile != "" && keyfile != "" {
|
||||
args = append(args, "--cert-file", certfile, "--key-file", keyfile)
|
||||
}
|
||||
if cafile != "" {
|
||||
args = append(args, "--ca-file", cafile)
|
||||
}
|
||||
if username != "" && password != "" {
|
||||
args = append(args, "--username", username, "--password", password)
|
||||
}
|
||||
helm.logger.Infof("Adding repo %v %v", name, repository)
|
||||
out, err = helm.exec(args, map[string]string{})
|
||||
default:
|
||||
helm.logger.Errorf("ERROR: unknown type '%v' for repository %v", managed, name)
|
||||
out = nil
|
||||
err = nil
|
||||
}
|
||||
if cafile != "" {
|
||||
args = append(args, "--ca-file", cafile)
|
||||
}
|
||||
if username != "" && password != "" {
|
||||
args = append(args, "--username", username, "--password", password)
|
||||
}
|
||||
helm.logger.Infof("Adding repo %v %v", name, repository)
|
||||
out, err := helm.exec(args, map[string]string{})
|
||||
helm.info(out)
|
||||
return err
|
||||
}
|
||||
@@ -148,6 +159,23 @@ func (helm *execer) UpdateRepo() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) RegistryLogin(repository string, username string, password string) error {
|
||||
helm.logger.Info("Logging in to registry")
|
||||
args := []string{
|
||||
"registry",
|
||||
"login",
|
||||
repository,
|
||||
"--username",
|
||||
username,
|
||||
"--password-stdin",
|
||||
}
|
||||
buffer := bytes.Buffer{}
|
||||
buffer.Write([]byte(fmt.Sprintf("%s\n", password)))
|
||||
out, err := helm.execStdIn(args, map[string]string{"HELM_EXPERIMENTAL_OCI": "1"}, &buffer)
|
||||
helm.info(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) BuildDeps(name, chart string) error {
|
||||
helm.logger.Infof("Building dependency release=%v, chart=%v", name, chart)
|
||||
out, err := helm.exec([]string{"dependency", "build", chart}, map[string]string{})
|
||||
@@ -174,7 +202,7 @@ func (helm *execer) SyncRelease(context HelmContext, name, chart string, flags .
|
||||
}
|
||||
|
||||
out, err := helm.exec(append(append(preArgs, "upgrade", "--install", "--reset-values", name, chart), flags...), env)
|
||||
helm.write(out)
|
||||
helm.write(nil, out)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -183,7 +211,7 @@ func (helm *execer) ReleaseStatus(context HelmContext, name string, flags ...str
|
||||
preArgs := context.GetTillerlessArgs(helm)
|
||||
env := context.getTillerlessEnv()
|
||||
out, err := helm.exec(append(append(preArgs, "status", name), flags...), env)
|
||||
helm.write(out)
|
||||
helm.write(nil, out)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -210,7 +238,7 @@ func (helm *execer) List(context HelmContext, filter string, flags ...string) (s
|
||||
lines = lines[1:]
|
||||
out = []byte(strings.Join(lines, "\n"))
|
||||
}
|
||||
helm.write(out)
|
||||
helm.write(nil, out)
|
||||
return string(out), err
|
||||
}
|
||||
|
||||
@@ -241,6 +269,7 @@ func (helm *execer) DecryptSecret(context HelmContext, name string, flags ...str
|
||||
out, err := helm.exec(append(append(preArgs, "secrets", "dec", absPath), flags...), env)
|
||||
helm.info(out)
|
||||
if err != nil {
|
||||
secret.err = err
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -249,10 +278,18 @@ func (helm *execer) DecryptSecret(context HelmContext, name string, flags ...str
|
||||
if len(decSuffix) == 0 {
|
||||
decSuffix = ".yaml.dec"
|
||||
}
|
||||
decFilename := strings.Replace(absPath, ".yaml", decSuffix, 1)
|
||||
|
||||
// helm secrets replaces the extension with its suffix ONLY when the extension is ".yaml"
|
||||
var decFilename string
|
||||
if strings.HasSuffix(absPath, ".yaml") {
|
||||
decFilename = strings.Replace(absPath, ".yaml", decSuffix, 1)
|
||||
} else {
|
||||
decFilename = absPath + decSuffix
|
||||
}
|
||||
|
||||
secretBytes, err := ioutil.ReadFile(decFilename)
|
||||
if err != nil {
|
||||
secret.err = err
|
||||
return "", err
|
||||
}
|
||||
secret.bytes = secretBytes
|
||||
@@ -268,18 +305,40 @@ func (helm *execer) DecryptSecret(context HelmContext, name string, flags ...str
|
||||
secret.mutex.RLock()
|
||||
helm.decryptedSecretMutex.Unlock()
|
||||
defer secret.mutex.RUnlock()
|
||||
|
||||
if secret.err != nil {
|
||||
return "", secret.err
|
||||
}
|
||||
}
|
||||
|
||||
tmpFile, err := ioutil.TempFile("", "secret")
|
||||
if err != nil {
|
||||
return "", err
|
||||
tempFile := helm.writeTempFile
|
||||
|
||||
if tempFile == nil {
|
||||
tempFile = func(content []byte) (string, error) {
|
||||
dir := filepath.Dir(name)
|
||||
extension := filepath.Ext(name)
|
||||
tmpFile, err := ioutil.TempFile(dir, "secret*"+extension)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = tmpFile.Write(content)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tmpFile.Name(), nil
|
||||
}
|
||||
}
|
||||
_, err = tmpFile.Write(secret.bytes)
|
||||
|
||||
tmpFileName, err := tempFile(secret.bytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tmpFile.Name(), err
|
||||
helm.logger.Debugf("Decrypted %s into %s", absPath, tmpFileName)
|
||||
|
||||
return tmpFileName, err
|
||||
}
|
||||
|
||||
func (helm *execer) TemplateRelease(name string, chart string, flags ...string) error {
|
||||
@@ -292,12 +351,40 @@ func (helm *execer) TemplateRelease(name string, chart string, flags ...string)
|
||||
}
|
||||
|
||||
out, err := helm.exec(append(args, flags...), map[string]string{})
|
||||
helm.write(out)
|
||||
|
||||
var outputToFile bool
|
||||
|
||||
for _, f := range flags {
|
||||
if strings.HasPrefix("--output-dir", f) {
|
||||
outputToFile = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if outputToFile {
|
||||
// With --output-dir is passed to helm-template,
|
||||
// we can safely direct all the logs from it to our logger.
|
||||
//
|
||||
// It's safe because anything written to stdout by helm-template with output-dir is logs,
|
||||
// like excessive `wrote path/to/output/dir/chart/template/file.yaml` messages,
|
||||
// but manifets.
|
||||
//
|
||||
// See https://github.com/roboll/helmfile/pull/1691#issuecomment-805636021 for more information.
|
||||
helm.info(out)
|
||||
} else {
|
||||
// Always write to stdout for use with e.g. `helmfile template | kubectl apply -f -`
|
||||
helm.write(nil, out)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) DiffRelease(context HelmContext, name, chart string, suppressDiff bool, flags ...string) error {
|
||||
helm.logger.Infof("Comparing release=%v, chart=%v", name, chart)
|
||||
if context.Writer != nil {
|
||||
fmt.Fprintf(context.Writer, "Comparing release=%v, chart=%v\n", name, chart)
|
||||
} else {
|
||||
helm.logger.Infof("Comparing release=%v, chart=%v", name, chart)
|
||||
}
|
||||
preArgs := context.GetTillerlessArgs(helm)
|
||||
env := context.getTillerlessEnv()
|
||||
out, err := helm.exec(append(append(preArgs, "diff", "upgrade", "--reset-values", "--allow-unreleased", name, chart), flags...), env)
|
||||
@@ -315,13 +402,13 @@ func (helm *execer) DiffRelease(context HelmContext, name, chart string, suppres
|
||||
case ExitError:
|
||||
if e.ExitStatus() == 2 {
|
||||
if !(suppressDiff) {
|
||||
helm.write(out)
|
||||
helm.write(context.Writer, out)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if !(suppressDiff) {
|
||||
helm.write(out)
|
||||
helm.write(context.Writer, out)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -329,7 +416,7 @@ func (helm *execer) DiffRelease(context HelmContext, name, chart string, suppres
|
||||
func (helm *execer) Lint(name, chart string, flags ...string) error {
|
||||
helm.logger.Infof("Linting release=%v, chart=%v", name, chart)
|
||||
out, err := helm.exec(append([]string{"lint", chart}, flags...), map[string]string{})
|
||||
helm.write(out)
|
||||
helm.write(nil, out)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -340,12 +427,26 @@ func (helm *execer) Fetch(chart string, flags ...string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) ChartPull(chart string, flags ...string) error {
|
||||
helm.logger.Infof("Pulling %v", chart)
|
||||
out, err := helm.exec(append([]string{"chart", "pull", chart}, flags...), map[string]string{"HELM_EXPERIMENTAL_OCI": "1"})
|
||||
helm.info(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) ChartExport(chart string, path string, flags ...string) error {
|
||||
helm.logger.Infof("Exporting %v", chart)
|
||||
out, err := helm.exec(append([]string{"chart", "export", chart, "--destination", path}, flags...), map[string]string{"HELM_EXPERIMENTAL_OCI": "1"})
|
||||
helm.info(out)
|
||||
return err
|
||||
}
|
||||
|
||||
func (helm *execer) DeleteRelease(context HelmContext, name string, flags ...string) error {
|
||||
helm.logger.Infof("Deleting %v", name)
|
||||
preArgs := context.GetTillerlessArgs(helm)
|
||||
env := context.getTillerlessEnv()
|
||||
out, err := helm.exec(append(append(preArgs, "delete", name), flags...), env)
|
||||
helm.write(out)
|
||||
helm.write(nil, out)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -355,7 +456,7 @@ func (helm *execer) TestRelease(context HelmContext, name string, flags ...strin
|
||||
env := context.getTillerlessEnv()
|
||||
args := []string{"test", name}
|
||||
out, err := helm.exec(append(append(preArgs, args...), flags...), env)
|
||||
helm.write(out)
|
||||
helm.write(nil, out)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -365,13 +466,35 @@ func (helm *execer) exec(args []string, env map[string]string) ([]byte, error) {
|
||||
cmdargs = append(cmdargs, helm.extra...)
|
||||
}
|
||||
if helm.kubeContext != "" {
|
||||
cmdargs = append(cmdargs, "--kube-context", helm.kubeContext)
|
||||
cmdargs = append([]string{"--kube-context", helm.kubeContext}, cmdargs...)
|
||||
}
|
||||
cmd := fmt.Sprintf("exec: %s %s", helm.helmBinary, strings.Join(cmdargs, " "))
|
||||
helm.logger.Debug(cmd)
|
||||
bytes, err := helm.runner.Execute(helm.helmBinary, cmdargs, env)
|
||||
helm.logger.Debugf("%s: %s", cmd, bytes)
|
||||
return bytes, err
|
||||
outBytes, err := helm.runner.Execute(helm.helmBinary, cmdargs, env)
|
||||
return outBytes, err
|
||||
}
|
||||
|
||||
func (helm *execer) execStdIn(args []string, env map[string]string, stdin io.Reader) ([]byte, error) {
|
||||
cmdargs := args
|
||||
if len(helm.extra) > 0 {
|
||||
cmdargs = append(cmdargs, helm.extra...)
|
||||
}
|
||||
if helm.kubeContext != "" {
|
||||
cmdargs = append([]string{"--kube-context", helm.kubeContext}, cmdargs...)
|
||||
}
|
||||
cmd := fmt.Sprintf("exec: %s %s", helm.helmBinary, strings.Join(cmdargs, " "))
|
||||
helm.logger.Debug(cmd)
|
||||
outBytes, err := helm.runner.ExecuteStdIn(helm.helmBinary, cmdargs, env, stdin)
|
||||
return outBytes, err
|
||||
}
|
||||
|
||||
func (helm *execer) azcli(name string) ([]byte, error) {
|
||||
cmdargs := append(strings.Split("acr helm repo add --name", " "), name)
|
||||
cmd := fmt.Sprintf("exec: az %s", strings.Join(cmdargs, " "))
|
||||
helm.logger.Debug(cmd)
|
||||
outBytes, err := helm.runner.Execute("az", cmdargs, map[string]string{})
|
||||
helm.logger.Debugf("%s: %s", cmd, outBytes)
|
||||
return outBytes, err
|
||||
}
|
||||
|
||||
func (helm *execer) info(out []byte) {
|
||||
@@ -380,20 +503,28 @@ func (helm *execer) info(out []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func (helm *execer) write(out []byte) {
|
||||
func (helm *execer) write(w io.Writer, out []byte) {
|
||||
if len(out) > 0 {
|
||||
fmt.Printf("%s\n", out)
|
||||
if w == nil {
|
||||
w = os.Stdout
|
||||
}
|
||||
fmt.Fprintf(w, "%s\n", out)
|
||||
}
|
||||
}
|
||||
|
||||
func (helm *execer) IsHelm3() bool {
|
||||
return helm.version.Major == 3
|
||||
return helm.version.Major() == 3
|
||||
}
|
||||
|
||||
func (helm *execer) GetVersion() Version {
|
||||
return helm.version
|
||||
return Version{
|
||||
Major: int(helm.version.Major()),
|
||||
Minor: int(helm.version.Minor()),
|
||||
Patch: int(helm.version.Patch()),
|
||||
}
|
||||
}
|
||||
|
||||
func (helm *execer) IsVersionAtLeast(major int, minor int) bool {
|
||||
return helm.version.Major >= major && helm.version.Minor >= minor
|
||||
func (helm *execer) IsVersionAtLeast(versionStr string) bool {
|
||||
ver := semver.MustParse(versionStr)
|
||||
return helm.version.Equal(ver) || helm.version.GreaterThan(ver)
|
||||
}
|
||||
|
||||
+118
-75
@@ -3,12 +3,16 @@ package helmexec
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -19,6 +23,10 @@ type mockRunner struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (mock *mockRunner) ExecuteStdIn(cmd string, args []string, env map[string]string, stdin io.Reader) ([]byte, error) {
|
||||
return mock.output, mock.err
|
||||
}
|
||||
|
||||
func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string) ([]byte, error) {
|
||||
return mock.output, mock.err
|
||||
}
|
||||
@@ -71,51 +79,84 @@ func Test_SetHelmBinary(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AddRepo_Helm_3_3_2(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := &execer{
|
||||
helmBinary: "helm",
|
||||
version: *semver.MustParse("3.3.2"),
|
||||
logger: logger,
|
||||
kubeContext: "dev",
|
||||
runner: &mockRunner{},
|
||||
}
|
||||
helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "", "")
|
||||
expected := `Adding repo myRepo https://repo.example.com/
|
||||
exec: helm --kube-context dev repo add myRepo https://repo.example.com/ --force-update --cert-file cert.pem --key-file key.pem
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AddRepo(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "")
|
||||
helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "", "")
|
||||
expected := `Adding repo myRepo https://repo.example.com/
|
||||
exec: helm repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem --kube-context dev
|
||||
exec: helm repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem --kube-context dev:
|
||||
exec: helm --kube-context dev repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
helm.AddRepo("myRepo", "https://repo.example.com/", "ca.crt", "", "", "", "")
|
||||
helm.AddRepo("myRepo", "https://repo.example.com/", "ca.crt", "", "", "", "", "")
|
||||
expected = `Adding repo myRepo https://repo.example.com/
|
||||
exec: helm repo add myRepo https://repo.example.com/ --ca-file ca.crt --kube-context dev
|
||||
exec: helm repo add myRepo https://repo.example.com/ --ca-file ca.crt --kube-context dev:
|
||||
exec: helm --kube-context dev repo add myRepo https://repo.example.com/ --ca-file ca.crt
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "", "")
|
||||
helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "", "", "")
|
||||
expected = `Adding repo myRepo https://repo.example.com/
|
||||
exec: helm repo add myRepo https://repo.example.com/ --kube-context dev
|
||||
exec: helm repo add myRepo https://repo.example.com/ --kube-context dev:
|
||||
exec: helm --kube-context dev repo add myRepo https://repo.example.com/
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password")
|
||||
helm.AddRepo("acrRepo", "", "", "", "", "", "", "acr")
|
||||
expected = `Adding repo acrRepo (acr)
|
||||
exec: az acr helm repo add --name acrRepo
|
||||
exec: az acr helm repo add --name acrRepo:
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
helm.AddRepo("otherRepo", "", "", "", "", "", "", "unknown")
|
||||
expected = `ERROR: unknown type 'unknown' for repository otherRepo
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password", "")
|
||||
expected = `Adding repo myRepo https://repo.example.com/
|
||||
exec: helm repo add myRepo https://repo.example.com/ --username example_user --password example_password --kube-context dev
|
||||
exec: helm repo add myRepo https://repo.example.com/ --username example_user --password example_password --kube-context dev:
|
||||
exec: helm --kube-context dev repo add myRepo https://repo.example.com/ --username example_user --password example_password
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
helm.AddRepo("", "https://repo.example.com/", "", "", "", "", "")
|
||||
helm.AddRepo("", "https://repo.example.com/", "", "", "", "", "", "")
|
||||
expected = `empty field name
|
||||
|
||||
`
|
||||
@@ -130,8 +171,7 @@ func Test_UpdateRepo(t *testing.T) {
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.UpdateRepo()
|
||||
expected := `Updating repo
|
||||
exec: helm repo update --kube-context dev
|
||||
exec: helm repo update --kube-context dev:
|
||||
exec: helm --kube-context dev repo update
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.UpdateRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -142,10 +182,9 @@ func Test_SyncRelease(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.SyncRelease(HelmContext{}, "release", "chart", "--timeout 10", "--wait")
|
||||
helm.SyncRelease(HelmContext{}, "release", "chart", "--timeout 10", "--wait", "--wait-for-jobs")
|
||||
expected := `Upgrading release=release, chart=chart
|
||||
exec: helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev
|
||||
exec: helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev:
|
||||
exec: helm --kube-context dev upgrade --install --reset-values release chart --timeout 10 --wait --wait-for-jobs
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -154,8 +193,7 @@ exec: helm upgrade --install --reset-values release chart --timeout 10 --wait --
|
||||
buffer.Reset()
|
||||
helm.SyncRelease(HelmContext{}, "release", "chart")
|
||||
expected = `Upgrading release=release, chart=chart
|
||||
exec: helm upgrade --install --reset-values release chart --kube-context dev
|
||||
exec: helm upgrade --install --reset-values release chart --kube-context dev:
|
||||
exec: helm --kube-context dev upgrade --install --reset-values release chart
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -167,10 +205,9 @@ func Test_SyncReleaseTillerless(t *testing.T) {
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.SyncRelease(HelmContext{Tillerless: true, TillerNamespace: "foo"}, "release", "chart",
|
||||
"--timeout 10", "--wait")
|
||||
"--timeout 10", "--wait", "--wait-for-jobs")
|
||||
expected := `Upgrading release=release, chart=chart
|
||||
exec: helm tiller run foo -- helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev
|
||||
exec: helm tiller run foo -- helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev:
|
||||
exec: helm --kube-context dev tiller run foo -- helm upgrade --install --reset-values release chart --timeout 10 --wait --wait-for-jobs
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.SyncRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -183,8 +220,7 @@ func Test_UpdateDeps(t *testing.T) {
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.UpdateDeps("./chart/foo")
|
||||
expected := `Updating dependency ./chart/foo
|
||||
exec: helm dependency update ./chart/foo --kube-context dev
|
||||
exec: helm dependency update ./chart/foo --kube-context dev:
|
||||
exec: helm --kube-context dev dependency update ./chart/foo
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.UpdateDeps()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -194,8 +230,7 @@ exec: helm dependency update ./chart/foo --kube-context dev:
|
||||
helm.SetExtraArgs("--verify")
|
||||
helm.UpdateDeps("./chart/foo")
|
||||
expected = `Updating dependency ./chart/foo
|
||||
exec: helm dependency update ./chart/foo --verify --kube-context dev
|
||||
exec: helm dependency update ./chart/foo --verify --kube-context dev:
|
||||
exec: helm --kube-context dev dependency update ./chart/foo --verify
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -208,8 +243,7 @@ func Test_BuildDeps(t *testing.T) {
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.BuildDeps("foo", "./chart/foo")
|
||||
expected := `Building dependency release=foo, chart=./chart/foo
|
||||
exec: helm dependency build ./chart/foo --kube-context dev
|
||||
exec: helm dependency build ./chart/foo --kube-context dev:
|
||||
exec: helm --kube-context dev dependency build ./chart/foo
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.BuildDeps()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -219,8 +253,7 @@ exec: helm dependency build ./chart/foo --kube-context dev:
|
||||
helm.SetExtraArgs("--verify")
|
||||
helm.BuildDeps("foo", "./chart/foo")
|
||||
expected = `Building dependency release=foo, chart=./chart/foo
|
||||
exec: helm dependency build ./chart/foo --verify --kube-context dev
|
||||
exec: helm dependency build ./chart/foo --verify --kube-context dev:
|
||||
exec: helm --kube-context dev dependency build ./chart/foo --verify
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.BuildDeps()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -231,6 +264,12 @@ func Test_DecryptSecret(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "dev")
|
||||
|
||||
tmpFilePath := "path/to/temp/file"
|
||||
helm.writeTempFile = func(content []byte) (string, error) {
|
||||
return tmpFilePath, nil
|
||||
}
|
||||
|
||||
helm.DecryptSecret(HelmContext{}, "secretName")
|
||||
cwd, err := filepath.Abs(".")
|
||||
if err != nil {
|
||||
@@ -241,13 +280,35 @@ func Test_DecryptSecret(t *testing.T) {
|
||||
|
||||
expected := fmt.Sprintf(`Preparing to decrypt secret %v/secretName
|
||||
Decrypting secret %s/secretName
|
||||
exec: helm secrets dec %s/secretName --kube-context dev
|
||||
exec: helm secrets dec %s/secretName --kube-context dev:
|
||||
exec: helm --kube-context dev secrets dec %s/secretName
|
||||
Preparing to decrypt secret %s/secretName
|
||||
Found secret in cache %s/secretName
|
||||
`, cwd, cwd, cwd, cwd, cwd, cwd)
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.DecryptSecret()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
`, cwd, cwd, cwd, cwd, cwd)
|
||||
if d := cmp.Diff(expected, buffer.String()); d != "" {
|
||||
t.Errorf("helmexec.DecryptSecret(): want (-), got (+):\n%s", d)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_DecryptSecretWithGotmpl(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "dev")
|
||||
|
||||
tmpFilePath := "path/to/temp/file"
|
||||
helm.writeTempFile = func(content []byte) (string, error) {
|
||||
return tmpFilePath, nil
|
||||
}
|
||||
|
||||
secretName := "secretName.yaml.gotmpl"
|
||||
_, decryptErr := helm.DecryptSecret(HelmContext{}, secretName)
|
||||
cwd, err := filepath.Abs(".")
|
||||
if err != nil {
|
||||
t.Errorf("Error: %v", err)
|
||||
}
|
||||
|
||||
expected := fmt.Sprintf(`%s/%s.yaml.dec`, cwd, secretName)
|
||||
if d := cmp.Diff(expected, decryptErr.(*os.PathError).Path); d != "" {
|
||||
t.Errorf("helmexec.DecryptSecret(): want (-), got (+):\n%s", d)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,10 +316,9 @@ func Test_DiffRelease(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.DiffRelease(HelmContext{}, "release", "chart", false, "--timeout 10", "--wait")
|
||||
helm.DiffRelease(HelmContext{}, "release", "chart", false, "--timeout 10", "--wait", "--wait-for-jobs")
|
||||
expected := `Comparing release=release, chart=chart
|
||||
exec: helm diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --kube-context dev
|
||||
exec: helm diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --kube-context dev:
|
||||
exec: helm --kube-context dev diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --wait-for-jobs
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.DiffRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -267,8 +327,7 @@ exec: helm diff upgrade --reset-values --allow-unreleased release chart --timeou
|
||||
buffer.Reset()
|
||||
helm.DiffRelease(HelmContext{}, "release", "chart", false)
|
||||
expected = `Comparing release=release, chart=chart
|
||||
exec: helm diff upgrade --reset-values --allow-unreleased release chart --kube-context dev
|
||||
exec: helm diff upgrade --reset-values --allow-unreleased release chart --kube-context dev:
|
||||
exec: helm --kube-context dev diff upgrade --reset-values --allow-unreleased release chart
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.DiffRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -279,10 +338,9 @@ func Test_DiffReleaseTillerless(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.DiffRelease(HelmContext{Tillerless: true}, "release", "chart", false, "--timeout 10", "--wait")
|
||||
helm.DiffRelease(HelmContext{Tillerless: true}, "release", "chart", false, "--timeout 10", "--wait", "--wait-for-jobs")
|
||||
expected := `Comparing release=release, chart=chart
|
||||
exec: helm tiller run -- helm diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --kube-context dev
|
||||
exec: helm tiller run -- helm diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --kube-context dev:
|
||||
exec: helm --kube-context dev tiller run -- helm diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --wait-for-jobs
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.DiffRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -295,8 +353,7 @@ func Test_DeleteRelease(t *testing.T) {
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.DeleteRelease(HelmContext{}, "release")
|
||||
expected := `Deleting release
|
||||
exec: helm delete release --kube-context dev
|
||||
exec: helm delete release --kube-context dev:
|
||||
exec: helm --kube-context dev delete release
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.DeleteRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -308,8 +365,7 @@ func Test_DeleteRelease_Flags(t *testing.T) {
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.DeleteRelease(HelmContext{}, "release", "--purge")
|
||||
expected := `Deleting release
|
||||
exec: helm delete release --purge --kube-context dev
|
||||
exec: helm delete release --purge --kube-context dev:
|
||||
exec: helm --kube-context dev delete release --purge
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.DeleteRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -322,8 +378,7 @@ func Test_TestRelease(t *testing.T) {
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.TestRelease(HelmContext{}, "release")
|
||||
expected := `Testing release
|
||||
exec: helm test release --kube-context dev
|
||||
exec: helm test release --kube-context dev:
|
||||
exec: helm --kube-context dev test release
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.TestRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -335,8 +390,7 @@ func Test_TestRelease_Flags(t *testing.T) {
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.TestRelease(HelmContext{}, "release", "--cleanup", "--timeout", "60")
|
||||
expected := `Testing release
|
||||
exec: helm test release --cleanup --timeout 60 --kube-context dev
|
||||
exec: helm test release --cleanup --timeout 60 --kube-context dev:
|
||||
exec: helm --kube-context dev test release --cleanup --timeout 60
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.TestRelease()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -349,8 +403,7 @@ func Test_ReleaseStatus(t *testing.T) {
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.ReleaseStatus(HelmContext{}, "myRelease")
|
||||
expected := `Getting status myRelease
|
||||
exec: helm status myRelease --kube-context dev
|
||||
exec: helm status myRelease --kube-context dev:
|
||||
exec: helm --kube-context dev status myRelease
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.ReleaseStatus()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -364,7 +417,6 @@ func Test_exec(t *testing.T) {
|
||||
env := map[string]string{}
|
||||
helm.exec([]string{"version"}, env)
|
||||
expected := `exec: helm version
|
||||
exec: helm version:
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -378,9 +430,8 @@ exec: helm version:
|
||||
|
||||
buffer.Reset()
|
||||
helm = MockExecer(logger, "dev")
|
||||
helm.exec([]string{"diff", "release", "chart", "--timeout 10", "--wait"}, env)
|
||||
expected = `exec: helm diff release chart --timeout 10 --wait --kube-context dev
|
||||
exec: helm diff release chart --timeout 10 --wait --kube-context dev:
|
||||
helm.exec([]string{"diff", "release", "chart", "--timeout 10", "--wait", "--wait-for-jobs"}, env)
|
||||
expected = `exec: helm --kube-context dev diff release chart --timeout 10 --wait --wait-for-jobs
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -388,8 +439,7 @@ exec: helm diff release chart --timeout 10 --wait --kube-context dev:
|
||||
|
||||
buffer.Reset()
|
||||
helm.exec([]string{"version"}, env)
|
||||
expected = `exec: helm version --kube-context dev
|
||||
exec: helm version --kube-context dev:
|
||||
expected = `exec: helm --kube-context dev version
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -398,8 +448,7 @@ exec: helm version --kube-context dev:
|
||||
buffer.Reset()
|
||||
helm.SetExtraArgs("foo")
|
||||
helm.exec([]string{"version"}, env)
|
||||
expected = `exec: helm version foo --kube-context dev
|
||||
exec: helm version foo --kube-context dev:
|
||||
expected = `exec: helm --kube-context dev version foo
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -410,7 +459,6 @@ exec: helm version foo --kube-context dev:
|
||||
helm.SetHelmBinary("overwritten")
|
||||
helm.exec([]string{"version"}, env)
|
||||
expected = `exec: overwritten version
|
||||
exec: overwritten version:
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -423,8 +471,7 @@ func Test_Lint(t *testing.T) {
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.Lint("release", "path/to/chart", "--values", "file.yml")
|
||||
expected := `Linting release=release, chart=path/to/chart
|
||||
exec: helm lint path/to/chart --values file.yml --kube-context dev
|
||||
exec: helm lint path/to/chart --values file.yml --kube-context dev:
|
||||
exec: helm --kube-context dev lint path/to/chart --values file.yml
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.Lint()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -437,8 +484,7 @@ func Test_Fetch(t *testing.T) {
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.Fetch("chart", "--version", "1.2.3", "--untar", "--untardir", "/tmp/dir")
|
||||
expected := `Fetching chart
|
||||
exec: helm fetch chart --version 1.2.3 --untar --untardir /tmp/dir --kube-context dev
|
||||
exec: helm fetch chart --version 1.2.3 --untar --untardir /tmp/dir --kube-context dev:
|
||||
exec: helm --kube-context dev fetch chart --version 1.2.3 --untar --untardir /tmp/dir
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.Lint()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -448,7 +494,6 @@ exec: helm fetch chart --version 1.2.3 --untar --untardir /tmp/dir --kube-contex
|
||||
var logLevelTests = map[string]string{
|
||||
"debug": `Adding repo myRepo https://repo.example.com/
|
||||
exec: helm repo add myRepo https://repo.example.com/ --username example_user --password example_password
|
||||
exec: helm repo add myRepo https://repo.example.com/ --username example_user --password example_password:
|
||||
`,
|
||||
"info": `Adding repo myRepo https://repo.example.com/
|
||||
`,
|
||||
@@ -461,7 +506,7 @@ func Test_LogLevels(t *testing.T) {
|
||||
buffer.Reset()
|
||||
logger := NewLogger(&buffer, logLevel)
|
||||
helm := MockExecer(logger, "")
|
||||
helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password")
|
||||
helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password", "")
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.AddRepo()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
}
|
||||
@@ -508,8 +553,7 @@ func Test_Template(t *testing.T) {
|
||||
helm := MockExecer(logger, "dev")
|
||||
helm.TemplateRelease("release", "path/to/chart", "--values", "file.yml")
|
||||
expected := `Templating release=release, chart=path/to/chart
|
||||
exec: helm template path/to/chart --name release --values file.yml --kube-context dev
|
||||
exec: helm template path/to/chart --name release --values file.yml --kube-context dev:
|
||||
exec: helm --kube-context dev template path/to/chart --name release --values file.yml
|
||||
`
|
||||
if buffer.String() != expected {
|
||||
t.Errorf("helmexec.Template()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
@@ -557,16 +601,15 @@ func Test_GetVersion(t *testing.T) {
|
||||
func Test_IsVersionAtLeast(t *testing.T) {
|
||||
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")}
|
||||
helm := New("helm", NewLogger(os.Stdout, "info"), "dev", &helm2Runner)
|
||||
if !helm.IsVersionAtLeast(2, 1) {
|
||||
if !helm.IsVersionAtLeast("2.1.0") {
|
||||
t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1")
|
||||
}
|
||||
|
||||
if helm.IsVersionAtLeast(2, 19) {
|
||||
if helm.IsVersionAtLeast("2.19.0") {
|
||||
t.Error("helmexec.IsVersionAtLeast - 2.16.1 is atleast 2.19")
|
||||
}
|
||||
|
||||
if helm.IsVersionAtLeast(3, 2) {
|
||||
if helm.IsVersionAtLeast("3.2.0") {
|
||||
t.Error("helmexec.IsVersionAtLeast - 2.16.1 is atleast 3.2")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,19 +5,55 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func newExitError(helmCmdPath string, exitStatus int, errorMessage string) ExitError {
|
||||
func newExitError(path string, args []string, exitStatus int, err error, stderr, combined string) ExitError {
|
||||
var out string
|
||||
|
||||
out += fmt.Sprintf("PATH:\n%s", Indent(path, " "))
|
||||
|
||||
out += "\n\nARGS:"
|
||||
for i, a := range args {
|
||||
out += fmt.Sprintf("\n%s", Indent(fmt.Sprintf("%d: %s (%d bytes)", i, a, len(a)), " "))
|
||||
}
|
||||
|
||||
out += fmt.Sprintf("\n\nERROR:\n%s", Indent(err.Error(), " "))
|
||||
|
||||
out += fmt.Sprintf("\n\nEXIT STATUS\n%s", Indent(fmt.Sprintf("%d", exitStatus), " "))
|
||||
|
||||
if len(stderr) > 0 {
|
||||
out += fmt.Sprintf("\n\nSTDERR:\n%s", Indent(stderr, " "))
|
||||
}
|
||||
|
||||
if len(combined) > 0 {
|
||||
out += fmt.Sprintf("\n\nCOMBINED OUTPUT:\n%s", Indent(combined, " "))
|
||||
}
|
||||
|
||||
return ExitError{
|
||||
Message: fmt.Sprintf("the following cmd exited with status %d:\n%s\n\n%s", exitStatus, indent(strings.TrimSpace(helmCmdPath)), indent(strings.TrimSpace(errorMessage))),
|
||||
Message: fmt.Sprintf("command %q exited with non-zero status:\n\n%s", path, out),
|
||||
Code: exitStatus,
|
||||
}
|
||||
}
|
||||
|
||||
func indent(text string) string {
|
||||
// indents a block of text with an indent string
|
||||
func Indent(text, indent string) string {
|
||||
var b strings.Builder
|
||||
|
||||
b.Grow(len(text) * 2)
|
||||
|
||||
lines := strings.Split(text, "\n")
|
||||
for i := range lines {
|
||||
lines[i] = " " + lines[i]
|
||||
|
||||
last := len(lines) - 1
|
||||
|
||||
for i, j := range lines {
|
||||
if i > 0 && i < last && j != "" {
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
if j != "" {
|
||||
b.WriteString(indent + j)
|
||||
}
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ExitError is created whenever your shell command exits with a non-zero exit status
|
||||
|
||||
@@ -12,14 +12,17 @@ type Interface interface {
|
||||
SetExtraArgs(args ...string)
|
||||
SetHelmBinary(bin string)
|
||||
|
||||
AddRepo(name, repository, cafile, certfile, keyfile, username, password string) error
|
||||
AddRepo(name, repository, cafile, certfile, keyfile, username, password string, managed string) error
|
||||
UpdateRepo() error
|
||||
RegistryLogin(name string, username string, password string) error
|
||||
BuildDeps(name, chart string) error
|
||||
UpdateDeps(chart string) error
|
||||
SyncRelease(context HelmContext, name, chart string, flags ...string) error
|
||||
DiffRelease(context HelmContext, name, chart string, suppressDiff bool, flags ...string) error
|
||||
TemplateRelease(name, chart string, flags ...string) error
|
||||
Fetch(chart string, flags ...string) error
|
||||
ChartPull(chart string, flags ...string) error
|
||||
ChartExport(chart string, path string, flags ...string) error
|
||||
Lint(name, chart string, flags ...string) error
|
||||
ReleaseStatus(context HelmContext, name string, flags ...string) error
|
||||
DeleteRelease(context HelmContext, name string, flags ...string) error
|
||||
@@ -28,7 +31,7 @@ type Interface interface {
|
||||
DecryptSecret(context HelmContext, name string, flags ...string) (string, error)
|
||||
IsHelm3() bool
|
||||
GetVersion() Version
|
||||
IsVersionAtLeast(major int, minor int) bool
|
||||
IsVersionAtLeast(versionStr string) bool
|
||||
}
|
||||
|
||||
type DependencyUpdater interface {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+46
-12
@@ -4,11 +4,14 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -19,6 +22,7 @@ const (
|
||||
// Runner interface for shell commands
|
||||
type Runner interface {
|
||||
Execute(cmd string, args []string, env map[string]string) ([]byte, error)
|
||||
ExecuteStdIn(cmd string, args []string, env map[string]string, stdin io.Reader) ([]byte, error)
|
||||
}
|
||||
|
||||
// ShellRunner implemention for shell commands
|
||||
@@ -33,24 +37,46 @@ func (shell ShellRunner) Execute(cmd string, args []string, env map[string]strin
|
||||
preparedCmd := exec.Command(cmd, args...)
|
||||
preparedCmd.Dir = shell.Dir
|
||||
preparedCmd.Env = mergeEnv(os.Environ(), env)
|
||||
return combinedOutput(preparedCmd, shell.Logger)
|
||||
return Output(preparedCmd, &logWriterGenerator{
|
||||
log: shell.Logger,
|
||||
})
|
||||
}
|
||||
|
||||
func combinedOutput(c *exec.Cmd, logger *zap.SugaredLogger) ([]byte, error) {
|
||||
// Execute a shell command
|
||||
func (shell ShellRunner) ExecuteStdIn(cmd string, args []string, env map[string]string, stdin io.Reader) ([]byte, error) {
|
||||
preparedCmd := exec.Command(cmd, args...)
|
||||
preparedCmd.Dir = shell.Dir
|
||||
preparedCmd.Env = mergeEnv(os.Environ(), env)
|
||||
preparedCmd.Stdin = stdin
|
||||
return Output(preparedCmd, &logWriterGenerator{
|
||||
log: shell.Logger,
|
||||
})
|
||||
}
|
||||
|
||||
func Output(c *exec.Cmd, logWriterGenerators ...*logWriterGenerator) ([]byte, error) {
|
||||
if c.Stdout != nil {
|
||||
return nil, errors.New("exec: Stdout already set")
|
||||
}
|
||||
if c.Stderr != nil {
|
||||
return nil, errors.New("exec: Stderr already set")
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
c.Stdout = &stdout
|
||||
c.Stderr = &stderr
|
||||
err := c.Run()
|
||||
var combined bytes.Buffer
|
||||
|
||||
o := stdout.Bytes()
|
||||
e := stderr.Bytes()
|
||||
var logWriters []io.Writer
|
||||
|
||||
id := newExecutionID()
|
||||
for _, g := range logWriterGenerators {
|
||||
logPrefix := fmt.Sprintf("%s:%s> ", filepath.Base(c.Path), id)
|
||||
logWriters = append(logWriters, g.Writer(logPrefix))
|
||||
}
|
||||
|
||||
c.Stdout = io.MultiWriter(append([]io.Writer{&stdout, &combined}, logWriters...)...)
|
||||
c.Stderr = io.MultiWriter(append([]io.Writer{&stderr, &combined}, logWriters...)...)
|
||||
|
||||
err := c.Run()
|
||||
|
||||
if err != nil {
|
||||
// TrimSpace is necessary, because otherwise helmfile prints the redundant new-lines after each error like:
|
||||
@@ -64,14 +90,13 @@ func combinedOutput(c *exec.Cmd, logger *zap.SugaredLogger) ([]byte, error) {
|
||||
// so that helmfile could return its own exit code accordingly
|
||||
waitStatus := ee.Sys().(syscall.WaitStatus)
|
||||
exitStatus := waitStatus.ExitStatus()
|
||||
cmd := fmt.Sprintf("%s %s", c.Path, strings.Join(c.Args, " "))
|
||||
err = newExitError(cmd, exitStatus, string(e))
|
||||
err = newExitError(c.Path, c.Args, exitStatus, ee, stderr.String(), combined.String())
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected error: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
return o, err
|
||||
return stdout.Bytes(), err
|
||||
}
|
||||
|
||||
func mergeEnv(orig []string, new map[string]string) []string {
|
||||
@@ -94,7 +119,16 @@ func env2map(env []string) map[string]string {
|
||||
wanted := map[string]string{}
|
||||
for _, cur := range env {
|
||||
pair := strings.SplitN(cur, "=", 2)
|
||||
wanted[pair[0]] = pair[1]
|
||||
|
||||
var v string
|
||||
|
||||
// An environment can completely miss `=` and the right side.
|
||||
// If we didn't deal with that, this may fail due to an index-out-of-range error
|
||||
if len(pair) > 1 {
|
||||
v = pair[1]
|
||||
}
|
||||
|
||||
wanted[pair[0]] = v
|
||||
}
|
||||
return wanted
|
||||
}
|
||||
|
||||
+18
-1
@@ -149,7 +149,7 @@ func Parse(goGetterSrc string) (*Source, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Remote) Fetch(goGetterSrc string) (string, error) {
|
||||
func (r *Remote) Fetch(goGetterSrc string, cacheDirOpt ...string) (string, error) {
|
||||
u, err := Parse(goGetterSrc)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -167,6 +167,11 @@ func (r *Remote) Fetch(goGetterSrc string) (string, error) {
|
||||
|
||||
// This should be shared across variant commands, so that they can share cache for the shared imports
|
||||
cacheBaseDir := DefaultCacheDir
|
||||
if len(cacheDirOpt) == 1 {
|
||||
cacheBaseDir = cacheDirOpt[0]
|
||||
} else if len(cacheDirOpt) > 0 {
|
||||
return "", fmt.Errorf("[bug] cacheDirOpt's length: want 0 or 1, got %d", len(cacheDirOpt))
|
||||
}
|
||||
|
||||
query := u.RawQuery
|
||||
|
||||
@@ -260,3 +265,15 @@ func (g *GoGetter) Get(wd, src, dst string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewRemote(logger *zap.SugaredLogger, homeDir string, readFile func(string) ([]byte, error), dirExists func(string) bool, fileExists func(string) bool) *Remote {
|
||||
remote := &Remote{
|
||||
Logger: logger,
|
||||
Home: homeDir,
|
||||
Getter: &GoGetter{Logger: logger},
|
||||
ReadFile: readFile,
|
||||
DirExists: dirExists,
|
||||
FileExists: fileExists,
|
||||
}
|
||||
return remote
|
||||
}
|
||||
|
||||
+83
-12
@@ -2,18 +2,20 @@ package remote
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/testhelper"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/testhelper"
|
||||
)
|
||||
|
||||
func TestRemote_HttpsGitHub(t *testing.T) {
|
||||
cleanfs := map[string]string{
|
||||
"path/to/home": "",
|
||||
"/path/to/home": "",
|
||||
}
|
||||
cachefs := map[string]string{
|
||||
"path/to/home/.helmfile/cache/https_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml": "foo: bar",
|
||||
"/path/to/home/.helmfile/cache/https_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml": "foo: bar",
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
@@ -35,7 +37,7 @@ func TestRemote_HttpsGitHub(t *testing.T) {
|
||||
hit := true
|
||||
|
||||
get := func(wd, src, dst string) error {
|
||||
if wd != "path/to/home" {
|
||||
if wd != "/path/to/home" {
|
||||
return fmt.Errorf("unexpected wd: %s", wd)
|
||||
}
|
||||
if src != "git::https://github.com/cloudposse/helmfiles.git?ref=0.40.0" {
|
||||
@@ -52,7 +54,7 @@ func TestRemote_HttpsGitHub(t *testing.T) {
|
||||
}
|
||||
remote := &Remote{
|
||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||
Home: "path/to/home",
|
||||
Home: "/path/to/home",
|
||||
Getter: getter,
|
||||
ReadFile: testfs.ReadFile,
|
||||
FileExists: testfs.FileExistsAt,
|
||||
@@ -71,7 +73,7 @@ func TestRemote_HttpsGitHub(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if file != "path/to/home/.helmfile/cache/https_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml" {
|
||||
if file != "/path/to/home/.helmfile/cache/https_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml" {
|
||||
t.Errorf("unexpected file located: %s", file)
|
||||
}
|
||||
|
||||
@@ -87,10 +89,10 @@ func TestRemote_HttpsGitHub(t *testing.T) {
|
||||
|
||||
func TestRemote_SShGitHub(t *testing.T) {
|
||||
cleanfs := map[string]string{
|
||||
"path/to/home": "",
|
||||
"/path/to/home": "",
|
||||
}
|
||||
cachefs := map[string]string{
|
||||
"path/to/home/.helmfile/cache/ssh_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml": "foo: bar",
|
||||
"/path/to/home/.helmfile/cache/ssh_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml": "foo: bar",
|
||||
}
|
||||
|
||||
type testcase struct {
|
||||
@@ -112,7 +114,7 @@ func TestRemote_SShGitHub(t *testing.T) {
|
||||
hit := true
|
||||
|
||||
get := func(wd, src, dst string) error {
|
||||
if wd != "path/to/home" {
|
||||
if wd != "/path/to/home" {
|
||||
return fmt.Errorf("unexpected wd: %s", wd)
|
||||
}
|
||||
if src != "git::ssh://git@github.com/cloudposse/helmfiles.git?ref=0.40.0" {
|
||||
@@ -129,7 +131,7 @@ func TestRemote_SShGitHub(t *testing.T) {
|
||||
}
|
||||
remote := &Remote{
|
||||
Logger: helmexec.NewLogger(os.Stderr, "debug"),
|
||||
Home: "path/to/home",
|
||||
Home: "/path/to/home",
|
||||
Getter: getter,
|
||||
ReadFile: testfs.ReadFile,
|
||||
FileExists: testfs.FileExistsAt,
|
||||
@@ -142,7 +144,7 @@ func TestRemote_SShGitHub(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if file != "path/to/home/.helmfile/cache/ssh_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml" {
|
||||
if file != "/path/to/home/.helmfile/cache/ssh_github_com_cloudposse_helmfiles_git.ref=0.40.0/releases/kiam.yaml" {
|
||||
t.Errorf("unexpected file located: %s", file)
|
||||
}
|
||||
|
||||
@@ -156,6 +158,75 @@ func TestRemote_SShGitHub(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
type testcase struct {
|
||||
input string
|
||||
getter, scheme, dir, file, query string
|
||||
err string
|
||||
}
|
||||
|
||||
testcases := []testcase{
|
||||
{
|
||||
input: "raw/incubator",
|
||||
err: "parse url: missing scheme - probably this is a local file path? raw/incubator",
|
||||
},
|
||||
{
|
||||
input: "git::https://github.com/stakater/Forecastle.git@deployments/kubernetes/chart/forecastle?ref=v1.0.54",
|
||||
getter: "git",
|
||||
scheme: "https",
|
||||
dir: "/stakater/Forecastle.git",
|
||||
file: "deployments/kubernetes/chart/forecastle",
|
||||
query: "ref=v1.0.54",
|
||||
},
|
||||
}
|
||||
|
||||
for i := range testcases {
|
||||
tc := testcases[i]
|
||||
|
||||
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
|
||||
src, err := Parse(tc.input)
|
||||
|
||||
var errMsg string
|
||||
if err != nil {
|
||||
errMsg = err.Error()
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.err, errMsg); diff != "" {
|
||||
t.Fatalf("Unexpected error:\n%s", diff)
|
||||
}
|
||||
|
||||
var getter, scheme, dir, file, query string
|
||||
if src != nil {
|
||||
getter = src.Getter
|
||||
scheme = src.Scheme
|
||||
dir = src.Dir
|
||||
file = src.File
|
||||
query = src.RawQuery
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.getter, getter); diff != "" {
|
||||
t.Fatalf("Unexpected getter:\n%s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.scheme, scheme); diff != "" {
|
||||
t.Fatalf("Unexpected scheme:\n%s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.file, file); diff != "" {
|
||||
t.Fatalf("Unexpected file:\n%s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.dir, dir); diff != "" {
|
||||
t.Fatalf("Unexpected dir:\n%s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.query, query); diff != "" {
|
||||
t.Fatalf("Unexpected query:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type testGetter struct {
|
||||
get func(wd, src, dst string) error
|
||||
}
|
||||
|
||||
@@ -2,18 +2,19 @@ package state
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Masterminds/semver"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
goversion "github.com/hashicorp/go-version"
|
||||
"github.com/r3labs/diff"
|
||||
"github.com/roboll/helmfile/pkg/app/version"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"go.uber.org/zap"
|
||||
"gopkg.in/yaml.v2"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ChartMeta struct {
|
||||
@@ -129,7 +130,7 @@ func (d *ResolvedDependencies) Get(chart, versionConstraint string) (string, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no resolved dependency found for \"%s\"", chart)
|
||||
return "", fmt.Errorf("no resolved dependency found for \"%s\", running \"helmfile deps\" may resolve the issue", chart)
|
||||
}
|
||||
|
||||
func (st *HelmState) mergeLockedDependencies() (*HelmState, error) {
|
||||
@@ -254,7 +255,7 @@ func updateDependencies(st *HelmState, shell helmexec.DependencyUpdater, unresol
|
||||
|
||||
_, err := depMan.Update(shell, wd, unresolved)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to resolve %d deps: %v", len(unresolved.deps), err)
|
||||
return nil, fmt.Errorf("unable to update %d deps: %v", len(unresolved.deps), err)
|
||||
}
|
||||
|
||||
return resolveDependencies(st, depMan, unresolved)
|
||||
|
||||
+71
-29
@@ -4,10 +4,12 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/remote"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/roboll/helmfile/pkg/environment"
|
||||
"github.com/roboll/helmfile/pkg/maputil"
|
||||
@@ -38,11 +40,15 @@ func (e *UndefinedEnvError) Error() string {
|
||||
}
|
||||
|
||||
type StateCreator struct {
|
||||
logger *zap.SugaredLogger
|
||||
readFile func(string) ([]byte, error)
|
||||
fileExists func(string) (bool, error)
|
||||
abs func(string) (string, error)
|
||||
glob func(string) ([]string, error)
|
||||
logger *zap.SugaredLogger
|
||||
|
||||
readFile func(string) ([]byte, error)
|
||||
fileExists func(string) (bool, error)
|
||||
abs func(string) (string, error)
|
||||
glob func(string) ([]string, error)
|
||||
DeleteFile func(string) error
|
||||
directoryExistsAt func(string) bool
|
||||
|
||||
valsRuntime vals.Evaluator
|
||||
|
||||
Strict bool
|
||||
@@ -52,20 +58,27 @@ type StateCreator struct {
|
||||
getHelm func(*HelmState) helmexec.Interface
|
||||
|
||||
overrideHelmBinary string
|
||||
|
||||
remote *remote.Remote
|
||||
}
|
||||
|
||||
func NewCreator(logger *zap.SugaredLogger, readFile func(string) ([]byte, error), fileExists func(string) (bool, error), abs func(string) (string, error), glob func(string) ([]string, error), valsRuntime vals.Evaluator, getHelm func(*HelmState) helmexec.Interface, overrideHelmBinary string) *StateCreator {
|
||||
func NewCreator(logger *zap.SugaredLogger, readFile func(string) ([]byte, error), fileExists func(string) (bool, error), abs func(string) (string, error), glob func(string) ([]string, error), directoryExistsAt func(string) bool, valsRuntime vals.Evaluator, getHelm func(*HelmState) helmexec.Interface, overrideHelmBinary string, remote *remote.Remote) *StateCreator {
|
||||
return &StateCreator{
|
||||
logger: logger,
|
||||
readFile: readFile,
|
||||
fileExists: fileExists,
|
||||
abs: abs,
|
||||
glob: glob,
|
||||
logger: logger,
|
||||
|
||||
readFile: readFile,
|
||||
fileExists: fileExists,
|
||||
abs: abs,
|
||||
glob: glob,
|
||||
directoryExistsAt: directoryExistsAt,
|
||||
|
||||
Strict: true,
|
||||
valsRuntime: valsRuntime,
|
||||
getHelm: getHelm,
|
||||
|
||||
overrideHelmBinary: overrideHelmBinary,
|
||||
|
||||
remote: remote,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +138,7 @@ func (c *StateCreator) Parse(content []byte, baseDir, file string) (*HelmState,
|
||||
state.removeFile = os.Remove
|
||||
state.fileExists = c.fileExists
|
||||
state.glob = c.glob
|
||||
state.directoryExistsAt = c.directoryExistsAt
|
||||
state.valsRuntime = c.valsRuntime
|
||||
|
||||
return &state, nil
|
||||
@@ -139,11 +153,15 @@ func (c *StateCreator) LoadEnvValues(target *HelmState, env string, ctxEnv *envi
|
||||
return nil, &StateLoadError{fmt.Sprintf("failed to read %s", state.FilePath), err}
|
||||
}
|
||||
|
||||
e.Defaults, err = state.loadValuesEntries(nil, state.DefaultValues)
|
||||
newDefaults, err := state.loadValuesEntries(nil, state.DefaultValues, c.remote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := mergo.Merge(&e.Defaults, newDefaults, mergo.WithOverride, mergo.WithOverwriteWithEmptyValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
state.Env = *e
|
||||
|
||||
return &state, nil
|
||||
@@ -175,6 +193,12 @@ func (c *StateCreator) ParseAndLoad(content []byte, baseDir, file string, envNam
|
||||
|
||||
state.FilePath = file
|
||||
|
||||
vals, err := state.Env.GetMergedValues()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rendering values: %w", err)
|
||||
}
|
||||
state.RenderedValues = vals
|
||||
|
||||
return state, nil
|
||||
}
|
||||
|
||||
@@ -203,7 +227,7 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn
|
||||
envSpec, ok := st.Environments[name]
|
||||
if ok {
|
||||
var err error
|
||||
envVals, err = st.loadValuesEntries(envSpec.MissingFileHandler, envSpec.Values)
|
||||
envVals, err = st.loadValuesEntries(envSpec.MissingFileHandler, envSpec.Values, c.remote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -248,42 +272,54 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn
|
||||
func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles []string, envVals map[string]interface{}, readFile func(string) ([]byte, error)) error {
|
||||
var errs []error
|
||||
|
||||
helm := c.getHelm(st)
|
||||
inputs := envSecretFiles
|
||||
inputsSize := len(inputs)
|
||||
|
||||
type secretResult struct {
|
||||
id int
|
||||
result map[string]interface{}
|
||||
err error
|
||||
path string
|
||||
}
|
||||
|
||||
secrets := make(chan string, inputsSize)
|
||||
type secretInput struct {
|
||||
id int
|
||||
path string
|
||||
}
|
||||
|
||||
secrets := make(chan secretInput, inputsSize)
|
||||
results := make(chan secretResult, inputsSize)
|
||||
|
||||
st.scatterGather(0, inputsSize,
|
||||
func() {
|
||||
for _, secretFile := range envSecretFiles {
|
||||
secrets <- secretFile
|
||||
for i, secretFile := range envSecretFiles {
|
||||
secrets <- secretInput{i, secretFile}
|
||||
}
|
||||
close(secrets)
|
||||
},
|
||||
func(id int) {
|
||||
for path := range secrets {
|
||||
for secret := range secrets {
|
||||
release := &ReleaseSpec{}
|
||||
flags := st.appendConnectionFlags([]string{}, release)
|
||||
decFile, err := c.getHelm(st).DecryptSecret(st.createHelmContext(release, 0), path, flags...)
|
||||
flags := st.appendConnectionFlags([]string{}, helm, release)
|
||||
decFile, err := helm.DecryptSecret(st.createHelmContext(release, 0), secret.path, flags...)
|
||||
if err != nil {
|
||||
results <- secretResult{nil, err, path}
|
||||
results <- secretResult{secret.id, nil, err, secret.path}
|
||||
continue
|
||||
}
|
||||
defer func() {
|
||||
if err := c.DeleteFile(decFile); err != nil {
|
||||
c.logger.Warnf("removing decrypted file %s: %w", decFile, err)
|
||||
}
|
||||
}()
|
||||
bytes, err := readFile(decFile)
|
||||
if err != nil {
|
||||
results <- secretResult{nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", path, err), path}
|
||||
results <- secretResult{secret.id, nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", secret.path, err), secret.path}
|
||||
continue
|
||||
}
|
||||
m := map[string]interface{}{}
|
||||
if err := yaml.Unmarshal(bytes, &m); err != nil {
|
||||
results <- secretResult{nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", path, err), path}
|
||||
results <- secretResult{secret.id, nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", secret.path, err), secret.path}
|
||||
continue
|
||||
}
|
||||
// All the nested map key should be string. Otherwise we get strange errors due to that
|
||||
@@ -291,15 +327,22 @@ func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles
|
||||
// See https://github.com/roboll/helmfile/issues/677
|
||||
vals, err := maputil.CastKeysToStrings(m)
|
||||
if err != nil {
|
||||
results <- secretResult{nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", path, err), path}
|
||||
results <- secretResult{secret.id, nil, fmt.Errorf("failed to load environment secrets file \"%s\": %v", secret.path, err), secret.path}
|
||||
continue
|
||||
}
|
||||
results <- secretResult{vals, nil, path}
|
||||
results <- secretResult{secret.id, vals, nil, secret.path}
|
||||
}
|
||||
},
|
||||
func() {
|
||||
sortedSecrets := make([]secretResult, inputsSize)
|
||||
|
||||
for i := 0; i < inputsSize; i++ {
|
||||
result := <-results
|
||||
sortedSecrets[result.id] = result
|
||||
}
|
||||
close(results)
|
||||
|
||||
for _, result := range sortedSecrets {
|
||||
if result.err != nil {
|
||||
errs = append(errs, result.err)
|
||||
} else {
|
||||
@@ -308,11 +351,10 @@ func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles
|
||||
}
|
||||
}
|
||||
}
|
||||
close(results)
|
||||
},
|
||||
)
|
||||
|
||||
if len(errs) > 1 {
|
||||
if len(errs) > 0 {
|
||||
for _, err := range errs {
|
||||
st.logger.Error(err)
|
||||
}
|
||||
@@ -321,11 +363,11 @@ func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *HelmState) loadValuesEntries(missingFileHandler *string, entries []interface{}) (map[string]interface{}, error) {
|
||||
func (st *HelmState) loadValuesEntries(missingFileHandler *string, entries []interface{}, remote *remote.Remote) (map[string]interface{}, error) {
|
||||
envVals := map[string]interface{}{}
|
||||
|
||||
valuesEntries := append([]interface{}{}, entries...)
|
||||
ld := NewEnvironmentValuesLoader(st.storage(), st.readFile, st.logger)
|
||||
ld := NewEnvironmentValuesLoader(st.storage(), st.readFile, st.logger, remote)
|
||||
var err error
|
||||
envVals, err = ld.LoadEnvironmentValues(missingFileHandler, valuesEntries)
|
||||
if err != nil {
|
||||
|
||||
+140
-5
@@ -2,10 +2,13 @@ package state
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/remote"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/testhelper"
|
||||
"go.uber.org/zap"
|
||||
|
||||
@@ -18,7 +21,9 @@ func createFromYaml(content []byte, file string, env string, logger *zap.Sugared
|
||||
logger: logger,
|
||||
readFile: ioutil.ReadFile,
|
||||
abs: filepath.Abs,
|
||||
Strict: true,
|
||||
|
||||
DeleteFile: os.Remove,
|
||||
Strict: true,
|
||||
}
|
||||
return c.ParseAndLoad(content, filepath.Dir(file), file, env, true, nil)
|
||||
}
|
||||
@@ -59,6 +64,35 @@ func TestReadFromYaml_NonexistentEnv(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type stateTestEnv struct {
|
||||
Files map[string]string
|
||||
WorkDir string
|
||||
}
|
||||
|
||||
func (testEnv stateTestEnv) MustLoadState(t *testing.T, file, envName string) *HelmState {
|
||||
t.Helper()
|
||||
|
||||
testFs := testhelper.NewTestFs(testEnv.Files)
|
||||
|
||||
if testFs.Cwd == "" {
|
||||
testFs.Cwd = "/"
|
||||
}
|
||||
|
||||
yamlContent, ok := testEnv.Files[file]
|
||||
if !ok {
|
||||
t.Fatalf("no file named %q registered", file)
|
||||
}
|
||||
|
||||
r := remote.NewRemote(logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt)
|
||||
state, err := NewCreator(logger, testFs.ReadFile, testFs.FileExists, testFs.Abs, testFs.Glob, testFs.DirectoryExistsAt, nil, nil, "", r).
|
||||
ParseAndLoad([]byte(yamlContent), filepath.Dir(file), file, envName, true, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
func TestReadFromYaml_NonDefaultEnv(t *testing.T) {
|
||||
yamlFile := "/example/path/to/helmfile.yaml"
|
||||
yamlContent := []byte(`environments:
|
||||
@@ -96,9 +130,15 @@ bar: {{ readFile "bar.txt" }}
|
||||
}
|
||||
|
||||
valuesFile := "/example/path/to/values.yaml.gotmpl"
|
||||
valuesContent := []byte(`env: {{ .Environment.Name }}`)
|
||||
valuesContent := []byte(`env: {{ .Environment.Name }}
|
||||
releaseName: {{ .Release.Name }}
|
||||
releaseNamespace: {{ .Release.Namespace }}
|
||||
`)
|
||||
|
||||
expectedValues := `env: production`
|
||||
expectedValues := `env: production
|
||||
releaseName: myrelease
|
||||
releaseNamespace: mynamespace
|
||||
`
|
||||
|
||||
testFs := testhelper.NewTestFs(map[string]string{
|
||||
fooYamlFile: string(fooYamlContent),
|
||||
@@ -108,7 +148,9 @@ bar: {{ readFile "bar.txt" }}
|
||||
})
|
||||
testFs.Cwd = "/example/path/to"
|
||||
|
||||
state, err := NewCreator(logger, testFs.ReadFile, testFs.FileExists, testFs.Abs, testFs.Glob, nil, nil, "").ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, nil)
|
||||
r := remote.NewRemote(logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt)
|
||||
state, err := NewCreator(logger, testFs.ReadFile, testFs.FileExists, testFs.Abs, testFs.Glob, testFs.DirectoryExistsAt, nil, nil, "", r).
|
||||
ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -118,7 +160,98 @@ bar: {{ readFile "bar.txt" }}
|
||||
t.Errorf("unexpected environment values: expected=%v, actual=%v", expected, actual)
|
||||
}
|
||||
|
||||
actualValuesData, err := state.RenderValuesFileToBytes(valuesFile)
|
||||
release := state.Releases[0]
|
||||
|
||||
state.ApplyOverrides(&release)
|
||||
|
||||
actualValuesData, err := state.RenderReleaseValuesFileToBytes(&release, valuesFile)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
actualValues := string(actualValuesData)
|
||||
|
||||
if !reflect.DeepEqual(expectedValues, actualValues) {
|
||||
t.Errorf("unexpected values: expected=%v, actual=%v", expectedValues, actualValues)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFromYaml_OverrideNamespace(t *testing.T) {
|
||||
yamlFile := "/example/path/to/helmfile.yaml"
|
||||
yamlContent := []byte(`environments:
|
||||
production:
|
||||
values:
|
||||
- foo.yaml
|
||||
- bar.yaml.gotmpl
|
||||
|
||||
# A.k.a helmfile apply --namespace myns
|
||||
namespace: myns
|
||||
|
||||
releases:
|
||||
- name: myrelease
|
||||
namespace: mynamespace
|
||||
chart: mychart
|
||||
values:
|
||||
- values.yaml.gotmpl
|
||||
`)
|
||||
|
||||
fooYamlFile := "/example/path/to/foo.yaml"
|
||||
fooYamlContent := []byte(`foo: foo
|
||||
# As this file doesn't have an file extension ".gotmpl", this template expression should not be evaluated
|
||||
baz: "{{ readFile \"baz.txt\" }}"`)
|
||||
|
||||
barYamlFile := "/example/path/to/bar.yaml.gotmpl"
|
||||
barYamlContent := []byte(`foo: FOO
|
||||
bar: {{ readFile "bar.txt" }}
|
||||
`)
|
||||
|
||||
barTextFile := "/example/path/to/bar.txt"
|
||||
barTextContent := []byte("BAR")
|
||||
|
||||
expected := map[string]interface{}{
|
||||
"foo": "FOO",
|
||||
"bar": "BAR",
|
||||
// As the file doesn't have an file extension ".gotmpl", this template expression should not be evaluated
|
||||
"baz": "{{ readFile \"baz.txt\" }}",
|
||||
}
|
||||
|
||||
valuesFile := "/example/path/to/values.yaml.gotmpl"
|
||||
valuesContent := []byte(`env: {{ .Environment.Name }}
|
||||
releaseName: {{ .Release.Name }}
|
||||
releaseNamespace: {{ .Release.Namespace }}
|
||||
overrideNamespace: {{ .Namespace }}
|
||||
`)
|
||||
|
||||
expectedValues := `env: production
|
||||
releaseName: myrelease
|
||||
releaseNamespace: myns
|
||||
overrideNamespace: myns
|
||||
`
|
||||
|
||||
testFs := testhelper.NewTestFs(map[string]string{
|
||||
fooYamlFile: string(fooYamlContent),
|
||||
barYamlFile: string(barYamlContent),
|
||||
barTextFile: string(barTextContent),
|
||||
valuesFile: string(valuesContent),
|
||||
})
|
||||
testFs.Cwd = "/example/path/to"
|
||||
|
||||
r := remote.NewRemote(logger, testFs.Cwd, testFs.ReadFile, testFs.DirectoryExistsAt, testFs.FileExistsAt)
|
||||
state, err := NewCreator(logger, testFs.ReadFile, testFs.FileExists, testFs.Abs, testFs.Glob, testFs.DirectoryExistsAt, nil, nil, "", r).
|
||||
ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "production", true, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
actual := state.Env.Values
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("unexpected environment values: expected=%v, actual=%v", expected, actual)
|
||||
}
|
||||
|
||||
release := state.Releases[0]
|
||||
|
||||
state.ApplyOverrides(&release)
|
||||
|
||||
actualValuesData, err := state.RenderReleaseValuesFileToBytes(&release, valuesFile)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -244,6 +377,8 @@ func TestReadFromYaml_FilterNegatives(t *testing.T) {
|
||||
[]bool{false, true, false}},
|
||||
{LabelFilter{negativeLabels: [][]string{[]string{"stage", "pre"}, []string{"stage", "post"}}},
|
||||
[]bool{false, false, true}},
|
||||
{LabelFilter{negativeLabels: [][]string{[]string{"foo", "bar"}}},
|
||||
[]bool{false, true, true}},
|
||||
}
|
||||
state, err := createFromYaml(yamlContent, yamlFile, DefaultEnv, logger)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package state
|
||||
|
||||
type EnvironmentSpec struct {
|
||||
Values []interface{} `yaml:"values,omitempty"`
|
||||
Secrets []string `yaml:"secrets,omitempty"`
|
||||
Values []interface{} `yaml:"values,omitempty"`
|
||||
Secrets []string `yaml:"secrets,omitempty"`
|
||||
KubeContext string `yaml:"kubeContext,omitempty"`
|
||||
|
||||
// MissingFileHandler instructs helmfile to fail when unable to find a environment values file listed
|
||||
// under `environments.NAME.values`.
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/roboll/helmfile/pkg/environment"
|
||||
"github.com/roboll/helmfile/pkg/maputil"
|
||||
"github.com/roboll/helmfile/pkg/remote"
|
||||
"github.com/roboll/helmfile/pkg/tmpl"
|
||||
"go.uber.org/zap"
|
||||
"gopkg.in/yaml.v2"
|
||||
@@ -17,13 +18,16 @@ type EnvironmentValuesLoader struct {
|
||||
readFile func(string) ([]byte, error)
|
||||
|
||||
logger *zap.SugaredLogger
|
||||
|
||||
remote *remote.Remote
|
||||
}
|
||||
|
||||
func NewEnvironmentValuesLoader(storage *Storage, readFile func(string) ([]byte, error), logger *zap.SugaredLogger) *EnvironmentValuesLoader {
|
||||
func NewEnvironmentValuesLoader(storage *Storage, readFile func(string) ([]byte, error), logger *zap.SugaredLogger, remote *remote.Remote) *EnvironmentValuesLoader {
|
||||
return &EnvironmentValuesLoader{
|
||||
storage: storage,
|
||||
readFile: readFile,
|
||||
logger: logger,
|
||||
remote: remote,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +40,11 @@ func (ld *EnvironmentValuesLoader) LoadEnvironmentValues(missingFileHandler *str
|
||||
switch strOrMap := entry.(type) {
|
||||
case string:
|
||||
urlOrPath := strOrMap
|
||||
localPath, err := ld.remote.Locate(urlOrPath)
|
||||
if err == nil {
|
||||
urlOrPath = localPath
|
||||
}
|
||||
|
||||
files, skipped, err := ld.storage.resolveFile(missingFileHandler, "environment values", urlOrPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -2,6 +2,7 @@ package state
|
||||
|
||||
import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/roboll/helmfile/pkg/remote"
|
||||
"go.uber.org/zap"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
@@ -23,7 +24,10 @@ func newLoader() *EnvironmentValuesLoader {
|
||||
logger: sugar,
|
||||
}
|
||||
|
||||
return NewEnvironmentValuesLoader(storage, ioutil.ReadFile, sugar)
|
||||
readFile := func(s string) ([]byte, error) { return []byte{}, nil }
|
||||
dirExists := func(d string) bool { return false }
|
||||
fileExists := func(f string) bool { return false }
|
||||
return NewEnvironmentValuesLoader(storage, ioutil.ReadFile, sugar, remote.NewRemote(sugar, "/tmp", readFile, dirExists, fileExists))
|
||||
}
|
||||
|
||||
// See https://github.com/roboll/helmfile/pull/1169
|
||||
|
||||
+141
-39
@@ -1,11 +1,13 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/variantdev/chartify"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/remote"
|
||||
"github.com/variantdev/chartify"
|
||||
)
|
||||
|
||||
type Dependency struct {
|
||||
@@ -22,18 +24,85 @@ func (st *HelmState) appendHelmXFlags(flags []string, release *ReleaseSpec) ([]s
|
||||
return flags, nil
|
||||
}
|
||||
|
||||
func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSpec, workerIndex int) (bool, *chartify.ChartifyOpts, error) {
|
||||
var opts chartify.ChartifyOpts
|
||||
func fileExistsAt(path string) bool {
|
||||
fileInfo, err := os.Stat(path)
|
||||
return err == nil && fileInfo.Mode().IsRegular()
|
||||
}
|
||||
|
||||
opts.WorkaroundOutputDirIssue = true
|
||||
func directoryExistsAt(path string) bool {
|
||||
fileInfo, err := os.Stat(path)
|
||||
return err == nil && fileInfo.Mode().IsDir()
|
||||
}
|
||||
|
||||
type Chartify struct {
|
||||
Opts *chartify.ChartifyOpts
|
||||
Clean func()
|
||||
}
|
||||
|
||||
func (st *HelmState) downloadChartWithGoGetter(r *ReleaseSpec) (string, error) {
|
||||
pathElems := []string{
|
||||
remote.DefaultCacheDir,
|
||||
}
|
||||
|
||||
if r.Namespace != "" {
|
||||
pathElems = append(pathElems, r.Namespace)
|
||||
}
|
||||
|
||||
if r.KubeContext != "" {
|
||||
pathElems = append(pathElems, r.KubeContext)
|
||||
}
|
||||
|
||||
pathElems = append(pathElems, r.Name)
|
||||
|
||||
cacheDir := filepath.Join(pathElems...)
|
||||
|
||||
return st.goGetterChart(r.Chart, r.Directory, cacheDir, r.ForceGoGetter)
|
||||
}
|
||||
|
||||
func (st *HelmState) goGetterChart(chart, dir, cacheDir string, force bool) (string, error) {
|
||||
if dir != "" && chart == "" {
|
||||
chart = dir
|
||||
}
|
||||
|
||||
_, err := remote.Parse(chart)
|
||||
if err != nil {
|
||||
if force {
|
||||
return "", fmt.Errorf("Parsing url from dir failed due to error %q.\nContinuing the process assuming this is a regular Helm chart or a local dir.", err.Error())
|
||||
}
|
||||
} else {
|
||||
r := remote.NewRemote(st.logger, st.basePath, st.readFile, directoryExistsAt, fileExistsAt)
|
||||
|
||||
fetchedDir, err := r.Fetch(chart, cacheDir)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("fetching %q: %v", chart, err)
|
||||
}
|
||||
|
||||
chart = fetchedDir
|
||||
}
|
||||
|
||||
return chart, nil
|
||||
}
|
||||
|
||||
func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSpec, chart string, workerIndex int) (*Chartify, func(), error) {
|
||||
c := &Chartify{
|
||||
Opts: &chartify.ChartifyOpts{
|
||||
WorkaroundOutputDirIssue: true,
|
||||
EnableKustomizeAlphaPlugins: true,
|
||||
ChartVersion: release.Version,
|
||||
Namespace: release.Namespace,
|
||||
ID: ReleaseToID(release),
|
||||
},
|
||||
}
|
||||
|
||||
var filesNeedCleaning []string
|
||||
|
||||
clean := func() {
|
||||
st.removeFiles(filesNeedCleaning)
|
||||
}
|
||||
|
||||
var shouldRun bool
|
||||
|
||||
opts.EnableKustomizeAlphaPlugins = true
|
||||
|
||||
opts.ChartVersion = release.Version
|
||||
|
||||
dir := filepath.Join(st.basePath, release.Chart)
|
||||
dir := filepath.Join(st.basePath, chart)
|
||||
if stat, _ := os.Stat(dir); stat != nil && stat.IsDir() {
|
||||
if exists, err := st.fileExists(filepath.Join(dir, "Chart.yaml")); err == nil && !exists {
|
||||
shouldRun = true
|
||||
@@ -41,57 +110,79 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
|
||||
}
|
||||
|
||||
for _, d := range release.Dependencies {
|
||||
var dep string
|
||||
chart := d.Chart
|
||||
if st.directoryExistsAt(chart) {
|
||||
var err error
|
||||
|
||||
if d.Alias != "" {
|
||||
dep += d.Alias + "="
|
||||
} else {
|
||||
a := strings.Split(d.Chart, "/")
|
||||
|
||||
chart := a[len(a)-1]
|
||||
|
||||
dep += chart + "="
|
||||
// Otherwise helm-dependency-up on the temporary chart generated by chartify ends up errors like:
|
||||
// Error: directory /tmp/chartify945964195/myapp-57fb4495cf/test/integration/charts/httpbin not found]
|
||||
// which is due to that the temporary chart is generated outside of the current working directory/basePath,
|
||||
// and therefore the relative path in `chart` points to somewhere inexistent.
|
||||
chart, err = filepath.Abs(filepath.Join(st.basePath, chart))
|
||||
if err != nil {
|
||||
return nil, clean, err
|
||||
}
|
||||
}
|
||||
|
||||
dep += d.Chart
|
||||
|
||||
if d.Version != "" {
|
||||
dep += ":" + d.Version
|
||||
}
|
||||
|
||||
opts.AdhocChartDependencies = append(opts.AdhocChartDependencies, dep)
|
||||
c.Opts.AdhocChartDependencies = append(c.Opts.AdhocChartDependencies, chartify.ChartDependency{
|
||||
Alias: d.Alias,
|
||||
Chart: chart,
|
||||
Version: d.Version,
|
||||
})
|
||||
|
||||
shouldRun = true
|
||||
}
|
||||
|
||||
jsonPatches := release.JSONPatches
|
||||
if len(jsonPatches) > 0 {
|
||||
generatedFiles, err := st.generateTemporaryValuesFiles(jsonPatches, release.MissingFileHandler)
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, jsonPatches, release.MissingFileHandler)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
return nil, clean, err
|
||||
}
|
||||
|
||||
filesNeedCleaning = append(filesNeedCleaning, generatedFiles...)
|
||||
|
||||
for _, f := range generatedFiles {
|
||||
opts.JsonPatches = append(opts.JsonPatches, f)
|
||||
c.Opts.JsonPatches = append(c.Opts.JsonPatches, f)
|
||||
}
|
||||
|
||||
release.generatedValues = append(release.generatedValues, generatedFiles...)
|
||||
|
||||
shouldRun = true
|
||||
}
|
||||
|
||||
strategicMergePatches := release.StrategicMergePatches
|
||||
if len(strategicMergePatches) > 0 {
|
||||
generatedFiles, err := st.generateTemporaryValuesFiles(strategicMergePatches, release.MissingFileHandler)
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, strategicMergePatches, release.MissingFileHandler)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
return nil, clean, err
|
||||
}
|
||||
|
||||
for _, f := range generatedFiles {
|
||||
opts.StrategicMergePatches = append(opts.StrategicMergePatches, f)
|
||||
c.Opts.StrategicMergePatches = append(c.Opts.StrategicMergePatches, f)
|
||||
}
|
||||
|
||||
release.generatedValues = append(release.generatedValues, generatedFiles...)
|
||||
filesNeedCleaning = append(filesNeedCleaning, generatedFiles...)
|
||||
|
||||
shouldRun = true
|
||||
}
|
||||
|
||||
transformers := release.Transformers
|
||||
if len(transformers) > 0 {
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, transformers, release.MissingFileHandler)
|
||||
if err != nil {
|
||||
return nil, clean, err
|
||||
}
|
||||
|
||||
for _, f := range generatedFiles {
|
||||
c.Opts.Transformers = append(c.Opts.Transformers, f)
|
||||
}
|
||||
|
||||
filesNeedCleaning = append(filesNeedCleaning, generatedFiles...)
|
||||
|
||||
shouldRun = true
|
||||
}
|
||||
|
||||
if release.ForceNamespace != "" {
|
||||
c.Opts.OverrideNamespace = release.ForceNamespace
|
||||
|
||||
shouldRun = true
|
||||
}
|
||||
@@ -99,11 +190,22 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
|
||||
if shouldRun {
|
||||
generatedFiles, err := st.generateValuesFiles(helm, release, workerIndex)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
return nil, clean, err
|
||||
}
|
||||
|
||||
opts.ValuesFiles = generatedFiles
|
||||
filesNeedCleaning = append(filesNeedCleaning, generatedFiles...)
|
||||
|
||||
c.Opts.ValuesFiles = generatedFiles
|
||||
setFlags, err := st.setFlags(release.SetValues)
|
||||
if err != nil {
|
||||
return nil, clean, fmt.Errorf("rendering set value entry for release %s: %v", release.Name, err)
|
||||
}
|
||||
c.Opts.SetFlags = setFlags
|
||||
c.Opts.TemplateData = st.newReleaseTemplateData(release)
|
||||
c.Opts.TemplateFuncs = st.newReleaseTemplateFuncMap(dir)
|
||||
|
||||
return c, clean, nil
|
||||
}
|
||||
|
||||
return shouldRun, &opts, nil
|
||||
return nil, clean, nil
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSelectReleasesWithOverrides(t *testing.T) {
|
||||
type testcase struct {
|
||||
subject string
|
||||
selector []string
|
||||
want []string
|
||||
}
|
||||
|
||||
testcases := []testcase{
|
||||
{
|
||||
subject: "multiple OR selectors (nillable label first)",
|
||||
selector: []string{"type=bar", "name=nolabel2", "name=nolabel1"},
|
||||
want: []string{"nolabel1", "nolabel2", "foo"},
|
||||
},
|
||||
{
|
||||
subject: "multiple OR selectors (non-nillable label first)",
|
||||
selector: []string{"name=foo", "type!=bar"},
|
||||
want: []string{"nolabel1", "nolabel2", "foo"},
|
||||
},
|
||||
{
|
||||
subject: "multiple AND conditions (nillable label first)",
|
||||
selector: []string{"type!=bar,name!=nolabel2"},
|
||||
want: []string{"nolabel1"},
|
||||
},
|
||||
{
|
||||
subject: "multiple AND conditions (non-nillable label first)",
|
||||
selector: []string{"name!=nolabel2,type!=bar"},
|
||||
want: []string{"nolabel1"},
|
||||
},
|
||||
{
|
||||
subject: "inequality on nillable label",
|
||||
selector: []string{"type!=bar"},
|
||||
want: []string{"nolabel1", "nolabel2"},
|
||||
},
|
||||
{
|
||||
subject: "equality on nillable label",
|
||||
selector: []string{"type=bar"},
|
||||
want: []string{"foo"},
|
||||
},
|
||||
{
|
||||
subject: "inequality on non-nillable label",
|
||||
selector: []string{"name!=nolabel1"},
|
||||
want: []string{"nolabel2", "foo"},
|
||||
},
|
||||
{
|
||||
subject: "equality on non-nillable label",
|
||||
selector: []string{"name=nolabel1"},
|
||||
want: []string{"nolabel1"},
|
||||
},
|
||||
}
|
||||
|
||||
example := []byte(`releases:
|
||||
- name: nolabel1
|
||||
namespace: kube-system
|
||||
chart: stable/nolabel
|
||||
- name: nolabel2
|
||||
namespace: default
|
||||
chart: stable/nolabel
|
||||
- name: foo
|
||||
namespace: kube-system
|
||||
chart: stable/foo
|
||||
labels:
|
||||
type: bar
|
||||
`)
|
||||
|
||||
state := stateTestEnv{
|
||||
Files: map[string]string{
|
||||
"/helmfile.yaml": string(example),
|
||||
},
|
||||
WorkDir: "/",
|
||||
}.MustLoadState(t, "/helmfile.yaml", "default")
|
||||
|
||||
for _, tc := range testcases {
|
||||
state.Selectors = tc.selector
|
||||
|
||||
rs, err := state.GetSelectedReleasesWithOverrides()
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v", tc.selector, tc.subject, err)
|
||||
}
|
||||
|
||||
var got []string
|
||||
|
||||
for _, r := range rs {
|
||||
got = append(got, r.Name)
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.want, got); d != "" {
|
||||
t.Errorf("%s %s: %s", tc.selector, tc.subject, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1434
-362
File diff suppressed because it is too large
Load Diff
@@ -8,24 +8,31 @@ import (
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func (st *HelmState) Values() (map[string]interface{}, error) {
|
||||
return st.Env.GetMergedValues()
|
||||
}
|
||||
|
||||
func (st *HelmState) mustLoadVals() map[string]interface{} {
|
||||
vals, err := st.Values()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
func (st *HelmState) Values() map[string]interface{} {
|
||||
if st.RenderedValues == nil {
|
||||
panic("[bug] RenderedValues is nil")
|
||||
}
|
||||
return vals
|
||||
|
||||
return st.RenderedValues
|
||||
}
|
||||
|
||||
func (st *HelmState) valuesFileTemplateData() EnvironmentTemplateData {
|
||||
return EnvironmentTemplateData{
|
||||
func (st *HelmState) createReleaseTemplateData(release *ReleaseSpec, vals map[string]interface{}) releaseTemplateData {
|
||||
tmplData := releaseTemplateData{
|
||||
Environment: st.Env,
|
||||
KubeContext: st.OverrideKubeContext,
|
||||
Namespace: st.OverrideNamespace,
|
||||
Values: st.mustLoadVals(),
|
||||
Chart: st.OverrideChart,
|
||||
Values: vals,
|
||||
Release: releaseTemplateDataRelease{
|
||||
Name: release.Name,
|
||||
Chart: release.Chart,
|
||||
Namespace: release.Namespace,
|
||||
Labels: release.Labels,
|
||||
KubeContext: release.KubeContext,
|
||||
},
|
||||
}
|
||||
tmplData.StateValues = &tmplData.Values
|
||||
return tmplData
|
||||
}
|
||||
|
||||
func getBoolRefFromStringTemplate(templateRef string) (*bool, error) {
|
||||
@@ -80,19 +87,21 @@ func updateBoolTemplatedValues(r *ReleaseSpec) error {
|
||||
func (st *HelmState) ExecuteTemplates() (*HelmState, error) {
|
||||
r := *st
|
||||
|
||||
vals, err := st.Values()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vals := st.Values()
|
||||
|
||||
for i, rt := range st.Releases {
|
||||
if rt.KubeContext == "" {
|
||||
rt.KubeContext = r.HelmDefaults.KubeContext
|
||||
}
|
||||
if rt.Labels == nil {
|
||||
rt.Labels = map[string]string{}
|
||||
}
|
||||
for k, v := range st.CommonLabels {
|
||||
rt.Labels[k] = v
|
||||
}
|
||||
successFlag := false
|
||||
for it, prev := 0, &rt; it < 6; it++ {
|
||||
tmplData := releaseTemplateData{
|
||||
Environment: st.Env,
|
||||
Release: *prev,
|
||||
Values: vals,
|
||||
}
|
||||
tmplData := st.createReleaseTemplateData(prev, vals)
|
||||
renderer := tmpl.NewFileRenderer(st.readFile, st.basePath, tmplData)
|
||||
r, err := rt.ExecuteTemplateExpressions(renderer)
|
||||
if err != nil {
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestHelmState_executeTemplates(t *testing.T) {
|
||||
Name: "test-app",
|
||||
Namespace: "test-namespace-{{ .Release.Name }}",
|
||||
ValuesTemplate: []interface{}{"config/{{ .Environment.Name }}/{{ .Release.Name }}/values.yaml"},
|
||||
Secrets: []string{"config/{{ .Environment.Name }}/{{ .Release.Name }}/secrets.yaml"},
|
||||
Secrets: []interface{}{"config/{{ .Environment.Name }}/{{ .Release.Name }}/secrets.yaml"},
|
||||
Labels: map[string]string{"id": "{{ .Release.Name }}"},
|
||||
},
|
||||
want: ReleaseSpec{
|
||||
@@ -45,7 +45,7 @@ func TestHelmState_executeTemplates(t *testing.T) {
|
||||
Name: "test-app",
|
||||
Namespace: "test-namespace-test-app",
|
||||
Values: []interface{}{"config/test_env/test-app/values.yaml"},
|
||||
Secrets: []string{"config/test_env/test-app/secrets.yaml"},
|
||||
Secrets: []interface{}{"config/test_env/test-app/secrets.yaml"},
|
||||
Labels: map[string]string{"id": "test-app"},
|
||||
},
|
||||
},
|
||||
@@ -135,15 +135,18 @@ func TestHelmState_executeTemplates(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
basePath: ".",
|
||||
HelmDefaults: HelmSpec{
|
||||
KubeContext: "test_context",
|
||||
},
|
||||
Env: environment.Environment{Name: "test_env"},
|
||||
OverrideNamespace: "test-namespace_",
|
||||
Repositories: nil,
|
||||
Releases: []ReleaseSpec{
|
||||
tt.input,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
HelmDefaults: HelmSpec{
|
||||
KubeContext: "test_context",
|
||||
},
|
||||
Env: environment.Environment{Name: "test_env"},
|
||||
OverrideNamespace: "test-namespace_",
|
||||
Repositories: nil,
|
||||
Releases: []ReleaseSpec{
|
||||
tt.input,
|
||||
},
|
||||
},
|
||||
RenderedValues: map[string]interface{}{},
|
||||
}
|
||||
|
||||
r, err := state.ExecuteTemplates()
|
||||
@@ -235,15 +238,18 @@ func TestHelmState_recursiveRefsTemplates(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
basePath: ".",
|
||||
HelmDefaults: HelmSpec{
|
||||
KubeContext: "test_context",
|
||||
},
|
||||
Env: environment.Environment{Name: "test_env"},
|
||||
OverrideNamespace: "test-namespace_",
|
||||
Repositories: nil,
|
||||
Releases: []ReleaseSpec{
|
||||
tt.input,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
HelmDefaults: HelmSpec{
|
||||
KubeContext: "test_context",
|
||||
},
|
||||
Env: environment.Environment{Name: "test_env"},
|
||||
OverrideNamespace: "test-namespace_",
|
||||
Repositories: nil,
|
||||
Releases: []ReleaseSpec{
|
||||
tt.input,
|
||||
},
|
||||
},
|
||||
RenderedValues: map[string]interface{}{},
|
||||
}
|
||||
|
||||
r, err := state.ExecuteTemplates()
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGoGetter(t *testing.T) {
|
||||
logger := helmexec.NewLogger(os.Stderr, "warn")
|
||||
|
||||
testcases := []struct {
|
||||
chart, dir string
|
||||
force bool
|
||||
|
||||
out, err string
|
||||
}{
|
||||
{
|
||||
chart: "raw/incubator",
|
||||
dir: "",
|
||||
force: false,
|
||||
out: "raw/incubator",
|
||||
err: "",
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testcases {
|
||||
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
|
||||
d, err := ioutil.TempDir("", "testgogetter")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(d)
|
||||
|
||||
st := &HelmState{
|
||||
logger: logger,
|
||||
readFile: ioutil.ReadFile,
|
||||
basePath: d,
|
||||
}
|
||||
|
||||
out, err := st.goGetterChart(tc.chart, tc.dir, "", false)
|
||||
|
||||
if diff := cmp.Diff(tc.out, out); diff != "" {
|
||||
t.Fatalf("Unexpected out:\n%s", diff)
|
||||
}
|
||||
|
||||
var errMsg string
|
||||
|
||||
if err != nil {
|
||||
errMsg = err.Error()
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.err, errMsg); diff != "" {
|
||||
t.Fatalf("Unexpected err:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+82
-25
@@ -1,8 +1,10 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
@@ -97,17 +99,20 @@ func (st *HelmState) iterateOnReleases(helm helmexec.Interface, concurrency int,
|
||||
return nil
|
||||
}
|
||||
|
||||
type PlanOpts struct {
|
||||
Reverse bool
|
||||
type PlanOptions struct {
|
||||
Reverse bool
|
||||
IncludeNeeds bool
|
||||
SkipNeeds bool
|
||||
SelectedReleases []ReleaseSpec
|
||||
}
|
||||
|
||||
func (st *HelmState) PlanReleases(reverse bool) ([][]Release, error) {
|
||||
func (st *HelmState) PlanReleases(opts PlanOptions) ([][]Release, error) {
|
||||
marked, err := st.SelectReleasesWithOverrides()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groups, err := SortedReleaseGroups(marked, reverse)
|
||||
groups, err := SortedReleaseGroups(marked, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -115,22 +120,24 @@ func (st *HelmState) PlanReleases(reverse bool) ([][]Release, error) {
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func SortedReleaseGroups(releases []Release, reverse bool) ([][]Release, error) {
|
||||
groups, err := GroupReleasesByDependency(releases)
|
||||
func SortedReleaseGroups(releases []Release, opts PlanOptions) ([][]Release, error) {
|
||||
reverse := opts.Reverse
|
||||
|
||||
groups, err := GroupReleasesByDependency(releases, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if reverse {
|
||||
sort.Slice(groups, func(i, j int) bool {
|
||||
return j < i
|
||||
})
|
||||
for i, j := 0, len(groups)-1; i < j; i, j = i+1, j-1 {
|
||||
groups[i], groups[j] = groups[j], groups[i]
|
||||
}
|
||||
}
|
||||
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func GroupReleasesByDependency(releases []Release) ([][]Release, error) {
|
||||
func GroupReleasesByDependency(releases []Release, opts PlanOptions) ([][]Release, error) {
|
||||
idToReleases := map[string][]Release{}
|
||||
idToIndex := map[string]int{}
|
||||
|
||||
@@ -144,23 +151,73 @@ func GroupReleasesByDependency(releases []Release) ([][]Release, error) {
|
||||
|
||||
// Only compute dependencies from non-filtered releases
|
||||
if !r.Filtered {
|
||||
d.Add(id, dag.Dependencies(r.Needs))
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range releases {
|
||||
if !r.Filtered {
|
||||
for _, n := range r.Needs {
|
||||
if _, ok := idToReleases[n]; !ok {
|
||||
id := ReleaseToID(&r.ReleaseSpec)
|
||||
return nil, fmt.Errorf("%q depends on nonexistent release %q", id, n)
|
||||
}
|
||||
var needs []string
|
||||
for i := 0; i < len(r.Needs); i++ {
|
||||
n := r.Needs[i]
|
||||
needs = append(needs, n)
|
||||
}
|
||||
d.Add(id, dag.Dependencies(needs))
|
||||
}
|
||||
}
|
||||
|
||||
plan, err := d.Plan()
|
||||
var ids []string
|
||||
for id := range idToReleases {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
var selectedReleaseIDs []string
|
||||
|
||||
for _, r := range opts.SelectedReleases {
|
||||
id := ReleaseToID(&r)
|
||||
selectedReleaseIDs = append(selectedReleaseIDs, id)
|
||||
}
|
||||
|
||||
plan, err := d.Plan(dag.SortOptions{
|
||||
Only: selectedReleaseIDs,
|
||||
WithDependencies: opts.IncludeNeeds,
|
||||
WithoutDependencies: opts.SkipNeeds,
|
||||
})
|
||||
if err != nil {
|
||||
if ude, ok := err.(*dag.UnhandledDependencyError); ok {
|
||||
msgs := make([]string, len(ude.UnhandledDependencies))
|
||||
for i, ud := range ude.UnhandledDependencies {
|
||||
id := ud.Id
|
||||
|
||||
ds := make([]string, len(ud.Dependents))
|
||||
for i, d := range ud.Dependents {
|
||||
ds[i] = fmt.Sprintf("%q", d)
|
||||
}
|
||||
|
||||
var dsHumanized string
|
||||
if len(ds) < 3 {
|
||||
dsHumanized = strings.Join(ds, " and ")
|
||||
} else {
|
||||
dsHumanized = strings.Join(ds[:len(ds)-1], ", ")
|
||||
dsHumanized += ", and " + ds[len(ds)-1]
|
||||
}
|
||||
|
||||
var verb string
|
||||
if len(ds) == 1 {
|
||||
verb = "depends"
|
||||
} else {
|
||||
verb = "depend"
|
||||
}
|
||||
|
||||
idComponents := strings.Split(id, "/")
|
||||
name := idComponents[len(idComponents)-1]
|
||||
|
||||
msg := fmt.Sprintf(
|
||||
"release %s %s on %q which does not match the selectors. "+
|
||||
"Please add a selector like \"--selector name=%s\", or indicate whether to skip (--skip-needs) or include (--include-needs) these dependencies",
|
||||
dsHumanized,
|
||||
verb,
|
||||
id,
|
||||
name,
|
||||
)
|
||||
msgs[i] = msg
|
||||
}
|
||||
return nil, errors.New(msgs[0])
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -186,11 +243,11 @@ func GroupReleasesByDependency(releases []Release) ([][]Release, error) {
|
||||
})
|
||||
|
||||
for _, id := range idsInGroup {
|
||||
releases, ok := idToReleases[id]
|
||||
rs, ok := idToReleases[id]
|
||||
if !ok {
|
||||
panic(fmt.Errorf("bug: unexpectedly failed to get releases for id %q", id))
|
||||
panic(fmt.Errorf("bug: unexpectedly failed to get releases for id %q: %v", id, ids))
|
||||
}
|
||||
releasesInGroup = append(releasesInGroup, releases...)
|
||||
releasesInGroup = append(releasesInGroup, rs...)
|
||||
}
|
||||
|
||||
result = append(result, releasesInGroup)
|
||||
|
||||
+252
-144
@@ -8,6 +8,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/roboll/helmfile/pkg/exectest"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"github.com/roboll/helmfile/pkg/testhelper"
|
||||
@@ -21,6 +22,7 @@ func injectFs(st *HelmState, fs *testhelper.TestFs) *HelmState {
|
||||
st.glob = fs.Glob
|
||||
st.readFile = fs.ReadFile
|
||||
st.fileExists = fs.FileExists
|
||||
st.directoryExistsAt = fs.DirectoryExistsAt
|
||||
return st
|
||||
}
|
||||
|
||||
@@ -127,12 +129,14 @@ func TestHelmState_applyDefaultsTo(t *testing.T) {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
basePath: tt.fields.BaseChartPath,
|
||||
DeprecatedContext: tt.fields.Context,
|
||||
DeprecatedReleases: tt.fields.DeprecatedReleases,
|
||||
OverrideNamespace: tt.fields.Namespace,
|
||||
Repositories: tt.fields.Repositories,
|
||||
Releases: tt.fields.Releases,
|
||||
basePath: tt.fields.BaseChartPath,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
DeprecatedContext: tt.fields.Context,
|
||||
DeprecatedReleases: tt.fields.DeprecatedReleases,
|
||||
OverrideNamespace: tt.fields.Namespace,
|
||||
Repositories: tt.fields.Repositories,
|
||||
Releases: tt.fields.Releases,
|
||||
},
|
||||
}
|
||||
if state.ApplyOverrides(&tt.args.spec); !reflect.DeepEqual(tt.args.spec, tt.want) {
|
||||
t.Errorf("HelmState.ApplyOverrides() = %v, want %v", tt.args.spec, tt.want)
|
||||
@@ -163,7 +167,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
version *helmexec.Version
|
||||
version *semver.Version
|
||||
defaults HelmSpec
|
||||
release *ReleaseSpec
|
||||
want []string
|
||||
@@ -309,6 +313,24 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
||||
"--namespace", "test-namespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wait-for-jobs",
|
||||
defaults: HelmSpec{
|
||||
WaitForJobs: false,
|
||||
},
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
WaitForJobs: &enable,
|
||||
Name: "test-charts",
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
want: []string{
|
||||
"--version", "0.1",
|
||||
"--wait-for-jobs",
|
||||
"--namespace", "test-namespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "devel",
|
||||
defaults: HelmSpec{
|
||||
@@ -580,11 +602,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
||||
defaults: HelmSpec{
|
||||
Verify: false,
|
||||
},
|
||||
version: &helmexec.Version{
|
||||
Major: 3,
|
||||
Minor: 2,
|
||||
Patch: 0,
|
||||
},
|
||||
version: semver.MustParse("3.2.0"),
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
@@ -604,11 +622,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
||||
Verify: false,
|
||||
CreateNamespace: &disable,
|
||||
},
|
||||
version: &helmexec.Version{
|
||||
Major: 3,
|
||||
Minor: 2,
|
||||
Patch: 0,
|
||||
},
|
||||
version: semver.MustParse("3.2.0"),
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
@@ -627,11 +641,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
||||
Verify: false,
|
||||
CreateNamespace: &disable,
|
||||
},
|
||||
version: &helmexec.Version{
|
||||
Major: 3,
|
||||
Minor: 2,
|
||||
Patch: 0,
|
||||
},
|
||||
version: semver.MustParse("3.2.0"),
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
@@ -652,11 +662,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
||||
Verify: false,
|
||||
CreateNamespace: &enable,
|
||||
},
|
||||
version: &helmexec.Version{
|
||||
Major: 3,
|
||||
Minor: 2,
|
||||
Patch: 0,
|
||||
},
|
||||
version: semver.MustParse("3.2.0"),
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
@@ -676,11 +682,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
||||
Verify: false,
|
||||
CreateNamespace: &enable,
|
||||
},
|
||||
version: &helmexec.Version{
|
||||
Major: 2,
|
||||
Minor: 16,
|
||||
Patch: 0,
|
||||
},
|
||||
version: semver.MustParse("2.16.0"),
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
@@ -695,17 +697,19 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
basePath: "./",
|
||||
DeprecatedContext: "default",
|
||||
Releases: []ReleaseSpec{*tt.release},
|
||||
HelmDefaults: tt.defaults,
|
||||
valsRuntime: valsRuntime,
|
||||
basePath: "./",
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
DeprecatedContext: "default",
|
||||
Releases: []ReleaseSpec{*tt.release},
|
||||
HelmDefaults: tt.defaults,
|
||||
},
|
||||
valsRuntime: valsRuntime,
|
||||
}
|
||||
helm := &exectest.Helm{
|
||||
Version: tt.version,
|
||||
}
|
||||
|
||||
args, err := state.flagsForUpgrade(helm, tt.release, 0)
|
||||
args, _, err := state.flagsForUpgrade(helm, tt.release, 0)
|
||||
if err != nil && tt.wantErr == "" {
|
||||
t.Errorf("unexpected error flagsForUpgrade: %v", err)
|
||||
}
|
||||
@@ -771,11 +775,11 @@ func Test_isLocalChart(t *testing.T) {
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "local chart in 3-level deep dir",
|
||||
name: "remote chart in 3-level deep dir (e.g. ChartCenter)",
|
||||
args: args{
|
||||
chart: "foo/bar/baz",
|
||||
chart: "center/bar/baz",
|
||||
},
|
||||
want: true,
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for i := range tests {
|
||||
@@ -801,26 +805,50 @@ func Test_normalizeChart(t *testing.T) {
|
||||
{
|
||||
name: "construct local chart path",
|
||||
args: args{
|
||||
basePath: "/Users/jane/code/deploy/charts",
|
||||
basePath: "/src",
|
||||
chart: "./app",
|
||||
},
|
||||
want: "/Users/jane/code/deploy/charts/app",
|
||||
want: "/src/app",
|
||||
},
|
||||
{
|
||||
name: "construct local chart path, without leading dot",
|
||||
args: args{
|
||||
basePath: "/src",
|
||||
chart: "published",
|
||||
},
|
||||
want: "/src/published",
|
||||
},
|
||||
{
|
||||
name: "repo path",
|
||||
args: args{
|
||||
basePath: "/Users/jane/code/deploy/charts",
|
||||
basePath: "/src",
|
||||
chart: "remote/app",
|
||||
},
|
||||
want: "remote/app",
|
||||
},
|
||||
{
|
||||
name: "construct local chart path, parent dir",
|
||||
name: "chartcenter repo path",
|
||||
args: args{
|
||||
basePath: "/Users/jane/code/deploy/charts",
|
||||
basePath: "/src",
|
||||
chart: "center/stable/myapp",
|
||||
},
|
||||
want: "center/stable/myapp",
|
||||
},
|
||||
{
|
||||
name: "construct local chart path, sibling dir",
|
||||
args: args{
|
||||
basePath: "/src",
|
||||
chart: "../app",
|
||||
},
|
||||
want: "/Users/jane/code/deploy/app",
|
||||
want: "/app",
|
||||
},
|
||||
{
|
||||
name: "construct local chart path, parent dir",
|
||||
args: args{
|
||||
basePath: "/src",
|
||||
chart: "./..",
|
||||
},
|
||||
want: "/",
|
||||
},
|
||||
{
|
||||
name: "too much parent levels",
|
||||
@@ -864,7 +892,18 @@ func TestHelmState_SyncRepos(t *testing.T) {
|
||||
},
|
||||
},
|
||||
helm: &exectest.Helm{},
|
||||
want: []string{"name", "http://example.com/", "", "", "", "", ""},
|
||||
want: []string{"name", "http://example.com/", "", "", "", "", "", ""},
|
||||
},
|
||||
{
|
||||
name: "ACR hosted repository",
|
||||
repos: []RepositorySpec{
|
||||
{
|
||||
Name: "name",
|
||||
Managed: "acr",
|
||||
},
|
||||
},
|
||||
helm: &exectest.Helm{},
|
||||
want: []string{"name", "", "", "", "", "", "", "acr"},
|
||||
},
|
||||
{
|
||||
name: "repository with cert and key",
|
||||
@@ -879,7 +918,7 @@ func TestHelmState_SyncRepos(t *testing.T) {
|
||||
},
|
||||
},
|
||||
helm: &exectest.Helm{},
|
||||
want: []string{"name", "http://example.com/", "", "certfile", "keyfile", "", ""},
|
||||
want: []string{"name", "http://example.com/", "", "certfile", "keyfile", "", "", ""},
|
||||
},
|
||||
{
|
||||
name: "repository with ca file",
|
||||
@@ -893,7 +932,7 @@ func TestHelmState_SyncRepos(t *testing.T) {
|
||||
},
|
||||
},
|
||||
helm: &exectest.Helm{},
|
||||
want: []string{"name", "http://example.com/", "cafile", "", "", "", ""},
|
||||
want: []string{"name", "http://example.com/", "cafile", "", "", "", "", ""},
|
||||
},
|
||||
{
|
||||
name: "repository with username and password",
|
||||
@@ -908,7 +947,7 @@ func TestHelmState_SyncRepos(t *testing.T) {
|
||||
},
|
||||
},
|
||||
helm: &exectest.Helm{},
|
||||
want: []string{"name", "http://example.com/", "", "", "", "example_user", "example_password"},
|
||||
want: []string{"name", "http://example.com/", "", "", "", "example_user", "example_password", ""},
|
||||
},
|
||||
}
|
||||
for i := range tests {
|
||||
@@ -921,9 +960,11 @@ func TestHelmState_SyncRepos(t *testing.T) {
|
||||
}
|
||||
}
|
||||
state := &HelmState{
|
||||
Repositories: tt.repos,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Repositories: tt.repos,
|
||||
},
|
||||
}
|
||||
if _ = state.SyncRepos(tt.helm); !reflect.DeepEqual(tt.helm.Repo, tt.want) {
|
||||
if _, _ = state.SyncRepos(tt.helm, map[string]bool{}); !reflect.DeepEqual(tt.helm.Repo, tt.want) {
|
||||
t.Errorf("HelmState.SyncRepos() for [%s] = %v, want %v", tt.name, tt.helm.Repo, tt.want)
|
||||
}
|
||||
})
|
||||
@@ -1032,9 +1073,12 @@ func TestHelmState_SyncReleases(t *testing.T) {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
Releases: tt.releases,
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
RenderedValues: map[string]interface{}{},
|
||||
}
|
||||
if errs := state.SyncReleases(&AffectedReleases{}, tt.helm, []string{}, 1); errs != nil && len(errs) > 0 {
|
||||
if len(errs) != len(tt.wantErrorMsgs) {
|
||||
@@ -1135,10 +1179,13 @@ func TestHelmState_SyncReleases_MissingValuesFileForUndesiredRelease(t *testing.
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
basePath: ".",
|
||||
Releases: []ReleaseSpec{tt.release},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
basePath: ".",
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: []ReleaseSpec{tt.release},
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
RenderedValues: map[string]interface{}{},
|
||||
}
|
||||
fs := testhelper.NewTestFs(map[string]string{})
|
||||
state = injectFs(state, fs)
|
||||
@@ -1281,9 +1328,12 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
Releases: tt.releases,
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
RenderedValues: map[string]interface{}{},
|
||||
}
|
||||
helm := &exectest.Helm{
|
||||
Lists: map[exectest.ListKey]string{},
|
||||
@@ -1291,7 +1341,7 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) {
|
||||
//simulate the release is already installed
|
||||
for i, release := range tt.releases {
|
||||
if tt.installed != nil && tt.installed[i] {
|
||||
helm.Lists[exectest.ListKey{Filter: "^" + release.Name + "$", Flags: "--deployed--failed--pending"}] = release.Name
|
||||
helm.Lists[exectest.ListKey{Filter: "^" + release.Name + "$", Flags: "--deleting--deployed--failed--pending"}] = release.Name
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1383,15 +1433,18 @@ func TestGetDeployedVersion(t *testing.T) {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
Releases: []ReleaseSpec{tt.release},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: []ReleaseSpec{tt.release},
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
RenderedValues: map[string]interface{}{},
|
||||
}
|
||||
helm := &exectest.Helm{
|
||||
Lists: map[exectest.ListKey]string{},
|
||||
}
|
||||
//simulate the helm.list call result
|
||||
helm.Lists[exectest.ListKey{Filter: "^" + tt.release.Name + "$", Flags: "--deployed--failed--pending"}] = tt.listResult
|
||||
helm.Lists[exectest.ListKey{Filter: "^" + tt.release.Name + "$", Flags: "--deleting--deployed--failed--pending"}] = tt.listResult
|
||||
|
||||
affectedReleases := AffectedReleases{}
|
||||
state.SyncReleases(&affectedReleases, helm, []string{}, 1)
|
||||
@@ -1510,11 +1563,14 @@ func TestHelmState_DiffReleases(t *testing.T) {
|
||||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
Releases: tt.releases,
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
RenderedValues: map[string]interface{}{},
|
||||
}
|
||||
_, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, false, false, false)
|
||||
_, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, false, false, false, false)
|
||||
if errs != nil && len(errs) > 0 {
|
||||
t.Errorf("unexpected error: %v", errs)
|
||||
}
|
||||
@@ -1582,13 +1638,16 @@ func TestHelmState_SyncReleasesCleanup(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
numRemovedFiles := 0
|
||||
state := &HelmState{
|
||||
Releases: tt.releases,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
removeFile: func(f string) error {
|
||||
numRemovedFiles += 1
|
||||
return nil
|
||||
},
|
||||
RenderedValues: map[string]interface{}{},
|
||||
}
|
||||
testfs := testhelper.NewTestFs(map[string]string{
|
||||
"/path/to/someFile": `foo: FOO`,
|
||||
@@ -1666,20 +1725,23 @@ func TestHelmState_DiffReleasesCleanup(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
numRemovedFiles := 0
|
||||
state := &HelmState{
|
||||
Releases: tt.releases,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
removeFile: func(f string) error {
|
||||
numRemovedFiles += 1
|
||||
return nil
|
||||
},
|
||||
RenderedValues: map[string]interface{}{},
|
||||
}
|
||||
testfs := testhelper.NewTestFs(map[string]string{
|
||||
"/path/to/someFile": `foo: bar
|
||||
`,
|
||||
})
|
||||
state = injectFs(state, testfs)
|
||||
if _, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, false, false, false); errs != nil && len(errs) > 0 {
|
||||
if _, errs := state.DiffReleases(tt.helm, []string{}, 1, false, false, false, false, false, false); errs != nil && len(errs) > 0 {
|
||||
t.Errorf("unexpected errors: %v", errs)
|
||||
}
|
||||
|
||||
@@ -1725,63 +1787,67 @@ generated: 2019-05-16T15:42:45.50486+09:00
|
||||
}
|
||||
|
||||
logger := helmexec.NewLogger(os.Stderr, "debug")
|
||||
basePath := "/src"
|
||||
state := &HelmState{
|
||||
basePath: "/src",
|
||||
basePath: basePath,
|
||||
FilePath: "/src/helmfile.yaml",
|
||||
Releases: []ReleaseSpec{
|
||||
{
|
||||
Chart: "./..",
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: []ReleaseSpec{
|
||||
{
|
||||
Chart: "/example",
|
||||
},
|
||||
{
|
||||
Chart: "./example",
|
||||
},
|
||||
{
|
||||
Chart: "published/deeper",
|
||||
},
|
||||
{
|
||||
Chart: "stable/envoy",
|
||||
Version: "1.5.0",
|
||||
},
|
||||
{
|
||||
Chart: "stable/envoy",
|
||||
Version: "1.4.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Chart: "../examples",
|
||||
},
|
||||
{
|
||||
Chart: "../../helmfile",
|
||||
},
|
||||
{
|
||||
Chart: "published",
|
||||
},
|
||||
{
|
||||
Chart: "published/deeper",
|
||||
},
|
||||
{
|
||||
Chart: "stable/envoy",
|
||||
Version: "1.5.0",
|
||||
},
|
||||
{
|
||||
Chart: "stable/envoy",
|
||||
Version: "1.4.0",
|
||||
},
|
||||
},
|
||||
Repositories: []RepositorySpec{
|
||||
{
|
||||
Name: "stable",
|
||||
URL: "https://kubernetes-charts.storage.googleapis.com",
|
||||
Repositories: []RepositorySpec{
|
||||
{
|
||||
Name: "stable",
|
||||
URL: "https://kubernetes-charts.storage.googleapis.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
tempDir: tempDir,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
fs := testhelper.NewTestFs(map[string]string{
|
||||
"/example/Chart.yaml": `foo: FOO`,
|
||||
"/src/example/Chart.yaml": `foo: FOO`,
|
||||
})
|
||||
fs.Cwd = basePath
|
||||
state = injectFs(state, fs)
|
||||
errs := state.UpdateDeps(helm)
|
||||
want := []string{"/", "/examples", "/helmfile", "/src/published", generatedDir}
|
||||
|
||||
want := []string{"/example", "./example", generatedDir}
|
||||
if !reflect.DeepEqual(helm.Charts, want) {
|
||||
t.Errorf("HelmState.UpdateDeps() = %v, want %v", helm.Charts, want)
|
||||
}
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("HelmState.UpdateDeps() - no errors, but got %d: %v", len(errs), errs)
|
||||
t.Errorf("HelmState.UpdateDeps() - unexpected %d errors: %v", len(errs), errs)
|
||||
}
|
||||
|
||||
resolved, err := state.ResolveDeps()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.Errorf("HelmState.ResolveDeps() - unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if resolved.Releases[5].Version != "1.5.0" {
|
||||
t.Errorf("unexpected version number: expected=1.5.0, got=%s", resolved.Releases[5].Version)
|
||||
if resolved.Releases[3].Version != "1.5.0" {
|
||||
t.Errorf("HelmState.ResolveDeps() - unexpected version number: expected=1.5.0, got=%s", resolved.Releases[5].Version)
|
||||
}
|
||||
if resolved.Releases[6].Version != "1.4.0" {
|
||||
t.Errorf("unexpected version number: expected=1.4.0, got=%s", resolved.Releases[6].Version)
|
||||
if resolved.Releases[4].Version != "1.4.0" {
|
||||
t.Errorf("HelmState.ResolveDeps() - unexpected version number: expected=1.4.0, got=%s", resolved.Releases[6].Version)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1790,30 +1856,32 @@ func TestHelmState_ResolveDeps_NoLockFile(t *testing.T) {
|
||||
state := &HelmState{
|
||||
basePath: "/src",
|
||||
FilePath: "/src/helmfile.yaml",
|
||||
Releases: []ReleaseSpec{
|
||||
{
|
||||
Chart: "./..",
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: []ReleaseSpec{
|
||||
{
|
||||
Chart: "./..",
|
||||
},
|
||||
{
|
||||
Chart: "../examples",
|
||||
},
|
||||
{
|
||||
Chart: "../../helmfile",
|
||||
},
|
||||
{
|
||||
Chart: "published",
|
||||
},
|
||||
{
|
||||
Chart: "published/deeper",
|
||||
},
|
||||
{
|
||||
Chart: "stable/envoy",
|
||||
},
|
||||
},
|
||||
{
|
||||
Chart: "../examples",
|
||||
},
|
||||
{
|
||||
Chart: "../../helmfile",
|
||||
},
|
||||
{
|
||||
Chart: "published",
|
||||
},
|
||||
{
|
||||
Chart: "published/deeper",
|
||||
},
|
||||
{
|
||||
Chart: "stable/envoy",
|
||||
},
|
||||
},
|
||||
Repositories: []RepositorySpec{
|
||||
{
|
||||
Name: "stable",
|
||||
URL: "https://kubernetes-charts.storage.googleapis.com",
|
||||
Repositories: []RepositorySpec{
|
||||
{
|
||||
Name: "stable",
|
||||
URL: "https://kubernetes-charts.storage.googleapis.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
logger: logger,
|
||||
@@ -1906,8 +1974,10 @@ func TestHelmState_ReleaseStatuses(t *testing.T) {
|
||||
tt := tests[i]
|
||||
f := func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
Releases: tt.releases,
|
||||
logger: logger,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
},
|
||||
logger: logger,
|
||||
fileExists: func(f string) (bool, error) {
|
||||
if f != "foo.yaml" {
|
||||
return false, fmt.Errorf("unexpected file: %s", f)
|
||||
@@ -1991,8 +2061,10 @@ func TestHelmState_TestReleasesNoCleanUp(t *testing.T) {
|
||||
tt := tests[i]
|
||||
f := func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
Releases: tt.releases,
|
||||
logger: logger,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
},
|
||||
logger: logger,
|
||||
}
|
||||
errs := state.TestReleases(tt.helm, tt.cleanup, 1, 1)
|
||||
if (errs != nil) != tt.wantErr {
|
||||
@@ -2042,8 +2114,11 @@ func TestHelmState_NoReleaseMatched(t *testing.T) {
|
||||
tt := tests[i]
|
||||
f := func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
Releases: releases,
|
||||
logger: logger,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: releases,
|
||||
},
|
||||
logger: logger,
|
||||
RenderedValues: map[string]interface{}{},
|
||||
}
|
||||
state.Selectors = []string{tt.labels}
|
||||
errs := state.FilterReleases()
|
||||
@@ -2208,11 +2283,14 @@ func TestHelmState_Delete(t *testing.T) {
|
||||
release,
|
||||
}
|
||||
state := &HelmState{
|
||||
HelmDefaults: HelmSpec{
|
||||
KubeContext: tt.defKubeContext,
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
HelmDefaults: HelmSpec{
|
||||
KubeContext: tt.defKubeContext,
|
||||
},
|
||||
Releases: releases,
|
||||
},
|
||||
Releases: releases,
|
||||
logger: logger,
|
||||
logger: logger,
|
||||
RenderedValues: map[string]interface{}{},
|
||||
}
|
||||
helm := &exectest.Helm{
|
||||
Lists: map[exectest.ListKey]string{},
|
||||
@@ -2235,3 +2313,33 @@ func TestHelmState_Delete(t *testing.T) {
|
||||
t.Run(tt.name, f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReverse(t *testing.T) {
|
||||
num := 8
|
||||
st := &HelmState{}
|
||||
|
||||
for i := 0; i < num; i++ {
|
||||
name := fmt.Sprintf("%d", i)
|
||||
st.Helmfiles = append(st.Helmfiles, SubHelmfileSpec{
|
||||
Path: name,
|
||||
})
|
||||
st.Releases = append(st.Releases, ReleaseSpec{
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
|
||||
st.Reverse()
|
||||
|
||||
for i := 0; i < num; i++ {
|
||||
j := num - 1 - i
|
||||
want := fmt.Sprintf("%d", j)
|
||||
|
||||
if got := st.Helmfiles[i].Path; got != want {
|
||||
t.Errorf("sub-helmfile at %d has incorrect path: want %q, got %q", i, want, got)
|
||||
}
|
||||
|
||||
if got := st.Releases[i].Name; got != want {
|
||||
t.Errorf("release at %d has incorrect name: want %q, got %q", i, want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
func createTempValuesFile(release *ReleaseSpec, data interface{}) (*os.File, error) {
|
||||
p, err := tempValuesFilePath(release, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := os.Create(*p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func tempValuesFilePath(release *ReleaseSpec, data interface{}) (*string, error) {
|
||||
id, err := generateValuesID(release, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
workDir := os.Getenv("HELMFILE_TEMPDIR")
|
||||
if workDir == "" {
|
||||
workDir, err = ioutil.TempDir(os.TempDir(), "helmfile")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
d := filepath.Join(workDir, id)
|
||||
|
||||
info, err := os.Stat(d)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
panic(err)
|
||||
} else if info == nil {
|
||||
|
||||
}
|
||||
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
func generateValuesID(release *ReleaseSpec, data interface{}) (string, error) {
|
||||
var id []string
|
||||
|
||||
if release.Namespace != "" {
|
||||
id = append(id, release.Namespace)
|
||||
}
|
||||
|
||||
id = append(id, release.Name, "values")
|
||||
|
||||
hash, err := HashObject([]interface{}{release, data})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
id = append(id, hash)
|
||||
|
||||
return strings.Join(id, "-"), nil
|
||||
}
|
||||
|
||||
func HashObject(obj interface{}) (string, error) {
|
||||
hash := fnv.New32a()
|
||||
|
||||
hash.Reset()
|
||||
|
||||
printer := spew.ConfigState{
|
||||
Indent: " ",
|
||||
SortKeys: true,
|
||||
DisableMethods: true,
|
||||
SpewKeys: true,
|
||||
}
|
||||
printer.Fprintf(hash, "%#v", obj)
|
||||
|
||||
sum := fmt.Sprint(hash.Sum32())
|
||||
|
||||
return SafeEncodeString(sum), nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// See k8s.io/apimachinery/pkg/util/rand/rand.go
|
||||
package state
|
||||
|
||||
const (
|
||||
// We omit vowels from the set of available characters to reduce the chances
|
||||
// of "bad words" being formed.
|
||||
alphanums = "bcdfghjklmnpqrstvwxz2456789"
|
||||
)
|
||||
|
||||
// SafeEncodeString encodes s using the same characters as rand.String. This reduces the chances of bad words and
|
||||
// ensures that strings generated from hash functions appear consistent throughout the API.
|
||||
func SafeEncodeString(s string) string {
|
||||
r := make([]byte, len(s))
|
||||
for i, b := range []rune(s) {
|
||||
r[i] = alphanums[(int(b) % len(alphanums))]
|
||||
}
|
||||
return string(r)
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestGenerateID(t *testing.T) {
|
||||
type testcase struct {
|
||||
subject string
|
||||
release ReleaseSpec
|
||||
data interface{}
|
||||
want string
|
||||
}
|
||||
|
||||
ids := map[string]int{}
|
||||
|
||||
run := func(tc testcase) {
|
||||
t.Helper()
|
||||
|
||||
t.Run(tc.subject, func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
got, err := generateValuesID(&tc.release, tc.data)
|
||||
if err != nil {
|
||||
t.Fatalf("uenxpected error: %v", err)
|
||||
}
|
||||
|
||||
if d := cmp.Diff(tc.want, got); d != "" {
|
||||
t.Fatalf("unexpected result: want (-), got (+):\n%s", d)
|
||||
}
|
||||
|
||||
ids[got]++
|
||||
})
|
||||
}
|
||||
|
||||
run(testcase{
|
||||
subject: "baseline",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
want: "foo-values-779795898b",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different bytes content",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
data: []byte(`{"k":"v"}`),
|
||||
want: "foo-values-6b7ffbccc",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different map content",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
data: map[string]interface{}{"k": "v"},
|
||||
want: "foo-values-6d57bbfccf",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different chart",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"},
|
||||
want: "foo-values-5c45b58947",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different name",
|
||||
release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"},
|
||||
want: "bar-values-99d5fdbcc",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "specific ns",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"},
|
||||
want: "myns-foo-values-544cb97d7f",
|
||||
})
|
||||
|
||||
for id, n := range ids {
|
||||
if n > 1 {
|
||||
t.Fatalf("too many occurences of %s: %d", id, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
-4
@@ -19,12 +19,44 @@ type EnvironmentTemplateData struct {
|
||||
Values map[string]interface{}
|
||||
}
|
||||
|
||||
// releaseTemplateData provides variables accessible while executing golang text/template expressions in releases of a helmfile YAML file
|
||||
// releaseTemplateData provides variables accessible while executing golang text/template expressions in release templates
|
||||
// and release values templates within a Helmfile YAML file.
|
||||
type releaseTemplateData struct {
|
||||
// Environment is accessible as `.Environment` from any template expression executed by the renderer
|
||||
Environment environment.Environment
|
||||
// Release is accessible as `.Release` from any template expression executed by the renderer
|
||||
Release ReleaseSpec
|
||||
// Release is accessible as `.Release` from any template expression executed by the renderer.
|
||||
// It contains a subset of ReleaseSpec that is known to be useful to dynamically render values.
|
||||
Release releaseTemplateDataRelease
|
||||
// Values is accessible as `.Values` and it contains default state values overrode by environment values and override values.
|
||||
Values map[string]interface{}
|
||||
Values map[string]interface{}
|
||||
StateValues *map[string]interface{}
|
||||
// KubeContext is HelmState.OverrideKubeContext.
|
||||
// You should better use Release.KubeContext as it might work as you'd expect even if HelmState.OverrideKubeContext is not set.
|
||||
// See releaseTemplateDataRelease.KubeContext for more information.
|
||||
KubeContext string
|
||||
// Namespace is HelmState.OverrideNamespace.
|
||||
// You should better use Release.Namespace as it might work as you'd expect even if OverrideNamespace is not set.
|
||||
// See releaseTemplateDataRelease.Namespace for more information.
|
||||
Namespace string
|
||||
// Chart is HelmState.OverrideChart.
|
||||
// You should better use Release.Chart as it might work as you'd expect even if OverrideChart is not set.
|
||||
// See releaseTemplateDataRelease.Chart for more information.
|
||||
Chart string
|
||||
}
|
||||
|
||||
type releaseTemplateDataRelease struct {
|
||||
// Name is basically ReleaseSpec.Name exposed to the template
|
||||
Name string
|
||||
|
||||
// Namespace is HelmState.OverrideNamespace, or if it's empty, ReleaseSpec.Namespace.
|
||||
Namespace string
|
||||
|
||||
// Labels is ReleaseSpec.Labels
|
||||
Labels map[string]string
|
||||
|
||||
// Chart is ReleaseSpec.Chart
|
||||
Chart string
|
||||
|
||||
// KubeContext is ReleaseSpec.KubeContext
|
||||
KubeContext string
|
||||
}
|
||||
|
||||
+9
-3
@@ -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",
|
||||
|
||||
+13
-12
@@ -21,7 +21,7 @@ type TestFs struct {
|
||||
func NewTestFs(files map[string]string) *TestFs {
|
||||
dirs := map[string]bool{}
|
||||
for abs, _ := range files {
|
||||
for d := filepath.Dir(abs); !dirs[d]; d = filepath.Dir(d) {
|
||||
for d := filepath.ToSlash(filepath.Dir(abs)); !dirs[d]; d = filepath.ToSlash(filepath.Dir(d)) {
|
||||
dirs[d] = true
|
||||
fmt.Fprintf(os.Stderr, "testfs: recognized dir: %s\n", d)
|
||||
}
|
||||
@@ -39,10 +39,10 @@ func NewTestFs(files map[string]string) *TestFs {
|
||||
|
||||
func (f *TestFs) FileExistsAt(path string) bool {
|
||||
var ok bool
|
||||
if strings.Contains(path, "/") {
|
||||
if strings.HasPrefix(path, "/") {
|
||||
_, ok = f.files[path]
|
||||
} else {
|
||||
_, ok = f.files[filepath.Join(f.Cwd, path)]
|
||||
_, ok = f.files[filepath.ToSlash(filepath.Join(f.Cwd, path))]
|
||||
}
|
||||
return ok
|
||||
}
|
||||
@@ -53,10 +53,10 @@ func (f *TestFs) FileExists(path string) (bool, error) {
|
||||
|
||||
func (f *TestFs) DirectoryExistsAt(path string) bool {
|
||||
var ok bool
|
||||
if strings.Contains(path, "/") {
|
||||
if strings.HasPrefix(path, "/") {
|
||||
_, ok = f.dirs[path]
|
||||
} else {
|
||||
_, ok = f.dirs[filepath.Join(f.Cwd, path)]
|
||||
_, ok = f.dirs[filepath.ToSlash(filepath.Join(f.Cwd, path))]
|
||||
}
|
||||
return ok
|
||||
}
|
||||
@@ -64,13 +64,13 @@ func (f *TestFs) DirectoryExistsAt(path string) bool {
|
||||
func (f *TestFs) ReadFile(filename string) ([]byte, error) {
|
||||
var str string
|
||||
var ok bool
|
||||
if filename[0] == '/' {
|
||||
if strings.HasPrefix(filename, "/") {
|
||||
str, ok = f.files[filename]
|
||||
} else {
|
||||
str, ok = f.files[filepath.Join(f.Cwd, filename)]
|
||||
str, ok = f.files[filepath.ToSlash(filepath.Join(f.Cwd, filename))]
|
||||
}
|
||||
if !ok {
|
||||
return []byte(nil), fmt.Errorf("no registered file found: %s", filename)
|
||||
return []byte(nil), os.ErrNotExist
|
||||
}
|
||||
|
||||
f.fileReaderCalls += 1
|
||||
@@ -90,10 +90,10 @@ func (f *TestFs) FileReaderCalls() int {
|
||||
|
||||
func (f *TestFs) Glob(relPattern string) ([]string, error) {
|
||||
var pattern string
|
||||
if relPattern[0] == '/' {
|
||||
if strings.HasPrefix(relPattern, "/") {
|
||||
pattern = relPattern
|
||||
} else {
|
||||
pattern = filepath.Join(f.Cwd, relPattern)
|
||||
pattern = filepath.ToSlash(filepath.Join(f.Cwd, relPattern))
|
||||
}
|
||||
|
||||
fixtures, ok := f.GlobFixtures[pattern]
|
||||
@@ -115,13 +115,14 @@ func (f *TestFs) Glob(relPattern string) ([]string, error) {
|
||||
}
|
||||
|
||||
func (f *TestFs) Abs(path string) (string, error) {
|
||||
path = filepath.ToSlash(path)
|
||||
var p string
|
||||
if path[0] == '/' {
|
||||
if strings.HasPrefix(path, "/") {
|
||||
p = path
|
||||
} else {
|
||||
p = filepath.Join(f.Cwd, path)
|
||||
}
|
||||
return filepath.Clean(p), nil
|
||||
return filepath.ToSlash(filepath.Clean(p)), nil
|
||||
}
|
||||
|
||||
func (f *TestFs) Getwd() (string, error) {
|
||||
|
||||
@@ -2,8 +2,9 @@ package tmpl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/roboll/helmfile/pkg/helmexec"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"gopkg.in/yaml.v2"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -97,25 +98,9 @@ func (c *Context) Exec(command string, args []interface{}, inputs ...string) (st
|
||||
g.Go(func() error {
|
||||
// We use CombinedOutput to produce helpful error messages
|
||||
// See https://github.com/roboll/helmfile/issues/1158
|
||||
bs, err := cmd.CombinedOutput()
|
||||
bs, err := helmexec.Output(cmd)
|
||||
if err != nil {
|
||||
args := strings.Join(strArgs, ", ")
|
||||
shownCmd := []string{command}
|
||||
if len(args) > 0 {
|
||||
shownCmd = append(shownCmd, args)
|
||||
}
|
||||
|
||||
var out string
|
||||
|
||||
out += fmt.Sprintf("\n\nCOMMAND:\n%s", Indent(strings.Join(shownCmd, " "), " "))
|
||||
|
||||
out += fmt.Sprintf("\n\nERROR:\n%s", Indent(err.Error(), " "))
|
||||
|
||||
if len(bs) > 0 {
|
||||
out += fmt.Sprintf("\n\nCOMBINED OUTPUT:\n%s", Indent(string(bs), " "))
|
||||
}
|
||||
|
||||
return fmt.Errorf("%v%s", err, out)
|
||||
return err
|
||||
}
|
||||
|
||||
bytes = bs
|
||||
@@ -130,29 +115,6 @@ func (c *Context) Exec(command string, args []interface{}, inputs ...string) (st
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
// indents a block of text with an indent string
|
||||
func Indent(text, indent string) string {
|
||||
var b strings.Builder
|
||||
|
||||
b.Grow(len(text) * 2)
|
||||
|
||||
lines := strings.Split(text, "\n")
|
||||
|
||||
last := len(lines) - 1
|
||||
|
||||
for i, j := range lines {
|
||||
if i > 0 && i < last && j != "" {
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
if j != "" {
|
||||
b.WriteString(indent + j)
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (c *Context) ReadFile(filename string) (string, error) {
|
||||
var path string
|
||||
if filepath.IsAbs(filename) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package tmpl
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -47,12 +49,31 @@ func TestReadFile_PassAbsPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestToYaml_UnsupportedNestedMapKey(t *testing.T) {
|
||||
expected := ``
|
||||
vals := Values(map[string]interface{}{
|
||||
"foo": map[interface{}]interface{}{
|
||||
"bar": "BAR",
|
||||
},
|
||||
})
|
||||
actual, err := ToYaml(vals)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error but got none")
|
||||
} else if err.Error() != "error marshaling into JSON: json: unsupported type: map[interface {}]interface {}" {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("unexpected result: expected=%v, actual=%v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToYaml(t *testing.T) {
|
||||
expected := `foo:
|
||||
bar: BAR
|
||||
`
|
||||
vals := Values(map[string]interface{}{
|
||||
"foo": map[interface{}]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "BAR",
|
||||
},
|
||||
})
|
||||
@@ -70,7 +91,7 @@ func TestFromYaml(t *testing.T) {
|
||||
bar: BAR
|
||||
`
|
||||
expected := Values(map[string]interface{}{
|
||||
"foo": map[interface{}]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "BAR",
|
||||
},
|
||||
})
|
||||
@@ -83,6 +104,27 @@ func TestFromYaml(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFromYamlToJson(t *testing.T) {
|
||||
input := `foo:
|
||||
bar: BAR
|
||||
`
|
||||
want := `{"foo":{"bar":"BAR"}}`
|
||||
|
||||
m, err := FromYaml(input)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
got, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if d := cmp.Diff(want, string(got)); d != "" {
|
||||
t.Errorf("unexpected result: want (-), got (+):\n%s", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetValueAtPath_OneComponent(t *testing.T) {
|
||||
input := map[string]interface{}{
|
||||
"foo": "",
|
||||
|
||||
@@ -2,15 +2,37 @@ package tmpl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/Masterminds/sprig"
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func (c *Context) newTemplate() *template.Template {
|
||||
func (c *Context) CreateFuncMap() template.FuncMap {
|
||||
aliased := template.FuncMap{}
|
||||
|
||||
aliases := map[string]string{
|
||||
"get": "sprigGet",
|
||||
}
|
||||
|
||||
funcMap := sprig.TxtFuncMap()
|
||||
|
||||
for orig, alias := range aliases {
|
||||
aliased[alias] = funcMap[orig]
|
||||
}
|
||||
|
||||
for name, f := range c.createFuncMap() {
|
||||
funcMap[name] = f
|
||||
}
|
||||
|
||||
for name, f := range aliased {
|
||||
funcMap[name] = f
|
||||
}
|
||||
|
||||
return funcMap
|
||||
}
|
||||
|
||||
func (c *Context) newTemplate() *template.Template {
|
||||
funcMap := c.CreateFuncMap()
|
||||
|
||||
tmpl := template.New("stringTemplate").Funcs(funcMap)
|
||||
if c.preRender {
|
||||
tmpl = tmpl.Option("missingkey=zero")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-test/deep"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
)
|
||||
|
||||
type diffSource string
|
||||
|
||||
const (
|
||||
diffSourceLeft diffSource = "left"
|
||||
diffSourceRight diffSource = "right"
|
||||
)
|
||||
|
||||
var (
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "diff-yamls dir-with-yamls/ dir-with-yamls/",
|
||||
Short: "Print any diff between the given directories",
|
||||
Long: `Similar to the 'diff' command, but file contents
|
||||
are compared after being parsed as a set of Kubernetes manifests.`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: run,
|
||||
}
|
||||
)
|
||||
|
||||
func run(cmd *cobra.Command, args []string) {
|
||||
left := args[0]
|
||||
right := args[1]
|
||||
leftYamls, err := globYamlFilenames(left)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
rightYamls, err := globYamlFilenames(right)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
exitCode := 0
|
||||
|
||||
onlyInLeft := leftYamls.Difference(rightYamls)
|
||||
if onlyInLeft.Len() > 0 {
|
||||
exitCode = 1
|
||||
for _, f := range onlyInLeft.List() {
|
||||
fmt.Fprintf(os.Stderr, "Only in %s: %s\n", left, f)
|
||||
}
|
||||
}
|
||||
|
||||
onlyInRight := rightYamls.Difference(leftYamls)
|
||||
if onlyInRight.Len() > 0 {
|
||||
exitCode = 1
|
||||
for _, f := range onlyInRight.List() {
|
||||
fmt.Fprintf(os.Stderr, "Only in %s: %s\n", right, f)
|
||||
}
|
||||
}
|
||||
|
||||
inBoth := leftYamls.Intersection(rightYamls)
|
||||
for _, f := range inBoth.List() {
|
||||
leftPath := filepath.Join(left, f)
|
||||
leftNodes, err := readManifest(leftPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
rightPath := filepath.Join(right, f)
|
||||
rightNodes, err := readManifest(rightPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
ps := pairs{}
|
||||
for _, node := range leftNodes {
|
||||
if err := ps.add(node, diffSourceLeft); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
for _, node := range rightNodes {
|
||||
if err := ps.add(node, diffSourceRight); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
for _, p := range ps.list {
|
||||
switch {
|
||||
case p.left != nil && p.right == nil:
|
||||
fmt.Fprintf(os.Stderr, "Only in %s: %s\n", leftPath, p.left.getId())
|
||||
exitCode = 1
|
||||
case p.left == nil && p.right != nil:
|
||||
fmt.Fprintf(os.Stderr, "Only in %s: %s\n", rightPath, p.right.getId())
|
||||
exitCode = 1
|
||||
default:
|
||||
diff := deep.Equal(p.left, p.right)
|
||||
if diff != nil {
|
||||
id := p.left.getId()
|
||||
fmt.Fprintf(os.Stderr, "< %s %s\n", id, leftPath)
|
||||
fmt.Fprintf(os.Stderr, "> %s %s\n", id, rightPath)
|
||||
for _, d := range diff {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", d)
|
||||
}
|
||||
exitCode = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func globYamlFilenames(dir string) (sets.String, error) {
|
||||
matches, err := filepath.Glob(filepath.Join(dir, "*.yaml"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
set := sets.String{}
|
||||
for _, f := range matches {
|
||||
set.Insert(filepath.Base(f))
|
||||
}
|
||||
return set, nil
|
||||
}
|
||||
|
||||
type resource map[string]interface{}
|
||||
|
||||
type meta struct {
|
||||
apiVersion string
|
||||
kind string
|
||||
name string
|
||||
namespace string
|
||||
}
|
||||
|
||||
func (res resource) getMeta() (meta, error) {
|
||||
if len(res) == 0 {
|
||||
return meta{}, nil
|
||||
}
|
||||
m := meta{}
|
||||
apiVersion, _ := res["apiVersion"].(string)
|
||||
m.apiVersion = apiVersion
|
||||
kind, _ := res["kind"].(string)
|
||||
m.kind = kind
|
||||
metadata, _ := res["metadata"].(map[string]interface{})
|
||||
name, _ := metadata["name"].(string)
|
||||
m.name = name
|
||||
namespace, _ := metadata["namespace"].(string)
|
||||
m.namespace = namespace
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func readManifest(path string) ([]resource, error) {
|
||||
var err error
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
decoder := yaml.NewYAMLToJSONDecoder(f)
|
||||
resources := []resource{}
|
||||
for {
|
||||
r := make(resource)
|
||||
err = decoder.Decode(&r)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if len(r) > 0 {
|
||||
resources = append(resources, r)
|
||||
}
|
||||
}
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
func (res resource) getId() string {
|
||||
meta, err := res.getMeta()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%v", res)
|
||||
}
|
||||
ns := meta.namespace
|
||||
if ns == "" {
|
||||
ns = "~X"
|
||||
}
|
||||
nm := meta.name
|
||||
if nm == "" {
|
||||
nm = "~N"
|
||||
}
|
||||
gv := meta.apiVersion
|
||||
if gv == "" {
|
||||
gv = "~G_~V"
|
||||
}
|
||||
k := meta.kind
|
||||
if k == "" {
|
||||
k = "~K"
|
||||
}
|
||||
gvk := strings.Join([]string{gv, k}, "_")
|
||||
return strings.Join([]string{gvk, ns, nm}, "|")
|
||||
|
||||
}
|
||||
|
||||
// lifted from kustomize/kyaml/kio/filters/merge3.go
|
||||
type pairs struct {
|
||||
list []*pair
|
||||
}
|
||||
|
||||
func (ps *pairs) isSameResource(meta1, meta2 meta) bool {
|
||||
if meta1.name != meta2.name {
|
||||
return false
|
||||
}
|
||||
if meta1.namespace != meta2.namespace {
|
||||
return false
|
||||
}
|
||||
if meta1.apiVersion != meta2.apiVersion {
|
||||
return false
|
||||
}
|
||||
if meta1.kind != meta2.kind {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ps *pairs) add(node resource, source diffSource) error {
|
||||
nodeMeta, err := node.getMeta()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range ps.list {
|
||||
p := ps.list[i]
|
||||
if ps.isSameResource(p.meta, nodeMeta) {
|
||||
return p.add(node, source)
|
||||
}
|
||||
}
|
||||
p := &pair{meta: nodeMeta}
|
||||
if err := p.add(node, source); err != nil {
|
||||
return err
|
||||
}
|
||||
ps.list = append(ps.list, p)
|
||||
return nil
|
||||
}
|
||||
|
||||
type pair struct {
|
||||
meta meta
|
||||
left resource
|
||||
right resource
|
||||
}
|
||||
|
||||
func (p *pair) add(node resource, source diffSource) error {
|
||||
switch source {
|
||||
case diffSourceLeft:
|
||||
if p.left != nil {
|
||||
return fmt.Errorf("left source already specified")
|
||||
}
|
||||
p.left = node
|
||||
case diffSourceRight:
|
||||
if p.right != nil {
|
||||
return fmt.Errorf("right source already specified")
|
||||
}
|
||||
p.right = node
|
||||
default:
|
||||
return fmt.Errorf("unknown diff source value: %s", source)
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
rootCmd.Execute()
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
:-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: This is a revocation certificate
|
||||
|
||||
iQG2BCABCgAgFiEEstbXu+wDsuZlccjACtGOFs/e9wAFAmA4+f8CHQAACgkQCtGO
|
||||
Fs/e9wBN4wv/S0D4RCu5+BLt8y0vpAI9o1iRXj+yzOvPJUCm/QH1kN39saeI3rbr
|
||||
8pyytpmzO5wIr+G+6BHh56p3NzjxWxfIt/RQ1vPMYf/dxzvtRuvYY00Fu683A65i
|
||||
UqU9hiA6q1310OrMyEds4GmIteM++5xtKhV2s3A4bXJp6QD83MKV3m1IFYJ5cWfM
|
||||
GmUSVlXBMiZ5Vfe8a04KaPKU3EkVSSIxLsvOW5+tPDZzkdUAHMCoHRAIBeOd7Aqo
|
||||
uNytWylZtk1SeSeglbpm22NrXdvQbxV2oZplEazegqkydyUy8XmCh/57t1lHekAZ
|
||||
Dcw0XgXu1s5TWj0vXa2g9sW+CKLGsDNg5XYp2cR4UGkGXCctp7aZb6k1+wv33zjB
|
||||
hEL03GRq4AQ7yRzMsP0ue1YveolUfHRy+9OlFVdN25rL2VOvIkAxknnpe1c0gPLk
|
||||
NOer0NAVPzE8QzHFQIDICriJ2urfzMm/nfa2UHW8O81opib5LNRbcPz+GOmKEgLf
|
||||
aKRmcXNIZkPx
|
||||
=ohmg
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,21 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
description: Chart for testing helmx features
|
||||
name: helmx
|
||||
version: 0.1.0
|
||||
@@ -0,0 +1,16 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "helmx.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
*/}}
|
||||
{{- define "helmx.fullname" -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: release-name
|
||||
data:
|
||||
name: {{ .Release.Name }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: release-namespace
|
||||
data:
|
||||
namespace: {{ .Release.Namespace }}
|
||||
@@ -1,4 +1,4 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
metadata:
|
||||
@@ -10,6 +10,9 @@ metadata:
|
||||
heritage: {{ .Release.Service }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ template "httpbin.name" . }}
|
||||
strategy: {}
|
||||
template:
|
||||
metadata:
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
key_1: value
|
||||
key_2: value
|
||||
key_shared: value
|
||||
@@ -0,0 +1,2 @@
|
||||
key_1: value_1
|
||||
key_shared: value_1
|
||||
@@ -0,0 +1,2 @@
|
||||
key_2: value_2
|
||||
key_shared: value_2
|
||||
@@ -1 +1,3 @@
|
||||
mysecret: MYSECRET
|
||||
raw2:
|
||||
enabled: false
|
||||
|
||||
@@ -4,10 +4,8 @@ environments:
|
||||
- environment.values.yaml
|
||||
---
|
||||
repositories:
|
||||
- name: stable
|
||||
url: https://kubernetes-charts.storage.googleapis.com/
|
||||
- name: incubator
|
||||
url: https://kubernetes-charts-incubator.storage.googleapis.com
|
||||
url: https://charts.helm.sh/incubator
|
||||
|
||||
helmDefaults:
|
||||
kubeContext: minikube
|
||||
@@ -19,9 +17,40 @@ releases:
|
||||
set:
|
||||
- name: ingress.enabled
|
||||
value: false
|
||||
hooks:
|
||||
- events: ["prepare", "cleanup"]
|
||||
showlogs: true
|
||||
command: "echo"
|
||||
args:
|
||||
- "{{`{{.Environment.Name}}`}}"
|
||||
- "{{`{{.Release.Name}}`}}"
|
||||
- "{{`{{.HelmfileCommand}}`}}"
|
||||
|
||||
- name: raw
|
||||
chart: incubator/raw
|
||||
version: 0.2.3
|
||||
values:
|
||||
- mysecret: {{ .Environment.Values.mysecret }}
|
||||
- values.yaml
|
||||
|
||||
- name: helmx
|
||||
chart: ./charts/helmx
|
||||
namespace: helmx-system
|
||||
jsonPatches:
|
||||
- target:
|
||||
version: v1
|
||||
kind: ConfigMap
|
||||
name: release-name
|
||||
patch:
|
||||
- op: add
|
||||
path: /metadata/annotations
|
||||
value:
|
||||
foo: bar
|
||||
|
||||
- name: raw2
|
||||
chart: incubator/raw
|
||||
version: 0.2.3
|
||||
condition: raw2.enabled
|
||||
values:
|
||||
- mysecret: {{ .Environment.Values.mysecret }}
|
||||
- values.yaml
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
repositories:
|
||||
- name: prometheus-community
|
||||
url: https://prometheus-community.github.io/helm-charts
|
||||
|
||||
releases:
|
||||
- name: prometheus-operator-k8s-test
|
||||
chart: prometheus-community/kube-prometheus-stack
|
||||
namespace: monitoring
|
||||
forceNamespace: monitoring
|
||||
version: 15.4.6
|
||||
labels:
|
||||
component: test-upg
|
||||
values:
|
||||
- nameOverride: k8s
|
||||
fullnameOverride: prometheus-k8s
|
||||
global:
|
||||
imagePullSecrets:
|
||||
- name: regsecret
|
||||
alertmanager:
|
||||
enabled: false
|
||||
grafana:
|
||||
enabled: {{ .Values.grafanaEnabled }}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user