From 43c8b77c60f692535a1fa329d4a58218959050f6 Mon Sep 17 00:00:00 2001 From: iam-karan-suresh Date: Sat, 27 Jun 2026 19:34:30 +0530 Subject: [PATCH 1/3] ci: split e2e workflow into smaller parallel jobs Signed-off-by: iam-karan-suresh (cherry picked from commit a2cd974555818c97d4ab8a75eb55fa4245722b87) --- .github/workflows/e2e.yaml | 2577 ++++++++++++++++++------------------ 1 file changed, 1293 insertions(+), 1284 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index e801ee142..76923bf6c 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -7,19 +7,33 @@ on: - "main" - "release/**" jobs: - kind: + test: runs-on: ubuntu-latest permissions: - contents: read # for reading the repository code. + contents: read steps: - - name: Test suite setup - uses: fluxcd/gha-workflows/.github/actions/setup-kubernetes@526a0ab488375330ba526c26584ae22ee270d5b6 # v0.11.0 + - name: Checkout + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - name: Setup Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version: 1.26.x + cache-dependency-path: | + **/go.sum + **/go.mod - name: Run tests run: make test - name: Check if working tree is dirty run: make verify + e2e: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Test suite setup + uses: fluxcd/gha-workflows/.github/actions/setup-kubernetes@526a0ab488375330ba526c26584ae22ee270d5b6 # v0.11.0 + with: + go-version: 1.26.x - name: Build container image run: | make docker-build IMG=test/helm-controller:latest \ @@ -45,1294 +59,1289 @@ jobs: kubectl -n helm-system rollout status deploy/helm-controller --timeout=1m env: KUBEBUILDER_ASSETS: ${{ github.workspace }}/kubebuilder/bin - - name: Test samples - run: | - kubectl create ns samples - kubectl -n samples apply -f config/samples - kubectl -n samples wait hr/podinfo-ocirepository --for=condition=ready --timeout=4m - kubectl -n samples wait hr/podinfo-gitrepository --for=condition=ready --timeout=4m - kubectl -n samples wait hr/podinfo-helmrepository --for=condition=ready --timeout=4m - kubectl delete ns samples - name: Install sources run: | kubectl -n helm-system apply -f config/testdata/sources - - name: Run smoke test - run: | - kubectl -n helm-system apply -f config/testdata/podinfo - kubectl -n helm-system wait helmreleases/podinfo --for=condition=ready --timeout=4m - - # Inventory tracking enables drift detection and garbage collection. - # Ensure it captures managed objects from the Helm release. - INVENTORY=$(kubectl -n helm-system get helmrelease/podinfo -o jsonpath='{.status.inventory.entries}') - INVENTORY_COUNT=$(echo "$INVENTORY" | jq 'length') - if [ "$INVENTORY_COUNT" -lt 1 ]; then - echo "Expected inventory entries, got $INVENTORY_COUNT" - exit 1 - fi - # Deployment is a primary workload resource; its presence confirms - # that the inventory correctly tracks resources from the rendered manifests. - if ! echo "$INVENTORY" | jq -e '.[] | select(.id | contains("_Deployment"))' > /dev/null; then - echo "Expected Deployment in inventory" - echo "Inventory: $INVENTORY" - exit 1 - fi - - kubectl -n helm-system wait helmreleases/podinfo-git --for=condition=ready --timeout=4m - kubectl -n helm-system wait helmreleases/podinfo-oci --for=condition=ready --timeout=4m - kubectl -n helm-system delete -f config/testdata/podinfo - - name: Run Job with TTL test - run: | - # This test verifies that the wait logic correctly handles Jobs with - # ttlSecondsAfterFinished that get garbage-collected after completion. - # Without the fix, the wait would fail with NotFound error. - kubectl -n helm-system apply -f config/testdata/job-ttl - kubectl -n helm-system wait helmreleases/job-ttl --for=condition=ready --timeout=4m - kubectl -n helm-system delete -f config/testdata/job-ttl - - name: Run client-side apply upgrade test - run: | - set -euo pipefail - - test_name=no-server-side-apply - deploy_name=$test_name-podinfo - - kubectl -n helm-system apply -f config/testdata/server-side-apply/no-ssa.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Capture managed fields before upgrade. - echo ">>> Checking managed fields after install" - MANAGED_FIELDS_BEFORE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - # Ensure we got managed fields data. - FIELD_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_BEFORE" | jq 'length') - if [ "$FIELD_COUNT_BEFORE" -lt 1 ]; then - echo "ERROR: No managed fields found on deployment" - exit 1 - fi - echo "Found $FIELD_COUNT_BEFORE managed field entries" - - # Show all managers and their operations. - echo "Managers before upgrade:" - echo "$MANAGED_FIELDS_BEFORE" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Verify helm-controller used Update (not Apply) after install. - HELM_UPDATE_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_BEFORE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - HELM_APPLY_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_BEFORE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - echo "helm-controller operations: Update=$HELM_UPDATE_COUNT_BEFORE, Apply=$HELM_APPLY_COUNT_BEFORE" - - if [ "$HELM_UPDATE_COUNT_BEFORE" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Update operation" - exit 1 - fi - if [ "$HELM_APPLY_COUNT_BEFORE" != "0" ]; then - echo "ERROR: Unexpected Apply operation from helm-controller" - exit 1 - fi - echo "PASS: Install used CSA (Update operation)" - - # Trigger upgrade by changing values. - echo ">>> Triggering upgrade via patch (expecting CSA)" - TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":{"upgrade-timestamp":"'$TIMESTAMP'"}}}}' - - # Wait for the upgrade to complete (revision count should be 2). - echo -n ">>> Waiting for upgrade" - count=0 - until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Verify the pod annotation was applied. - POD_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.upgrade-timestamp}') - echo "Pod annotation upgrade-timestamp: $POD_ANNOTATION" - if [ "$POD_ANNOTATION" != "$TIMESTAMP" ]; then - echo "ERROR: Pod annotation not updated, upgrade may not have occurred" - exit 1 - fi - - # Capture managed fields after upgrade. - echo ">>> Checking managed fields after upgrade" - MANAGED_FIELDS_AFTER=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - # Show all managers and their operations. - echo "Managers after upgrade:" - echo "$MANAGED_FIELDS_AFTER" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Verify helm-controller used Update (not Apply) after upgrade. - HELM_UPDATE_COUNT_AFTER=$(echo "$MANAGED_FIELDS_AFTER" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - HELM_APPLY_COUNT_AFTER=$(echo "$MANAGED_FIELDS_AFTER" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - echo "helm-controller operations: Update=$HELM_UPDATE_COUNT_AFTER, Apply=$HELM_APPLY_COUNT_AFTER" - - if [ "$HELM_UPDATE_COUNT_AFTER" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Update operation" - exit 1 - fi - if [ "$HELM_APPLY_COUNT_AFTER" != "0" ]; then - echo "ERROR: Unexpected Apply operation from helm-controller" - exit 1 - fi - echo "PASS: Upgrade used CSA (Update operation)" - - kubectl -n helm-system delete -f config/testdata/server-side-apply/no-ssa.yaml - - name: Run SSA install with CSA upgrade test - run: | - set -euo pipefail - - test_name=ssa-install-no-ssa-upgrade - deploy_name=$test_name-podinfo - - kubectl -n helm-system apply -f config/testdata/server-side-apply/$test_name.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Capture managed fields after install. - echo ">>> Checking managed fields after install (expecting SSA)" - MANAGED_FIELDS_INSTALL=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - FIELD_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq 'length') - if [ "$FIELD_COUNT" -lt 1 ]; then - echo "ERROR: No managed fields found on deployment" - exit 1 - fi - echo "Found $FIELD_COUNT managed field entries" - - echo "Managers after install:" - echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Show fields managed by helm-controller. - echo "Fields managed by helm-controller:" - echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' - - # Verify helm-controller used Apply (SSA) after install. - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" - - if [ "$HELM_APPLY_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Apply operation for SSA install" - exit 1 - fi - echo "PASS: Install used SSA (Apply operation)" - - # Trigger upgrade by changing values. - echo ">>> Triggering upgrade via patch (expecting CSA)" - TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":{"upgrade-timestamp":"'$TIMESTAMP'"}}}}' - - # Wait for the upgrade to complete. - echo -n ">>> Waiting for upgrade" - count=0 - until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - echo "DEBUG: Helm history:" - helm -n helm-system history $test_name || true - echo "DEBUG: HelmRelease status:" - kubectl -n helm-system get helmrelease/$test_name -o jsonpath='{.status}' | jq . || true - exit 1 - fi - done - echo ' done' - - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Verify the pod annotation was applied. - POD_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.upgrade-timestamp}') - echo "Pod annotation upgrade-timestamp: $POD_ANNOTATION" - if [ "$POD_ANNOTATION" != "$TIMESTAMP" ]; then - echo "ERROR: Pod annotation not updated, upgrade may not have occurred" - exit 1 - fi - - # Capture managed fields after upgrade. - echo ">>> Checking managed fields after upgrade (expecting CSA)" - MANAGED_FIELDS_UPGRADE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - echo "Managers after upgrade:" - echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Show fields managed by each helm-controller entry. - echo "Fields managed by helm-controller:" - echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | select(.manager | test("helm")) | if .operation == "Apply" then "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields" else "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0) | map(tostring) | join(".")] | sort | join(", "))" end' - - # Verify helm-controller used Update (CSA) after upgrade. - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" - - if [ "$HELM_UPDATE_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Update operation for CSA upgrade" - exit 1 - fi - echo "PASS: Upgrade used CSA (Update operation)" - - kubectl -n helm-system delete -f config/testdata/server-side-apply/$test_name.yaml - - name: Run CSA install with SSA upgrade test - run: | - set -euo pipefail - - test_name=no-ssa-install-ssa-upgrade - deploy_name=$test_name-podinfo - - kubectl -n helm-system apply -f config/testdata/server-side-apply/$test_name.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Capture managed fields after install. - echo ">>> Checking managed fields after install (expecting CSA)" - MANAGED_FIELDS_INSTALL=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - FIELD_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq 'length') - if [ "$FIELD_COUNT" -lt 1 ]; then - echo "ERROR: No managed fields found on deployment" - exit 1 - fi - echo "Found $FIELD_COUNT managed field entries" - - echo "Managers after install:" - echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Show fields managed by helm-controller. - echo "Fields managed by helm-controller:" - echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' - - # Verify helm-controller used Update (CSA) after install. - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" - - if [ "$HELM_UPDATE_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Update operation for CSA install" - exit 1 - fi - if [ "$HELM_APPLY_COUNT" != "0" ]; then - echo "ERROR: Unexpected Apply operation from helm-controller" - exit 1 - fi - echo "PASS: Install used CSA (Update operation)" - - # Trigger upgrade by changing values. - echo ">>> Triggering upgrade via patch (expecting SSA)" - TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":{"upgrade-timestamp":"'$TIMESTAMP'"}}}}' - - # Wait for the upgrade to complete. - echo -n ">>> Waiting for upgrade" - count=0 - until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Verify the pod annotation was applied. - POD_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.upgrade-timestamp}') - echo "Pod annotation upgrade-timestamp: $POD_ANNOTATION" - if [ "$POD_ANNOTATION" != "$TIMESTAMP" ]; then - echo "ERROR: Pod annotation not updated, upgrade may not have occurred" - exit 1 - fi - - # Capture managed fields after upgrade. - echo ">>> Checking managed fields after upgrade (expecting SSA)" - MANAGED_FIELDS_UPGRADE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - echo "Managers after upgrade:" - echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Show fields managed by helm-controller. - echo "Fields managed by helm-controller:" - echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | select(.manager | test("helm")) | if .operation == "Apply" then "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields" else "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0) | map(tostring) | join(".")] | sort | join(", "))" end' - - # Verify helm-controller used Apply (SSA) after upgrade. - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" - - if [ "$HELM_APPLY_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Apply operation for SSA upgrade" - exit 1 - fi - echo "PASS: Upgrade used SSA (Apply operation)" - - kubectl -n helm-system delete -f config/testdata/server-side-apply/$test_name.yaml - - name: Run SSA to CSA field removal test - run: | - set -euo pipefail - - # This test verifies the behavior when switching from SSA to CSA and - # removing a field from values. The field WILL be removed because - # Helm tracks field ownership and removes fields that are no longer - # in the rendered manifests, regardless of the apply method. - - test_name=ssa-to-csa-field-removal - deploy_name=$test_name-podinfo - - kubectl -n helm-system apply -f config/testdata/server-side-apply/$test_name.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Verify install used SSA. - echo ">>> Checking managed fields after install (expecting SSA)" - MANAGED_FIELDS_INSTALL=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - FIELD_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq 'length') - if [ "$FIELD_COUNT" -lt 1 ]; then - echo "ERROR: No managed fields found on deployment" - exit 1 - fi - echo "Found $FIELD_COUNT managed field entries" - - echo "Managers after install:" - echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Show fields managed by helm-controller. - echo "Fields managed by helm-controller:" - HELM_FIELD_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | [.fieldsV1 | paths(type == "object" and length == 0)] | length] | add') - echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' - - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" - - if [ "$HELM_APPLY_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Apply operation for SSA install" - exit 1 - fi - echo "PASS: Install used SSA (Apply operation)" - - # Verify the SSA-owned annotation is present after install. - echo ">>> Verifying SSA-owned annotation after install" - SSA_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.ssa-owned-field}') - echo "Pod annotation ssa-owned-field: $SSA_ANNOTATION" - if [ "$SSA_ANNOTATION" != "this-should-persist-after-csa-upgrade" ]; then - echo "ERROR: Expected ssa-owned-field annotation to be present after install" - exit 1 - fi - echo "PASS: SSA-owned annotation present after install" - - # Trigger upgrade by REMOVING the podAnnotations entirely. - # This simulates someone removing a field from their HelmRelease values. - echo ">>> Triggering upgrade by removing podAnnotations from values" - kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":null}}}' - - # Wait for the upgrade to complete. - echo -n ">>> Waiting for upgrade" - count=0 - until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - echo "DEBUG: Helm history:" - helm -n helm-system history $test_name || true - echo "DEBUG: HelmRelease status:" - kubectl -n helm-system get helmrelease/$test_name -o jsonpath='{.status}' | jq . || true - exit 1 - fi - done - echo ' done' - - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Check managed fields after upgrade. - echo ">>> Checking managed fields after upgrade" - MANAGED_FIELDS_UPGRADE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - echo "Managers after upgrade:" - echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Show fields managed by helm-controller. - echo "Fields managed by helm-controller:" - HELM_FIELD_COUNT_AFTER=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | [.fieldsV1 | paths(type == "object" and length == 0)] | length] | add') - echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' - - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" - - # Verify field count decreased (annotation was removed). - echo ">>> Verifying field count decreased" - echo "Field count before: $HELM_FIELD_COUNT_BEFORE" - echo "Field count after: $HELM_FIELD_COUNT_AFTER" - if [ "$HELM_FIELD_COUNT_AFTER" -ge "$HELM_FIELD_COUNT_BEFORE" ]; then - echo "ERROR: Expected field count to decrease after removing podAnnotations" - exit 1 - fi - echo "PASS: Field count decreased from $HELM_FIELD_COUNT_BEFORE to $HELM_FIELD_COUNT_AFTER" - - # THIS IS THE KEY ASSERTION: The field should be REMOVED because Helm - # tracks field ownership and removes fields no longer in the manifests. - echo ">>> Verifying SSA-owned field was removed after upgrade" - SSA_ANNOTATION_AFTER=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.ssa-owned-field}') - echo "Pod annotation ssa-owned-field after upgrade: '$SSA_ANNOTATION_AFTER'" - - if [ -n "$SSA_ANNOTATION_AFTER" ]; then - echo "FAIL: SSA-owned field was NOT removed after upgrade" - echo "Expected the field to be removed when podAnnotations is removed from values." - exit 1 - fi - - echo "PASS: SSA-owned field was removed after upgrade" - echo "" - echo "This confirms that removing a field from HelmRelease values WILL" - echo "remove it from the actual object, even when switching from SSA to CSA." - echo "Helm properly tracks field ownership and cleans up removed fields." - - kubectl -n helm-system delete -f config/testdata/server-side-apply/$test_name.yaml - - name: Run server-side apply test - run: | - set -euo pipefail - - test_name=server-side-apply - deploy_name=$test_name-podinfo - - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Verify the release is deployed with SSA via Helm secret. - echo ">>> Checking Helm release secret after install" - APPLY_METHOD=$(kubectl -n helm-system get secret sh.helm.release.v1.$test_name.v1 -o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | jq -r '.apply_method') - echo "Helm release apply_method: $APPLY_METHOD" - if [ "$APPLY_METHOD" != "ssa" ]; then - echo "ERROR: Unexpected apply method: $APPLY_METHOD (expected: ssa)" - exit 1 - fi - - # Verify SSA via managed fields on deployment. - echo ">>> Checking managed fields after install" - MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - echo "Managers after install:" - echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' - - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - echo "helm-controller Apply operations: $HELM_APPLY_COUNT" - if [ "$HELM_APPLY_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Apply operation for SSA" - exit 1 - fi - echo "PASS: Install used SSA (Apply operation)" - - # Upgrade with SSA. - echo ">>> Applying upgrade manifest" - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Validate release was upgraded. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - echo "Helm revision count: $REVISION_COUNT" - if [ "$REVISION_COUNT" != 2 ]; then - echo "ERROR: Unexpected revision count: $REVISION_COUNT (expected: 2)" - exit 1 - fi - - # Verify SSA via managed fields after upgrade. - echo ">>> Checking managed fields after upgrade" - MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - echo "Managers after upgrade:" - echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' - - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - echo "helm-controller Apply operations: $HELM_APPLY_COUNT" - if [ "$HELM_APPLY_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Apply operation for SSA" - exit 1 - fi - echo "PASS: Upgrade used SSA (Apply operation)" - - kubectl -n helm-system delete -f config/testdata/$test_name/install.yaml - - name: Run server-side apply rollback test - run: | - set -euo pipefail - - test_name=server-side-apply-rollback - deploy_name=$test_name-podinfo - - kubectl -n helm-system apply -f config/testdata/server-side-apply/rollback-install.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Verify the release is deployed with SSA via Helm secret. - echo ">>> Checking Helm release secret after install" - APPLY_METHOD=$(kubectl -n helm-system get secret sh.helm.release.v1.$test_name.v1 -o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | jq -r '.apply_method') - echo "Helm release apply_method: $APPLY_METHOD" - if [ "$APPLY_METHOD" != "ssa" ]; then - echo "ERROR: Unexpected apply method: $APPLY_METHOD (expected: ssa)" - exit 1 - fi - - # Verify SSA via managed fields on deployment. - echo ">>> Checking managed fields after install" - MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - echo "Managers after install:" - echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' - - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - echo "helm-controller Apply operations: $HELM_APPLY_COUNT" - if [ "$HELM_APPLY_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Apply operation for SSA" - exit 1 - fi - echo "PASS: Install used SSA (Apply operation)" - - # Upgrade with failing config to trigger rollback. - echo ">>> Applying failing upgrade to trigger rollback" - kubectl -n helm-system apply -f config/testdata/server-side-apply/rollback-upgrade.yaml - echo -n ">>> Waiting for rollback" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Remediated=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate rollback happened (revision 3 = rollback to 1). - echo ">>> Checking Helm history after rollback" - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - echo "Helm revision count: $REVISION_COUNT" - if [ "$REVISION_COUNT" != 3 ]; then - echo "ERROR: Unexpected revision count: $REVISION_COUNT (expected: 3)" - exit 1 - fi - LAST_REVISION_DESCRIPTION=$(echo "$HISTORY" | jq -r 'last | .description') - echo "Last revision description: $LAST_REVISION_DESCRIPTION" - if [ "$LAST_REVISION_DESCRIPTION" != "Rollback to 1" ]; then - echo "ERROR: Unexpected last revision description: $LAST_REVISION_DESCRIPTION (expected: Rollback to 1)" - exit 1 - fi - echo "PASS: Rollback occurred (revision 3 = Rollback to 1)" - - # Verify the rollback release used SSA via Helm secret. - echo ">>> Checking Helm release secret after rollback" - APPLY_METHOD=$(kubectl -n helm-system get secret sh.helm.release.v1.$test_name.v3 -o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | jq -r '.apply_method') - echo "Helm release apply_method: $APPLY_METHOD" - if [ "$APPLY_METHOD" != "ssa" ]; then - echo "ERROR: Unexpected apply method after rollback: $APPLY_METHOD (expected: ssa)" - exit 1 - fi - - # Verify SSA via managed fields after rollback. - echo ">>> Checking managed fields after rollback" - MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - echo "Managers after rollback:" - echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' - - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - echo "helm-controller Apply operations: $HELM_APPLY_COUNT" - if [ "$HELM_APPLY_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Apply operation for SSA" - exit 1 - fi - echo "PASS: Rollback used SSA (Apply operation)" - - kubectl -n helm-system delete -f config/testdata/server-side-apply/rollback-install.yaml - - name: Run dependency tests - run: | - kubectl -n helm-system apply -f config/testdata/dependencies - kubectl -n helm-system wait helmreleases/backend --for=condition=ready --timeout=4m - kubectl -n helm-system wait helmreleases/frontend --for=condition=ready --timeout=4m - kubectl -n helm-system delete -f config/testdata/dependencies - - name: Run values test - run: | - kubectl -n helm-system apply -f config/testdata/valuesfrom - kubectl -n helm-system wait helmreleases/valuesfrom --for=condition=ready --timeout=4m - - RESULT=$(helm -n helm-system get values valuesfrom) - EXPECTED=$(cat config/testdata/valuesfrom/golden.txt) - if [ "$RESULT" != "$EXPECTED" ]; then - echo -e "$RESULT\n\ndoes not equal\n\n$EXPECTED" - exit 1 - fi - - kubectl -n helm-system delete -f config/testdata/valuesfrom - - name: Run target namespace test - run: | - kubectl -n helm-system apply -f config/testdata/targetnamespace - kubectl -n helm-system wait helmreleases/targetnamespace --for=condition=ready --timeout=4m - - # Confirm release in "default" namespace - kubectl -n default get deployment default-targetnamespace-podinfo - - kubectl -n helm-system delete -f config/testdata/targetnamespace - - name: Run install create target namespace test - run: | - kubectl -n helm-system apply -f config/testdata/install-create-target-ns - kubectl -n helm-system wait helmreleases/install-create-target-ns --for=condition=ready --timeout=4m - - # Confirm release in "install-create-target-ns" namespace - kubectl -n install-create-target-ns get deployment install-create-target-ns-install-create-target-ns-podinfo - - kubectl -n helm-system delete -f config/testdata/install-create-target-ns - - name: Run install from helmChart test - run: | - kubectl -n helm-system apply -f config/testdata/install-from-hc-source - kubectl -n helm-system wait helmreleases/podinfo-from-hc --for=condition=ready --timeout=4m - kubectl -n helm-system delete -f config/testdata/install-from-hc-source - - name: Run install from ocirepo test - run: | - kubectl -n helm-system apply -f config/testdata/install-from-ocirepo-source - kubectl -n helm-system wait helmreleases/podinfo-from-ocirepo --for=condition=ready --timeout=4m - kubectl -n helm-system delete -f config/testdata/install-from-ocirepo-source - - name: Run install fail test - run: | - test_name=install-fail - kubectl -n helm-system apply -f config/testdata/$test_name - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed and not uninstalled. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system delete -f config/testdata/$test_name - - name: Run install test fail test - run: | - test_name=install-test-fail - kubectl -n helm-system apply -f config/testdata/$test_name - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .TestSuccess=="False" and .Ready=="False"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed and not uninstalled. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system delete -f config/testdata/$test_name - - name: Run install test fail ignore test - run: | - test_name=install-test-fail-ignore - kubectl -n helm-system apply -f config/testdata/$test_name - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .TestSuccess=="False" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed and not uninstalled. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system delete -f config/testdata/$test_name - - name: Run install fail with remediation test - run: | - test_name=install-fail-remediate - kubectl -n helm-system apply -f config/testdata/$test_name - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Remediated=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Ensure release was uninstalled. - RELEASE_STATUS=$(helm -n helm-system history $test_name -o json | jq -r 'if length == 1 then .[0].status else empty end') - if [ "$RELEASE_STATUS" != "uninstalled" ]; then - echo -e "Unexpected release status: $RELEASE_STATUS" - exit 1 - fi - - kubectl -n helm-system delete -f config/testdata/$test_name - helm -n helm-system delete $test_name - - name: Run install fail with retry test - run: | - test_name=install-fail-retry - kubectl -n helm-system apply -f config/testdata/$test_name - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.installFailures == 2 and ( .status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Stalled=="True" )' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system delete -f config/testdata/$test_name - - name: Run upgrade fail test - run: | - test_name=upgrade-fail - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Stalled=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was upgraded and not rolled back. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 2 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - - name: Run upgrade test fail test - run: | - test_name=upgrade-test-fail - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .TestSuccess=="False" and .Ready=="False" and .Stalled=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was upgraded and not rolled back. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 2 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - - name: Run upgrade fail with remediation test - run: | - test_name=upgrade-fail-remediate - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Remediated=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was upgraded and then rolled back. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 3 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - fi - LAST_REVISION_DESCRIPTION=$(echo "$HISTORY" | jq -r 'last | .description') - if [ "$LAST_REVISION_DESCRIPTION" != "Rollback to 1" ]; then - echo -e "Unexpected last revision description: $LAST_REVISION_DESCRIPTION" - exit 1 - fi - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - - name: Run upgrade fail retry test - run: | - test_name=upgrade-fail-retry - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.upgradeFailures == 2 and ( .status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" )' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was upgraded and rolled back twice. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 5 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - fi - LAST_REVISION_DESCRIPTION=$(echo "$HISTORY" | jq -r 'last | .description') - if [ "$LAST_REVISION_DESCRIPTION" != "Rollback to 3" ]; then - echo -e "Unexpected last revision description: $LAST_REVISION_DESCRIPTION" - exit 1 - fi - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - - name: Run upgrade from ocirepo source - run: | - test_name=upgrade-from-ocirepo-source - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' + - parallel: + - name: Run smoke tests + run: | + set -euo pipefail + echo ">>> Test samples" + kubectl create ns samples + kubectl -n samples apply -f config/samples + kubectl -n samples wait hr/podinfo-ocirepository --for=condition=ready --timeout=4m + kubectl -n samples wait hr/podinfo-gitrepository --for=condition=ready --timeout=4m + kubectl -n samples wait hr/podinfo-helmrepository --for=condition=ready --timeout=4m + kubectl delete ns samples + echo ">>> Run smoke test" + kubectl -n helm-system apply -f config/testdata/podinfo + kubectl -n helm-system wait helmreleases/podinfo --for=condition=ready --timeout=4m + + # Inventory tracking enables drift detection and garbage collection. + # Ensure it captures managed objects from the Helm release. + INVENTORY=$(kubectl -n helm-system get helmrelease/podinfo -o jsonpath='{.status.inventory.entries}') + INVENTORY_COUNT=$(echo "$INVENTORY" | jq 'length') + if [ "$INVENTORY_COUNT" -lt 1 ]; then + echo "Expected inventory entries, got $INVENTORY_COUNT" exit 1 fi - done - echo ' done' - - # Validate release was installed. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' + # Deployment is a primary workload resource; its presence confirms + # that the inventory correctly tracks resources from the rendered manifests. + if ! echo "$INVENTORY" | jq -e '.[] | select(.id | contains("_Deployment"))' > /dev/null; then + echo "Expected Deployment in inventory" + echo "Inventory: $INVENTORY" exit 1 fi - done - echo ' done' - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - - name: Run upgrade fail with uninstall remediation strategy test - run: | - test_name=upgrade-fail-remediate-uninstall - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') - if [ "$LAST_REVISION_STATUS" != "deployed" ]; then - echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.upgradeFailures == 1 and .status.installFailures == 1 and ( .status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" )' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was uninstalled/reinstalled. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') - if [ "$LAST_REVISION_STATUS" != "failed" ]; then - echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" - exit 1 - fi - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - - name: Run impersonation tests - run: | - kubectl apply -f config/testdata/impersonation - kubectl -n impersonation wait helmreleases/podinfo --for=condition=ready --timeout=2m - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n impersonation get helmrelease/podinfo-fail -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - name: Run delete-ns tests - run: | - kubectl apply -f config/testdata/delete-ns - kubectl -n delete-ns wait helmreleases/podinfo --for=condition=ready --timeout=2m - kubectl delete ns delete-ns 1>/dev/null 2>&1 & - echo -n ">>> Waiting for namespace to be deleted" - if kubectl wait --for=delete namespace delete-ns --timeout=5m; then - echo ' Namespace deleted successfully' - else - echo ' Timed out waiting for namespace to be deleted' - kubectl get all -n delete-ns - exit 1 - fi - - name: Run post-renderer-kustomize test - run: | - kubectl -n helm-system apply -f config/testdata/post-renderer-kustomize - kubectl -n helm-system wait helmreleases/post-renderer-kustomize --for=condition=ready --timeout=4m - RESULT=$(kubectl get deployment -n helm-system mypodinfo -o jsonpath='{.metadata.labels.xxxx}') - if [ "$RESULT" != "yyyy" ]; then - echo -e "$RESULT\n\ndoes not equal\n\nyyyy" - exit 1 - fi - RESULT=$(kubectl get deployment -n helm-system mypodinfo -o jsonpath='{.metadata.labels.yyyy}') - if [ "$RESULT" != "xxxx" ]; then - echo -e "$RESULT\n\ndoes not equal\n\nxxxx" - exit 1 - fi - kubectl -n helm-system delete -f config/testdata/post-renderer-kustomize - - name: Bootstrap Tests Using Local Helm Chart - if: ${{ startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) }} - run: | - if [ "${{ github.event_name }}" = "pull_request" ]; then - TYPE=branch - REF="${GITHUB_HEAD_REF}" - else - REF=${GITHUB_REF} - if echo "$REF" | grep 'refs/tags/'; then - TYPE=tag - REF=${REF#refs/tags/} - else - TYPE=branch - REF=${REF#refs/heads/} - fi - fi - echo "REF=$REF of type $TYPE" - echo "helm install --namespace default --set $TYPE=$REF --set url=https://github.com/${{ github.repository }} this config/testdata/charts/crds/bootstrap" - helm install --namespace default --set $TYPE=$REF --set url=https://github.com/${{ github.repository }} this config/testdata/charts/crds/bootstrap - kubectl -n default apply -f config/testdata/crds-upgrade/init - kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m - - name: CRDs Upgrade Test Create - if: ${{ startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) }} - run: | - kubectl -n default apply -f config/testdata/crds-upgrade/create - kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m - - name: CRDs Upgrade Test CreateReplace - if: ${{ startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) }} - run: | - kubectl -n default apply -f config/testdata/crds-upgrade/create-replace - kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m - - name: Run upgrade with chart name changed and default ChartNameChangeStrategy - if: ${{ startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) }} - run: | - test_name=upgrade-chart-name-change - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') - if [ "$LAST_REVISION_STATUS" != "deployed" ]; then - echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Validate release was uninstalled/reinstalled. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') - if [ "$LAST_REVISION_STATUS" != "deployed" ]; then - echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" - exit 1 - fi - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - - name: Run upgrade with chart name changed and reinstall ChartNameChangeStrategy - if: ${{ startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) }} - run: | - test_name=upgrade-chart-name-change - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') - if [ "$LAST_REVISION_STATUS" != "deployed" ]; then - echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade-reinstall.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Validate release was uninstalled/reinstalled. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') - if [ "$LAST_REVISION_STATUS" != "deployed" ]; then - echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" - exit 1 - fi - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - - name: Run upgrade with chart name changed and InPlaceUpdate ChartNameChangeStrategy - if: ${{ startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/') || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) }} - run: | - test_name=upgrade-chart-name-change - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') - if [ "$LAST_REVISION_STATUS" != "deployed" ]; then - echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade-inplace.yaml - - # Wait for the upgrade to complete (revision count should be 2). - echo -n ">>> Waiting for upgrade" - count=0 - until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + kubectl -n helm-system wait helmreleases/podinfo-git --for=condition=ready --timeout=4m + kubectl -n helm-system wait helmreleases/podinfo-oci --for=condition=ready --timeout=4m + kubectl -n helm-system delete -f config/testdata/podinfo + echo ">>> Run Job with TTL test" + # This test verifies that the wait logic correctly handles Jobs with + # ttlSecondsAfterFinished that get garbage-collected after completion. + # Without the fix, the wait would fail with NotFound error. + kubectl -n helm-system apply -f config/testdata/job-ttl + kubectl -n helm-system wait helmreleases/job-ttl --for=condition=ready --timeout=4m + kubectl -n helm-system delete -f config/testdata/job-ttl + echo ">>> Run dependency tests" + kubectl -n helm-system apply -f config/testdata/dependencies + kubectl -n helm-system wait helmreleases/backend --for=condition=ready --timeout=4m + kubectl -n helm-system wait helmreleases/frontend --for=condition=ready --timeout=4m + kubectl -n helm-system delete -f config/testdata/dependencies + echo ">>> Run values test" + kubectl -n helm-system apply -f config/testdata/valuesfrom + kubectl -n helm-system wait helmreleases/valuesfrom --for=condition=ready --timeout=4m + + RESULT=$(helm -n helm-system get values valuesfrom) + EXPECTED=$(cat config/testdata/valuesfrom/golden.txt) + if [ "$RESULT" != "$EXPECTED" ]; then + echo -e "$RESULT\n\ndoes not equal\n\n$EXPECTED" + exit 1 + fi + + kubectl -n helm-system delete -f config/testdata/valuesfrom + echo ">>> Run target namespace test" + kubectl -n helm-system apply -f config/testdata/targetnamespace + kubectl -n helm-system wait helmreleases/targetnamespace --for=condition=ready --timeout=4m + + # Confirm release in "default" namespace + kubectl -n default get deployment default-targetnamespace-podinfo + + kubectl -n helm-system delete -f config/testdata/targetnamespace + echo ">>> Run install create target namespace test" + kubectl -n helm-system apply -f config/testdata/install-create-target-ns + kubectl -n helm-system wait helmreleases/install-create-target-ns --for=condition=ready --timeout=4m + + # Confirm release in "install-create-target-ns" namespace + kubectl -n install-create-target-ns get deployment install-create-target-ns-install-create-target-ns-podinfo + + kubectl -n helm-system delete -f config/testdata/install-create-target-ns + echo ">>> Run install from helmChart test" + kubectl -n helm-system apply -f config/testdata/install-from-hc-source + kubectl -n helm-system wait helmreleases/podinfo-from-hc --for=condition=ready --timeout=4m + kubectl -n helm-system delete -f config/testdata/install-from-hc-source + echo ">>> Run install from ocirepo test" + kubectl -n helm-system apply -f config/testdata/install-from-ocirepo-source + kubectl -n helm-system wait helmreleases/podinfo-from-ocirepo --for=condition=ready --timeout=4m + kubectl -n helm-system delete -f config/testdata/install-from-ocirepo-source + echo ">>> Run post-renderer-kustomize test" + kubectl -n helm-system apply -f config/testdata/post-renderer-kustomize + kubectl -n helm-system wait helmreleases/post-renderer-kustomize --for=condition=ready --timeout=4m + RESULT=$(kubectl get deployment -n helm-system mypodinfo -o jsonpath='{.metadata.labels.xxxx}') + if [ "$RESULT" != "yyyy" ]; then + echo -e "$RESULT\n\ndoes not equal\n\nyyyy" + exit 1 + fi + RESULT=$(kubectl get deployment -n helm-system mypodinfo -o jsonpath='{.metadata.labels.yyyy}') + if [ "$RESULT" != "xxxx" ]; then + echo -e "$RESULT\n\ndoes not equal\n\nxxxx" + exit 1 + fi + kubectl -n helm-system delete -f config/testdata/post-renderer-kustomize + + - name: Run SSA tests + run: | + set -euo pipefail + echo ">>> Run client-side apply upgrade test" + set -euo pipefail + + test_name=no-server-side-apply + deploy_name=$test_name-podinfo + + kubectl -n helm-system apply -f config/testdata/server-side-apply/no-ssa.yaml + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + # Capture managed fields before upgrade. + echo ">>> Checking managed fields after install" + MANAGED_FIELDS_BEFORE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + + # Ensure we got managed fields data. + FIELD_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_BEFORE" | jq 'length') + if [ "$FIELD_COUNT_BEFORE" -lt 1 ]; then + echo "ERROR: No managed fields found on deployment" + exit 1 + fi + echo "Found $FIELD_COUNT_BEFORE managed field entries" + + # Show all managers and their operations. + echo "Managers before upgrade:" + echo "$MANAGED_FIELDS_BEFORE" | jq -r '.[] | " - \(.manager): \(.operation)"' + + # Verify helm-controller used Update (not Apply) after install. + HELM_UPDATE_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_BEFORE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') + HELM_APPLY_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_BEFORE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') + echo "helm-controller operations: Update=$HELM_UPDATE_COUNT_BEFORE, Apply=$HELM_APPLY_COUNT_BEFORE" + + if [ "$HELM_UPDATE_COUNT_BEFORE" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Update operation" + exit 1 + fi + if [ "$HELM_APPLY_COUNT_BEFORE" != "0" ]; then + echo "ERROR: Unexpected Apply operation from helm-controller" + exit 1 + fi + echo "PASS: Install used CSA (Update operation)" + + # Trigger upgrade by changing values. + echo ">>> Triggering upgrade via patch (expecting CSA)" + TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":{"upgrade-timestamp":"'$TIMESTAMP'"}}}}' + + # Wait for the upgrade to complete (revision count should be 2). + echo -n ">>> Waiting for upgrade" + count=0 + until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + # Verify the pod annotation was applied. + POD_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.upgrade-timestamp}') + echo "Pod annotation upgrade-timestamp: $POD_ANNOTATION" + if [ "$POD_ANNOTATION" != "$TIMESTAMP" ]; then + echo "ERROR: Pod annotation not updated, upgrade may not have occurred" + exit 1 + fi + + # Capture managed fields after upgrade. + echo ">>> Checking managed fields after upgrade" + MANAGED_FIELDS_AFTER=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + + # Show all managers and their operations. + echo "Managers after upgrade:" + echo "$MANAGED_FIELDS_AFTER" | jq -r '.[] | " - \(.manager): \(.operation)"' + + # Verify helm-controller used Update (not Apply) after upgrade. + HELM_UPDATE_COUNT_AFTER=$(echo "$MANAGED_FIELDS_AFTER" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') + HELM_APPLY_COUNT_AFTER=$(echo "$MANAGED_FIELDS_AFTER" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') + echo "helm-controller operations: Update=$HELM_UPDATE_COUNT_AFTER, Apply=$HELM_APPLY_COUNT_AFTER" + + if [ "$HELM_UPDATE_COUNT_AFTER" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Update operation" + exit 1 + fi + if [ "$HELM_APPLY_COUNT_AFTER" != "0" ]; then + echo "ERROR: Unexpected Apply operation from helm-controller" + exit 1 + fi + echo "PASS: Upgrade used CSA (Update operation)" + + kubectl -n helm-system delete -f config/testdata/server-side-apply/no-ssa.yaml + echo ">>> Run SSA install with CSA upgrade test" + set -euo pipefail + + test_name=ssa-install-no-ssa-upgrade + deploy_name=$test_name-podinfo + + kubectl -n helm-system apply -f config/testdata/server-side-apply/$test_name.yaml + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + # Capture managed fields after install. + echo ">>> Checking managed fields after install (expecting SSA)" + MANAGED_FIELDS_INSTALL=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + + FIELD_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq 'length') + if [ "$FIELD_COUNT" -lt 1 ]; then + echo "ERROR: No managed fields found on deployment" + exit 1 + fi + echo "Found $FIELD_COUNT managed field entries" + + echo "Managers after install:" + echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | " - \(.manager): \(.operation)"' + + # Show fields managed by helm-controller. + echo "Fields managed by helm-controller:" + echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' + + # Verify helm-controller used Apply (SSA) after install. + HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') + HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') + echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" + + if [ "$HELM_APPLY_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Apply operation for SSA install" + exit 1 + fi + echo "PASS: Install used SSA (Apply operation)" + + # Trigger upgrade by changing values. + echo ">>> Triggering upgrade via patch (expecting CSA)" + TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":{"upgrade-timestamp":"'$TIMESTAMP'"}}}}' + + # Wait for the upgrade to complete. + echo -n ">>> Waiting for upgrade" + count=0 + until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + echo "DEBUG: Helm history:" + helm -n helm-system history $test_name || true + echo "DEBUG: HelmRelease status:" + kubectl -n helm-system get helmrelease/$test_name -o jsonpath='{.status}' | jq . || true + exit 1 + fi + done + echo ' done' + + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + # Verify the pod annotation was applied. + POD_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.upgrade-timestamp}') + echo "Pod annotation upgrade-timestamp: $POD_ANNOTATION" + if [ "$POD_ANNOTATION" != "$TIMESTAMP" ]; then + echo "ERROR: Pod annotation not updated, upgrade may not have occurred" + exit 1 + fi + + # Capture managed fields after upgrade. + echo ">>> Checking managed fields after upgrade (expecting CSA)" + MANAGED_FIELDS_UPGRADE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + + echo "Managers after upgrade:" + echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | " - \(.manager): \(.operation)"' + + # Show fields managed by each helm-controller entry. + echo "Fields managed by helm-controller:" + echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | select(.manager | test("helm")) | if .operation == "Apply" then "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields" else "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0) | map(tostring) | join(".")] | sort | join(", "))" end' + + # Verify helm-controller used Update (CSA) after upgrade. + HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') + HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') + echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" + + if [ "$HELM_UPDATE_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Update operation for CSA upgrade" + exit 1 + fi + echo "PASS: Upgrade used CSA (Update operation)" + + kubectl -n helm-system delete -f config/testdata/server-side-apply/$test_name.yaml + echo ">>> Run CSA install with SSA upgrade test" + set -euo pipefail + + test_name=no-ssa-install-ssa-upgrade + deploy_name=$test_name-podinfo + + kubectl -n helm-system apply -f config/testdata/server-side-apply/$test_name.yaml + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + # Capture managed fields after install. + echo ">>> Checking managed fields after install (expecting CSA)" + MANAGED_FIELDS_INSTALL=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + + FIELD_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq 'length') + if [ "$FIELD_COUNT" -lt 1 ]; then + echo "ERROR: No managed fields found on deployment" + exit 1 + fi + echo "Found $FIELD_COUNT managed field entries" + + echo "Managers after install:" + echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | " - \(.manager): \(.operation)"' + + # Show fields managed by helm-controller. + echo "Fields managed by helm-controller:" + echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' + + # Verify helm-controller used Update (CSA) after install. + HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') + HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') + echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" + + if [ "$HELM_UPDATE_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Update operation for CSA install" + exit 1 + fi + if [ "$HELM_APPLY_COUNT" != "0" ]; then + echo "ERROR: Unexpected Apply operation from helm-controller" + exit 1 + fi + echo "PASS: Install used CSA (Update operation)" + + # Trigger upgrade by changing values. + echo ">>> Triggering upgrade via patch (expecting SSA)" + TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":{"upgrade-timestamp":"'$TIMESTAMP'"}}}}' + + # Wait for the upgrade to complete. + echo -n ">>> Waiting for upgrade" + count=0 + until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + # Verify the pod annotation was applied. + POD_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.upgrade-timestamp}') + echo "Pod annotation upgrade-timestamp: $POD_ANNOTATION" + if [ "$POD_ANNOTATION" != "$TIMESTAMP" ]; then + echo "ERROR: Pod annotation not updated, upgrade may not have occurred" + exit 1 + fi + + # Capture managed fields after upgrade. + echo ">>> Checking managed fields after upgrade (expecting SSA)" + MANAGED_FIELDS_UPGRADE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + + echo "Managers after upgrade:" + echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | " - \(.manager): \(.operation)"' + + # Show fields managed by helm-controller. + echo "Fields managed by helm-controller:" + echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | select(.manager | test("helm")) | if .operation == "Apply" then "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields" else "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0) | map(tostring) | join(".")] | sort | join(", "))" end' + + # Verify helm-controller used Apply (SSA) after upgrade. + HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') + HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') + echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" + + if [ "$HELM_APPLY_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Apply operation for SSA upgrade" + exit 1 + fi + echo "PASS: Upgrade used SSA (Apply operation)" + + kubectl -n helm-system delete -f config/testdata/server-side-apply/$test_name.yaml + echo ">>> Run SSA to CSA field removal test" + set -euo pipefail + + # This test verifies the behavior when switching from SSA to CSA and + # removing a field from values. The field WILL be removed because + # Helm tracks field ownership and removes fields that are no longer + # in the rendered manifests, regardless of the apply method. + + test_name=ssa-to-csa-field-removal + deploy_name=$test_name-podinfo + + kubectl -n helm-system apply -f config/testdata/server-side-apply/$test_name.yaml + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + # Verify install used SSA. + echo ">>> Checking managed fields after install (expecting SSA)" + MANAGED_FIELDS_INSTALL=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + + FIELD_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq 'length') + if [ "$FIELD_COUNT" -lt 1 ]; then + echo "ERROR: No managed fields found on deployment" + exit 1 + fi + echo "Found $FIELD_COUNT managed field entries" + + echo "Managers after install:" + echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | " - \(.manager): \(.operation)"' + + # Show fields managed by helm-controller. + echo "Fields managed by helm-controller:" + HELM_FIELD_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | [.fieldsV1 | paths(type == "object" and length == 0)] | length] | add') + echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' + + HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') + HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') + echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" + + if [ "$HELM_APPLY_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Apply operation for SSA install" + exit 1 + fi + echo "PASS: Install used SSA (Apply operation)" + + # Verify the SSA-owned annotation is present after install. + echo ">>> Verifying SSA-owned annotation after install" + SSA_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.ssa-owned-field}') + echo "Pod annotation ssa-owned-field: $SSA_ANNOTATION" + if [ "$SSA_ANNOTATION" != "this-should-persist-after-csa-upgrade" ]; then + echo "ERROR: Expected ssa-owned-field annotation to be present after install" + exit 1 + fi + echo "PASS: SSA-owned annotation present after install" + + # Trigger upgrade by REMOVING the podAnnotations entirely. + # This simulates someone removing a field from their HelmRelease values. + echo ">>> Triggering upgrade by removing podAnnotations from values" + kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":null}}}' + + # Wait for the upgrade to complete. + echo -n ">>> Waiting for upgrade" + count=0 + until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + echo "DEBUG: Helm history:" + helm -n helm-system history $test_name || true + echo "DEBUG: HelmRelease status:" + kubectl -n helm-system get helmrelease/$test_name -o jsonpath='{.status}' | jq . || true + exit 1 + fi + done + echo ' done' + + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + # Check managed fields after upgrade. + echo ">>> Checking managed fields after upgrade" + MANAGED_FIELDS_UPGRADE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + + echo "Managers after upgrade:" + echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | " - \(.manager): \(.operation)"' + + # Show fields managed by helm-controller. + echo "Fields managed by helm-controller:" + HELM_FIELD_COUNT_AFTER=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | [.fieldsV1 | paths(type == "object" and length == 0)] | length] | add') + echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' + + HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') + HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') + echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" + + # Verify field count decreased (annotation was removed). + echo ">>> Verifying field count decreased" + echo "Field count before: $HELM_FIELD_COUNT_BEFORE" + echo "Field count after: $HELM_FIELD_COUNT_AFTER" + if [ "$HELM_FIELD_COUNT_AFTER" -ge "$HELM_FIELD_COUNT_BEFORE" ]; then + echo "ERROR: Expected field count to decrease after removing podAnnotations" + exit 1 + fi + echo "PASS: Field count decreased from $HELM_FIELD_COUNT_BEFORE to $HELM_FIELD_COUNT_AFTER" + + # THIS IS THE KEY ASSERTION: The field should be REMOVED because Helm + # tracks field ownership and removes fields no longer in the manifests. + echo ">>> Verifying SSA-owned field was removed after upgrade" + SSA_ANNOTATION_AFTER=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.ssa-owned-field}') + echo "Pod annotation ssa-owned-field after upgrade: '$SSA_ANNOTATION_AFTER'" + + if [ -n "$SSA_ANNOTATION_AFTER" ]; then + echo "FAIL: SSA-owned field was NOT removed after upgrade" + echo "Expected the field to be removed when podAnnotations is removed from values." + exit 1 + fi + + echo "PASS: SSA-owned field was removed after upgrade" + echo "" + echo "This confirms that removing a field from HelmRelease values WILL" + echo "remove it from the actual object, even when switching from SSA to CSA." + echo "Helm properly tracks field ownership and cleans up removed fields." + + kubectl -n helm-system delete -f config/testdata/server-side-apply/$test_name.yaml + echo ">>> Run server-side apply test" + set -euo pipefail + + test_name=server-side-apply + deploy_name=$test_name-podinfo + + kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + # Verify the release is deployed with SSA via Helm secret. + echo ">>> Checking Helm release secret after install" + APPLY_METHOD=$(kubectl -n helm-system get secret sh.helm.release.v1.$test_name.v1 -o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | jq -r '.apply_method') + echo "Helm release apply_method: $APPLY_METHOD" + if [ "$APPLY_METHOD" != "ssa" ]; then + echo "ERROR: Unexpected apply method: $APPLY_METHOD (expected: ssa)" + exit 1 + fi + + # Verify SSA via managed fields on deployment. + echo ">>> Checking managed fields after install" + MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + echo "Managers after install:" + echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' + + HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') + echo "helm-controller Apply operations: $HELM_APPLY_COUNT" + if [ "$HELM_APPLY_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Apply operation for SSA" + exit 1 + fi + echo "PASS: Install used SSA (Apply operation)" + + # Upgrade with SSA. + echo ">>> Applying upgrade manifest" + kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + # Validate release was upgraded. + REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') + echo "Helm revision count: $REVISION_COUNT" + if [ "$REVISION_COUNT" != 2 ]; then + echo "ERROR: Unexpected revision count: $REVISION_COUNT (expected: 2)" + exit 1 + fi + + # Verify SSA via managed fields after upgrade. + echo ">>> Checking managed fields after upgrade" + MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + echo "Managers after upgrade:" + echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' + + HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') + echo "helm-controller Apply operations: $HELM_APPLY_COUNT" + if [ "$HELM_APPLY_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Apply operation for SSA" + exit 1 + fi + echo "PASS: Upgrade used SSA (Apply operation)" + + kubectl -n helm-system delete -f config/testdata/$test_name/install.yaml + echo ">>> Run server-side apply rollback test" + set -euo pipefail + + test_name=server-side-apply-rollback + deploy_name=$test_name-podinfo + + kubectl -n helm-system apply -f config/testdata/server-side-apply/rollback-install.yaml + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + # Verify the release is deployed with SSA via Helm secret. + echo ">>> Checking Helm release secret after install" + APPLY_METHOD=$(kubectl -n helm-system get secret sh.helm.release.v1.$test_name.v1 -o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | jq -r '.apply_method') + echo "Helm release apply_method: $APPLY_METHOD" + if [ "$APPLY_METHOD" != "ssa" ]; then + echo "ERROR: Unexpected apply method: $APPLY_METHOD (expected: ssa)" + exit 1 + fi + + # Verify SSA via managed fields on deployment. + echo ">>> Checking managed fields after install" + MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + echo "Managers after install:" + echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' + + HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') + echo "helm-controller Apply operations: $HELM_APPLY_COUNT" + if [ "$HELM_APPLY_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Apply operation for SSA" + exit 1 + fi + echo "PASS: Install used SSA (Apply operation)" + + # Upgrade with failing config to trigger rollback. + echo ">>> Applying failing upgrade to trigger rollback" + kubectl -n helm-system apply -f config/testdata/server-side-apply/rollback-upgrade.yaml + echo -n ">>> Waiting for rollback" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Remediated=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate rollback happened (revision 3 = rollback to 1). + echo ">>> Checking Helm history after rollback" + HISTORY=$(helm -n helm-system history -o json $test_name) + REVISION_COUNT=$(echo "$HISTORY" | jq 'length') + echo "Helm revision count: $REVISION_COUNT" + if [ "$REVISION_COUNT" != 3 ]; then + echo "ERROR: Unexpected revision count: $REVISION_COUNT (expected: 3)" + exit 1 + fi + LAST_REVISION_DESCRIPTION=$(echo "$HISTORY" | jq -r 'last | .description') + echo "Last revision description: $LAST_REVISION_DESCRIPTION" + if [ "$LAST_REVISION_DESCRIPTION" != "Rollback to 1" ]; then + echo "ERROR: Unexpected last revision description: $LAST_REVISION_DESCRIPTION (expected: Rollback to 1)" + exit 1 + fi + echo "PASS: Rollback occurred (revision 3 = Rollback to 1)" + + # Verify the rollback release used SSA via Helm secret. + echo ">>> Checking Helm release secret after rollback" + APPLY_METHOD=$(kubectl -n helm-system get secret sh.helm.release.v1.$test_name.v3 -o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | jq -r '.apply_method') + echo "Helm release apply_method: $APPLY_METHOD" + if [ "$APPLY_METHOD" != "ssa" ]; then + echo "ERROR: Unexpected apply method after rollback: $APPLY_METHOD (expected: ssa)" + exit 1 + fi + + # Verify SSA via managed fields after rollback. + echo ">>> Checking managed fields after rollback" + MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + echo "Managers after rollback:" + echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' + + HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') + echo "helm-controller Apply operations: $HELM_APPLY_COUNT" + if [ "$HELM_APPLY_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Apply operation for SSA" + exit 1 + fi + echo "PASS: Rollback used SSA (Apply operation)" + + kubectl -n helm-system delete -f config/testdata/server-side-apply/rollback-install.yaml + + - name: Run lifecycle tests + run: | + set -euo pipefail + echo ">>> Run install fail test" + test_name=install-fail + kubectl -n helm-system apply -f config/testdata/$test_name + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed and not uninstalled. + REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + + kubectl -n helm-system delete -f config/testdata/$test_name + echo ">>> Run install test fail test" + test_name=install-test-fail + kubectl -n helm-system apply -f config/testdata/$test_name + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .TestSuccess=="False" and .Ready=="False"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed and not uninstalled. + REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + + kubectl -n helm-system delete -f config/testdata/$test_name + echo ">>> Run install test fail ignore test" + test_name=install-test-fail-ignore + kubectl -n helm-system apply -f config/testdata/$test_name + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .TestSuccess=="False" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed and not uninstalled. + REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + + kubectl -n helm-system delete -f config/testdata/$test_name + echo ">>> Run install fail with remediation test" + test_name=install-fail-remediate + kubectl -n helm-system apply -f config/testdata/$test_name + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Remediated=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Ensure release was uninstalled. + RELEASE_STATUS=$(helm -n helm-system history $test_name -o json | jq -r 'if length == 1 then .[0].status else empty end') + if [ "$RELEASE_STATUS" != "uninstalled" ]; then + echo -e "Unexpected release status: $RELEASE_STATUS" + exit 1 + fi + + kubectl -n helm-system delete -f config/testdata/$test_name + helm -n helm-system delete $test_name + echo ">>> Run install fail with retry test" + test_name=install-fail-retry + kubectl -n helm-system apply -f config/testdata/$test_name + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.installFailures == 2 and ( .status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Stalled=="True" )' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed. + REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + + kubectl -n helm-system delete -f config/testdata/$test_name + echo ">>> Run upgrade fail test" + test_name=upgrade-fail + kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed. + REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + + kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Stalled=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was upgraded and not rolled back. + REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') + if [ "$REVISION_COUNT" != 2 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + + kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml + echo ">>> Run upgrade test fail test" + test_name=upgrade-test-fail + kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed. + REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + + kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .TestSuccess=="False" and .Ready=="False" and .Stalled=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was upgraded and not rolled back. + REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') + if [ "$REVISION_COUNT" != 2 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + + kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml + echo ">>> Run upgrade fail with remediation test" + test_name=upgrade-fail-remediate + kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed. + REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + + kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Remediated=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was upgraded and then rolled back. + HISTORY=$(helm -n helm-system history -o json $test_name) + REVISION_COUNT=$(echo "$HISTORY" | jq 'length') + if [ "$REVISION_COUNT" != 3 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + fi + LAST_REVISION_DESCRIPTION=$(echo "$HISTORY" | jq -r 'last | .description') + if [ "$LAST_REVISION_DESCRIPTION" != "Rollback to 1" ]; then + echo -e "Unexpected last revision description: $LAST_REVISION_DESCRIPTION" + exit 1 + fi + + kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml + echo ">>> Run upgrade fail retry test" + test_name=upgrade-fail-retry + kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed. + REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + + kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.upgradeFailures == 2 and ( .status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" )' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was upgraded and rolled back twice. + HISTORY=$(helm -n helm-system history -o json $test_name) + REVISION_COUNT=$(echo "$HISTORY" | jq 'length') + if [ "$REVISION_COUNT" != 5 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + fi + LAST_REVISION_DESCRIPTION=$(echo "$HISTORY" | jq -r 'last | .description') + if [ "$LAST_REVISION_DESCRIPTION" != "Rollback to 3" ]; then + echo -e "Unexpected last revision description: $LAST_REVISION_DESCRIPTION" + exit 1 + fi + + kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml + echo ">>> Run upgrade from ocirepo source" + test_name=upgrade-from-ocirepo-source + kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed. + REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + + kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml + echo ">>> Run upgrade fail with uninstall remediation strategy test" + test_name=upgrade-fail-remediate-uninstall + kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed. + HISTORY=$(helm -n helm-system history -o json $test_name) + REVISION_COUNT=$(echo "$HISTORY" | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') + if [ "$LAST_REVISION_STATUS" != "deployed" ]; then + echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" + exit 1 + fi + + kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.upgradeFailures == 1 and .status.installFailures == 1 and ( .status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" )' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was uninstalled/reinstalled. + HISTORY=$(helm -n helm-system history -o json $test_name) + REVISION_COUNT=$(echo "$HISTORY" | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') + if [ "$LAST_REVISION_STATUS" != "failed" ]; then + echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" + exit 1 + fi + + kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml + + - name: Run advanced tests + run: | + set -euo pipefail + echo ">>> Run impersonation tests" + kubectl apply -f config/testdata/impersonation + kubectl -n impersonation wait helmreleases/podinfo --for=condition=ready --timeout=2m + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n impersonation get helmrelease/podinfo-fail -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + echo ">>> Run delete-ns tests" + kubectl apply -f config/testdata/delete-ns + kubectl -n delete-ns wait helmreleases/podinfo --for=condition=ready --timeout=2m + kubectl delete ns delete-ns 1>/dev/null 2>&1 & + echo -n ">>> Waiting for namespace to be deleted" + if kubectl wait --for=delete namespace delete-ns --timeout=5m; then + echo ' Namespace deleted successfully' + else + echo ' Timed out waiting for namespace to be deleted' + kubectl get all -n delete-ns + exit 1 + fi + + echo ">>> Bootstrap Tests Using Local Helm Chart" + PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) + if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then + if [ "${{ github.event_name }}" = "pull_request" ]; then + TYPE=branch + REF="${GITHUB_HEAD_REF}" + else + REF=${GITHUB_REF} + if echo "$REF" | grep 'refs/tags/'; then + TYPE=tag + REF=${REF#refs/tags/} + else + TYPE=branch + REF=${REF#refs/heads/} + fi + fi + echo "REF=$REF of type $TYPE" + echo "helm install --namespace default --set $TYPE=$REF --set url=https://github.com/${{ github.repository }} this config/testdata/charts/crds/bootstrap" + helm install --namespace default --set $TYPE=$REF --set url=https://github.com/${{ github.repository }} this config/testdata/charts/crds/bootstrap + kubectl -n default apply -f config/testdata/crds-upgrade/init + kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m + fi + echo ">>> CRDs Upgrade Test Create" + PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) + if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then + kubectl -n default apply -f config/testdata/crds-upgrade/create + kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m + fi + echo ">>> CRDs Upgrade Test CreateReplace" + PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) + if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then + kubectl -n default apply -f config/testdata/crds-upgrade/create-replace + kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m + fi + echo ">>> Run upgrade with chart name changed and default ChartNameChangeStrategy" + PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) + if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then + test_name=upgrade-chart-name-change + kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed. + HISTORY=$(helm -n helm-system history -o json $test_name) + REVISION_COUNT=$(echo "$HISTORY" | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') + if [ "$LAST_REVISION_STATUS" != "deployed" ]; then + echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" + exit 1 + fi + + kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml + + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + # Validate release was uninstalled/reinstalled. + HISTORY=$(helm -n helm-system history -o json $test_name) + REVISION_COUNT=$(echo "$HISTORY" | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') + if [ "$LAST_REVISION_STATUS" != "deployed" ]; then + echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" + exit 1 + fi + + kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml + fi + echo ">>> Run upgrade with chart name changed and reinstall ChartNameChangeStrategy" + PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) + if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then + test_name=upgrade-chart-name-change + kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed. + HISTORY=$(helm -n helm-system history -o json $test_name) + REVISION_COUNT=$(echo "$HISTORY" | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') + if [ "$LAST_REVISION_STATUS" != "deployed" ]; then + echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" + exit 1 + fi + + kubectl -n helm-system apply -f config/testdata/$test_name/upgrade-reinstall.yaml + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + # Validate release was uninstalled/reinstalled. + HISTORY=$(helm -n helm-system history -o json $test_name) + REVISION_COUNT=$(echo "$HISTORY" | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') + if [ "$LAST_REVISION_STATUS" != "deployed" ]; then + echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" + exit 1 + fi + + kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml + fi + echo ">>> Run upgrade with chart name changed and InPlaceUpdate ChartNameChangeStrategy" + PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) + if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then + test_name=upgrade-chart-name-change + kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed. + HISTORY=$(helm -n helm-system history -o json $test_name) + REVISION_COUNT=$(echo "$HISTORY" | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') + if [ "$LAST_REVISION_STATUS" != "deployed" ]; then + echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" + exit 1 + fi + + kubectl -n helm-system apply -f config/testdata/$test_name/upgrade-inplace.yaml + + # Wait for the upgrade to complete (revision count should be 2). + echo -n ">>> Waiting for upgrade" + count=0 + until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml + fi - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - name: Logs if: always() continue-on-error: true From 616832e93c7820245d371386b94ceb7ff5aeb8b5 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Sun, 12 Jul 2026 19:11:56 +0100 Subject: [PATCH 2/3] Split e2e scripts into files Signed-off-by: Matheus Pimenta (cherry picked from commit 0b1aceed03176da3854a094bf19f604288592900) --- .github/workflows/e2e.yaml | 1277 +----------------------------------- hack/ci/e2e/advanced.sh | 214 ++++++ hack/ci/e2e/lifecycle.sh | 408 ++++++++++++ hack/ci/e2e/smoke.sh | 94 +++ hack/ci/e2e/ssa.sh | 555 ++++++++++++++++ 5 files changed, 1276 insertions(+), 1272 deletions(-) create mode 100644 hack/ci/e2e/advanced.sh create mode 100644 hack/ci/e2e/lifecycle.sh create mode 100644 hack/ci/e2e/smoke.sh create mode 100644 hack/ci/e2e/ssa.sh diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 76923bf6c..1c227d2db 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -25,7 +25,7 @@ jobs: run: make test - name: Check if working tree is dirty run: make verify - e2e: + kind: runs-on: ubuntu-latest permissions: contents: read @@ -65,1283 +65,16 @@ jobs: - parallel: - name: Run smoke tests run: | - set -euo pipefail - echo ">>> Test samples" - kubectl create ns samples - kubectl -n samples apply -f config/samples - kubectl -n samples wait hr/podinfo-ocirepository --for=condition=ready --timeout=4m - kubectl -n samples wait hr/podinfo-gitrepository --for=condition=ready --timeout=4m - kubectl -n samples wait hr/podinfo-helmrepository --for=condition=ready --timeout=4m - kubectl delete ns samples - echo ">>> Run smoke test" - kubectl -n helm-system apply -f config/testdata/podinfo - kubectl -n helm-system wait helmreleases/podinfo --for=condition=ready --timeout=4m - - # Inventory tracking enables drift detection and garbage collection. - # Ensure it captures managed objects from the Helm release. - INVENTORY=$(kubectl -n helm-system get helmrelease/podinfo -o jsonpath='{.status.inventory.entries}') - INVENTORY_COUNT=$(echo "$INVENTORY" | jq 'length') - if [ "$INVENTORY_COUNT" -lt 1 ]; then - echo "Expected inventory entries, got $INVENTORY_COUNT" - exit 1 - fi - # Deployment is a primary workload resource; its presence confirms - # that the inventory correctly tracks resources from the rendered manifests. - if ! echo "$INVENTORY" | jq -e '.[] | select(.id | contains("_Deployment"))' > /dev/null; then - echo "Expected Deployment in inventory" - echo "Inventory: $INVENTORY" - exit 1 - fi - - kubectl -n helm-system wait helmreleases/podinfo-git --for=condition=ready --timeout=4m - kubectl -n helm-system wait helmreleases/podinfo-oci --for=condition=ready --timeout=4m - kubectl -n helm-system delete -f config/testdata/podinfo - echo ">>> Run Job with TTL test" - # This test verifies that the wait logic correctly handles Jobs with - # ttlSecondsAfterFinished that get garbage-collected after completion. - # Without the fix, the wait would fail with NotFound error. - kubectl -n helm-system apply -f config/testdata/job-ttl - kubectl -n helm-system wait helmreleases/job-ttl --for=condition=ready --timeout=4m - kubectl -n helm-system delete -f config/testdata/job-ttl - echo ">>> Run dependency tests" - kubectl -n helm-system apply -f config/testdata/dependencies - kubectl -n helm-system wait helmreleases/backend --for=condition=ready --timeout=4m - kubectl -n helm-system wait helmreleases/frontend --for=condition=ready --timeout=4m - kubectl -n helm-system delete -f config/testdata/dependencies - echo ">>> Run values test" - kubectl -n helm-system apply -f config/testdata/valuesfrom - kubectl -n helm-system wait helmreleases/valuesfrom --for=condition=ready --timeout=4m - - RESULT=$(helm -n helm-system get values valuesfrom) - EXPECTED=$(cat config/testdata/valuesfrom/golden.txt) - if [ "$RESULT" != "$EXPECTED" ]; then - echo -e "$RESULT\n\ndoes not equal\n\n$EXPECTED" - exit 1 - fi - - kubectl -n helm-system delete -f config/testdata/valuesfrom - echo ">>> Run target namespace test" - kubectl -n helm-system apply -f config/testdata/targetnamespace - kubectl -n helm-system wait helmreleases/targetnamespace --for=condition=ready --timeout=4m - - # Confirm release in "default" namespace - kubectl -n default get deployment default-targetnamespace-podinfo - - kubectl -n helm-system delete -f config/testdata/targetnamespace - echo ">>> Run install create target namespace test" - kubectl -n helm-system apply -f config/testdata/install-create-target-ns - kubectl -n helm-system wait helmreleases/install-create-target-ns --for=condition=ready --timeout=4m - - # Confirm release in "install-create-target-ns" namespace - kubectl -n install-create-target-ns get deployment install-create-target-ns-install-create-target-ns-podinfo - - kubectl -n helm-system delete -f config/testdata/install-create-target-ns - echo ">>> Run install from helmChart test" - kubectl -n helm-system apply -f config/testdata/install-from-hc-source - kubectl -n helm-system wait helmreleases/podinfo-from-hc --for=condition=ready --timeout=4m - kubectl -n helm-system delete -f config/testdata/install-from-hc-source - echo ">>> Run install from ocirepo test" - kubectl -n helm-system apply -f config/testdata/install-from-ocirepo-source - kubectl -n helm-system wait helmreleases/podinfo-from-ocirepo --for=condition=ready --timeout=4m - kubectl -n helm-system delete -f config/testdata/install-from-ocirepo-source - echo ">>> Run post-renderer-kustomize test" - kubectl -n helm-system apply -f config/testdata/post-renderer-kustomize - kubectl -n helm-system wait helmreleases/post-renderer-kustomize --for=condition=ready --timeout=4m - RESULT=$(kubectl get deployment -n helm-system mypodinfo -o jsonpath='{.metadata.labels.xxxx}') - if [ "$RESULT" != "yyyy" ]; then - echo -e "$RESULT\n\ndoes not equal\n\nyyyy" - exit 1 - fi - RESULT=$(kubectl get deployment -n helm-system mypodinfo -o jsonpath='{.metadata.labels.yyyy}') - if [ "$RESULT" != "xxxx" ]; then - echo -e "$RESULT\n\ndoes not equal\n\nxxxx" - exit 1 - fi - kubectl -n helm-system delete -f config/testdata/post-renderer-kustomize - + bash -e hack/ci/e2e/smoke.sh - name: Run SSA tests run: | - set -euo pipefail - echo ">>> Run client-side apply upgrade test" - set -euo pipefail - - test_name=no-server-side-apply - deploy_name=$test_name-podinfo - - kubectl -n helm-system apply -f config/testdata/server-side-apply/no-ssa.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Capture managed fields before upgrade. - echo ">>> Checking managed fields after install" - MANAGED_FIELDS_BEFORE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - # Ensure we got managed fields data. - FIELD_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_BEFORE" | jq 'length') - if [ "$FIELD_COUNT_BEFORE" -lt 1 ]; then - echo "ERROR: No managed fields found on deployment" - exit 1 - fi - echo "Found $FIELD_COUNT_BEFORE managed field entries" - - # Show all managers and their operations. - echo "Managers before upgrade:" - echo "$MANAGED_FIELDS_BEFORE" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Verify helm-controller used Update (not Apply) after install. - HELM_UPDATE_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_BEFORE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - HELM_APPLY_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_BEFORE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - echo "helm-controller operations: Update=$HELM_UPDATE_COUNT_BEFORE, Apply=$HELM_APPLY_COUNT_BEFORE" - - if [ "$HELM_UPDATE_COUNT_BEFORE" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Update operation" - exit 1 - fi - if [ "$HELM_APPLY_COUNT_BEFORE" != "0" ]; then - echo "ERROR: Unexpected Apply operation from helm-controller" - exit 1 - fi - echo "PASS: Install used CSA (Update operation)" - - # Trigger upgrade by changing values. - echo ">>> Triggering upgrade via patch (expecting CSA)" - TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":{"upgrade-timestamp":"'$TIMESTAMP'"}}}}' - - # Wait for the upgrade to complete (revision count should be 2). - echo -n ">>> Waiting for upgrade" - count=0 - until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Verify the pod annotation was applied. - POD_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.upgrade-timestamp}') - echo "Pod annotation upgrade-timestamp: $POD_ANNOTATION" - if [ "$POD_ANNOTATION" != "$TIMESTAMP" ]; then - echo "ERROR: Pod annotation not updated, upgrade may not have occurred" - exit 1 - fi - - # Capture managed fields after upgrade. - echo ">>> Checking managed fields after upgrade" - MANAGED_FIELDS_AFTER=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - # Show all managers and their operations. - echo "Managers after upgrade:" - echo "$MANAGED_FIELDS_AFTER" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Verify helm-controller used Update (not Apply) after upgrade. - HELM_UPDATE_COUNT_AFTER=$(echo "$MANAGED_FIELDS_AFTER" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - HELM_APPLY_COUNT_AFTER=$(echo "$MANAGED_FIELDS_AFTER" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - echo "helm-controller operations: Update=$HELM_UPDATE_COUNT_AFTER, Apply=$HELM_APPLY_COUNT_AFTER" - - if [ "$HELM_UPDATE_COUNT_AFTER" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Update operation" - exit 1 - fi - if [ "$HELM_APPLY_COUNT_AFTER" != "0" ]; then - echo "ERROR: Unexpected Apply operation from helm-controller" - exit 1 - fi - echo "PASS: Upgrade used CSA (Update operation)" - - kubectl -n helm-system delete -f config/testdata/server-side-apply/no-ssa.yaml - echo ">>> Run SSA install with CSA upgrade test" - set -euo pipefail - - test_name=ssa-install-no-ssa-upgrade - deploy_name=$test_name-podinfo - - kubectl -n helm-system apply -f config/testdata/server-side-apply/$test_name.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Capture managed fields after install. - echo ">>> Checking managed fields after install (expecting SSA)" - MANAGED_FIELDS_INSTALL=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - FIELD_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq 'length') - if [ "$FIELD_COUNT" -lt 1 ]; then - echo "ERROR: No managed fields found on deployment" - exit 1 - fi - echo "Found $FIELD_COUNT managed field entries" - - echo "Managers after install:" - echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Show fields managed by helm-controller. - echo "Fields managed by helm-controller:" - echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' - - # Verify helm-controller used Apply (SSA) after install. - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" - - if [ "$HELM_APPLY_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Apply operation for SSA install" - exit 1 - fi - echo "PASS: Install used SSA (Apply operation)" - - # Trigger upgrade by changing values. - echo ">>> Triggering upgrade via patch (expecting CSA)" - TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":{"upgrade-timestamp":"'$TIMESTAMP'"}}}}' - - # Wait for the upgrade to complete. - echo -n ">>> Waiting for upgrade" - count=0 - until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - echo "DEBUG: Helm history:" - helm -n helm-system history $test_name || true - echo "DEBUG: HelmRelease status:" - kubectl -n helm-system get helmrelease/$test_name -o jsonpath='{.status}' | jq . || true - exit 1 - fi - done - echo ' done' - - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Verify the pod annotation was applied. - POD_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.upgrade-timestamp}') - echo "Pod annotation upgrade-timestamp: $POD_ANNOTATION" - if [ "$POD_ANNOTATION" != "$TIMESTAMP" ]; then - echo "ERROR: Pod annotation not updated, upgrade may not have occurred" - exit 1 - fi - - # Capture managed fields after upgrade. - echo ">>> Checking managed fields after upgrade (expecting CSA)" - MANAGED_FIELDS_UPGRADE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - echo "Managers after upgrade:" - echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Show fields managed by each helm-controller entry. - echo "Fields managed by helm-controller:" - echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | select(.manager | test("helm")) | if .operation == "Apply" then "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields" else "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0) | map(tostring) | join(".")] | sort | join(", "))" end' - - # Verify helm-controller used Update (CSA) after upgrade. - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" - - if [ "$HELM_UPDATE_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Update operation for CSA upgrade" - exit 1 - fi - echo "PASS: Upgrade used CSA (Update operation)" - - kubectl -n helm-system delete -f config/testdata/server-side-apply/$test_name.yaml - echo ">>> Run CSA install with SSA upgrade test" - set -euo pipefail - - test_name=no-ssa-install-ssa-upgrade - deploy_name=$test_name-podinfo - - kubectl -n helm-system apply -f config/testdata/server-side-apply/$test_name.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Capture managed fields after install. - echo ">>> Checking managed fields after install (expecting CSA)" - MANAGED_FIELDS_INSTALL=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - FIELD_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq 'length') - if [ "$FIELD_COUNT" -lt 1 ]; then - echo "ERROR: No managed fields found on deployment" - exit 1 - fi - echo "Found $FIELD_COUNT managed field entries" - - echo "Managers after install:" - echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Show fields managed by helm-controller. - echo "Fields managed by helm-controller:" - echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' - - # Verify helm-controller used Update (CSA) after install. - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" - - if [ "$HELM_UPDATE_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Update operation for CSA install" - exit 1 - fi - if [ "$HELM_APPLY_COUNT" != "0" ]; then - echo "ERROR: Unexpected Apply operation from helm-controller" - exit 1 - fi - echo "PASS: Install used CSA (Update operation)" - - # Trigger upgrade by changing values. - echo ">>> Triggering upgrade via patch (expecting SSA)" - TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":{"upgrade-timestamp":"'$TIMESTAMP'"}}}}' - - # Wait for the upgrade to complete. - echo -n ">>> Waiting for upgrade" - count=0 - until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Verify the pod annotation was applied. - POD_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.upgrade-timestamp}') - echo "Pod annotation upgrade-timestamp: $POD_ANNOTATION" - if [ "$POD_ANNOTATION" != "$TIMESTAMP" ]; then - echo "ERROR: Pod annotation not updated, upgrade may not have occurred" - exit 1 - fi - - # Capture managed fields after upgrade. - echo ">>> Checking managed fields after upgrade (expecting SSA)" - MANAGED_FIELDS_UPGRADE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - echo "Managers after upgrade:" - echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Show fields managed by helm-controller. - echo "Fields managed by helm-controller:" - echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | select(.manager | test("helm")) | if .operation == "Apply" then "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields" else "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0) | map(tostring) | join(".")] | sort | join(", "))" end' - - # Verify helm-controller used Apply (SSA) after upgrade. - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" - - if [ "$HELM_APPLY_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Apply operation for SSA upgrade" - exit 1 - fi - echo "PASS: Upgrade used SSA (Apply operation)" - - kubectl -n helm-system delete -f config/testdata/server-side-apply/$test_name.yaml - echo ">>> Run SSA to CSA field removal test" - set -euo pipefail - - # This test verifies the behavior when switching from SSA to CSA and - # removing a field from values. The field WILL be removed because - # Helm tracks field ownership and removes fields that are no longer - # in the rendered manifests, regardless of the apply method. - - test_name=ssa-to-csa-field-removal - deploy_name=$test_name-podinfo - - kubectl -n helm-system apply -f config/testdata/server-side-apply/$test_name.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Verify install used SSA. - echo ">>> Checking managed fields after install (expecting SSA)" - MANAGED_FIELDS_INSTALL=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - FIELD_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq 'length') - if [ "$FIELD_COUNT" -lt 1 ]; then - echo "ERROR: No managed fields found on deployment" - exit 1 - fi - echo "Found $FIELD_COUNT managed field entries" - - echo "Managers after install:" - echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Show fields managed by helm-controller. - echo "Fields managed by helm-controller:" - HELM_FIELD_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | [.fieldsV1 | paths(type == "object" and length == 0)] | length] | add') - echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' - - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" - - if [ "$HELM_APPLY_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Apply operation for SSA install" - exit 1 - fi - echo "PASS: Install used SSA (Apply operation)" - - # Verify the SSA-owned annotation is present after install. - echo ">>> Verifying SSA-owned annotation after install" - SSA_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.ssa-owned-field}') - echo "Pod annotation ssa-owned-field: $SSA_ANNOTATION" - if [ "$SSA_ANNOTATION" != "this-should-persist-after-csa-upgrade" ]; then - echo "ERROR: Expected ssa-owned-field annotation to be present after install" - exit 1 - fi - echo "PASS: SSA-owned annotation present after install" - - # Trigger upgrade by REMOVING the podAnnotations entirely. - # This simulates someone removing a field from their HelmRelease values. - echo ">>> Triggering upgrade by removing podAnnotations from values" - kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":null}}}' - - # Wait for the upgrade to complete. - echo -n ">>> Waiting for upgrade" - count=0 - until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - echo "DEBUG: Helm history:" - helm -n helm-system history $test_name || true - echo "DEBUG: HelmRelease status:" - kubectl -n helm-system get helmrelease/$test_name -o jsonpath='{.status}' | jq . || true - exit 1 - fi - done - echo ' done' - - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Check managed fields after upgrade. - echo ">>> Checking managed fields after upgrade" - MANAGED_FIELDS_UPGRADE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - - echo "Managers after upgrade:" - echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | " - \(.manager): \(.operation)"' - - # Show fields managed by helm-controller. - echo "Fields managed by helm-controller:" - HELM_FIELD_COUNT_AFTER=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | [.fieldsV1 | paths(type == "object" and length == 0)] | length] | add') - echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' - - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') - echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" - - # Verify field count decreased (annotation was removed). - echo ">>> Verifying field count decreased" - echo "Field count before: $HELM_FIELD_COUNT_BEFORE" - echo "Field count after: $HELM_FIELD_COUNT_AFTER" - if [ "$HELM_FIELD_COUNT_AFTER" -ge "$HELM_FIELD_COUNT_BEFORE" ]; then - echo "ERROR: Expected field count to decrease after removing podAnnotations" - exit 1 - fi - echo "PASS: Field count decreased from $HELM_FIELD_COUNT_BEFORE to $HELM_FIELD_COUNT_AFTER" - - # THIS IS THE KEY ASSERTION: The field should be REMOVED because Helm - # tracks field ownership and removes fields no longer in the manifests. - echo ">>> Verifying SSA-owned field was removed after upgrade" - SSA_ANNOTATION_AFTER=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.ssa-owned-field}') - echo "Pod annotation ssa-owned-field after upgrade: '$SSA_ANNOTATION_AFTER'" - - if [ -n "$SSA_ANNOTATION_AFTER" ]; then - echo "FAIL: SSA-owned field was NOT removed after upgrade" - echo "Expected the field to be removed when podAnnotations is removed from values." - exit 1 - fi - - echo "PASS: SSA-owned field was removed after upgrade" - echo "" - echo "This confirms that removing a field from HelmRelease values WILL" - echo "remove it from the actual object, even when switching from SSA to CSA." - echo "Helm properly tracks field ownership and cleans up removed fields." - - kubectl -n helm-system delete -f config/testdata/server-side-apply/$test_name.yaml - echo ">>> Run server-side apply test" - set -euo pipefail - - test_name=server-side-apply - deploy_name=$test_name-podinfo - - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Verify the release is deployed with SSA via Helm secret. - echo ">>> Checking Helm release secret after install" - APPLY_METHOD=$(kubectl -n helm-system get secret sh.helm.release.v1.$test_name.v1 -o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | jq -r '.apply_method') - echo "Helm release apply_method: $APPLY_METHOD" - if [ "$APPLY_METHOD" != "ssa" ]; then - echo "ERROR: Unexpected apply method: $APPLY_METHOD (expected: ssa)" - exit 1 - fi - - # Verify SSA via managed fields on deployment. - echo ">>> Checking managed fields after install" - MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - echo "Managers after install:" - echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' - - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - echo "helm-controller Apply operations: $HELM_APPLY_COUNT" - if [ "$HELM_APPLY_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Apply operation for SSA" - exit 1 - fi - echo "PASS: Install used SSA (Apply operation)" - - # Upgrade with SSA. - echo ">>> Applying upgrade manifest" - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Validate release was upgraded. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - echo "Helm revision count: $REVISION_COUNT" - if [ "$REVISION_COUNT" != 2 ]; then - echo "ERROR: Unexpected revision count: $REVISION_COUNT (expected: 2)" - exit 1 - fi - - # Verify SSA via managed fields after upgrade. - echo ">>> Checking managed fields after upgrade" - MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - echo "Managers after upgrade:" - echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' - - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - echo "helm-controller Apply operations: $HELM_APPLY_COUNT" - if [ "$HELM_APPLY_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Apply operation for SSA" - exit 1 - fi - echo "PASS: Upgrade used SSA (Apply operation)" - - kubectl -n helm-system delete -f config/testdata/$test_name/install.yaml - echo ">>> Run server-side apply rollback test" - set -euo pipefail - - test_name=server-side-apply-rollback - deploy_name=$test_name-podinfo - - kubectl -n helm-system apply -f config/testdata/server-side-apply/rollback-install.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Verify the release is deployed with SSA via Helm secret. - echo ">>> Checking Helm release secret after install" - APPLY_METHOD=$(kubectl -n helm-system get secret sh.helm.release.v1.$test_name.v1 -o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | jq -r '.apply_method') - echo "Helm release apply_method: $APPLY_METHOD" - if [ "$APPLY_METHOD" != "ssa" ]; then - echo "ERROR: Unexpected apply method: $APPLY_METHOD (expected: ssa)" - exit 1 - fi - - # Verify SSA via managed fields on deployment. - echo ">>> Checking managed fields after install" - MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - echo "Managers after install:" - echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' - - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - echo "helm-controller Apply operations: $HELM_APPLY_COUNT" - if [ "$HELM_APPLY_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Apply operation for SSA" - exit 1 - fi - echo "PASS: Install used SSA (Apply operation)" - - # Upgrade with failing config to trigger rollback. - echo ">>> Applying failing upgrade to trigger rollback" - kubectl -n helm-system apply -f config/testdata/server-side-apply/rollback-upgrade.yaml - echo -n ">>> Waiting for rollback" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Remediated=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate rollback happened (revision 3 = rollback to 1). - echo ">>> Checking Helm history after rollback" - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - echo "Helm revision count: $REVISION_COUNT" - if [ "$REVISION_COUNT" != 3 ]; then - echo "ERROR: Unexpected revision count: $REVISION_COUNT (expected: 3)" - exit 1 - fi - LAST_REVISION_DESCRIPTION=$(echo "$HISTORY" | jq -r 'last | .description') - echo "Last revision description: $LAST_REVISION_DESCRIPTION" - if [ "$LAST_REVISION_DESCRIPTION" != "Rollback to 1" ]; then - echo "ERROR: Unexpected last revision description: $LAST_REVISION_DESCRIPTION (expected: Rollback to 1)" - exit 1 - fi - echo "PASS: Rollback occurred (revision 3 = Rollback to 1)" - - # Verify the rollback release used SSA via Helm secret. - echo ">>> Checking Helm release secret after rollback" - APPLY_METHOD=$(kubectl -n helm-system get secret sh.helm.release.v1.$test_name.v3 -o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | jq -r '.apply_method') - echo "Helm release apply_method: $APPLY_METHOD" - if [ "$APPLY_METHOD" != "ssa" ]; then - echo "ERROR: Unexpected apply method after rollback: $APPLY_METHOD (expected: ssa)" - exit 1 - fi - - # Verify SSA via managed fields after rollback. - echo ">>> Checking managed fields after rollback" - MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') - echo "Managers after rollback:" - echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' - - HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') - echo "helm-controller Apply operations: $HELM_APPLY_COUNT" - if [ "$HELM_APPLY_COUNT" -lt 1 ]; then - echo "ERROR: Expected helm-controller to use Apply operation for SSA" - exit 1 - fi - echo "PASS: Rollback used SSA (Apply operation)" - - kubectl -n helm-system delete -f config/testdata/server-side-apply/rollback-install.yaml - + bash -e hack/ci/e2e/ssa.sh - name: Run lifecycle tests run: | - set -euo pipefail - echo ">>> Run install fail test" - test_name=install-fail - kubectl -n helm-system apply -f config/testdata/$test_name - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed and not uninstalled. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system delete -f config/testdata/$test_name - echo ">>> Run install test fail test" - test_name=install-test-fail - kubectl -n helm-system apply -f config/testdata/$test_name - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .TestSuccess=="False" and .Ready=="False"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed and not uninstalled. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system delete -f config/testdata/$test_name - echo ">>> Run install test fail ignore test" - test_name=install-test-fail-ignore - kubectl -n helm-system apply -f config/testdata/$test_name - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .TestSuccess=="False" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed and not uninstalled. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system delete -f config/testdata/$test_name - echo ">>> Run install fail with remediation test" - test_name=install-fail-remediate - kubectl -n helm-system apply -f config/testdata/$test_name - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Remediated=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Ensure release was uninstalled. - RELEASE_STATUS=$(helm -n helm-system history $test_name -o json | jq -r 'if length == 1 then .[0].status else empty end') - if [ "$RELEASE_STATUS" != "uninstalled" ]; then - echo -e "Unexpected release status: $RELEASE_STATUS" - exit 1 - fi - - kubectl -n helm-system delete -f config/testdata/$test_name - helm -n helm-system delete $test_name - echo ">>> Run install fail with retry test" - test_name=install-fail-retry - kubectl -n helm-system apply -f config/testdata/$test_name - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.installFailures == 2 and ( .status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Stalled=="True" )' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system delete -f config/testdata/$test_name - echo ">>> Run upgrade fail test" - test_name=upgrade-fail - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Stalled=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was upgraded and not rolled back. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 2 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - echo ">>> Run upgrade test fail test" - test_name=upgrade-test-fail - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .TestSuccess=="False" and .Ready=="False" and .Stalled=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was upgraded and not rolled back. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 2 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - echo ">>> Run upgrade fail with remediation test" - test_name=upgrade-fail-remediate - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Remediated=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was upgraded and then rolled back. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 3 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - fi - LAST_REVISION_DESCRIPTION=$(echo "$HISTORY" | jq -r 'last | .description') - if [ "$LAST_REVISION_DESCRIPTION" != "Rollback to 1" ]; then - echo -e "Unexpected last revision description: $LAST_REVISION_DESCRIPTION" - exit 1 - fi - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - echo ">>> Run upgrade fail retry test" - test_name=upgrade-fail-retry - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.upgradeFailures == 2 and ( .status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" )' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was upgraded and rolled back twice. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 5 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - fi - LAST_REVISION_DESCRIPTION=$(echo "$HISTORY" | jq -r 'last | .description') - if [ "$LAST_REVISION_DESCRIPTION" != "Rollback to 3" ]; then - echo -e "Unexpected last revision description: $LAST_REVISION_DESCRIPTION" - exit 1 - fi - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - echo ">>> Run upgrade from ocirepo source" - test_name=upgrade-from-ocirepo-source - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - echo ">>> Run upgrade fail with uninstall remediation strategy test" - test_name=upgrade-fail-remediate-uninstall - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') - if [ "$LAST_REVISION_STATUS" != "deployed" ]; then - echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.upgradeFailures == 1 and .status.installFailures == 1 and ( .status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" )' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was uninstalled/reinstalled. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') - if [ "$LAST_REVISION_STATUS" != "failed" ]; then - echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" - exit 1 - fi - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - + bash -e hack/ci/e2e/lifecycle.sh - name: Run advanced tests run: | - set -euo pipefail - echo ">>> Run impersonation tests" - kubectl apply -f config/testdata/impersonation - kubectl -n impersonation wait helmreleases/podinfo --for=condition=ready --timeout=2m - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n impersonation get helmrelease/podinfo-fail -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - echo ">>> Run delete-ns tests" - kubectl apply -f config/testdata/delete-ns - kubectl -n delete-ns wait helmreleases/podinfo --for=condition=ready --timeout=2m - kubectl delete ns delete-ns 1>/dev/null 2>&1 & - echo -n ">>> Waiting for namespace to be deleted" - if kubectl wait --for=delete namespace delete-ns --timeout=5m; then - echo ' Namespace deleted successfully' - else - echo ' Timed out waiting for namespace to be deleted' - kubectl get all -n delete-ns - exit 1 - fi - - echo ">>> Bootstrap Tests Using Local Helm Chart" - PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) - if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then - if [ "${{ github.event_name }}" = "pull_request" ]; then - TYPE=branch - REF="${GITHUB_HEAD_REF}" - else - REF=${GITHUB_REF} - if echo "$REF" | grep 'refs/tags/'; then - TYPE=tag - REF=${REF#refs/tags/} - else - TYPE=branch - REF=${REF#refs/heads/} - fi - fi - echo "REF=$REF of type $TYPE" - echo "helm install --namespace default --set $TYPE=$REF --set url=https://github.com/${{ github.repository }} this config/testdata/charts/crds/bootstrap" - helm install --namespace default --set $TYPE=$REF --set url=https://github.com/${{ github.repository }} this config/testdata/charts/crds/bootstrap - kubectl -n default apply -f config/testdata/crds-upgrade/init - kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m - fi - echo ">>> CRDs Upgrade Test Create" - PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) - if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then - kubectl -n default apply -f config/testdata/crds-upgrade/create - kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m - fi - echo ">>> CRDs Upgrade Test CreateReplace" - PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) - if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then - kubectl -n default apply -f config/testdata/crds-upgrade/create-replace - kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m - fi - echo ">>> Run upgrade with chart name changed and default ChartNameChangeStrategy" - PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) - if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then - test_name=upgrade-chart-name-change - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') - if [ "$LAST_REVISION_STATUS" != "deployed" ]; then - echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml - - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Validate release was uninstalled/reinstalled. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') - if [ "$LAST_REVISION_STATUS" != "deployed" ]; then - echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" - exit 1 - fi - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - fi - echo ">>> Run upgrade with chart name changed and reinstall ChartNameChangeStrategy" - PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) - if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then - test_name=upgrade-chart-name-change - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') - if [ "$LAST_REVISION_STATUS" != "deployed" ]; then - echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade-reinstall.yaml - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - # Validate release was uninstalled/reinstalled. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') - if [ "$LAST_REVISION_STATUS" != "deployed" ]; then - echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" - exit 1 - fi - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - fi - echo ">>> Run upgrade with chart name changed and InPlaceUpdate ChartNameChangeStrategy" - PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) - if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then - test_name=upgrade-chart-name-change - kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml - echo -n ">>> Waiting for expected conditions" - count=0 - until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - # Validate release was installed. - HISTORY=$(helm -n helm-system history -o json $test_name) - REVISION_COUNT=$(echo "$HISTORY" | jq 'length') - if [ "$REVISION_COUNT" != 1 ]; then - echo -e "Unexpected revision count: $REVISION_COUNT" - exit 1 - fi - LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') - if [ "$LAST_REVISION_STATUS" != "deployed" ]; then - echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" - exit 1 - fi - - kubectl -n helm-system apply -f config/testdata/$test_name/upgrade-inplace.yaml - - # Wait for the upgrade to complete (revision count should be 2). - echo -n ">>> Waiting for upgrade" - count=0 - until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do - echo -n '.' - sleep 5 - count=$((count + 1)) - if [[ ${count} -eq 24 ]]; then - echo ' No more retries left!' - exit 1 - fi - done - echo ' done' - - kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m - - kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml - fi - + bash -e hack/ci/e2e/advanced.sh - name: Logs if: always() continue-on-error: true diff --git a/hack/ci/e2e/advanced.sh b/hack/ci/e2e/advanced.sh new file mode 100644 index 000000000..db357451b --- /dev/null +++ b/hack/ci/e2e/advanced.sh @@ -0,0 +1,214 @@ +#!/usr/bin/env bash + +echo ">>> Run impersonation tests" +kubectl apply -f config/testdata/impersonation +kubectl -n impersonation wait helmreleases/podinfo --for=condition=ready --timeout=2m +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n impersonation get helmrelease/podinfo-fail -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' +echo ">>> Run delete-ns tests" +kubectl apply -f config/testdata/delete-ns +kubectl -n delete-ns wait helmreleases/podinfo --for=condition=ready --timeout=2m +kubectl delete ns delete-ns 1>/dev/null 2>&1 & +echo -n ">>> Waiting for namespace to be deleted" +if kubectl wait --for=delete namespace delete-ns --timeout=5m; then + echo ' Namespace deleted successfully' +else + echo ' Timed out waiting for namespace to be deleted' + kubectl get all -n delete-ns + exit 1 +fi + +echo ">>> Bootstrap Tests Using Local Helm Chart" +PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) +if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then + if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then + TYPE=branch + REF="${GITHUB_HEAD_REF}" + else + REF=${GITHUB_REF} + if echo "$REF" | grep 'refs/tags/'; then + TYPE=tag + REF=${REF#refs/tags/} + else + TYPE=branch + REF=${REF#refs/heads/} + fi + fi + echo "REF=$REF of type $TYPE" + echo "helm install --namespace default --set $TYPE=$REF --set url=https://github.com/$GITHUB_REPOSITORY this config/testdata/charts/crds/bootstrap" + helm install --namespace default --set $TYPE=$REF --set url=https://github.com/$GITHUB_REPOSITORY this config/testdata/charts/crds/bootstrap + kubectl -n default apply -f config/testdata/crds-upgrade/init + kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m +fi +echo ">>> CRDs Upgrade Test Create" +PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) +if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then + kubectl -n default apply -f config/testdata/crds-upgrade/create + kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m +fi +echo ">>> CRDs Upgrade Test CreateReplace" +PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) +if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then + kubectl -n default apply -f config/testdata/crds-upgrade/create-replace + kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m +fi +echo ">>> Run upgrade with chart name changed and default ChartNameChangeStrategy" +PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) +if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then + test_name=upgrade-chart-name-change + kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed. + HISTORY=$(helm -n helm-system history -o json $test_name) + REVISION_COUNT=$(echo "$HISTORY" | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') + if [ "$LAST_REVISION_STATUS" != "deployed" ]; then + echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" + exit 1 + fi + + kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml + + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + # Validate release was uninstalled/reinstalled. + HISTORY=$(helm -n helm-system history -o json $test_name) + REVISION_COUNT=$(echo "$HISTORY" | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') + if [ "$LAST_REVISION_STATUS" != "deployed" ]; then + echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" + exit 1 + fi + + kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml +fi +echo ">>> Run upgrade with chart name changed and reinstall ChartNameChangeStrategy" +PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) +if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then + test_name=upgrade-chart-name-change + kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed. + HISTORY=$(helm -n helm-system history -o json $test_name) + REVISION_COUNT=$(echo "$HISTORY" | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') + if [ "$LAST_REVISION_STATUS" != "deployed" ]; then + echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" + exit 1 + fi + + kubectl -n helm-system apply -f config/testdata/$test_name/upgrade-reinstall.yaml + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + # Validate release was uninstalled/reinstalled. + HISTORY=$(helm -n helm-system history -o json $test_name) + REVISION_COUNT=$(echo "$HISTORY" | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') + if [ "$LAST_REVISION_STATUS" != "deployed" ]; then + echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" + exit 1 + fi + + kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml +fi +echo ">>> Run upgrade with chart name changed and InPlaceUpdate ChartNameChangeStrategy" +PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) +if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then + test_name=upgrade-chart-name-change + kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml + echo -n ">>> Waiting for expected conditions" + count=0 + until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + # Validate release was installed. + HISTORY=$(helm -n helm-system history -o json $test_name) + REVISION_COUNT=$(echo "$HISTORY" | jq 'length') + if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 + fi + LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') + if [ "$LAST_REVISION_STATUS" != "deployed" ]; then + echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" + exit 1 + fi + + kubectl -n helm-system apply -f config/testdata/$test_name/upgrade-inplace.yaml + + # Wait for the upgrade to complete (revision count should be 2). + echo -n ">>> Waiting for upgrade" + count=0 + until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi + done + echo ' done' + + kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + + kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml +fi diff --git a/hack/ci/e2e/lifecycle.sh b/hack/ci/e2e/lifecycle.sh new file mode 100644 index 000000000..5ac1b1783 --- /dev/null +++ b/hack/ci/e2e/lifecycle.sh @@ -0,0 +1,408 @@ +#!/usr/bin/env bash + +echo ">>> Run install fail test" +test_name=install-fail +kubectl -n helm-system apply -f config/testdata/$test_name +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate release was installed and not uninstalled. +REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') +if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 +fi + +kubectl -n helm-system delete -f config/testdata/$test_name +echo ">>> Run install test fail test" +test_name=install-test-fail +kubectl -n helm-system apply -f config/testdata/$test_name +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .TestSuccess=="False" and .Ready=="False"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate release was installed and not uninstalled. +REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') +if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 +fi + +kubectl -n helm-system delete -f config/testdata/$test_name +echo ">>> Run install test fail ignore test" +test_name=install-test-fail-ignore +kubectl -n helm-system apply -f config/testdata/$test_name +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .TestSuccess=="False" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate release was installed and not uninstalled. +REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') +if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 +fi + +kubectl -n helm-system delete -f config/testdata/$test_name +echo ">>> Run install fail with remediation test" +test_name=install-fail-remediate +kubectl -n helm-system apply -f config/testdata/$test_name +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Remediated=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Ensure release was uninstalled. +RELEASE_STATUS=$(helm -n helm-system history $test_name -o json | jq -r 'if length == 1 then .[0].status else empty end') +if [ "$RELEASE_STATUS" != "uninstalled" ]; then + echo -e "Unexpected release status: $RELEASE_STATUS" + exit 1 +fi + +kubectl -n helm-system delete -f config/testdata/$test_name +helm -n helm-system delete $test_name +echo ">>> Run install fail with retry test" +test_name=install-fail-retry +kubectl -n helm-system apply -f config/testdata/$test_name +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.installFailures == 2 and ( .status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Stalled=="True" )' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate release was installed. +REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') +if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 +fi + +kubectl -n helm-system delete -f config/testdata/$test_name +echo ">>> Run upgrade fail test" +test_name=upgrade-fail +kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate release was installed. +REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') +if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 +fi + +kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Stalled=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate release was upgraded and not rolled back. +REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') +if [ "$REVISION_COUNT" != 2 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 +fi + +kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml +echo ">>> Run upgrade test fail test" +test_name=upgrade-test-fail +kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate release was installed. +REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') +if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 +fi + +kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .TestSuccess=="False" and .Ready=="False" and .Stalled=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate release was upgraded and not rolled back. +REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') +if [ "$REVISION_COUNT" != 2 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 +fi + +kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml +echo ">>> Run upgrade fail with remediation test" +test_name=upgrade-fail-remediate +kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate release was installed. +REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') +if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 +fi + +kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Remediated=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate release was upgraded and then rolled back. +HISTORY=$(helm -n helm-system history -o json $test_name) +REVISION_COUNT=$(echo "$HISTORY" | jq 'length') +if [ "$REVISION_COUNT" != 3 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" +fi +LAST_REVISION_DESCRIPTION=$(echo "$HISTORY" | jq -r 'last | .description') +if [ "$LAST_REVISION_DESCRIPTION" != "Rollback to 1" ]; then + echo -e "Unexpected last revision description: $LAST_REVISION_DESCRIPTION" + exit 1 +fi + +kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml +echo ">>> Run upgrade fail retry test" +test_name=upgrade-fail-retry +kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate release was installed. +REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') +if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 +fi + +kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.upgradeFailures == 2 and ( .status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" )' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate release was upgraded and rolled back twice. +HISTORY=$(helm -n helm-system history -o json $test_name) +REVISION_COUNT=$(echo "$HISTORY" | jq 'length') +if [ "$REVISION_COUNT" != 5 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" +fi +LAST_REVISION_DESCRIPTION=$(echo "$HISTORY" | jq -r 'last | .description') +if [ "$LAST_REVISION_DESCRIPTION" != "Rollback to 3" ]; then + echo -e "Unexpected last revision description: $LAST_REVISION_DESCRIPTION" + exit 1 +fi + +kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml +echo ">>> Run upgrade from ocirepo source" +test_name=upgrade-from-ocirepo-source +kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate release was installed. +REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') +if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 +fi + +kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml +echo ">>> Run upgrade fail with uninstall remediation strategy test" +test_name=upgrade-fail-remediate-uninstall +kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="True" and .Ready=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate release was installed. +HISTORY=$(helm -n helm-system history -o json $test_name) +REVISION_COUNT=$(echo "$HISTORY" | jq 'length') +if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 +fi +LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') +if [ "$LAST_REVISION_STATUS" != "deployed" ]; then + echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" + exit 1 +fi + +kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml +echo -n ">>> Waiting for expected conditions" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.upgradeFailures == 1 and .status.installFailures == 1 and ( .status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" )' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate release was uninstalled/reinstalled. +HISTORY=$(helm -n helm-system history -o json $test_name) +REVISION_COUNT=$(echo "$HISTORY" | jq 'length') +if [ "$REVISION_COUNT" != 1 ]; then + echo -e "Unexpected revision count: $REVISION_COUNT" + exit 1 +fi +LAST_REVISION_STATUS=$(echo "$HISTORY" | jq -r 'last | .status') +if [ "$LAST_REVISION_STATUS" != "failed" ]; then + echo -e "Unexpected last revision status: $LAST_REVISION_STATUS" + exit 1 +fi + +kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml diff --git a/hack/ci/e2e/smoke.sh b/hack/ci/e2e/smoke.sh new file mode 100644 index 000000000..275f97272 --- /dev/null +++ b/hack/ci/e2e/smoke.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash + +echo ">>> Test samples" +kubectl create ns samples +kubectl -n samples apply -f config/samples +kubectl -n samples wait hr/podinfo-ocirepository --for=condition=ready --timeout=4m +kubectl -n samples wait hr/podinfo-gitrepository --for=condition=ready --timeout=4m +kubectl -n samples wait hr/podinfo-helmrepository --for=condition=ready --timeout=4m +kubectl delete ns samples +echo ">>> Run smoke test" +kubectl -n helm-system apply -f config/testdata/podinfo +kubectl -n helm-system wait helmreleases/podinfo --for=condition=ready --timeout=4m + +# Inventory tracking enables drift detection and garbage collection. +# Ensure it captures managed objects from the Helm release. +INVENTORY=$(kubectl -n helm-system get helmrelease/podinfo -o jsonpath='{.status.inventory.entries}') +INVENTORY_COUNT=$(echo "$INVENTORY" | jq 'length') +if [ "$INVENTORY_COUNT" -lt 1 ]; then + echo "Expected inventory entries, got $INVENTORY_COUNT" + exit 1 +fi +# Deployment is a primary workload resource; its presence confirms +# that the inventory correctly tracks resources from the rendered manifests. +if ! echo "$INVENTORY" | jq -e '.[] | select(.id | contains("_Deployment"))' > /dev/null; then + echo "Expected Deployment in inventory" + echo "Inventory: $INVENTORY" + exit 1 +fi + +kubectl -n helm-system wait helmreleases/podinfo-git --for=condition=ready --timeout=4m +kubectl -n helm-system wait helmreleases/podinfo-oci --for=condition=ready --timeout=4m +kubectl -n helm-system delete -f config/testdata/podinfo +echo ">>> Run Job with TTL test" +# This test verifies that the wait logic correctly handles Jobs with +# ttlSecondsAfterFinished that get garbage-collected after completion. +# Without the fix, the wait would fail with NotFound error. +kubectl -n helm-system apply -f config/testdata/job-ttl +kubectl -n helm-system wait helmreleases/job-ttl --for=condition=ready --timeout=4m +kubectl -n helm-system delete -f config/testdata/job-ttl +echo ">>> Run dependency tests" +kubectl -n helm-system apply -f config/testdata/dependencies +kubectl -n helm-system wait helmreleases/backend --for=condition=ready --timeout=4m +kubectl -n helm-system wait helmreleases/frontend --for=condition=ready --timeout=4m +kubectl -n helm-system delete -f config/testdata/dependencies +echo ">>> Run values test" +kubectl -n helm-system apply -f config/testdata/valuesfrom +kubectl -n helm-system wait helmreleases/valuesfrom --for=condition=ready --timeout=4m + +RESULT=$(helm -n helm-system get values valuesfrom) +EXPECTED=$(cat config/testdata/valuesfrom/golden.txt) +if [ "$RESULT" != "$EXPECTED" ]; then + echo -e "$RESULT\n\ndoes not equal\n\n$EXPECTED" + exit 1 +fi + + kubectl -n helm-system delete -f config/testdata/valuesfrom +echo ">>> Run target namespace test" +kubectl -n helm-system apply -f config/testdata/targetnamespace +kubectl -n helm-system wait helmreleases/targetnamespace --for=condition=ready --timeout=4m + +# Confirm release in "default" namespace +kubectl -n default get deployment default-targetnamespace-podinfo + +kubectl -n helm-system delete -f config/testdata/targetnamespace +echo ">>> Run install create target namespace test" +kubectl -n helm-system apply -f config/testdata/install-create-target-ns +kubectl -n helm-system wait helmreleases/install-create-target-ns --for=condition=ready --timeout=4m + +# Confirm release in "install-create-target-ns" namespace +kubectl -n install-create-target-ns get deployment install-create-target-ns-install-create-target-ns-podinfo + +kubectl -n helm-system delete -f config/testdata/install-create-target-ns +echo ">>> Run install from helmChart test" +kubectl -n helm-system apply -f config/testdata/install-from-hc-source +kubectl -n helm-system wait helmreleases/podinfo-from-hc --for=condition=ready --timeout=4m +kubectl -n helm-system delete -f config/testdata/install-from-hc-source +echo ">>> Run install from ocirepo test" +kubectl -n helm-system apply -f config/testdata/install-from-ocirepo-source +kubectl -n helm-system wait helmreleases/podinfo-from-ocirepo --for=condition=ready --timeout=4m +kubectl -n helm-system delete -f config/testdata/install-from-ocirepo-source +echo ">>> Run post-renderer-kustomize test" +kubectl -n helm-system apply -f config/testdata/post-renderer-kustomize +kubectl -n helm-system wait helmreleases/post-renderer-kustomize --for=condition=ready --timeout=4m +RESULT=$(kubectl get deployment -n helm-system mypodinfo -o jsonpath='{.metadata.labels.xxxx}') +if [ "$RESULT" != "yyyy" ]; then + echo -e "$RESULT\n\ndoes not equal\n\nyyyy" + exit 1 +fi +RESULT=$(kubectl get deployment -n helm-system mypodinfo -o jsonpath='{.metadata.labels.yyyy}') +if [ "$RESULT" != "xxxx" ]; then + echo -e "$RESULT\n\ndoes not equal\n\nxxxx" + exit 1 +fi +kubectl -n helm-system delete -f config/testdata/post-renderer-kustomize diff --git a/hack/ci/e2e/ssa.sh b/hack/ci/e2e/ssa.sh new file mode 100644 index 000000000..d498d5bdd --- /dev/null +++ b/hack/ci/e2e/ssa.sh @@ -0,0 +1,555 @@ +#!/usr/bin/env bash + +echo ">>> Run client-side apply upgrade test" +set -euo pipefail + +test_name=no-server-side-apply +deploy_name=$test_name-podinfo + +kubectl -n helm-system apply -f config/testdata/server-side-apply/no-ssa.yaml +kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + +# Capture managed fields before upgrade. +echo ">>> Checking managed fields after install" +MANAGED_FIELDS_BEFORE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + +# Ensure we got managed fields data. +FIELD_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_BEFORE" | jq 'length') +if [ "$FIELD_COUNT_BEFORE" -lt 1 ]; then + echo "ERROR: No managed fields found on deployment" + exit 1 +fi +echo "Found $FIELD_COUNT_BEFORE managed field entries" + +# Show all managers and their operations. +echo "Managers before upgrade:" +echo "$MANAGED_FIELDS_BEFORE" | jq -r '.[] | " - \(.manager): \(.operation)"' + +# Verify helm-controller used Update (not Apply) after install. +HELM_UPDATE_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_BEFORE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') +HELM_APPLY_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_BEFORE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') +echo "helm-controller operations: Update=$HELM_UPDATE_COUNT_BEFORE, Apply=$HELM_APPLY_COUNT_BEFORE" + +if [ "$HELM_UPDATE_COUNT_BEFORE" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Update operation" + exit 1 +fi +if [ "$HELM_APPLY_COUNT_BEFORE" != "0" ]; then + echo "ERROR: Unexpected Apply operation from helm-controller" + exit 1 +fi +echo "PASS: Install used CSA (Update operation)" + +# Trigger upgrade by changing values. +echo ">>> Triggering upgrade via patch (expecting CSA)" +TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") +kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":{"upgrade-timestamp":"'$TIMESTAMP'"}}}}' + +# Wait for the upgrade to complete (revision count should be 2). +echo -n ">>> Waiting for upgrade" +count=0 +until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + +# Verify the pod annotation was applied. +POD_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.upgrade-timestamp}') +echo "Pod annotation upgrade-timestamp: $POD_ANNOTATION" +if [ "$POD_ANNOTATION" != "$TIMESTAMP" ]; then + echo "ERROR: Pod annotation not updated, upgrade may not have occurred" + exit 1 +fi + +# Capture managed fields after upgrade. +echo ">>> Checking managed fields after upgrade" +MANAGED_FIELDS_AFTER=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + +# Show all managers and their operations. +echo "Managers after upgrade:" +echo "$MANAGED_FIELDS_AFTER" | jq -r '.[] | " - \(.manager): \(.operation)"' + +# Verify helm-controller used Update (not Apply) after upgrade. +HELM_UPDATE_COUNT_AFTER=$(echo "$MANAGED_FIELDS_AFTER" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') +HELM_APPLY_COUNT_AFTER=$(echo "$MANAGED_FIELDS_AFTER" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') +echo "helm-controller operations: Update=$HELM_UPDATE_COUNT_AFTER, Apply=$HELM_APPLY_COUNT_AFTER" + +if [ "$HELM_UPDATE_COUNT_AFTER" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Update operation" + exit 1 +fi +if [ "$HELM_APPLY_COUNT_AFTER" != "0" ]; then + echo "ERROR: Unexpected Apply operation from helm-controller" + exit 1 +fi +echo "PASS: Upgrade used CSA (Update operation)" + +kubectl -n helm-system delete -f config/testdata/server-side-apply/no-ssa.yaml +echo ">>> Run SSA install with CSA upgrade test" +set -euo pipefail + +test_name=ssa-install-no-ssa-upgrade +deploy_name=$test_name-podinfo + +kubectl -n helm-system apply -f config/testdata/server-side-apply/$test_name.yaml +kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + +# Capture managed fields after install. +echo ">>> Checking managed fields after install (expecting SSA)" +MANAGED_FIELDS_INSTALL=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + +FIELD_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq 'length') +if [ "$FIELD_COUNT" -lt 1 ]; then + echo "ERROR: No managed fields found on deployment" + exit 1 +fi +echo "Found $FIELD_COUNT managed field entries" + +echo "Managers after install:" +echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | " - \(.manager): \(.operation)"' + +# Show fields managed by helm-controller. +echo "Fields managed by helm-controller:" +echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' + +# Verify helm-controller used Apply (SSA) after install. +HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') +HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') +echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" + +if [ "$HELM_APPLY_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Apply operation for SSA install" + exit 1 +fi +echo "PASS: Install used SSA (Apply operation)" + +# Trigger upgrade by changing values. +echo ">>> Triggering upgrade via patch (expecting CSA)" +TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") +kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":{"upgrade-timestamp":"'$TIMESTAMP'"}}}}' + +# Wait for the upgrade to complete. +echo -n ">>> Waiting for upgrade" +count=0 +until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + echo "DEBUG: Helm history:" + helm -n helm-system history $test_name || true + echo "DEBUG: HelmRelease status:" + kubectl -n helm-system get helmrelease/$test_name -o jsonpath='{.status}' | jq . || true + exit 1 + fi +done +echo ' done' + +kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + +# Verify the pod annotation was applied. +POD_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.upgrade-timestamp}') +echo "Pod annotation upgrade-timestamp: $POD_ANNOTATION" +if [ "$POD_ANNOTATION" != "$TIMESTAMP" ]; then + echo "ERROR: Pod annotation not updated, upgrade may not have occurred" + exit 1 +fi + +# Capture managed fields after upgrade. +echo ">>> Checking managed fields after upgrade (expecting CSA)" +MANAGED_FIELDS_UPGRADE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + +echo "Managers after upgrade:" +echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | " - \(.manager): \(.operation)"' + +# Show fields managed by each helm-controller entry. +echo "Fields managed by helm-controller:" +echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | select(.manager | test("helm")) | if .operation == "Apply" then "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields" else "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0) | map(tostring) | join(".")] | sort | join(", "))" end' + +# Verify helm-controller used Update (CSA) after upgrade. +HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') +HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') +echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" + +if [ "$HELM_UPDATE_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Update operation for CSA upgrade" + exit 1 +fi +echo "PASS: Upgrade used CSA (Update operation)" + +kubectl -n helm-system delete -f config/testdata/server-side-apply/$test_name.yaml +echo ">>> Run CSA install with SSA upgrade test" +set -euo pipefail + +test_name=no-ssa-install-ssa-upgrade +deploy_name=$test_name-podinfo + +kubectl -n helm-system apply -f config/testdata/server-side-apply/$test_name.yaml +kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + +# Capture managed fields after install. +echo ">>> Checking managed fields after install (expecting CSA)" +MANAGED_FIELDS_INSTALL=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + +FIELD_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq 'length') +if [ "$FIELD_COUNT" -lt 1 ]; then + echo "ERROR: No managed fields found on deployment" + exit 1 +fi +echo "Found $FIELD_COUNT managed field entries" + +echo "Managers after install:" +echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | " - \(.manager): \(.operation)"' + +# Show fields managed by helm-controller. +echo "Fields managed by helm-controller:" +echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' + +# Verify helm-controller used Update (CSA) after install. +HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') +HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') +echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" + +if [ "$HELM_UPDATE_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Update operation for CSA install" + exit 1 +fi +if [ "$HELM_APPLY_COUNT" != "0" ]; then + echo "ERROR: Unexpected Apply operation from helm-controller" + exit 1 +fi +echo "PASS: Install used CSA (Update operation)" + +# Trigger upgrade by changing values. +echo ">>> Triggering upgrade via patch (expecting SSA)" +TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ") +kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":{"upgrade-timestamp":"'$TIMESTAMP'"}}}}' + +# Wait for the upgrade to complete. +echo -n ">>> Waiting for upgrade" +count=0 +until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + +# Verify the pod annotation was applied. +POD_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.upgrade-timestamp}') +echo "Pod annotation upgrade-timestamp: $POD_ANNOTATION" +if [ "$POD_ANNOTATION" != "$TIMESTAMP" ]; then + echo "ERROR: Pod annotation not updated, upgrade may not have occurred" + exit 1 +fi + +# Capture managed fields after upgrade. +echo ">>> Checking managed fields after upgrade (expecting SSA)" +MANAGED_FIELDS_UPGRADE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + +echo "Managers after upgrade:" +echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | " - \(.manager): \(.operation)"' + +# Show fields managed by helm-controller. +echo "Fields managed by helm-controller:" +echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | select(.manager | test("helm")) | if .operation == "Apply" then "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields" else "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0) | map(tostring) | join(".")] | sort | join(", "))" end' + +# Verify helm-controller used Apply (SSA) after upgrade. +HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') +HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') +echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" + +if [ "$HELM_APPLY_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Apply operation for SSA upgrade" + exit 1 +fi +echo "PASS: Upgrade used SSA (Apply operation)" + +kubectl -n helm-system delete -f config/testdata/server-side-apply/$test_name.yaml +echo ">>> Run SSA to CSA field removal test" +set -euo pipefail + +# This test verifies the behavior when switching from SSA to CSA and +# removing a field from values. The field WILL be removed because +# Helm tracks field ownership and removes fields that are no longer +# in the rendered manifests, regardless of the apply method. + +test_name=ssa-to-csa-field-removal +deploy_name=$test_name-podinfo + +kubectl -n helm-system apply -f config/testdata/server-side-apply/$test_name.yaml +kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + +# Verify install used SSA. +echo ">>> Checking managed fields after install (expecting SSA)" +MANAGED_FIELDS_INSTALL=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + +FIELD_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq 'length') +if [ "$FIELD_COUNT" -lt 1 ]; then + echo "ERROR: No managed fields found on deployment" + exit 1 +fi +echo "Found $FIELD_COUNT managed field entries" + +echo "Managers after install:" +echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | " - \(.manager): \(.operation)"' + +# Show fields managed by helm-controller. +echo "Fields managed by helm-controller:" +HELM_FIELD_COUNT_BEFORE=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | [.fieldsV1 | paths(type == "object" and length == 0)] | length] | add') +echo "$MANAGED_FIELDS_INSTALL" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' + +HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') +HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_INSTALL" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') +echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" + +if [ "$HELM_APPLY_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Apply operation for SSA install" + exit 1 +fi +echo "PASS: Install used SSA (Apply operation)" + +# Verify the SSA-owned annotation is present after install. +echo ">>> Verifying SSA-owned annotation after install" +SSA_ANNOTATION=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.ssa-owned-field}') +echo "Pod annotation ssa-owned-field: $SSA_ANNOTATION" +if [ "$SSA_ANNOTATION" != "this-should-persist-after-csa-upgrade" ]; then + echo "ERROR: Expected ssa-owned-field annotation to be present after install" + exit 1 +fi +echo "PASS: SSA-owned annotation present after install" + +# Trigger upgrade by REMOVING the podAnnotations entirely. +# This simulates someone removing a field from their HelmRelease values. +echo ">>> Triggering upgrade by removing podAnnotations from values" +kubectl -n helm-system patch helmrelease/$test_name --type=merge -p '{"spec":{"values":{"podAnnotations":null}}}' + +# Wait for the upgrade to complete. +echo -n ">>> Waiting for upgrade" +count=0 +until [ '2' == "$(helm -n helm-system history -o json $test_name 2>/dev/null | jq 'length')" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + echo "DEBUG: Helm history:" + helm -n helm-system history $test_name || true + echo "DEBUG: HelmRelease status:" + kubectl -n helm-system get helmrelease/$test_name -o jsonpath='{.status}' | jq . || true + exit 1 + fi +done +echo ' done' + +kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + +# Check managed fields after upgrade. +echo ">>> Checking managed fields after upgrade" +MANAGED_FIELDS_UPGRADE=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') + +echo "Managers after upgrade:" +echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | " - \(.manager): \(.operation)"' + +# Show fields managed by helm-controller. +echo "Fields managed by helm-controller:" +HELM_FIELD_COUNT_AFTER=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | [.fieldsV1 | paths(type == "object" and length == 0)] | length] | add') +echo "$MANAGED_FIELDS_UPGRADE" | jq -r '.[] | select(.manager | test("helm")) | "\(.operation): \([.fieldsV1 | paths(type == "object" and length == 0)] | length) fields"' + +HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') +HELM_UPDATE_COUNT=$(echo "$MANAGED_FIELDS_UPGRADE" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Update")] | length') +echo "helm-controller operations: Apply=$HELM_APPLY_COUNT, Update=$HELM_UPDATE_COUNT" + +# Verify field count decreased (annotation was removed). +echo ">>> Verifying field count decreased" +echo "Field count before: $HELM_FIELD_COUNT_BEFORE" +echo "Field count after: $HELM_FIELD_COUNT_AFTER" +if [ "$HELM_FIELD_COUNT_AFTER" -ge "$HELM_FIELD_COUNT_BEFORE" ]; then + echo "ERROR: Expected field count to decrease after removing podAnnotations" + exit 1 +fi +echo "PASS: Field count decreased from $HELM_FIELD_COUNT_BEFORE to $HELM_FIELD_COUNT_AFTER" + +# THIS IS THE KEY ASSERTION: The field should be REMOVED because Helm +# tracks field ownership and removes fields no longer in the manifests. +echo ">>> Verifying SSA-owned field was removed after upgrade" +SSA_ANNOTATION_AFTER=$(kubectl -n helm-system get deployment $deploy_name -o jsonpath='{.spec.template.metadata.annotations.ssa-owned-field}') +echo "Pod annotation ssa-owned-field after upgrade: '$SSA_ANNOTATION_AFTER'" + +if [ -n "$SSA_ANNOTATION_AFTER" ]; then + echo "FAIL: SSA-owned field was NOT removed after upgrade" + echo "Expected the field to be removed when podAnnotations is removed from values." + exit 1 +fi + +echo "PASS: SSA-owned field was removed after upgrade" +echo "" +echo "This confirms that removing a field from HelmRelease values WILL" +echo "remove it from the actual object, even when switching from SSA to CSA." +echo "Helm properly tracks field ownership and cleans up removed fields." + +kubectl -n helm-system delete -f config/testdata/server-side-apply/$test_name.yaml +echo ">>> Run server-side apply test" +set -euo pipefail + +test_name=server-side-apply +deploy_name=$test_name-podinfo + +kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml +kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + +# Verify the release is deployed with SSA via Helm secret. +echo ">>> Checking Helm release secret after install" +APPLY_METHOD=$(kubectl -n helm-system get secret sh.helm.release.v1.$test_name.v1 -o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | jq -r '.apply_method') +echo "Helm release apply_method: $APPLY_METHOD" +if [ "$APPLY_METHOD" != "ssa" ]; then + echo "ERROR: Unexpected apply method: $APPLY_METHOD (expected: ssa)" + exit 1 +fi + +# Verify SSA via managed fields on deployment. +echo ">>> Checking managed fields after install" +MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') +echo "Managers after install:" +echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' + +HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') +echo "helm-controller Apply operations: $HELM_APPLY_COUNT" +if [ "$HELM_APPLY_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Apply operation for SSA" + exit 1 +fi +echo "PASS: Install used SSA (Apply operation)" + +# Upgrade with SSA. +echo ">>> Applying upgrade manifest" +kubectl -n helm-system apply -f config/testdata/$test_name/upgrade.yaml +kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + +# Validate release was upgraded. +REVISION_COUNT=$(helm -n helm-system history -o json $test_name | jq 'length') +echo "Helm revision count: $REVISION_COUNT" +if [ "$REVISION_COUNT" != 2 ]; then + echo "ERROR: Unexpected revision count: $REVISION_COUNT (expected: 2)" + exit 1 +fi + +# Verify SSA via managed fields after upgrade. +echo ">>> Checking managed fields after upgrade" +MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') +echo "Managers after upgrade:" +echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' + +HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') +echo "helm-controller Apply operations: $HELM_APPLY_COUNT" +if [ "$HELM_APPLY_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Apply operation for SSA" + exit 1 +fi +echo "PASS: Upgrade used SSA (Apply operation)" + +kubectl -n helm-system delete -f config/testdata/$test_name/install.yaml +echo ">>> Run server-side apply rollback test" +set -euo pipefail + +test_name=server-side-apply-rollback +deploy_name=$test_name-podinfo + +kubectl -n helm-system apply -f config/testdata/server-side-apply/rollback-install.yaml +kubectl -n helm-system wait helmreleases/$test_name --for=condition=ready --timeout=4m + +# Verify the release is deployed with SSA via Helm secret. +echo ">>> Checking Helm release secret after install" +APPLY_METHOD=$(kubectl -n helm-system get secret sh.helm.release.v1.$test_name.v1 -o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | jq -r '.apply_method') +echo "Helm release apply_method: $APPLY_METHOD" +if [ "$APPLY_METHOD" != "ssa" ]; then + echo "ERROR: Unexpected apply method: $APPLY_METHOD (expected: ssa)" + exit 1 +fi + +# Verify SSA via managed fields on deployment. +echo ">>> Checking managed fields after install" +MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') +echo "Managers after install:" +echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' + +HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') +echo "helm-controller Apply operations: $HELM_APPLY_COUNT" +if [ "$HELM_APPLY_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Apply operation for SSA" + exit 1 +fi +echo "PASS: Install used SSA (Apply operation)" + +# Upgrade with failing config to trigger rollback. +echo ">>> Applying failing upgrade to trigger rollback" +kubectl -n helm-system apply -f config/testdata/server-side-apply/rollback-upgrade.yaml +echo -n ">>> Waiting for rollback" +count=0 +until [ 'true' == "$( kubectl -n helm-system get helmrelease/$test_name -o json | jq '.status.conditions | map( { (.type): .status } ) | add | .Released=="False" and .Ready=="False" and .Remediated=="True"' )" ]; do + echo -n '.' + sleep 5 + count=$((count + 1)) + if [[ ${count} -eq 24 ]]; then + echo ' No more retries left!' + exit 1 + fi +done +echo ' done' + +# Validate rollback happened (revision 3 = rollback to 1). +echo ">>> Checking Helm history after rollback" +HISTORY=$(helm -n helm-system history -o json $test_name) +REVISION_COUNT=$(echo "$HISTORY" | jq 'length') +echo "Helm revision count: $REVISION_COUNT" +if [ "$REVISION_COUNT" != 3 ]; then + echo "ERROR: Unexpected revision count: $REVISION_COUNT (expected: 3)" + exit 1 +fi +LAST_REVISION_DESCRIPTION=$(echo "$HISTORY" | jq -r 'last | .description') +echo "Last revision description: $LAST_REVISION_DESCRIPTION" +if [ "$LAST_REVISION_DESCRIPTION" != "Rollback to 1" ]; then + echo "ERROR: Unexpected last revision description: $LAST_REVISION_DESCRIPTION (expected: Rollback to 1)" + exit 1 +fi +echo "PASS: Rollback occurred (revision 3 = Rollback to 1)" + +# Verify the rollback release used SSA via Helm secret. +echo ">>> Checking Helm release secret after rollback" +APPLY_METHOD=$(kubectl -n helm-system get secret sh.helm.release.v1.$test_name.v3 -o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | jq -r '.apply_method') +echo "Helm release apply_method: $APPLY_METHOD" +if [ "$APPLY_METHOD" != "ssa" ]; then + echo "ERROR: Unexpected apply method after rollback: $APPLY_METHOD (expected: ssa)" + exit 1 +fi + +# Verify SSA via managed fields after rollback. +echo ">>> Checking managed fields after rollback" +MANAGED_FIELDS=$(kubectl -n helm-system get deployment $deploy_name --show-managed-fields -o jsonpath='{.metadata.managedFields}') +echo "Managers after rollback:" +echo "$MANAGED_FIELDS" | jq -r '.[] | " - \(.manager): \(.operation)"' + +HELM_APPLY_COUNT=$(echo "$MANAGED_FIELDS" | jq '[.[] | select(.manager | test("helm")) | select(.operation == "Apply")] | length') +echo "helm-controller Apply operations: $HELM_APPLY_COUNT" +if [ "$HELM_APPLY_COUNT" -lt 1 ]; then + echo "ERROR: Expected helm-controller to use Apply operation for SSA" + exit 1 +fi +echo "PASS: Rollback used SSA (Apply operation)" + +kubectl -n helm-system delete -f config/testdata/server-side-apply/rollback-install.yaml From e2b2b6c1371bf8fe07d0f888f907fae71cfdd7ae Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Sun, 12 Jul 2026 20:13:05 +0100 Subject: [PATCH 3/3] Allow Git repo-based chart tests to run in e2e for fork PRs Signed-off-by: Matheus Pimenta (cherry picked from commit 6b062335bcc51af072ab49d47828715a4f07d73d) --- hack/ci/e2e/advanced.sh | 58 +++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/hack/ci/e2e/advanced.sh b/hack/ci/e2e/advanced.sh index db357451b..6b2247b53 100644 --- a/hack/ci/e2e/advanced.sh +++ b/hack/ci/e2e/advanced.sh @@ -29,42 +29,34 @@ else fi echo ">>> Bootstrap Tests Using Local Helm Chart" -PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) -if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then - if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then - TYPE=branch - REF="${GITHUB_HEAD_REF}" +TEST_REPOSITORY=$GITHUB_REPOSITORY +if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then + TYPE=branch + REF=${GITHUB_HEAD_REF} + TEST_REPOSITORY=$(jq -er '.pull_request.head.repo.full_name' "$GITHUB_EVENT_PATH") +else + REF=${GITHUB_REF} + if echo "$REF" | grep 'refs/tags/'; then + TYPE=tag + REF=${REF#refs/tags/} else - REF=${GITHUB_REF} - if echo "$REF" | grep 'refs/tags/'; then - TYPE=tag - REF=${REF#refs/tags/} - else - TYPE=branch - REF=${REF#refs/heads/} - fi + TYPE=branch + REF=${REF#refs/heads/} fi - echo "REF=$REF of type $TYPE" - echo "helm install --namespace default --set $TYPE=$REF --set url=https://github.com/$GITHUB_REPOSITORY this config/testdata/charts/crds/bootstrap" - helm install --namespace default --set $TYPE=$REF --set url=https://github.com/$GITHUB_REPOSITORY this config/testdata/charts/crds/bootstrap - kubectl -n default apply -f config/testdata/crds-upgrade/init - kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m fi +echo "REF=$REF of type $TYPE" +echo "helm install --namespace default --set $TYPE=$REF --set url=https://github.com/$TEST_REPOSITORY this config/testdata/charts/crds/bootstrap" +helm install --namespace default --set $TYPE=$REF --set url=https://github.com/$TEST_REPOSITORY this config/testdata/charts/crds/bootstrap +kubectl -n default apply -f config/testdata/crds-upgrade/init +kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m echo ">>> CRDs Upgrade Test Create" -PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) -if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then - kubectl -n default apply -f config/testdata/crds-upgrade/create - kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m -fi +kubectl -n default apply -f config/testdata/crds-upgrade/create +kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m echo ">>> CRDs Upgrade Test CreateReplace" -PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) -if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then - kubectl -n default apply -f config/testdata/crds-upgrade/create-replace - kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m -fi +kubectl -n default apply -f config/testdata/crds-upgrade/create-replace +kubectl -n default wait helmreleases/crds-upgrade-test --for=condition=ready --timeout=2m echo ">>> Run upgrade with chart name changed and default ChartNameChangeStrategy" -PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) -if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then +if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then test_name=upgrade-chart-name-change kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml echo -n ">>> Waiting for expected conditions" @@ -113,8 +105,7 @@ if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml fi echo ">>> Run upgrade with chart name changed and reinstall ChartNameChangeStrategy" -PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) -if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then +if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then test_name=upgrade-chart-name-change kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml echo -n ">>> Waiting for expected conditions" @@ -162,8 +153,7 @@ if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || kubectl delete -n helm-system -f config/testdata/$test_name/install.yaml fi echo ">>> Run upgrade with chart name changed and InPlaceUpdate ChartNameChangeStrategy" -PR_HEAD_REPO=$(jq -r '.pull_request.head.repo.full_name // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || true) -if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || { [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] && [[ "$PR_HEAD_REPO" == "$GITHUB_REPOSITORY" ]]; }; then +if [[ "$GITHUB_REF" == refs/tags/* ]] || [[ "$GITHUB_REF" == refs/heads/* ]] || [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then test_name=upgrade-chart-name-change kubectl -n helm-system apply -f config/testdata/$test_name/install.yaml echo -n ">>> Waiting for expected conditions"