Eller hur man får vackra badges för sitt projekt på en kväll av avslappnad kodning
För varje utvecklare med åtminstone ett pet-projekt dyker det troligtvis en gång i tiden upp en längtan efter vackra badges med statusar, kodtäckning, versioner av paket i nuget... Och den längtan ledde mig till att skriva denna artikel. Under förberedelserna för att skriva den här artikeln skaffade jag mig den här skönheten i ett av mina projekt:

Artikeln kommer att behandla grundinställningen av kontinuerlig integration och leverans för ett klassbiblioteksprojekt på .Net Core i GitLab, med publicering av dokumentation i GitLab Pages och sändning av de byggda paketen till en privat feed i Azure DevOps.
Som utvecklingsmiljö användes VS Code med tillägget (för validering av konfigurationsfilen direkt från utvecklingsmiljön).
Kort introduktion
CD — är det när du just har pushat och kunden redan har allt kraschat?
Vad CI/CD är och varför det behövs kan lätt googlas. Heltäckande dokumentation för att ställa in pipelines i GitLab går . Här kommer jag kortfattat och så noggrant som möjligt att beskriva systemets arbetsprocess från ett fågelperspektiv:
- utvecklaren skickar en commit till repositoryn, skapar en merge request via webbplatsen, eller på något annat sätt uttryckligen eller tyst startar en pipeline,
- från konfigurationen väljs alla uppgifter vars villkor tillåter dem att startas i detta sammanhang,
- uppgifterna organiseras enligt sina faser,
- faserna utförs i tur och ordning — det vill säga, parallellt utförs alla uppgifter i denna fas,
- om fasen misslyckas (det vill säga om minst en uppgift i fasen misslyckas) — stannar pipelinen (nästan alltid),
- om alla faser avslutas framgångsrikt, anses pipelinen ha genomgått med framgång.
Således har vi:
- en pipeline — en uppsättning uppgifter organiserade i faser, där man kan bygga, testa, paketera kod, distribuera en färdig byggnad till en molntjänst, m.m.,
- en fas (stage) — en enhet av organisering av pipelinen, innehåller 1+ uppgifter,
- en uppgift (job) — en enhet av arbete i pipelinen. Består av ett skript (obligatoriskt), villkor för start, inställningar för publicering/cache av artefakter och mycket annat.
Därför handlar uppgiften vid inställning av CI/CD om att skapa en uppsättning uppgifter som implementerar alla nödvändiga åtgärder för att bygga, testa och publicera kod och artefakter.
Innan vi börjar: varför?
- Varför GitLab?
För att när behovet uppstod att skapa privata repositorier för pet-projekt, var de betalningsbara på GitHub, och jag — var snål. Repositories blev gratis, men det är fortfarande inte tillräckligt för mig att flytta till GitHub.
- Varför inte Azure DevOps Pipelines?
För att där är inställningen enkel — inga kunskaper om kommandoraden krävs. Integrationen med externa git-leverantörer — bara några klick, import av SSH-nycklar för att skicka commits till repository — också, pipelinen är lätt att ställa in även utan en mall.
Utgångsläge: vad har vi och vad vill vi ha?
Vi har:
- ett repository i GitLab.
Vi vill ha:
- automatisk byggnation och testning för varje merge request,
- bygga paket för varje merge request och push till master under förutsättning att ett visst textavsnitt finns i commit-meddelandet,
- skicka de byggda paketen till ett privat feed i Azure DevOps,
- bygga dokumentation och publicera på GitLab Pages,
- badges!11
De beskrivna kraven passar bra in på följande pipelinemodell:
- Steg 1 — byggnation
- Vi bygger koden, utdatafiler publiceras som artefakter
- Steg 2 — testning
- Vi får artefakterna från byggsteget, kör tester och samlar in täckningsdata för koden
- Steg 3 — skickande
- Uppgift 1 — vi bygger nuget-paketet och skickar det till Azure DevOps
- Uppgift 2 — vi bygger webbplatsen från xmldoc i källkoden och publicerar på GitLab Pages
Låt oss börja!
Vi samlar konfigurationen
Vi förbereder konton
Vi skapar ett konto i
Vi går till
Vi skapar ett nytt projekt
- Namn — valfritt
- Synlighet — vilken som helst

