Ou comment obtenir de jolis badges pour votre projet en une soirée de codage détendu
Il est probable que chaque développeur ayant au moins un projet personnel ressente à un moment donné l'envie d'avoir de jolis badges avec des statuts, la couverture de code, les versions des paquets dans nuget... Et cette envie m'a amené à écrire cet article. Dans le cadre de la préparation de cet article, j'ai créé cette beauté dans l'un de mes projets :

Cet article abordera la configuration de base de l'intégration et de la livraison continues pour un projet de bibliothèque de classes sur .Net Core dans GitLab, avec la publication de la documentation dans GitLab Pages et l'envoi des paquets construits vers un flux privé dans Azure DevOps.
L'environnement de développement utilisé était VS Code avec l'extension (pour la validation du fichier de configuration directement depuis l'environnement de développement).
Introduction rapide
CD, c'est quand vous avez juste poussé, et que tout est tombé chez le client ?
Qu'est-ce que CI/CD et pourquoi en avez-vous besoin — cela se trouve facilement sur Google. Trouver la documentation complète sur la configuration des pipelines dans GitLab . Ici, je vais décrire brièvement et, autant que possible, sans erreurs, le fonctionnement du système avec une vue d'ensemble :
- le développeur envoie un commit dans le dépôt, crée une merge request via le site, ou bien lance implicitement ou explicitement le pipeline,
- toutes les tâches dont les conditions permettent un lancement dans ce contexte sont sélectionnées à partir de la configuration,
- les tâches sont organisées selon leurs étapes,
- les étapes sont exécutées l'une après l'autre — c'est-à-dire que toutes les tâches de cette étape sont exécutées en parallèle, si une étape échoue (c'est-à-dire si au moins une des tâches de l'étape échoue) — le pipeline s'arrête (
- presque toujourssi toutes les étapes réussissent, le pipeline est considéré comme réussi.),
- Ainsi, nous avons :
un pipeline est un ensemble de tâches organisées en étapes, dans lequel on peut compiler, tester, empaqueter du code, déployer une version prête dans un service cloud, etc.,
- une étape (
- stage) — une unité d'organisation du pipeline, contenant 1+ tâche,une tâche (
- job) — une unité de travail dans le pipeline. Elle se compose d'un script (obligatoire), de conditions d'exécution, de paramètres de publication/de mise en cache des artefacts et bien d'autres.) — unité de travail dans le pipeline. Composée d'un script (obligatoire), des conditions de lancement, des paramètres de publication/caching des artefacts et bien d'autres choses.
Ainsi, la tâche lors de la configuration de CI/CD consiste à créer un ensemble de tâches réalisant toutes les actions nécessaires pour la compilation, les tests et la publication du code et des artefacts.
Avant de commencer : pourquoi ?
- Pourquoi GitLab ?
Parce que lorsque j'avais besoin de créer des dépôts privés pour des projets personnels, ils étaient payants sur GitHub, et j'étais avare. Les dépôts sont devenus gratuits, mais cela ne me pousse pas encore à déménager sur GitHub.
- Pourquoi pas Azure DevOps Pipelines ?
Parce que la configuration y est élémentaire — même des connaissances en ligne de commande ne sont pas nécessaires. L'intégration avec des fournisseurs git externes se fait en quelques clics, l'importation des clés SSH pour envoyer des commits dans le dépôt également, le pipeline se configure facilement même sans template.
Position de départ : ce que nous avons et ce que nous voulons
Nous avons :
- un dépôt dans GitLab.
Nous voulons :
- une compilation et des tests automatiques pour chaque merge request,
- une compilation des paquets pour chaque merge request et un push dans master sous condition qu'une certaine ligne soit présente dans le message de commit,
- l'envoi des paquets compilés dans un feed privé dans Azure DevOps,
- la compilation de la documentation et sa publication dans GitLab Pages,
- des badges !11
Les exigences décrites s'appliquent à la modèle de pipeline suivante :
- Étape 1 — compilation
- Nous compilons le code, publions les fichiers de sortie comme artefacts
- Étape 2 — tests
- Nous récupérons les artefacts de l'étape de compilation, exécutons les tests, collectons les données de couverture de code
- Étape 3 — envoi
- Tâche 1 — nous créons un package nuget et l'envoyons dans Azure DevOps
- Tâche 2 — nous créons le site à partir du xmldoc dans le code source et le publions dans GitLab Pages
Allons-y !
Configurons
Préparons les comptes
Créons un compte dans
Passons à
Créons un nouveau projet
- Nom — n'importe lequel
- Visibilité — n'importe laquelle

