feat/postit-acl #32

Merged
notazof merged 33 commits from feat/postit-acl into release/1.0.7 2026-08-18 16:14:32 +01:00
Showing only changes of commit c2d55317ab - Show all commits

ci(forgejo): build JSON bodies with jq instead of hand-rolled sed

L'image runner pazof/yavsc-build-env installe jq (>= 1.7) à partir
de debian12-dotnet10-android36-v2 (Dockerfile du repo
dotnet-android-build-image, commit e06f096 "adds jq"). On en
profite pour supprimer json_escape et json_field à base de sed,
qui étaient fragiles :

  * sed est greedy par défaut : sur du JSON minifié d'une seule
    ligne (ce que renvoie l'API Forgejo de cette instance pour
    /releases/tags/<tag>), la regex s/.*"id".../\1/p attrape la
    DERNIÈRE occurrence de "id":<digits> sur la ligne, qui est
    l'id de l'auteur de la release (1, premier user du repo),
    pas l'id de la release (10706).
  * Le head -3 ajouté en PR #30 ne tient pas sur du JSON minifié :
    il n'isole rien et le sed greedy continue à capturer
    l'id de l'auteur.
  * PATCH /releases/1 tombait alors en 404 "The target couldn't
    be found" (cf. run échoué du 2026-08-17 04:05 sur le tag
    1.0.6).

jq résout les deux problèmes en une fois :
  * jq -r '.id' retourne le champ id racine, pas l'id imbriqué
    dans author.
  * jq -n --arg body "$RELEASE_BODY" '{body: $body, prerelease:
    $prerelease}' construit un body JSON proprement échappé
    (backslashes, guillemets, newlines, caractères de contrôle
    Unicode) sans avoir à le reproduire à la main.

Effet de bord : les bodies PATCH et POST sont écrits dans
/tmp/patch.json et /tmp/post.json puis passés à curl via
--data-binary @<file> au lieu d'une variable shell. Plus de
problème de quoting en chaîne shell, plus de collision avec
les espaces ou les caractères spéciaux du body.

Pré-requis côté runner : image pazof/yavsc-build-env:debian12-
dotnet10-android36-v2 (avec jq) + maj du label correspondant
dans la config du runner Forgejo.
Paul Schneider 2026-08-17 04:35:00 +01:00
Signed by: notazof
GPG key ID: 1DD5D838E5343B06

View file

