Udostępnij za pośrednictwem


Samouczek: tworzenie akcji usługi GitHub za pomocą platformy .NET

Dowiedz się, jak utworzyć aplikację platformy .NET, która może być używana jako akcja usługi GitHub. Funkcja GitHub Actions umożliwia automatyzację i kompozycję przepływu pracy. Za pomocą funkcji GitHub Actions można tworzyć, testować i wdrażać kod źródłowy z usługi GitHub. Ponadto akcje uwidaczniają możliwość programowej interakcji z problemami, tworzenia żądań ściągnięcia, przeprowadzania przeglądów kodu i zarządzania gałęziami. Aby uzyskać więcej informacji na temat ciągłej integracji z funkcją GitHub Actions, zobacz Kompilowanie i testowanie platformy .NET.

Z tego samouczka dowiesz się, jak wykonywać następujące czynności:

  • Przygotowywanie aplikacji .NET dla funkcji GitHub Actions
  • Definiowanie danych wejściowych i wyjściowych akcji
  • Tworzenie przepływu pracy

Wymagania wstępne

Intencja aplikacji

Aplikacja w tym samouczku wykonuje analizę metryk kodu przez:

  • Skanowanie i odnajdywanie plików projektów *.csproj i *.vbproj .

  • Analizowanie odnalezionego kodu źródłowego w następujących projektach:

    • Złożoność cyklotyczna
    • Indeks możliwości konserwacji
    • Głębokość dziedziczenia
    • Sprzęganie klas
    • Liczba wierszy kodu źródłowego
    • Przybliżone wiersze kodu wykonywalnego
  • Tworzenie (lub aktualizowanie) pliku CODE_METRICS.md .

Aplikacja nie jest odpowiedzialna za utworzenie żądania ściągnięcia ze zmianami w pliku CODE_METRICS.md. Te zmiany są zarządzane w ramach kompozycji przepływu pracy.

Odwołania do kodu źródłowego w tym samouczku zawierają fragmenty aplikacji pominięte w celu zwięzłości. Pełny kod aplikacji jest dostępny w witrynie GitHub.

Eksplorowanie aplikacji

Aplikacja konsolowa platformy .NET używa CommandLineParser pakietu NuGet do analizowania argumentów w ActionInputs obiekcie.

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

Poprzednia klasa danych wejściowych akcji definiuje kilka wymaganych danych wejściowych, aby aplikacja została pomyślnie uruchomiona. Konstruktor zapisze wartość zmiennej środowiskowej "GREETINGS" , jeśli jest ona dostępna w bieżącym środowisku wykonywania. Właściwości Name i Branch są analizowane i przypisywane z ostatniego segmentu "/" rozdzielanego ciągu.

Po zdefiniowaniu klasy akcji skoncentruj się na pliku 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);
}

Plik Program jest uproszczony w celu zwięzłości, aby zapoznać się z pełnym źródłem przykładu, zobacz Program.cs. Mechanika w miejscu demonstruje standardowy kod wymagany do użycia:

Można użyć odwołań do projektu zewnętrznego lub pakietu i zarejestrować się za pomocą wstrzykiwania zależności. Jest Get<TService> to statyczna funkcja lokalna, która wymaga wystąpienia i służy do rozwiązywania IHost wymaganych usług. W przypadku pojedynczego CommandLine.Parser.Default wystąpienia aplikacja pobiera parser wystąpienie z klasy args. Gdy argumenty nie mogą być analizowane, aplikacja kończy działanie z kodem zakończenia bez zera. Aby uzyskać więcej informacji, zobacz Ustawianie kodów zakończenia dla akcji.

Po pomyślnym przeanalizowaniu argumentów aplikacja została wywołana poprawnie z wymaganymi danymi wejściowymi. W takim przypadku jest wykonywane wywołanie funkcji podstawowej StartAnalysisAsync .

Aby zapisać wartości wyjściowe, należy postępować zgodnie z formatem rozpoznawanym przez funkcję GitHub Actions: ustawianie parametru wyjściowego.

Przygotowywanie aplikacji .NET dla funkcji GitHub Actions

Funkcja GitHub Actions obsługuje dwie odmiany tworzenia aplikacji, albo

  • JavaScript (opcjonalnie TypeScript)
  • Kontener platformy Docker (dowolna aplikacja działająca na platformie Docker)

Środowisko wirtualne, w którym jest hostowana akcja Usługi GitHub, może lub nie ma zainstalowanego platformy .NET. Aby uzyskać informacje o tym, co jest wstępnie zainstalowane w środowisku docelowym, zobacz GitHub Actions Virtual Environments (Środowiska wirtualne funkcji GitHub Actions). Chociaż można uruchamiać polecenia interfejsu wiersza polecenia platformy .NET z przepływów pracy funkcji GitHub Actions, aby uzyskać bardziej funkcjonalne polecenie . Akcja usługi GitHub oparta na platformie NET zaleca się konteneryzowanie aplikacji. Aby uzyskać więcej informacji, zobacz Containerize a .NET app (Konteneryzowanie aplikacji platformy .NET).

