O come ottenere badge bellissimi per il tuo progetto in una sola serata di programmazione senza stress
Probabilmente ogni sviluppatore, che ha almeno un progetto personale, in un certo momento prova il desiderio di avere badge eleganti con stati, copertura del codice, versioni dei pacchetti in nuget... E questa voglia mi ha portato a scrivere questo articolo. Nel processo di preparazione alla sua stesura ho ottenuto questa bellezza in uno dei miei progetti:

L'articolo esaminerà la configurazione di base dell'integrazione continua e della distribuzione per un progetto di libreria classi su .Net Core in GitLab, con la pubblicazione della documentazione in GitLab Pages e l'invio dei pacchetti compilati a un feed privato in Azure DevOps.
Come ambiente di sviluppo è stato utilizzato VS Code con l'estensione (per la convalida del file di configurazione direttamente dall'ambiente di sviluppo).
Breve introduzione
CD è quando hai appena fatto un push e il cliente ha già tutto giù?
Cos'è CI/CD e perché è necessario - si può facilmente cercare online. Trovare una documentazione completa sulla configurazione dei pipeline in GitLab . Qui descriverò brevemente e per quanto possibile senza errori il processo di lavoro del sistema dall'alto:
- lo sviluppatore invia un commit nel repository, crea una merge request tramite il sito web, o avvia esplicitamente o implicitamente il pipeline in altro modo,
- dalla configurazione vengono selezionate tutte le attività le cui condizioni consentono di avviarle in questo contesto,
- le attività sono organizzate in base ai loro passaggi,
- i passaggi vengono eseguiti uno dopo l'altro - cioè. in parallelo tutte le attività di questo passaggio vengono eseguite,
- se il passaggio termina con un errore (cioè se almeno una delle attività del passaggio termina con un errore) - il pipeline si ferma (quasi sempre),
- se tutti i passaggi sono stati completati con successo, il pipeline viene considerato completato con successo.
In questo modo, abbiamo:
- un pipeline è un insieme di attività organizzate in passaggi, in cui è possibile compilare, testare, pacchettizzare il codice, distribuire la build pronta in un servizio cloud, ecc.,
- un passaggio (stage) è un'unità di organizzazione del pipeline, contiene 1+ attività,
- un'attività (job) è un'unità di lavoro nel pipeline. Consiste in uno script (obbligatorio), condizioni di avvio, impostazioni di pubblicazione/cache degli artefatti e molto altro.
Pertanto, l'obiettivo nella configurazione di CI/CD è quello di creare un insieme di attività che realizzino tutte le azioni necessarie per la build, il testing e la pubblicazione del codice e degli artefatti.
Prima di iniziare: perché?
- Perché GitLab?
Perché quando è emersa la necessità di creare repository privati per progetti personali, su GitHub erano a pagamento, e io sono avaro. I repository sono diventati gratuiti, ma questo non è ancora un motivo sufficiente per migrare su GitHub.
- Perché non Azure DevOps Pipelines?
Perché lì la configurazione è elementare — non sono nemmeno necessarie conoscenze della riga di comando. L'integrazione con fornitori esterni di git è questione di un paio di clic, l'importazione delle chiavi SSH per inviare commit al repository è altrettanto semplice, e la pipeline si configura facilmente anche senza un template.
Posizione di partenza: cosa abbiamo e cosa desideriamo
Abbiamo:
- un repository in GitLab.
Desideriamo:
- una build automatica e un testing per ogni merge request,
- la build di pacchetti per ogni merge request e il push in master a condizione che nel messaggio del commit sia presente una certa stringa,
- l'invio dei pacchetti assemblati in un feed privato in Azure DevOps,
- la build della documentazione e la pubblicazione in GitLab Pages,
- badge!11
I requisiti descritti si adattano perfettamente al seguente modello di pipeline:
- Fase 1 — build
- Costruiamo il codice, pubblichiamo i file di output come artefatti
- Fase 2 — testing
- Otteniamo gli artefatti dalla fase di build, eseguiamo i test, raccogliamo i dati di copertura del codice
- Fase 3 — invio
- Attività 1 — costruiamo un pacchetto nuget e lo inviamo in Azure DevOps
- Attività 2 — costruiamo il sito da xmldoc nel codice sorgente e pubblichiamo in GitLab Pages
Iniziamo!
Configuriamo
Prepariamo gli account
Creiamo un account in
Passiamo a
Creiamo un nuovo progetto
- Nome — qualsiasi
- Visibilità — qualsiasi