När du klickar på knappen Skapa kommer projektet att skapas och du kommer att omdirigeras till dess sida. På denna sida kan du stänga av onödiga funktioner genom att gå till projektinställningarna (den nedre länken i vänstermenyn -> Översikt -> blocket Azure DevOps Services)

Vi går till Atrifacts, klickar på Skapa feed
- Vi anger namnet på källan
- Vi väljer synlighet
- Vi avmarkerar rutan Inkludera paket från vanliga offentliga källor, så att källan inte blir en sophög av klonade nuget

Vi klickar på Anslut till feed, väljer Visual Studio, kopierar källan från blocket Maskininställning

Vi går till kontoinställningarna, väljer Personlig åtkomsttoken

Vi skapar en ny åtkomsttoken
- Namn — valfritt
- Organisation — nuvarande
- Upphört — högst 1 år
- Omfång (scope) — Packaging/Read & Write

Vi kopierar den skapade token — efter att det modala fönstret stängs kommer värdet att inte vara tillgängligt
Gå till inställningarna för lagret i GitLab, välj CI/CD-inställningar

Expandera blocket Variabler, lägg till en ny
- Namn — valfritt utan mellanslag (kommer vara tillgängligt i kommandoraden)
- Värde — åtkomsttoken från punkt 9
- Välj Mask variable