En appuyant sur le bouton Créer, le projet sera créé et nous serons redirigés vers sa page. Sur cette page, nous pouvons désactiver les fonctionnalités inutiles en allant dans les paramètres du projet (lien en bas dans la liste à gauche -> Aperçu -> bloc Azure DevOps Services)

Allons dans Atrifacts, cliquons sur Créer un feed
- Entrons le nom de la source
- Choisissons la visibilité
- Décochons la case Inclure les paquets de sources publiques communes, pour que la source ne devienne pas une poubelle clone de nuget

Cliquons sur Connecter au feed, choisissons Visual Studio, et copions la Source du bloc Configuration Machine

Allons dans les paramètres du compte, choisissons Jeton d'accès personnel

Créons un nouveau jeton d'accès
- Nom — aléatoire
- Organisation — actuelle
- Durée maximale — 1 an
- Portée — Packaging/Read & Write

Nous copions le jeton créé — après la fermeture de la fenêtre modale, la valeur ne sera plus accessible
Accédez aux paramètres du dépôt dans GitLab, sélectionnez les paramètres CI/CD

Développez le bloc Variables, ajoutez un nouveau
- Nom — quelque chose sans espaces (sera accessible dans le shell)
- Valeur — le jeton d'accès du point 9
- Sélectionnez Masquer la variable

