Of hoe je in één avond moeiteloos mooie badges voor je project kunt creëren.
Waarschijnlijk heeft elke ontwikkelaar met minstens één pet-project op een bepaald moment de drang om mooie badges te maken met statussen, code coverage, versies van pakketten in NuGet... En deze drang leidde me tot het schrijven van dit artikel. Tijdens de voorbereiding op het schrijven ervan heb ik deze schoonheid in een van mijn projecten gekregen:

In dit artikel wordt de basisconfiguratie voor continue integratie en levering voor een .Net Core class library project in GitLab besproken, met publicatie van documentatie in GitLab Pages en het verzenden van de verzamelde pakketten naar een private feed in Azure DevOps.
Als ontwikkelomgeving werd VS Code gebruikt met de extensie (voor de validatie van het configuratiebestand direct vanuit de ontwikkelomgeving).
Korte introductie
CD is wanneer je net hebt gepusht en de klant al alles is kwijtgeraakt?
Wat CI/CD is en waarom het nodig is - dat kun je gemakkelijk googelen. Volledige documentatie over het configureren van pipelines in GitLab vinden . Hier beschrijf ik kort en zo goed mogelijk het proces van het systeem vanuit vogelperspectief:
- de ontwikkelaar stuurt een commit naar de repository, maakt een merge request aan via de website, of start de pipeline op een andere manier, expliciet of impliciet,
- uit de configuratie worden alle taken geselecteerd waarvan de voorwaarden het toestaan om ze in deze context te starten,
- de taken worden georganiseerd volgens hun fasen,
- de fasen worden om de beurt uitgevoerd — dat wil zeggen, parallel worden alle taken van deze fase uitgevoerd,
- als een fase mislukt (d.w.z. als ten minste één taak van de fase mislukt) - stopt de pipeline (bijna altijd),
- als alle fasen succesvol zijn voltooid, wordt de pipeline als succesvol beschouwd.
Zo hebben we:
- een pipeline - een set taken, georganiseerd in fasen, waarin je de code kunt bouwen, testen, verpakken, de kant-en-klare build naar een cloudservice kunt implementeren, enz.
- fase (stage) - een eenheid van organisatie van de pipeline, bevat 1+ taak,
- taak (job) - een eenheid van werk in de pipeline. Bestaat uit een script (verplicht), voorwaarden voor uitvoering, instellingen voor publicatie/caching van artefacten en veel meer.
Daarom is de taak bij het instellen van CI/CD om een set taken te creëren die alle noodzakelijke handelingen voor het bouwen, testen en publiceren van code en artefacten realiseren.
Voorafgaand: waarom?
- Waarom GitLab?
Omdat er een behoefte ontstond om privé-repositories voor pet-projecten te creëren, en op GitHub waren ze betaalde, en ik ben - gierig. De repositories zijn nu gratis, maar dat is voor mij niet voldoende reden om naar GitHub te verhuizen.
- Waarom geen Azure DevOps Pipelines?
Omdat de configuratie daar eenvoudig is - er zijn geen kennis van de commandoregel vereist. Integratie met externe git-providers gaat met een paar klikken, het importeren van SSH-sleutels voor het verzenden van commits naar de repository ook, en de pipeline kan eenvoudig worden ingesteld, zelfs niet vanuit een sjabloon.
Huidige situatie: wat is er en wat willen we?
We hebben:
- een repository in GitLab.
We willen:
- automatische building en testing voor elke merge request,
- building van pakketten voor elke merge request en push naar master, mits er een bepaalde regel in de commitboodschap staat,
- het verzenden van de gebouwde pakketten naar een privé-feed in Azure DevOps,
- documentatie bouwen en publiceren op GitLab Pages,
- badges!11
De beschreven vereisten passen goed in het volgende model van de pipeline:
- Stap 1 - build
- We bouwen de code, de uitvoerbestanden publiceren we als artefacten
- Stap 2 - testen
- We ontvangen de artefacten van de build fase, draaien de tests en verzamelen de codecoverage-gegevens
- Stap 3 - verzenden
- Taak 1 - we bouwen het nuget-pakket en sturen het naar Azure DevOps
- Taak 2 - we bouwen de site uit xmldoc in de broncode en publiceren deze op GitLab Pages
Laten we beginnen!
We verzamelen de configuratie
We bereiden accounts voor
We creëren een account in
We gaan naar
We creëren een nieuw project
- Naam - elk
- Zichtbaarheid - elk