Denna preliminära inställning är klar.
Förbereda konfigurationsram
Som standard används en fil .gitlab-ci.yml från rotkatalogen av lagret för att konfigurera CI/CD i GitLab. Det går att ställa in vilken sökväg som helst till denna fil i lagrets inställningar, men i detta fall är det inte nödvändigt.
Som det framgår av filens förlängning innehåller filen konfiguration i formatet YAML. Dokumentationen beskriver utförligt vilka nycklar som kan finnas på det översta nivån av konfigurationen, och vid varje av de inre nivåerna.
Först lägger vi till en länk till docker-bilden i konfigurationsfilen, där uppgifterna kommer att köras. För detta finns det . I det finns en detaljerad handledning om vilken bild som ska väljas för olika uppgifter. För byggande passar bilden med .Net Core 3.1, så vi lägger till följande rad i konfigurationen
image: mcr.microsoft.com/dotnet/core/sdk:3.1Nu, när pipelinen startas, kommer den angivna bilden att laddas ner från Microsofts bildlager, där alla uppgifter från konfigurationen kommer att utföras.
Nästa steg — lägg till stagefaser. Som standard definierar GitLab 5 faser:
.pre— körs före alla faser,.post— körs efter alla faser,build— den första efter.prefasen,test— den andra fasen,deploy— den tredje fasen.
Det hindrar inget från att förklara dem uttryckligen. Ordningen i vilken faserna anges påverkar den ordning i vilken de utförs. För fullständighetens skull lägger vi till i konfigurationen:
stages:
- build
- test
- deployFör felsökning är det meningsfullt att få information om miljön där uppgifterna körs. Låt oss lägga till en global uppsättning kommandon som kommer att köras före varje uppgift, med hjälp av before_script:
before_script:
- $PSVersionTable.PSVersion
- dotnet --version
- nuget help | select-string VersionDet återstår att lägga till åtminstone en uppgift så att pipelinen startar vid commit. Låt oss än så länge lägga till en tom uppgift för demonstration:
dummy job:
script:
- echo okVi startar validering, får meddelandet att allt är bra, kommittar, pushar och kollar resultaten på webbplatsen… Och får ett skriptfel — bash: .PSVersion: kommandot hittades inte. Vad i hela friden?
Det är logiskt — som standard använder runnerna (som ansvarar för att köra uppgiftsskript och tillhandahålls av GitLab) bash för att köra kommandon. Du kan åtgärda detta genom att specifikt ange i uppgiftsbeskrivningen vilka etiketter den exekverande pipeline-runnar måste ha:
dummy job på windows:
script:
- echo ok
tags:
- windowsPerfekt! Nu körs pipelinen.
Den uppmärksamma läsaren som upprepar de angivna stegen kommer att märka att uppgiften kördes i etappen test, även om vi inte angav någon etapp. Som man kan gissa, test är det standardsteget.
Låt oss fortsätta skapa konfigurationsstrukturen genom att lägga till alla uppgifter som beskrivs ovan:
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: deployVi fick en inte särskilt funktionell, men ändå korrekt pipeline.
Inställning av triggar
Eftersom inga filter för triggering har angivits för någon av uppgifterna kommer pipelinen att fullständigt köras vid varje push av commits till förvaret. Eftersom detta inte är önskat beteende i allmänhet kommer vi att ställa in triggar för uppgifterna.
Filter kan ställas in i två format: och . Kortfattat tillåter only/except att ställa in filter baserat på triggar (merge_request, till exempel — ställer in en uppgift för att köras vid varje skapande av en sammanslagningsförfrågan och vid varje push av commits till den gren som är ursprung i sammanslagningsförfrågan) och grenintjäningsnamn (inklusive med användning av reguljära uttryck); rules tillåter ställa in en uppsättning av villkor och, valfritt, ändra villkoret för att köra uppgiften beroende på framgången av tidigare uppgifter ().
Låt oss påminna oss om uppsättningen krav — bygga och testa endast för merge request, paketera och distribuera till Azure DevOps — för merge request och pushes till master, generera dokumentation — för pushes till master.
Till att börja med, låt oss ställa in en uppgift för kodbyggande genom att lägga till en triggräregel som bara aktiveras vid merge request:
build job:
# snip
only:
- merge_requestLåt oss nu konfigurera paketuppgiften att utlösas av merge request och när commits läggs till i master:
pack and deploy job:
# snip
only:
- merge_request
- masterSom syns är det enkelt och rakt på sak.
Det går också att konfigurera uppgiften så att den endast utlöses om en merge request har skapats med en specifik mål- eller källgren:
rules:
- if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"I villkoren kan man använda ; regler rules är oförenliga med regler only/except.
Konfiguration av artefaktbevarande
Under uppgiftens utförande build job kommer vi att skapa byggartefakter som kan återanvändas i efterföljande uppgifter. För detta behöver vi i uppgiftens konfiguration lägga till vägar, de filer som ska bevaras och återanvändas i följande uppgifter, under nyckeln :
build job:
# snip
artifacts:
paths:
- path/to/build/artifacts
- another/path
- MyCoolLib.*/bin/Release/*Vägarna stödjer wildcards, vilket definitivt förenklar deras angivande.
Om uppgiften skapar artefakter, kommer varje efterföljande uppgift att kunna få åtkomst till dem — de kommer att finnas på samma vägar relativt rotmappen för förrådet som de samlades från den ursprungliga uppgiften. Artefakterna är också tillgängliga för nedladdning på webbplatsen.
Nu när vi har en färdig (och verifierad) ram för konfigurationen kan vi gå vidare till att skriva skripten för uppgifterna.
Skriver skript
Kanske för länge sedan, i en avlägsen galax, var det en smärta att bygga projekt (inklusive på .net) från kommandoraden. Nu kan man bygga, testa och publicera ett projekt med 3 kommandon:
dotnet build
dotnet test
dotnet packNaturligtvis finns det vissa subtiliteter som gör att vi komplicerar kommandona lite.
- Vi vill ha en release-version, inte en debug-version, så vi lägger till
-c Release - Vid testning vill vi samla in täckningsdata, så vi behöver ansluta en täckningsanalysator i testbiblioteken:
- I alla testbibliotek bör paketet
coverlet.msbuild:dotnet add package coverlet.msbuildfrån projektmappen - Till kommandot för att köra testerna lägger vi till
/p:CollectCoverage=true - I konfigurationen för testuppgiften lägger vi till en nyckel för att få täckningsresultat (se nedan)
- I alla testbibliotek bör paketet
- När koden packas i nuget-paket anger vi utmatningskatalogen för paketen:
-o .
Samlar in täckningsdata för koden
Coverlet skriver ut statistik om körningen till konsolen efter att testerna har körts:
Beräknar täckningsresultat...
Genererar rapport 'C:Usersxxxsourcereposmy-projectmyProject.testscoverage.json'
+-------------+--------+--------+--------+
| Modul | Rad | Gren | Metod |
+-------------+--------+--------+--------+
| projekt 1 | 83,24% | 66,66% | 92,1% |
+-------------+--------+--------+--------+
| projekt 2 | 87,5% | 50% | 100% |
+-------------+--------+--------+--------+
| projekt 3 | 100% | 83,33% | 100% |
+-------------+--------+--------+--------+
+---------+--------+--------+--------+
| | Rad | Gren | Metod |
+---------+--------+--------+--------+
| Totalt | 84,27% | 65,76% | 92,94% |
+---------+--------+--------+--------+
| Genomsnitt | 90,24% | 66,66% | 97,36% |
+---------+--------+--------+--------+GitLab låter dig ange ett reguljärt uttryck för att hämta statistik, som sedan kan visas som en badge. Det reguljära uttrycket anges i inställningarna för uppgiften med nyckeln coverage; i uttrycket måste det finnas en fångstgrupp, vars värde kommer att överföras till badgen:
test and cover job:
# snip
coverage: \/|s*Totals*|s*(d+[,.]d+%)\/Här får vi statistiken från raden med total täckning baserat på rader.
Publicera paket och dokumentation
Båda åtgärderna är inplanerade till det sista steget i pipelinen — eftersom både bygg och tester har gått igenom, kan vi dela våra resultat med omvärlden.
Först ska vi titta på publiceringen till paketkällan:
Om projektet saknar en nuget konfigurationsfil (
nuget.config), låt oss skapa en ny:dotnet new nugetconfigVarför: i bilden kan skrivåtkomst till globala (användar- och maskinkonfigurationer) vara förbjuden. För att undvika fel, skapa bara en ny lokal konfiguration och arbeta med den.
- Lägg till en ny paketkälla i den lokala konfigurationen:
nuget sources add -name <name> -source <url> -username <organization> -password <gitlab variable> -configfile nuget.config -StorePasswordInClearTextname— lokalt namn på källan, inte kritiskturl— URL från steget "Förbereder konton", p. 6organization— namnet på organisationen i Azure DevOpsgitlab variable— namnet på variabeln med åtkomsttoken, tillagd i GitLab ("Förbereder konton", p. 11). Självklart i formatet$variableName-StorePasswordInClearText— hack för att kringgå åtkomstfel ()- För eventuella fel kan det vara användbart att lägga till
-verbosity detailed
- Skicka paket till källan:
nuget push -source <name> -skipduplicate -apikey <key> *.nupkg- Vi skickar alla paket från den aktuella katalogen, så
*.nupkg. name— från steget ovan.key— vilken sträng som helst. I Azure DevOps ges alltid en sträng som exempel i fönstret Anslut till feedaz.-skipduplicate— när man försöker skicka ett redan existerande paket utan denna nyckel kommer källan att returnera ett fel.409 Konflikt; med nyckeln skickas det vidare.
- Vi skickar alla paket från den aktuella katalogen, så
Nu ska vi ställa in skapandet av dokumentationen:
- För att börja, i arkivet, i master-grenen, initierar vi docfx-projektet. För detta, från roten, behöver vi köra kommandot
docfx initoch i interaktivt läge ställa in nyckelparametrarna för att bygga dokumentationen. En detaljerad beskrivning av den minimala konfigurationen av projektet .- Vid konfiguration är det viktigt att ange utmatningskatalogen
..public— GitLab använder som standard innehållet i public-mappen i rotkatalogen för att hämta källan till Pages. Eftersom projektet kommer att ligga i en undermapp av arkivet — lägg till sökvägen för att gå upp en nivå.
- Vid konfiguration är det viktigt att ange utmatningskatalogen
- Vi skickar ändringarna till GitLab.
- I pipeline-konfigurationen lägger vi till uppgiften
pages(ett reserverat ord för uppgifter som publicerar webbplatser i GitLab Pages):- Script:
nuget install docfx.console -version 2.51.0— installerar docfx; versionen anges för att säkerställa korrekta installationsvägar för paketet..docfx.console.2.51.0toolsdocfx.exe .docfx_projectdocfx.json— vi bygger dokumentationen
- Artifact-noden:
- Script:
pages:
# snip
artifacts:
paths:
- publicEtt lyriskt avsnitt om docfx
Tidigare angav jag källkoden för dokumentationen som en lösningsfil. Huvudnackdelen är att dokumentationen också skapas för testprojekt. Om det inte behövs kan man ange detta värde för noden metadata.src:
{
"metadata": [
{
"src": [
{
"src": "..\/",
"files": [
"**/*.csproj"
],
"exclude":[
"*.tests*\/**"
]
}
],
\/\/ --- snip ---
},
\/\/ --- snip ---
],
\/\/ --- snip ---
}metadata.src.src: "..\/"— vi går upp en nivå i förhållande till placeringendocfx.json, eftersom mönstren inte fungerar med sökning uppåt i katalogträdet.metadata.src.files: ["**/*.csproj"]— ett globalt mönster, vi samlar in alla C#-projekt från alla kataloger.metadata.src.exclude: ["*.tests*\/**"]— ett globalt mönster, vi utesluter allt från mappar med.testsi namnet
Mellanresultat
Så här enkel konfiguration kan faktiskt sättas samman på bara en halvtimme och ett par koppar kaffe, vilket gör att vi vid varje sammanslagningsförfrågan och inlämning i master kan kontrollera att koden byggs och testerna går igenom, bygga ett nytt paket, uppdatera dokumentationen och föra glädje med vackra badges i projektets README.
Slutgiltig .gitlab-ci.yml
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+%) /
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.0/tools/docfx.exe .docfx/docfx.json
artifacts:
paths:
- public
only:
- masterFörresten om badges
De är ju anledningen till allt detta!
Badges med status för pipeline och kodtäckning finns i GitLab i inställningarna för CI/CD under blocket Gtntral pipelines:

Badgen med länk till dokumentationen skapade jag på plattformen — där är allt tillräckligt enkelt, man kan skapa sin egen badge och hämta den med en förfrågan.

Azure DevOps Artifacts gör det också möjligt att skapa badges för paket med angiven aktuell version. För att göra detta, på källan på Azure DevOps-webben, klicka på Skapa badge för det valda paketet och kopiera markdown-märkningen:


Lägg till lite skönhet
Markera gemensamma konfigurationsfragment
Under skrivandet av konfigurationen och när jag sökte efter dokumentation, snubblade jag över en intressant möjlighet i YAML — återanvändning av fragment.
Som syns från uppgiftsinställningarna kräver alla att det finns en tagg windows vid runner, och de utlöses vid skickande till master/skapande av pull request (förutom dokumentationen). Vi lägger till detta i fragmentet som vi ska återanvända:
.common_tags: &common_tags
tags:
- windows
.common_only: &common_only
only:
- merge_requests
- masterOch nu kan vi i uppgiftsbeskrivningen infoga det tidigare deklarerade fragmentet:
build job:
<<: *common_tags
<<: *common_onlyNamnen på fragmenten måste börja med en punkt för att inte tolkas som en uppgift.
Versionshantering av paket
Vid skapandet av ett paket kontrollerar kompilatorn kommandoradens nycklar och i deras frånvaro projektfiler; när den hittar noden Version tar den dess värde som version av det paketeras. Således, för att bygga ett paket med en ny version, måste man antingen uppdatera den i projektfilen eller skicka den som ett kommandoradsargument.
Låt oss lägga till en önskan till — de två sista siffrorna i versionen ska vara året och datumet för paketets skapande, och vi lägger till förhandsversioner. Självklart kan vi lägga till dessa data i projektfilen och kontrollera dem före varje uppladdning — men vi kan också göra det i pipelinen, samla paketversionen från kontexten och skicka den som ett kommandoradsargument.
Låt oss avtala att om meddelandet i commit innehåller en rad som ser ut som release (v.\/ver.\/version) <version number> (rev.\/revision <revision>)?, kommer vi att ta versionen av paketet från denna rad, komplettera den med det aktuella datumet och skicka den som ett argument till kommandot dotnet pack. I avsaknad av rader — kommer vi helt enkelt inte att bygga paketet.
Denna uppgift löses av följande skript:
# регулярное выражение для поиска строки с версией
$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=$versionVi lägger till skriptet i uppgiften pack and deploy job och övervakar paketskapandet strikt vid närvaro av den angivna stringen i commit-meddelandet.
Sammanfattningsvis
Efter att ha tillbringat cirka 30 minuter till en timme på att skriva konfigurationen, debugga i lokal PowerShell och kanske några misslyckade körningar, fick vi en enkel konfiguration för automatisering av rutinuppgifter.
Självklart är GitLab CI/CD mycket mer omfattande och mångfacetterat än vad som kan verka efter att ha läst denna guide — . Det finns till och med , som gör det möjligt
automatiskt upptäcka, bygga, testa, distribuerar och övervakar dina program
Nu i planerna — att konfigurera pipelinen för distribution av applikationer i Azure, med användning av Pulumi och automatiskt definiera målmiljön, vilket kommer att belystas i nästa artikel.
Källa: habr.com








