https://lafor.ge/feed.xml

Partie 2: Pipeline de CI

2024-04-22
Les articles de la série

Bonjour à toutes et à tous 😀

Dans l'article précédent nous avons poussé à la main des crates en utilisant curl en local.

Ce n'est pas du tout satisfaisant.

Nous allons automatiser tout ça via la CI. Et comme nous avons commencé avec la package registry de Gitlab, on va également utilisé la Gitlab CI.

J'ai un certain nombre d'exigences.

  • une adresse unique pour toutes les crates, je ne veaux pas à avoir à me rappeler du chemin à chaque fois
  • le versionnage est explicite et basé sur la version du package publié
  • support des versions flottantes de dev
  • ces versions de devs ont un versionnage implicite et automatique
  • cela doit être le moins contraignant à utiliser, le moins de boilerplate possible
  • il doit gérer les workspace Rust

Autant dire qu'il y a un peu de travail.

Pipeline de CI 1o1

Commençons par la base.

Si vous créez un fichier .gitlab-ci.yml à la racine d'un projet git et que vous le poussez sur Gitlab, cela va indiquer à Gitlab qu'il faut lancer un pipeline de CI.

Son format est le suivant

.gitlab-ci.yml
stages:
    - stage1
    - stage2

job1:
    stage: stage1

job2:
    stage: stage1

job3:
    stage: stage2

Cela créé un graphe d'exécution qui va ressembler à ceci

  flowchart LR
    job1 -->job3
    job2 --> job3

job1 et job2 doivent tous les deux réussir si l'on veut que job3 s'exécute. Ce système de strates se nomme des stages et permettent de réaliser des checkpoints bloquants entre les différentes étapes.

Par exemple ne packager que si le build est un succès est une bonne idée. 😎

Nous nous allons définir un stage qui s'appellera package.

Bon et maintenant, qu'est ce que l'on fait de nos jobs ?.

La première chose est de définir une image qui sera notre environnement de packaging, nous faisons du rust, ça sera donc une image rust. Et de préférence versionnée.

.gitlab-ci.yml
package-crate:
    image: rust:1.77
    stage: packaging

Nous allons écrire du bash, ce qui reste la meilleure façon d'intéragir avec un environnement.

Et ce bash vient se mettre dans un array nommé scripts.

.gitlab-ci.yml
package-crate:
    image: rust:1.77
    stage: packaging
    script: 
        - echo "Hello World"

Packaging en CI

D'abord récapitulons ce que nous avons fait en local:

  • nous avons défini un .ssh/config contenant le host de notre proxy
  • nous avons lancé les commandes de packaging
  • nous avons fait du curl

Ok, mimons ce fonctionnement.

Tout d'abord occupons-nous du SSH.

La première chose que l'on va faire c'est définir des informations sous forme de variable d'environnement

.gitlab-ci.yml
variables:
    CRATE_PACKAGE_ENDPOINT=$CRATES_ENDPOINT
    CRATE_PACKAGE_USER=personal-token

Bien maintenant le packaging en lui-même.

Il nécessite plusieurs éléments:

  • l'addresse de l'API gitlab trouvable dans la variable injectée $CI_API_V4_URL
  • la version du package
  • le nom du package
  • le chemin vers le fichier .crate à générer à partier des informations précédentes
  • le token que l'on possède déjà

Pour rappel, cela à cette tête-là:

curl --header "PRIVATE-TOKEN: $TOKEN" --upload-file target/package/${CRATE_FILE} "${ENDPOINT}/projects/${PROJECT_ID}/packages/generic/${CRATE_NAME}/${CRATE_VERSION}/$CRATE_FILE"

Commençons par récupérer le nom et la version du package.

Pour cela nous pouvons utiliser un utiliser un utilitaire qui se nomme tomlq, il vient avec yq et permet de réaliser des requêtes jq sur un fichier toml.

Pour cela, on se rajoute la dépendance.

apt update && apt install -y yq

Puis on génère nos variables.

export CRATE_NAME=$(tomlq '.package.name' Cargo.toml  | tr -d '"')
export CRATE_NAME=$(tomlq '.package.version' Cargo.toml  | tr -d '"')