@ -15,10 +15,16 @@
# the secret table). Bumping to Forgejo v16 should fix it; until then, # the secret table). Bumping to Forgejo v16 should fix it; until then,
# the runner-provided token keeps the workflow operational. # the runner-provided token keeps the workflow operational.
# #
# Why bash + curl, no third-party actions: the runner's docker label # Why bash + jq + curl, no third-party actions: the runner's docker
# points at pazof/yavsc-build-env, a Debian image without Node.js. Any # label points at pazof/yavsc-build-env, a Debian image with jq but
# action like actions/checkout, rasterstate/forgejo-release-action, etc. # without Node.js or python3. Any action like actions/checkout,
# fails with "executable file not found in $PATH". Same constraint as # rasterstate/forgejo-release-action, etc. fails with "executable
# file not found in $PATH". jq is shipped in the image from
# debian12-dotnet10-android36-v2 onward; earlier tags fell back to
# hand-rolled JSON building via sed, which was fragile (cf. PR #30:
# sed greedy + head -3 still matched author.id instead of the
# release id on the minified JSON this instance returns, PATCH
# /releases/1 → 404). Same constraint as
# .forgejo/workflows/buildAndTest.yml. # .forgejo/workflows/buildAndTest.yml.
# #
# This workflow complements .github/workflows/docker-publish-android.yml # This workflow complements .github/workflows/docker-publish-android.yml
@ -222,31 +228,22 @@ jobs:
API_BASE="${GITHUB_API_URL%/}" API_BASE="${GITHUB_API_URL%/}"
API_BASE="${API_BASE%/api/v1}" API_BASE="${API_BASE%/api/v1}"
# Pas de python3, pas de jq dans l'image runner. On génère # Construction des bodies JSON et extraction de champs via
# le JSON à la main : escaping minimal des caractères # jq. L'image runner pazof/yavsc-build-env installe jq
# spéciaux JSON dans les chaînes (\\, \", \n, \r, \t). # (>= 1.7) depuis debian12-dotnet10-android36-v2. La
# Suffisant pour un CHANGELOG.md bien formé. # chaîne de construction --arg/--argjson garantit un
json_escape() { # escaping correct (backslashes, guillemets, newlines,
local s="$1" # caractères de contrôle Unicode) sans avoir à le
s="${s//\\/\\\\}" # reproduire à la main.
s="${s//\"/\\\"}" #
s="${s//$'\n'/\\n}" # json_escape et json_field à base de sed ont vécu : le
s="${s//$'\r'/\\r}" # sed greedy matche la dernière occurrence d'un champ
s="${s//$'\t'/\\t}" # dans la ligne, et l'API renvoie sur cette instance un
printf '%s' "$s" # JSON minifié d'une seule ligne où l'id de l'auteur
} # (1, premier user du repo) suit l'id de la release
# (10706). PATCH /releases/<sed-captured-id> tombait
# Extraction d'un champ JSON scalaire de premier niveau depuis un # alors en 404 "The target couldn't be found". jq
# fichier. On ne lit que les premières lignes pour éviter de # résout les deux problèmes en une fois.
# matcher un champ homonyme dans un objet imbriqué (par ex.
# le champ "id" de l'auteur d'une release Forgejo, qui vaut
# typiquement 1 pour le premier user du repo). Sans cette
# restriction, le PATCH sur /releases/<id extrait> tombe en
# 404 "The target couldn't be found".
json_field() {
local file="$1" field="$2"
head -3 "$file" | sed -n "s/.*\"$field\"[[:space:]]*:[[:space:]]*\"\?\([0-9][0-9]*\)\"\?.*/\1/p" | head -1
}
# 1. Vérifier si la release existe déjà pour ce tag. # 1. Vérifier si la release existe déjà pour ce tag.
echo "::group::Check existing release for tag $TAG" echo "::group::Check existing release for tag $TAG"
@ -257,7 +254,7 @@ jobs:
echo "GET releases/tags/$TAG -> HTTP $HTTP" echo "GET releases/tags/$TAG -> HTTP $HTTP"
EXISTING_ID="" EXISTING_ID=""
if [[ "$HTTP" == "200" ]]; then if [[ "$HTTP" == "200" ]]; then
EXISTING_ID=$(json_field /tmp/existing.json id) EXISTING_ID=$(jq -r '.id // empty' /tmp/existing.json)
echo "Existing release id: ${EXISTING_ID:-none}" echo "Existing release id: ${EXISTING_ID:-none}"
fi fi
echo "::endgroup::" echo "::endgroup::"
@ -265,30 +262,35 @@ jobs:
# 2. Créer ou mettre à jour la release. # 2. Créer ou mettre à jour la release.
if [[ -n "$EXISTING_ID" ]]; then if [[ -n "$EXISTING_ID" ]]; then
echo "::group::Update release id=$EXISTING_ID" echo "::group::Update release id=$EXISTING_ID"
BODY=$(printf '{"body":"%s","prerelease":%s}' \ jq -n \
"$(json_escape "$RELEASE_BODY")" "$IS_PRERELEASE") --arg body "$RELEASE_BODY" \
--argjson prerelease "$IS_PRERELEASE" \
'{body: $body, prerelease: $prerelease}' \
> /tmp/patch.json
HTTP=$(curl -sS -o /tmp/release.json -w '%{http_code}' \ HTTP=$(curl -sS -o /tmp/release.json -w '%{http_code}' \
-X PATCH \ -X PATCH \
-H "Authorization: token $GITHUB_TOKEN" \ -H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "Accept: application/json" \ -H "Accept: application/json" \
--data-binary "$BODY" \ --data-binary @/tmp/patch.json \
"$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/$EXISTING_ID") "$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/$EXISTING_ID")
echo "PATCH release -> HTTP $HTTP" echo "PATCH release -> HTTP $HTTP"
echo "::endgroup::" echo "::endgroup::"
else else
echo "::group::Create release" echo "::group::Create release"
BODY=$(printf '{"tag_name":"%s","name":"%s","body":"%s","prerelease":%s}' \ jq -n \
"$(json_escape "$TAG")" \ --arg tag "$TAG" \
"$(json_escape "$TAG")" \ --arg name "$TAG" \
"$(json_escape "$RELEASE_BODY")" \ --arg body "$RELEASE_BODY" \
"$IS_PRERELEASE") --argjson prerelease "$IS_PRERELEASE" \
'{tag_name: $tag, name: $name, body: $body, prerelease: $prerelease}' \
> /tmp/post.json
HTTP=$(curl -sS -o /tmp/release.json -w '%{http_code}' \ HTTP=$(curl -sS -o /tmp/release.json -w '%{http_code}' \
-X POST \ -X POST \
-H "Authorization: token $GITHUB_TOKEN" \ -H "Authorization: token $GITHUB_TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "Accept: application/json" \ -H "Accept: application/json" \
--data-binary "$BODY" \ --data-binary @/tmp/post.json \
"$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases") "$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases")
echo "POST release -> HTTP $HTTP" echo "POST release -> HTTP $HTTP"
echo "::endgroup::" echo "::endgroup::"
@ -300,7 +302,7 @@ jobs:
exit 1 exit 1
fi fi
RELEASE_ID=$(json_field /tmp/release.json id) RELEASE_ID=$(jq -r '.id' /tmp/release.json)
echo "Release id=$RELEASE_ID" echo "Release id=$RELEASE_ID"
# 3. Upload l'APK en asset. # 3. Upload l'APK en asset.