Esercitazione: Creare un'azione GitHub con .NET
Informazioni su come creare un'app .NET che può essere usata come GitHub Action. GitHub Actions abilita l'automazione e la composizione del flusso di lavoro. Con GitHub Actions è possibile compilare, testare e distribuire il codice sorgente da GitHub. Inoltre, le azioni espongono la possibilità di interagire a livello di codice con i problemi, creare richieste pull, eseguire revisioni del codice e gestire rami. Per altre informazioni sull'integrazione continua con GitHub Actions, vedere Compilazione e test di .NET.
In questa esercitazione apprenderai a:
- Preparare un'app .NET per GitHub Actions
- Definire input e output dell'azione
- Comporre un flusso di lavoro
Prerequisiti
- Un account GitHub
- .NET 6 SDK o versione successiva
- Ambiente di sviluppo integrato .NET (IDE)
- È possibile usare l'IDE di Visual Studio
Finalità dell'app
L'app in questa esercitazione esegue l'analisi delle metriche del codice in base a:
Analisi e individuazione dei file di progetto *.csproj e *.vbproj .
Analisi del codice sorgente individuato all'interno di questi progetti per:
- Complessità ciclomatica
- Indice di gestibilità
- Profondità dell'ereditarietà
- Accoppiamento tra classi
- Numero di righe di codice sorgente
- Righe approssimative di codice eseguibile
Creazione (o aggiornamento) di un file CODE_METRICS.md .
L'app non è responsabile della creazione di una richiesta pull con le modifiche apportate al file CODE_METRICS.md. Queste modifiche vengono gestite come parte della composizione del flusso di lavoro.
I riferimenti al codice sorgente in questa esercitazione hanno parti dell'app omesse per brevità. Il codice completo dell'app è disponibile in GitHub.
Esplora l'app
L'app console .NET usa il CommandLineParser
pacchetto NuGet per analizzare gli argomenti nell'oggetto 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 di input dell'azione precedente definisce diversi input necessari per l'esecuzione corretta dell'app. Il costruttore scriverà il valore della "GREETINGS"
variabile di ambiente, se disponibile nell'ambiente di esecuzione corrente. Le Name
proprietà e Branch
vengono analizzate e assegnate dall'ultimo segmento di una "/"
stringa delimitata.
Con la classe di input dell'azione definita, concentrarsi sul file 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);
}
Il Program
file è semplificato per brevità, per esplorare l'origine di esempio completa, vedere Program.cs. I meccanismi sul posto illustrano il codice boilerplate necessario per l'uso:
È possibile usare riferimenti a progetti o pacchetti esterni e registrarli con l'inserimento delle dipendenze. Get<TService>
è una funzione locale statica, che richiede l'istanza IHost
e viene usata per risolvere i servizi necessari. Con il CommandLine.Parser.Default
singleton, l'app ottiene un'istanza args
parser
da . Quando gli argomenti non possono essere analizzati, l'app viene chiusa con un codice di uscita diverso da zero. Per altre informazioni, vedere Impostazione dei codici di uscita per le azioni.
Quando gli argomenti vengono analizzati correttamente, l'app è stata chiamata correttamente con gli input necessari. In questo caso viene effettuata una chiamata alla funzionalità StartAnalysisAsync
primaria.
Per scrivere valori di output, è necessario seguire il formato riconosciuto da GitHub Actions: Impostazione di un parametro di output.
Preparare l'app .NET per GitHub Actions
GitHub Actions supporta due varianti dello sviluppo di app, entrambe
- JavaScript (facoltativamente TypeScript)
- Contenitore Docker (qualsiasi app eseguita in Docker)
L'ambiente virtuale in cui è ospitato GitHub Action può avere o meno installato .NET. Per informazioni su ciò che è preinstallato nell'ambiente di destinazione, vedere Ambienti virtuali di GitHub Actions. Anche se è possibile eseguire i comandi dell'interfaccia della riga di comando di .NET dai flussi di lavoro di GitHub Actions, per un'interfaccia della riga di comando più funzionante. GitHub Action basato su NET, è consigliabile inserire in contenitori l'app. Per altre informazioni, vedere Containerize a .NET app (Containerize a .NET app).
Dockerfile
Un Dockerfile è un set di istruzioni per compilare un'immagine. Per le applicazioni .NET, il Dockerfile si trova in genere nella radice della directory accanto a un file di soluzione.
# 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
L'app .NET in questa esercitazione si basa su .NET SDK come parte delle funzionalità. Il Dockerfile crea un nuovo set di livelli Docker, indipendentemente da quelli precedenti. Inizia da zero con l'immagine DELL'SDK e aggiunge l'output di compilazione del set di livelli precedente. Per le applicazioni che non richiedono .NET SDK come parte della relativa funzionalità, devono invece basarsi solo sul runtime .NET. Ciò riduce notevolmente le dimensioni dell'immagine.
FROM mcr.microsoft.com/dotnet/runtime:7.0
Avviso
Prestare particolare attenzione a ogni passaggio all'interno del Dockerfile, in quanto differisce dal Dockerfile standard creato dalla funzionalità "add docker support". In particolare, gli ultimi passaggi variano non specificando un nuovo WORKDIR
che cambierebbe il percorso dell'app ENTRYPOINT
.
I passaggi di Dockerfile precedenti includono:
- Impostazione dell'immagine di base da
mcr.microsoft.com/dotnet/sdk:7.0
come aliasbuild-env
. - Copia del contenuto e pubblicazione dell'app .NET:
- L'app viene pubblicata usando il
dotnet publish
comando .
- L'app viene pubblicata usando il
- Applicazione di etichette al contenitore.
- Inoltro dell'immagine .NET SDK da
mcr.microsoft.com/dotnet/sdk:7.0
- Copia dell'output di compilazione pubblicato da
build-env
. - Definizione del punto di ingresso, che delega a
dotnet /DotNet.GitHubAction.dll
.
Suggerimento
McR è mcr.microsoft.com
l'acronimo di "Microsoft Container Registry" ed è il catalogo dei contenitori diffuso di Microsoft dall'hub Docker ufficiale. Per altre informazioni, vedere Catalogo contenitori dei sindacati Microsoft.
Attenzione
Se si usa un file global.json per aggiungere la versione dell'SDK, è necessario fare riferimento in modo esplicito a tale versione nel Dockerfile. Ad esempio, se è stato usato global.json per aggiungere la versione 5.0.300
dell'SDK, il Dockerfile deve usare mcr.microsoft.com/dotnet/sdk:5.0.300
. Ciò impedisce l'interruzione di GitHub Actions quando viene rilasciata una nuova revisione secondaria.
Definire input e output dell'azione
Nella sezione Esplorare l'app si è appresa la ActionInputs
classe . Questo oggetto rappresenta gli input per GitHub Action. Per consentire a GitHub di riconoscere che il repository è un'azione GitHub, è necessario avere un file action.yml nella radice del repository.
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 }}
Il file action.yml precedente definisce:
- e
name
description
di GitHub Action - ,
branding
usato in GitHub Marketplace per identificare in modo più univoco l'azione - ,
inputs
che esegue il mapping uno-a-uno con laActionInputs
classe - Oggetto
outputs
, scritto inProgram
e usato come parte della composizione del flusso di lavoro - Nodo
runs
, che indica a GitHub che l'app è un'applicazionedocker
e quali argomenti passare
Per altre informazioni, vedere Sintassi dei metadati per GitHub Actions.
Variabili di ambiente predefinite
Con GitHub Actions si otterranno molte variabili di ambiente per impostazione predefinita. Ad esempio, la variabile GITHUB_REF
conterrà sempre un riferimento al ramo o al tag che ha attivato l'esecuzione del flusso di lavoro. GITHUB_REPOSITORY
ha il nome del proprietario e del repository, dotnet/docs
ad esempio .
È consigliabile esplorare le variabili di ambiente predefinite e usarle di conseguenza.
Composizione del flusso di lavoro
Con l'app .NET in contenitori e gli input e gli output dell'azione definiti, è possibile usare l'azione. GitHub Actions non deve essere pubblicato in GitHub Marketplace per l'uso. I flussi di lavoro vengono definiti nella directory .github/workflows di un repository come file 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
Per GitHub Actions in contenitori, è necessario usare runs-on: ubuntu-latest
. Per altre informazioni, vedere Sintassi jobs.<job_id>.runs-on
del flusso di lavoro.
Il file YAML del flusso di lavoro precedente definisce tre nodi primari:
name
del flusso di lavoro. Questo nome viene usato anche durante la creazione di una notifica di stato del flusso di lavoro.- Il
on
nodo definisce quando e come viene attivata l'azione. - Il
jobs
nodo descrive i vari processi e passaggi all'interno di ogni processo. I singoli passaggi usano GitHub Actions.
Per altre informazioni, vedere Creazione del primo flusso di lavoro.
Concentrandosi sul steps
nodo, la composizione è più ovvia:
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.'
Rappresenta jobs.steps
la composizione del flusso di lavoro. I passaggi vengono orchestrati in modo che siano sequenziali, comunicativi e componibili. Con varie azioni di GitHub Actions che rappresentano i passaggi, ognuno con input e output, i flussi di lavoro possono essere composti.
Nei passaggi precedenti è possibile osservare:
Il repository è estratto.
Un messaggio viene stampato nel log del flusso di lavoro, quando viene eseguito manualmente.
Un passaggio identificato come
dotnet-code-metrics
:uses: dotnet/samples/github-actions/DotNet.GitHubAction@main
è il percorso dell'app .NET in contenitori in questa esercitazione.env
crea una variabile"GREETING"
di ambiente , che viene stampata nell'esecuzione dell'app.with
specifica ogni input dell'azione richiesta.
Un passaggio condizionale, denominato viene eseguito
Create pull request
quando ildotnet-code-metrics
passaggio specifica un parametro di output diupdated-metrics
con un valore .true
Importante
GitHub consente la creazione di segreti crittografati. I segreti possono essere usati all'interno della composizione del flusso di lavoro, usando la ${{ secrets.SECRET_NAME }}
sintassi . Nel contesto di un'azione GitHub è presente un token GitHub popolato automaticamente per impostazione predefinita: ${{ secrets.GITHUB_TOKEN }}
. Per altre informazioni, vedere Sintassi di contesto ed espressione per GitHub Actions.
Combinare tutti gli elementi
Il repository GitHub dotnet/samples ospita molti progetti di codice sorgente di esempio .NET, inclusa l'app in questa esercitazione.
Il file CODE_METRICS.md generato è esplorabile. Questo file rappresenta la gerarchia dei progetti analizzati. Ogni progetto ha una sezione di primo livello e un'emoji che rappresenta lo stato complessivo della maggiore complessità ciclomatica per gli oggetti annidati. Mentre si esplora il file, ogni sezione espone opportunità di drill-down con un riepilogo di ogni area. Il markdown include sezioni collapible come ulteriore praticità.
La gerarchia procede da:
- File di progetto nell'assembly
- Assembly in spazio dei nomi
- Spazio dei nomi a tipo denominato
- Ogni tipo denominato ha una tabella e ogni tabella include:
- Collegamenti a numeri di riga per campi, metodi e proprietà
- Classificazioni individuali per le metriche del codice
In azione
Il flusso di lavoro specifica che on
un push
oggetto nel main
ramo , l'azione viene attivata per l'esecuzione. Quando viene eseguita, la scheda Actions in GitHub invierà il flusso di log live dell'esecuzione. Di seguito è riportato un esempio di log dell'esecuzione .NET code metrics
:
Miglioramenti delle prestazioni
Se è stato seguito l'esempio, si potrebbe aver notato che ogni volta che viene usata questa azione, verrà eseguita una compilazione Docker per tale immagine. Di conseguenza, ogni trigger deve affrontare un po' di tempo per compilare il contenitore prima di eseguirlo. Prima di rilasciare GitHub Actions nel marketplace, è necessario:
- (automaticamente) Compilare l'immagine Docker
- Eseguire il push dell'immagine Docker nel Registro Contenitori GitHub (o in qualsiasi altro registro contenitori pubblico)
- Modificare l'azione per non compilare l'immagine, ma per usare un'immagine da un registro pubblico.
# 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!!
Per altre informazioni, vedere GitHub Docs: Uso del registro contenitori.
Vedi anche
- Host generico .NET
- Inserimento delle dipendenze in .NET
- Valori delle metriche del codice
- Compilazione open source di GitHub Action in .NET con un flusso di lavoro per la compilazione e il push automatico dell'immagine Docker.