On vient "trim" les guillemets qui vont nous ennuyer par la suite.

On peut alors constituer notre $CRATE_FILE

export CRATE_FILE=${CRATE_NAME}-${CRATE_VERSION}.crate

Il nous faut également un $CRATE_PACKAGE_PROJECT_ID que l'on défini dans les variables d'environement

Nous avons tous les ingédients, nous pouvons créer notre job.

.gitlab-ci.yml
stages:
  - packaging

variables:
  # gitlab token
  CRATE_PACKAGE_TOKEN: $CI_JOB_TOKEN
  # l'ID du projet qui supportera les crates par défaut le project du job
  CRATE_PACKAGE_PROJECT_ID: $CI_PROJECT_ID
  # API user
  CRATE_PACKAGE_USER_API: JOB-TOKEN

package-crate:
  image: rust:1.77
  stage: packaging
  script:
    # dépendences
    - apt update && apt install -y yq
    # récupération des informations du paquet
    - export CRATE_NAME=$(tomlq '.package.name' Cargo.toml  | tr -d '"')
    - export CRATE_VERSION=$(tomlq '.package.version' Cargo.toml  | tr -d '"')
    - export CRATE_FILE=${CRATE_NAME}-${CRATE_VERSION}.crate
    # packaging
    - cargo package
    - cargo metadata --format-version 1 > metadata.json
    # upload
    - 'curl --header "$CRATE_PACKAGE_USER_API: $CRATE_PACKAGE_TOKEN" --upload-file target/package/${CRATE_FILE} "${CI_API_V4_URL}/projects/${CRATE_PACKAGE_PROJECT_ID}/packages/generic/${CRATE_NAME}/${CRATE_VERSION}/$CRATE_FILE"'
    - 'curl --header "$CRATE_PACKAGE_USER_API: $CRATE_PACKAGE_TOKEN" --upload-file metadata.json "${CI_API_V4_URL}/projects/${CRATE_PACKAGE_PROJECT_ID}/packages/generic/${CRATE_NAME}/${CRATE_VERSION}/metadata.json"'

Si tout se passe bien, vous devriez voir apparaître dans la package registry de votre projet Gitlab un paquet project v0.1.0.

Maintenant intéressons nous au cas où le projet nécessite une dépendence qui elle même est privée.

On se créé un projet pour tester.

cargo init --lib need_project && cd need_project
mkdir -p .cargo && cat << EOF > .cargo/config
[registries.private-crate]
index = "ssh://noa-crates.cleverapps.io/noa-crates/project"
[net]
git-fetch-with-cli = true
EOF
echo  'project = {version="0.1.0", registry="private-crate"}' >> Cargo.toml

Comme vous pouvez le voir, nous allons devoir nous authentifier.

Pour cela nous définissons dans l'objet variables les différentes informations nécessaire à la connexion SSH.

.gitlab-ci.yml
variables:
    CRATE_PACKAGE_TOKEN: $CI_JOB_TOKEN
    CRATE_PACKAGE_ENDPOINT: noa-crates.cleverapps.io
    CRATE_PACKAGE_PORT: 22066
    CRATE_PACKAGE_USER: personal-token

On créé le répertoire .ssh avec les bons droits

mkdir -p ~/.ssh && chmod -R 700 ~/.ssh

Via heredoc nous générons la configuration SSH:

cat << EOF > ~/.ssh/config 
Host $CRATE_PACKAGE_ENDPOINT
    User $CRATE_PACKAGE_USER:$CRATE_PACKAGE_TOKEN
    Port $CRATE_PACKAGE_PORT
EOF

Mais cela n'est pas suffisant, il faut également générer une clef privée pour que la connexion SSH se fasse.

ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519

Et finalement récupérer la clef publique de notre proxy SSH, sinon la connexion échouera en non-intéractif car le endpoint n'est pas "connu".

ssh-keyscan -p $CRATE_PACKAGE_PORT $CRATE_PACKAGE_ENDPOINT >> ~/.ssh/known_hosts

