Или как да се снабдите с красиви значки за проекта си за един вечер лесно кодиране
Сигурно всеки разработчик, който има поне един проект на хартия, по определен момент усеща нуждата от красиви значки със статуси, покритие на кода, версии на пакети в nuget… И мен този импулс ме доведе до написването на тази статия. В процеса на подготовка за нея създадох такава красота в един от моите проекти:

В статията ще се разгледа основната настройка на непрекъсната интеграция и доставка за проект на библиотека от класове на .Net Core в GitLab, с публикуване на документацията в GitLab Pages и изпращане на събраните пакети в личен фид в Azure DevOps.
Като среда за разработка беше използван VS Code с разширение (за валидиране на файла с настройки директно от средата за разработка).
Кратко въведение
CD — това ли е, когато просто пуснеш, а на клиента вече всичко е паднало?
Какво е CI/CD и защо е нужно — лесно може да се намери в Google. Пълната документация за настройка на пайплайни в GitLab също не е трудна за намиране. разработчикът изпраща комит в репозитория, създава merge request през сайта,
- или по какъвто и да е друг начин явно или неявно стартира пайплайна от конфигурацията се избират всички задачи, чиито условия позволяват да бъдат стартирани в този контекст,,
- задачите се организират в съответствие с етапите си,
- етапите се изпълняват последователно — т.е.
- паралелно се изпълняват всички задачи на този етап, ако етапът завърши с неуспех (т.е. ако поне една от задачите на етапа завърши с неуспех) — пайплайнът спира (
- почти винагиако всички етапи са завършени успешно, пайплайнът се счита за успешно преминал.),
- По този начин имаме:
пайплайн — набор от задачи, организирани в етапи, в който може да се събира, тества, опакова код, да се разгръща готовото изпълнение в облачен сервис и т.н.,
- етап (
- ) — единица организация на пайплайна, съдържаща 1+ задача,и да разпространявате обновления в него преди разпространението в каналитезадача (
- job) — единица работа в пайплайна. Състои се от скрипт (задължително), условия за стартиране, настройки за публикуване/кеширане на артефакти и много други.) — единица работа в пайплайна. Състои се от скрипт (задължителен), условия за стартиране, настройки за публикуване/кеширане на артефакти и много други.
Съответно, задачата при настройка на CI/CD е да създадем набор от задачи, които реализират всичките необходими действия за изграждане, тестване и публикуване на код и артефакти.
Преди да започнем: защо?
- Защо GitLab?
Защото, когато се появи необходимостта да създам частни репозитории за пет-проекти, в GitHub те бяха платени, а аз — алчен. Репозиториите станаха безплатни, но все още не е достатъчна причина за мен да премина на GitHub.
- Защо не Azure DevOps Pipelines?
Защото настройването там е елементарно — дори не са необходими познания по командния ред. Интеграцията с външни доставчици на git става с няколко кликвания, импортът на SSH ключове за изпращане на комити в репозитория — също, пайплайнът лесно се настройва дори без шаблон.
Изходна позиция: какво имаме и какво искаме
Имаме:
- репозитория в GitLab.
Искаме:
- автоматично изграждане и тестване за всяко merge request,
- изграждане на пакети за всяко merge request и пуш в master при условие, че съобщението за комита съдържа определена стринга,
- изпращане на събраните пакети в частен фид в Azure DevOps,
- изграждане на документация и публикуване в GitLab Pages,
- бейджи!11
Описаните изисквания органично се вписват в следния модел на пайплайн:
- Етап 1 — изграждане
- Събираме кода, изходните файлове публикуваме като артефакти
- Етап 2 — тестване
- Получаваме артефактите от етапа на изграждане, провеждаме тестове, събираме данни за покритие на кода
- Етап 3 — изпращане
- Задача 1 — събираме nuget пакет и го изпращаме в Azure DevOps
- Задача 2 — събираме сайт от xmldoc в изходния код и публикуваме в GitLab Pages
Да започнем!
Събираме конфигурацията
Подготвяме акаунтите
Създаваме акаунт в
Преминаваме към
Създаваме нов проект
- Име — произволно
- Видимост — произволна

