diff --git a/.forgejo/workflows/prerelease.yml b/.forgejo/workflows/release-pipeline.yml similarity index 72% rename from .forgejo/workflows/prerelease.yml rename to .forgejo/workflows/release-pipeline.yml index e648f9b..53e2ad3 100644 --- a/.forgejo/workflows/prerelease.yml +++ b/.forgejo/workflows/release-pipeline.yml @@ -1,30 +1,47 @@ -name: Pre Release Workflow +name: Release Pipeline on: push: tags: - - '*rc*' + - '[0-9]*.[0-9]*.[0-9]*' jobs: set-release-target: runs-on: docker outputs: release_cible: ${{ steps.setvar.outputs.release_cible }} + prerelease: ${{ steps.setvar.outputs.prerelease }} steps: - name: Déterminer la release cible id: setvar run: | - if [[ "${GITHUB_REF}" == refs/tags/* ]]; then - TAG="${GITHUB_REF#refs/tags/}" - echo "release_cible=$TAG" >> $GITHUB_OUTPUT - elif [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then - echo "release_cible=latest" >> $GITHUB_OUTPUT + TAG="${GITHUB_REF#refs/tags/}" + + # Formes acceptées, et rien d'autre : + # X.Y.Z release finale → prerelease=false, notes obligatoires + # X.Y.ZrcNNN préversion → prerelease=true, notes optionnelles + # + # Le nombre de chiffres de rc est fixé à 3 (rc016, pas rc16) pour que + # le tri lexicographique des tags suive l'ordre chronologique. + if [[ "${TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] + then + PRERELEASE=false + elif [[ "${TAG}" =~ ^[0-9]+\.[0-9]+\.[0-9]+rc[0-9]{3}$ ]] + then + PRERELEASE=true else - echo "release_cible=unknown" >> $GITHUB_OUTPUT + echo "tag hors convention : ${TAG}" >&2 + echo "formes acceptées : X.Y.Z (release) ou X.Y.ZrcNNN (préversion, rc sur 3 chiffres)" >&2 + exit 1 fi + echo "release_cible=${TAG}" >> $GITHUB_OUTPUT + echo "prerelease=${PRERELEASE}" >> $GITHUB_OUTPUT + - name: Afficher la variable - run: echo "Release cible = ${{ steps.setvar.outputs.release_cible }}" + run: | + echo "Release cible = ${{ steps.setvar.outputs.release_cible }}" + echo "Prerelease = ${{ steps.setvar.outputs.prerelease }}" build: needs: [set-release-target] strategy: @@ -97,7 +114,7 @@ jobs: with: name: SHA256SUMS-${{ needs.set-release-target.outputs.release_cible }} path: SHA256SUMS - prerelease: + publish: # Tous les producteurs d'artefacts sont dans les needs : sans ça, le job # release peut appeler download-artifact avant la fin des uploads et publier # une release incomplète (assets ou manifeste manquants de façon non @@ -106,4 +123,5 @@ jobs: uses: ./.forgejo/workflows/release.yml with: tag: ${{ needs.set-release-target.outputs.release_cible }} + prerelease: ${{ needs.set-release-target.outputs.prerelease }} secrets: inherit diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index efc7694..a6a10a5 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -4,6 +4,9 @@ on: tag: required: true type: string + prerelease: + required: true + type: string jobs: release: @@ -11,7 +14,9 @@ jobs: env: TOKEN: ${{ secrets.RELEASE }} TAG: ${{ inputs.tag }} + PRERELEASE: ${{ inputs.prerelease }} steps: + - uses: actions/checkout@v3 - name: Download all build artifacts uses: actions/download-artifact@v3 with: @@ -22,21 +27,68 @@ jobs: run: | apt-get update apt-get install -y jq - - name: Create prerelease + - name: Préparer les notes de version run: | - curl -X POST \ - -H "Authorization: token $TOKEN" \ + NOTES="release_notes/${TAG}.md" + # Titre par défaut : le tag seul. Un H1 en première ligne des notes le + # remplace par « — ». + RELEASE_NAME="${TAG}" + + if [[ -f "${NOTES}" ]] + then + echo "notes trouvées : ${NOTES}" + FIRST_LINE=$(head -1 "${NOTES}") + if [[ "${FIRST_LINE}" =~ ^\#[[:space:]]+(.+)$ ]] + then + RELEASE_NAME="${TAG} — ${BASH_REMATCH[1]}" + # H1 retiré du corps : Forgejo affiche déjà le titre au-dessus, le + # garder l'afficherait deux fois. `/./,$!d` mange les lignes vides + # laissées en tête. + tail -n +2 "${NOTES}" | sed '/./,$!d' > body.md + else + cp "${NOTES}" body.md + fi + elif [[ "${PRERELEASE}" == "true" ]] + then + echo "aucune note pour ${TAG}, texte générique (préversion)" + echo "Prerelease automatique générée par la CI" > body.md + else + echo "release finale ${TAG} sans notes de version" >&2 + echo "créer ${NOTES} avant de pousser le tag" >&2 + exit 1 + fi + + echo "RELEASE_NAME=${RELEASE_NAME}" >> "${GITHUB_ENV}" + echo "--- titre --- ${RELEASE_NAME}" + echo "--- body ---"; cat body.md + - name: Create release + run: | + jq -n \ + --arg tag "${TAG}" \ + --arg name "${RELEASE_NAME}" \ + --rawfile body body.md \ + --argjson prerelease "${PRERELEASE}" \ + '{ + tag_name: $tag, + name: $name, + body: $body, + draft: false, + prerelease: $prerelease, + hide_archive_links: true + }' > payload.json + cat payload.json | jq -c '.body |= (.[0:60] + "…")' + + # Code HTTP vérifié explicitement plutôt qu'avec --fail-with-body, qui + # exige curl >= 7.76 : sans ça un échec (409 tag déjà publié, 401 token + # invalide) passerait inaperçu et la release resterait sans assets. + HTTP=$(curl -sS -o resp.json -w '%{http_code}' -X POST \ + -H "Authorization: token ${TOKEN}" \ -H "Content-Type: application/json" \ "https://git.g3e.fr/api/v1/repos/${{ github.repository }}/releases" \ - -d @- <&2; exit 1; } - name: Upload asset run: | RELEASE_ID=$(curl -s \ @@ -44,6 +96,10 @@ jobs: https://git.g3e.fr/api/v1/repos/${{ github.repository }}/releases/tags/${TAG} \ | jq -r .id) echo ${RELEASE_ID} + # Sans ce garde-fou, un id absent (null) enverrait chaque asset sur + # .../releases/null/assets : une cascade de 404 silencieux et une + # release publiée sans aucun binaire. + [[ "${RELEASE_ID}" =~ ^[0-9]+$ ]] || { echo "id de release introuvable pour ${TAG}" >&2; exit 1; } ls dist | while read tmp do FILE=$(ls "./dist/${tmp}") @@ -53,4 +109,4 @@ jobs: -H "Content-Type: application/octet-stream" \ --data-binary @dist/${tmp}/${FILE} \ "https://git.g3e.fr/api/v1/repos/${{ github.repository }}/releases/${RELEASE_ID}/assets?name=${FILE}" - done \ No newline at end of file + done diff --git a/release_notes/0.1.0.md b/release_notes/0.1.0.md new file mode 100644 index 0000000..be3d69c --- /dev/null +++ b/release_notes/0.1.0.md @@ -0,0 +1,54 @@ +# Varion + +Première version stable de **syonad/two**, orchestrateur réseau et VM mono-nœud. + +## Fonctionnalités + +**API et cycle de vie** + +- API HTTP `/vpcs`, `/subnets`, `/vms` : création et suppression asynchrones (202 + état en base) +- États unifiés pour les trois types de ressource : `creating` → `running` → `deleting` → `deleted`, + avec `error` en cas d'échec d'exécution ; suppression autorisée depuis `running` et `error` +- Migration au démarrage de l'agent : toute ressource restée dans un état transitoire est + basculée en `error`, la file de travail étant en mémoire + +**Réseau** + +- VPC isolés par network namespace, subnets en mode `vxlan` ou `bridge` +- DHCP par subnet via instances `dnsmasq@` dédiées, entrées ip→mac en base +- Route par défaut et route du VPC distribuées par DHCP (`default_route` par subnet) +- Isolation du DHCP par ebtables, redirection du service de metadata par iptables + +**Machines virtuelles** + +- Démarrage QEMU/KVM avec plusieurs disques et ordre de démarrage explicite +- Amorçage UEFI optionnel (OVMF), avec magasin de variables par VM +- Serveur de metadata cloud-init par VM (`metadata@`), sans base de données dans le processus +- Les VMs survivent à l'arrêt de l'agent : QEMU est lancé hors de son cgroup via `systemd-run` + +**Exploitation** + +- Métriques Prometheus : nombre de VPC, subnets et VMs par état +- `deploy.sh` avec profils d'host (`kvm`), préparation système déléguée à `bootstrap_kvm.sh` +- Units systemd et scripts publiés comme assets de release, avec manifeste `SHA256SUMS` + vérifié au déploiement + +## Périmètre et limites connues + +- Un seul nœud : pas d'ordonnanceur ni de placement entre hyperviseurs +- Pas de rollback en cas d'échec partiel d'une création — les ressources réseau orphelines + ne sont pas nettoyées automatiquement +- API destinée à un appelant logiciel : la validation de cohérence des entrées (CIDR, VXLAN + ID, format des noms) est à la charge de l'appelant +- Les packages `internal/netns`, `netif`, `qemu`, `vm`, `iptables` et `ebtables` ne + fonctionnent que sous Linux + +## Installation + +``` +curl -O https://git.g3e.fr/syonad/two/raw/branch/main/scripts/deploy.sh +bash ./deploy.sh -t 0.1.0 -i # -i : préparation de l'host (paquets, kernel, bridges) +``` + +`deploy.sh` se vérifie lui-même contre la branche et télécharge les binaires, les units et +le manifeste depuis cette release.