Ou como obter belos crachás para seu projeto em uma única noite de codificação descontraída
Provavelmente, todo desenvolvedor que tem pelo menos um projeto pessoal, em algum momento, sente a necessidade de criar belos crachás com status, cobertura de código, versões de pacotes no nuget... E essa necessidade me levou a escrever este artigo. Durante o processo de preparação, eu obtive essa beleza em um dos meus projetos:

Neste artigo, será abordada a configuração básica de integração contínua e entrega contínua para um projeto de biblioteca de classes em .Net Core no GitLab, com a publicação de documentação no GitLab Pages e o envio de pacotes construídos para um feed privado no Azure DevOps.
Como ambiente de desenvolvimento, foi utilizado o VS Code com a extensão para validação do arquivo de configuração diretamente do ambiente de desenvolvimento.
Introdução breve
CD - é quando você apenas fez o push, e o cliente já está tudo fora do ar?
O que é CI/CD e para que serve - pode ser facilmente encontrado no Google. A documentação completa sobre a configuração de pipelines no GitLab também não é difícil de encontrar. O desenvolvedor envia um commit para o repositório, cria um merge request pelo site,
- ou de alguma outra forma explicitamente ou implicitamente inicia o pipeline da configuração são selecionadas todas as tarefas cujas condições permitem que sejam iniciadas nesse contexto,,
- as tarefas são organizadas de acordo com suas etapas,
- as etapas são executadas sequencialmente - ou seja,
- paralelamente todas as tarefas dessa etapa são executadas, se a etapa falhar (ou seja, se pelo menos uma das tarefas da etapa falhar) - o pipeline é interrompido (
- quase semprese todas as etapas forem concluídas com sucesso, o pipeline é considerado bem-sucedido.),
- Assim, temos:
um pipeline - um conjunto de tarefas organizadas em etapas, onde é possível compilar, testar, empacotar código, implantar a construção pronta em um serviço em nuvem, etc.,
- uma etapa (
- ) - a unidade de organização do pipeline, contém 1+ tarefa,stagetarefa (
- job) - a unidade de trabalho no pipeline. Consiste em um script (obrigatório), condições de execução, configurações de publicação/cache de artefatos e muito mais.) — unidade de trabalho no pipeline. Consiste em um script (obrigatório), condições de execução, configurações de publicação/cache de artefatos e muito mais.
Assim, a tarefa ao configurar CI/CD é criar um conjunto de tarefas que realizem todas as ações necessárias para compilar, testar e publicar o código e os artefatos.
Antes de começar: por quê?
- Por que GitLab?
Porque quando surgiu a necessidade de criar repositórios privados para projetos pessoais, eles eram pagos no GitHub, e eu sou econômico. Os repositórios se tornaram gratuitos, mas isso ainda não é motivo suficiente para eu migrar para o GitHub.
- Por que não Azure DevOps Pipelines?
Porque lá a configuração é simples — nem é necessário saber usar a linha de comando. A integração com provedores git externos é feita em poucos cliques, a importação de chaves SSH para enviar commits para o repositório também é fácil, e o pipeline é facilmente configurável mesmo sem um template.
Ponto de partida: o que temos e o que queremos
Temos:
- um repositório no GitLab.
Queremos:
- compilação e teste automático para cada merge request,
- compilação de pacotes para cada merge request e push no master, desde que haja uma certa linha na mensagem do commit,
- envio dos pacotes compilados para um feed privado no Azure DevOps,
- compilação da documentação e publicação no GitLab Pages,
- badges!11
Os requisitos descritos se encaixam perfeitamente no seguinte modelo de pipeline:
- Etapa 1 — compilação
- Compilamos o código, publicamos os arquivos de saída como artefatos
- Etapa 2 — teste
- Obtemos os artefatos da etapa de compilação, executamos os testes e coletamos os dados de cobertura de código
- Etapa 3 — envio
- Tarefa 1 — compilamos o pacote NuGet e enviamos para o Azure DevOps
- Tarefa 2 — compilamos o site a partir do xmldoc no código-fonte e publicamos no GitLab Pages
Vamos lá!
Compilamos a configuração
Preparamos as contas
Criamos uma conta no
Vamos para
Criamos um novo projeto
- Nome — qualquer um
- Visibilidade — qualquer uma

Ao clicar no botão Criar, o projeto será criado e iremos para sua página. Nessa página, é possível desativar funcionalidades desnecessárias, acessando as configurações do projeto (link inferior na lista à esquerda -> Visão Geral -> bloco de Serviços do Azure DevOps)