Plik Dockerfile

Plik Dockerfile to zestaw instrukcji tworzenia obrazu. W przypadku aplikacji .NET plik Dockerfile zwykle znajduje się w katalogu głównym katalogu obok pliku rozwiązania.

# Set the base image as the .NET 7.0 SDK (this includes the runtime)
FROM mcr.microsoft.com/dotnet/sdk:7.0 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
COPY --from=build-env /app/out .
ENTRYPOINT [ "dotnet", "/DotNet.GitHubAction.dll" ]

Uwaga

Aplikacja .NET w tym samouczku opiera się na zestawie SDK platformy .NET w ramach jej funkcji. Plik Dockerfile tworzy nowy zestaw warstw platformy Docker niezależnie od poprzednich. Rozpoczyna się od podstaw przy użyciu obrazu zestawu SDK i dodaje dane wyjściowe kompilacji z poprzedniego zestawu warstw. W przypadku aplikacji, które nie wymagają zestawu SDK platformy .NET w ramach ich funkcji, należy zamiast tego polegać tylko na środowisku uruchomieniowym platformy .NET. Znacznie zmniejsza to rozmiar obrazu.

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

Ostrzeżenie

Zwróć szczególną uwagę na każdy krok w pliku Dockerfile, ponieważ różni się on od standardowego pliku Dockerfile utworzonego na podstawie funkcji "dodaj obsługę platformy Docker". W szczególności kilka ostatnich kroków różni się, nie określając nowego WORKDIR , co spowoduje zmianę ścieżki do aplikacji ENTRYPOINT.

Powyższe kroki pliku Dockerfile obejmują:

  • Ustawianie obrazu podstawowego z mcr.microsoft.com/dotnet/sdk:7.0 jako aliasu build-env.
  • Kopiowanie zawartości i publikowanie aplikacji .NET:
  • Stosowanie etykiet do kontenera.
  • Przekazywanie obrazu zestawu SDK platformy .NET z mcr.microsoft.com/dotnet/sdk:7.0
  • Skopiowanie danych wyjściowych opublikowanej kompilacji z pliku build-env.
  • Definiowanie punktu wejścia, który deleguje do elementu dotnet /DotNet.GitHubAction.dll.

Napiwek

McR to mcr.microsoft.com skrót od "Microsoft Container Registry" (Rejestr kontenerów firmy Microsoft) i jest katalogiem kontenerów syndyka firmy Microsoft z oficjalnego centrum Platformy Docker. Aby uzyskać więcej informacji, zobacz Katalog kontenerów syndykates firmy Microsoft.

Uwaga

Jeśli używasz pliku global.json do przypinania wersji zestawu SDK, należy jawnie odwołać się do tej wersji w pliku Dockerfile. Jeśli na przykład użyto pliku global.json do przypinania wersji 5.0.300zestawu SDK, plik Dockerfile powinien używać polecenia mcr.microsoft.com/dotnet/sdk:5.0.300. Zapobiega to uszkodzeniu funkcji GitHub Actions po wydaniu nowej poprawki pomocniczej.

Definiowanie danych wejściowych i wyjściowych akcji

W sekcji Eksploruj aplikację przedstawiono klasę ActionInputs . Ten obiekt reprezentuje dane wejściowe akcji usługi GitHub. Aby usługa GitHub rozpoznała, że repozytorium jest akcją usługi GitHub, musisz mieć plik action.yml w katalogu głównym repozytorium.

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

Powyższy plik action.yml definiuje:

  • Akcja name i description usługi GitHub
  • Element branding, który jest używany w witrynie GitHub Marketplace , aby ułatwić bardziej unikatowe zidentyfikowanie akcji
  • , inputsktóry mapuje jeden do jednego z klasą ActionInputs
  • Element outputs, który jest zapisywany w obiekcie Program i używany jako część kompozycji przepływu pracy
  • Węzeł runs , który informuje usługę GitHub, że aplikacja jest aplikacją docker i jakie argumenty mają być przekazywane do niej

Aby uzyskać więcej informacji, zobacz Składnia metadanych dla funkcji GitHub Actions.

Wstępnie zdefiniowane zmienne środowiskowe

Dzięki funkcji GitHub Actions domyślnie uzyskasz wiele zmiennych środowiskowych. Na przykład zmienna GITHUB_REF będzie zawsze zawierać odwołanie do gałęzi lub tagu, który wyzwolił przebieg przepływu pracy. GITHUB_REPOSITORY ma nazwę właściciela i repozytorium, na przykład dotnet/docs.

Należy zapoznać się ze wstępnie zdefiniowanymi zmiennymi środowiskowymi i użyć ich odpowiednio.

Kompozycja przepływu pracy

Po konteneryzowanej aplikacji .NET oraz zdefiniowanych danych wejściowych i wyjściowych akcji możesz przystąpić do korzystania z akcji. Do użycia w witrynie GitHub Marketplace nie trzeba publikować funkcji GitHub Actions. Przepływy pracy są definiowane w katalogu .github/workflows repozytorium jako pliki 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.'

