diff --git a/go.mod b/go.mod index 0b0143d7d..620c4642e 100644 --- a/go.mod +++ b/go.mod @@ -49,3 +49,6 @@ replace k8s.io/client-go => k8s.io/client-go v0.0.0-20191016111102-bec269661e48 // Pin Flux to master branch to break weaveworks/flux circular dependency (to be removed on Flux 1.18) replace github.com/fluxcd/flux => github.com/fluxcd/flux v1.17.2-0.20200121140732-3903cf8e71c3 + +// Patched release of Helm v3.0.3 until https://github.com/helm/helm/pull/7401 is merged +replace helm.sh/helm/v3 => github.com/hiddeco/helm/v3 v3.0.3-scheme-patched diff --git a/go.sum b/go.sum index 88c6c0e55..d782292b9 100644 --- a/go.sum +++ b/go.sum @@ -349,6 +349,8 @@ github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoI github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hiddeco/helm/v3 v3.0.3-scheme-patched h1:upWbTZDc4CNQXjDtoFKF+V6pQMLByUl76gkLcoOK2Ek= +github.com/hiddeco/helm/v3 v3.0.3-scheme-patched/go.mod h1:KBxE6XWO57XSNA1PA9CvVLYRY0zWqYQTad84bNXp1lw= github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -777,9 +779,6 @@ gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -helm.sh/helm/v3 v3.0.2/go.mod h1:KBxE6XWO57XSNA1PA9CvVLYRY0zWqYQTad84bNXp1lw= -helm.sh/helm/v3 v3.0.3 h1:FwoMFwq6evEuljmP9s08WPScb/QzVLB6ZXR2P/MZP6c= -helm.sh/helm/v3 v3.0.3/go.mod h1:KBxE6XWO57XSNA1PA9CvVLYRY0zWqYQTad84bNXp1lw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/test/e2e/15_upgrade.bats b/test/e2e/15_upgrade.bats index 4e7232a3b..cb5397517 100644 --- a/test/e2e/15_upgrade.bats +++ b/test/e2e/15_upgrade.bats @@ -4,6 +4,7 @@ function setup() { # Load libraries in setup() to access BATS_* variables load lib/env load lib/defer + load lib/helm load lib/install load lib/poll @@ -22,12 +23,12 @@ function setup() { kubectl create namespace "$DEMO_NAMESPACE" } -@test "Git mutation causes upgrade" { - # Apply the HelmRelease fixtures +@test "When a mutation it Git is made, a release is upgraded" { + # Apply the HelmRelease fixture kubectl apply -f "$FIXTURES_DIR/releases/git.yaml" >&3 # Wait for it to be deployed - poll_until_equals 'podinfo-git HelmRelease' 'deployed' "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o 'custom-columns=status:status.releaseStatus' --no-headers" + poll_until_equals 'release to be deployed' 'deployed' "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o 'custom-columns=status:status.releaseStatus' --no-headers" # Clone the charts repository local clone_dir @@ -46,16 +47,16 @@ function setup() { git push >&3 # Assert change is rolled out - poll_until_equals 'podinfo-git HelmRelease chart update' "successfully cloned chart revision: $head_hash" "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o jsonpath='{.status.conditions[?(@.type==\"ChartFetched\")].message}'" - poll_until_equals 'podinfo-git HelmRelease revision matches' "$head_hash" "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o jsonpath='{.status.revision}'" + poll_until_equals 'release chart update' "successfully cloned chart revision: $head_hash" "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o jsonpath='{.status.conditions[?(@.type==\"ChartFetched\")].message}'" + poll_until_equals 'revision match' "$head_hash" "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o jsonpath='{.status.revision}'" } -@test "Git values.yaml change causes upgrade" { - # Apply the HelmRelease fixtures +@test "When a values.yaml change in Git is made, a release is upgraded" { + # Apply the HelmRelease fixture kubectl apply -f "$FIXTURES_DIR/releases/git.yaml" >&3 # Wait for it to be deployed - poll_until_equals 'podinfo-git HelmRelease' 'deployed' "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o 'custom-columns=status:status.releaseStatus' --no-headers" + poll_until_equals 'release to be deployed' 'deployed' "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o 'custom-columns=status:status.releaseStatus' --no-headers" # Clone the charts repository local clone_dir @@ -74,8 +75,43 @@ function setup() { git push >&3 # Assert change is rolled out - poll_until_equals 'podinfo-git HelmRelease chart update' "successfully cloned chart revision: $head_hash" "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o jsonpath='{.status.conditions[?(@.type==\"ChartFetched\")].message}'" - poll_until_equals 'podinfo-git HelmRelease revision matches' "$head_hash" "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o jsonpath='{.status.revision}'" + poll_until_equals 'release chart update' "successfully cloned chart revision: $head_hash" "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o jsonpath='{.status.conditions[?(@.type==\"ChartFetched\")].message}'" + poll_until_equals 'revision match' "$head_hash" "kubectl -n $DEMO_NAMESPACE get helmrelease/podinfo-git -o jsonpath='{.status.revision}'" +} + +@test "When a HelmRelease is nested in a chart, an upgrade does succeed" { + # Install chartmuseum + install_chartmuseum chartmuseum_result + # shellcheck disable=SC2154 + local CHARTMUSEUM_URL="http://localhost:${chartmuseum_result[0]}" + # Teardown the created port-forward to chartmusem. + defer kill "${chartmuseum_result[1]}" + + # Package and upload chart fixture + package_and_upload_chart "$FIXTURES_DIR/charts/nested-helmrelease" "$CHARTMUSEUM_URL" + + # Apply the HelmRelease fixture + kubectl apply -f "$FIXTURES_DIR/releases/nested-helmrelease.yaml" >&3 + + # Wait for it and the child release to be deployed + poll_until_equals 'release to be deployed' 'deployed' "kubectl -n $DEMO_NAMESPACE get helmrelease/nested-helmrelease -o 'custom-columns=status:status.releaseStatus' --no-headers" + poll_until_equals 'child release to be deployed' 'deployed' "kubectl -n $DEMO_NAMESPACE get helmrelease/nested-helmrelease-child -o 'custom-columns=status:status.releaseStatus' --no-headers" + + releaseGen=$(kubectl -n "$DEMO_NAMESPACE" get helmrelease/nested-helmrelease -o 'custom-columns=status:status.observedGeneration' --no-headers) + childReleaseGen=$(kubectl -n "$DEMO_NAMESPACE" get helmrelease/nested-helmrelease -o 'custom-columns=status:status.observedGeneration' --no-headers) + + # Patch release + kubectl patch -f "$FIXTURES_DIR/releases/nested-helmrelease.yaml" --type='json' -p='[{"op": "replace", "path": "/spec/values/nested/deeper/deepest/image/tag", "value": "1.1.0"}]' >&3 + + # Wait for patch to be processed + poll_until_equals 'patch to be processed for release' "$((releaseGen+1))" "kubectl -n $DEMO_NAMESPACE get helmrelease/nested-helmrelease -o 'custom-columns=status:status.observedGeneration' --no-headers" + poll_until_equals 'patch to be processed for child release' "$((childReleaseGen+1))" "kubectl -n $DEMO_NAMESPACE get helmrelease/nested-helmrelease -o 'custom-columns=status:status.observedGeneration' --no-headers" + + # Assert successful release + releaseStatus=$(kubectl -n "$DEMO_NAMESPACE" get helmrelease/nested-helmrelease -o jsonpath='{.status.conditions[?(@.type=="Released")].reason}') + [ "$releaseStatus" = "HelmSuccess" ] + childReleaseStatus=$(kubectl -n "$DEMO_NAMESPACE" get helmrelease/nested-helmrelease-child -o jsonpath='{.status.conditions[?(@.type=="Released")].reason}') + [ "$childReleaseStatus" = "HelmSuccess" ] } function teardown() { diff --git a/test/e2e/fixtures/charts/nested-helmrelease/Chart.yaml b/test/e2e/fixtures/charts/nested-helmrelease/Chart.yaml new file mode 100644 index 000000000..d6cf3e48b --- /dev/null +++ b/test/e2e/fixtures/charts/nested-helmrelease/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: Nested HelmRelease chart +name: nested-helmrelease +version: 0.1.0 diff --git a/test/e2e/fixtures/charts/nested-helmrelease/templates/helmrelease.yaml b/test/e2e/fixtures/charts/nested-helmrelease/templates/helmrelease.yaml new file mode 100644 index 000000000..31745ffe4 --- /dev/null +++ b/test/e2e/fixtures/charts/nested-helmrelease/templates/helmrelease.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: helm.fluxcd.io/v1 +kind: HelmRelease +metadata: + name: {{ .Release.Name }}-child + namespace: {{ .Release.Namespace }} +spec: + releaseName: {{ .Release.Name }}-child + chart: + repository: https://stefanprodan.github.io/podinfo + version: 3.2.0 + name: podinfo + values: + {{- toYaml .Values.nested.deeper.deepest | nindent 4 }} diff --git a/test/e2e/fixtures/charts/nested-helmrelease/values.yaml b/test/e2e/fixtures/charts/nested-helmrelease/values.yaml new file mode 100644 index 000000000..e698335f3 --- /dev/null +++ b/test/e2e/fixtures/charts/nested-helmrelease/values.yaml @@ -0,0 +1,6 @@ +nested: + deeper: + deepest: + image: + name: some-other-image + tag: 1.0.1 diff --git a/test/e2e/fixtures/kustom/base/chartmuseum/chartmuseum.yaml b/test/e2e/fixtures/kustom/base/chartmuseum/chartmuseum.yaml new file mode 100644 index 000000000..d33c47e95 --- /dev/null +++ b/test/e2e/fixtures/kustom/base/chartmuseum/chartmuseum.yaml @@ -0,0 +1,75 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + name: chartmuseum + name: chartmuseum +spec: + replicas: 1 + selector: + matchLabels: + name: chartmuseum + template: + metadata: + labels: + name: chartmuseum + spec: + containers: + - name: chartmuseum + image: chartmuseum/chartmuseum:v0.11.0 + imagePullPolicy: IfNotPresent + env: + - name: "LOG_JSON" + value: "true" + - name: "STORAGE" + value: "local" + args: + - --port=8080 + - --storage-local-rootdir=/storage + livenessProbe: + httpGet: + path: /health + port: http + failureThreshold: 3 + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + readinessProbe: + httpGet: + path: /health + port: http + failureThreshold: 3 + initialDelaySeconds: 5 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + ports: + - containerPort: 8080 + name: http + protocol: TCP + volumeMounts: + - mountPath: /storage + name: storage-volume + securityContext: + fsGroup: 1000 + volumes: + - name: storage-volume + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + labels: + name: chartmuseum + name: chartmuseum +spec: + ports: + - name: http + port: 8080 + protocol: TCP + targetPort: http + selector: + name: chartmuseum + type: ClusterIP diff --git a/test/e2e/fixtures/kustom/base/chartmuseum/kustomization.yaml b/test/e2e/fixtures/kustom/base/chartmuseum/kustomization.yaml new file mode 100644 index 000000000..bd8144ead --- /dev/null +++ b/test/e2e/fixtures/kustom/base/chartmuseum/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - chartmuseum.yaml diff --git a/test/e2e/fixtures/releases/nested-helmrelease.yaml b/test/e2e/fixtures/releases/nested-helmrelease.yaml new file mode 100644 index 000000000..03efbe10c --- /dev/null +++ b/test/e2e/fixtures/releases/nested-helmrelease.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: helm.fluxcd.io/v1 +kind: HelmRelease +metadata: + name: nested-helmrelease + namespace: demo +spec: + releaseName: nested-helmrelease + chart: + repository: http://chartmuseum:8080 + name: nested-helmrelease + version: 0.1.0 + values: + nested: + deeper: + deepest: + image: + name: some-other-image + tag: 1.0.1 diff --git a/test/e2e/lib/helm.bash b/test/e2e/lib/helm.bash new file mode 100644 index 000000000..0bea7b11b --- /dev/null +++ b/test/e2e/lib/helm.bash @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# shellcheck disable=SC1090 +source "${E2E_DIR}/lib/defer.bash" + +function package_and_upload_chart() { + local chart=${1} + local chart_repository=${2} + + gen_dir=$(mktemp -d) + defer rm -rf "'$gen_dir'" + + # Package + if [ "$HELM_VERSION" == "v3" ]; then + helm3 package --destination "$gen_dir" "$chart" + else + helm2 package --destination "$gen_dir" "$chart" + fi + + # Upload + chart_tarbal=$(find "$gen_dir" -type f -name "*.tgz" | head -n1) + curl --data-binary "@$chart_tarbal" "$chart_repository/api/charts" +} diff --git a/test/e2e/lib/install.bash b/test/e2e/lib/install.bash index fa5e5af8a..744dec0b9 100644 --- a/test/e2e/lib/install.bash +++ b/test/e2e/lib/install.bash @@ -57,9 +57,8 @@ function install_git_srv() { local kustomization_dir=${2:-base/gitsrv} local gen_dir gen_dir=$(mktemp -d) - - ssh-keygen -t rsa -N "" -f "$gen_dir/id_rsa" defer rm -rf "'$gen_dir'" + ssh-keygen -t rsa -N "" -f "$gen_dir/id_rsa" kubectl create secret generic flux-git-deploy \ --namespace="${E2E_NAMESPACE}" \ --from-file="${FIXTURES_DIR}/known_hosts" \ @@ -87,8 +86,37 @@ function install_git_srv() { } function uninstall_git_srv() { - local secret_name=${1:-flux-git-deploy} + local kustomization_dir=${1:-base/gitsrv} + # Silence secret deletion errors since the secret can be missing (deleted by uninstalling Flux) - kubectl delete -n "${E2E_NAMESPACE}" secret "$secret_name" &> /dev/null - kubectl delete -n "${E2E_NAMESPACE}" -f "${FIXTURES_DIR}/gitsrv.yaml" + kubectl delete -n "${E2E_NAMESPACE}" secret flux-git-deploy &> /dev/null + kubectl delete -n "${E2E_NAMESPACE}" -k "${FIXTURES_DIR}/kustom/${kustomization_dir}" >&3 +} + +function install_chartmuseum() { + local external_access_result_var=${1} + local kustomization_dir=${2:-base/chartmuseum} + + kubectl apply -n "${E2E_NAMESPACE}" -k "${FIXTURES_DIR}/kustom/${kustomization_dir}" >&3 + + # Wait for the chartmuseum to become ready + kubectl -n "${E2E_NAMESPACE}" rollout status deployment/chartmuseum + + if [ -n "$external_access_result_var" ]; then + local chartmuseum_podname + chartmuseum_podname=$(kubectl get pod -n "${E2E_NAMESPACE}" -l name=chartmuseum -o jsonpath="{['items'][0].metadata.name}") + coproc kubectl port-forward -n "${E2E_NAMESPACE}" "$chartmuseum_podname" :8080 + local local_port + read -r local_port <&"${COPROC[0]}"- + # shellcheck disable=SC2001 + local_port=$(echo "$local_port" | sed 's%.*:\([0-9]*\).*%\1%') + # return the ssh command needed for git, and the PID of the port-forwarding PID into a variable of choice + eval "${external_access_result_var}=('$local_port' '$COPROC_PID')" + fi +} + +function uninstall_chartmuseum() { + local kustomization_dir=${1:-base/chartmuseum} + + kubectl delete -n "${E2E_NAMESPACE}" -k "${FIXTURES_DIR}/kustom/${kustomization_dir}" >&3 }