Когато натиснете бутона Create, проектът ще бъде създаден и ще последва пренос на страницата му. На тази страница можете да изключите ненужните функции, като отидете в настройките на проекта (долната връзка в списъка отляво -> Overview -> блок Azure DevOps Services)

Преминаваме в Atrifacts, натискаме Create feed
- Въвеждаме името на източника
- Избираме видимост
- Снемаме галочката Include packages from common public sources, за да не се превърне източникът в сметище на клони от nuget

Натискаме Connect to feed, избираме Visual Studio, от блока Machine Setup копираме Source

Отиваме в настройките на акаунта, избираме Personal Access Token

Създаваме нов токен за достъп
- Име — произволно
- Организация — текущата
- Срокът на действие – максимум 1 година
- Обхват (scope) – Packaging/Read & Write

Копираме създадения токен – след затварянето на модалния прозорец стойността ще бъде недостъпна
Влизаме в настройките на репозитория в GitLab, избираме настройките на CI/CD

Разширяваме блока Variables, добавяме нова
- Име – произволно без интервали (ще бъде достъпно в командната обвивка)
- Стойност – токен за достъп от п. 9
- Избираме Mask variable

На този етап предварителната настройка е завършена.
Подготвяме основата на конфигурацията
По подразбиране, за настройка на CI/CD в GitLab се използва файл .gitlab-ci.yml от корена на репозитория. Може да се настрои произволен път до този файл в настройките на репозитория, но в този случай това не е необходимо.
Както се вижда от разширението, файлът съдържа конфигурация във формат YAML. В документацията подробно е описано какви ключове могат да бъдат на върховото ниво на конфигурацията и във всеки от вложените нива.
Първо добавяме в конфигурационния файл връзка към docker-образа, в който ще се изпълняват задачите. За това намираме . В има подробно ръководство за това как да изберем образ за различни задачи. За изграждане ни подхожда образ с .Net Core 3.1, затова смело добавяме първия ред в конфигурацията
image: mcr.microsoft.com/dotnet/core/sdk:3.1Сега при стартиране на пайплайна от хранилището на образи Microsoft ще бъде изтеглен посоченият образ, в който ще се изпълняват всички задачи от конфигурацията.
Следващата стъпка – добавяне на и да разпространявате обновления в него преди разпространението в каналитеетапи. По подразбиране GitLab определя 5 етапа:
.pre– изпълнява се преди всички етапи,.post– изпълнява се след всички етапи,build– първият след.preетап,test– втори етап,deploy– трети етап.
Ничего не пречи да ги обявим явно. Поредността на етапите влияе на реда, в който те се изпълняват. За пълнота на изложението, добавяме в конфигурацията:
stages:
- build
- test
- deployЗа отстраняване на проблеми е разумно да се получи информация за средата, в която се изпълняват задачите. Добавяме глобален набор от команди, които ще се изпълняват преди всяка задача, с помощта на before_script:
before_script:
- $PSVersionTable.PSVersion
- dotnet --version
- nuget help | select-string VersionОстава да добавим поне една задача, за да може пайплайнът да се стартира при изпращане на комити. Засега ще добавим празна задача за демонстрация:
dummy job:
script:
- echo okСтартираме валидирането, получаваме съобщение, че всичко е наред, комитваме, пушваме, гледаме на сайта резултатите… И получаваме грешка в скрипта — bash: .PSVersion: команда не е намерена. Какво?
Всичко е логично — по подразбиране runner-ите (отговарящи за изпълнението на задачи и предоставяни от GitLab) използват bash за изпълнение на команди. Можем да поправим това, като явно посочим в описанието на задачата, какви тагове трябва да има изпълняващият пайплайн рънър:
dummy job on windows:
script:
- echo ok
tags:
- windowsОтлично! Сега пайплайнът се изпълнява.
Внимателният читател, повтаряйки посочените стъпки, ще забележи, че задачата е изпълнена в етапа test, въпреки че не сме посочили етап. Как може да се предположи, test являва се етап по подразбиране.
Да продължим с изграждането на скелета на конфигурацията, добавяйки всички задачи, описани по-горе:
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: deployПолучихме не особено функционален, но все пак коректен пайплайн.
Настройка на тригери
Поради факта, че за нито една от задачите не са посочени филтри за задействане, пайплайнът ще напълно изпълнява при всяко изпращане на комити в хранилището. Тъй като това не е желаното поведение в общия случай, ще настроим филтри за задействане на задачите.
Филтрите могат да се настройват в два формата: и . В кратце, only/except позволява настройка на филтри по тригери (merge_request, например — настройва задачата да се изпълнява при всяко създаване на искане за сливане и при всяко изпращане на комити в клон, който е източник в искането за сливане) и имената на клонове (в т.ч. с използване на регулярни изрази); rules позволява настройка на набор от условия и, по избор, промяна на условията за изпълнение на задачата в зависимост от успеха на предишни задачи ().
Нека си припомним набора от изисквания — изграждане и тестване само за merge request, опаковане и изпращане в Azure DevOps — за merge request и пушове в master, генериране на документация — за пушове в master.
За начало, нека настроим задачата за изграждане на код, добавяйки правило за задействане само при merge request:
build job:
# snip
only:
- merge_requestСега нека настроим задачата за опаковане на задействания за merge request и добавяне на комити в master:
опакуйте и разверните задачу:
# пропущено
только:
- merge_request
- masterКакто се вижда, всичко е просто и директно.
Също така, можете да настроите задачата да се задейства само ако е създаден merge request с определена целева или изходна клонка:
правила:
- ако: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"В условията могат да се използват ; правилата rules не са съвместими с правилата only/except.
Настройване на запазването на артефакти
По време на изпълнението на задачата сборна задача ще бъдат създадени артефакти от сборката, които могат да бъдат повторно използвани в следващите задачи. За целта трябва да добавите пътища в конфигурацията на задачата, файлове, които трябва да бъдат запазени и повторно използвани в следващите задачи, в ключа :
сборна задача:
# пропуснато
артефакти:
пътища:
- path/to/build/artifacts
- another/path
- MyCoolLib.*/bin/Release/*Пътищата поддържат wildcards, което определено улеснява задаването им.
Ако задачата създава артефакти, всяка следваща задача ще може да получи достъп до тях — те ще се намират на същите пътища спрямо корена на хранилището, по които са били събрани от изходната задача. Артефактите също така са достъпни за изтегляне на сайта.
Сега, когато имаме готов (и проверен) скелет на конфигурацията, можем да преминем към самото писане на сценарии за задачите.
Пишем сценарии
Възможно е, преди много време, в далечна галактика, сборът на проекти (включително и на .net) от командния ред да е бил болка. Сега обаче, сборът, тестването и публикуването на проект може да стане с 3 команди:
dotnet build
dotnet test
dotnet packЕстествено, има някои нюанси, поради които малко ще сложим командите.
- Искаме release версия, а не debug, така че добавяме
-c Release - При тестването искаме да съберем данни за покритие на кода, затова е необходимо да добавим анализатор за покритие в тестовите библиотеки:
- Във всички тестови библиотеки трябва да добавите пакета
coverlet.msbuild:dotnet add package coverlet.msbuildот папката на проекта - В командата за стартиране на тестовете ще добавим
/p:CollectCoverage=true - В конфигурацията на задачата за тестване добавяме ключ за получаване на резултатите от покритие (вж. по-долу)
- Във всички тестови библиотеки трябва да добавите пакета
- При опаковането на кода в nuget пакети задаваме изходната директория за пакетите:
-o .
Събираме данни за покритие на кода
Coverlet след стартиране на тестовете извежда в конзолата статистиката относно изпълнението:
Изчисляване на резултата от покритие...
Генериране на доклад 'C:Usersxxxsourcereposmy-projectmyProject.testscoverage.json'
+-------------+--------+--------+--------+
| Модул | Линия | Клон | Метод |
+-------------+--------+--------+--------+
| проект 1 | 83,24% | 66,66% | 92,1% |
+-------------+--------+--------+--------+
| проект 2 | 87,5% | 50% | 100% |
+-------------+--------+--------+--------+
| проект 3 | 100% | 83,33% | 100% |
+-------------+--------+--------+--------+
+---------+--------+--------+--------+
| | Линия | Клон | Метод |
+---------+--------+--------+--------+
| Общо | 84,27% | 65,76% | 92,94% |
+---------+--------+--------+--------+
| Средно | 90,24% | 66,66% | 97,36% |
+---------+--------+--------+--------+GitLab позволява да зададете регулярно изразяване за получаване на статистика, която по-късно може да бъде получена под формата на значка. Регулярното изразяване се задава в настройките на задачата с ключа покритие; в израза трябва да присъства capture-група, стойността на която ще бъде предадена в значката:
тест и покритие:
# пропускам
покритие: \/|s*Общото*|s*(d+[,.]d+%)\/Тук получаваме статистика от реда с общото покритие по линии.
Публикуваме пакети и документация
И двете действия са зададени на последния етап на пайплайна – след като сборката и тестовете приключиха, е време да споделим с света нашите разработки.
Първо ще разгледаме публикуването на източника на пакети:
Ако в проекта няма файл за конфигурация nuget (
nuget.config), ще създадем нов:dotnet new nugetconfigЗащо: в образа може да е забранен достъпът за запис към глобалните (потребителски и машинни) конфигурации. За да избегнем грешки, просто ще създадем нова локална конфигурация и ще работим с нея.
- Добавяме нов източник на пакети в локалната конфигурация:
nuget sources add -name <name> -source <url> -username <organization> -password <gitlab variable> -configfile nuget.config -StorePasswordInClearTextname— локалното име на източника, не принципиалноurl— URL на източника от етапа "Готвим акаунтите", п. 6organization— име на организацията в Azure DevOpsgitlab variable— име на променливата с токена за достъп, добавена в GitLab ("Готвим акаунтите", п. 11). Разбира се, в формата$variableName-StorePasswordInClearText— хак за избягване на грешката за отказ на достъп ()- В случай на грешки, може да е полезно да добавите
-verbosity detailed
- Изпращаме пакета в източника:
nuget push -source <name> -skipduplicate -apikey <key> *.nupkg- Изпращаме всички пакети от текущата директория, затова
*.nupkg. name— от горната стъпка.key— произволен ред. В Azure DevOps в прозореца Connect to feed винаги дават пример за редаaz.-skipduplicate— при опит за изпращане на вече съществуващ пакет без този ключ, източникът ще върне грешка409 Конфликт; с ключа, изпращането ще бъде пропуснато.
- Изпращаме всички пакети от текущата директория, затова
Сега ще настроим създаването на документация:
- Първо, в репозитория, в клона master, инициализираме проекта docfx. За целта от корена трябва да изпълним командата
docfx initи в интерактивен режим ще зададем основните параметри за генериране на документацията. Подробно описание на минималната настройка на проекта .- При настройка е важно да укажем изходната директория
..public— GitLab по подразбиране взема съдържанието на папката public в корена на репозитория като източник за Pages. Тъй като проектът ще бъде разположен в подпапка на репозитория, добавяме в пътя изход на ниво нагоре.
- При настройка е важно да укажем изходната директория
- Ще изпратим промените в GitLab.
- В конфигурацията на pipeline добавяме задача
pages(резервирана дума за задачи за публикуване на сайтове в GitLab Pages):- Скрипт:
nuget install docfx.console -version 2.51.0— ще инсталира docfx; версията е посочена за гарантиране на коректността на пътищата на инсталиране на пакета..docfx.console.2.51.0toolsdocfx.exe .docfx_projectdocfx.json— генерираме документацията
- Узел artifacts:
- Скрипт:
pages:
# snip
artifacts:
paths:
- publicЛичен отстъп за docfx
Преди при настройка на проекта, посочвах източника на кода за документацията като файл на решението. Основен недостатък — документацията се генерира и за тестовите проекти. В случай, че това не е нужно, може да се зададе такава стойност на узла metadata.src:
{
"metadata": [
{
"src": [
{
"src": "..\/",
"files": [
"**\/*.csproj"
],
"exclude":[
"*.tests*\/**"
]
}
],
\/\/ --- snip ---
},
\/\/ --- snip ---
],
\/\/ --- snip ---
}metadata.src.src: "..\/"— излизаме на ниво нагоре спрямо разположениетоdocfx.json, тъй като в шаблоните не работи търсене нагоре по дървото на директории.metadata.src.files: ["**\/*.csproj"]— глобален шаблон, събираме всички C# проекти от всички директории.metadata.src.exclude: ["*.tests*\/**"]— глобален шаблон, изключваме всичко от папки с.testsв името
Промеждутъчен резултат
Тази проста конфигурация може да бъде съставена буквално за половин час и две чаши кафе, която ще позволи при всяко искане за сливане и изпращане в master да се проверява, че кодът се компилира и тестовете минават, да събира нов пакет, да актуализира документацията и да радва окото с красиви значки в README на проекта.
Финален .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.0toolsdocfx.exe .docfxdocfx.json
artifacts:
paths:
- public
only:
- masterМежду другому за бейджовете
Всичко беше започнало заради тях!
Бейджове с статуси на пайплайна и покритие на кода са налични в GitLab в настройките CI/CD в блока Gtntral pipelines:

Бейджът с линк към документацията го създадох на платформата — там всичко е достатъчно директно, може да се създаде собствен бейдж и да се получава чрез запитване.

Azure DevOps Artifacts също така позволява създаването на бейджове за пакети с посочена актуална версия. За целта в източника на сайта Azure DevOps трябва да кликнете на Create badge на избрания пакет и да копирате markdown-разметката:


Добавяме елегантност
Изтъкваме общите фрагменти на конфигурацията
Докато пишех конфигурацията и търсех в документацията, се натъкнах на интересна възможност в YAML — повторно използване на фрагменти.
Както се вижда от настройките на задачите, всички те изискват наличието на таг windows на ранъра и сработват при изпращане в master/създаване на запитване за сливане (с изключение на документацията). Нека добавим това към фрагмента, който ще повторно използваме:
.common_tags: &common_tags
tags:
- windows
.common_only: &common_only
only:
- merge_requests
- masterИ сега в описанието на задачата можем да вмъкнем обявения по-рано фрагмент:
build job:
<<: *common_tags
<<: *common_onlyИмената на фрагментите трябва да започват с точка, за да не бъдат интерпретирани като задача.
Версиониране на пакети
При създаване на пакет компилаторът проверява командните редове и при тяхното отсъствие — файловете на проектите; като намери възел Version, взема стойността му като версия на създавания пакет. Така че, за да създадете пакет с нова версия, трябва или да я обновите във файла на проекта, или да я предадете като аргумент на командния ред.
Нека добавим още едно пожелание — да направим младшите две числа в версията година и дата на изграждане на пакета, и да добавим предварителни версии. Разбира се, може да добавяте тези данни в проекта и да ги проверявате преди всяко изпращане — но може да направите това и в пайплайна, събирайки версията на пакета от контекста и предавайки го чрез аргумент на командния ред.
Съгласяваме се, че ако в съобщението за комит има строка от вида release (v. / ver. / version) (rev. / revision )?, то ще вземем версията на пакета от тази строка, ще я допълним с текущата дата и ще я предадем като аргумент на командата dotnet pack. При отсъствие на строката — просто няма да създаваме пакет.
Тази задача решава следният скрипт:
# регулярное выражение для поиска строки с версией
$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=$versionДобавяме скрипта в задачата pack and deploy job и наблюдаваме изграждането на пакети строго при наличие на зададената строка в съобщението за комит.
Общо
След като отделихме около половин до един час за написване на конфигурацията, отстраняване на грешки в локалния PowerShell и, може би, няколко неуспешни стартирания, получихме проста конфигурация за автоматизация на рутинни задачи.
Разбира се, GitLab CI / CD е много по-обширен и многостранен, отколкото може да изглежда след прочитане на това ръководство — . Там дори , позволяващ
автоматично да открива, изгражда, тества, внедрява и следи вашите приложения
Сега в плановете е да конфигурираме пайплайн за разгръщане на приложения в Azure, с използване на Pulumi и автоматично откриване на целевата среда, което ще бъде разгледано в следващата статия.
Източник: habr.com








