O come ottenere dei badge eleganti per il proprio progetto in una sola serata di coding senza stress
Probabilmente, ogni sviluppatore con almeno un progetto personale sente a un certo punto il bisogno di avere dei badge belli con gli stati, la copertura del codice, le versioni dei pacchetti in nuget... E questo bisogno mi ha portato a scrivere questo articolo. Durante la preparazione per la sua scrittura, ho realizzato questa bellezza in uno dei miei progetti:

L'articolo tratterà della configurazione di base dell'integrazione e della distribuzione continua per un progetto di libreria di classi su .Net Core in GitLab, con pubblicazione della documentazione su GitLab Pages e invio dei pacchetti compilati a un feed privato in Azure DevOps.
Come ambiente di sviluppo, ho utilizzato VS Code con l'estensione (per la validazione del file di configurazione direttamente dall'ambiente di sviluppo).
Breve introduzione
CD è quando hai appena fatto push, e al cliente è già tutto andato in crash?
Cosa sono CI/CD e perché sono necessari — si può facilmente cercare su Google. Trovare la documentazione completa per la configurazione dei pipeline in GitLab non è difficile neanche. Qui descrivo brevemente, e per quanto possibile senza errori, il funzionamento del sistema dall'alto:
- lo sviluppatore invia un commit nel repository e crea una merge request tramite il sito, oppure avvia esplicitamente o implicitamente il pipeline in altro modo,
- dalla configurazione vengono selezionati tutti i task le cui condizioni consentono di avviarli in questo contesto,
- le attività sono organizzate in base alle loro fasi,
- le fasi vengono eseguite a turno — cioè, in parallelo tutti i task di questa fase vengono eseguiti,
- se una fase termina con un errore (cioè se almeno uno dei task della fase fallisce) — il pipeline si ferma (quasi sempre),
- se tutte le fasi terminano con successo, il pipeline è considerato riuscito.
In questo modo, abbiamo:
- un pipeline è un insieme di task organizzati in fasi, che possono essere utilizzati per assemblare, testare, impacchettare codice, distribuire la build finale su un servizio cloud, e altro ancora,
- fase (stage) — un'unità di organizzazione del pipeline, contiene 1+ task,
- task (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, il compito durante la configurazione del CI/CD consiste nel creare un insieme di attività che realizzino tutte le azioni necessarie per la costruzione, il test e la pubblicazione di codice e artefatti.
Prima di tutto: perché?
- Perché GitLab?
Perché, quando si è presentata la necessità di creare repository privati per progetti personali, su GitHub erano a pagamento, ed io ero — parsimonioso. I repository sono diventati gratuiti, ma questo non è ancora un motivo sufficiente per trasferirmi su GitHub.
- Perché non Azure DevOps Pipelines?
Perché là la configurazione è elementare — non servono neppure conoscenze della riga di comando. L'integrazione con fornitori git esterni avviene con un paio di clic, l'importazione delle chiavi SSH per inviare i commit nel repository avviene ugualmente, e il pipeline si configura facilmente anche senza un template.
Situazione attuale: cosa abbiamo e cosa desideriamo
Abbiamo:
- un repository su GitLab.
Desideriamo:
- una costruzione e un test automatici per ogni merge request,
- la costruzione di pacchetti per ogni merge request e il push nel master a condizione che ci sia una certa stringa nel messaggio del commit,
- l'invio dei pacchetti costruiti a un feed privato in Azure DevOps,
- la creazione della documentazione e la pubblicazione su GitLab Pages,
- badge!
I requisiti descritti si adattano perfettamente al seguente modello di pipeline:
- Fase 1 — costruzione
- Costruiamo il codice e pubblichiamo i file di output come artefatti
- Fase 2 — testing
- Otteniamo gli artefatti dalla fase di costruzione, eseguiamo i test e raccogliamo i dati sulla copertura del codice
- Fase 3 — invio
- Compito 1 — creiamo il pacchetto nuget e lo inviamo in Azure DevOps
- Compito 2 — costruiamo il sito da xmldoc nel codice sorgente e pubblichiamo su GitLab Pages
Iniziamo!
Configuriamo la configurazione
Prepariamo gli account
Creiamo un account in
Andiamo su
Creiamo un nuovo progetto
- Nome — a piacere
- Visibilità — qualsiasi

Facendo clic sul pulsante Crea, il progetto verrà creato e verrà effettuato il passaggio alla sua pagina. In questa pagina è possibile disattivare le funzionalità non necessarie accedendo alle impostazioni del progetto (collegamento in basso nella lista a sinistra -> Panoramica -> blocco Azure DevOps Services)

Andiamo su Artifacts, clicchiamo su Crea feed
- Inseriamo il nome della fonte
- Selezioniamo la visibilità
- Deselezioniamo Include packages from common public sources, affinché la fonte non diventi un raccoglitore di cloni nuget

Clicchiamo su Connetti al feed, selezioniamo Visual Studio, copiamo Source dal blocco Configurazione macchina

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

Creiamo un nuovo token di accesso
- Nome — a scelta
- Organizzazione — attuale
- Durata — massimo 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 una nuova
- Nome — qualsiasi, senza spazi (sarà disponibile nella shell dei comandi)
- Valore — token di accesso dal p. 9
- Selezioniamo Maschera variabile

A questo punto la configurazione preliminare è completata.
Prepariamo la struttura della configurazione
Per impostazione predefinita, per configurare CI/CD in GitLab si utilizza il file .gitlab-ci.yml nella radice del repository. Puoi configurare un percorso arbitrario 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 descrive in dettaglio quali chiavi possono essere presenti a livello superiore della configurazione e in ciascuno dei livelli nidificati.
Innanzitutto, 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 nostra compilazione, ci servirà l'immagine con .Net Core 3.1, quindi possiamo tranquillamente aggiungere come prima riga nella configurazione
image: mcr.microsoft.com/dotnet/core/sdk:3.1Ora, al momento dell'avvio della pipeline, l'immagine specificata verrà scaricata dal repository di immagini Microsoft, e tutte le attività della configurazione verranno eseguite in essa.
Il passo successivo è aggiungere stagefasi. Di default, GitLab definisce 5 fasi:
.pre— eseguita prima di tutte le fasi,.post— eseguita dopo tutte le fasi,build— la prima dopo.prela fase,test— la seconda fase,deploy— la terza fase.
Non c'è nulla che impedisca di dichiararle esplicitamente. L'ordine in cui sono specificate le fasi influisce sull'ordine in cui vengono eseguite. A scopo di 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 verranno eseguiti prima di ogni attività, utilizzando before_script:
before_script:
- $PSVersionTable.PSVersion
- dotnet --version
- nuget help | select-string VersionÈ necessario aggiungere almeno un'attività affinché il pipeline venga avviato al momento dell'invio dei commit. Aggiungiamo un'attività vuota per la dimostrazione:
job fittizio:
script:
- echo okAvviamo la validazione, otteniamo un messaggio che tutto va bene, effettuiamo il commit, eseguiamo il push e controlliamo i risultati sul sito... Ma otteniamo un errore dello script — bash: .PSVersion: comando non trovato. Che diavolo?
Tutto ha senso — per impostazione predefinita, i runner (responsabili dell'esecuzione degli script delle attività e forniti da GitLab) utilizzano bash per eseguire comandi. Possiamo risolvere questo problema specificando esplicitamente nella descrizione dell'attività quali tag devono avere i runner del pipeline in esecuzione:
job fittizio su Windows:
script:
- echo ok
tags:
- windowsOttimo! Ora il pipeline viene eseguito.
Il lettore attento, ripetendo i passaggi indicati, noterà che l'attività è stata completata nella fase test, anche se non abbiamo specificato una fase. Come si può intuire, test è la fase predefinita.
Continuiamo a creare lo scheletro della configurazione aggiungendo tutte le attività descritte sopra:
job di costruzione:
script:
- echo "costruzione..."
tags:
- windows
stage: costruzione
test e job di copertura:
script:
- echo "esecuzione dei test e analisi della copertura..."
tags:
- windows
stage: test
pack e job di distribuzione:
script:
- echo "imballaggio e invio a nuget..."
tags:
- windows
stage: distribuzione
pagine:
script:
- echo "creazione documenti..."
tags:
- windows
stage: distribuzioneAbbiamo ottenuto un pipeline non particolarmente funzionale, ma comunque corretto.
Impostazione dei trigger
Poiché nessuna delle attività ha filtri di attivazione specificati, il pipeline sarà completamente eseguito ad ogni invio di commit nel repository. Poiché questo non è un comportamento desiderato nella maggior parte dei casi, configureremo i filtri di attivazione per le attività.
I filtri possono essere configurati in due formati: e . In breve, only/except permette di configurare i filtri in base ai trigger (merge_request, ad esempio — configura l'attività per essere eseguita ad ogni creazione di una richiesta di merge e ad ogni invio di commit nel ramo che è sorgente nella richiesta di merge) e nei nomi dei rami (incluso l'uso di espressioni regolari); rules permette di configurare un insieme di condizioni e, opzionalmente, modificare la condizione di esecuzione dell'attività in base al successo delle attività precedenti ().
Ricordiamo l'insieme dei requisiti: compilazione e test solo per merge request, imballaggio e invio a Azure DevOps per merge request e push nel master, generazione della documentazione per push nel master.
Iniziamo configurando il task di compilazione del codice, aggiungendo una regola di attivazione solo per le merge request:
build job:
# snip
only:
- merge_requestAdesso configuriamo il task di imballaggio in modo da attivarsi per le merge request e l'aggiunta di commit nel master:
pack and deploy job:
# snip
only:
- merge_request
- masterCome si può vedere, è tutto semplice e diretto.
È anche possibile configurare un task 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 è possibile utilizzare ; le regole rules non sono compatibili con le regole only/except.
Configurazione del salvataggio degli artefatti
Durante l'esecuzione del task build job verranno creati artefatti di compilazione che possono essere riutilizzati nei task successivi. A questo scopo, è necessario aggiungere nella configurazione del task i percorsi e i file da salvare e riutilizzare nei task successivi, sotto la chiave :
build job:
# snip
artifacts:
paths:
- path/to/build/artifacts
- another/path
- MyCoolLib.*/bin/Release/*I percorsi supportano i wildcards, il che rende sicuramente più facile la loro definizione.
Se un'operazione genera artefatti, ogni operazione successiva potrà accedervi — si troveranno negli stessi percorsi rispetto alla radice del repository, da cui sono stati generati dall'operazione originale. Gli artefatti sono anche disponibili per il download sul sito.
Ora che abbiamo pronte (e verificate) le basi della configurazione, possiamo procedere alla scrittura degli script per le operazioni.
Scriviamo script
Forse, tanto tempo fa, in una galassia lontana, costruire progetti (anche su .net) dalla riga di comando era un vero problema. Oggi, tuttavia, è possibile costruire, testare e pubblicare un progetto con 3 comandi:
dotnet build
dotnet test
dotnet packNaturalmente, ci sono alcune sfumature che complicano un po' i comandi.
- Desideriamo una build di rilascio, non una di debug, quindi aggiungiamo a ciascun comando
-c Release - Durante i test, vogliamo raccogliere dati sulla copertura del codice, quindi dovremo connettere l'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 team di avvio dei test
/p:CollectCoverage=true - Nella configurazione del job di test aggiungeremo una chiave per ottenere i risultati di copertura (vedi sotto)
- In tutte le librerie di test, è necessario aggiungere il pacchetto
- Durante l'imballaggio del codice nei pacchetti nuget, specificheremo la directory di uscita per i pacchetti:
-o .
Raccogliamo i dati di copertura del codice
Coverlet fornisce nella console statistiche sull'esecuzione dopo il completamento dei test:
Calcolo del risultato di copertura...
Generazione del rapporto 'C:Usersxxxsourcereposmy-projectmyProject.testscoverage.json'
+-------------+--------+--------+--------+
| Modulo | Linea | Branch | Metodo |
+-------------+--------+--------+--------+
| progetto 1 | 83,24% | 66,66% | 92,1% |
+-------------+--------+--------+--------+
| progetto 2 | 87,5% | 50% | 100% |
+-------------+--------+--------+--------+
| progetto 3 | 100% | 83,33% | 100% |
+-------------+--------+--------+--------+
+---------+--------+--------+--------+
| | Linea | Branch | 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 possono poi essere visualizzate come un badge. L'espressione regolare viene fornita nelle impostazioni del job con la chiave coverage; l'espressione deve contenere un gruppo di cattura, il cui valore sarà passato al badge:
test and cover job:
# snip
coverage: /|s*Totals*|s*(d+[,.]d+%)/Qui otteniamo le statistiche dalla riga con la copertura complessiva delle linee.
Pubblicazione di pacchetti e documentazione
Entrambe le azioni sono programmate per l'ultima fase del pipeline: una volta completata la build e i test, possiamo condividere i risultati con il mondo.
Iniziamo con la pubblicazione in una sorgente di pacchetti:
Se il progetto non contiene il file di configurazione nuget (
nuget.config), creiamo uno nuovo: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 una nuova sorgente di pacchetti alla configurazione locale:
nuget sources add -name -source -username -password -configfile nuget.config -StorePasswordInClearTextname— nome locale della sorgente, non è fondamentaleurl— URL della sorgente dalla fase "Prepariamo gli account", p. 6organization— nome dell'organizzazione in Azure DevOpsgitlab variable— nome della variabile con il token di accesso, aggiunta in GitLab ("Prepariamo gli account", p. 11). Naturalmente, nel formato$variableName-StorePasswordInClearText— hack per aggirare l'errore di accesso negato ()- In caso di errori, potrebbe essere utile aggiungere
-verbosity detailed
- Inviamo il pacchetto alla fonte:
nuget push -source -skipduplicate -apikey *.nupkg- Stiamo inviando tutti i pacchetti dalla directory attuale, quindi
*.nupkg. name— dal passo precedente.key— qualsiasi stringa. In Azure DevOps nella finestra Connetti all'alimentazione, viene sempre fornito come esempio la stringaaz.-skipduplicate— tentando di inviare un pacchetto già esistente; senza questa chiave, la fonte restituirà un errore409 Conflict; con la chiave, l'invio verrà ignorato.
- Stiamo inviando tutti i pacchetti dalla directory attuale, quindi
Ora configuriamo la creazione della documentazione:
- Per iniziare, nel repository, sul branch master, inizializziamo il progetto docfx. Per fare ciò, dal root dobbiamo eseguire il comando
docfx inite in modalità interattiva impostiamo i parametri chiave per la compilazione della documentazione. Una descrizione dettagliata della configurazione minima del progetto .- Durante la configurazione è importante specificare la directory di uscita
..public— GitLab raccoglie di default il contenuto della cartella public nella root del repository come fonte per le Pages. Poiché il progetto si troverà in una cartella annidata nel repository, aggiungiamo nel percorso una risalita di livello.
- Durante la configurazione è importante specificare la directory di uscita
- Invieremo le modifiche a GitLab.
- Nella configurazione del pipeline aggiungeremo il task
pages(parola riservata per i task di pubblicazione di siti in GitLab Pages):- Script:
nuget install docfx.console -version 2.51.0— installerà docfx; la versione è specificata per garantire la correttezza dei percorsi di installazione del pacchetto..docfx.console.2.51.0toolsdocfx.exe .docfx_projectdocfx.json— generiamo la documentazione
- Nodo artifacts:
- Script:
pages:
# snip
artifacts:
paths:
- publicUna digressione lirica su docfx
In passato, quando configuravo il progetto, specificavo la fonte del codice per la documentazione come file di soluzione. Il principale svantaggio è che la documentazione viene creata anche per progetti di test. Se non è necessario, è possibile impostare questo valore sul nodo metadata.src:
{
"metadata": [
{
"src": [
{
"src": "../",
"files": [
"**/*.csproj"
],
"exclude":[
"*.tests*/**"
]
}
],
// --- snip ---
},
// --- snip ---
],
// --- snip ---
}metadata.src.src: "../"— usciamo di un livello rispetto alla posizionedocfx.json, poiché nelle espressioni non funziona la ricerca verso l'alto nell'albero delle directory.metadata.src.files: ["**/*.csproj"]— modello globale, raccogliamo tutti i progetti C# da tutte le directory.metadata.src.exclude: ["*.tests*/**"]— modello globale, escludiamo tutto dalle cartelle con.testsnel nome
Risultato intermedio
Questa semplice configurazione può essere creata in appena mezz'ora e con qualche tazza di caffè. Permette di controllare ad ogni richiesta di fusione che il codice si compila e che i test passano, raccogliere un nuovo pacchetto, aggiornare la documentazione e abbellire il progetto con bei badge nel README.
Il file .gitlab-ci.yml finale
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.0toolsdocfx.exe .docfxdocfx.json
artifacts:
paths:
- public
only:
- masterA proposito dei badge
È tutto iniziato per loro!
I badge con gli stati del pipeline e la copertura del codice sono disponibili su GitLab nelle impostazioni CI/CD nel blocco Gtntral pipelines:

Il badge con il link alla documentazione l'ho creato sulla piattaforma — lì tutto è abbastanza semplice, puoi creare il tuo badge e ottenerlo tramite richiesta.

Azure DevOps Artifacts consente anche di creare badge per i pacchetti specificando la versione attuale. Per questo, nella sorgente sul sito di Azure DevOps, è necessario fare clic su Crea badge per il pacchetto selezionato e copiare il markup markdown:


Aggiungiamo un tocco di stile
Evidenziamo i frammenti comuni della configurazione
Durante la scrittura della configurazione e la ricerca nella documentazione, mi sono imbattuto in una funzione interessante di YAML: il riutilizzo dei frammenti.
Come si può vedere dalle impostazioni dei task, tutti richiedono un tag windows per il runner e scattano all'invio su master/creazione di una richiesta di fusione (a parte la documentazione). Aggiungiamo questo al frammento che riutilizzeremo:
.common_tags: &common_tags
tags:
- windows
.common_only: &common_only
only:
- merge_requests
- masterE ora nella descrizione del task possiamo inserire il frammento precedentemente dichiarato:
build job:
<<: *common_tags
<<: *common_onlyI nomi dei frammenti devono iniziare con un punto per non essere interpretati come un'istruzione.
Versionamento dei pacchetti
Durante la creazione di un pacchetto, il compilatore verifica 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 in fase di compilazione. Quindi, per compilare un pacchetto con una nuova versione, è necessario aggiornare il valore nel file di progetto o passarlo come argomento nella riga di comando.
Aggiungiamo un'ulteriore richiesta: i due ultimi numeri della versione saranno l'anno e la data di compilazione del pacchetto, e aggiungiamo anche versioni pre-release. È possibile inserire questi dati nel file di progetto e verificarli prima di ogni invio, ma possiamo anche gestirli nel pipeline, costruendo la versione del pacchetto in base al contesto e passando i dati tramite un argomento nella riga di comando.
Conveniamo che se nel messaggio di commit è presente una stringa del tipo release (v./ver./version) <version number> (rev./revision <revision>)?, prenderemo la versione del pacchetto da questa stringa, la completeremo con la data attuale e la passeremo come argomento al comando dotnet pack. In assenza della stringa, non compileremo 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 compito di pack e deploy e osserviamo la costruzione dei pacchetti solo in presenza della stringa specificata nel messaggio di commit.
Totale
Dopo aver speso circa mezz'ora a un'ora per scrivere la configurazione, fare debug nel powershell locale e, probabilmente, alcuni avvii errati, abbiamo ottenuto una configurazione semplice per automatizzare compiti ripetitivi.
Certo, GitLab CI/CD è molto più vasto e complesso di quanto possa sembrare dopo aver letto questa guida — . Ci sono anche , che consente di
rilevare automaticamente, costruire, testare, distribuire e monitorare le tue applicazioni
Ora nei nostri piani c'è configurare un pipeline per il deployment delle applicazioni su Azure, utilizzando Pulumi e con rilevamento automatico dell'ambiente target, cosa che verrà trattata nel prossimo articolo.
Fonte: habr.com