Premendo il pulsante Crea, il progetto verrà creato e si passerà alla sua pagina. In questa pagina è possibile disabilitare funzionalità non necessarie passando alle impostazioni del progetto (link in fondo a sinistra -> Panoramica -> blocco Azure DevOps Services)

Passiamo a Atrifacts, clicchiamo su Crea feed
- Inseriamo il nome della fonte
- Scegliamo la visibilità
- Deselezioniamo l'opzione Include packages from common public sources, per evitare che la fonte diventi un accumulo di cloni nuget

Clicchiamo su Connetti al feed, scegliamo Visual Studio, dal blocco Impostazione macchina copiamo la fonte

Andiamo nelle impostazioni dell'account, scegliamo Token di accesso personale

Creiamo un nuovo token di accesso
- Nome — a piacere
- Organizzazione — attuale
- La durata è massima di 1 anno
- Ambito di applicazione (scope) — Packaging/Read & Write

Copiamo il token creato — dopo la chiusura della finestra modale, il valore non sarà più disponibile
Accediamo alle impostazioni del repository in GitLab, selezioniamo le impostazioni CI/CD

Espandiamo il blocco Variabili, aggiungiamo un nuovo elemento
- Nome — qualsiasi senza spazi (sarà disponibile nella shell dei comandi)
- Valore — token di accesso al punto 9
- Selezioniamo Maschera variabile