Ok, notre job de CI est maintenant capable de réaliser notre authentification au proxy.

Nous pouvons rassembler les pièces du puzzle

.gitlab-ci.yml
stages:
  - packaging

variables:
  # gitlab token
  CRATE_PACKAGE_TOKEN: $CI_JOB_TOKEN
  # l'ID du projet qui supportera les crates par défaut le project du job
  CRATE_PACKAGE_PROJECT_ID: $CI_PROJECT_ID
  # API user
  CRATE_PACKAGE_USER_API: JOB-TOKEN
  # Host du proxy SSH
  CRATE_PACKAGE_ENDPOINT: noa-crates.cleverapps.io
  # Port d'écoute du proxy SSH
  CRATE_PACKAGE_PORT: 22066
  # Utilisateur associé au token
  CRATE_PACKAGE_USER: personal-token

package-crate:
  image: rust:1.77
  stage: packaging
  script:
    # dépendences
    - apt update && apt install -y yq
    # configuration SSH
    - mkdir -p ~/.ssh && chmod -R 700 ~/.ssh
    - |
        cat << EOF > ~/.ssh/config 
        Host $CRATE_PACKAGE_ENDPOINT
            User $CRATE_PACKAGE_USER:$CRATE_PACKAGE_TOKEN
            Port $CRATE_PACKAGE_PORT
        EOF
    # création de la paire de clefs
    - ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
    # ajout de la clef publique du proxy
    - ssh-keyscan -p $CRATE_PACKAGE_PORT $CRATE_PACKAGE_ENDPOINT >> ~/.ssh/known_hosts
    # récupération des informations du paquet
    - export CRATE_NAME=$(tomlq '.package.name' Cargo.toml  | tr -d '"')
    - export CRATE_VERSION=$(tomlq '.package.version' Cargo.toml  | tr -d '"')
    - export CRATE_FILE=${CRATE_NAME}-${CRATE_VERSION}.crate
    # packaging
    - cargo package
    - cargo metadata --format-version 1 > metadata.json
    # upload
    - 'curl --header "$CRATE_PACKAGE_USER_API: $CRATE_PACKAGE_TOKEN" --upload-file target/package/${CRATE_FILE} "${CI_API_V4_URL}/projects/${CRATE_PACKAGE_PROJECT_ID}/packages/generic/${CRATE_NAME}/${CRATE_VERSION}/$CRATE_FILE"'
    - 'curl --header "$CRATE_PACKAGE_USER_API: $CRATE_PACKAGE_TOKEN" --upload-file metadata.json "${CI_API_V4_URL}/projects/${CRATE_PACKAGE_PROJECT_ID}/packages/generic/${CRATE_NAME}/${CRATE_VERSION}/metadata.json"'

Et là c'est le drame 😫

fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.

Oui, votre $CI_JOB_TOKEN n'a de droit que sur le projet need_project pas project.

Mais heureusement, il est possible de régler le souci en passant en paramètre du job un token apte à intéragir avec les deux projets, pour Gitlab SaaS en gratuit nous n'avons pas le choix, ça sera forcément des token personnels, mais pour des version self-hosted, il est possible de créer des group token.

Bref, dans la configuration de la CI du projet need_project rajouté une variable d'environnement CRATE_PACKAGE_TOKEN avec votre token.

Il faut également redéfinir le CRATE_PACKAGE_USER_API à PRIVATE-TOKEN

On relance

Si tout est correct,

Votre pipeline devrait afficher quelque part ceci:

$ cargo package
warning: manifest has no description, license, license-file, documentation, homepage or repository.
See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info.
   Packaging need_project v0.1.0 (/builds/noa-crates/need_project2)
   Verifying need_project v0.1.0 (/builds/noa-crates/need_project2)
    Updating `private-crate` index
From ssh://noa-crates.cleverapps.io/noa-crates/project
 * [new ref]                    -> origin/HEAD
 Downloading crates ...
  Downloaded project v0.1.0 (registry `private-crate`)
   Compiling project v0.1.0 (registry `private-crate`)
   Compiling need_project v0.1.0 (/builds/noa-crates/need_project2/target/package/need_project-0.1.0)
    Finished dev [unoptimized + debuginfo] target(s) in 3.00s

