Tutoriel : Créer une action GitHub avec .NET
Découvrez comment créer une application .NET qui peut être utilisée comme action GitHub. GitHub Actions permet l’automatisation et la composition des flux de travail. Avec GitHub Actions, vous pouvez générer, tester et déployer un code source directement à partir de GitHub. De plus, les actions exposent la possibilité d’interagir par programmation avec les problèmes, de créer des demandes de tirage, d’effectuer des révisions de code et de gérer des branches. Pour plus d’informations sur l’intégration continue avec GitHub Actions, consultez Création et test de code .NET.
Dans ce tutoriel, vous allez apprendre à :
- Préparer une application .NET pour GitHub Actions
- Définir des entrées et des sorties d’action
- Composer un flux de travail
Prérequis
- Un compte GitHub
- Kit de développement logiciel (SDK) .NET 6 ou ultérieur
- Un IDE (environnement de développement intégré) .NET
- N’hésitez pas à utiliser l’IDE Visual Studio
Intention de l’application
L’application de ce didacticiel effectue une analyse de la métrique de code en procédant en :
Analysant et découvrant des fichiers projet *.csproj et *.vbproj .
Analysant du code source découvert dans ces projets pour :
- Complexité cyclomatique
- L’Indice de maintenabilité
- Profondeur de l’héritage
- Couplage de classes
- Le nombre de lignes de code source
- Les lignes approximatives de code exécutable
Créant (ou mettant à jour) un fichier CODE_METRICS.md .
L’application n’est pas responsable de la création d’une requête de tirage avec les modifications apportées au fichier CODE_METRICS.md. Ces modifications sont gérées dans le cadre de la composition du flux de travail.
Des parties de l’application ont été omises dans les références au code source de ce didacticiel par souci de concision. Le code de l’application terminé est disponible sur GitHub.
Explorer l’application
L’application console .NET utilise le package CommandLineParser
NuGet pour analyser les arguments dans l’objet 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 classe d’entrées d’action précédente définit plusieurs entrées obligatoires pour que l’application s’exécute correctement. Le constructeur écrit la valeur de la variable d’environnement "GREETINGS"
, si elle est disponible dans l’environnement d’exécution actuel. Les propriétés Name
et Branch
sont analysées et affectées à partir du dernier segment d’une chaîne délimitée "/"
.
Avec la classe d’entrées d’action définie, concentrez-vous sur le fichier 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);
}
Le fichier Program
est simplifié par souci de concision. Pour explorer l’exemple de source complet, consultez Program.cs. Les mécanismes en place illustrent le code réutilisable requis pour utiliser :
Les références du projet ou du package externes peuvent être utilisées et inscrites avec l’injection de dépendances. Get<TService>
est une fonction locale statique, qui nécessite l’instance IHost
et est utilisée pour résoudre les services requis. Avec le singleton CommandLine.Parser.Default
, l’application obtient une instance parser
du args
. Lorsqu’il est impossible d’analyser les arguments, l’application se ferme avec un code de sortie différent de zéro. Pour plus d’informations, consultez Définition de codes de sortie pour les actions.
Lorsque les arguments sont correctement analysés, l’application a été appelée correctement avec les entrées requises. Dans ce cas, un appel à la fonctionnalité principale StartAnalysisAsync
est effectué.
Pour écrire des valeurs de sortie, vous devez suivre le format reconnu par GitHub Actions : Définition d’un paramètre de sortie.
Préparer l’application .NET pour GitHub Actions
GitHub Actions prend en charge deux variantes du développement d’applications, soit
- JavaScript (éventuellement TypeScript)
- Conteneur Docker (toute application qui s’exécute sur Docker)
L’environnement virtuel où l’action GitHub est hébergée peut avoir installé .NET ou non. Pour plus d’informations sur ce qui est préinstallé dans l’environnement cible, consultez GitHub Actions Virtual Environments. Bien qu’il soit possible d’exécuter des commandes CLI .NET à partir des flux de travail GitHub Actions, pour un fonctionnement plus complet d’un action GitHub basée sur .NET, nous vous recommandons de conteneuriser l’application. Pour plus d’informations, consultez Conteneuriser une application .NET.
Le fichier Dockerfile
Un Dockerfile est un ensemble d’instructions pour générer une image. Pour les applications .NET, le fichier Dockerfile se trouve généralement à la racine du répertoire à côté d’un fichier solution.
# 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" ]
Notes
L’application .NET de ce didacticiel s’appuie sur le Kit de développement logiciel (SDK) .NET dans le cadre de ses fonctionnalités. Le fichier Dockerfile crée un nouvel ensemble de couches Docker, indépendamment des précédentes. Il démarre à partir de zéro avec l’image du kit de développement logiciel (SDK) et ajoute la sortie de build de l’ensemble de couches précédent. Les applications qui ne nécessitent pas le kit de développement logiciel (SDK) .NET dans le cadre de leurs fonctionnalités, doivent s’appuyer uniquement sur le runtime .NET à la place. Cela réduit considérablement la taille de l’image.
FROM mcr.microsoft.com/dotnet/runtime:7.0
Avertissement
Faites attention à chaque étape du Dockerfile, car il diffère du Dockerfile standard créé à partir de la fonctionnalité « Ajouter la prise en charge de Docker ». Les dernières étapes notamment varient en ne spécifiant pas de nouveau WORKDIR
qui modifierait le chemin d’accès à l’application ENTRYPOINT
.
Les étapes dockerfile précédentes sont les suivantes :
- La définition de l’image de base à partir de
mcr.microsoft.com/dotnet/sdk:7.0
comme aliasbuild-env
. - La copie des contenus et la publication de l’application .NET :
- L’application est publiée à l’aide de la commande
dotnet publish
.
- L’application est publiée à l’aide de la commande
- Application d’étiquettes au conteneur.
- Relai de l’image du kit de développement logiciel (SDK) .NET à partir de
mcr.microsoft.com/dotnet/sdk:7.0
- Copie de la sortie du build publiée à partir de
build-env
. - Définition du point d’entrée, qui délègue à
dotnet /DotNet.GitHubAction.dll
.
Conseil
McR dans mcr.microsoft.com
signifie « Microsoft Container Registry » et est le catalogue de conteneurs syndiqué de Microsoft à partir du hub Docker officiel. Pour plus d’informations, consultez Microsoft syndicates container catalog.
Attention
Si vous utilisez un fichier global.json pour épingler la version du kit de développement logiciel (SDK), vous devez explicitement faire référence à cette version dans votre fichier Dockerfile. Par exemple, si vous avez utilisé un fichier global.json pour épingler la version 5.0.300
du kit de développement logiciel (SDK), votre fichier Dockerfile doit utiliser mcr.microsoft.com/dotnet/sdk:5.0.300
. Cela empêche la rupture de GitHub Actions lors de la publication d’une nouvelle révision mineure.
Définir des entrées et des sorties d’action
Dans la section Explorer l’application, vous avez découvert la classe ActionInputs
. Cet objet représente les entrées pour l’action GitHub. Pour que GitHub reconnaisse que le référentiel est une action GitHub, vous devez disposer d’un fichier action.yml à la racine du référentiel.
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 }}
Le fichier action.yml précédent définit :
- La
name
et ledescription
de l’action GitHub - Le
branding
qui est utilisé dans la Place de marché GitHub pour vous aider à identifier de manière plus unique votre action - Les
inputs
qui mappent un-à-un avec la classeActionInputs
- Les
outputs
qui sont écrits dans leProgram
et utilisés dans la composition de flux de travail - Le nœud
runs
, qui indique à GitHub que l’application est une applicationdocker
et quels arguments lui passer
Pour plus d’informations, consultez Metadata syntax for GitHub Actions.
Variables d’environnement prédéfinies
Avec GitHub Actions, vous obtenez un grand nombre de variables d’environnement par défaut. Par exemple, la variable GITHUB_REF
contient toujours une référence à la branche ou à la balise qui a déclenché l’exécution du flux de travail. GITHUB_REPOSITORY
a le nom du propriétaire et du dépôt, par exemple, dotnet/docs
.
Vous devez analyser les variables d’environnement prédéfinies et les utiliser en conséquence.
Composition du flux de travail
Une fois l’application .NET conteneurisée et les entrées et les sorties d’action définies, vous pouvez utiliser l’action. Les GitHub Actions ne doivent pas être publiées sur la Place de marché GitHub pour être utilisé. Les flux de travail sont définis dans le répertoire .github/workflows d’un référentiel en tant que fichiers 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.'
Important
Pour les GitHub Actions en conteneur, vous devez utiliser runs-on: ubuntu-latest
. Pour plus d’informations, consultez la syntaxe de flux de travailjobs.<job_id>.runs-on
.
Le fichier YAML de flux de travail précédent définit trois nœuds principaux :
name
du workflow. Ce nom est également utilisé lors de la création d’un badge d’état du flux de travail.- Le nœud
on
définit quand et comment l’action est déclenchée. - Le nœud
jobs
décrit les différents travaux et étapes de chaque travail. Les étapes individuelles consomment GitHub Actions.
Pour plus d’informations, consultez Création de votre premier workflow.
En se concentrant sur le nœud steps
, la composition est plus évidente :
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
représente la composition du flux de travail. Les étapes sont organisées de telle sorte qu’elles soient séquentielles, communicatives et composables. Avec différentes GitHub Actions représentant des étapes, chacune ayant des entrées et des sorties, des flux de travail peuvent être composés.
Dans les étapes précédentes, vous pouvez observer :
Le référentiel est extrait.
Un message est imprimé dans le journal de flux de travail lorsqu’il est exécuté manuellement.
Étape identifiée comme
dotnet-code-metrics
:uses: dotnet/samples/github-actions/DotNet.GitHubAction@main
est l’emplacement de l’application .NET conteneurisée dans ce didacticiel.env
crée une variable d’environnement"GREETING"
, qui est imprimée lors de l’exécution de l’application.with
spécifie chacune des entrées d’action requises.
Une étape conditionnelle nommée
Create pull request
s’exécute lorsque l’étapedotnet-code-metrics
spécifie un paramètre de sortie deupdated-metrics
avec la valeurtrue
.
Important
GitHub permet de créer des secrets chiffrés. Les secrets peuvent être utilisés dans la composition du flux de travail, à l’aide de la syntaxe ${{ secrets.SECRET_NAME }}
. Dans le contexte d’une action GitHub, il existe un jeton GitHub qui est rempli par défaut automatiquement : ${{ secrets.GITHUB_TOKEN }}
. Pour plus d’informations, consultez Syntaxe de contexte et d’expression pour GitHub Actions.
Assemblage
Le référentiel GitHub dotnet/samples contient de nombreux exemples de projets de code source .NET, y compris l’application dans ce tutoriel.
Le fichier CODE_METRICS.md généré est explorable. Ce fichier représente la hiérarchie des projets analysés. Chaque projet a une section de niveau supérieur et un emoji qui représente l’état général de la complexité cyclomatique la plus élevée pour les objets imbriqués. Lorsque vous parcourez le fichier, chaque section expose des opportunités d’exploration hiérarchique avec un résumé de chaque zone. Le markdown a des sections réductibles pour plus de commodité.
La hiérarchie progresse à partir du :
- Fichier projet à assembly
- Assembly à espace de noms
- Espace de noms à type nommé
- Chaque type nommé a une table, et chaque table a :
- Les liens vers des numéros de ligne pour les champs, méthodes et propriétés
- Les évaluations individuelles pour les métriques de code
En action
Le flux de travail spécifie que on
a push
dans la branche main
, l’action est déclenchée pour s’exécuter. Lorsqu’elle s’exécute, l’onglet Actions dans GitHub signale le flux de journal en direct de son exécution. Voici un exemple de journal de l’exécution .NET code metrics
:
Optimisation des performances
Si vous avez suivi l’exemple, vous avez peut-être remarqué que chaque fois que cette action est utilisée, elle effectue un build Docker pour cette image. Par conséquent, chaque déclencheur dispose d’un certain temps pour générer le conteneur avant de l’exécuter. Avant de publier votre GitHub Actions sur la place de marché, vous devez :
- (automatiquement) Générer l’image Docker
- Envoyer l’image Docker vers GitHub Container Registry (ou tout autre registre de conteneurs public)
- Modifier l’action pour ne pas générer l’image, mais pour utiliser une image d’un registre public.
# 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!!
Pour plus d’informations, consultez GitHub Docs : Utilisation du registre de conteneurs.
Voir aussi
- Hôte générique .NET
- Injection de dépendances dans .NET
- Valeurs de la métrique du code
- Build GitHub Action open source dans .NET avec un flux de travail permettant de générer et d’envoyer automatiquement l’image Docker.