Ważne

W przypadku konteneryzowanych funkcji GitHub Actions wymagane jest użycie polecenia runs-on: ubuntu-latest. Aby uzyskać więcej informacji, zobacz Składnia jobs.<job_id>.runs-onprzepływu pracy .

Powyższy plik YAML przepływu pracy definiuje trzy węzły podstawowe:

  • Przepływ name pracy. Ta nazwa jest również używana podczas tworzenia wskaźnika stanu przepływu pracy.
  • Węzeł on definiuje, kiedy i jak jest wyzwalana akcja.
  • Węzeł jobs przedstawia różne zadania i kroki w ramach każdego zadania. Poszczególne kroki korzystają z funkcji GitHub Actions.

Aby uzyskać więcej informacji, zobacz Tworzenie pierwszego przepływu pracy.

Skupienie się na węźle steps jest bardziej oczywiste:

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

Obiekt jobs.steps reprezentuje kompozycję przepływu pracy. Kroki są orkiestrowane w taki sposób, że są sekwencyjne, komunikatywne i komponowalne. W przypadku różnych funkcji GitHub Actions reprezentujących kroki każdy z nich ma dane wejściowe i wyjściowe, można tworzyć przepływy pracy.

W poprzednich krokach można obserwować:

  1. Repozytorium zostało wyewidencjonowane.

  2. Komunikat jest drukowany w dzienniku przepływu pracy po ręcznym uruchomieniu.

  3. Krok zidentyfikowany jako dotnet-code-metrics:

    • uses: dotnet/samples/github-actions/DotNet.GitHubAction@main to lokalizacja konteneryzowanej aplikacji .NET w tym samouczku.
    • env Tworzy zmienną środowiskową "GREETING", która jest drukowana podczas wykonywania aplikacji.
    • with określa każde z wymaganych danych wejściowych akcji.
  4. Krok warunkowy o nazwie Create pull request jest uruchamiany, gdy dotnet-code-metrics krok określa parametr updated-metrics wyjściowy z wartością true.

Ważne

Usługa GitHub umożliwia tworzenie zaszyfrowanych wpisów tajnych. Wpisy tajne mogą być używane w kompozycji przepływu pracy przy użyciu ${{ secrets.SECRET_NAME }} składni. W kontekście akcji usługi GitHub istnieje token usługi GitHub, który jest domyślnie wypełniany automatycznie: ${{ secrets.GITHUB_TOKEN }}. Aby uzyskać więcej informacji, zobacz Składnia kontekstu i wyrażenia dla funkcji GitHub Actions.

Zebranie wszystkich elementów

Repozytorium dotnet/samples w witrynie GitHub znajduje się w wielu przykładowych projektach kodu źródłowego platformy .NET, w tym aplikacji w tym samouczku.

Wygenerowany plik CODE_METRICS.md można nawigowalne. Ten plik reprezentuje hierarchię analizowanych projektów. Każdy projekt ma sekcję najwyższego poziomu i emoji reprezentujące ogólny stan najwyższej złożoności cyklatycznej dla zagnieżdżonych obiektów. Podczas nawigowania po pliku każda sekcja uwidacznia możliwości przechodzenia do szczegółów z podsumowaniem każdego obszaru. Znaczniki markdown mają zwijane sekcje jako dodatkową wygodę.

Hierarchia postępuje z:

  • Plik projektu do zestawu
  • Zestaw do przestrzeni nazw
  • Przestrzeń nazw do nazwanego typu
  • Każdy nazwany typ ma tabelę, a każda tabela ma następujące elementy:
    • Łącza do numerów wierszy dla pól, metod i właściwości
    • Indywidualne klasyfikacje metryk kodu

Działanie

Przepływ pracy określa, że on do pushmain gałęzi akcja jest wyzwalana do uruchomienia. Po uruchomieniu karta Akcje w usłudze GitHub będzie raportować strumień dziennika na żywo jego wykonywania. Oto przykładowy dziennik z .NET code metrics przebiegu:

.NET code metrics - GitHub Actions log

usprawnienia dotyczące wydajności

Jeśli wykonano czynności opisane w przykładzie, być może zauważysz, że za każdym razem, gdy ta akcja jest używana, zostanie utworzona kompilacja platformy Docker dla tego obrazu. Dlatego każdy wyzwalacz ma jakiś czas na skompilowanie kontenera przed jego uruchomieniem. Przed udostępnieniem funkcji GitHub Actions na platformie handlowej należy wykonać następujące czynności:

  1. (automatycznie) Kompilowanie obrazu platformy Docker
  2. Wypchnięcie obrazu platformy Docker do usługi GitHub Container Registry (lub dowolnego innego publicznego rejestru kontenerów)
  3. Zmień akcję, aby nie skompilować obrazu, ale użyć obrazu z rejestru publicznego.
# 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!!

Aby uzyskać więcej informacji, zobacz GitHub Docs: Praca z rejestrem kontenerów.

Zobacz też

Następne kroki