Bij het klikken op de knop Create wordt het project aangemaakt en zal er een doorgang zijn naar de pagina ervan. Op deze pagina kunnen we onnodige functies uitschakelen door naar de projectinstellingen te gaan (onderste link in de lijst links -> Overview -> blok Azure DevOps Services)

We gaan naar Atrifacts, klikken op Create feed
- We voeren de naam van de bron in
- We kiezen de zichtbaarheid
- We halen het vinkje weg Include packages from common public sources, zodat de bron niet in een rommelmond verandert met gekloonde nuget

We klikken op Connect to feed, kiezen Visual Studio en kopiëren de Source uit het blok Machine Setup

We gaan naar de accountinstellingen, kiezen Personal Access Token

We creëren een nieuw toegangstoken
- Naam - willekeurig
- Organisatie - huidig
- De looptijd is maximaal 1 jaar
- Toepassingsgebied (scope) — Packaging/Read & Write

Gekopieerde token — na het sluiten van het modaal venster is de waarde niet meer beschikbaar
Ga naar de instellingen van de repository in GitLab, selecteer CI/CD-instellingen

Vouw het blok Variabelen uit, voeg een nieuwe toe
- Naam — elk zonder spaties (beschikbaar in de commandoregel)
- Waarde — toegangstoken uit punt 9
- Selecteer Mask variable

