Dela via


Självstudie: Skapa en GitHub-åtgärd med .NET

Lär dig hur du skapar en .NET-app som kan användas som en GitHub-åtgärd. GitHub Actions möjliggör automatisering och sammansättning av arbetsflöden. Med GitHub Actions kan du skapa, testa och distribuera källkod från GitHub. Dessutom visar åtgärder möjligheten att programmatiskt interagera med problem, skapa pull-begäranden, utföra kodgranskningar och hantera grenar. Mer information om kontinuerlig integrering med GitHub Actions finns i Skapa och testa .NET.

I den här självstudien lär du dig att:

  • Förbereda en .NET-app för GitHub Actions
  • Definiera åtgärdsindata och utdata
  • Skapa ett arbetsflöde

Förutsättningar

Avsikten med appen

Appen i den här självstudien utför kodmåttanalys genom att:

  • Skanna och identifiera *.csproj - och *.vbproj-projektfiler .

  • Analysera den identifierade källkoden i dessa projekt för:

    • Cyklomatisk komplexitet
    • Underhållsindex
    • Arvsdjup
    • Klasskoppling
    • Antal rader med källkod
    • Ungefärliga rader med körbar kod
  • Skapa (eller uppdatera) en CODE_METRICS.md-fil .

Appen ansvarar inte för att skapa en pull-begäran med ändringarna i filen CODE_METRICS.md . Dessa ändringar hanteras som en del av arbetsflödets sammansättning.

Referenser till källkoden i den här självstudien innehåller delar av appen utelämnade för korthet. Den fullständiga appkoden är tillgänglig på GitHub.

Utforska appen

.NET-konsolappen CommandLineParser använder NuGet-paketet för att parsa argument i ActionInputs objektet.

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]);
        }
    }
}

Föregående åtgärdsindataklass definierar flera nödvändiga indata för att appen ska kunna köras. Konstruktorn skriver "GREETINGS" miljövariabelvärdet om ett är tillgängligt i den aktuella körningsmiljön. Name Egenskaperna och Branch parsas och tilldelas från det sista segmentet i en "/" avgränsad sträng.

Med klassen för definierade åtgärdsindata fokuserar du på filen 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);
}

Filen Program är förenklad för korthet, för att utforska den fullständiga exempelkällan, se Program.cs. Mekaniken på plats visar den pannplåtskod som krävs för att använda:

Externa projekt- eller paketreferenser kan användas och registreras med beroendeinmatning. Get<TService> är en statisk lokal funktion som kräver instansen IHost och används för att lösa nödvändiga tjänster. Med singletonen CommandLine.Parser.Default hämtar appen en parser instans från args. När argumenten inte kan parsas avslutas appen med en slutkod som inte är noll. Mer information finns i Ange slutkoder för åtgärder.

När args parsas anropades appen korrekt med nödvändiga indata. I det här fallet görs ett anrop till den primära funktionen StartAnalysisAsync .

Om du vill skriva utdatavärden måste du följa formatet som identifieras av GitHub Actions: Ange en utdataparameter.

Förbereda .NET-appen för GitHub Actions

GitHub Actions stöder två varianter av apputveckling, antingen

  • JavaScript (valfritt TypeScript)
  • Docker-container (alla appar som körs på Docker)

Den virtuella miljön där GitHub-åtgärden finns kanske eller kanske inte har .NET installerat. Information om vad som är förinstallerat i målmiljön finns i Virtuella GitHub Actions-miljöer. Även om det är möjligt att köra .NET CLI-kommandon från GitHub Actions-arbetsflöden, för en mer fullt fungerande . NET-baserad GitHub Action rekommenderar vi att du containeriserar appen. Mer information finns i Containerisera en .NET-app.

The Dockerfile

En Dockerfile är en uppsättning instruktioner för att skapa en avbildning. För .NET-program finns Dockerfile vanligtvis i roten i katalogen bredvid en lösningsfil.

# 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" ]

Kommentar

.NET-appen i den här självstudien förlitar sig på .NET SDK som en del av dess funktioner. Dockerfile skapar en ny uppsättning Docker-lager, oberoende av de tidigare. Den börjar från grunden med SDK-avbildningen och lägger till byggutdata från den tidigare uppsättningen lager. För program som inte kräver .NET SDK som en del av deras funktioner bör de bara förlita sig på .NET Runtime i stället. Detta minskar bildens storlek avsevärt.

FROM mcr.microsoft.com/dotnet/runtime:7.0

Varning

Var uppmärksam på varje steg i Dockerfile, eftersom det skiljer sig från standardfunktionerna för Dockerfile som skapats från funktionen "lägg till docker-support". I synnerhet varierar de senaste stegen genom att inte ange en ny WORKDIR som skulle ändra sökvägen till appens ENTRYPOINT.

Föregående Dockerfile-steg är:

  • Ange basavbildningen från mcr.microsoft.com/dotnet/sdk:7.0 som alias build-env.
  • Kopiera innehållet och publicera .NET-appen:
  • Tillämpa etiketter på containern.
  • Vidarebefordra .NET SDK-avbildningen från mcr.microsoft.com/dotnet/sdk:7.0
  • Kopiera publicerade build-utdata från build-env.
  • Definiera startpunkten, som delegerar till dotnet /DotNet.GitHubAction.dll.

Dricks

MCR i mcr.microsoft.com står för "Microsoft Container Registry" och är Microsofts syndikerade containerkatalog från den officiella Docker-hubben. Mer information finns i Containerkatalogen för Microsoft-syndikat.

Varning

Om du använder en global.json-fil för att fästa SDK-versionen bör du uttryckligen referera till den versionen i Din Dockerfile. Om du till exempel har använt global.json för att fästa SDK-versionen 5.0.300ska Dockerfile använda mcr.microsoft.com/dotnet/sdk:5.0.300. Detta förhindrar att GitHub Actions bryts när en ny mindre revision släpps.