Cela prouve que votre arbre de dépendences privée est bien reconstruite en CI. 🤩

Succès ⁉️

Oui et non, c'est très contraignant de connaître toute cette mécanique, on ne gère pas encore les workspace et les versions de pre-release sont impossible à réaliser en l'état.

Bref on repars en chasse !

Versions de pre-release

Qu'est ce qu'une version de pre-release pour moi ?

Une version de pre-release c'est une version pas fini mais qui est nécessaire de packager pour d'autres développements.

Si on se fie à la notation semver suivie par cargo.

Il est possible de créer des versions qui ne sont pas "finies" en les suffixant d'un attribut.

par exemple 1.0.0-alpha.0, le suffixe de version alpha.0 indique la notion de pre-release de cette 1.0.0.

Mais on peut y mettre ce que l'on désire comme le SHA1 réduit de 8 caratères du commit de version.

C'est ce que j'ai décidé de faire pour générer des versions comme 1.0.2-2e92f1ff.

Pour cela nous allons concaténer la version récupérer du Cargo.toml et la joindre au contenu de la varaible $CI_COMMIT_SHORT_SHA injecté par le job.

export CRATE_VERSION=$CRATE_VERSION-$CI_COMMIT_SHORT_SHA

Ok easy non ?

Alors non, si vous tentez de faire ça comme cela, la commande cargo package n'utilisera pas votre version modifiée et créera un target/package/project-0.1.0.crate au lieu du target/package/project-0.1.0-2e92f1ff.crate voulu.

Il va falloir trafiquer le Cargo.toml pour mettre à jour la version.

tomlq permet au moyen du paramètre --arg d'injecter de l'environnement.

tomlq --arg VERSION $CRATE_VERSION -t '.package.version = $VERSION' Cargo.toml > Cargo.toml.modified
mv Cargo.toml.modified Cargo.toml

Et il faut également rajouter un paramètre --allow-dirty pour packager sans avoir à commiter.

cargo package --allow-dirty

On est bon ?

Presque.

Il faut que l'on découple les run entre la branche main et les autres: je veux que le package de release ne se déclenche que sur la branche main et pas ailleurs.

Et je veux également que le run du packaging de pre-release soit une tâche manuelle.

Heuresement Gitlab Ci permet de configurer tout ceci.

.gitlab-ci.yml
stages:
    - packaging

release-dev:
    stage: packaging
    # nécessite une intervention humaine
    when: manual
    # si la branche n'est pas main
    rules:
      - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH

release-prod:
    stage: packaging
    # seulement si c'est main
    only:
    - $CI_DEFAULT_BRANCH

Et maintenant on rempli

stages:
    - packaging

variables:
  # gitlab token
  CRATE_PACKAGE_TOKEN: $CI_JOB_TOKEN
  # l'ID du projet qui supportera les crates par défaut le project du job
  CRATE_PACKAGE_PROJECT_ID: $CI_PROJECT_ID
  # API user
  CRATE_PACKAGE_USER_API: JOB-TOKEN
  # Host du proxy SSH
  CRATE_PACKAGE_ENDPOINT: noa-crates.cleverapps.io
  # Port d'écoute du proxy SSH
  CRATE_PACKAGE_PORT: 22066
  # Utilisateur associé au token
  CRATE_PACKAGE_USER: personal-token

