Tutorial: Creación de una Acción de GitHub con .NET
Aprenda a crear una aplicación de .NET que se pueda usar como una acción de GitHub. Acciones de GitHub habilitar la automatización y composición del flujo de trabajo. Con Acciones de GitHub, puede compilar, probar e implementar el código de origen desde GitHub. Además, las acciones exponen la capacidad de interactuar mediante programación con problemas, crear solicitudes de incorporación de cambios, realizar revisiones de código y administrar ramas. Para obtener más información sobre la integración continua con Acciones de GitHub, consulte Creación y prueba de .NET.
En este tutorial, aprenderá a:
- Preparación de una aplicación .NET para Acciones de GitHub
- Definición de entradas y salidas de acción
- Redacción de un flujo de trabajo
Requisitos previos
- Una cuenta de GitHub
- SDK de .NET 6 o posterior
- Un entorno de desarrollo integrado (IDE) de .NET
- No dude en usar el IDE de Visual Studio
La intención de la aplicación
La aplicación de este tutorial realiza el análisis de métricas de código mediante:
Examinar y detectar archivos de proyecto *.csproj y *.vbproj .
Análisis del código fuente detectado en estos proyectos para:
- Complejidad ciclomática
- Índice de mantenimiento
- Profundidad de herencia
- Acoplamiento de clases
- Número de líneas de código fuente
- Líneas aproximadas de código ejecutable
Crear (o actualizar) un archivo CODE_METRICS.md.
La aplicación no es responsable de crear una solicitud de incorporación de cambios con los cambios realizados en el archivo CODE_METRICS.md. Estos cambios se administran como parte de la composición del flujo de trabajo.
Las referencias al código fuente de este tutorial tienen partes de la aplicación omitidas para mayor brevedad. El código de la aplicación completado está disponible en GitHub.
Exploración de la aplicación
La aplicación de consola de .NET usa el CommandLineParser
paquete NuGet para analizar argumentos en el 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]);
}
}
}
La clase de entradas de acción anterior define varias entradas necesarias para que la aplicación se ejecute correctamente. El constructor escribirá el valor de la variable "GREETINGS"
de entorno, si hay uno disponible en el entorno de ejecución actual. Las propiedades Name
y Branch
se analizan y asignan desde el último segmento de una cadena "/"
delimitada.
Con la clase de entradas de acción definida, céntrese en el archivo 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);
}
El archivo Program
se simplifica para mayor brevedad, para explorar el origen de ejemplo completo, consulte Program.cs. La mecánica en su lugar muestra el código reutilizable necesario para usar:
Se pueden usar referencias de paquetes o proyectos externos y registrarse con la inserción de dependencias. Get<TService>
es una función local estática, que requiere la instancia IHost
y se usa para resolver los servicios necesarios. Con el singleton CommandLine.Parser.Default
, la aplicación obtiene una instancia parser
de args
. Cuando los argumentos no se pueden analizar, la aplicación se cierra con un código de salida distinto de cero. Para más información, vea Establecimiento de códigos de salida para acciones.
Cuando los argumentos se analizan correctamente, se llamó a la aplicación correctamente con las entradas necesarias. En este caso, se realiza una llamada a la funcionalidad StartAnalysisAsync
principal.
Para escribir valores de salida, debe seguir el formato reconocido por Acciones de GitHub: Establecer un parámetro de salida.
Preparación de una aplicación .NET para Acciones de GitHub
Acciones de GitHub admite dos variaciones de desarrollo de aplicaciones, cualquiera de las dos
- JavaScript (opcionalmente TypeScript)
- Contenedor de Docker (cualquier aplicación que se ejecute en Docker)
El entorno virtual donde se hospeda la acción de GitHub puede o no tener instalado .NET. Para obtener información sobre lo que está preinstalado en el entorno de destino, consulte Acciones de GitHub en entornos virtuales. Aunque es posible ejecutar comandos de la CLI de .NET desde los flujos de trabajo de Acciones de GitHub, para un funcionamiento más completo. Acción de GitHub basada en NET, se recomienda incluir en contenedores la aplicación. Para obtener más información, vea Incluir una aplicación de .NET Core en un contenedor.
El archivo Dockerfile
Un Dockerfile es un conjunto de instrucciones para compilar una imagen. En el caso de las aplicaciones .NET, el Dockerfile normalmente se encuentra en la raíz del directorio junto a un archivo de solución.
# 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
La aplicación .NET de este tutorial se basa en el SDK de .NET como parte de su funcionalidad. El Dockerfile crea un nuevo conjunto de capas de Docker, independientemente de los anteriores. Se inicia desde cero con la imagen del SDK y agrega la salida de compilación del conjunto anterior de capas. En el caso de las aplicaciones que no requieren el SDK de .NET como parte de su funcionalidad, deben basarse solo en el entorno de ejecución de .NET en su lugar. Esto reduce considerablemente el tamaño de la imagen.
FROM mcr.microsoft.com/dotnet/runtime:7.0
Advertencia
Preste mucha atención a todos los pasos del Dockerfile, ya que difiere del Dockerfile estándar creado a partir de la funcionalidad "agregar compatibilidad con Docker". En concreto, los últimos pasos varían sin especificar un nuevo WORKDIR
que cambiaría la ruta de acceso a la aplicación ENTRYPOINT
.
Los pasos anteriores del Dockerfile incluyen:
- Establecer la imagen base de
mcr.microsoft.com/dotnet/sdk:7.0
como el aliasbuild-env
. - Copiar el contenido y publicar la aplicación .NET:
- La aplicación se publica mediante el comando
dotnet publish
.
- La aplicación se publica mediante el comando
- Aplicación de etiquetas al contenedor.
- Retransmitir la imagen del SDK de .NET desde
mcr.microsoft.com/dotnet/sdk:7.0
- Copiar la salida de compilación publicada de
build-env
. - Definir el punto de entrada, que delega en
dotnet /DotNet.GitHubAction.dll
.
Sugerencia
MRC en mcr.microsoft.com
significa "Microsoft Container Registry", y es el catálogo de contenedores sindicados de Microsoft del centro de Docker oficial. Para obtener más información, consulte Catálogo de contenedores de sindicatos de Microsoft.
Precaución
Si usa un archivo global.json para anclar la versión del SDK, debe hacer referencia explícita a esa versión en el Dockerfile. Por ejemplo, si ha usado global.json para anclar la versión 5.0.300
del SDK, el Dockerfile debe usar mcr.microsoft.com/dotnet/sdk:5.0.300
. Esto evita interrumpir el Acciones de GitHub cuando se libera una nueva revisión secundaria.
Definición de entradas y salidas de acción
En la sección Explorar la aplicación, ha aprendido sobre la clase ActionInputs
. Este objeto representa las entradas de la acción de GitHub. Para que GitHub reconozca que el repositorio es una acción de GitHub, debe tener un archivo action.yml en la raíz del repositorio.
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 }}
El archivo action.yml anterior define:
name
ydescription
de la acción de GitHubbranding
, que se usa en Marketplace de GitHub para ayudar a identificar de forma más única la accióninputs
, que asigna uno a uno con la claseActionInputs
outputs
, que se escribe enProgram
y se usa como parte de la composición del flujo de trabajo- El nodo
runs
, que indica a GitHub que la aplicación es una aplicacióndocker
y qué argumentos se van a pasar a ella
Para más información, consulte Sintaxis de metadatos de trabajo para Acciones de GitHub.
Variables de entorno predefinidas
Con Acciones de GitHub, obtendrá una gran cantidad de variables de entorno de forma predeterminada. Por ejemplo, la variable GITHUB_REF
siempre contendrá una referencia a la rama o etiqueta que desencadenó la ejecución del flujo de trabajo. GITHUB_REPOSITORY
tiene el nombre del propietario y del repositorio, por ejemplo, dotnet/docs
.
Debe explorar las variables de entorno predefinidas y usarlas en consecuencia.
Composición del flujo de trabajo
Con la aplicación .NET en contenedores y las entradas y salidas de acción definidas, está listo para consumir la acción. Acciones de GitHub no es necesario publicar en Marketplace de GitHub para su uso. Los flujos de trabajo se definen en el directorio .github/workflows de un repositorio como archivos 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
En el caso de Acciones de GitHub en contenedor, debe usar runs-on: ubuntu-latest
. Para más información, consulte Sintaxis de flujo de trabajojobs.<job_id>.runs-on
.
El archivo YAML de flujo de trabajo anterior define tres nodos principales:
name
del flujo de trabajo. Este nombre también es lo que se usa al crear un distintivo de estado de flujo de trabajo.- El nodo
on
define cuándo y cómo se desencadena la acción. - El nodo
jobs
describe los distintos trabajos y pasos dentro de cada trabajo. Los pasos individuales consumen Acciones de GitHub.
Para obtener más información, consulte Creación del primer flujo de trabajo.
Centrarse en el nodo steps
, la composición es más obvia:
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.'
jobs.steps
representa la composición del flujo de trabajo. Los pasos se orquestan de forma que sean secuenciales, comunicativas y composables. Con varias Acciones de GitHub que representan los pasos, cada uno tiene entradas y salidas, se pueden componer flujos de trabajo.
En los pasos anteriores, puede observar:
El repositorio está desprotegido.
Cuando se ejecuta manualmente, se imprime un mensaje en el registro de flujo de trabajo.
Un paso identificado como
dotnet-code-metrics
:uses: dotnet/samples/github-actions/DotNet.GitHubAction@main
es la ubicación de la aplicación .NET en contenedor en este tutorial.env
crea una variable de entorno"GREETING"
, que se imprime en la ejecución de la aplicación.with
especifica cada una de las entradas de acción necesarias.
Un paso condicional, denominado
Create pull request
, se ejecuta cuando el pasodotnet-code-metrics
especifica un parámetro de salida deupdated-metrics
con un valor detrue
.
Importante
GitHub permite la creación de secretos cifrados. Los secretos se pueden usar en la composición del flujo de trabajo mediante la sintaxis ${{ secrets.SECRET_NAME }}
. En el contexto de una acción de GitHub, hay un token de GitHub que se rellena automáticamente de forma predeterminada: ${{ secrets.GITHUB_TOKEN }}
. Para obtener más información, consulte Sintaxis de contexto y expresión para Acciones de GitHub.
Colocación de todo junto
El repositorio dotnet/samples de GitHub es el hogar de muchos proyectos de código fuente de ejemplo de .NET, incluida la aplicación de este tutorial.
El archivo CODE_METRICS.md generado es navegable. Este archivo representa la jerarquía de los proyectos analizados. Cada proyecto tiene una sección de nivel superior y un emoji que representa el estado general de la complejidad ciclomática más alta para los objetos anidados. A medida que navega por el archivo, cada sección expone oportunidades de exploración en profundidad con un resumen de cada área. Markdown tiene secciones contraíbles como una comodidad adicional.
La jerarquía progresa de:
- Archivo de proyecto para ensamblado
- Ensamblado para espacio de nombres
- Espacio de nombres para tipo con nombre
- Cada tipo con nombre tiene una tabla y cada tabla tiene:
- Vínculos a números de línea para campos, métodos y propiedades
- Clasificaciones individuales para métricas de código
En acción
El flujo de trabajo especifica que on
se desencadena una push
en la rama main
, la acción se desencadena para ejecutarse. Cuando se ejecuta, la pestaña Acciones de GitHub notificará la secuencia de registro en directo de su ejecución. Un registro de ejemplo de la ejecución .NET code metrics
:
Mejoras en el rendimiento
Si ha seguido el ejemplo, es posible que haya observado que cada vez que se usa esta acción, realizará una compilación de Docker para esa imagen. Por lo tanto, cada desencadenador se enfrenta con algún tiempo para compilar el contenedor antes de ejecutarlo. Antes de publicar el Acciones de GitHub en Marketplace, debe hacer lo siguiente:
- (automáticamente) Compilación de la imagen de Docker
- Inserción de la imagen de Docker en GitHub Container Registry (o en cualquier otro registro de contenedor público)
- Cambie la acción para no compilar la imagen, pero para usar una imagen de un 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 obtener más información, consulta Trabajo con el registro de contenedor.
Consulte también
- Host genérico de .NET
- Inserción de dependencias en .NET
- Valores de métrica de código
- GitHub de código abierto en .NET con un flujo de trabajo para compilar e insertar automáticamente la imagen de Docker.