Definiera åtgärdsindata och utdata

I avsnittet Utforska appen har du lärt dig om ActionInputs klassen. Det här objektet representerar indata för GitHub-åtgärden. För att GitHub ska kunna känna igen att lagringsplatsen är en GitHub-åtgärd måste du ha en action.yml-fil i roten på lagringsplatsen.

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 }}

Föregående action.yml-fil definierar:

  • Och name description för GitHub-åtgärden
  • , brandingsom används på GitHub Marketplace för att hjälpa dig att identifiera din åtgärd på ett mer unikt sätt
  • , inputssom mappar en-till-en med ActionInputs klassen
  • , outputssom skrivs till i och används som en del av arbetsflödessammansättningen Program
  • Noden runs , som talar om för GitHub att appen är ett docker program och vilka argument som ska skickas till den

Mer information finns i Metadatasyntax för GitHub Actions.

Fördefinierade miljövariabler

Med GitHub Actions får du som standard många miljövariabler . Variabeln GITHUB_REF innehåller till exempel alltid en referens till den gren eller tagg som utlöste arbetsflödeskörningen. GITHUB_REPOSITORY har ägaren och lagringsplatsens namn, dotnet/docstill exempel .

Du bör utforska de fördefinierade miljövariablerna och använda dem i enlighet med detta.

Arbetsflödessammansättning

När .NET-appen är containerbaserad och åtgärdsindata och utdata har definierats är du redo att använda åtgärden. GitHub Actions behöver inte publiceras på GitHub Marketplace som ska användas. Arbetsflöden definieras i katalogen .github/workflows på en lagringsplats som YAML-filer.

# 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.'

Viktigt!

För github-åtgärder i containrar måste du använda runs-on: ubuntu-latest. Mer information finns i Arbetsflödessyntax .jobs.<job_id>.runs-on

Yaml-filen för föregående arbetsflöde definierar tre primära noder:

  • Arbetsflödets name . Det här namnet är också vad som används när du skapar ett statusmärke för arbetsflödet.
  • Noden on definierar när och hur åtgärden utlöses.
  • Noden jobs beskriver de olika jobben och stegen i varje jobb. Enskilda steg använder GitHub Actions.

Mer information finns i Skapa ditt första arbetsflöde.

Med fokus på steps noden är kompositionen mer uppenbar:

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 Representerar arbetsflödets sammansättning. Stegen samordnas så att de är sekventiella, kommunikativa och komposterbara. Med olika GitHub Actions som representerar steg, där var och en har indata och utdata, kan arbetsflöden bestå.

I föregående steg kan du observera:

  1. Lagringsplatsen är utcheckad.

  2. Ett meddelande skrivs ut till arbetsflödesloggen när det körs manuellt.

  3. Ett steg som identifieras som dotnet-code-metrics:

    • uses: dotnet/samples/github-actions/DotNet.GitHubAction@main är platsen för den containerbaserade .NET-appen i den här självstudien.
    • env skapar en miljövariabel "GREETING", som skrivs ut i körningen av appen.
    • with anger var och en av de nödvändiga åtgärdsindata.
  4. Ett villkorsstyrt steg med namnet Create pull request körs när dotnet-code-metrics steget anger en utdataparameter för updated-metrics med värdet true.

Viktigt!

Med GitHub kan du skapa krypterade hemligheter. Hemligheter kan användas i arbetsflödets sammansättning med hjälp av syntaxen ${{ secrets.SECRET_NAME }} . I kontexten för en GitHub-åtgärd finns det en GitHub-token som fylls i automatiskt som standard: ${{ secrets.GITHUB_TOKEN }}. Mer information finns i Kontext- och uttryckssyntax för GitHub Actions.

Färdigställa allt

GitHub-lagringsplatsen dotnet/samples är hem för många .NET-exempelkällkodprojekt, inklusive appen i den här självstudien.

Den genererade filen CODE_METRICS.md är navigeringsbar. Den här filen representerar hierarkin för de projekt som den analyserade. Varje projekt har ett avsnitt på den översta nivån och en emoji som representerar den övergripande statusen för den högsta cyklomatiska komplexiteten för kapslade objekt. När du navigerar i filen exponerar varje avsnitt möjligheter för ökad detaljnivå med en sammanfattning av varje område. Markdown har komprimerbara avsnitt som en extra bekvämlighet.

Hierarkin fortsätter från:

  • Projektfil till sammansättning
  • Sammansättning till namnområde
  • Namnområde till namngiven typ
  • Varje namngiven typ har en tabell och varje tabell har:
    • Länkar till radnummer för fält, metoder och egenskaper
    • Individuella klassificeringar för kodmått

Exempel

Arbetsflödet anger att on en push till grenen main , åtgärden utlöses för att köras. När den körs rapporterar fliken Åtgärder i GitHub liveloggströmmen för dess körning. Här är en exempellogg från körningen .NET code metrics :

.NET code metrics - GitHub Actions log

Prestandaförbättringar

Om du följde exemplet kanske du har märkt att varje gång den här åtgärden används, kommer den att göra en docker-version för avbildningen. Därför har varje utlösare en viss tid på sig att skapa containern innan den körs. Innan du släpper dina GitHub Actions på marknadsplatsen bör du:

  1. (automatiskt) Skapa Docker-avbildningen
  2. Push-överför docker-avbildningen till GitHub Container Registry (eller något annat offentligt containerregister)
  3. Ändra åtgärden till att inte skapa avbildningen, utan att använda en avbildning från ett offentligt register.
# 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!!

Mer information finns i GitHub Docs: Arbeta med containerregistret.

Se även

Nästa steg