👷 L'action build-push s'installe dans un dépôt public (raggaroth-factory/RF_CICD#3)
Premier vrai run, PRJ002-ScrumBoard-Backend run 31 : act_runner clone les actions de « uses: » sans s'authentifier, et RF_CICD étant privé, Gitea répond « repository not found ». L'action déménage ici, dans l'organisation « public » — un dépôt public dans « raggaroth-factory » resterait invisible, l'organisation elle-même l'étant aux anonymes. Reprise de RF_CICD/actions/build-push telle qu'éprouvée, moins ce qui décrivait l'infrastructure : le tag de l'image de job et le chemin de son Containerfile quittent le message d'erreur, et les fichiers temporaires perdent leur préfixe rf-cicd. Le README pose la règle qui en découle : rien de propre à une machine, un compte ou un chemin n'entre dans ce dépôt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VyxLNGHojdXeRMQgWFgzRV
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
# RF_ACTIONS
|
||||
|
||||
Actions Gitea partagées par les projets de Raggaroth Factory.
|
||||
|
||||
Ce dépôt est **public parce qu'il doit l'être** : `act_runner` télécharge les
|
||||
actions appelées par `uses:` sans s'authentifier, et un dépôt privé lui répond
|
||||
« repository not found ». Il ne contient donc que le code des actions — rien
|
||||
qui décrive l'infrastructure qui les exécute. Cette description, les décisions
|
||||
et la procédure d'installation du runner vivent dans RF_CICD, qui reste privé.
|
||||
|
||||
**Règle qui en découle** : rien de propre à une machine, un compte ou un chemin
|
||||
de l'infrastructure n'entre ici. Et aucun secret, jamais — ils arrivent par les
|
||||
entrées des actions, depuis les secrets du dépôt appelant.
|
||||
|
||||
## `build-push`
|
||||
|
||||
Construit une image avec **buildah**, sans démon ni socket, et la publie dans
|
||||
le registre de Gitea sous les tags de sa filière. **Ne déploie rien.**
|
||||
|
||||
```yaml
|
||||
- uses: https://gitea.raggaroth-factory.fr/public/RF_ACTIONS/build-push@main
|
||||
with:
|
||||
image: gitea.raggaroth-factory.fr/raggaroth-factory/<nom-de-l-image>
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
registry_user: ${{ secrets.REGISTRY_USER }}
|
||||
registry_password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
```
|
||||
|
||||
| Déclencheur | Tags publiés |
|
||||
|---|---|
|
||||
| poussée sur `develop` | `:dev` et `:<sha court, 12 caractères>` |
|
||||
| tag `vX.Y.Z` (ou `vX.Y.Z-rc.1`) | `:vX.Y.Z`, orthographié comme le tag git, et rien d'autre |
|
||||
| toute autre référence | **échec**, sans rien construire |
|
||||
|
||||
Sorties : `tags` (les tags publiés) et `digest` (l'empreinte `sha256:…` de
|
||||
l'image publiée).
|
||||
|
||||
**Exigences** : l'image du conteneur de job doit porter `buildah`. L'action
|
||||
construit avec `--storage-driver vfs --isolation chroot`, ce qui fonctionne
|
||||
dans un conteneur non privilégié, sans capacité ajoutée ni `/dev/fuse`. Les
|
||||
`FROM` des Dockerfiles construits doivent être des **noms complets**
|
||||
(`docker.io/library/nginx:alpine`, pas `nginx:alpine`) : buildah n'a pas de
|
||||
registre de recherche par défaut, et sa table d'alias est incomplète.
|
||||
|
||||
**Toujours `uses:` en URL complète** : la forme courte `public/RF_ACTIONS/…`
|
||||
serait résolue contre le serveur d'actions par défaut de l'instance, qui
|
||||
n'est pas forcément celle-ci.
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
name: 'Build & push'
|
||||
description: >-
|
||||
Construit une image avec buildah, sans démon ni socket, et la publie dans le
|
||||
registre de Gitea sous les tags de sa filière (ADR 0002 et 0003 de RF_CICD). Ne
|
||||
déploie rien : la filière dev tire l'image, la production se déploie sur
|
||||
ordre. Les tests et le lint du projet tournent AVANT cette étape, dans le
|
||||
workflow appelant ; l'action ne filtre rien elle-même.
|
||||
|
||||
# Tags publiés, selon ce qui a déclenché le workflow :
|
||||
#
|
||||
# poussée sur develop → :dev et :<sha court, 12 caractères>
|
||||
# tag vX.Y.Z → :vX.Y.Z, orthographié comme le tag git, et rien d'autre
|
||||
# toute autre référence → échec, sans rien construire
|
||||
#
|
||||
# Le refus des autres références est voulu : une fusion sur main ne publie
|
||||
# rien (ADR 0002), et une branche de fonctionnalité qui appellerait l'action
|
||||
# par erreur ne doit pas pouvoir écraser :dev. Mieux vaut un job rouge qui dit
|
||||
# pourquoi qu'une image publiée sous un nom que personne n'a choisi.
|
||||
#
|
||||
# Il n'y a volontairement aucun tag flottant de production (pas de :prod, pas
|
||||
# de :latest), et rien n'est élagué.
|
||||
|
||||
inputs:
|
||||
image:
|
||||
description: >-
|
||||
Nom complet de l'image, sans tag — doit commencer par le registre, ex.
|
||||
gitea.raggaroth-factory.fr/raggaroth-factory/scrum-board-backend
|
||||
required: true
|
||||
dockerfile:
|
||||
description: Chemin du Dockerfile/Containerfile à construire.
|
||||
required: true
|
||||
context:
|
||||
description: Contexte de construction.
|
||||
required: false
|
||||
default: '.'
|
||||
registry:
|
||||
description: Hôte du registre.
|
||||
required: false
|
||||
default: gitea.raggaroth-factory.fr
|
||||
registry_user:
|
||||
description: Compte qui publie dans le registre.
|
||||
required: true
|
||||
registry_password:
|
||||
description: Jeton du compte qui publie. Par un secret Gitea Actions, jamais en clair.
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
tags:
|
||||
description: Les tags publiés, séparés par des espaces.
|
||||
value: ${{ steps.tags.outputs.tags }}
|
||||
digest:
|
||||
description: L'empreinte (sha256:…) de l'image publiée — c'est elle que la filière dev compare.
|
||||
value: ${{ steps.publish.outputs.digest }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
# Les entrées passent TOUJOURS par l'environnement, jamais par « ${{ }} »
|
||||
# dans le script : une expression est recopiée telle quelle dans le texte
|
||||
# bash avant son exécution, et une valeur contenant « "; … » deviendrait du
|
||||
# code. L'ancienne version interpolait ainsi le mot de passe du registre
|
||||
# dans un « echo ».
|
||||
- name: Tags de la filière
|
||||
id: tags
|
||||
shell: bash
|
||||
env:
|
||||
INPUT_IMAGE: ${{ inputs.image }}
|
||||
INPUT_REGISTRY: ${{ inputs.registry }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
case "$INPUT_IMAGE" in
|
||||
"$INPUT_REGISTRY"/*) ;;
|
||||
*) echo "::error::l'image « $INPUT_IMAGE » n'est pas dans le registre « $INPUT_REGISTRY » : les identifiants seraient envoyés au mauvais hôte, ou l'image poussée sans eux"
|
||||
exit 1 ;;
|
||||
esac
|
||||
|
||||
# GITHUB_REF et non GITHUB_REF_NAME : seul le premier distingue une
|
||||
# branche « v1 » d'un tag « v1 ».
|
||||
case "$GITHUB_REF" in
|
||||
refs/heads/develop)
|
||||
TAGS="dev ${GITHUB_SHA:0:12}" ;;
|
||||
refs/tags/v*)
|
||||
VERSION=${GITHUB_REF#refs/tags/}
|
||||
# Un tag d'image n'accepte que [A-Za-z0-9_.-] : on exige une
|
||||
# version lisible plutôt que de corriger en silence ce qui a été
|
||||
# posé, pour que le tag git et le tag d'image restent identiques.
|
||||
if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$ ]]; then
|
||||
echo "::error::tag « $VERSION » : attendu vX.Y.Z (ou vX.Y.Z-rc.1)"
|
||||
exit 1
|
||||
fi
|
||||
TAGS="$VERSION" ;;
|
||||
*)
|
||||
echo "::error::« $GITHUB_REF » ne publie rien : seules une poussée sur develop et un tag vX.Y.Z publient (ADR 0002)"
|
||||
exit 1 ;;
|
||||
esac
|
||||
echo "tags=$TAGS" >> "$GITHUB_OUTPUT"
|
||||
echo "Tags : $TAGS"
|
||||
|
||||
- name: Construire et publier
|
||||
id: publish
|
||||
shell: bash
|
||||
env:
|
||||
INPUT_IMAGE: ${{ inputs.image }}
|
||||
INPUT_DOCKERFILE: ${{ inputs.dockerfile }}
|
||||
INPUT_CONTEXT: ${{ inputs.context }}
|
||||
INPUT_REGISTRY: ${{ inputs.registry }}
|
||||
INPUT_TAGS: ${{ steps.tags.outputs.tags }}
|
||||
REGISTRY_USER: ${{ inputs.registry_user }}
|
||||
REGISTRY_PASSWORD: ${{ inputs.registry_password }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if ! command -v buildah >/dev/null; then
|
||||
echo "::error::buildah absent de l'image de job : le runner doit exécuter ce job dans une image qui le porte (voir README de RF_ACTIONS)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# « --storage-driver vfs » sur CHAQUE appel, et pas par la variable
|
||||
# STORAGE_DRIVER : constaté le 2026-10-07 sur buildah 1.33.7, avec la
|
||||
# seule variable, un « build » a laissé un stockage overlay que l'appel
|
||||
# suivant a refusé d'ouvrir (« 'overlay' is not supported over
|
||||
# overlayfs »). Avec le drapeau explicite, build, images et push
|
||||
# passent. vfs est le seul pilote qui tienne
|
||||
# dans un conteneur de job sans privilège ni /dev/fuse (ADR 0003).
|
||||
b() { buildah --storage-driver vfs "$@"; }
|
||||
|
||||
# Fichier d'authentification explicite, dans le répertoire temporaire du
|
||||
# job, et effacé en sortie quoi qu'il arrive. Le conteneur est jeté à la
|
||||
# fin du job, mais un jeton n'a pas à lui survivre d'une étape.
|
||||
export REGISTRY_AUTH_FILE="${RUNNER_TEMP:-/tmp}/build-push-auth.json"
|
||||
trap 'rm -f "$REGISTRY_AUTH_FILE"' EXIT
|
||||
|
||||
# printf est une primitive du shell : le jeton ne passe dans l'argv
|
||||
# d'aucun processus, donc pas dans /proc/<pid>/cmdline.
|
||||
printf '%s' "$REGISTRY_PASSWORD" \
|
||||
| b login --username "$REGISTRY_USER" --password-stdin "$INPUT_REGISTRY"
|
||||
|
||||
TAG_ARGS=()
|
||||
for TAG in $INPUT_TAGS; do TAG_ARGS+=(--tag "$INPUT_IMAGE:$TAG"); done
|
||||
|
||||
# --isolation chroot : pas d'espace de noms imbriqué à créer pour les
|
||||
# RUN, c'est ce qui permet de construire sans privilège (ADR 0003).
|
||||
# Les étiquettes OCI disent de quel commit vient l'image : c'est ce que
|
||||
# lira un « deployer.sh --constat » pour dire ce qui tourne.
|
||||
b build \
|
||||
--isolation chroot \
|
||||
--file "$INPUT_DOCKERFILE" \
|
||||
--label "org.opencontainers.image.revision=$GITHUB_SHA" \
|
||||
--label "org.opencontainers.image.source=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY" \
|
||||
"${TAG_ARGS[@]}" \
|
||||
"$INPUT_CONTEXT"
|
||||
|
||||
DIGEST_FILE="${RUNNER_TEMP:-/tmp}/build-push-digest"
|
||||
for TAG in $INPUT_TAGS; do
|
||||
b push --digestfile "$DIGEST_FILE" "$INPUT_IMAGE:$TAG" "docker://$INPUT_IMAGE:$TAG"
|
||||
echo "Publié : $INPUT_IMAGE:$TAG@$(cat "$DIGEST_FILE")"
|
||||
done
|
||||
echo "digest=$(cat "$DIGEST_FILE")" >> "$GITHUB_OUTPUT"
|
||||
|
||||
b logout "$INPUT_REGISTRY" || true
|
||||
Reference in New Issue
Block a user