Pipelines para .NET
Saiba como montar uma esteira automatizada para seu programa ou biblioteca em .NET, com exemplos em GitHub Actions.
Pipelines
As esteiras automatizadas, também conhecidas como pipelines, são seqüências de comandos executados para garantir a integridade do seu código e para gerar um artefato final, como um programa executável ou uma biblioteca.
Ter uma pipeline significa ter um processo consistente que minimiza o risco de erros humanos no produto final e economiza tempo do programador, pois ele pode se ocupar de outras tarefas enquanto o código é compilado, verificado e empacotado.
Etapas
flowchart TD
checkout -->
clean -->
restore -->
audit -->
sbom -->
build -->
unittests[unit tests]
unittests --se for programa--> publish --com docker--> dockerk8s[docker / kubernetes]
publish --sem docker--> zipinstaller[zip / instalador]
unittests --se for pacote nuget--> pack --> nugetpush[nuget push]
checkout
Obtém o código da branch em questão, no Git. Se for um processo de integração (CI), pega o código da branch que pretende ser mergeada; se for um deploy (CD), pega o código da branch de publicação, como develop, master ou release_candidate.
Linha de comando: git clone
clean
Limpa as pastas bin e obj, para garantir que tudo vai começar do zero, sem interferências de compilações anteriores.
Linha de comando: dotnet clean
restore
Garante que as referências entre os projetos na solução estão corretas e faz o download dos pacotes NuGet necessários.
Linha de comando: dotnet restore
audit
Verifica se há algum pacote NuGet do projeto com problemas de segurança, conferindo nas listas de vulnerabilidades CVE (Common Vulnerabilities and Exposures) e GHSA (GitHub Advisory Database).
Linha de comando: dotnet list package --vulnerable --include-transitive
sbom
O SBOM, em inglês, software bill of materials, é um documento que informa quais componentes foram utilizados para a produção de um programa ou biblioteca.
Esse documento é de suma importância para softwares críticos, pois através dele, as organizações podem facilmente saber quais de suas aplicações estão em perigo quando uma vulnerabilidade em uma biblioteca é reportada. Após o ciberataque ao governo dos EUA em 2020, os SBOMs tornaram-se uma prática endossada pela Casa Branca.
Recomendo o formato CycloneDX, por ser mais sucinto e de leitura mais fácil.
Linha de comando:
- Em formato CycloneDX:
dotnet CycloneDX
- Em formato SPDX:
sbom-tool generate
build
Compila o código da solução.
Linha de comando: dotnet build
unit tests
Roda os testes unitários da solução para assegurar que estão passando.
Nessa etapa, podemos produzir um relatório que exibe o nível de cobertura dos testes unitários em relação ao código, mostrando quais classes, métodos e linhas foram abrangidos pelos testes. O ReportGenerator é a principal ferramenta para esses relatórios em projetos .NET.
Linha de comando:
- Testes unitários:
dotnet test
- Relatório de cobertura:
reportgenerator
publish
Gera o programa final, voltado para execução. Essa etapa difere do build porque nesta, pode-se especificar opções de compilação, como o runtime de destino, se é self-contained, se é single-file, entre outras.
Linha de comando: dotnet publish
pack
Produz um pacote NuGet, no caso de um código que é feito para ser uma biblioteca.
Linha de comando: dotnet pack
nuget push
Sobe o pacote para um servidor NuGet, privado ou público, para que possa ser utilizado por outras pessoas.
Linha de comando: dotnet nuget push
Motores de pipeline
Existem vários motores de pipeline disponíveis, como o GitHub Actions, GitLab CI, Jenkins, Azure Pipelines, CircleCI e diversos outros.
Além desses, você pode ter sua pipeline como um script para rodar localmente na sua máquina. Essa é uma boa prática por ser uma salvaguarda caso sua pipeline remota esteja fora do ar e porque permite testar modificações antes de commitá-las.
Recomendo pessoalmente usar scripts PowerShell para pipelines locais, pois é uma linguagem multiplataforma e amigável, com fácil interação com XML e JSON. Contudo, você pode usar outras linguagens de script, como Batch, Shell, Python e outras que você preferir.
Exemplo em GitHub Actions para programa .NET
name: Publicar console / API / programa desktop
on:
workflow_dispatch: # acionamento manual
inputs:
version:
required: true
type: string
rid:
required: true
default: linux-x64 # onde o programa vai rodar
type: string
# https://learn.microsoft.com/en-us/dotnet/core/rid-catalog
jobs:
gerar_programa:
runs-on: ubuntu-latest
env:
OUTPUT_FOLDER: ${{ format('./out/{0}/', inputs.rid) }}
VERSION_NAME: ${{ inputs.version }}
RID: ${{ inputs.rid }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Instalar .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x # versão do .NET aqui
- name: Instalar CycloneDX .NET
run: dotnet tool install --global CycloneDX
- name: Limpar solução
run: dotnet clean --nologo --verbosity quiet
- name: Restaurar solução
run: dotnet restore --nologo --verbosity quiet
- name: Auditar solução
shell: pwsh
run: |
$projectPath = "./src/MeuProjeto.Console/MeuProjeto.Console.csproj"
$jsonObj = (dotnet list $projectPath package --vulnerable --include-transitive --format json) | ConvertFrom-Json;
$hasAnyVulnerability = ($jsonObj.projects[0].frameworks -ne $null);
if ($hasAnyVulnerability) {
dotnet list package --vulnerable --include-transitive;
exit 1;
}
- name: Compilar solução
run: dotnet build --no-restore --configuration Release --nologo --verbosity quiet
- name: Rodar testes unitários
run: dotnet test --no-build --configuration Release --nologo --verbosity quiet --collect:"XPlat Code Coverage" --results-directory ./TestResults/
- name: Relatório de cobertura de testes unitários
uses: danielpalme/ReportGenerator-GitHub-Action@5.2.4
with:
reports: TestResults/**/coverage.cobertura.xml
targetdir: TestResults
reporttypes: JsonSummary;Html
- name: Gerar SBOM
shell: pwsh
run: dotnet CycloneDX ./src/MeuProjeto.Console/MeuProjeto.Console.csproj -o $env:OUTPUT_FOLDER -f sbom.json -sv $env:VERSION_NAME --json
- name: Publicar programa
shell: pwsh
run: |
dotnet publish ./src/MeuProjeto.Console/MeuProjeto.Console.csproj `
--verbosity quiet `
--nologo `
--configuration Release `
-p:PublishSingleFile=true `
-p:Version=${env:VERSION_NAME} `
--self-contained true `
--runtime ${env:RID} `
--output ${env:OUTPUT_FOLDER};
- name: Setar atributos de execução (UNIX apenas)
if: ${{ startsWith(inputs.rid, 'linux') || startsWith(inputs.rid, 'osx') }}
shell: pwsh
run: chmod +x "${env:OUTPUT_FOLDER}/MeuProjeto.Console"
- name: Empacotar programa
shell: pwsh
run: |
$zipName = "MeuProjeto.Console_${env:VERSION_NAME}_${env:RID}.zip";
# se for Linux ou MacOSX, devemos usar o zip ao invés do Compress-Archive,
# para preservar os atributos de arquivos do Unix.
if ($IsWindows) {
Compress-Archive -CompressionLevel Optimal -Path $env:OUTPUT_FOLDER -DestinationPath "./out/${zipName}"
} else {
cd $env:OUTPUT_FOLDER
zip -9 -r ../${zipName} *
cd ../..
}
Remove-Item $env:OUTPUT_FOLDER -Force -Recurse -ErrorAction Ignore
echo "OUTPUT_FILE_NAME=${zipName}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
- name: Subir programa para resultados do workflow
uses: actions/upload-artifact@v4
with:
compression-level: 0 # não precisa comprimir pq passo anterior já comprime
name: ${{ env.OUTPUT_FILE_NAME }}
path: ${{ format('./out/{0}', env.OUTPUT_FILE_NAME) }}
- name: Subir SBOM para resultados do workflow
uses: actions/upload-artifact@v4
with:
name: sbom.json
path: ./out/sbom.json
- name: Subir relatório de cobertura para resultados do workflow
uses: actions/upload-artifact@v4
with:
name: relatorio_de_cobertura
path: TestResults
# outras etapas subseqüentes podem ser adicionadas aqui,
# como docker e kubernetes,
# ou geração de instalador, no caso de programas desktop.
Exemplo em GitHub Actions para pacote NuGet
name: Publicar pacote NuGet
on:
workflow_dispatch: # acionamento manual
jobs:
gerar_pacote_nuget:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Instalar .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x # versão do .NET aqui
- name: Instalar CycloneDX .NET
run: dotnet tool install --global CycloneDX
- name: Limpar solução
run: dotnet clean --nologo --verbosity quiet
- name: Restaurar solução
run: dotnet restore --nologo --verbosity quiet
- name: Auditar solução
shell: pwsh
run: |
$projectPath = "./src/MeuProjeto.Console/MeuProjeto.Console.csproj"
$jsonObj = (dotnet list $projectPath package --vulnerable --include-transitive --format json) | ConvertFrom-Json;
$hasAnyVulnerability = ($jsonObj.projects[0].frameworks -ne $null);
if ($hasAnyVulnerability) {
dotnet list package --vulnerable --include-transitive;
exit 1;
}
- name: Compilar solução
run: dotnet build --no-restore --configuration Release --nologo --verbosity quiet
- name: Rodar testes unitários
run: dotnet test --no-build --configuration Release --nologo --verbosity quiet --collect:"XPlat Code Coverage" --results-directory ./TestResults/
- name: Relatório de cobertura de testes unitários
uses: danielpalme/ReportGenerator-GitHub-Action@5.2.4
with:
reports: TestResults/**/coverage.cobertura.xml
targetdir: TestResults
reporttypes: JsonSummary;Html
- name: Ler versão do pacote
shell: pwsh
run: |
# o PackageVersion deve estar declarado no .csproj
([XML]$nugetCsprojXml = Get-Content ./src/MeuProjeto.Biblioteca/MeuProjeto.Biblioteca.csproj)
$versionName = $nugetCsprojXml.Project.PropertyGroup.PackageVersion
# adiciona às variáveis de ambiente do workflow
echo "VERSION_NAME=${versionName}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
- name: Gerar SBOM
shell: pwsh
run: dotnet CycloneDX ./src/MeuProjeto.Biblioteca/MeuProjeto.Biblioteca.csproj -o ./out/ -f sbom_meuprojeto_biblioteca.json -sv $env:VERSION_NAME --json
- name: Gerar pacote
run: dotnet pack ./src/MeuProjeto.Biblioteca/MeuProjeto.Biblioteca.csproj --nologo --verbosity quiet --configuration Release
- name: Subir pacote para servidor NuGet
shell: pwsh
run: |
$filePath = "./src/MeuProjeto.Biblioteca/bin/Release/MeuProjeto.Biblioteca.${env:VERSION_NAME}.nupkg"
dotnet nuget push $filePath --api-key $env:NUGET_API_KEY --source https://api.nuget.org/v3/index.json
# se for um servidor NuGet privado, usar outro source.
# portal web para testes do NuGet: https://int.nugettest.org
# source para testes: https://apiint.nugettest.org/v3/index.json
env:
NUGET_API_KEY: ${{ secrets.MINHA_NUGET_API_KEY }}
- name: Subir pacote para resultados do workflow
uses: actions/upload-artifact@v4
with:
compression-level: 0 # não precisa comprimir pq .nupkg já é um zip
name: ${{ format('MeuProjeto.Biblioteca.{0}.nupkg', env.VERSION_NAME) }}
path: ${{ format('./src/MeuProjeto.Biblioteca/bin/Release/MeuProjeto.Biblioteca.{0}.nupkg', env.VERSION_NAME) }}
- name: Subir SBOM para resultados do workflow
uses: actions/upload-artifact@v4
with:
name: sbom_meuprojeto_biblioteca.json
path: ./out/sbom_meuprojeto_biblioteca.json
- name: Subir relatório de cobertura para resultados do workflow
uses: actions/upload-artifact@v4
with:
name: relatorio_de_cobertura
path: TestResults
Fonte da imagem
https://dyno.co.nz/products/telescopic-and-expandable-conveyors/telescopic-conveyor/
Campinas/SP,
Brasil