Tutorial: Criar uma ação do GitHub com o .NET
Saiba como criar um aplicativo .NET que pode ser usado como uma Ação do GitHub. As ações do GitHub permitem a automação e a composição do fluxo de trabalho. Com o GitHub Actions, você pode criar, testar e implantar código-fonte do GitHub. Além disso, as ações expõem a capacidade de interagir programaticamente com problemas, criar solicitações pull, executar revisões de código e gerenciar ramificações. Para obter mais informações sobre a integração contínua com as ações do GitHub, consulte Criando e testando o .NET.
Neste tutorial, irá aprender a:
- Preparar um aplicativo .NET para ações do GitHub
- Definir entradas e saídas de ação
- Redigir um fluxo de trabalho
Pré-requisitos
- Uma conta do GitHub
- O SDK do .NET 6 ou posterior
- Um ambiente de desenvolvimento integrado (IDE) .NET
- Sinta-se à vontade para usar o IDE do Visual Studio
A intenção do aplicativo
O aplicativo neste tutorial executa a análise de métrica de código por:
Digitalização e descoberta de arquivos de projeto *.csproj e *.vbproj.
Analisando o código-fonte descoberto nesses projetos para:
- Complexidade ciclomática
- Índice de manutenibilidade
- Profundidade da herança
- Acoplamento de classe
- Número de linhas do 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 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.
As referências ao código-fonte neste tutorial têm partes do aplicativo omitidas por brevidade. O código completo do aplicativo está disponível no GitHub.
Explorar a aplicação
O aplicativo de console .NET usa o CommandLineParser
pacote NuGet para analisar argumentos no ActionInputs
objeto.
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 gravará o valor da "GREETINGS"
variável de ambiente, se estiver disponível no ambiente de execução atual. As Name
propriedades e são analisadas e Branch
atribuídas a partir do último segmento de uma "/"
cadeia de caracteres delimitada.
Com a classe de entradas de ação definida, concentre-se no 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 Program
arquivo é simplificado para brevidade, para explorar a fonte de exemplo completa, consulte Program.cs. Os mecânicos em vigor demonstram o código clichê necessário para usar:
Referências de projeto ou pacote externo podem ser usadas e registradas com injeção de dependência. O Get<TService>
é uma função local estática, que requer a IHost
instância e é usada para resolver os serviços necessários. Com o singleton, o CommandLine.Parser.Default
aplicativo obtém uma parser
instância do 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 Definindo códigos de saída para ações.
Quando os args são analisados com êxito, o aplicativo foi chamado corretamente com as entradas necessárias. Nesse caso, é feita uma chamada para a funcionalidade StartAnalysisAsync
principal.
Para escrever valores de saída, você deve seguir o formato reconhecido por GitHub Actions: Setting an output parameter.
Preparar o aplicativo .NET para ações do GitHub
As Ações do GitHub suportam duas variações de desenvolvimento de aplicativos, ou
- JavaScript (opcionalmente TypeScript)
- Contêiner do Docker (qualquer aplicativo executado no Docker)
O ambiente virtual onde a Ação do GitHub está hospedada pode ou não ter o .NET instalado. Para obter informações sobre o que está pré-instalado no ambiente de destino, consulte Ambientes virtuais de ações do GitHub. Embora seja possível executar comandos da CLI do .NET a partir dos fluxos de trabalho do GitHub Actions, para um funcionamento mais completo. Ação do GitHub baseada em NET, recomendamos que você coloque o aplicativo em contêineres. Para obter mais informações, consulte Containerize a .NET app.
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" ]
Nota
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. Ele começa do zero com a imagem do SDK e adiciona a saída de compilação do conjunto anterior de camadas. Para aplicativos que não exigem o SDK do .NET como parte de sua funcionalidade, eles devem confiar apenas no .NET Runtime. Isso reduz muito o tamanho da imagem.
FROM mcr.microsoft.com/dotnet/runtime:7.0
Aviso
Preste muita atenção a cada etapa dentro do Dockerfile, pois ele difere do Dockerfile padrão criado a partir da funcionalidade "adicionar suporte ao docker". Em particular, as últimas etapas variam por não especificar um novo WORKDIR
que mudaria o caminho para o .ENTRYPOINT
As etapas anteriores do Dockerfile incluem:
- Definindo a imagem base como
mcr.microsoft.com/dotnet/sdk:7.0
o aliasbuild-env
. - Copiando o conteúdo e publicando o aplicativo .NET:
- O aplicativo é publicado usando o
dotnet publish
comando.
- O aplicativo é publicado usando o
- Aplicação de etiquetas ao recipiente.
- Retransmitindo a imagem do SDK do .NET a partir de
mcr.microsoft.com/dotnet/sdk:7.0
- Copiando a saída de compilação publicada do
build-env
. - Definindo o ponto de entrada, que delega ao
dotnet /DotNet.GitHubAction.dll
.
Gorjeta
O MCR em mcr.microsoft.com
significa "Microsoft Container Registry", e é o catálogo de contêineres sindicados da Microsoft a partir do hub oficial do Docker. Para obter mais informações, consulte Catálogo de contêineres de sindicatos da Microsoft.
Atenção
Se você usar um arquivo global.json para fixar a versão do SDK, deverá consultar explicitamente essa versão no Dockerfile. Por exemplo, se você usou global.json para fixar a versão 5.0.300
do SDK, seu Dockerfile deve usar mcr.microsoft.com/dotnet/sdk:5.0.300
. Isso evita quebrar as ações do GitHub quando uma nova revisão secundária é lançada.
Definir entradas e saídas de ação
Na seção Explore o aplicativo, você aprendeu sobre a ActionInputs
aula. Este objeto representa as entradas para a Ação do GitHub. Para que o GitHub reconheça que o repositório é uma Ação do GitHub, 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:
- O
name
edescription
da Ação GitHub - O
branding
, que é usado no GitHub Marketplace para ajudar a identificar sua ação de forma mais exclusiva - O
inputs
, que mapeia um para um com aActionInputs
classe - O
outputs
, que é gravado noProgram
e usado como parte da composição do fluxo de trabalho - O
runs
nó, que informa ao GitHub que o aplicativo é umdocker
aplicativo e quais argumentos passar para ele
Para obter mais informações, consulte Sintaxe de metadados para ações do GitHub.
Variáveis de ambiente pré-definidas
Com o GitHub Actions, você obterá muitas variáveis de ambiente por padrão. Por exemplo, a variável GITHUB_REF
sempre conterá uma referência à ramificação ou tag que disparou a execução do fluxo de trabalho. GITHUB_REPOSITORY
tem o proprietário e o nome do repositório, por exemplo, dotnet/docs
.
Você deve explorar as variáveis de ambiente predefinidas e usá-las de acordo.
Composição do fluxo de trabalho
Com o aplicativo .NET em contêiner e as entradas e saídas de ação definidas , você está pronto para consumir a ação. As Ações do GitHub não precisam ser publicadas no GitHub Marketplace para serem usadas. Os fluxos de trabalho são definidos no diretório .github/workflows 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 Ações do GitHub em contêineres, é necessário usar runs-on: ubuntu-latest
o . Para obter mais informações, consulte Sintaxe jobs.<job_id>.runs-on
do fluxo de trabalho .
O arquivo YAML do fluxo de trabalho anterior define três nós principais:
- O
name
do fluxo de trabalho. Esse nome também é usado ao criar um selo de status do fluxo de trabalho. - O
on
nó define quando e como a ação é acionada. - O
jobs
nó descreve os vários trabalhos e etapas dentro de cada trabalho. As etapas individuais consomem as ações do GitHub.
Para obter mais informações, consulte Criando seu primeiro fluxo de trabalho.
Focando no steps
nó, 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. Os passos são orquestrados de tal forma que são sequenciais, comunicativos e compostos. Com várias Ações do GitHub representando etapas, cada uma com entradas e saídas, os fluxos de trabalho podem ser compostos.
Nas etapas anteriores, você pode observar:
Foi feito check-out do repositório.
Uma mensagem é impressa no log do 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êiner neste tutorial.env
Cria uma variável"GREETING"
de ambiente , que é impressa na execução do aplicativo.with
Especifica cada uma das entradas de ação necessárias.
Uma etapa condicional, denominada
Create pull request
é executada quando adotnet-code-metrics
etapa especifica um parâmetro de saída com um valor deupdated-metrics
true
.
Importante
O GitHub permite a criação de segredos criptografados. Os segredos podem ser usados na composição do fluxo de trabalho, usando a ${{ secrets.SECRET_NAME }}
sintaxe. No contexto de uma Ação do GitHub, há um token do GitHub que é preenchido automaticamente por padrão: ${{ secrets.GITHUB_TOKEN }}
. Para obter mais informações, consulte Sintaxe de contexto e expressão para ações do GitHub.
Juntar tudo
O repositório dotnet/samples do GitHub é o lar de muitos projetos de código-fonte de exemplo .NET, incluindo o aplicativo neste tutorial.
O arquivo CODE_METRICS.md gerado é navegável. Este 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. À medida que você navega pelo arquivo, cada seção expõe oportunidades de detalhamento com um resumo de cada área. A marcação tem seções dobráveis como uma conveniência adicional.
A hierarquia progride a partir de:
- Arquivo de projeto para montagem
- Assembly para namespace
- Namespace para named-type
- 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
a para a ramificação, a push
main
ação é acionada para ser executada. Quando ele é executado, a guia Ações no GitHub relatará o fluxo de log ao vivo de sua execução. Aqui está um exemplo de log da .NET code metrics
execução:
Melhoramentos de desempenho
Se você acompanhou o exemplo, deve ter notado que toda vez que essa ação for usada, ela fará uma compilação docker para essa imagem. Assim, cada gatilho é confrontado com algum tempo para construir o contêiner antes de executá-lo. Antes de lançar suas Ações do GitHub para o mercado, você deve:
- (automaticamente) Criar a imagem do Docker
- Envie a imagem do docker para o Registro de Contêiner do GitHub (ou qualquer outro registro de contêiner público)
- Altere 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, consulte GitHub Docs: Trabalhando com o registro de contêiner.
Consulte também
- Host Genérico .NET
- Injeção de dependência no .NET
- Valores de métricas de código
- Ação do GitHub de código aberto incorpora o .NET com um fluxo de trabalho para criar e enviar a imagem do docker automaticamente.