Delen via


Zelfstudie: Een GitHub-actie maken met .NET

Meer informatie over het maken van een .NET-app die kan worden gebruikt als een GitHub Action. GitHub Actions maken werkstroomautomatisering en -samenstelling mogelijk. Met GitHub Actions kunt u broncode bouwen, testen en implementeren vanuit GitHub. Daarnaast bieden acties de mogelijkheid om programmatisch te communiceren met problemen, pull-aanvragen te maken, codebeoordelingen uit te voeren en vertakkingen te beheren. Zie .NET bouwen en testen voor meer informatie over continue integratie met GitHub Actions.

In deze zelfstudie leert u het volgende:

  • Een .NET-app voorbereiden voor GitHub Actions
  • Actie-invoer en -uitvoer definiëren
  • Een werkstroom opstellen

Vereisten

De intentie van de app

De app in deze zelfstudie voert metrische codeanalyse uit op:

  • Scannen en detecteren van projectbestanden *.csproj en *.vbproj .

  • De gedetecteerde broncode in deze projecten analyseren voor:

    • Cyclomatische complexiteit
    • Index voor onderhoudbaarheid
    • Diepte van overname
    • Klassekoppeling
    • Aantal regels broncode
    • Geschatte regels uitvoerbare code
  • Een CODE_METRICS.md-bestand maken (of bijwerken).

De app is niet verantwoordelijk voor het maken van een pull-aanvraag met de wijzigingen in het bestand CODE_METRICS.md . Deze wijzigingen worden beheerd als onderdeel van de werkstroomsamenstelling.

Verwijzingen naar de broncode in deze zelfstudie bevatten gedeelten van de app die ter beknoptheid zijn weggelaten. De volledige app-code is beschikbaar op GitHub.

De app verkennen

De .NET-console-app maakt gebruik van het CommandLineParser NuGet-pakket om argumenten in het ActionInputs object te parseren.

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

De voorgaande actie-invoerklasse definieert verschillende vereiste invoer voor de app om te worden uitgevoerd. De constructor schrijft de waarde van de "GREETINGS" omgevingsvariabele als deze beschikbaar is in de huidige uitvoeringsomgeving. De Name en Branch eigenschappen worden geparseerd en toegewezen vanuit het laatste segment van een "/" gescheiden tekenreeks.

Met de gedefinieerde actie-invoerklasse richt u zich op het bestand 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);
}

Het Program bestand is vereenvoudigd voor beknoptheid, om de volledige voorbeeldbron te verkennen, zie Program.cs. De monteurs laten de standaardcode zien die vereist is voor gebruik:

Externe project- of pakketverwijzingen kunnen worden gebruikt en geregistreerd bij afhankelijkheidsinjectie. Het Get<TService> is een statische lokale functie, waarvoor het IHost exemplaar is vereist en wordt gebruikt om vereiste services op te lossen. Met de CommandLine.Parser.Default singleton haalt de app een parser exemplaar op van de args. Wanneer de argumenten niet kunnen worden geparseerd, wordt de app afgesloten met een afsluitcode die niet nul is. Zie Afsluitcodes instellen voor acties voor meer informatie.

Wanneer de argumenten zijn geparseerd, werd de app correct aangeroepen met de vereiste invoer. In dit geval wordt een aanroep naar de primaire functionaliteit StartAnalysisAsync uitgevoerd.

Als u uitvoerwaarden wilt schrijven, moet u de indeling volgen die wordt herkend door GitHub Actions: een uitvoerparameter instellen.

De .NET-app voorbereiden voor GitHub Actions

GitHub Actions ondersteunen twee variaties van app-ontwikkeling, ofwel

  • JavaScript (optioneel TypeScript)
  • Docker-container (elke app die wordt uitgevoerd in Docker)

De virtuele omgeving waarop de GitHub Action wordt gehost, heeft .NET al dan niet geïnstalleerd. Zie GitHub Actions Virtual Environments voor informatie over wat vooraf is geïnstalleerd in de doelomgeving. Hoewel het mogelijk is om .NET CLI-opdrachten uit te voeren vanuit de GitHub Actions-werkstromen, voor een volledigere werking. GitHub Action op basis van NET raden we u aan om de app in een container te plaatsen. Zie Een .NET-app containeriseren voor meer informatie.

Het Dockerfile

Een Dockerfile is een set instructies voor het bouwen van een installatiekopieën. Voor .NET-toepassingen bevindt het Dockerfile zich meestal in de hoofdmap van de map naast een oplossingsbestand.

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

Notitie

