Tutorial: Criar uma ação do GitHub com o .NET
Saiba como criar um GitHub Action com um aplicativo .NET em contêineres. GitHub Actions permite a automação e a composição do fluxo de trabalho. Com o GitHub Actions, você pode criar, testar e implantar seu código-fonte do GitHub. Além disso, as ações mostram a capacidade de interagir de forma programática com problemas, criar solicitações de pull, executar revisões de código e gerenciar branches. Para obter mais informações sobre a integração contínua com GitHub Actions, consulte Compilar e testar o .NET.
Neste tutorial, você aprenderá a:
- Preparar um aplicativo .NET para GitHub Actions
- Definir entradas e saídas de ação
- Criar um fluxo de trabalho
Pré-requisitos
- Uma conta do GitHub
- O SDK do .NET 6 ou posterior
- Um ambiente de desenvolvimento integrado do .NET (IDE)
- Fique à vontade para usar o IDE do Visual Studio
A intenção do aplicativo
Neste tutorial, o aplicativo executa a análise de métrica de código ao:
analisar e encontrar arquivos de projeto *.csproj e *.vbproj.
analisar o código-fonte encontrado nesses projetos para:
- Complexidade ciclomática
- Índice de facilidade de manutenção
- Profundidade da herança
- Acoplamento de classes
- Número de linhas de código-fonte
- Linhas aproximadas de código executável
Criar (ou atualizar) um arquivo CODE_METRICS.md.
O aplicativo não é responsável por criar uma solicitação de pull com as alterações no arquivo CODE_METRICS.md. Essas alterações são gerenciadas como parte da composição do fluxo de trabalho.
Neste tutorial, as referências ao código-fonte têm partes do aplicativo omitidas para mais rapidez. O código de aplicativo completo está disponível no GitHub.
Explorar o aplicativo
O aplicativo de console .NET usa o pacote CommandLineParser
NuGet para analisar argumentos no objeto ActionInputs
.
using CommandLine;
namespace DotNet.GitHubAction;
public class ActionInputs
{
string _repositoryName = null!;
string _branchName = null!;
public ActionInputs()
{
if (Environment.GetEnvironmentVariable("GREETINGS") is { Length: > 0 } greetings)
{
Console.WriteLine(greetings);
}
}
[Option('o', "owner",
Required = true,
HelpText = "The owner, for example: \"dotnet\". Assign from `github.repository_owner`.")]
public string Owner { get; set; } = null!;
[Option('n', "name",
Required = true,
HelpText = "The repository name, for example: \"samples\". Assign from `github.repository`.")]
public string Name
{
get => _repositoryName;
set => ParseAndAssign(value, str => _repositoryName = str);
}
[Option('b', "branch",
Required = true,
HelpText = "The branch name, for example: \"refs/heads/main\". Assign from `github.ref`.")]
public string Branch
{
get => _branchName;
set => ParseAndAssign(value, str => _branchName = str);
}
[Option('d', "dir",
Required = true,
HelpText = "The root directory to start recursive searching from.")]
public string Directory { get; set; } = null!;
[Option('w', "workspace",
Required = true,
HelpText = "The workspace directory, or repository root directory.")]
public string WorkspaceDirectory { get; set; } = null!;
static void ParseAndAssign(string? value, Action<string> assign)
{
if (value is { Length: > 0 } && assign is not null)
{
assign(value.Split("/")[^1]);
}
}
}
A classe de entradas de ação anterior define várias entradas necessárias para que o aplicativo seja executado com êxito. O construtor grava o valor da variável de ambiente "GREETINGS"
, se estiver disponível no ambiente de execução atual. As propriedades Name
e Branch
são analisadas e atribuídas a partir do último segmento de uma cadeia de caracteres "/"
delimitada.
Com a classe de entradas de ação definida,tenha como foco o arquivo Program.cs.
using System.Text;
using CommandLine;
using DotNet.GitHubAction;
using DotNet.GitHubAction.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using static CommandLine.Parser;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddGitHubActionServices();
using IHost host = builder.Build();
ParserResult<ActionInputs> parser = Default.ParseArguments<ActionInputs>(() => new(), args);
parser.WithNotParsed(
errors =>
{
host.Services
.GetRequiredService<ILoggerFactory>()
.CreateLogger("DotNet.GitHubAction.Program")
.LogError("{Errors}", string.Join(
Environment.NewLine, errors.Select(error => error.ToString())));
Environment.Exit(2);
});
await parser.WithParsedAsync(
async options => await StartAnalysisAsync(options, host));
await host.RunAsync();
static async ValueTask StartAnalysisAsync(ActionInputs inputs, IHost host)
{
// Omitted for brevity, here is the pseudo code:
// - Read projects
// - Calculate code metric analytics
// - Write the CODE_METRICS.md file
// - Set the outputs
var updatedMetrics = true;
var title = "Updated 2 projects";
var summary = "Calculated code metrics on two projects.";
// Do the work here...
// Write GitHub Action workflow outputs.
var gitHubOutputFile = Environment.GetEnvironmentVariable("GITHUB_OUTPUT");
if (!string.IsNullOrWhiteSpace(gitHubOutputFile))
{
using StreamWriter textWriter = new(gitHubOutputFile, true, Encoding.UTF8);
textWriter.WriteLine($"updated-metrics={updatedMetrics}");
textWriter.WriteLine($"summary-title={title}");
textWriter.WriteLine($"summary-details={summary}");
}
await ValueTask.CompletedTask;
Environment.Exit(0);
}
O arquivo Program
é simplificado para maior rapidez, para explorar a fonte de exemplo completa, consulte Program.cs. O funcionamento atual mostra um código de texto clichê necessário para:
Referências externas de projeto ou pacote podem ser usadas e registradas com injeção de dependência. O Get<TService>
é uma função local estática, que requer a instância IHost
e é usada para resolver os serviços necessários. Com o singleton CommandLine.Parser.Default
, o aplicativo obtém uma instância parser
de args
. Quando os argumentos não podem ser analisados, o aplicativo sai com um código de saída diferente de zero. Para obter mais informações, consulte "Como configurar códigos de saída para ações".
Quando os args são analisados com êxito, o aplicativo é chamado corretamente com as entradas necessárias. Nesse caso, uma chamada para a funcionalidade primária StartAnalysisAsync
é feita.
Para gravar valores de saída, você deve seguir o formato reconhecido por GitHub Actions: configurando um parâmetro de saída.
Preparar um aplicativo .NET para GitHub Actions
GitHub Actions oferecem suporte a duas variações de desenvolvimento de aplicativos:
- JavaScript (opcionalmente TypeScript)
- Contêiner do Docker (qualquer aplicativo executado no Docker)
O ambiente virtual em que o GitHub Action está hospedado pode ou não ter o .NET instalado. Para obter informações sobre o que é pré-instalado no ambiente de destino, consulte Ambientes Virtuais do GitHub Actions. Embora seja possível executar comandos da CLI do .NET dos fluxos de trabalho do GitHub Actions, para um funcionamento mais completo do GitHub Action, baseado em NET, recomendamos que você conteinerize o aplicativo. Para obter mais informações, consulte Containerize um aplicativo .NET.
O Dockerfile
Um Dockerfile é um conjunto de instruções para criar uma imagem. Para aplicativos .NET, o Dockerfile geralmente fica na raiz do diretório ao lado de um arquivo de solução.
# Set the base image as the .NET 7.0 SDK (this includes the runtime)
FROM mcr.microsoft.com/dotnet/sdk:7.0@sha256:d32bd65cf5843f413e81f5d917057c82da99737cb1637e905a1a4bc2e7ec6c8d as build-env
# Copy everything and publish the release (publish implicitly restores and builds)
WORKDIR /app
COPY . ./
RUN dotnet publish ./DotNet.GitHubAction/DotNet.GitHubAction.csproj -c Release -o out --no-self-contained
# Label the container
LABEL maintainer="David Pine <david.pine@microsoft.com>"
LABEL repository="https://github.com/dotnet/samples"
LABEL homepage="https://github.com/dotnet/samples"
# Label as GitHub action
LABEL com.github.actions.name="The name of your GitHub Action"
# Limit to 160 characters
LABEL com.github.actions.description="The description of your GitHub Action."
# See branding:
# https://docs.github.com/actions/creating-actions/metadata-syntax-for-github-actions#branding
LABEL com.github.actions.icon="activity"
LABEL com.github.actions.color="orange"
# Relayer the .NET SDK, anew with the build output
FROM mcr.microsoft.com/dotnet/sdk:7.0@sha256:d32bd65cf5843f413e81f5d917057c82da99737cb1637e905a1a4bc2e7ec6c8d
COPY --from=build-env /app/out .
ENTRYPOINT [ "dotnet", "/DotNet.GitHubAction.dll" ]
Observação
O aplicativo .NET neste tutorial depende do SDK do .NET como parte de sua funcionalidade. O Dockerfile cria um novo conjunto de camadas do Docker, independente das anteriores. O Dockerfile começa do zero com a imagem do SDK e adiciona a saída de build do conjunto anterior de camadas. Para aplicativos que não exigem o SDK do .NET como parte de sua funcionalidade, devem depender apenas do Runtime do .NET. Isso reduz consideravelmente o tamanho da imagem.
FROM mcr.microsoft.com/dotnet/runtime:7.0
Aviso
Preste muita atenção a cada etapa dentro do Dockerfile, pois ela difere do Dockerfile padrão criado a partir da funcionalidade "adicionar suporte ao Docker". Em particular, as últimas etapas variam não especificando uma nova WORKDIR
que alteraria o caminho para o ENTRYPOINT
do aplicativo.
As etapas anteriores do Dockerfile incluem:
- Definindo a imagem base como
mcr.microsoft.com/dotnet/sdk:7.0
o aliasbuild-env
. - Copiar conteúdos e publicar o aplicativo .NET:
- O aplicativo é publicado usando o comando
dotnet publish
.
- O aplicativo é publicado usando o comando
- Aplicando rótulos ao contêiner.
- Retransmissão da imagem do SDK do .NET a partir de
mcr.microsoft.com/dotnet/sdk:7.0
- Copiando a saída de build publicada do
build-env
. - Definindo o ponto de entrada, que atribui para
dotnet /DotNet.GitHubAction.dll
.
Dica
O MCR no mcr.microsoft.com
significa"Registro de Contêiner da Microsoft" e é o catálogo de contêineres sindicalizado da Microsoft a partir do hub oficial do Docker. Para obter mais informações, consulte Microsoft analisa catálogo de contêiner.
Cuidado
Se você usar um arquivo global.json para fixar a versão do SDK, consulte explicitamente essa versão em seu Dockerfile. Por exemplo, se você tiver usado global.json para fixar a versão do SDK5.0.300
, seu Dockerfile deve usar mcr.microsoft.com/dotnet/sdk:5.0.300
. Isso impede a interrupção do GitHub Actions quando uma nova revisão secundária é liberada.
Definir entradas e saídas de ação
Na seção Explorar o aplicativo, você aprendeu sobre a classe ActionInputs
. Esse objeto representa as entradas para o GitHub Action. Para que o GitHub reconheça que o repositório é um GitHub Action, você precisa ter um arquivo action.yml na raiz do repositório.
name: 'The title of your GitHub Action'
description: 'The description of your GitHub Action'
branding:
icon: activity
color: orange
inputs:
owner:
description:
'The owner of the repo. Assign from github.repository_owner. Example, "dotnet".'
required: true
name:
description:
'The repository name. Example, "samples".'
required: true
branch:
description:
'The branch name. Assign from github.ref. Example, "refs/heads/main".'
required: true
dir:
description:
'The root directory to work from. Examples, "path/to/code".'
required: false
default: '/github/workspace'
outputs:
summary-title:
description:
'The title of the code metrics action.'
summary-details:
description:
'A detailed summary of all the projects that were flagged.'
updated-metrics:
description:
'A boolean value, indicating whether or not the action updated metrics.'
runs:
using: 'docker'
image: 'Dockerfile'
args:
- '-o'
- ${{ inputs.owner }}
- '-n'
- ${{ inputs.name }}
- '-b'
- ${{ inputs.branch }}
- '-d'
- ${{ inputs.dir }}
O arquivo action.yml anterior define:
name
edescription
do GitHub Actionbranding
, que é usado no GitHub Marketplace para ajudar a identificar sua ação de forma mais exclusivainputs
, que mapeia um a um com a classeActionInputs
outputs
, que é gravado noProgram
e usado como parte da Composição do fluxo de trabalho- O nó
runs
, que informa ao GitHub que o aplicativo é um aplicativodocker
e os argumentos para passar para ele
Para obter mais informações, consulte Sintaxe de metadados para GitHub Actions.
Variáveis de ambiente predefinidas
Com GitHub Actions, você terá muitas variáveis de ambiente por padrão. Por exemplo, a variável GITHUB_REF
sempre contém uma referência à branch ou marca que iniciou a execução do fluxo de trabalho. GITHUB_REPOSITORY
tem o nome do proprietário e do repositório, por exemplo, dotnet/docs
.
Você deve explorar as variáveis de ambiente predefinidas e usá-las adequadamente.
Composição do fluxo de trabalho
Com o aplicativo .NET em contêineres e as entradas e saídas de ação definidas, você está pronto para realizar a ação. GitHub Actions não precisam ser publicados no GitHub Marketplace a serem usados. Os fluxos de trabalho são definidos no diretório .github/fluxos de trabalho de um repositório como arquivos YAML.
# The name of the work flow. Badges will use this name
name: '.NET code metrics'
on:
push:
branches: [ main ]
paths:
- 'github-actions/DotNet.GitHubAction/**' # run on all changes to this dir
- '!github-actions/DotNet.GitHubAction/CODE_METRICS.md' # ignore this file
workflow_dispatch:
inputs:
reason:
description: 'The reason for running the workflow'
required: true
default: 'Manual run'
jobs:
analysis:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v3
- name: 'Print manual run reason'
if: ${{ github.event_name == 'workflow_dispatch' }}
run: |
echo 'Reason: ${{ github.event.inputs.reason }}'
- name: .NET code metrics
id: dotnet-code-metrics
uses: dotnet/samples/github-actions/DotNet.GitHubAction@main
env:
GREETINGS: 'Hello, .NET developers!' # ${{ secrets.GITHUB_TOKEN }}
with:
owner: ${{ github.repository_owner }}
name: ${{ github.repository }}
branch: ${{ github.ref }}
dir: ${{ './github-actions/DotNet.GitHubAction' }}
- name: Create pull request
uses: peter-evans/create-pull-request@v4
if: ${{ steps.dotnet-code-metrics.outputs.updated-metrics }} == 'true'
with:
title: '${{ steps.dotnet-code-metrics.outputs.summary-title }}'
body: '${{ steps.dotnet-code-metrics.outputs.summary-details }}'
commit-message: '.NET code metrics, automated pull request.'
Importante
Para GitHub Actions em contêineres, você precisa usar runs-on: ubuntu-latest
. Para obter mais informações, consulte Sintaxe do fluxo de trabalhojobs.<job_id>.runs-on
.
O arquivo YAML do fluxo de trabalho anterior define três nós primários:
- O
name
do fluxo de trabalho. Esse nome também é usado ao criar uma notificação de status de fluxo de trabalho. - O nó
on
define quando e como a ação é iniciada. - O nó
jobs
descreve os diversos trabalhos e etapas de cada trabalho. Etapas individuais produzem GitHub Actions.
Para obter mais informações, consulte Criando seu primeiro fluxo de trabalho.
Com foco no nó steps
, a composição é mais óbvia:
steps:
- uses: actions/checkout@v3
- name: 'Print manual run reason'
if: ${{ github.event_name == 'workflow_dispatch' }}
run: |
echo 'Reason: ${{ github.event.inputs.reason }}'
- name: .NET code metrics
id: dotnet-code-metrics
uses: dotnet/samples/github-actions/DotNet.GitHubAction@main
env:
GREETINGS: 'Hello, .NET developers!' # ${{ secrets.GITHUB_TOKEN }}
with:
owner: ${{ github.repository_owner }}
name: ${{ github.repository }}
branch: ${{ github.ref }}
dir: ${{ './github-actions/DotNet.GitHubAction' }}
- name: Create pull request
uses: peter-evans/create-pull-request@v4
if: ${{ steps.dotnet-code-metrics.outputs.updated-metrics }} == 'true'
with:
title: '${{ steps.dotnet-code-metrics.outputs.summary-title }}'
body: '${{ steps.dotnet-code-metrics.outputs.summary-details }}'
commit-message: '.NET code metrics, automated pull request.'
O jobs.steps
representa a composição do fluxo de trabalho. As etapas são administradas de modo que sejam sequenciais, comunicativas e combinável. Com vários GitHub Actions representando etapas, cada uma com entradas e saídas, os fluxos de trabalho podem ser combinados.
Nas etapas anteriores, você pode observar:
O repositório está em check-out.
Uma mensagem é impressa no log de fluxo de trabalho, quando executada manualmente.
Uma etapa identificada como
dotnet-code-metrics
:uses: dotnet/samples/github-actions/DotNet.GitHubAction@main
é o local do aplicativo .NET em contêineres neste tutorial.env
cria uma variável de ambiente"GREETING"
, que é impressa na execução do aplicativo.with
especifica cada uma das entradas de ação necessárias.
Uma etapa condicional, nomeada
Create pull request
, é executada quando a etapadotnet-code-metrics
especifica um parâmetro de saída comupdated-metrics
um valor detrue
.
Importante
O GitHub permite a criação de segredos criptografados. Os segredos podem ser usados na composição do fluxo de trabalho usando a sintaxe ${{ secrets.SECRET_NAME }}
. No contexto de um GitHub Action, há um token GitHub que é preenchido automaticamente por padrão: ${{ secrets.GITHUB_TOKEN }}
. Para obter mais informações, consulte Sintaxe de contexto e de expressão do GitHub Actions.
Colocar tudo isso junto
O repositório GitHub dotnet/samples abriga muitos projetos de código-fonte de exemplo do .NET, incluindo o aplicativo neste tutorial.
O arquivo CODE_METRICS.md gerado é navegável. Esse arquivo representa a hierarquia dos projetos analisados. Cada projeto tem uma seção de nível superior e um emoji que representa o status geral da maior complexidade ciclomática para objetos aninhados. Conforme você navega pelo arquivo, cada seção mostra oportunidades de detalhamento com um resumo de cada área. O markdown tem seções recolhiveis como uma conveniência a mais.
A hierarquia progride de:
- Arquivo de projeto para assembly
- Assembly para namespace
- Namespace para tipo nomeado
- Cada tipo nomeado tem uma tabela e cada tabela tem:
- Links para números de linha para campos, métodos e propriedades
- Classificações individuais para métricas de código
Em ação
O fluxo de trabalho especifica que on
e push
para o branch main
, a ação deve iniciar a execução. Quando for executado, a guia Ações no GitHub informa o fluxo de log ao vivo de sua execução. Veja aqui um log de exemplo a partir da execução .NET code metrics
:
Aprimoramentos de desempenho
Se você seguiu o exemplo, talvez tenha notado que toda vez que essa ação é usada, ela fará um build do Docker para essa imagem. Portanto, cada gatilho é confrontado com algum tempo para compilar o contêiner antes de executá-lo. Antes de liberar seu GitHub Actions para o marketplace, você deve:
- Criar (automaticamente) a imagem do Docker
- Enviar a imagem do docker por push para o Registro de Contêiner do GitHub (ou qualquer outro registro de contêiner público)
- Alterar a ação para não criar a imagem, mas para usar uma imagem de um registro público.
# Rest of action.yml content removed for readability
# using Dockerfile
runs:
using: 'docker'
image: 'Dockerfile' # Change this line
# using container image from public registry
runs:
using: 'docker'
image: 'docker://ghcr.io/some-user/some-registry' # Starting with docker:// is important!!
Para obter mais informações, confira GitHub Docs: como trabalhar com o Registro de contêiner.
Confira também
- Host Genérico .NET
- Injeção de dependência no .NET
- Valores de métrica de código
- Build do GitHub Action de software livre no .NET com um fluxo de trabalho para compilar e enviar a imagem do docker automaticamente.