atharvalade commented on code in PR #3019: URL: https://github.com/apache/iggy/pull/3019#discussion_r2991174032
########## scripts/ci/test_helm.sh: ########## @@ -0,0 +1,418 @@ +#!/usr/bin/env bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: scripts/ci/test_helm.sh <validate|smoke|cleanup-smoke|collect-smoke-diagnostics> + +Commands: + validate Run Helm lint and render validation scenarios. + smoke Run the Helm runtime smoke scenario against the current Kubernetes context. + cleanup-smoke Remove the Helm smoke release namespace and any failed-install leftovers. + collect-smoke-diagnostics Collect diagnostics for the Helm smoke namespace. + +Notes: + - validate requires helm. + - smoke requires helm, kubectl, curl, and python3, plus an existing cluster and ingress controller. + - cleanup-smoke requires kubectl and optionally helm. + - collect-smoke-diagnostics is best-effort and does not fail on missing resources. +EOF +} + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$REPO_ROOT" + +CHART_DIR="${CHART_DIR:-helm/charts/iggy}" +HELM_RENDER_DIR="${HELM_RENDER_DIR:-/tmp/helm-render}" +HELM_SMOKE_NAMESPACE="${HELM_SMOKE_NAMESPACE:-iggy-smoke}" +HELM_SMOKE_RELEASE="${HELM_SMOKE_RELEASE:-iggy-smoke}" +HELM_SMOKE_REPORT_DIR="${HELM_SMOKE_REPORT_DIR:-reports/helm-smoke}" +HELM_SMOKE_SERVER_HOST="${HELM_SMOKE_SERVER_HOST:-server.iggy.local}" +HELM_SMOKE_UI_HOST="${HELM_SMOKE_UI_HOST:-ui.iggy.local}" +HELM_SMOKE_TIMEOUT="${HELM_SMOKE_TIMEOUT:-5m}" +HELM_SMOKE_INGRESS_CLASS="${HELM_SMOKE_INGRESS_CLASS:-nginx}" +HELM_SMOKE_KIND_NAME="${HELM_SMOKE_KIND_NAME:-iggy-helm-smoke}" +HELM_SMOKE_SERVER_CPU_ALLOCATION="${HELM_SMOKE_SERVER_CPU_ALLOCATION:-1}" + +require_command() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Error: required command '$1' not found" >&2 + exit 1 + fi +} + +extract_chart_field() { + local field="$1" + local value + + value="$( + awk -v field="$field" ' + $1 == field ":" { + gsub(/"/, "", $2) + print $2 + exit + } + ' "$CHART_DIR/Chart.yaml" + )" + + if [ -z "$value" ]; then + echo "Error: could not extract '$field' from $CHART_DIR/Chart.yaml" >&2 + exit 1 + fi + + printf '%s\n' "$value" +} + +extract_values_tag() { + local section="$1" + local value + + value="$( + awk -v section="$section" ' + $1 == section ":" { + in_section = 1 + next + } + in_section && /^[^[:space:]]/ { + in_section = 0 + } + in_section && $1 == "tag:" { + gsub(/"/, "", $2) + print $2 + exit + } + ' "$CHART_DIR/values.yaml" + )" + + if [ -z "$value" ]; then + echo "Error: could not extract '$section.image.tag' from $CHART_DIR/values.yaml" >&2 + exit 1 + fi + + printf '%s\n' "$value" +} + +prepare_render_dir() { + if [ -z "$HELM_RENDER_DIR" ] || [ "$HELM_RENDER_DIR" = "/" ]; then + echo "Error: HELM_RENDER_DIR must not be empty or /" >&2 + exit 1 + fi + + rm -rf "$HELM_RENDER_DIR" + mkdir -p "$HELM_RENDER_DIR" +} + +extract_kind_names() { + local file="$1" + local kind="$2" + + awk -v kind="$kind" ' + /^kind: / { + current_kind = $2 + in_metadata = 0 + next + } + /^metadata:$/ { + in_metadata = 1 + next + } + in_metadata && /^ name: / { + if (current_kind == kind) { + print $2 + } + in_metadata = 0 + } + ' "$file" +} + +validate() { + require_command helm + + local chart_version + local chart_app_version + local server_image_tag + local ui_image_tag + + chart_version="$(extract_chart_field version)" + chart_app_version="$(extract_chart_field appVersion)" + server_image_tag="$(extract_values_tag server)" + ui_image_tag="$(extract_values_tag ui)" + + prepare_render_dir + + helm lint --strict "$CHART_DIR" + + helm template iggy "$CHART_DIR" > "$HELM_RENDER_DIR/default.yaml" + test "$(grep -c '^kind: Deployment$' "$HELM_RENDER_DIR/default.yaml")" -eq 2 + test "$(grep -c '^kind: Service$' "$HELM_RENDER_DIR/default.yaml")" -eq 2 + test "$(grep -c '^kind: ServiceAccount$' "$HELM_RENDER_DIR/default.yaml")" -eq 1 + test "$(grep -c '^kind: Secret$' "$HELM_RENDER_DIR/default.yaml")" -eq 1 + grep -q "helm.sh/chart: iggy-${chart_version}" "$HELM_RENDER_DIR/default.yaml" + grep -q "helm.sh/chart: iggy-ui-${chart_version}" "$HELM_RENDER_DIR/default.yaml" + grep -q "app.kubernetes.io/version: \"${chart_app_version}\"" "$HELM_RENDER_DIR/default.yaml" + grep -q "image: \"apache/iggy:${server_image_tag}\"" "$HELM_RENDER_DIR/default.yaml" + grep -q "image: \"apache/iggy-web-ui:${ui_image_tag}\"" "$HELM_RENDER_DIR/default.yaml" + + helm template iggy "$CHART_DIR" \ + --set server.persistence.enabled=true \ + --set autoscaling.enabled=true \ + --set autoscaling.targetCPUUtilizationPercentage=80 \ + --set server.ingress.enabled=true \ + --set ui.ingress.enabled=true \ + --set server.serviceMonitor.enabled=true \ + > "$HELM_RENDER_DIR/all-features.yaml" + grep -q '^kind: PersistentVolumeClaim$' "$HELM_RENDER_DIR/all-features.yaml" + grep -q '^kind: HorizontalPodAutoscaler$' "$HELM_RENDER_DIR/all-features.yaml" + test "$(grep -c '^kind: Ingress$' "$HELM_RENDER_DIR/all-features.yaml")" -eq 2 + test "$(extract_kind_names "$HELM_RENDER_DIR/all-features.yaml" Ingress | sort -u | wc -l | tr -d ' ')" -eq 2 + extract_kind_names "$HELM_RENDER_DIR/all-features.yaml" Ingress | grep -qx 'iggy' + extract_kind_names "$HELM_RENDER_DIR/all-features.yaml" Ingress | grep -qx 'iggy-ui' + grep -q '^kind: ServiceMonitor$' "$HELM_RENDER_DIR/all-features.yaml" + + helm template iggy "$CHART_DIR" \ + --kube-version 1.18.0 \ + --api-versions networking.k8s.io/v1beta1 \ + --api-versions autoscaling/v2beta2 \ + --set autoscaling.enabled=true \ + --set autoscaling.targetCPUUtilizationPercentage=80 \ + --set server.ingress.enabled=true \ + --set ui.ingress.enabled=true \ + > "$HELM_RENDER_DIR/legacy-k8s-1.18.yaml" + test "$(grep -c '^apiVersion: networking.k8s.io/v1beta1$' "$HELM_RENDER_DIR/legacy-k8s-1.18.yaml")" -eq 2 + grep -q '^apiVersion: autoscaling/v2beta2$' "$HELM_RENDER_DIR/legacy-k8s-1.18.yaml" + + helm template iggy "$CHART_DIR" --set ui.enabled=false > "$HELM_RENDER_DIR/server-only.yaml" + test "$(grep -c '^kind: Deployment$' "$HELM_RENDER_DIR/server-only.yaml")" -eq 1 + test "$(grep -c '^kind: Service$' "$HELM_RENDER_DIR/server-only.yaml")" -eq 1 + + helm template iggy "$CHART_DIR" --set server.enabled=false > "$HELM_RENDER_DIR/ui-only.yaml" + test "$(grep -c '^kind: Deployment$' "$HELM_RENDER_DIR/ui-only.yaml")" -eq 1 + test "$(grep -c '^kind: Service$' "$HELM_RENDER_DIR/ui-only.yaml")" -eq 1 + + helm template iggy "$CHART_DIR" \ + --set server.users.root.createSecret=false \ + --set server.users.root.existingSecret.name=supersecret \ + > "$HELM_RENDER_DIR/existing-secret.yaml" + if grep -q 'root-credentials' "$HELM_RENDER_DIR/existing-secret.yaml"; then + echo "Error: existing-secret render should not include generated root credentials" >&2 + exit 1 + fi + grep -q 'name: supersecret' "$HELM_RENDER_DIR/existing-secret.yaml" +} + +smoke() { + require_command helm + require_command kubectl + require_command curl + require_command python3 + + local ui_image_tag + local server_ping_status + local ui_healthz_status + local leftover_resources + local helm_status + local smoke_values_file + + mkdir -p "$HELM_SMOKE_REPORT_DIR" + + if ! helm status "$HELM_SMOKE_RELEASE" -n "$HELM_SMOKE_NAMESPACE" >/dev/null 2>&1; then + leftover_resources="$( + kubectl -n "$HELM_SMOKE_NAMESPACE" get deployment,service,ingress,secret,serviceaccount \ + -l "app.kubernetes.io/instance=${HELM_SMOKE_RELEASE}" \ + -o name 2>/dev/null || true + )" + if [ -n "$leftover_resources" ]; then + cat >&2 <<EOF +Found leftover resources for a failed Helm smoke install in namespace '${HELM_SMOKE_NAMESPACE}'. +Run 'scripts/ci/test_helm.sh cleanup-smoke' once, then rerun the smoke test. +EOF + printf '%s\n' "$leftover_resources" >&2 + exit 1 + fi + fi + + ui_image_tag="$( + python3 - <<'PY' +import json + +with open("web/package.json", "r", encoding="utf-8") as f: + print(json.load(f)["version"]) +PY + )" + + echo "$ui_image_tag" > "$HELM_SMOKE_REPORT_DIR/ui-image-tag.txt" + + smoke_values_file="$(mktemp "${TMPDIR:-/tmp}/iggy-helm-smoke-values.XXXXXX.yaml")" + + cat > "$smoke_values_file" <<EOF +server: Review Comment: Smoke and validate can disagree on which UI image tag is “canonical”: at this revision `values.yaml` has `ui.image.tag: "edge"` while web/package.json has `"0.2.1-edge.1"`. I checked Docker Hub: `apache/iggy-web-ui:0.2.1-edge.1` exists, so smoke is not guaranteed to fail today. The critical angle is future CI breakage if npm version and published image tags drift apart worth aligning with `extract_values_tag` ui, `scripts/extract-version.sh`, or documenting why smoke must override the chart default. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