De .NET-app in deze zelfstudie is afhankelijk van de .NET SDK als onderdeel van de functionaliteit. Het Dockerfile maakt een nieuwe set Docker-lagen, onafhankelijk van de vorige lagen. Het begint helemaal opnieuw met de SDK-installatiekopieën en voegt de build-uitvoer van de vorige set lagen toe. Voor toepassingen die de .NET SDK niet nodig hebben als onderdeel van hun functionaliteit, moeten ze alleen afhankelijk zijn van de .NET Runtime. Dit vermindert de grootte van de afbeelding aanzienlijk.

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

Waarschuwing

Let goed op elke stap in het Dockerfile, omdat deze verschilt van de standaard Dockerfile die is gemaakt met de functionaliteit Docker-ondersteuning toevoegen. In het bijzonder variëren de laatste stappen door geen nieuwe WORKDIR op te geven die het pad naar de app ENTRYPOINTzou wijzigen.

De voorgaande Dockerfile-stappen omvatten:

  • De basisinstallatiekopieën mcr.microsoft.com/dotnet/sdk:7.0 instellen als de alias build-env.
  • De inhoud kopiëren en de .NET-app publiceren:
    • De app wordt gepubliceerd met behulp van de dotnet publish opdracht.
  • Labels toepassen op de container.
  • De .NET SDK-installatiekopieën van mcr.microsoft.com/dotnet/sdk:7.0
  • De gepubliceerde build-uitvoer van de build-env.
  • Het beginpunt definiëren, waarnaar wordt dotnet /DotNet.GitHubAction.dllgedelegeerd.

Tip

De MCR in mcr.microsoft.com staat voor 'Microsoft Container Registry' en is de gesyndiceerde containercatalogus van Microsoft van de officiële Docker-hub. Zie de containercatalogus van Microsoft syndicates voor meer informatie.

Let op

Als u een global.json-bestand gebruikt om de SDK-versie vast te maken, moet u expliciet naar die versie verwijzen in uw Dockerfile. Als u bijvoorbeeld global.json hebt gebruikt om sdk-versie 5.0.300vast te maken, moet uw Dockerfile worden gebruiktmcr.microsoft.com/dotnet/sdk:5.0.300. Dit voorkomt dat de GitHub Actions worden onderbroken wanneer er een nieuwe kleine revisie wordt uitgebracht.

Actie-invoer en -uitvoer definiëren

In de sectie De app verkennen hebt u meer geleerd over de ActionInputs klas. Dit object vertegenwoordigt de invoer voor de GitHub-actie. GitHub kan alleen herkennen dat de opslagplaats een GitHub Action is. U moet een action.yml-bestand hebben in de hoofdmap van de opslagplaats.

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

Het voorgaande bestand action.yml definieert:

  • De name en description van de GitHub Action
  • De branding, die wordt gebruikt in de GitHub Marketplace om uw actie unieker te identificeren
  • De inputs, waarmee een-op-een met de ActionInputs klasse wordt toegewezen
  • De outputs, waarnaar wordt geschreven in de Program en wordt gebruikt als onderdeel van werkstroomsamenstelling
  • Het runs knooppunt, dat GitHub vertelt dat de app een docker toepassing is en welke argumenten aan de app moeten worden doorgegeven

Zie De syntaxis van metagegevens voor GitHub Actions voor meer informatie.

Vooraf gedefinieerde omgevingsvariabelen

Met GitHub Actions krijgt u standaard veel omgevingsvariabelen . De variabele GITHUB_REF bevat bijvoorbeeld altijd een verwijzing naar de vertakking of tag die de werkstroomuitvoering heeft geactiveerd. GITHUB_REPOSITORY heeft de naam van de eigenaar en de opslagplaats, dotnet/docsbijvoorbeeld.

U moet de vooraf gedefinieerde omgevingsvariabelen verkennen en dienovereenkomstig gebruiken.

Werkstroomsamenstelling

Nu de .NET-app is gecontaineriseerd en de actie-invoer en -uitvoer zijn gedefinieerd, kunt u de actie gebruiken. GitHub Actions hoeven niet te worden gepubliceerd in de GitHub Marketplace die moet worden gebruikt. Werkstromen worden gedefinieerd in de map .github/workflows van een opslagplaats als YAML-bestanden.

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

Belangrijk

Voor in containers geplaatste GitHub Actions moet u gebruiken runs-on: ubuntu-latest. Zie Werkstroomsyntaxis jobs.<job_id>.runs-onvoor meer informatie.