Dit is het einde van de voorlopige configuratie.
We bereiden de configuratiestructuur voor
Standaard wordt voor CI/CD-configuratie in GitLab het bestand gebruikt .gitlab-ci.yml uit de hoofdrepository. Een willekeurige pad naar dit bestand kan bijvoorbeeld in de repository-instellingen worden ingesteld, maar in dit geval is dat niet nodig.
Zoals te zien is aan de extensie, bevat het bestand configuratie in het formaat YAML. De documentatie beschrijft gedetailleerd welke sleutels op het hoogste niveau van de configuratie kunnen worden opgenomen, en in elk van de geneste niveaus.
Laten we eerst in het configuratiebestand een link opnemen naar de docker-image waarin de taken worden uitgevoerd. Zoek hiervoor de wordt een tabel met wijzigingen en instructies voor de overgang naar de nieuwe configuratie gegeven. Voor meer informatie, zie waar een gedetailleerde handleiding te vinden is over welke afbeelding voor verschillende taken te kiezen. Voor onze build is de afbeelding met .Net Core 3.1 geschikt, dus laten we deze als eerste regel in de configuratie toevoegen
image: mcr.microsoft.com/dotnet/core/sdk:3.1Nu, wanneer de pipeline wordt gestart, zal de opgegeven afbeelding van Microsoft's image repository worden gedownload, waarin alle taken uit de configuratie zullen worden uitgevoerd.
De volgende stap is om stagefasen toe te voegen. Standaard definieert GitLab 5 fasen:
.prewordt uitgevoerd vóór alle fasen,.postwordt uitgevoerd na alle fasen,buildde eerste na.prefase,testde tweede fase,deployde derde fase.
Er staat niets in de weg om ze expliciet te verklaren. De volgorde waarin de fasen zijn opgegeven, beïnvloedt de volgorde waarin ze worden uitgevoerd. Voor de volledigheid voegen we aan de configuratie toe:
stages:
- build
- test
- deployVoor debugging is het zinvol om informatie over de omgeving waarin taken worden uitgevoerd te verkrijgen. Laten we een globale set opdrachten toevoegen die vóór elke taak worden uitgevoerd, met behulp van before_script:
before_script:
- $PSVersionTable.PSVersion
- dotnet --version
- nuget help | select-string VersionWe moeten minstens één taak toevoegen, zodat de pipeline wordt gestart bij het indienen van commits. Voor nu voegen we een lege taak toe voor demonstratiedoeleinden:
dummy job:
script:
- echo okWe start validation, receive a message that everything is fine, commit, push, and check the results on the site... And we get a script error — bash: .PSVersion: command not found. WTF?
Everything makes sense — by default, runners (responsible for executing task scripts and provided by GitLab) use bash to execute commands. We can fix this by explicitly specifying in the job description which tags should be assigned to the executing pipeline runner:
dummy job on windows:
script:
- echo ok
tags:
- windowsGreat! Now the pipeline is executing.
An attentive reader, repeating the above steps, will notice that the job was executed in the stage test, although we did not specify a stage. As you might guess, test is the default stage.
Let's continue creating the configuration skeleton by adding all the tasks described above:
build job:
script:
- echo "building..."
tags:
- windows
stage: build
test and cover job:
script:
- echo "running tests and coverage analysis..."
tags:
- windows
stage: test
pack and deploy job:
script:
- echo "packing and pushing to nuget..."
tags:
- windows
stage: deploy
pages:
script:
- echo "creating docs..."
tags:
- windows
stage: deployWe received a not very functional, yet correct pipeline.
Trigger configuration
Since no filters have been specified for any of the jobs, the pipeline will be fully executed on every commit sent to the repository. Since this is not the desired behavior in general, we will configure trigger filters for the jobs.
Filters can be configured in two formats: en . In short, only/except allows configuring filters by triggers (merge_request, for example — configures the job to run on every merge request creation and every commit sent to the branch that is the source of the merge request) and by branch names (including using regular expressions); rules allows configuring a set of conditions and, optionally, changing the job execution condition based on the success of preceding tasks ().
Let's recall the set of requirements — building and testing only for merge requests, packaging and sending to Azure DevOps — for merge requests and pushes to master, generating documentation — for pushes to master.
To start, let's configure the code build task by adding a trigger rule only for merge requests:
build job:
# snip
only:
- merge_requestLaten we nu de pack taak instellen om te triggeren bij een merge request en het toevoegen van commits aan de master:
pack en deploy job:
# snip
only:
- merge_request
- masterZoals te zien is, is het eenvoudig en recht toe recht aan.
Daarnaast kan de taak zo worden ingesteld dat deze alleen wordt getriggerd wanneer er een merge request is aangemaakt met een specifieke doel- of bronbranch:
rules:
- if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"In voorwaarden kunnen de volgende variabelen worden gebruikt ; regels rules zijn niet compatibel met regels only/except.
Instellingen voor het opslaan van artifacts
Tijdens de uitvoering van de taak build job zullen er build artifacts worden aangemaakt die opnieuw kunnen worden gebruikt in volgende taken. Hiervoor moeten we de paden en bestanden aan de taakconfiguratie toevoegen die we willen opslaan en hergebruiken in volgende taken, in de sleutel :
build job:
# snip
artifacts:
paths:
- path/to/build/artifacts
- another/path
- MyCoolLib.*/bin/Release/*Paden ondersteunen wildcards, wat zeker het instellen ervan vergemakkelijkt.
Als de taak artifacts aanmaakt, kan elke volgende taak er toegang toe krijgen — ze worden op dezelfde paden ten opzichte van de root van de repository geplaatst als waar ze vanuit de oorspronkelijke taak zijn opgebouwd. Ook zijn de artifacts beschikbaar voor download op de site.
Nu we een (en geteste) configuratie schets hebben, kunnen we overgaan tot het schrijven van scripts voor de taken.
We schrijven scripts
Misschien was het ooit een pijn om projecten (ook .net) vanaf de commandoregel te bouwen in een verre, verre galaxy. Nu kunnen we een project in 3 opdrachten bouwen, testen en publiceren:
dotnet build
dotnet test
dotnet packNatuurlijk zijn er enkele nuances waardoor we de opdrachten iets ingewikkelder maken.
- We willen een releasebuild en geen debugbuild, dus voegen we aan elke opdracht toe
-c Release - Bij het testen willen we gegevens over de codecovering verzamelen, dus moeten we een coverage-analyse-tool in de testbibliotheken opnemen:
- Voeg het pakket toe aan alle testbibliotheken
coverlet.msbuild:dotnet add package coverlet.msbuilduit de projectmap - Laten we de testopdracht aanvullen met
/p:CollectCoverage=true - Voeg in de configuratie van de testtaak een sleutel toe voor het verkrijgen van de coverage-resultaten (zie hieronder)
- Voeg het pakket toe aan alle testbibliotheken
- Bij het verpakken van de code in nuget-pakketten stellen we de uitvoermap voor de pakketten in:
-o .
We verzamelen de gegevens over de codecovering
Coverlet geeft na het uitvoeren van de tests statistieken over de uitvoeringen weer in de console:
Bereken resultaat dekking...
Genereer rapport 'C:Usersxxxsourcereposmy-projectmyProject.testscoverage.json'
+-------------+--------+--------+--------+
| Module | Regel | Tak | Methode |
+-------------+--------+--------+--------+
| project 1 | 83,24% | 66,66% | 92,1% |
+-------------+--------+--------+--------+
| project 2 | 87,5% | 50% | 100% |
+-------------+--------+--------+--------+
| project 3 | 100% | 83,33% | 100% |
+-------------+--------+--------+--------+
+---------+--------+--------+--------+
| | Regel | Tak | Methode |
+---------+--------+--------+--------+
| Totaal | 84,27% | 65,76% | 92,94% |
+---------+--------+--------+--------+
| Gemiddelde | 90,24% | 66,66% | 97,36% |
+---------+--------+--------+--------+GitLab geeft de mogelijkheid om een reguliere expressie op te geven om statistieken te verkrijgen, die vervolgens als badge kunnen worden weergegeven. De reguliere expressie kan worden opgegeven in de taakinstellingen met de sleutel coverage; de expressie moet een capturegroep bevatten, waarvan de waarde naar de badge wordt verzonden:
test en dekkingstaak:
# snip
dekking: \/|s*Totals*|s*(d+[,.]d+%)\/Hier krijgen we statistieken uit de regel met de totale dekking.
Publiceer pakketten en documentatie
Beide acties zijn toegewezen aan de laatste stap van de pipeline — aangezien de build en tests geslaagd zijn, kunnen we onze bevindingen met de wereld delen.
Laten we beginnen met de publicatie naar de pakketbron:
Als er geen nuget-configuratiebestand in het project aanwezig is (
nuget.config), laten we dan een nieuwe creëren:dotnet new nugetconfigWaarom: in de afbeelding kan toegang tot globale (gebruikers- en machine-) configuraties zijn uitgeschakeld. Om fouten te voorkomen, creëren we gewoon een nieuwe lokale configuratie en werken we daarmee.
- Laten we een nieuwe pakketbron aan de lokale configuratie toevoegen:
nuget sources add -name <name> -source <url> -username <organization> -password <gitlab variable> -configfile nuget.config -StorePasswordInClearTextnaam— lokale naam van de bron, niet essentieelurl— URL van de bron uit de stap "Accounts voorbereiden", punt 6organization— naam van de organisatie in Azure DevOpsgitlab variable— naam van de variabele met de toegangstoken, toegevoegd in GitLab ("Accounts voorbereiden", punt 11). Natuurlijk in de vorm van$variableName-StorePasswordInClearText— hack om toegangsfouten te omzeilen ()- In geval van fouten kan het nuttig zijn om toe te voegen
-verbosity detailed
- Stuur het pakket naar de bron:
nuget push -source <name> -skipduplicate -apikey <key> *.nupkg- Verzend alle pakketten vanuit de huidige directory, daarom
*.nupkg. naam— van de stap hierboven.key— elke string. In Azure DevOps wordt altijd de stringaz.-skipduplicate— bij poging om een al bestaand pakket zonder deze sleutel te verzenden, zal de bron een foutmelding geven409 Conflict; met sleutel wordt verzending overgeslagen.
- Verzend alle pakketten vanuit de huidige directory, daarom
Laten we nu de documentatie aanmaken:
- Om te beginnen, in de repository, in de master branch, initialiseren we het docfx-project. Voer hiervoor de opdracht uit vanuit de root
docfx initen in de interactieve modus stellen we de belangrijkste parameters in voor het bouwen van de documentatie. Een gedetailleerde beschrijving van de minimale projectconfiguratie .- Bij het configureren is het belangrijk om de uitvoerpaden op te geven
..public— GitLab neemt standaard de inhoud van de public map in de root van de repository als bron voor Pages. Aangezien het project zich in een map binnen de repository zal bevinden, voegen we het pad uit naar boven toe.
- Bij het configureren is het belangrijk om de uitvoerpaden op te geven
- Laten we de wijzigingen naar GitLab sturen.
- We voegen een taak toe aan de pipeline-configuratie
pages(gereserveerd woord voor taken voor sitepublicaties in GitLab Pages):- Script:
nuget install docfx.console -version 2.51.0— installeert docfx; de versie is opgegeven om de juistheid van de installatiepaden te waarborgen..docfx.console.2.51.0toolsdocfx.exe .docfx_projectdocfx.json— we bouwen de documentatie
- Artikelen onderdeel:
- Script:
pages:
# snip
artifacts:
paths:
- publicLyrische uitweiding over docfx
Eerder gaf ik bij het instellen van het project de codebron voor de documentatie op als een oplossingsbestand. Het grootste nadeel is dat documentatie ook voor testprojecten wordt aangemaakt. Als dit niet nodig is, kan een waarde voor de knoop worden ingesteld als metadata.src:
{
"metadata": [
{
"src": [
{
"src": "..\/",
"files": [
"**\/*.csproj"
],
"exclude":[
"*.tests*\/**"
]
}
],
\/\/ --- snip ---
},
\/\/ --- snip ---
],
\/\/ --- snip ---
}metadata.src.src: "..\/"— we gaan een niveau omhoog ten opzichte van de locatiedocfx.json, aangezien in patronen geen omhoogzoeken door de directorystructuur werkt.metadata.src.files: ["**\/*.csproj"]— globale patroon, we verzamelen alle C#-projecten uit alle mappen.metadata.src.exclude: ["*.tests*\/**"]— globale patroon, we sluiten alles uit mappen met.testsin de naam
Voorlopige conclusie
Deze eenvoudige configuratie kan letterlijk in een half uur en een paar kopjes koffie worden gemaakt, waardoor bij elke samenvoeging en push naar master wordt gecontroleerd of de code wordt gebouwd en de tests slagen, zodat een nieuw pakket wordt samengesteld, de documentatie wordt bijgewerkt en de ogen worden verwend met mooie badges in de README van het project.
Eind .gitlab-ci.yml
afbeelding: 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+%)//
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:
- masterOver badges gesproken
Daarvoor werd het alles gepland!
Badges met statussen van de pipeline en code coverage zijn beschikbaar in GitLab in de CI/CD-instellingen in het deel Gtntral pipelines:

De badge met een link naar de documentatie heb ik gemaakt op het platform — het is daar vrij eenvoudig, je kunt je eigen badge maken en deze ophalen met een verzoek.

Azure DevOps Artifacts stelt je ook in staat om badges voor pakketten te maken met de huidige versie. Hiervoor moet je op de website van Azure DevOps op 'Create badge' bij het gekozen pakket klikken en de markdown-code kopiëren:


Voeg wat flair toe
We benadrukken de gemeenschappelijke configuratiefragmenten
Tijdens het schrijven van de configuratie en het doorzoeken van de documentatie stuitte ik op een interessante mogelijkheid van YAML — het hergebruiken van fragmenten.
Zoals te zien is in de taakinstellingen, vereisen ze allemaal dat er een tag windows op de runner is, en ze worden geactiveerd bij het indienen naar master/het creëren van een merge-verzoek (behalve de documentatie). Laten we dit toevoegen aan het fragment dat we zullen hergebruiken:
.common_tags: &common_tags
tags:
- windows
.common_only: &common_only
only:
- merge_requests
- masterEn nu kunnen we het eerder gedefinieerde fragment in de taakbeschrijving invoegen:
build job:
<<: *common_tags
<<: *common_onlyDe namen van de fragmenten moeten met een punt beginnen, zodat ze niet als taak worden geïnterpreteerd.
Versiebeheer van pakketten
Bij het aanmaken van een pakket controleert de compiler de commandoregelargumenten en, bij afwezigheid daarvan, de projectbestanden; wanneer het knooppunt Version wordt gevonden, neemt hij de waarde ervan als de versie van het te bouwen pakket. Dit betekent dat om een pakket met een nieuwe versie te bouwen, je ofwel de versie in het projectbestand moet bijwerken, of deze als commandoregelargument moet doorgeven.
Laten we nog een wens toevoegen – laat de laatste twee nummers van de versie het jaar en de datum van het bouwen van het pakket zijn en laten we prerelease-versies toevoegen. Natuurlijk kunnen we deze gegevens in het projectbestand opnemen en bij elke verzending controleren - maar we kunnen dit ook in de pipeline doen, de versie van het pakket uit de context verzamelen en deze via een commandoregelargument doorgeven.
Laten we aannemen dat als er in de commitboodschap een regel is van de vorm release (v. / ver. / versie) <versienummer> (rev. / revisie <revisie>)?, we de versie van het pakket uit deze regel zullen halen, deze aanvullen met de huidige datum en doorgeven als argument aan het commando dotnet pack. Bij afwezigheid van deze regel – bouwen we gewoon het pakket niet.
Deze taak wordt opgelost door het volgende script:
# регулярное выражение для поиска строки с версией
$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=$versionVoeg het script toe aan de taak pack and deploy job en observeer het bouwen van pakketten strikt als de opgegeven regel in de commitboodschap aanwezig is.
Total
Na ongeveer een half uur tot een uur tijd besteed aan het schrijven van de configuratie, debugging in lokale PowerShell en misschien een paar mislukte uitvoeringen, hebben we een eenvoudige configuratie voor het automatiseren van routinetaken gekregen.
Natuurlijk is GitLab CI / CD veel uitgebreider en veelzijdiger dan het lijkt na het lezen van deze gids – . Daar is zelfs , wat
automatisch uw applicaties detecteert, bouwt, test, implementeert en monitort
Nu is het plan om de pipeline te configureren voor de uitrol van applicaties naar Azure, met behulp van Pulumi en automatische detectie van de doelomgeving, wat in het volgende artikel aan bod zal komen.
Bron: habr.com