release-dev:
  image: rust:1.77
  stage: packaging 
  # nécessite une intervention humaine
  when: manual
  # si la branche n'est pas main
  rules:
    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
  script:
    # dépendences
    - apt update && apt install -y yq
    # configuration SSH
    - mkdir -p ~/.ssh && chmod -R 700 ~/.ssh
    - |
        cat << EOF > ~/.ssh/config 
        Host $CRATE_PACKAGE_ENDPOINT
            User $CRATE_PACKAGE_USER:$CRATE_PACKAGE_TOKEN
            Port $CRATE_PACKAGE_PORT
        EOF
    # création de la paire de clefs
    - ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
    # ajout de la clef publique du proxy
    - ssh-keyscan -p $CRATE_PACKAGE_PORT $CRATE_PACKAGE_ENDPOINT >> ~/.ssh/known_hosts
    # récupération des informations du paquet
    - export CRATE_NAME=$(tomlq '.package.name' Cargo.toml  | tr -d '"')
    - export CRATE_VERSION=$(tomlq '.package.version' Cargo.toml  | tr -d '"')
    # on créé la version flottante
    - export CRATE_VERSION=$CRATE_VERSION-$CI_COMMIT_SHORT_SHA
    - export CRATE_FILE=${CRATE_NAME}-${CRATE_VERSION}.crate
    # On remplace la version
    - tomlq --arg VERSION $CRATE_VERSION -t '.package.version = $VERSION' Cargo.toml > Cargo.toml.modified
    - mv Cargo.toml.modified Cargo.toml
    # packaging
    - cargo package --allow-dirty
    - cargo metadata --format-version 1 > metadata.json
    # upload
    - 'curl --header "$CRATE_PACKAGE_USER_API: $CRATE_PACKAGE_TOKEN" --upload-file target/package/${CRATE_FILE} "${CI_API_V4_URL}/projects/${CRATE_PACKAGE_PROJECT_ID}/packages/generic/${CRATE_NAME}/${CRATE_VERSION}/$CRATE_FILE"'
    - 'curl --header "$CRATE_PACKAGE_USER_API: $CRATE_PACKAGE_TOKEN" --upload-file metadata.json "${CI_API_V4_URL}/projects/${CRATE_PACKAGE_PROJECT_ID}/packages/generic/${CRATE_NAME}/${CRATE_VERSION}/metadata.json"'

release-prod:
  image: rust:1.77
  stage: packaging
  # seulement si c'est main
  only:
    - $CI_DEFAULT_BRANCH
  script:
    # dépendences
    - apt update && apt install -y yq
    # configuration SSH
    - mkdir -p ~/.ssh && chmod -R 700 ~/.ssh
    - |
        cat << EOF > ~/.ssh/config 
        Host $CRATE_PACKAGE_ENDPOINT
            User $CRATE_PACKAGE_USER:$CRATE_PACKAGE_TOKEN
            Port $CRATE_PACKAGE_PORT
        EOF
    # création de la paire de clefs
    - ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
    # ajout de la clef publique du proxy
    - ssh-keyscan -p $CRATE_PACKAGE_PORT $CRATE_PACKAGE_ENDPOINT >> ~/.ssh/known_hosts
    # récupération des informations du paquet
    - export CRATE_NAME=$(tomlq '.package.name' Cargo.toml  | tr -d '"')
    - export CRATE_VERSION=$(tomlq '.package.version' Cargo.toml  | tr -d '"')
    - export CRATE_FILE=${CRATE_NAME}-${CRATE_VERSION}.crate
    # packaging
    - cargo package
    - cargo metadata --format-version 1 > metadata.json
    # upload
    - 'curl --header "$CRATE_PACKAGE_USER_API: $CRATE_PACKAGE_TOKEN" --upload-file target/package/${CRATE_FILE} "${CI_API_V4_URL}/projects/${CRATE_PACKAGE_PROJECT_ID}/packages/generic/${CRATE_NAME}/${CRATE_VERSION}/$CRATE_FILE"'
    - 'curl --header "$CRATE_PACKAGE_USER_API: $CRATE_PACKAGE_TOKEN" --upload-file metadata.json "${CI_API_V4_URL}/projects/${CRATE_PACKAGE_PROJECT_ID}/packages/generic/${CRATE_NAME}/${CRATE_VERSION}/metadata.json"'

{%end%}

Si l'on

