Udostępnij przez


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 ukazują możliwość programistycznej interakcji z kwestiami, tworzenia pull requestów, 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.

W tym poradniku nauczysz się, jak:

  • 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 pull request z wprowadzonymi 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.

W przypadku zdefiniowanej klasy danych wejściowych 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. Obecna mechanika demonstruje kod szablonowy wymagany do użycia.

Można użyć zewnętrznych odwołań do projektów lub pakietów i zarejestrować przy użyciu wstrzykiwania zależności. Funkcja lokalna Get<TService> jest statyczna, wymaga wystąpienia IHost i jest używana do rozwiązywania wymaganych usług. W przypadku singletonu CommandLine.Parser.Default, aplikacja pobiera instancję parser z args. Gdy argumenty nie mogą być parsowane, aplikacja kończy działanie z niezerowym kodem wyjścia. Aby uzyskać więcej informacji, zobacz Ustawianie kodów zakończenia dla działań.

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 hostowana jest akcja GitHub, może mieć zainstalowaną platformę .NET lub jej nie mieć. 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 .NET CLI z przepływów pracy GitHub Actions, rekomendujemy konteneryzację aplikacji dla bardziej funkcjonalnej akcji GitHub opartej na platformie .NET. 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 obok pliku solution.

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

Uwaga / Notatka

Aplikacja .NET w tym samouczku opiera się na SDK platformy .NET jako część swojej funkcjonalności. Plik Dockerfile tworzy nowy zestaw warstw platformy Docker niezależnie od poprzednich. Zaczyna od zera, używając obrazu SDK, i dodaje wynik 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ę, ponieważ nie określono nowego WORKDIR, co spowodowałoby 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 dotnet /DotNet.GitHubAction.dll.

Wskazówka

MCR w mcr.microsoft.com to skrót od "Microsoft Container Registry" (Rejestr Kontenerów Microsoft) i jest to katalog kontenerów firmy Microsoft dostępny w oficjalnym Docker Hub. Aby uzyskać więcej informacji, zobacz Katalog kontenerów Microsoft.

Ostrzeżenie

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 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 unikalne określenie tożsamości twojej akcji
  • inputs, który mapuje jeden do jednego z klasą ActionInputs
  • Element outputs, który jest zapisywany w 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 tym, jak aplikacja .NET została konteneryzowana, a dane wejściowe i wyjściowe akcji są zdefiniowane, jesteś gotowy na skorzystanie z akcji. Nie trzeba publikować GitHub Actions w GitHub Marketplace, aby je użyć. 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.

Skupiając się na węźle steps, struktura staje się bardziej oczywista.

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 jest lokalizacją 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.

Połącz wszystko

Repozytorium dotnet/samples w witrynie GitHub zawiera wiele przykładowych projektów kodu źródłowego platformy .NET, w tym aplikację używaną w niniejszym samouczku.

Wygenerowany plik CODE_METRICS.md jest możliwy do nawigacji. 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. Markdown ma zwijane sekcje dla dodatkowej wygody.

Hierarchia rozpoczyna się od:

  • Plik projektu do kompilacji
  • Zestaw do przestrzeni nazw
  • Przestrzeń nazw do jakiegoś zdefiniowanego 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

W akcji

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ć na żywo strumień dziennika jego wykonywania. Oto przykładowy log z .NET code metrics procesu:

Metryki kodu platformy .NET — dziennik funkcji GitHub Actions

Ulepszenia wydajności

Jeśli śledziłeś przykład, mogłeś zauważyć, że za każdym razem, gdy używana jest ta akcja, wykonywana jest komenda docker build dla danego obrazu. Każdy wyzwalacz musi poświęcić czas na zbudowanie 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. Wypchnij obraz Docker do 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 także

Dalsze kroki