Cleanup Old Review Namespaces #167
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Cleanup Old Review Namespaces | |
| on: | |
| schedule: | |
| - cron: "0 4 * * *" | |
| workflow_dispatch: {} | |
| concurrency: | |
| group: cleanup-old-review-namespaces | |
| cancel-in-progress: false | |
| jobs: | |
| cleanup_old_namespaces: | |
| name: "Delete review namespaces older than 3 weeks + DBs" | |
| runs-on: self-hosted | |
| env: | |
| KUBE_CONFIG_BASE64_DATA: ${{ secrets.TEST_CLUSTER_KUBE_CONFIG_BASE64_DATA }} | |
| TEST_CLUSTER_DB_CREDENTIALS: ${{ secrets.TEST_CLUSTER_DB_CREDENTIALS }} | |
| RETENTION_DAYS: "21" | |
| NS_PREFIX: "review-" | |
| IGNORE_NAMESPACES: ${{ vars.IGNORE_NAMESPACES }} | |
| steps: | |
| - name: Compute cutoff timestamp | |
| id: cutoff | |
| shell: bash | |
| run: | | |
| CUTOFF_TS=$(date -u -d "${RETENTION_DAYS} days ago" +%Y-%m-%dT%H:%M:%SZ) | |
| echo "CUTOFF_TS=$CUTOFF_TS" >> "$GITHUB_ENV" | |
| echo "Cutoff: $CUTOFF_TS" | |
| - name: List candidate namespaces older than cutoff | |
| id: list_old | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # Build ignore list from Actions Variable (supports commas or newlines) | |
| printf '%s\n' "${IGNORE_NAMESPACES:-}" \ | |
| | tr ',' '\n' \ | |
| | sed -E 's/^[[:space:]]+|[[:space:]]+$//g' \ | |
| | sed '/^[[:space:]]*$/d' \ | |
| | sort -u > ignore_ns.txt || true | |
| echo "Exact ignore entries:" | |
| if [[ -s ignore_ns.txt ]]; then cat ignore_ns.txt; else echo "(none)"; fi | |
| echo | |
| # Gather candidates older than cutoff | |
| kubectl --kubeconfig <(echo "$KUBE_CONFIG_BASE64_DATA" | base64 --decode) \ | |
| get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.creationTimestamp}{"\n"}{end}' \ | |
| | awk -v prefix="$NS_PREFIX" -v cutoff="$CUTOFF_TS" ' | |
| $1 ~ ("^" prefix) && $2 < cutoff { print $1 } | |
| ' \ | |
| | sort -u > candidates.txt || true | |
| echo "Candidates before ignores:" | |
| if [[ -s candidates.txt ]]; then cat candidates.txt; else echo "(none)"; fi | |
| echo | |
| # Drop exact matches from candidates | |
| : > ignored_exact.txt | |
| if [[ -s ignore_ns.txt && -s candidates.txt ]]; then | |
| comm -12 <(sort -u candidates.txt) <(sort -u ignore_ns.txt) > ignored_exact.txt || true | |
| grep -F -x -v -f ignore_ns.txt candidates.txt > tmp.txt || true | |
| mv tmp.txt candidates.txt || true | |
| fi | |
| echo "Ignored (exact matches):" | |
| if [[ -s ignored_exact.txt ]]; then cat ignored_exact.txt; else echo "(none)"; fi | |
| echo | |
| # Final list to delete | |
| mv candidates.txt namespaces_to_delete.txt || true | |
| echo "Namespaces to delete:" | |
| if [[ -s namespaces_to_delete.txt ]]; then cat namespaces_to_delete.txt; else echo "(none)"; fi | |
| NS_LIST=$(paste -sd, namespaces_to_delete.txt 2>/dev/null || true) | |
| echo "ns_list=$NS_LIST" >> "$GITHUB_OUTPUT" | |
| - name: Delete old namespaces | |
| if: steps.list_old.outputs.ns_list != '' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| while IFS= read -r ns; do | |
| [[ -z "$ns" ]] && continue | |
| echo "Deleting namespace: $ns" | |
| kubectl --kubeconfig <(echo "$KUBE_CONFIG_BASE64_DATA" | base64 --decode) \ | |
| delete namespace "$ns" --wait=false || { | |
| echo "Warning: failed to delete $ns (continuing)"; continue; | |
| } | |
| done < namespaces_to_delete.txt | |
| - name: Drop corresponding review databases | |
| if: steps.list_old.outputs.ns_list != '' | |
| shell: bash | |
| env: | |
| PSQL_DSN: ${{ secrets.TEST_CLUSTER_DB_CREDENTIALS }} | |
| run: | | |
| set -euo pipefail | |
| psql --version | |
| TMP_SQL="$(mktemp)" | |
| echo "-- Autogenerated DROP DATABASE statements" > "$TMP_SQL" | |
| while IFS= read -r ns; do | |
| [[ -z "$ns" ]] && continue | |
| raw="${ns#review-}" | |
| sanitized="$(echo "$raw" | sed 's/[^a-zA-Z0-9_-]//g; s/_/-/g' | cut -c -30)" | |
| if [[ -z "$sanitized" ]]; then | |
| echo "Skipping DB cleanup for $ns: empty derived prefix" | |
| continue | |
| fi | |
| echo "-- From namespace: $ns -> prefix: $sanitized" >> "$TMP_SQL" | |
| echo "WITH to_drop AS (SELECT datname FROM pg_database WHERE datname LIKE '${sanitized}%' AND datname NOT IN ('postgres','template0','template1')) SELECT 'DROP DATABASE ' || quote_ident(datname) || ' WITH (FORCE);' FROM to_drop;" >> "$TMP_SQL" | |
| done < namespaces_to_delete.txt | |
| echo "Planned DROP statements:" | |
| # Show the generated DROP statements (do not execute yet) | |
| psql -d "$PSQL_DSN" -v ON_ERROR_STOP=1 -Atqc "\i $TMP_SQL" | sed 's/^/ /' || true | |
| echo "Executing drops..." | |
| # Generate the DROP statements, then pipe them into a second psql for execution | |
| psql -d "$PSQL_DSN" -v ON_ERROR_STOP=1 -Atqc "\i $TMP_SQL" | psql -d "$PSQL_DSN" -v ON_ERROR_STOP=1 | |
| echo "Database cleanup finished." | |
| - name: Summary | |
| shell: bash | |
| run: | | |
| echo "Cleanup completed." | |
| echo "Retention: $RETENTION_DAYS days" | |
| echo "Cutoff: $CUTOFF_TS" |