Vamos para Atrifacts, clicamos em Criar feed
- Inserimos o nome da fonte
- Escolhemos a visibilidade
- Desmarcamos a opção Incluir pacotes de fontes públicas comuns, para que a fonte não se transforme em um depósito de clones do NuGet

Clicamos em Conectar ao feed, escolhemos Visual Studio, copiamos a Fonte do bloco Configuração da Máquina

Vamos para as configurações da conta, escolhemos Token de Acesso Pessoal

Criamos um novo token de acesso
- Nome — qualquer um
- Organização — a atual
- Prazo de validade — no máximo 1 ano
- Escopo — Packaging/Read & Write

Copiamos o token criado — após fechar a janela modal, o valor não estará mais disponível
Acesse as configurações do repositório no GitLab, escolha as configurações de CI/CD

Expanda o bloco Variables, adicione um novo
- Nome — qualquer um sem espaços (estará disponível no shell)
- Valor — token de acesso do item 9
- Escolha Mask variable

Com isso, a configuração preliminar está concluída.
Preparando a estrutura de configuração
Por padrão, para configurar CI/CD no GitLab, usa-se o arquivo .gitlab-ci.yml na raiz do repositório. É possível configurar um caminho arbitrário para esse arquivo nas configurações do repositório, mas neste caso, isso não é necessário.
Como pode ser visto pela extensão, o arquivo contém a configuração no formato YAML. A documentação descreve em detalhes quais chaves podem ser contidas no nível superior da configuração e em cada um dos níveis aninhados.
Primeiro, adicionaremos ao arquivo de configuração um link para a imagem do Docker, onde as tarefas serão executadas. Para isso, encontramos . No que fornece um guia detalhado sobre qual imagem escolher para diferentes tarefas. Para compilações, vamos usar a imagem com .Net Core 3.1, então simplesmente adicionamos a primeira linha na configuração
image: mcr.microsoft.com/dotnet/core/sdk:3.1Agora, ao executar o pipeline, a imagem especificada será baixada do repositório de imagens da Microsoft, onde todas as tarefas da configuração serão executadas.
A próxima etapa é adicionar stageestágios. Por padrão, o GitLab define 5 estágios:
.pre— é executado antes de todos os estágios,.post— é executado após todos os estágios,build— primeiro após.preestágio,test— segundo estágio,deploy— terceiro estágio.
Nada impede que os declaramos explicitamente. A ordem em que os estágios são especificados afeta a ordem em que são executados. Para completar, adicionamos à configuração:
stages:
- build
- test
- deployPara depuração, faz sentido obter informações sobre o ambiente em que as tarefas são executadas. Adicionaremos um conjunto global de comandos que será executado antes de cada tarefa, usando before_script:
before_script:
- $PSVersionTable.PSVersion
- dotnet --version
- nuget help | select-string VersionResta adicionar pelo menos uma tarefa, para que ao enviar commits o pipeline seja executado. Por ora, vamos adicionar uma tarefa vazia para demonstração:
dummy job:
script:
- echo okIniciamos a validação, recebemos a mensagem de que está tudo bem, fazemos o commit, fazemos o push, verificamos os resultados no site… E obtemos um erro de script — bash: .PSVersion: comando não encontrado. O que?!
Tudo faz sentido — por padrão, os runners (responsáveis pela execução dos scripts de tarefas e fornecidos pelo GitLab) usam bash para executar comandos. Podemos corrigir isso, especificando explicitamente na descrição da tarefa quais tags devem estar no runner do pipeline que executa:
tarefa dummy em windows:
script:
- echo ok
tags:
- windowsÓtimo! Agora o pipeline está sendo executado.
Um leitor atento, ao repetir os passos indicados, notará que a tarefa foi executada na etapa test, embora não tenhamos especificado a etapa. Como pode ser deduzido, test é a etapa padrão.
Vamos continuar a criação da estrutura de configuração, adicionando todas as tarefas descritas acima:
tarefa de build:
script:
- echo "building..."
tags:
- windows
stage: build
tarefa de teste e cobertura:
script:
- echo "executando testes e análise de cobertura..."
tags:
- windows
stage: test
tarefa de empacotamento e implantação:
script:
- echo "empacotando e enviando para nuget..."
tags:
- windows
stage: deploy
páginas:
script:
- echo "criando docs..."
tags:
- windows
stage: deployObtivemos um pipeline não muito funcional, mas ainda assim correto.
Configuração de gatilhos
Como não foram especificados filtros de acionamento para nenhuma das tarefas, o pipeline será totalmente executado a cada envio de commits para o repositório. Como esse não é o comportamento desejado na maioria das vezes, iremos configurar filtros de acionamento para as tarefas.
Os filtros podem ser configurados em dois formatos: e . Em resumo, only/except permite configurar filtros por gatilhos (merge_request, por exemplo — configura a tarefa para ser executada a cada criação de um pedido de mesclagem e a cada envio de commits para a branch que é a origem do pedido de mesclagem) e nomes de branches (incluindo o uso de expressões regulares); rules permetem configurar um conjunto de condições e, opcionalmente, alterar as condições de execução da tarefa dependendo do sucesso das tarefas anteriores ().
Vamos lembrar o conjunto de requisitos — build e teste apenas para merge requests, empacotamento e envio para Azure DevOps — para merge requests e pushes no master, geração de documentação — para pushes no master.
Para começar, vamos configurar a tarefa de build de código, adicionando uma regra de acionamento apenas para merge requests:
tarefa de build:
# snip
only:
- merge_requestAgora vamos configurar a tarefa de empacotamento para ser acionada por merge requests e adições de commits no master:
tarefa de empacotamento e implantação:
# snip
only:
- merge_request
- masterComo pode ser visto, é tudo simples e direto.
Também é possível configurar a tarefa para ser acionada apenas se um merge request for criado com um branch de destino ou origem específico:
regras:
- se: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"Nas condições, podem ser usadas ; regras rules não são compatíveis com as regras only/except.
Configuração de salvamento de artefatos
Durante a execução da tarefa tarefa de build teremos artefatos de build criados, que podem ser reutilizados em tarefas subsequentes. Para isso, é necessário adicionar os caminhos na configuração da tarefa onde os arquivos devem ser salvos e reutilizados nas próximas tarefas, na chave :
tarefa de build:
# snip
artefatos:
caminhos:
- path/to/build/artifacts
- another/path
- MyCoolLib.*/bin/Release/*Os caminhos suportam wildcards, o que definitivamente facilita a sua definição.
Se a tarefa cria artefatos, então cada tarefa subsequente poderá acessá-los — eles estarão localizados nos mesmos caminhos em relação à raiz do repositório, pelos quais foram coletados da tarefa original. Além disso, os artefatos estão disponíveis para download no site.
Agora que temos a estrutura da configuração pronta (e testada), podemos passar para a escrita dos scripts para as tarefas.
Escrevendo scripts
Talvez, há muito tempo, em uma galáxia muito, muito distante, compilar projetos (incluindo em .net) a partir da linha de comando fosse um desafio. Agora, compilar, testar e publicar um projeto pode ser feito em 3 comandos:
dotnet build
dotnet test
dotnet packNaturalmente, existem algumas nuances que farão com que complicamos um pouco os comandos.
- Queremos uma compilação de release, e não de debug, então adicionamos
-c Release - Ao testar, queremos coletar dados de cobertura de código, portanto precisaremos incluir o analisador de cobertura nas bibliotecas de teste:
- Em todas as bibliotecas de teste, deve-se adicionar o pacote
coverlet.msbuild:dotnet add package coverlet.msbuildda pasta do projeto - No comando de execução dos testes, adicionaremos
/p:CollectCoverage=true - Na configuração da tarefa de teste, adicionaremos uma chave para obter os resultados de cobertura (veja abaixo)
- Em todas as bibliotecas de teste, deve-se adicionar o pacote
- Ao empacotar o código em pacotes nuget, definiremos o diretório de saída para os pacotes:
-o .
Coletando dados de cobertura de código
Coverlet, após a execução dos testes, exibe no console as estatísticas sobre a execução:
Calculando o resultado da cobertura...
Gerando relatório 'C:Usersxxxsourcereposmy-projectmyProject.testscoverage.json'
+-------------+--------+--------+--------+
| Módulo | Linha | Ramificação | Método |
+-------------+--------+--------+--------+
| projeto 1 | 83,24% | 66,66% | 92,1% |
+-------------+--------+--------+--------+
| projeto 2 | 87,5% | 50% | 100% |
+-------------+--------+--------+--------+
| projeto 3 | 100% | 83,33% | 100% |
+-------------+--------+--------+--------+
+---------+--------+--------+--------+
| | Linha | Ramificação | Método |
+---------+--------+--------+--------+
| Total | 84,27% | 65,76% | 92,94% |
+---------+--------+--------+--------+
| Média | 90,24% | 66,66% | 97,36% |
+---------+--------+--------+--------+O GitLab permite especificar uma expressão regular para obter estatísticas, que depois podem ser apresentadas como um badge. A expressão regular é especificada nas configurações da tarefa com a chave coverage; a expressão deve conter um grupo de captura, cujo valor será passado para o badge:
trabalho de teste e cobertura:
# snip
cobertura: \/|s*Totals*|s*(d+[,.]d+%)\/Aqui obtemos estatísticas da linha com a cobertura total.
Publicar pacotes e documentação
Ambas as ações estão agendadas para a última etapa do pipeline — com a construção e os testes concluídos, podemos compartilhar nossos resultados com o mundo.
Para começar, vamos considerar a publicação na origem dos pacotes:
Se o projeto não contém um arquivo de configuração nuget (
nuget.config), vamos criar um novo:dotnet new nugetconfigPor quê: na imagem pode ser proibido o acesso de gravação nas configurações globais (do usuário e do sistema). Para evitar erros, basta criar uma nova configuração local e trabalhar com ela.
- Vamos adicionar uma nova origem de pacotes à configuração local:
nuget sources add -name <nome> -source <url> -username <organização> -password <variável do gitlab> -configfile nuget.config -StorePasswordInClearTextnome— nome local da origem, não é crucialurl— URL da origem da etapa 'Preparando contas', item 6organização— nome da organização no Azure DevOpsvariável do gitlab— nome da variável com o token de acesso, adicionada no GitLab ('Preparando contas', item 11). Naturalmente, no formato$variávelNome-StorePasswordInClearText— hack para contornar o erro de acesso negado ()- Em caso de erros, pode ser útil adicionar
-verbosity detailed
- Enviando pacote para a origem:
nuget push -source <nome> -skipduplicate -apikey <chave> *.nupkg- Enviando todos os pacotes do diretório atual, portanto
*.nupkg. nome— do passo anterior.key— qualquer string. No Azure DevOps, na janela Conectar ao feed, sempre é apresentada uma string de exemploaz.-skipduplicate— ao tentar enviar um pacote já existente sem essa chave, a origem retornará um erro409 Conflito; com a chave, o envio será ignorado.
- Enviando todos os pacotes do diretório atual, portanto
Agora vamos configurar a criação da documentação:
- Primeiro, no repositório, na branch master, inicializamos o projeto docfx. Para isso, a partir da raiz, precisamos executar o comando
docfx inite em modo interativo definiremos os parâmetros chave para a construção da documentação. A descrição detalhada da configuração mínima do projeto .- Ao configurar, é importante indicar o diretório de saída
..public— O GitLab, por padrão, usa o conteúdo da pasta public na raiz do repositório como fonte para Pages. Como o projeto estará localizado em uma pasta aninhada ao repositório, adicionamos ao caminho de saída um nível para cima.
- Ao configurar, é importante indicar o diretório de saída
- Vamos enviar as alterações para o GitLab.
- Na configuração do pipeline, adicionaremos a tarefa
pages(palavra reservada para tarefas de publicação de sites no GitLab Pages):- Script:
nuget install docfx.console -version 2.51.0— instalará o docfx; a versão está especificada para garantir a correção dos caminhos de instalação do pacote..docfx.console.2.51.0toolsdocfx.exe .docfx_projectdocfx.json— estamos construindo a documentação
- Nó de artifacts:
- Script:
pages:
# snip
artifacts:
paths:
- publicUm desvio lírico sobre o docfx
Anteriormente, ao configurar o projeto, eu indicava a fonte de código para a documentação como um arquivo de solução. A principal desvantagem é que a documentação é criada também para projetos de teste. Caso isso não seja necessário, podemos atribuir esse valor ao nó metadata.src:
{
"metadata": [
{
"src": [
{
"src": "..\/",
"files": [
"**\/*.csproj"
],
"exclude":[
"*.tests*\/**"
]
}
],
\/\/ --- snip ---
},
\/\/ --- snip ---
],
\/\/ --- snip ---
}metadata.src.src: "..\/"— saímos um nível acima em relação à localizaçãodocfx.json, pois a busca em padrões não funciona para cima na árvore de diretórios.metadata.src.files: ["**\/*.csproj"]— padrão global, coletamos todos os projetos C# de todos os diretórios.metadata.src.exclude: ["*.tests*\/**"]— padrão global, excluímos tudo das pastas com.testsno nome
Resumo Intermediário
Esta configuração simples pode ser feita em literalmente meia hora e algumas xícaras de café, permitindo que a cada merge request e envio para master, verifiquemos se o código compila e os testes passam, coletar um novo pacote, atualizar a documentação e alegrar os olhos com belos badges no README do projeto.
O resultado final .gitlab-ci.yml
imagem: mcr.microsoft.com/dotnet/core/sdk:3.1
antes_do_script:
- $PSVersionTable.PSVersion
- dotnet --version
- nuget help | select-string Version
etapas:
- build
- test
- deploy
trabalho de construção:
etapa: build
script:
- dotnet build -c Release
tags:
- windows
somente:
- merge_requests
- master
artefatos:
caminhos:
- seu/caminho/para/binaries
trabalho de teste e cobertura:
etapa: test
tags:
- windows
script:
- dotnet test -c Release /p:CollectCoverage=true
cobertura: /|s*Totals*|s*(d+[,.]d+%)|
somente:
- merge_requests
- master
trabalho de empacotamento e implantação:
etapa: deploy
tags:
- windows
script:
- dotnet pack -c Release -o .
- dotnet new nugetconfig
- nuget sources add -name feedName -source https://pkgs.dev.azure.com/seu-organizacao/_packaging/seu-feed/nuget/v3/index.json -username seu-organizacao -password $nugetFeedToken -configfile nuget.config -StorePasswordInClearText
- nuget push -source feedName -skipduplicate -apikey az *.nupkg
somente:
- master
páginas:
tags:
- windows
etapa: 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
artefatos:
caminhos:
- public
somente:
- masterA propósito dos badges
Por causa deles, tudo foi planejado!
Os badges com os status do pipeline e cobertura de código estão disponíveis no GitLab nas configurações de CI/CD na seção de pipelines Centrais:

O badge com link para a documentação eu criei na plataforma — lá tudo é bastante direto, você pode criar seu próprio badge e obtê-lo por meio de uma solicitação.

O Azure DevOps Artifacts também permite criar badges para pacotes com a versão atual. Para isso, na fonte no site do Azure DevOps, você deve clicar em Criar badge no pacote selecionado e copiar a marcação markdown:


Adicionando beleza
Destacando fragmentos comuns da configuração
Durante a escrita da configuração e pesquisas na documentação, encontrei uma possibilidade interessante do YAML — reutilização de fragmentos.
Como pode ser visto nas configurações das tarefas, todas elas exigem a presença de uma tag windows no runner e são acionadas ao enviar para o master/criar uma solicitação de mesclagem (exceto pela documentação). Vamos adicionar isso ao fragmento que iremos reutilizar:
.common_tags: &common_tags
tags:
- windows
.common_only: &common_only
only:
- merge_requests
- masterE agora na descrição da tarefa podemos inserir o fragmento declarado anteriormente:
trabalho de construção:
<<: *common_tags
<<: *common_onlyOs nomes dos fragmentos devem começar com um ponto, para não serem interpretados como uma tarefa.
Versionamento de pacotes
Ao criar um pacote, o compilador verifica as chaves da linha de comando e, na sua ausência, os arquivos de projeto; ao encontrar o nó Version, ele usa seu valor como a versão do pacote a ser construído. Portanto, para compilar um pacote com uma nova versão, é necessário atualizar a versão no arquivo do projeto ou passá-la como argumento da linha de comando.
Vamos adicionar mais um desejo - que os dois últimos números na versão sejam o ano e a data de construção do pacote, e também adicionar versões pré-lançamento. Adicionar esses dados no arquivo do projeto e verificar antes de cada envio é claro — mas isso pode ser feito também no pipeline, obtendo a versão do pacote a partir do contexto e passando através do argumento da linha de comando.
Concordamos que, se a mensagem do commit contém uma linha do tipo release (v. / ver. / version) (rev. / revisão )?, então vamos pegar a versão do pacote dessa linha, complementá-la com a data atual e passá-la como argumento para o comando dotnet pack. Na ausência da linha - simplesmente não vamos construir o pacote.
A seguinte tarefa é resolvida pelo 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=$versionAdicionamos o script à tarefa pack and deploy job e observamos a construção dos pacotes rigidamente na presença da linha especificada na mensagem do commit.
Total
Depois de gastar cerca de meia hora a uma hora escrevendo a configuração, depurando no PowerShell local e, possivelmente, algumas execuções malsucedidas, obtivemos uma configuração simples para automatizar tarefas rotineiras.
Claro, o GitLab CI/CD é muito mais abrangente e multifacetado do que pode parecer após a leitura deste guia - . Há até mesmo , que permite
detecta automaticamente, constrói, testa, implementa e monitora suas aplicações
Agora, nos planos - configurar um pipeline para o deployment de aplicações no Azure, utilizando Pulumi e a detecção automática do ambiente de destino, o que será abordado no próximo artigo.
Fonte: habr.com