In het voorgaande YAML-werkstroombestand worden drie primaire knooppunten gedefinieerd:

  • De name werkstroom. Deze naam wordt ook gebruikt bij het maken van een werkstroomstatusbadge.
  • Het on knooppunt definieert wanneer en hoe de actie wordt geactiveerd.
  • Het jobs knooppunt bevat een overzicht van de verschillende taken en stappen binnen elke taak. Afzonderlijke stappen verbruiken GitHub Actions.

Zie Uw eerste werkstroom maken voor meer informatie.

Focus op het steps knooppunt, de samenstelling is duidelijker:

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

De jobs.steps vertegenwoordigt de werkstroomsamenstelling. Stappen worden zodanig ingedeeld dat ze opeenvolgend, communicatief en samenstelbaar zijn. Met verschillende GitHub Actions die stappen vertegenwoordigen, elk met invoer en uitvoer, kunnen werkstromen worden samengesteld.

In de voorgaande stappen kunt u het volgende bekijken:

  1. De opslagplaats is uitgecheckt.

  2. Er wordt een bericht afgedrukt naar het werkstroomlogboek wanneer het handmatig wordt uitgevoerd.

  3. Een stap die is geïdentificeerd als dotnet-code-metrics:

    • uses: dotnet/samples/github-actions/DotNet.GitHubAction@main is de locatie van de in containers geplaatste .NET-app in deze zelfstudie.
    • env maakt een omgevingsvariabele "GREETING"die wordt afgedrukt in de uitvoering van de app.
    • with geeft elk van de vereiste actie-invoer.
  4. Een voorwaardelijke stap, benoemd Create pull request , wordt uitgevoerd wanneer de dotnet-code-metrics stap een uitvoerparameter updated-metrics van met een waarde van true.

Belangrijk

Met GitHub kunt u versleutelde geheimen maken. Geheimen kunnen worden gebruikt in werkstroomsamenstelling, met behulp van de ${{ secrets.SECRET_NAME }} syntaxis. In de context van een GitHub Action is er een GitHub-token dat standaard automatisch wordt ingevuld: ${{ secrets.GITHUB_TOKEN }} Zie context- en expressiesyntaxis voor GitHub Actions voor meer informatie.

Alles samenvoegen

De GitHub-opslagplaats dotnet/samples is de thuisbasis van veel .NET-voorbeeldbroncodeprojecten, waaronder de app in deze zelfstudie.

Het gegenereerde bestand CODE_METRICS.md is bevaarbaar. Dit bestand vertegenwoordigt de hiërarchie van de projecten die worden geanalyseerd. Elk project heeft een sectie op het hoogste niveau en een emoji die de algehele status van de hoogste cyclomatische complexiteit voor geneste objecten vertegenwoordigt. Wanneer u door het bestand navigeert, worden in elke sectie inzoommogelijkheden weergegeven met een samenvatting van elk gebied. Markdown heeft samenvouwbare secties als extra gemak.

De hiërarchie wordt voortgezet van:

  • Projectbestand naar assembly
  • Assembly naar naamruimte
  • Naamruimte naar benoemd-type
  • Elk benoemd type heeft een tabel en elke tabel heeft:
    • Koppelingen naar regelnummers voor velden, methoden en eigenschappen
    • Afzonderlijke classificaties voor metrische codegegevens

In actie

De werkstroom geeft aan dat on een push naar de main vertakking, de actie wordt geactiveerd om uit te voeren. Wanneer het wordt uitgevoerd, rapporteert het tabblad Acties in GitHub de livelogboekstream van de uitvoering. Hier volgt een voorbeeldlogboek van de .NET code metrics uitvoering:

.NET code metrics - GitHub Actions log

Prestatieverbeteringen

Als u het voorbeeld hebt gevolgd, hebt u misschien gemerkt dat elke keer dat deze actie wordt gebruikt, een Docker-build voor die installatiekopieën uitvoert. Dus, elke trigger wordt geconfronteerd met enige tijd om de container te bouwen voordat deze wordt uitgevoerd. Voordat u uw GitHub Actions publiceert in de marketplace, moet u het volgende doen:

  1. (automatisch) De Docker-installatiekopieën bouwen
  2. Push de docker-installatiekopieën naar het GitHub-containerregister (of een ander openbaar containerregister)
  3. Wijzig de actie om de installatiekopieën niet te bouwen, maar om een installatiekopieën uit een openbaar register te gebruiken.
# 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!!

Zie GitHub Docs voor meer informatie: Werken met het containerregister.

Zie ook

Volgende stappen