Les Crash-Devs d'un Passionné

Une application versionnée automatiquement

/Catégorie/python

Temps de lecture : 8 minutes

Je ne vais pas détailler les raisons pour lesquelles il est important de versionner une application, je vais plutôt m'attacher à décrire comment faire.

Ce tuto se repose sur l'outil python semantic release qui s'appuie sur les conventions de nommage des commits et de la convention de numérotation sémantique. C'est-à-dire qu'en respectant une certaine structure de message de commit git, la numérotation de version de votre application sera automatiquement réalisée.

SemVer

La numérotation sémantique est largement utilisée, elle consiste à mettre en place 3 chiffres dont chacun ont un rôle précis :

  • 1er chiffre : version majeur : indique qu'il y a de gros changements dans le code (suppression méthode, renommage, configuration supplémentaire...) pouvant introduire des modifications dans le code où vous utilisez cette librairie
  • 2ème chiffre : version mineure : indique qu'il n'y a pas de changement important profond, on garde la rétro-compatibilité (ajout d'une méthode, warning sur les méthodes qui seront supprimées, ajout d'une fonctionnalité légère...)
  • 3 ème chiffre : version patch : indique qu'il y a des corrections d'anomalies (bugs, style, doc ...) en gardant la rétro-compatibilité

Cela donnera une version de ce type :

1.2.3

Pour plus de renseignement https://semver.org

Convention Angular

Angular utilise une convention pour structurer ses messages de commits https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits

Je vais me centrer sur le `type` qui peut contenir plusieurs valeurs :

  • feat
    : Ajout/Modification de fonctionnalité
  • fix
    : Correction d'une anomalie
  • doc
    : Ajout/Modification de documentation
  • style
    : Ajout/Modification de l'aspect visuel
  • refactor
    : Ré-écriture d'un bout de code
  • perf
    : Optimisation d'exécution
  • test
    : Ajout/Modification des tests

Configuration python semantique release

Il faut commencer par installer la librairies dans vos dépendances de dev (car nous n'utiliserons ce module dans la CI et non en prod) avec votre gestionnaire de package (ici

pipenv
) :
pipenv install python-semantic-release --dev

Il faut stoquer l'endroit où la version de l'application est mémorisé, j'ai choisi de le faire dans

setup.py

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from setuptools import setup

__version__ = "0.3.8"

if __name__ == "__main__":
    setup(
        name="Blog",
        version=__version__,
        author="ITARVERNE",
        ...

Pour la configuration du module python semantique release j'ai choisi de tout placer dans le fichier

pyproject.toml

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
[tool.semantic_release.branches.main]
match = "(master)"

[tool.semantic_release.remote]
type="gitlab"
token = { env = "GITLAB_TOKEN" }

[tool.semantic_release]
version_variables=['setup.py:__version__']
commit_message = "build: tag {version}"
commit_author = "david <user@company.fr>"

[tool.semantic_release.commit_parser_options]
allowed_tags = [
    "build", "ci", "docs", "feat", "fix",
    "perf", "style", "refactor", "test"
]
minor_tags = ["feat"]
patch_tags = ["fix", "perf",  "style", "ci", "test", "docs"]

La section

[tool.semantic_release.branches.main]
permet de choisir sur quelle branche lancer l'outil, ici
master

La section

[tool.semantic_release.remote]
permet de déterminer où l'outil doit push, ici
gitlab
. Il faut également mettre une variable d'env GITLAB_TOKEN qui contient un token généré avec les droits `api, read_api, write_repository, sudo, admin_mode`

La section

[tool.semantic_release]
permet d'indiquer où la version doit être mise à jour (ici dans la variable
__version__
du fichier
setup.py
) et les informations du commiter quand l'outil fera ses actions (on fera plus loin ces actions)

La section

[tool.semantic_release.commit_parser_options]
permet de déterminer comment l'outil doit incrémenter la version. On lui donne une liste de type avec la version mineure ou patch.

Si comme moi vous utilisez docker, pensez à mettre git dans votre container car l'outil en aura besoin

L'automatisation

J'ai choisi gitlab, voici un extrait du job version :

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
version:
  stage: version

  environment: production

  rules:
    - if: $CI_COMMIT_TAG != null && $CI_COMMIT_BRANCH == "master"
      when: never
    - if: $CI_COMMIT_BRANCH == "master" && $CI_COMMIT_TAG == null
      when: manual

  variables:
    GIT_STRATEGY: clone

  script:
    - export PIP_NO_CACHE_DIR=off

    # Git config, according clone strategy
    - git config --global user.email ${GITLAB_USER_EMAIL}
    - git config --global user.name ${GITLAB_USER_NAME}
    - git remote set-url origin https://${GITLAB_USER_LOGIN}:${GITLAB_TOKEN}@$CI_SERVER_HOST/$CI_PROJECT_PATH.git
    - git checkout $CI_COMMIT_BRANCH

    # For Python Semantic Release
    - cd back
    - pipenv install --dev
    - set +e
    - new_release=$(pipenv run semantic-release --noop version --print  | grep "No release will be made" | wc -l)
    # Si pas de nouvelle version on arrête le job
    - if [[ new_release -ge 1 ]]; then exit 0 ; fi
    # Stop le job si erreur
    - set -e
    - pipenv run semantic-release -v version ${FORCE_SEMVER_MAJOR:+"--major"}
    - last_commit_hash_master=$(git log -1 --format=%H)

    # Report des modifs sur la branche dev
    - git fetch origin dev
    - git checkout -f dev
    - git branch
    # -m pour prendre la branche master dans le cas d'un commit de merge
    # --strategy=recursive -X ours : pour forcer la version de master
    - git cherry-pick --strategy=recursive -X theirs -x -m 1 $last_commit_hash_master
    - git push
  • La règle pour exécuter ce job est qu'une MR doit être réalisée avec la branche master en destination
  • La
    GIT_STRATEGY: clone
    permet de ne pas utiliser le cache des runners pour bien avoir les derniers commits
  • Je modifie la configuration de git pour notamment intégrer dans
    origin
    le token
    TOKEN_GITLAB
    généré auparavant
  • La commande
    pipenv run semantic-release -v version
    permet de lancer l'outil pour vérifier s'il y a un incrément de version à faire
  • Et enfin je reporte les modifications de la branche master (où travaille l'outil) sur la branche de dev

Workflow

Voici le schéma simplifié des branches du projet

workflow_gitlab_semantic_release_cherrypick

Lors de la validation de la MR entre dev et master, le job version sera exécuté. Il lancera la commande

semantic-release version
qui fera :

  • Lire les messages de commits ce cette MR pour en extraire les types
  • En fonction de la configuration détermine quelle incrémentation de version faire
  • Mettre à jour la version dans la variable
    __version__
    du fichier
    setup.py
  • Mettre à jour le
    CHANGELOG.md
    structuré en fonction des messages de commit
changelog updated by semantic release
  • Crée un tag correspondant à la version trouvée
  • Push les modifications sur la branche master
  • Créer une release gitlab
release gitlab

A ce moment master est à jour, je décide d'appliquer également ces modifications sur la branche dev en faisant un cherry pick

La version générée par l'outil ne prend pas en compte la mise à jour de la version majeure. Par définition elle est importante et doit être forcer volontairement. J'ai donc une variable d'env

FORCE_SEMVER_MAJOR
qui lorsqu'elle est mise à 1 ajoute l'option
 --major
lors de l'exécution du script :
pipenv run semantic-release -v version ${FORCE_SEMVER_MAJOR:+"--major"}

Il ne vous reste plus qu'à déployer votre application depuis le tag git généré, via

Ansible
par exemple