git checkout -b `dev
git push --set-upstream origin dev 

Un pipeline avec une tâche manuelle va apparaître

Et dedans normalement

$ cargo package --allow-dirty
   Packaging need_project v0.1.0-0eb85488 (/builds/noa-crates/need_project2)
   Verifying need_project v0.1.0-0eb85488 (/builds/noa-crates/need_project2)
    Updating `private-crate` index
From ssh://noa-crates.cleverapps.io/noa-crates/project
 * [new ref]                    -> origin/HEAD
 Downloading crates ...
  Downloaded project v0.1.0 (registry `private-crate`)
   Compiling project v0.1.0 (registry `private-crate`)
   Compiling need_project v0.1.0-0eb85488 (/builds/noa-crates/need_project2/target/package/need_project-0.1.0-0eb85488)
    Finished dev [unoptimized + debuginfo] target(s) in 2.89s
    Packaged 6 files, 4.7KiB (1.8KiB compressed)

Félicitation, vous avez packagé votre version 0.1.0-0eb85488 de need_project et celle-ci peut alors se retrouver dans la package registry de need_project.

Factorisation

Tout cela c'est très bien mais on voit bien que les étapes sont très semblables entre le job de release et de pre-release.

Il doit être possible de mettre en place un système qui permet de factoriser ces répétitions, et ce mécanisme ne provient pas de Gitlab mais du format YAML, lui-même.

Il s'agit des YAML anchors.

Qu'est ce que l'on peut factoriser ?

Tout d'abord les dépendences

.dependencies: &dependencies
  # dépendences
  - apt update && apt install -y yq

Puis la définition de la configuration SSH

.ssh-connexion: &ssh-connexion:
  # configuration SSH
  - mkdir -p ~/.ssh && chmod -R 700 ~/.ssh
  - |
    cat << EOF > ~/.ssh/config 
    Host $CRATE_PACKAGE_ENDPOINT
        User $CRATE_PACKAGE_USER:$CRATE_PACKAGE_TOKEN
        Port $CRATE_PACKAGE_PORT
    EOF
  # création de la paire de clefs
  - ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
  # ajout de la clef publique du proxy
  - ssh-keyscan -p $CRATE_PACKAGE_PORT $CRATE_PACKAGE_ENDPOINT >> ~/.ssh/known_hosts

Ensuite, la récupération des informations du package.

.version: &version
  # récupération des informations du paquet
  - export CRATE_NAME=$(tomlq '.package.name' Cargo.toml  | tr -d '"')
  - export CRATE_VERSION=$(tomlq '.package.version' Cargo.toml  | tr -d '"')
  - export CRATE_FILE=${CRATE_NAME}-${CRATE_VERSION}.crate

Et finalement, le packaging en lui-même

.packaging: &packaging
  # packaging
  - cargo package --allow-dirty
  - cargo metadata --format-version 1 > metadata.json
  - ls target/package/$CRATE_FILE
  # upload
  - 'curl -i --header "$CRATE_PACKAGE_USER_API: $CRATE_PACKAGE_TOKEN" --upload-file target/package/${CRATE_FILE} "${CI_API_V4_URL}/projects/${CRATE_PACKAGE_PROJECT_ID}/packages/generic/${CRATE_NAME}/${CRATE_VERSION}/$CRATE_FILE"'
  - 'curl -i --header "$CRATE_PACKAGE_USER_API: $CRATE_PACKAGE_TOKEN" --upload-file metadata.json "${CI_API_V4_URL}/projects/${CRATE_PACKAGE_PROJECT_ID}/packages/generic/${CRATE_NAME}/${CRATE_VERSION}/metadata.json"'

On peut alors jouer aux légos 🧱


.prepare: &prepare
  - *dependencies
  - *ssh-connexion
  - *version

release-dev:
  image: rust:1.77
  stage: packaging
  # manuel
  when: manual
  # si la branche n'est pas main
  rules:
    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
  script:
    - *prepare
    # on créé la version flottante
    - export CRATE_VERSION=$CRATE_VERSION-$CI_COMMIT_SHORT_SHA
    - export CRATE_FILE=${CRATE_NAME}-${CRATE_VERSION}.crate
    # On remplace la version
    - tomlq --arg VERSION $CRATE_VERSION -t '.package.version = $VERSION' Cargo.toml > Cargo.toml.modified
    - mv Cargo.toml.modified Cargo.toml
    - *packaging

release-prod:
  image: rust:1.77
  stage: packaging
  # seulement si c'est main
  only:
    - main
  script:
    - *prepare
    - *packaging

C'est quand même plus clean comme ça 😀

.gitlab-ci.yml
stages:
  - packaging

variables:
  # gitlab token
  CRATE_PACKAGE_TOKEN: $CI_JOB_TOKEN
  # l'ID du projet qui supportera les crates par défaut le project du job
  CRATE_PACKAGE_PROJECT_ID: $CI_PROJECT_ID
  # API user
  CRATE_PACKAGE_USER_API: JOB-TOKEN
  # Host du proxy SSH
  CRATE_PACKAGE_ENDPOINT: noa-crates.cleverapps.io
  # Port d'écoute du proxy SSH
  CRATE_PACKAGE_PORT: 22066
  # Utilisateur associé au token
  CRATE_PACKAGE_USER: personal-token

.dependencies: &dependencies
  # dépendences
  - apt update && apt install -y yq

.ssh-connexion: &ssh-connexion
  # configuration SSH
  - mkdir -p ~/.ssh && chmod -R 700 ~/.ssh
  - |
    cat << EOF > ~/.ssh/config 
    Host $CRATE_PACKAGE_ENDPOINT
        User $CRATE_PACKAGE_USER:$CRATE_PACKAGE_TOKEN
        Port $CRATE_PACKAGE_PORT
    EOF
  # création de la paire de clefs
  - ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
  # ajout de la clef publique du proxy
  - ssh-keyscan -p $CRATE_PACKAGE_PORT $CRATE_PACKAGE_ENDPOINT >> ~/.ssh/known_hosts

.packaging: &packaging
  # packaging
  - cargo package --allow-dirty
  - cargo metadata --format-version 1 > metadata.json
  - ls target/package/$CRATE_FILE
  # upload
  - 'curl -i --header "$CRATE_PACKAGE_USER_API: $CRATE_PACKAGE_TOKEN" --upload-file target/package/${CRATE_FILE} "${CI_API_V4_URL}/projects/${CRATE_PACKAGE_PROJECT_ID}/packages/generic/${CRATE_NAME}/${CRATE_VERSION}/$CRATE_FILE"'
  - 'curl -i --header "$CRATE_PACKAGE_USER_API: $CRATE_PACKAGE_TOKEN" --upload-file metadata.json "${CI_API_V4_URL}/projects/${CRATE_PACKAGE_PROJECT_ID}/packages/generic/${CRATE_NAME}/${CRATE_VERSION}/metadata.json"'

.version: &version
  # récupération des informations du paquet
  - export CRATE_NAME=$(tomlq '.package.name' Cargo.toml  | tr -d '"')
  - export CRATE_VERSION=$(tomlq '.package.version' Cargo.toml  | tr -d '"')
  - export CRATE_FILE=${CRATE_NAME}-${CRATE_VERSION}.crate

.prepare: &prepare
  - *dependencies
  - *ssh-connexion
  - *version

release-dev:
  image: rust:1.77
  stage: packaging
  # manuel
  when: manual
  # si la branche n'est pas main
  rules:
    - if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
  script:
    - *prepare
    # on créé la version flottante
    - export CRATE_VERSION=$CRATE_VERSION-$CI_COMMIT_SHORT_SHA
    - export CRATE_FILE=${CRATE_NAME}-${CRATE_VERSION}.crate
    # On remplace la version
    - tomlq --arg VERSION $CRATE_VERSION -t '.package.version = $VERSION' Cargo.toml > Cargo.toml.modified
    - mv Cargo.toml.modified Cargo.toml
    - *packaging

release-prod:
  image: rust:1.77
  stage: packaging
  # seulement si c'est main
  only:
    - main
  script:
    - *prepare
    - *packaging

Pour le prochain, article on verra comment gérer le problème épineux des workspaces.

avatar

Auteur: Akanoa

Je découvre, j'apprends, je comprends et j'explique ce que j'ai compris dans ce blog.

Ce travail est sous licence CC BY-NC-SA 4.0.