Con questo abbiamo completato la configurazione preliminare.
Prepariamo la struttura di configurazione
Di default, per configurare CI/CD in GitLab si utilizza il file .gitlab-ci.yml nella radice del repository. È possibile configurare un percorso personalizzato per questo file nelle impostazioni del repository, ma in questo caso non è necessario.
Come si può vedere dall'estensione, il file contiene una configurazione nel formato YAML. La documentazione spiega in dettaglio quali chiavi possono essere presenti a livello superiore della configurazione e in ciascun livello annidato.
Per prima cosa, aggiungiamo al file di configurazione un collegamento all'immagine Docker, in cui verranno eseguite le attività. A tal fine, troviamo . In c'è una guida dettagliata su quale immagine scegliere per diverse attività. Per la compilazione, ci servirà l'immagine con .Net Core 3.1, quindi possiamo aggiungerla come prima riga nella configurazione
image: mcr.microsoft.com/dotnet/core/sdk:3.1Ora, quando avvii il pipeline dal repository, verrà scaricata l'immagine specificata da Microsoft e tutte le attività dalla configurazione verranno eseguite lì.
Il passaggio successivo è aggiungere stagestadi. Di default, GitLab definisce 5 stadi:
.pre— viene eseguito prima di tutti gli stadi,.post— viene eseguito dopo tutti gli stadi,build— primo dopo.prestagione,test— secondo stadio,deploy— terzo stadio.
Non c'è nulla che impedisca di dichiararli esplicitamente, tuttavia. L'ordine in cui sono elencati gli stadi influisce sull'ordine in cui vengono eseguiti. Per completezza, aggiungiamo alla configurazione:
stages:
- build
- test
- deployPer il debug, ha senso ottenere informazioni sull'ambiente in cui vengono eseguite le attività. Aggiungiamo un insieme globale di comandi che verrà eseguito prima di ogni attività, utilizzando before_script:
before_script:
- $PSVersionTable.PSVersion
- dotnet --version
- nuget help | select-string VersionRimane da aggiungere almeno un'attività, in modo che il pipeline si avvii all'invio di commit. Per ora, aggiungiamo un'attività vuota per dimostrazione:
dummy job:
script:
- echo okAvviamo la validazione, riceviamo un messaggio che va tutto bene, facciamo il commit, eseguiamo il push, controlliamo i risultati sul sito… E riceviamo un errore di script — bash: .PSVersion: comando non trovato. WTF?
Tutto ha senso — di default i runner (che si occupano dell'esecuzione degli script delle attività e forniti da GitLab) utilizzano bash per eseguire i comandi. Possiamo risolvere la cosa specificando esplicitamente nella descrizione dell'attività quali tag devono essere presenti nel runner del pipeline esecutore:
dummy job on windows:
script:
- echo ok
tags:
- windowsOttimo! Ora il pipeline viene eseguito.
Il lettore attento, ripetendo i passaggi indicati, noterà che l'attività è stata eseguita nella fase test, anche se non abbiamo specificato la fase. Come si può intuire, test è la fase predefinita.
Continuiamo a creare lo scheletro di configurazione, aggiungendo tutte le attività descritte sopra:
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: deployAbbiamo ottenuto un pipeline non particolarmente funzionale, ma comunque corretto.
Impostazione dei trigger
Poiché per nessuna delle attività sono stati specificati filtri di attivazione, il pipeline sarà completamente eseguito ad ogni invio di commit nel repository. Poiché questo non è desiderabile in generale, configureremo i filtri di attivazione per le attività.
I filtri possono essere configurati in due formati: e . In breve, only/except consente di configurare filtri basati sui trigger (merge_request, ad esempio — configura l'attività per essere eseguita ad ogni creazione di richiesta di merge e ad ogni invio di commit nel ramo che è la fonte della richiesta di merge) e sui nomi dei rami (incluso l'uso di espressioni regolari); rules consente di configurare un insieme di condizioni e, facoltativamente, modificare la condizione di esecuzione dell'attività in base al successo delle attività precedenti ().
Ricordiamo l'insieme di requisiti — build e test solo per le merge request, imballaggio e invio in Azure DevOps — per merge request e push nel master, generazione della documentazione — per push nel master.
Per iniziare, configuriamo l'attività di build del codice, aggiungendo una regola di attivazione solo per le merge request:
build job:
# snip
only:
- merge_requestOra configuriamo il lavoro di imballaggio per attivarsi su merge request e l'aggiunta di commit nel master:
pack and deploy job:
# snip
only:
- merge_request
- masterCome puoi vedere, è tutto semplice e diretto.
È possibile configurare il lavoro per attivarsi solo se viene creata una merge request con un ramo target o source specifico:
rules:
- if: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"Nelle condizioni puoi utilizzare ; le regole rules non sono compatibili con le regole only/except.
Configurazione per il salvataggio degli artefatti
Durante l'esecuzione del lavoro build job verranno creati artefatti di build che possono essere riutilizzati nei lavori successivi. Per questo è necessario aggiungere nella configurazione del lavoro i percorsi, i file che devono essere salvati e riutilizzati nei lavori successivi, nella chiave :
build job:
# snip
artifacts:
paths:
- path/to/build/artifacts
- another/path
- MyCoolLib.*/bin/Release/*I percorsi supportano i caratteri jolly, il che semplifica sicuramente la loro definizione.
Se il lavoro crea artefatti, ogni lavoro successivo potrà accedervi — saranno posizionati negli stessi percorsi relativi alla radice del repository, da cui sono stati creati dal lavoro originale. Gli artefatti sono anche disponibili per il download sul sito.
Ora che abbiamo pronto (e verificato) lo scheletro della configurazione, possiamo passare alla scrittura degli script per i lavori.
Scriviamo gli script
Forse, molto tempo fa, in una galassia lontana lontana, compilare progetti (compresi quelli .net) dalla riga di comando era un problema. Ora, invece, compilare, testare e pubblicare un progetto può essere fatto in 3 comandi:
dotnet build
dotnet test
dotnet packNaturalmente, ci sono alcune sfide che complicano un po' i comandi.
- Vogliamo una build di rilascio, non una di debug, quindi a ogni comando aggiungiamo
-c Release - Durante i test vogliamo raccogliere dati sulla copertura del codice, quindi sarà necessario integrare un analizzatore di copertura nelle librerie di test:
- In tutte le librerie di test è necessario aggiungere il pacchetto
coverlet.msbuild:dotnet add package coverlet.msbuilddalla cartella del progetto - Aggiungeremo al comando di esecuzione dei test
/p:CollectCoverage=true - Nella configurazione del lavoro di test aggiungeremo una chiave per ottenere i risultati della copertura (vedi sotto)
- In tutte le librerie di test è necessario aggiungere il pacchetto
- Quando impacchettiamo il codice in pacchetti nuget, specificheremo la directory di uscita per i pacchetti:
-o .
Raccogliamo i dati sulla copertura del codice
Coverlet, dopo l'esecuzione dei test, mostra nella console le statistiche dell'esecuzione:
Calcolo dei risultati di copertura...
Generazione del rapporto 'C:Usersxxxsourcereposmy-projectmyProject.testscoverage.json'
+-------------+--------+--------+--------+
| Modulo | Linea | Ramo | Metodo |
+-------------+--------+--------+--------+
| progetto 1 | 83,24% | 66,66% | 92,1% |
+-------------+--------+--------+--------+
| progetto 2 | 87,5% | 50% | 100% |
+-------------+--------+--------+--------+
| progetto 3 | 100% | 83,33% | 100% |
+-------------+--------+--------+--------+
+---------+--------+--------+--------+
| | Linea | Ramo | Metodo |
+---------+--------+--------+--------+
| Totale | 84,27% | 65,76% | 92,94% |
+---------+--------+--------+--------+
| Media | 90,24% | 66,66% | 97,36% |
+---------+--------+--------+--------+GitLab consente di specificare un'espressione regolare per ottenere statistiche, che poi possono essere visualizzate come un badge. L'espressione regolare viene specificata nelle impostazioni del task con la chiave copertura; nell'espressione deve essere presente un gruppo di cattura, il cui valore verrà passato al badge:
job di test e copertura:
# snip
copertura: \/|s*Totali*|s*(d+[,.]d+%)\/Qui otteniamo statistiche dalla stringa con la copertura totale per le righe.
Pubblicazione di pacchetti e documentazione
Entrambe le azioni sono pianificate per l'ultima fase del pipeline: dato che la build e i test sono stati completati, possiamo condividere le nostre scoperte con il mondo.
Iniziamo a considerare la pubblicazione in un repository di pacchetti:
Se nel progetto non è presente un file di configurazione nuget (
nuget.config), creiamo un nuovo file:dotnet new nugetconfigPerché: nell'immagine potrebbe essere vietato l'accesso in scrittura alle configurazioni globali (utente e macchina). Per evitare errori, creiamo semplicemente una nuova configurazione locale e lavoriamo con essa.
- Aggiungiamo un nuovo repository di pacchetti alla configurazione locale:
nuget sources add -name -source -username -password -configfile nuget.config -StorePasswordInClearTextname— nome locale del repository, non è decisivourl— URL del repository dalla fase "Preparazione degli account", punto 6organization— nome dell'organizzazione in Azure DevOpsgitlab variable— nome della variabile con il token di accesso, aggiunta in GitLab ("Preparazione degli account", punto 11). Naturalmente, nel formato$variableName-StorePasswordInClearText— hack per aggirare l'errore di accesso negato ()- In caso di errori, potrebbe essere utile aggiungere
-verbosity detailed
- Inviare il pacchetto al repository:
nuget push -source -skipduplicate -apikey *.nupkg- Invia tutti i pacchetti dalla directory corrente, quindi
*.nupkg. name— dal passaggio precedente.key— qualsiasi stringa. In Azure DevOps, nella finestra Connect to feed, viene sempre fornita come esempio la stringaaz.-skipduplicate— durante il tentativo di inviare un pacchetto già esistente senza questa chiave, la fonte restituirà un errore409 Conflitto; con la chiave l'invio sarà saltato.
- Invia tutti i pacchetti dalla directory corrente, quindi
Ora configuriamo la creazione della documentazione:
- Innanzitutto, nel repository, nel ramo master, inizializziamo il progetto docfx. Per fare ciò, dalla radice dobbiamo eseguire il comando
docfx inite in modalità interattiva indichiamo i parametri chiave per la build della documentazione. Una descrizione dettagliata della configurazione minima del progetto .- È importante specificare la directory di output
..public— GitLab per impostazione predefinita prende il contenuto della cartella public nella radice del repository come fonte per le Pages. Poiché il progetto si trova in una cartella all'interno del repository, aggiungiamo nel percorso l'uscita a un livello superiore.
- È importante specificare la directory di output
- Invieremo le modifiche in GitLab.
- Nella configurazione del pipeline aggiungeremo il compito
pages(parola riservata per i compiti di pubblicazione dei siti in GitLab Pages):- Script:
nuget install docfx.console -version 2.51.0— installerà docfx; la versione è indicata per garantire la correttezza dei percorsi di installazione del pacchetto..docfx.console.2.51.0toolsdocfx.exe .docfx_projectdocfx.json— compiliamo la documentazione
- Nodo artifacts:
- Script:
pages:
# snip
artifacts:
paths:
- publicRiflessione lirica su docfx
In precedenza, durante la configurazione del progetto, indicavo la fonte del codice per la documentazione come file della soluzione. Il principale svantaggio è che la documentazione viene creata anche per i progetti di test. Nel caso in cui ciò non sia necessario, è possibile specificare un valore per il nodo metadata.src:
{
"metadata": [
{
"src": [
{
"src": "..\/",
"files": [
"**/*.csproj"
],
"exclude":[
"*.tests*\/**"
]
}
],
\/\/ --- snip ---
},
\/\/ --- snip ---
],
\/\/ --- snip ---
}metadata.src.src: "..\/"— usciamo a un livello superiore rispetto alla posizionedocfx.json, poiché nei pattern non funziona la ricerca a ritroso nell'albero delle directory.metadata.src.files: ["**/*.csproj"]— pattern globale, raccogliamo tutti i progetti C# da tutte le directory.metadata.src.exclude: ["*.tests*\/**"]— pattern globale, escludiamo tutto dalle cartelle con.testsnel nome
Risultato intermedio
Una configurazione così semplice può essere creata letteralmente in mezz'ora e un paio di tazze di caffè, che permetterà ad ogni richiesta di unione e invio al master di verificare che il codice si compili e i test passino, compili un nuovo pacchetto, aggiorni la documentazione e soddisfi la vista con bei badge nel README del progetto.
File finale .gitlab-ci.yml
immagine: 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:
- masterA proposito dei badge
È per loro che tutto è stato progettato!
I badge con gli stati della pipeline e la copertura del codice sono disponibili in GitLab nelle impostazioni CI/CD nel blocco Gtntral pipelines:

Ho creato il badge con il link alla documentazione sulla piattaforma — lì è tutto piuttosto diretto, puoi creare il tuo badge e ottenerlo tramite richiesta.

Azure DevOps Artifacts consente anche di creare badge per i pacchetti indicando la versione attuale. Per fare ciò, nel sito di Azure DevOps, è necessario fare clic su Crea badge per il pacchetto selezionato e copiare la sintassi markdown:


Aggiungiamo un po' di bellezza
Evidenziamo i frammenti comuni di configurazione
Durante la scrittura della configurazione e la ricerca nella documentazione, ho trovato un'interessante funzionalità di YAML: il riutilizzo dei frammenti.
Come si può vedere dalle impostazioni delle attività, tutte richiedono un tag windows sul runner, e si attivano quando viene effettuato un invio nel master/creazione di una pull request (eccetto per la documentazione). Aggiungiamo ciò nel frammento che riutilizzeremo:
.common_tags: &common_tags
tags:
- windows
.common_only: &common_only
only:
- merge_requests
- masterE ora nella descrizione dell'attività possiamo inserire il frammento dichiarato in precedenza:
build job:
<<: *common_tags
<<: *common_onlyI nomi dei frammenti devono iniziare con un punto per non essere interpretati come attività.
Versioning dei pacchetti
Quando si crea un pacchetto, il compilatore controlla le chiavi della riga di comando e, in loro assenza, i file di progetto; trovando il nodo Version, ne prende il valore come versione del pacchetto generato. Pertanto, per costruire un pacchetto con una nuova versione, è necessario aggiornare quest'ultima nel file di progetto o passarla come argomento della riga di comando.
Aggiungiamo un'altra richiesta: i due numeri inferiori nella versione devono rappresentare l'anno e la data di costruzione del pacchetto, e dobbiamo aggiungere le versioni di pre-rilascio. È sicuramente possibile aggiungere questi dati al file del progetto e verificarli prima di ogni invio, ma ciò può essere fatto anche nel pipeline, costruendo la versione del pacchetto dal contesto e passando come argomento della riga di comando.
Conveniamo che se nel messaggio del commit c'è una riga del tipo release (v.\/ver.\/version) <numero della versione> (rev.\/revision <revision>)?, estrarremo da questa riga la versione del pacchetto, la completeremo con la data corrente e la passeremo come argomento al comando dotnet pack. In assenza della riga, semplicemente non costruiremo il pacchetto.
Questo compito è risolto dal seguente 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=$versionAggiungiamo lo script al compito pack and deploy job e osserviamo la costruzione dei pacchetti solo in presenza della riga richiesta nel messaggio del commit.
Totale
Dopo aver speso circa mezz'ora-ora per scrivere la configurazione, fare debugging in powershell locale e, forse, qualche esecuzione non riuscita, abbiamo ottenuto una configurazione semplice per automatizzare le attività quotidiane.
Certo, GitLab CI\/CD è molto più ampio e complesso di quanto possa sembrare dopo aver letto questa guida — . Lì c'è anche , che permette di
detect, build, test, deploy e monitorare automaticamente le tue applicazioni.
Ora nei piani c'è di configurare un pipeline per la distribuzione delle applicazioni in Azure, utilizzando Pulumi e la determinazione automatica dell'ambiente target, un argomento che sarà trattato nel prossimo articolo.
Fonte: habr.com








