Tutorial: Erstellen einer GitHub-Aktion mit .NET
Erfahren Sie, wie Sie eine .NET-App erstellen, die als GitHub-Aktion verwendet werden kann. GitHub Actions Workflowautomatisierung und -komposition aktivieren. Mit GitHub Actions können Sie Quellcode in GitHub erstellen, testen und bereitstellen. Darüber hinaus bietet Actions die Möglichkeit, programmgesteuert mit Problemen zu interagieren, Pull Requests zu erstellen, Code Reviews durchzuführen und Branches zu verwalten. Weitere Informationen zur Continuous Integration mit GitHub Actions finden Sie unter Erstellen und Testen von .NET.
In diesem Tutorial lernen Sie Folgendes:
- Vorbereiten einer .NET-App für GitHub Actions
- Definieren von Aktionseingaben und -ausgaben
- Erstellen eines Workflows
Voraussetzungen
- Ein GitHub-Konto
- Das .NET 6 SDK oder höher
- In .NET integrierte Entwicklungsumgebung (Integrated Development Environment, IDE)
- Sie können die Visual Studio IDE verwenden
Der Zweck der App
Die App in diesem Tutorial führt codemetrische Analysen durch:
Überprüfen und Ermitteln von *.csproj- und *.vbproj-Projektdateien.
Analysieren des ermittelten Quellcodes in diesen Projekten für Folgendes:
- Zyklomatische Komplexität
- Wartbarkeitsindex
- Vererbungstiefe
- Kopplung zwischen Klassen
- Anzahl der Zeilen des Quellcodes
- Ungefähre Zeilen des ausführbaren Codes
Erstellen (oder Aktualisieren) einer CODE_METRICS.md-Datei.
Die App ist nicht für das Erstellen eines Pull Request mit den Änderungen an der Datei CODE_METRICS.md verantwortlich. Diese Änderungen werden im Rahmen der Workflowkomposition verwaltet.
Bei Verweisen auf den Quellcode in diesem Tutorial wurden Teile der App aus Gründen der Kürze nicht angegeben. Der vollständige Code der App ist auf GitHub verfügbar.
App erkunden
Die .NET-Konsolen-App verwendet das CommandLineParser
NuGet-Paket, um Argumente in das ActionInputs
-Objekt zu parsen.
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]);
}
}
}
Die oben abgebildete Aktionseingabeklasse definiert mehrere erforderliche Eingaben, damit die App erfolgreich ausgeführt werden kann. Der Konstruktor schreibt den Wert der "GREETINGS"
-Umgebungsvariable, wenn in der aktuellen Ausführungsumgebung ein Wert verfügbar ist. Die Eigenschaften Name
und Branch
werden geparst und aus dem letzten Segment einer "/"
durch Trennzeichen getrennten Zeichenfolge zugewiesen.
Konzentrieren Sie sich mit der definierten Aktionseingabeklasse auf die Datei 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);
}
Die Datei Program
ist aus Gründen der Kürze vereinfacht. Die vollständige Beispielquelle können Sie unter Program.cs anzeigen. Die vorhandenen Mechanismen zeigen den erforderlichen Codebaustein:
Externe Projekt- oder Paketverweise können verwendet und mit Abhängigkeitsinjektion registriert werden. Get<TService>
ist eine statische lokale Funktion, die die IHost
-Instanz erfordert und zum Auflösen erforderlicher Dienste verwendet wird. Mit dem Singleton CommandLine.Parser.Default
ruft die App eine parser
-Instanz von args
ab. Wenn die Argumente nicht analysiert werden können, wird die App mit einem Exitcode ohne Null beendet. Weitere Informationen finden Sie unter Festlegen von Exitcodes für Aktionen.
Wenn die Argumente erfolgreich geparst wurden, wurde die App ordnungsgemäß mit den erforderlichen Eingaben aufgerufen. In diesem Fall wird die primäre Funktionalität StartAnalysisAsync
aufgerufen.
Zum Schreiben von Ausgabewerten müssen Sie das von GitHub Actions: Festlegen eines Ausgabeparameters erkannte Format verwenden.
Vorbereiten der .NET-App für GitHub Actions
GitHub Actions unterstützt zwei Varianten der App-Entwicklung:
- JavaScript (optional TypeScript)
- Docker-Container (jede App, die unter Docker ausgeführt wird)
In der virtuellen Umgebung, in der GitHub Action gehostet wird, kann .NET installiert sein oder nicht. Informationen dazu, was in der Zielumgebung vorinstalliert ist, finden Sie unter GitHub Actions: Virtuelle Umgebungen. Es ist zwar möglich, .NET CLI-Befehle aus den GitHub Actions-Workflows auszuführen, für eine besser funktionierende .NET-basierte GitHub-Aktion empfehlen wir Ihnen, die Anwendung zu containerisieren. Weitere Informationen finden Sie unter Containerisieren einer .NET-App.
Die Dockerfile-Datei
Eine Dockerfile-Datei ist eine Reihe von Anweisungen zum Erstellen eines Images. Bei .NET-Apps befindet sich die Dockerfile-Datei in der Regel im Stamm des Verzeichnisses neben einer Projektmappendatei.
# 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" ]
Hinweis
Die .NET-App in diesem Tutorial basiert auf dem .NET SDK als Teil der Funktionalität. Die Dockerfile-Datei erstellt einen neuen Satz von Docker-Ebenen, unabhängig von den vorherigen Ebenen. Sie beginnt von Grund auf mit dem SDK-Image und fügt die Buildausgabe aus dem vorherigen Satz von Ebenen hinzu. Für Anwendungen, die das .NET SDK nicht als Teil ihrer Funktionalität benötigen, sollten sie stattdessen nur auf die .NET Runtime zurückgreifen. Dadurch wird die Größe des Bilds erheblich reduziert.
FROM mcr.microsoft.com/dotnet/runtime:7.0
Warnung
Achten Sie auf jeden Schritt innerhalb der Dockerfile-Datei, da sie sich von der Dockerfile-Standarddatei unterscheidet, die mit der Funktion "Docker-Unterstützung hinzufügen" erstellt wurde. Insbesondere unterscheiden sich die letzten Schritte, indem sie keinen neuen WORKDIR
angeben, der den Pfad zur ENTRYPOINT
der App ändern würde.
Die obigen Dockerfile-Schritte umfassen Folgendes:
- Festlegen des Basisimages von
mcr.microsoft.com/dotnet/sdk:7.0
als Aliasbuild-env
. - Kopieren des Inhalts und Veröffentlichen der .NET-App:
- Die App wird mit dem Befehl
dotnet publish
veröffentlicht.
- Die App wird mit dem Befehl
- Anwenden von Bezeichnungen auf den Container.
- Neuschichten des .NET SDK-Image aus
mcr.microsoft.com/dotnet/sdk:7.0
- Kopieren der veröffentlichten Buildausgabe aus
build-env
. - Definieren des Einstiegspunkts, der an
dotnet /DotNet.GitHubAction.dll
delegiert wird.
Tipp
MCR in mcr.microsoft.com
steht für „Microsoft Container Registry“ und ist der syndizierte Containerkatalog von Microsoft aus dem offiziellen Docker-Hub. Weitere Informationen finden Sie unter Containerkatalog für Microsoft-Syndikate.
Achtung
Wenn Sie eine global.json-Datei verwenden, um die SDK-Version anzuheften, sollten Sie explizit auf diese Version in Ihrer Dockerfile-Datei verweisen. Wenn Sie beispielsweise global.json zum Anheften der SDK-Version 5.0.300
verwendet haben, sollte Ihre Dockerfile-Datei mcr.microsoft.com/dotnet/sdk:5.0.300
verwenden. Dadurch wird verhindert, dass GitHub Actions bei der Veröffentlichung einer neuen Nebenrevision abgebrochen wird.
Definieren von Aktionseingaben und -ausgaben
Im Abschnitt Erkunden der App haben Sie mehr über die ActionInputs
-Klasse erfahren. Dieses Objekt stellt die Eingaben für die GitHub-Aktion dar. Damit GitHub erkennt, dass es sich bei dem Repository um eine GitHub-Aktion handelt, müssen Sie eine action.yml-Datei im Stammverzeichnis des Repositorys haben.
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 }}
Die oben abgebildete action.yml-Datei definiert Folgendes:
name
unddescription
der GitHub-Aktion- das
branding
, das im GitHub Marketplace verwendet wird, um Ihre Aktion eindeutig zu identifizieren - die
inputs
, die eins zu eins derActionInputs
-Klasse zuordnet werden - die
outputs
, die in dasProgram
geschrieben und als Teil der Workflowkomposition verwendet werden - der
runs
-Knoten, der GitHub mitteilt, dass es sich bei der App um einedocker
-Anwendung handelt und welche Argumente an sie übergeben werden sollen
Weitere Informationen finden Sie unter Metadatensyntax für GitHub Actions.
Vordefinierte Umgebungsvariablen
Mit GitHub Actions erhalten Sie standardmäßig viele Umgebungsvariablen. Beispielsweise enthält die Variable GITHUB_REF
immer einen Verweis auf den Branch oder das Tag, das die Workflowausführung ausgelöst hat. GITHUB_REPOSITORY
bezeichnet den Besitzer und den Repositorynamen, z. B dotnet/docs
.
Wir empfehlen Ihnen, die vordefinierten Umgebungsvariablen zu ergründen und entsprechend zu verwenden.
Workflowkomposition
Wenn die .NET-App containerisiert ist und die Aktionsein- und -ausgaben definiert sind, können Sie die Aktion nutzen. GitHub-Aktionen müssen nicht im GitHub Marketplace veröffentlicht werden, um verwendet zu werden. Workflows werden im Verzeichnis .github/workflows eines Repositorys als YAML-Dateien definiert.
# 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.'
Wichtig
Für containerisierte GitHub-Aktionen müssen Sie runs-on: ubuntu-latest
verwenden. Weitere Informationen finden Sie unter Workflowsyntax jobs.<job_id>.runs-on
.
Die oben abgebildete WORKFLOW-YAML-Datei definiert drei primäre Knoten:
- Der
name
des Workflows. Dieser Name wird auch beim Erstellen eines Workflowstatus-Badges verwendet. - Der
on
-Knoten definiert, wann und wie die Aktion ausgelöst wird. - Der
jobs
-Knoten beschreibt die verschiedenen Einzelvorgänge und Schritte innerhalb der einzelnen Aufträge. Jeder Schritt nutzt eine GitHub-Aktion.
Weitere Informationen finden Sie unter Erstellen Ihres ersten Workflows.
Beim steps
-Knoten ist die Komposition offensichtlicher:
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
stellt die Workflowkomposition dar. Die Schritte sind so orchestriert, dass sie sequenziell, kommunikativ und komponierbar sind. Mit verschiedenen GitHub-Aktionen, die Schritte darstellen, die jeweils Ein- und Ausgaben enthalten, können Workflows zusammengestellt werden.
In den oben dargestellten Schritten können Sie beobachten:
Das Repository ist ausgecheckt.
Bei manueller Ausführung wird eine Nachricht in das Workflowprotokoll gedruckt.
Ein als
dotnet-code-metrics
identifizierter Schritt:uses: dotnet/samples/github-actions/DotNet.GitHubAction@main
ist der Speicherort der containerisierten .NET-App in diesem Tutorial.env
erstellt eine Umgebungsvariable"GREETING"
, die bei der Ausführung der App ausgegeben wird.with
gibt jede der erforderlichen Aktionseingaben an.
Ein bedingter Schritt mit dem Namen
Create pull request
wird ausgeführt, wenn der Schrittdotnet-code-metrics
einen Ausgabeparameter vonupdated-metrics
mit dem Werttrue
angibt.
Wichtig
GitHub ermöglicht die Erstellung verschlüsselter Geheimnisse. Geheimnisse können in der Workflowkomposition mithilfe der Syntax ${{ secrets.SECRET_NAME }}
verwendet werden. Im Kontext einer GitHub-Aktion gibt es ein GitHub-Token, das standardmäßig automatisch aufgefüllt wird: ${{ secrets.GITHUB_TOKEN }}
. Weitere Informationen finden Sie unter Syntax für Kontexte und Ausdrücke für GitHub Actions.
Korrektes Zusammenfügen
Im GitHub-Repository dotnet/samples befinden sich viele .NET-Beispielquellcodeprojekte, einschließlich der App in diesem Tutorial.
Die generierte CODE_METRICS.md-Datei ist navigierbar. Diese Datei stellt die Hierarchie der analysierten Projekte dar. Jedes Projekt verfügt über einen Abschnitt auf oberster Ebene und ein Emoji, das den Gesamtstatus der höchsten zyklomatischen Komplexität für geschachtelte Objekte darstellt. Beim Navigieren durch die Datei bietet jeder Abschnitt Drill-Down-Möglichkeiten mit einer Zusammenfassung der einzelnen Bereiche. Das Markdown verfügt über reduzierbare Abschnitte.
Die Hierarchie sieht wie folgt aus:
- Projektdatei zu Assembly
- Assembly zum Namespace
- Namespace für benannten Typ
- Jeder benannte Typ verfügt über eine Tabelle, und jede Tabelle beinhaltet Folgendes:
- Links zu Zeilennummern für Felder, Methoden und Eigenschaften
- Individuelle Bewertungen für Codemetriken
In Aktion
Der Workflow sieht vor, dass die Aktion bei on
in Bezug auf einen push
auf den main
-Branch ausgelöst wird. Wenn sie ausgeführt wird, meldet die Registerkarte Aktionen in GitHub den Liveprotokollstream seiner Ausführung. Hier sehen Sie ein Beispielprotokoll aus der .NET code metrics
-Ausführung:
Leistungsverbesserungen
Wenn Sie das Beispiel befolgt haben, haben Sie möglicherweise bemerkt, dass bei jeder Verwendung dieser Aktion ein Docker-Build für dieses Image ausgeführt wird. Jeder Trigger benötigt also eine gewisse Zeit, um den Container zu erstellen, bevor er ausgeführt wird. Bevor Sie Ihre GitHub-Aktion auf dem Marketplace freigeben, sollten Sie Folgendes ausführen:
- (automatisches) Erstellen des Docker-Images
- Pushen des Docker-Images an die GitHub Container Registry (oder eine andere öffentliche Containerregistrierung)
- Ändern der Aktion so, dass das Image nicht erstellt wird, sondern ein Image aus einer öffentlichen Registrierung verwendet wird.
# 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!!
Weitere Informationen finden Sie unter GitHub Docs: Arbeiten mit der Containerregistrierung.
Siehe auch
- Generischer .NET-Host
- Abhängigkeitsinjektion in .NET
- Codemetrikwerte
- Open-Source-GitHub Action-Build in .NET mit einem Workflow zum automatischen Erstellen und Pushen des Docker-Images.