chore(demo): zet de berichtenbox op de huidige main van de proeftuin #119
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
| # Ruimt de preview van een PR op: de `pr-<n>`-deployment in elk van de drie ZAD-projecten, de | ||
|
Check warning on line 1 in .github/workflows/cleanup-preview.yml
|
||
| # GitHub-omgeving en -deployments, de deploy-comment en de ghcr-versies van die PR. | ||
| # | ||
| # WAAROM EEN EIGEN WORKFLOW EN NIET IN deploy.yml: die filtert met `branches: [main]` op de | ||
| # DOELbranch, zodat een gestapelde PR geen preview krijgt en geen stale required checks | ||
| # achterlaat. Datzelfde filter sloeg ook toe op het sluiten: werd een PR ná zijn preview-deploy | ||
| # weggericht van main (base op een feature-branch gezet en daar gemerged), dan matchte het | ||
| # close-event het filter niet en bleef de hele preview staan. Teardown heeft dat filter niet | ||
| # nodig zolang geen van deze jobs een required context op main is — voeg ze daar dus niet aan toe: | ||
| # deze workflow draait alleen bij het sluiten van een PR, dus zo'n context verschijnt op een open PR | ||
| # nooit en zou elke merge voorgoed blokkeren. | ||
| # | ||
| # WAAROM `pull_request_target` EN NIET `pull_request`: dat laatste draait niet als de PR een | ||
| # merge-conflict heeft (GitHub voert de workflow uit op de merge-ref, en die is er dan niet) — en | ||
| # een conflicterende, verlaten stapel-PR is precies het geval dat je wél opgeruimd wilt hebben. | ||
| # Bijkomend: `pull_request_target` draait altijd de versie van dit bestand op de default branch, | ||
| # dus een branch kan zijn eigen teardown niet uitschakelen. De gebruikelijke waarschuwing bij | ||
| # `pull_request_target` (fork-code met secrets) geldt hier niet: er wordt geen enkele PR-inhoud | ||
| # uitgecheckt of uitgevoerd; deze workflow gebruikt alleen het PR-NUMMER. | ||
| # | ||
| # ELKE VERWIJDERING WAARVAN DE EXITCODE GEEN BEWIJS IS, WORDT NAGEMETEN. Annotaties (`::warning::`, | ||
| # `::error::`) laten een step niet falen: `zad-actions/cleanup` meldt een mislukte deployment-delete | ||
| # als `::warning::` ("cleanup failures are non-fatal" in zijn zad-common.sh). De job blijft dan | ||
| # groen, en zo groeide de lijst wees-deployments. Bij de omgeving speelt iets anders: GitHub | ||
| # antwoordt 404 zowel bij "bestond al niet" als bij te weinig rechten, dus ook een eigen DELETE zegt | ||
| # daar niets. Na die stappen staat daarom een controle die de resource ópvraagt en alleen een | ||
| # aantoonbare afwezigheid als "weg" accepteert. Die controles vragen de LIJST op in plaats van het | ||
| # item: een 404 op een item-URL betekent óók "onbekend project" of "verkeerd pad", dus dan zou een | ||
| # typefout of een API-versiewissel als "opgeruimd" lezen. Een lijst die met HTTP 200 terugkomt, | ||
| # aantoonbaar het juiste project beschrijft en onze naam niet bevat, is positief bewijs. | ||
| # | ||
| # De deploy-comment en de ghcr-versies verwijderen we zelf, en daar telt de exitcode van onze eigen | ||
| # delete wél: bij de comment breekt de stap direct af, bij de ghcr-sweep levert elke mislukte versie | ||
| # een waarschuwing en eindigt de job rood via de aggregatie onderaan die stap. | ||
| # | ||
| # DESTRUCTIEF: de deployment-DELETE draait Argo `prune` + `Delete` en, voor projecten met de | ||
| # `postgresql-database`-service, `database_cleanup` — de database van die deployment gaat mee. Voor | ||
| # een `pr-<n>`-preview is dat de bedoeling. Vandaar de rem op de handmatige route: het nummer moet | ||
| # een getal zijn (zodat er geen andere deployment-naam als `test` uit te construeren is) én bij een | ||
| # PR horen die daadwerkelijk gesloten is. | ||
| # | ||
| # Repo-secrets (Settings → Secrets and variables → Actions): | ||
| # ZAD_API_KEY_UITVRAAG — API-key project mpfb-8wh | ||
| # ZAD_API_KEY_PROFIEL — API-key project mpfpsm-lcl (externe-stubs) | ||
| # ZAD_API_KEY_MAGAZIJNEN — API-key project mpfm-w3h | ||
| # GH_ADMIN_TOKEN — PAT met Administration: write op deze repo (fine-grained), of een | ||
| # classic token met `repo`-scope én admin op de repo. GITHUB_TOKEN mag | ||
| # omgevingen niet verwijderen, en de naam mag niet met `GITHUB_` | ||
| # beginnen — die prefix is gereserveerd. | ||
| name: Cleanup ZAD preview | ||
| on: | ||
| # GEEN `branches`-filter: zie de toelichting bovenaan. Alleen `closed` — een gesloten PR is het | ||
| # enige moment waarop een preview overbodig wordt (heropenen deployt opnieuw via deploy.yml). | ||
| pull_request_target: | ||
| types: [closed] | ||
| workflow_dispatch: | ||
| inputs: | ||
| pr: | ||
| description: "PR-nummer waarvan de preview opgeruimd moet worden (bv. 195)" | ||
| required: true | ||
| type: string | ||
| # Least-privilege default; elke job die schrijft declareert zijn eigen scopes. | ||
| permissions: read-all | ||
| env: | ||
| OM_API: https://operations-manager.rig.prd1.gn2.quattro.rijksapps.nl/api | ||
| # De ZAD-delete duurt ~4 minuten (Argo prune + database_cleanup) en zit daarmee te dicht op de | ||
| # action-default van 300 s. 900 s is hetzelfde marge-getal als PREVIEW_TASK_TIMEOUT in deploy.yml; | ||
| # de job-timeout staat er ruim boven, zodat de CLI zijn eigen diagnose kan afmaken vóórdat GitHub | ||
| # de job afkapt. | ||
| CLEANUP_TASK_TIMEOUT: "900" | ||
| # Deze workflow zoekt de comment op deze tekst; deploy.yml geeft dezelfde tekst mee aan | ||
| # preview-comment.sh, dat hem plaatst en zelf dezelfde default draagt. Houd die drie gelijk — | ||
| # drift laat de comment stil achter op een gesloten PR. test-preview-comment.sh bewaakt dat. | ||
| COMMENT_HEADER: "## 🚀 Preview Deployment" | ||
| jobs: | ||
| # Eén plek waar het PR-nummer vandaan komt en gevalideerd wordt. | ||
| # | ||
| # Geen fork-uitsluiting: een fork-PR heeft nooit een preview (de deploy-action krijgt geen | ||
| # secrets), en opruimen van iets dat niet bestaat is een no-op die de controles hieronder als | ||
| # "afwezig" bevestigen. Een uitzondering zou juist een gat maken zodra er tóch iets van een | ||
| # fork-PR blijft staan. | ||
| doel: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| outputs: | ||
| pr: ${{ steps.doel.outputs.pr }} | ||
| owner: ${{ steps.doel.outputs.owner }} | ||
| steps: | ||
| - name: Bepaal en controleer het doel | ||
| id: doel | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| # Bij een close-event komt het nummer uit het event, bij een handmatige run uit de input. | ||
| PR_NUMMER: ${{ github.event.pull_request.number || inputs.pr }} | ||
| run: | | ||
| set -euo pipefail | ||
| # Het nummer bepaalt de naam van de te verwijderen deployment (`pr-<n>`) en de | ||
| # ghcr-tag-prefix. Een vrij tekstveld uit `workflow_dispatch` mag daar nooit | ||
| # ongefilterd in: alleen cijfers, zodat er geen andere deployment (bv. `test`) of | ||
| # bredere tag-prefix uit te construeren is. De lengtegrens houdt de normalisatie | ||
| # hieronder binnen het bereik van een 64-bits int; die wrapt anders stil. | ||
| case "$PR_NUMMER" in | ||
| ''|*[!0-9]*) | ||
| echo "::error::PR-nummer '$PR_NUMMER' is geen getal — opruimen afgebroken." | ||
| exit 1 | ||
| ;; | ||
| esac | ||
| if [ "${#PR_NUMMER}" -gt 7 ]; then | ||
| echo "::error::PR-nummer '$PR_NUMMER' heeft meer dan 7 cijfers — opruimen afgebroken." | ||
| exit 1 | ||
| fi | ||
| # Leidende nullen zijn een plausibele typefout bij handmatig invullen en ondubbelzinnig te | ||
| # herstellen, dus normaliseren in plaats van weigeren. Zonder dit zou `0195` de niet- | ||
| # bestaande deployment `pr-0195` opruimen: een groene run die niets doet. | ||
| nummer=$((10#$PR_NUMMER)) | ||
| if [ "$nummer" -eq 0 ]; then | ||
| echo "::error::PR-nummer 0 bestaat niet — opruimen afgebroken." | ||
| exit 1 | ||
| fi | ||
| # Een handmatige run is de inhaalslag voor een gemiste opruiming, dus bediend door iemand | ||
| # met haast. Een typefout zou anders drie keer "niet gevonden" opleveren: een volledig | ||
| # groene run die niets opruimde. Erger is een bestaand nummer van een ópen PR: dat sloopt | ||
| # een levende preview inclusief database. Bij een close-event zijn beide eisen per | ||
| # definitie al waar. | ||
| if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then | ||
| # `gh api` faalt hier op 404 én op 403/429/5xx; de melding trekt daarom geen conclusie | ||
| # over de oorzaak. Weigeren is in alle gevallen de veilige kant. | ||
| if ! staat=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$nummer" --jq .state); then | ||
| echo "::error::Kon niet bevestigen dat PR $nummer in $GITHUB_REPOSITORY bestaat (bestaat niet, of GitHub gaf een fout) — opruimen afgebroken." | ||
| exit 1 | ||
| fi | ||
| if [ "$staat" != "closed" ]; then | ||
| echo "::error::PR $nummer is nog $staat — opruimen zou een levende preview slopen, inclusief database. Sluit de PR eerst." | ||
| exit 1 | ||
| fi | ||
| fi | ||
| { | ||
| echo "pr=$nummer" | ||
| # GHCR eist lowercase in de owner. | ||
| echo "owner=${GITHUB_REPOSITORY_OWNER,,}" | ||
| } >> "$GITHUB_OUTPUT" | ||
| # De ZAD-teardown per project, als matrix: de drie projecten verschillen alleen in id, key en | ||
| # groepsnaam. Bewust géén drie losse jobs meer — de controlestap eronder was dan drie keer | ||
| # byte-identiek, en de eerste keer dat iemand hem op twee van de drie bijwerkt is de verificatie | ||
| # asymmetrisch zonder dat iets dat opmerkt. | ||
| # | ||
| # De GitHub-kant (omgeving, deployments, comment) hangt aan de PR en niet aan een project en | ||
| # staat daarom in een eigen job; alle `delete-*`-schakelaars van de action staan hier uit, zodat | ||
| # de action precies één ding doet. | ||
| cleanup-preview-zad: | ||
| needs: doel | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 20 | ||
| strategy: | ||
| # Eén falend project mag de teardown van de andere twee niet afbreken. | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| - naam: uitvraag | ||
| project: mpfb-8wh | ||
| key: ZAD_API_KEY_UITVRAAG | ||
| - naam: externe-stubs | ||
| project: mpfpsm-lcl | ||
| key: ZAD_API_KEY_PROFIEL | ||
| - naam: magazijnen | ||
| project: mpfm-w3h | ||
| key: ZAD_API_KEY_MAGAZIJNEN | ||
| # Zelfde groepsnaam als de deploy-job van dit project + deze PR in deploy.yml: opruimen mag een | ||
| # lopende deploy niet onder handen wegtrekken. Concurrency-groepen zijn repo-breed, dus de | ||
| # verhuizing naar een eigen workflow verandert daar niets aan — de namen moeten wél gelijk | ||
| # blijven aan `zad-<naam>-pr-<n>` daar. | ||
| concurrency: | ||
| group: zad-${{ matrix.naam }}-pr-${{ needs.doel.outputs.pr }} | ||
| cancel-in-progress: false | ||
| steps: | ||
| # Voor zad-deployment-bestaat.sh. Een `pull_request_target`-checkout pakt de basisbranch, niet | ||
| # de PR-code — dat is precies wat hier hoort, want het script draait met de projectsleutels. | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| persist-credentials: false | ||
| # Een PR die nooit een preview had, heeft hier niets op te ruimen. Een delete zou dan toch een | ||
| # taak in Operations Manager worden, en die vergrendelt het project: zo'n taak liet de uitrol | ||
| # van een andere PR in hetzelfde project al stranden op `superseded` | ||
| # (MinBZK/MijnOverheidZakelijk#1075). | ||
| # | ||
| # Alleen een aantoonbare afwezigheid slaat de delete over. Is de lijst niet te lezen, dan | ||
| # verwijdert de stap hieronder gewoon, en beslist de nameting erna. | ||
| - name: Bepaal of er een preview is | ||
| id: bestaat | ||
| env: | ||
| ZAD_API_KEY: ${{ secrets[matrix.key] }} | ||
| ZAD_API_URL: ${{ env.OM_API }} | ||
| PROJECT: ${{ matrix.project }} | ||
| DEPLOYMENT: pr-${{ needs.doel.outputs.pr }} | ||
| run: | | ||
| rc=0 | ||
| .github/scripts/zad-deployment-bestaat.sh "$PROJECT" "$DEPLOYMENT" || rc=$? | ||
| case "$rc" in | ||
| 0) echo "bestaat=true" >> "$GITHUB_OUTPUT" ;; | ||
| 1) | ||
| echo "::notice::$DEPLOYMENT bestaat niet in $PROJECT; er is niets op te ruimen." | ||
| echo "bestaat=false" >> "$GITHUB_OUTPUT" | ||
| ;; | ||
| *) echo "bestaat=onbekend" >> "$GITHUB_OUTPUT" ;; | ||
| esac | ||
| # Geen aparte stap voor de cross-domain-regels: die staan in de projectspec ónder de deployment | ||
| # (`deployments[].services`), en de delete haalt dat hele blok weg. Ze eerst leegmaken liet | ||
| # Operations Manager het héle project opnieuw verwerken en op elke deployment wachten — minuten | ||
| # werk in een vergrendeld project, voor regels die een stap later toch verdwijnen. | ||
| - name: Verwijder de ZAD-deployment | ||
| # `!= 'false'` en niet `== 'true'`: valt de stap hierboven om, dan is de output leeg, en dan | ||
| # hoort er gewoon verwijderd te worden. | ||
| if: ${{ !cancelled() && steps.bestaat.outputs.bestaat != 'false' }} | ||
| uses: RijksICTGilde/zad-actions/cleanup@4ac541a57542a22bde95b28f3ace4ee3123acd7a # v4 | ||
| with: | ||
| api-key: ${{ secrets[matrix.key] }} | ||
| project-id: ${{ matrix.project }} | ||
| deployment-name: pr-${{ needs.doel.outputs.pr }} | ||
| task-timeout: ${{ env.CLEANUP_TASK_TIMEOUT }} | ||
| delete-github-env: "false" | ||
| delete-github-deployments: "false" | ||
| delete-container: "false" | ||
| delete-pr-comment: "false" | ||
| # Teardown mag niet afhangen van wie de PR opende: een preview van een bot-PR moet net zo | ||
| # goed weg, en op een deployment die niet bestaat is de delete een no-op. Zonder deze | ||
| # uitzetting slaat de action bij een bot-auteur álles over en rapporteert groen. | ||
| skip-bot-prs: "false" | ||
| - name: Controleer dat de ZAD-deployment weg is | ||
| # Ook draaien als de stap hierboven faalde: dan is juist de vraag wat er is blijven staan. | ||
| if: ${{ !cancelled() }} | ||
| env: | ||
| ZAD_API_KEY: ${{ secrets[matrix.key] }} | ||
| ZAD_API_URL: ${{ env.OM_API }} | ||
| PROJECT: ${{ matrix.project }} | ||
| DEPLOYMENT: pr-${{ needs.doel.outputs.pr }} | ||
| run: | | ||
| rc=0 | ||
| .github/scripts/zad-deployment-bestaat.sh "$PROJECT" "$DEPLOYMENT" || rc=$? | ||
| case "$rc" in | ||
| 1) echo "$DEPLOYMENT staat niet meer in de deploymentlijst van $PROJECT." ;; | ||
| 0) | ||
| # De DELETE zit bij OM bewust op het korte pad, zónder `/deployments/`. | ||
| echo "::error::$DEPLOYMENT bestaat nog in $PROJECT. Herdraai deze workflow via workflow_dispatch; blijft hij staan, verwijder hem dan met DELETE $ZAD_API_URL/v2/projects/$PROJECT/$DEPLOYMENT." | ||
| exit 1 | ||
| ;; | ||
| *) | ||
| echo "::error::De deploymentlijst van $PROJECT is niet te lezen — de teardown van $DEPLOYMENT is NIET geverifieerd." | ||
| exit 1 | ||
| ;; | ||
| esac | ||
| # De GitHub-kant doen we zelf in plaats van via de action: die verwijdert deployments zonder te | ||
| # pagineren (de GitHub-API levert er standaard 30 per pagina, dus vanaf de 31e blijft er stil een | ||
| # staan), meldt een mislukte deployment-delete met een gewone `echo` zónder annotatie, en kan bij | ||
| # de omgeving een 404 wegens ontbrekende rechten niet onderscheiden van "bestond al niet". De | ||
| # comment-opruiming van de action draait hier zelfs nooit: die hangt aan | ||
| # `github.event_name == 'pull_request'` en deze workflow triggert op `pull_request_target` en | ||
| # `workflow_dispatch`. Zelf verwijderen kost een paar regels en levert wél een exitcode én een | ||
| # nameting op. | ||
| cleanup-preview-github: | ||
| # Ná de ZAD-teardown en niet parallel eraan, hoewel deze job niets van die uitkomst nodig heeft: | ||
| # hij deelt de groep `zad-uitvraag-pr-<n>` met de matrix-leg `uitvraag`. Van twee jobs die in | ||
| # dezelfde groep staan te wachten, annuleert GitHub de eerst-wachtende zodra er een nieuwe | ||
| # bijkomt — `cancel-in-progress: false` beschermt alleen de lópende job. Parallel gequeued zou | ||
| # een bezette groep (een lopende deploy van dezelfde PR) dus stil één van de twee teardowns | ||
| # opeten. `!cancelled()` zodat een mislukte ZAD-teardown de GitHub-kant niet overslaat; de eis | ||
| # op `doel` blijft er expliciet bij staan, want een statusfunctie in `if:` zet de impliciete | ||
| # needs-poort volledig uit — zonder die eis draait deze job na een afgekeurd PR-nummer met een | ||
| # leeg nummer en meet hij de afwezigheid van `pr-`. | ||
| needs: [doel, cleanup-preview-zad] | ||
| if: ${{ !cancelled() && needs.doel.result == 'success' }} | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 15 | ||
| # De omgeving hoort bij de uitvraag-deploy (die draagt `environment: pr-<n>`), dus dezelfde | ||
| # groep als die deploy: hem weghalen tijdens een lopende deploy zou die deploy onderuit halen. | ||
| concurrency: | ||
| group: zad-uitvraag-pr-${{ needs.doel.outputs.pr }} | ||
| cancel-in-progress: false | ||
| permissions: | ||
| contents: read | ||
| deployments: write | ||
| pull-requests: write | ||
| steps: | ||
| # Als eerste, want deze stap heeft het admin-token niet nodig: een ontbrekend PAT mag de | ||
| # comment- en deployment-opruiming niet tegenhouden. | ||
| - name: Verwijder de deploy-comment | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| PR_NUMMER: ${{ needs.doel.outputs.pr }} | ||
| run: | | ||
| set -euo pipefail | ||
| # Bretels naast de `needs.doel`-eis: met een leeg nummer wordt de URL `/issues//comments`, | ||
| # die als 404 uit `gh` komt nadat de stap al is begonnen. Hier breekt hij af met een | ||
| # leesbare reden, vóór de lus die comments verwijdert. | ||
| if [ -z "$PR_NUMMER" ]; then | ||
| echo "::error::Leeg PR-nummer — opruimen afgebroken." | ||
| exit 1 | ||
| fi | ||
| # Pagineren, want op een PR met veel comments valt de deploy-comment buiten de eerste | ||
| # pagina en zou hij op de gesloten PR blijven staan. | ||
| # | ||
| # De header via `jq --arg` i.p.v. in het programma interpoleren: `gh api --jq` kent geen | ||
| # `--arg`, en tekst in een jq-programma plakken breekt zodra er een quote of backslash in | ||
| # staat. Een mislukte read komt hier hoe dan ook door: `pipefail` staat aan en er is geen | ||
| # `2>/dev/null`. | ||
| ids=$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMMER/comments?per_page=100" --paginate \ | ||
| | jq -r --arg h "$COMMENT_HEADER" '.[] | select(.body | startswith($h)) | .id') | ||
| if [ -z "$ids" ]; then | ||
| echo "Geen deploy-comment op PR $PR_NUMMER." | ||
| exit 0 | ||
| fi | ||
| for id in $ids; do | ||
| gh api "repos/$GITHUB_REPOSITORY/issues/comments/$id" -X DELETE | ||
| echo "Comment $id verwijderd." | ||
| done | ||
| - name: Verwijder de GitHub-deployments en controleer dat ze weg zijn | ||
| if: ${{ !cancelled() }} | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| OMGEVING: pr-${{ needs.doel.outputs.pr }} | ||
| run: | | ||
| set -euo pipefail | ||
| # Een deployment-record per deploy-run, dus per push: pagineren is nodig, anders blijft er | ||
| # vanaf de 31e stilletjes een staan. Geen `2>/dev/null`: een mislukte read moet deze job | ||
| # rood maken en niet als "geen deployments" lezen. | ||
| ids=$(gh api "repos/$GITHUB_REPOSITORY/deployments?environment=$OMGEVING&per_page=100" \ | ||
| --paginate --jq '.[].id') | ||
| echo "Gevonden: $(printf '%s' "${ids:-}" | grep -c . || true) deployment-records op $OMGEVING." | ||
| verwijderd=0 | ||
| for id in ${ids:-}; do | ||
| # Een deployment is pas verwijderbaar als hij inactief is. | ||
| gh api "repos/$GITHUB_REPOSITORY/deployments/$id/statuses" -X POST -f state=inactive --silent | ||
| gh api "repos/$GITHUB_REPOSITORY/deployments/$id" -X DELETE --silent | ||
| verwijderd=$((verwijderd + 1)) | ||
| done | ||
| echo "Verwijderd: $verwijderd." | ||
| # Eerst lezen (een leesfout is dan rood via `set -e`), dan tellen met `grep -c`: `wc -l` | ||
| # telt een laatste regel zonder afsluitende newline niet mee en zou één resterend record | ||
| # als "weg" lezen. | ||
| rest=$(gh api "repos/$GITHUB_REPOSITORY/deployments?environment=$OMGEVING&per_page=100" \ | ||
| --paginate --jq '.[].id') | ||
| resterend=$(printf '%s' "$rest" | grep -c . || true) | ||
| if [ "$resterend" -ne 0 ]; then | ||
| echo "::error::Er staan nog $resterend GitHub-deployments op omgeving $OMGEVING." | ||
| exit 1 | ||
| fi | ||
| echo "Geen GitHub-deployments meer op $OMGEVING." | ||
| - name: Verwijder de GitHub-omgeving en controleer dat hij weg is | ||
| if: ${{ !cancelled() }} | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| ADMIN_TOKEN: ${{ secrets.GH_ADMIN_TOKEN }} | ||
| OMGEVING: pr-${{ needs.doel.outputs.pr }} | ||
| run: | | ||
| set -euo pipefail | ||
| if [ -z "$ADMIN_TOKEN" ]; then | ||
| echo "::error::GH_ADMIN_TOKEN ontbreekt — de omgeving $OMGEVING kan niet verwijderd worden. Zet de repo-secret (PAT met Administration: write)." | ||
| exit 1 | ||
| fi | ||
| code=$(curl -sS --max-time 30 --retry 3 --retry-connrefused -o /dev/null \ | ||
| -w '%{http_code}' -X DELETE \ | ||
| -H "Authorization: Bearer $ADMIN_TOKEN" \ | ||
| -H 'Accept: application/vnd.github+json' \ | ||
| -H 'X-GitHub-Api-Version: 2022-11-28' \ | ||
| "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/environments/$OMGEVING") || code=000 | ||
| echo "DELETE omgeving $OMGEVING gaf HTTP $code." | ||
| # De statuscode van die DELETE is geen bewijs: GitHub antwoordt met 404 wanneer het token | ||
| # de resource niet mag zien, wat niet te onderscheiden is van "bestond al niet". Lezen mag | ||
| # wél met het automatische token — alleen verwijderen vraagt Administration: write — dus | ||
| # de lijst is hier de betrouwbare toets. | ||
| # | ||
| # Eerst lezen in een variabele, dan beoordelen. In één pipeline (`gh ... | grep -qxF`) is de | ||
| # exitcode dubbelzinnig: een mislukte read geeft lege uitvoer en `grep` exit 1, wat als | ||
| # "afwezig" zou lezen, en `grep -q` sluit de pipe bij de eerste match — heeft `gh` dan nog | ||
| # uitvoer te schrijven (een lijst die niet in de pipe-buffer past), dan sterft het op | ||
| # SIGPIPE en levert `pipefail` diezelfde false-negatieve uitkomst. | ||
| namen=$(gh api "repos/$GITHUB_REPOSITORY/environments?per_page=100" --paginate \ | ||
| --jq '.environments[].name') | ||
| # Deze repo heeft altijd `test` en `github-pages`; een lege lijst is dus geen leeg | ||
| # antwoord maar een kapotte meting. | ||
| if [ -z "$namen" ]; then | ||
| echo "::error::Lege omgevingslijst — de teardown van $OMGEVING is NIET geverifieerd." | ||
| exit 1 | ||
| fi | ||
| # Drie takken i.p.v. een `if`: daarin zou `grep` exit 2 (een echte fout) als "niet | ||
| # gevonden" doorgaan. | ||
| status=0 | ||
| grep -qxF "$OMGEVING" <<<"$namen" || status=$? | ||
| case "$status" in | ||
| 0) | ||
| echo "::error::Omgeving $OMGEVING bestaat nog (DELETE gaf HTTP $code). Geef GH_ADMIN_TOKEN 'Administration: write' op deze repo (fine-grained) of 'repo'-scope plus repo-admin (classic) en herdraai deze workflow via workflow_dispatch." | ||
| exit 1 | ||
| ;; | ||
| 1) echo "Omgeving $OMGEVING staat niet meer in de lijst." ;; | ||
| *) | ||
| echo "::error::Kon de omgevingslijst niet beoordelen (grep gaf $status) — de teardown van $OMGEVING is NIET geverifieerd." | ||
| exit 1 | ||
| ;; | ||
| esac | ||
| # Ruimt alle GETAGDE versies van de PR op: één ghcr-versie per commit, niet alleen de laatste. | ||
| # Ongetagde child-manifests (multi-arch, attestations) vallen per definitie buiten een tag-prefix | ||
| # en blijven staan; die horen bij het opruimen op termijn. De | ||
| # tags van deze PR zijn te herkennen aan de prefix `pr-<n>-`; het afsluitende koppelteken | ||
| # is essentieel, anders zou het sluiten van PR 16 de images van PR 168 meenemen. | ||
| # | ||
| # Bewust niet de `delete-container`-stap van zad-actions: die verwijdert één exacte tag, wat | ||
| # met unieke tags per commit alle tussenversies zou laten staan. Ook bewust géén | ||
| # package-brede fallback bij "cannot delete the last tagged version" (die de action wél | ||
| # heeft): dat zou een preview-cleanup het hele package laten verwijderen inclusief de | ||
| # main-images. De `main-<sha7>`-tags zorgen ervoor dat die situatie zich niet voordoet. | ||
| # | ||
| # RACE bij sluiten tijdens een lopende build: deze job serialiseert niet tegen de build-jobs in | ||
| # deploy.yml (die hebben elk hun eigen groep, anders blokkeren ze elkaar). Sluit iemand de PR | ||
| # vlak na een push, dan kan de sweep vóór die build af zijn en blijft één ghcr-versie achter. | ||
| # Gevolg is één weesversie, geen kapotte deploy; herdraaien via workflow_dispatch ruimt hem op. | ||
| cleanup-preview-images: | ||
| needs: doel | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 20 | ||
| permissions: | ||
| packages: write | ||
| steps: | ||
| - name: Verwijder de ghcr-versies van deze PR | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| ORG: ${{ needs.doel.outputs.owner }} | ||
| PR: ${{ needs.doel.outputs.pr }} | ||
| PREFIX: pr-${{ needs.doel.outputs.pr }}- | ||
| # Overgangsgeval: PR's uit de tijd vóór de per-commit-tags hebben nog een kale | ||
| # `pr-<n>`-versie in ghcr staan. Die valt niet onder de prefix, dus expliciet meenemen. | ||
| LEGACY_TAG: pr-${{ needs.doel.outputs.pr }} | ||
| run: | | ||
| set -euo pipefail | ||
| fail=0 | ||
| blijvers="" | ||
| # Expliciete lijst, bewust niet afgeleid uit alle org-packages: de prefix `pr-<n>-` | ||
| # is niet uniek binnen de organisatie, dus een brede sweep zou images van een | ||
| # gelijkgenummerde PR in een andere repo kunnen wissen. Houd deze lijst in sync met | ||
| # de `build`-matrix, `build-externe-stubs`, `build-contract-bootstrap` en | ||
| # `build-demo-images` in deploy.yml; een vergeten package laat weesversies achter. | ||
| # | ||
| # Alleen onze eigen images: `proeftuin` draait op een image uit MinBZK/moza-poc, dat een | ||
| # ander project bouwt en opruimt. Dat hier meenemen zou hun tags wissen. | ||
| for pkg in fbs-berichtenuitvraag fbs-berichtenmagazijn fbs-externe-stubs fbs-demo-profiel fbs-fsc-contract-bootstrap fbs-demo-console fbs-demo-personas fbs-magazijn-simulator; do | ||
| if ! versions=$(gh api --paginate "orgs/$ORG/packages/container/$pkg/versions?per_page=100"); then | ||
| echo "::warning::Kon de versies van $pkg niet ophalen — images van deze PR blijven mogelijk staan." | ||
| fail=1 | ||
| continue | ||
| fi | ||
| # Exit 0 met een lege of niet-array body (204, proxy-pagina) zou anders als "geen | ||
| # versies van deze PR" lezen in plaats van als mislukte meting. | ||
| if ! printf '%s' "$versions" | jq -e 'type == "array" and length > 0' >/dev/null; then | ||
| echo "::warning::Versielijst van $pkg is leeg of geen array — images van deze PR blijven mogelijk staan." | ||
| fail=1 | ||
| continue | ||
| fi | ||
| # De filter in een losse `jq` i.p.v. via `--jq`, omdat `gh api --jq` geen `--arg` kent | ||
| # en de prefix data hoort te zijn, geen code. `// []` voor een versie zonder tags: | ||
| # anders faalt jq op zo'n versie en blijven de PR-versies van dít package staan, met | ||
| # alleen een waarschuwing. | ||
| if ! ids=$(printf '%s' "$versions" | jq -r --arg p "$PREFIX" --arg legacy "$LEGACY_TAG" \ | ||
| '.[] | select((.metadata.container.tags // []) | any(startswith($p) or . == $legacy)) | .id'); then | ||
| echo "::warning::Kon de versies van $pkg niet beoordelen — images van deze PR blijven mogelijk staan." | ||
| fail=1 | ||
| continue | ||
| fi | ||
| if [ -z "$ids" ]; then | ||
| echo "$pkg: geen versies met tag $LEGACY_TAG of prefix $PREFIX." | ||
| continue | ||
| fi | ||
| for id in $ids; do | ||
| # De foutmelding is het enige onderscheid tussen de weigeringen; ze komen allemaal | ||
| # als 4xx terug, dus de exitcode zegt niet wat er aan de hand is. Hem meenemen in de | ||
| # waarschuwing scheelt bovendien een duik in het ruwe joblog. | ||
| if err=$(gh api "orgs/$ORG/packages/container/$pkg/versions/$id" -X DELETE 2>&1); then | ||
| echo "$pkg: versie $id verwijderd." | ||
| elif printf '%s' "$err" | grep -qF 'downloads cannot be deleted'; then | ||
| # GitHub weigert een publieke versie te verwijderen zodra zijn downloadteller boven | ||
| # de 5000 uitkomt. Die teller kan er ver naast zitten — waargenomen op een versie | ||
| # die geen enkele afnemer had en waarvan de package-pagina 0 downloads meldde — en | ||
| # corrigeert zichzelf: dezelfde verwijdering slaagde een dag later wél. Herdraaien | ||
| # is dus de oplossing, en daarom blijft dit een rode job: groen melden zou de enige | ||
| # aanleiding wegnemen om dat te doen. | ||
| echo "::warning::$pkg: versie $id niet verwijderd — GitHub's downloadteller blokkeert het. Die teller loopt achter; herdraai deze workflow later." | ||
| blijvers="${blijvers}| \`$pkg\` | [$id](https://github.com/orgs/$ORG/packages/container/$pkg/$id) |"$'\n' | ||
| fail=1 | ||
| else | ||
| echo "::warning::$pkg: versie $id niet verwijderd: $err" | ||
| fail=1 | ||
| fi | ||
| done | ||
| done | ||
| # De teller-weigeringen bij elkaar in de job-samenvatting, met het commando dat ze | ||
| # opruimt: uit het joblog alleen valt niet af te lezen dat wachten-en-herdraaien hier de | ||
| # hele remedie is. | ||
| if [ -n "$blijvers" ]; then | ||
| { | ||
| echo "### Achtergebleven ghcr-versies" | ||
| echo | ||
| echo "GitHub weigerde deze versies te verwijderen omdat zijn downloadteller boven de" | ||
| echo "5000 staat. Die teller loopt achter en corrigeert zichzelf; herdraai deze" | ||
| echo "workflow later opnieuw:" | ||
| echo | ||
| echo '```' | ||
| echo "gh workflow run cleanup-preview.yml -f pr=$PR" | ||
| echo '```' | ||
| echo | ||
| echo "| package | versie |" | ||
| echo "| --- | --- |" | ||
| printf '%s' "$blijvers" | ||
| } >> "$GITHUB_STEP_SUMMARY" | ||
| fi | ||
| # Achterblijvende images zijn geen reden om de PR-afsluiting te blokkeren, maar het | ||
| # moet wel als rode job zichtbaar zijn — anders groeit de ghcr-berg ongemerkt door. | ||
| exit "$fail" | ||