Ainsi, la configuration préliminaire est terminée.
Préparons l'ébauche de configuration
Par défaut, pour configurer CI/CD dans GitLab, le fichier .gitlab-ci.yml à la racine du dépôt. Il est possible de configurer un chemin arbitraire vers ce fichier dans les paramètres du dépôt, mais dans ce cas, ce n'est pas nécessaire.
Comme le montre l'extension, le fichier contient la configuration au format YAML. La documentation décrit en détail quels clés peuvent être présentes au niveau supérieur de la configuration, ainsi qu'à chacun des niveaux imbriqués.
Ajoutons d'abord au fichier de configuration un lien vers l'image docker où les tâches seront exécutées. Pour cela, nous trouvons . Il y a un guide détaillé sur le choix de l'image pour différentes tâches. Pour la construction, nous avons besoin de l'image .Net Core 3.1, donc nous ajoutons sans hésitation la première ligne dans la configuration
image: mcr.microsoft.com/dotnet/core/sdk:3.1Désormais, lors du lancement du pipeline à partir du registre d'images Microsoft, l'image spécifiée sera téléchargée, et toutes les tâches de la configuration y seront exécutées.
L'étape suivante consiste à ajouter ) — une unité d'organisation du pipeline, contenant 1+ tâche,'étapes. Par défaut, GitLab définit 5 étapes :
.pre— exécuté avant toutes les étapes,.post— exécuté après toutes les étapes,build— la première après.preétape,test— la deuxième étape,deploy— la troisième étape.
Rien n'empêche de les déclarer explicitement. L'ordre dans lequel les étapes sont indiquées influence l'ordre dans lequel elles sont exécutées. Pour plus de clarté, ajoutons dans la configuration :
stages:
- build
- test
- deployPour le débogage, il est judicieux d'obtenir des informations sur l'environnement dans lequel les tâches sont exécutées. Ajoutons un ensemble de commandes global, qui sera exécuté avant chaque tâche, à l'aide de before_script:
before_script:
- $PSVersionTable.PSVersion
- dotnet --version
- nuget help | select-string VersionIl ne reste plus qu'à ajouter au moins une tâche, afin que le pipeline se lance lors de l'envoi des commits. Pour l'instant, ajoutons une tâche vide pour démonstration :
dummy job:
script:
- echo okNous lançons la validation, recevons le message que tout va bien, nous commitons, pushons, regardons les résultats sur le site... Et nous obtenons une erreur de script — bash: .PSVersion: commande introuvable. WTF?
Tout est logique — par défaut, les runners (responsables de l'exécution des scripts de tâches, fournis par GitLab) utilisent bash pour exécuter les commandes. Cela peut être corrigé en spécifiant explicitement dans la description de la tâche quels tags doivent avoir le runner exécutant le pipeline :
tâche fictive sur windows:
script:
- echo ok
tags:
- windowsParfait ! Le pipeline s'exécute maintenant.
Le lecteur attentif, en répétant les étapes mentionnées, remarquera que la tâche a été exécutée à l'étape test, même si nous n'avons pas précisé d'étape. Comme on peut le deviner, test c'est l'étape par défaut.
Continuons la création de la configuration en ajoutant toutes les tâches décrites ci-dessus :
tâche de construction:
script:
- echo "construction..."
tags:
- windows
stage: build
tâche de test et de couverture:
script:
- echo "exécution des tests et analyse de couverture..."
tags:
- windows
stage: test
pack et déployer:
script:
- echo "emballage et envoi vers nuget..."
tags:
- windows
stage: deploy
pages:
script:
- echo "création de la documentation..."
tags:
- windows
stage: deployNous avons obtenu un pipeline pas tellement fonctionnel, mais néanmoins correct.
Configuration des déclencheurs
Puisque aucun filtre de déclenchement n'est spécifié pour les tâches, le pipeline sera entièrement exécuté à chaque envoi de commits dans le dépôt. Cela n’étant généralement pas le comportement souhaité, nous allons configurer des filtres de déclenchement pour les tâches.
Les filtres peuvent être configurés dans deux formats : et . En résumé, only/except permet de configurer des filtres par déclencheurs (merge_request, par exemple — configure une tâche pour s'exécuter à chaque création d'une demande de fusion et à chaque envoi de commits dans la branche qui est la source de la demande de fusion) et par noms de branches (y compris l'utilisation d'expressions régulières) ; rules permet de configurer un ensemble de conditions et, en option, de modifier la condition d'exécution d'une tâche en fonction du succès des tâches précédentes ().
Rappelons-nous l'ensemble des exigences — construction et test uniquement pour les demandes de fusion, emballage et envoi à Azure DevOps — pour les demandes de fusion et les push sur la branche maître, génération de documentation — pour les push sur la branche maître.
Pour commencer, configurons la tâche de construction de code en ajoutant une règle de déclenchement uniquement pour les demandes de fusion :
tâche de construction:
# snip
only:
- merge_requestNous allons maintenant configurer la tâche de packaging pour qu'elle se déclenche lors d'une merge request et de l'ajout de commits dans master :
pack and deploy job:
# snip
only:
- merge_request
- masterComme vous pouvez le constater, tout est simple et direct.
Vous pouvez également configurer la tâche pour qu'elle se déclenche uniquement si une merge request est créée avec une branche cible ou source spécifique :
rules:
- if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"Dans les conditions, vous pouvez utiliser ; les règles rules ne sont pas compatibles avec les règles only/except.
Configuration de la sauvegarde des artefacts
Lors de l'exécution de la tâche build job des artefacts de build seront créés, que vous pourrez réutiliser dans les tâches suivantes. Pour cela, il faut ajouter dans la configuration de la tâche les chemins, les fichiers à sauvegarder et à réutiliser dans les prochaines tâches, dans la clé :
build job:
# snip
artifacts:
paths:
- path/to/build/artifacts
- another/path
- MyCoolLib.*/bin/Release/*Les chemins prennent en charge les jokers, ce qui facilite certainement leur définition.
Si la tâche crée des artefacts, chaque tâche suivante pourra y accéder — ils se trouveront aux mêmes chemins par rapport à la racine du dépôt, que ceux par lesquels ils ont été assemblés à partir de la tâche source. Les artefacts sont également disponibles pour téléchargement sur le site.
Maintenant que nous avons le cadre de configuration prêt (et vérifié), nous pouvons passer à l'écriture des scripts pour les tâches.
Écrivons des scripts
Il était peut-être une fois, dans une galaxie lointaine, que construire des projets (y compris .net) depuis la ligne de commande était un calvaire. Maintenant, construire, tester et publier un projet peut se faire en 3 commandes :
dotnet build
dotnet test
dotnet packNaturellement, il y a quelques détails pour lesquels nous allons compliquer un peu les commandes.
- Nous voulons une compilation de release, et non une compilation de debug, donc nous ajoutons à chaque commande
-c Release - Lors des tests, nous voulons rassembler des données sur la couverture de code, donc il faudra activer l'analyseur de couverture dans les bibliothèques de tests :
- Il faut ajouter le package dans toutes les bibliothèques de tests
coverlet.msbuild:dotnet add package coverlet.msbuilddepuis le dossier du projet - Dans la commande de lancement des tests, nous ajouterons
/p:CollectCoverage=true - Dans la configuration de la tâche de test, nous ajouterons une clé pour obtenir les résultats de couverture (voir ci-dessous)
- Il faut ajouter le package dans toutes les bibliothèques de tests
- Lors de l'empaquetage du code en packages nuget, nous définirons le répertoire de sortie pour les packages :
-o .
Rassembler les données de couverture de code
Coverlet après exécution des tests affiche dans la console des statistiques sur l'exécution :
Calcul de la couverture...
Génération du rapport 'C:Usersxxxsourcereposmy-projectmyProject.testscoverage.json'
+-------------+--------+--------+--------+
| Module | Ligne | Branche | Méthode |
+-------------+--------+--------+--------+
| projet 1 | 83,24% | 66,66% | 92,1% |
+-------------+--------+--------+--------+
| projet 2 | 87,5% | 50% | 100% |
+-------------+--------+--------+--------+
| projet 3 | 100% | 83,33% | 100% |
+-------------+--------+--------+--------+
+---------+--------+--------+--------+
| | Ligne | Branche | Méthode |
+---------+--------+--------+--------+
| Total | 84,27% | 65,76% | 92,94% |
+---------+--------+--------+--------+
| Moyenne | 90,24% | 66,66% | 97,36% |
+---------+--------+--------+--------+GitLab permet de spécifier une expression régulière pour obtenir des statistiques, qui peuvent ensuite être récupérées sous forme de badge. L'expression régulière est indiquée dans les paramètres de la tâche avec la clé couverture; l'expression doit contenir un groupe de capture, dont la valeur sera transmise au badge :
test et travail de couverture :
# snip
couverture : \/|s*Totals*|s*(d+[,.]d+%)\/Ici, nous extrayons les statistiques de la ligne contenant la couverture globale.
Publication de paquets et de documentation
Les deux actions sont programmées pour la dernière étape du pipeline — maintenant que la construction et les tests sont passés, nous pouvons partager nos résultats avec le monde.
Commençons par examiner la publication dans la source des paquets :
Si le projet ne contient pas de fichier de configuration nuget (
nuget.config), créons un nouveau :dotnet new nugetconfigPourquoi : l'image peut interdire l'accès en écriture aux configurations globales (utilisateur et machine). Pour éviter les erreurs, créons simplement une nouvelle configuration locale et travaillons avec celle-ci.
- Ajoutons un nouveau source de paquets à la configuration locale :
nuget sources add -name <name> -source <url> -username <organization> -password <gitlab variable> -configfile nuget.config -StorePasswordInClearTextnom— nom local de la source, pas essentielurl— URL de la source de l'étape "Préparer les comptes", p. 6organization— nom de l'organisation dans Azure DevOpsgitlab variable— nom de la variable avec le jeton d'accès, ajoutée dans GitLab ("Préparer les comptes", p. 11). Naturellement, au format$variableName-StorePasswordInClearText— hack pour contourner l'erreur d'accès refusé ()- En cas d'erreurs, il peut être utile d'ajouter
-verbosity detailed
- Envoi du paquet vers la source :
nuget push -source <name> -skipduplicate -apikey <key> *.nupkg- Nous envoyons tous les paquets du répertoire actuel, donc
*.nupkg. nom— de l'étape précédente.key— n'importe quelle chaîne. Dans Azure DevOps, dans la fenêtre Connect to feed, on fournit toujours un exemple de chaîneaz.-skipduplicate— lors de la tentative d'envoi d'un paquet déjà existant sans cette clé, la source renverra une erreur409 Conflit; avec la clé, l'envoi sera ignoré.
- Nous envoyons tous les paquets du répertoire actuel, donc
Configurons maintenant la création de la documentation :
- Pour commencer, dans le dépôt, sur la branche master, nous allons initialiser le projet docfx. Pour cela, il faut exécuter la commande depuis la racine
docfx initet en mode interactif, nous fournirons les paramètres clés pour la compilation de la documentation. Description détaillée de la configuration minimale du projet .- Lors de la configuration, il est important d'indiquer le répertoire de sortie
..public— GitLab prend par défaut le contenu du dossier public à la racine du dépôt comme source pour Pages. Comme le projet sera situé dans un dossier imbriqué dans le dépôt, nous devons ajouter un chemin de sortie vers le niveau supérieur.
- Lors de la configuration, il est important d'indiquer le répertoire de sortie
- Envoyons les modifications dans GitLab.
- Dans la configuration du pipeline, ajoutons une tâche
pages(mot réservé pour les tâches de publication de sites dans GitLab Pages) :- Script :
nuget install docfx.console -version 2.51.0— installera docfx ; la version est indiquée pour garantir la validité des chemins d'installation du package..docfx.console.2.51.0toolsdocfx.exe .docfx_projectdocfx.json— nous compilons la documentation
- Nœud artifacts :
- Script :
pages:
# snip
artifacts:
paths:
- publicPetite réflexion sur docfx
Auparavant, lors de la configuration du projet, j'avais spécifié la source du code pour la documentation comme un fichier de solution. Le principal inconvénient est que la documentation est également créée pour les projets de test. Dans le cas où cela n'est pas nécessaire, on peut spécifier une valeur pour le nœud metadata.src:
{
"metadata": [
{
"src": [
{
"src": "..\/",
"files": [
"**\/*.csproj"
],
"exclude":[
"*.tests*\/**"
]
}
],
\/\/ --- snip ---
},
\/\/ --- snip ---
],
\/\/ --- snip ---
}metadata.src.src: "..\/"— nous montons d'un niveau par rapport à la positiondocfx.json, car dans les motifs, la recherche vers le haut dans l'arborescence des répertoires ne fonctionne pas.metadata.src.files: ["**\/*.csproj"]— motif global, nous rassemblons tous les projets C# de tous les répertoires.metadata.src.exclude: ["*.tests*\/**"]— motif global, nous excluons tout des dossiers avec.testsdans le nom
Conclusion intermédiaire
Cette simple configuration peut être réalisée en à peine une demi-heure et une paire de tasses de café, permettant de vérifier à chaque demande de fusion et d'envoi vers master que le code se compile et que les tests passent, de rassembler un nouveau paquet, de mettre à jour la documentation et d'embellir le projet avec de jolis badges dans le README.
Fichier .gitlab-ci.yml final
image: mcr.microsoft.com/dotnet/core/sdk:3.1
before_script:
- $PSVersionTable.PSVersion
- dotnet --version
- nuget help | select-string Version
stages:
- build
- test
- deploy
build job:
stage: build
script:
- dotnet build -c Release
tags:
- windows
only:
- merge_requests
- master
artifacts:
paths:
- your/path/to/binaries
test and cover job:
stage: test
tags:
- windows
script:
- dotnet test -c Release /p:CollectCoverage=true
coverage: /|s*Totals*|s*(\d+[,.]\d+%)\/\n only:
- merge_requests
- master
pack and deploy job:
stage: deploy
tags:
- windows
script:
- dotnet pack -c Release -o .
- dotnet new nugetconfig
- nuget sources add -name feedName -source https://pkgs.dev.azure.com/your-organization/_packaging/your-feed/nuget/v3/index.json -username your-organization -password $nugetFeedToken -configfile nuget.config -StorePasswordInClearText
- nuget push -source feedName -skipduplicate -apikey az *.nupkg
only:
- master
pages:
tags:
- windows
stage: deploy
script:
- nuget install docfx.console -version 2.51.0
- $env:path = "$env:path;$($(get-location).Path)"
- .docfx.console.2.51.0toolsdocfx.exe .docfxdocfx.json
artifacts:
paths:
- public
only:
- masterÀ propos des badges
C'est pour cela que tout a été conçu !
Les badges avec les statuts de pipeline et la couverture de code sont disponibles dans GitLab dans les paramètres CI/CD dans le bloc Pipelines central :

J'ai créé un badge avec un lien vers la documentation sur la plateforme — c'est assez simple, on peut créer son propre badge et l'obtenir grâce à une requête.

Azure DevOps Artifacts permet également de créer des badges pour les paquets avec la version actuelle. Pour cela, sur le site Azure DevOps, il faut cliquer sur Créer un badge pour le paquet choisi et copier la syntaxe markdown :


Ajoutons de l'esthétique
Mettons en évidence des fragments de configuration communs
Lors de la rédaction de la configuration et des recherches dans la documentation, j'ai découvert une fonctionnalité intéressante de YAML — la réutilisation de fragments.
Comme on peut le voir dans les paramètres des tâches, toutes requièrent la présence d'un tag windows sur le runner, et se déclenchent lors de l'envoi dans master/création d'une demande de fusion (excepté pour la documentation). Ajoutons cela dans le fragment que nous allons réutiliser :
.common_tags: &common_tags
tags:
- windows
.common_only: &common_only
only:
- merge_requests
- masterEt maintenant, dans la description de la tâche, nous pouvons insérer le fragment précédemment déclaré :
build job:
<<: *common_tags
<<: *common_onlyLes noms des fragments doivent commencer par un point pour ne pas être interprétés comme une tâche.
Versioning des paquets
Lors de la création d'un package, le compilateur vérifie les clés de la ligne de commande, et en leur absence, les fichiers de projet ; en trouvant le nœud Version, il prend sa valeur comme version du package à assembler. Ainsi, pour créer un package avec une nouvelle version, il faut soit la mettre à jour dans le fichier de projet, soit la passer en argument de la ligne de commande.
Ajoutons encore une capacité — que les deux derniers chiffres de la version soient l'année et la date de construction du package, et ajoutons des versions préliminaires. Bien sûr, il est possible d'ajouter ces données dans le fichier de projet et de les vérifier avant chaque envoi — mais on peut également le faire dans le pipeline, en construisant la version du package à partir du contexte et en la passant en argument de la ligne de commande.
Convengons que si dans le message de commit il y a une ligne de la forme release (v. / ver. / version) (rev. / revision ) ?, alors nous prendrons la version du package à partir de cette ligne, nous la compléterons avec la date actuelle et la passerons comme argument à la commande dotnet pack. En l'absence de cette ligne, nous ne construirons tout simplement pas le package.
La tâche est résolue par le script suivant :
# регулярное выражение для поиска строки с версией
$rx = "releases+(v.?|ver.?|version)s*(?<maj>d+)(?<min>.d+)?(?<rel>.d+)?s*((rev.?|revision)?s+(?<rev>[a-zA-Z0-9-_]+))?"
# ищем строку в сообщении коммита, передаваемом в одной из предопределяемых GitLab'ом переменных
$found = $env:CI_COMMIT_MESSAGE -match $rx
# совпадений нет - выходим
if (!$found) { Write-Output "no release info found, aborting"; exit }
# извлекаем мажорную и минорную версии
$maj = $matches['maj']
$min = $matches['min']
# если строка содержит номер релиза - используем его, иначе - текущий год
if ($matches.ContainsKey('rel')) { $rel = $matches['rel'] } else { $rel = ".$(get-date -format "yyyy")" }
# в качестве номера сборки - текущие месяц и день
$bld = $(get-date -format "MMdd")
# если есть данные по пререлизной версии - включаем их в версию
if ($matches.ContainsKey('rev')) { $rev = "-$($matches['rev'])" } else { $rev = '' }
# собираем единую строку версии
$version = "$maj$min$rel.$bld$rev"
# собираем пакеты
dotnet pack -c Release -o . /p:Version=$versionAjoutons le script à la tâche pack and deploy job et observons la construction des packages uniquement en présence de la ligne spécifiée dans le message de commit.
Au total
Après avoir passé environ une demi-heure à une heure à écrire la configuration, déboguer dans le powershell local et, peut-être, quelques échecs d'exécution, nous avons obtenu une configuration simple pour automatiser les tâches routinières.
Bien sûr, GitLab CI / CD est beaucoup plus vaste et multifacette qu'il n'y paraît après avoir lu ce guide — . Il y a même , permettant de
détecter, construire, tester, déployer et surveiller automatiquement vos applications
Les prochaines étapes consistent à configurer un pipeline pour le déploiement d'applications sur Azure, en utilisant Pulumi et en déterminant automatiquement l'environnement cible, ce qui sera abordé dans l'article suivant.
Source : habr.com








