Поделиться через


Учебник. Создание действия GitHub Actions с помощью .NET

Узнайте, как создать приложение .NET, которое можно использовать в качестве действия GitHub. GitHub Actions обеспечивает автоматизацию и композицию рабочих процессов. С помощью GitHub Actions можно создавать, тестировать и развертывать исходный код из GitHub. Кроме того, действия предоставляют возможность программного взаимодействия с проблемами, создания запросов на вытягивание, выполнения проверок кода и управления ветвями. Дополнительные сведения о непрерывной интеграции с GitHub Actions см. в статье "Создание и тестирование .NET".

В этом руководстве описано следующее:

  • Подготовка приложения .NET для GitHub Actions
  • Определение входных и выходных данных действий
  • Создание рабочего процесса

Необходимые компоненты

Намерение приложения

Приложение в этом руководстве выполняет анализ метрик кода следующим образом:

  • Сканирование и обнаружение файлов проекта *.csproj и *.vbproj .

  • Анализ обнаруженного исходного кода в этих проектах:

    • Цикломатическая сложность
    • Индекс удобства обслуживания
    • Глубина наследования
    • Взаимозависимость классов
    • Количество строк исходного кода
    • Приблизительные строки исполняемого кода
  • Создание (или обновление) файла CODE_METRICS.md .

Приложение не несет ответственности за создание запроса на вытягивание с изменениями в файле CODE_METRICS.md. Эти изменения управляются как часть композиции рабочего процесса.

Ссылки на исходный код в этом руководстве содержат части приложения, пропущенные для краткости. Полный код приложения доступен на сайте GitHub.

Ознакомиться с приложением

Консольное приложение .NET использует CommandLineParser пакет NuGet для анализа аргументов в 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]);
        }
    }
}

Предыдущий класс входных данных действия определяет несколько необходимых входных данных для успешного выполнения приложения. Конструктор записывает значение переменной "GREETINGS" среды, если он доступен в текущей среде выполнения. Branch Свойства Name анализируются и назначаются из последнего сегмента "/" строки с разделителями.

С помощью определенного класса входных данных действия сосредоточьтесь на файле 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);
}

Файл Program упрощен для краткости, чтобы просмотреть полный пример источника, см. статью Program.cs. Механика на месте демонстрирует стандартный код, необходимый для использования:

Внешние ссылки на проект или пакет можно использовать и зарегистрировать с помощью внедрения зависимостей. Это Get<TService> статическую локальную функцию, которая требует IHost экземпляра и используется для разрешения необходимых служб. При использовании одноэлементного CommandLine.Parser.Default приложения приложение получает parser экземпляр из args. Если аргументы не могут быть проанализированы, приложение завершает работу с кодом выхода без нуля. Дополнительные сведения см. в разделе "Настройка кодов выхода для действий".

При успешном анализе args приложение было вызвано правильно с необходимыми входными данными. В этом случае выполняется вызов основной функции StartAnalysisAsync .

Чтобы записать выходные значения, необходимо выполнить формат, распознанный GitHub Actions: задание выходного параметра.

Подготовка приложения .NET для GitHub Actions

GitHub Actions поддерживает два варианта разработки приложений.

  • JavaScript (необязательно TypeScript)
  • Контейнер Docker (любое приложение, работающее в Docker)

Виртуальная среда, в которой размещено действие GitHub, может или не установлена .NET. Сведения о том, что предустановлено в целевой среде, см. в виртуальных средах GitHub Actions. Хотя можно запускать команды .NET CLI из рабочих процессов GitHub Actions, для более полного функционирования. GitHub Action на основе NET рекомендуется контейнеризировать приложение. Дополнительные сведения см. в разделе "Контейнеризация приложения .NET".

Файл Dockerfile

Dockerfile — это набор инструкций для создания образа. Для приложений .NET файл Dockerfile обычно находится в корне каталога рядом с файлом решения.

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

Примечание.

Приложение .NET в этом руководстве использует пакет SDK для .NET в рамках его функциональных возможностей. Dockerfile создает новый набор слоев Docker, не зависящий от предыдущих. Он начинается с нуля с образа пакета SDK и добавляет выходные данные сборки из предыдущего набора слоев. Для приложений, которые не требуют пакета SDK для .NET в рамках их функциональных возможностей, они должны полагаться только на среду выполнения .NET. Это значительно сокращает размер изображения.

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

Предупреждение

Обратите особое внимание на каждый шаг в Dockerfile, так как он отличается от стандартного Файла Dockerfile, созданного из функции "добавление поддержки Docker". В частности, последние несколько шагов различаются, не указывая новый WORKDIR , который изменит путь к приложению ENTRYPOINT.

Для Dockerfile предусмотрены следующие этапы.

  • Установка базового образа из mcr.microsoft.com/dotnet/sdk:7.0 в качестве псевдонима build-env.
  • Копирование содержимого и публикация приложения .NET:
    • Приложение публикуется с помощью команды dotnet publish.
  • Применение меток к контейнеру.
  • Ретрансляция образа пакета SDK для .NET из mcr.microsoft.com/dotnet/sdk:7.0
  • Копирование опубликованных выходных данных сборки из .build-env
  • Определение точки входа, которая делегируется dotnet /DotNet.GitHubAction.dll.

Совет

MCR в mcr.microsoft.com означает "Microsoft Container Registry", а также является каталогом для выобъединенного контейнера Майкрософт из официального центра Docker. Дополнительные сведения см. в каталоге контейнеров microsoft syndicates.

Внимание

Если вы используете файл global.json для закрепления версии пакета SDK, необходимо явно ссылаться на эту версию в Dockerfile. Например, если вы использовали global.json для закрепления версии 5.0.300пакета SDK, следует использовать mcr.microsoft.com/dotnet/sdk:5.0.300Dockerfile. Это предотвращает нарушение GitHub Actions при выпуске новой дополнительной редакции.

Определение входных и выходных данных действий

В разделе "Изучение приложения" вы узнали о ActionInputs классе. Этот объект представляет входные данные для действия GitHub. Чтобы GitHub распознал, что репозиторий является действием GitHub, необходимо иметь файл action.yml в корне репозитория.

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

Предыдущий файл action.yml определяет следующее:

  • description Действие name GitHub
  • , brandingкоторый используется в GitHub Marketplace для более уникальной идентификации вашего действия
  • Объект inputs, который сопоставляет один к одному с классом ActionInputs
  • Объект outputs, который записывается в состав рабочего процесса и используется в Program составе композиции рабочего процесса.
  • Узел runs , который сообщает GitHub, что приложение является приложением docker и какие аргументы для передачи в него

Дополнительные сведения см. в синтаксисе метаданных для GitHub Actions.

Предопределенные переменные среды

С помощью GitHub Actions вы получите множество переменных среды по умолчанию. Например, переменная GITHUB_REF всегда будет содержать ссылку на ветвь или тег, активировав выполнение рабочего процесса. GITHUB_REPOSITORY имеет имя владельца и репозитория, например dotnet/docs.

Необходимо изучить предварительно определенные переменные среды и использовать их соответствующим образом.

Состав рабочего процесса

С помощью контейнера приложения .NET и определенных входных и выходных данных действия можно использовать действие. Действия GitHub не требуются для публикации в GitHub Marketplace. Рабочие процессы определяются в каталоге github/workflows репозитория в виде файлов 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.'

Важно!

Для контейнерных действий GitHub необходимо использовать runs-on: ubuntu-latest. Дополнительные сведения см. в синтаксисе jobs.<job_id>.runs-onрабочего процесса.

Предыдущий файл YAML рабочего процесса определяет три основных узла:

  • Тип name рабочего процесса. Это имя также используется при создании индикатора состояния рабочего процесса.
  • Узел on определяет, когда и как активируется действие.
  • Узел jobs описывает различные задания и шаги в каждом задании. Отдельные шаги используют GitHub Actions.

Дополнительные сведения см. в статье "Создание первого рабочего процесса".

Фокусируясь на steps узле, композиция более очевидна:

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 состав рабочего процесса. Шаги оркестрируются таким образом, чтобы они были последовательными, коммуникатными и компонуемыми. С помощью различных действий GitHub Actions, представляющих шаги, каждый из которых имеет входные и выходные данные, рабочие процессы можно создавать.

На предыдущих шагах можно наблюдать:

  1. Репозиторий проверка отключен.

  2. Сообщение выводится в журнал рабочего процесса при выполнении вручную.

  3. Шаг, определенный как dotnet-code-metrics:

    • uses: dotnet/samples/github-actions/DotNet.GitHubAction@main — это расположение контейнерного приложения .NET в этом руководстве.
    • env создает переменную "GREETING"среды, которая печатается в выполнении приложения.
    • with указывает каждый из обязательных входных данных действия.
  4. Условный шаг с именем Create pull request выполняется при dotnet-code-metrics указании выходного параметра updated-metrics со значением true.

Важно!

GitHub позволяет создавать зашифрованные секреты. Секреты можно использовать в составе рабочего процесса с помощью синтаксиса ${{ secrets.SECRET_NAME }} . В контексте действия GitHub существует маркер GitHub, который автоматически заполняется по умолчанию: ${{ secrets.GITHUB_TOKEN }} Дополнительные сведения см. в синтаксисе контекста и выражений для GitHub Actions.

Сборка

Репозиторий dotnet/samples GitHub является домом для многих проектов исходного кода .NET, включая приложение в этом руководстве.

Созданный файл CODE_METRICS.md доступен для навигации. Этот файл представляет иерархию проанализированных проектов. Каждый проект имеет раздел верхнего уровня и эмодзи, представляющий общее состояние максимальной сложности цикломатики для вложенных объектов. При переходе по файлу каждый раздел предоставляет возможности детализации с сводкой по каждой области. Markdown содержит сворачиваемые разделы в качестве дополнительного удобства.

Иерархия выполняется из:

  • Файл проекта для сборки
  • Сборка для пространства имен
  • Пространство имен для именованного типа
  • Каждый именованный тип имеет таблицу, и каждая таблица имеет:
    • Ссылки на номера строк для полей, методов и свойств
    • Отдельные оценки для метрик кода

В действии

Рабочий процесс указывает, что on push ветвь main активируется действие для выполнения. При выполнении вкладка "Действия " в GitHub сообщает поток динамического журнала его выполнения. Ниже приведен пример журнала из .NET code metrics запуска:

.NET code metrics - GitHub Actions log

Улучшения производительности

Если вы последовали по образцу, возможно, вы заметили, что при каждом использовании этого действия будет выполняться сборка docker для этого образа. Таким образом, каждый триггер сталкивается с некоторое время, чтобы создать контейнер перед его запуском. Перед выпуском GitHub Actions в Marketplace необходимо:

  1. (автоматически) Создание образа Docker
  2. Отправьте образ Docker в реестр контейнеров GitHub (или любой другой общедоступный реестр контейнеров)
  3. Измените действие, чтобы не создать образ, а использовать образ из общедоступного реестра.
# 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!!

Дополнительные сведения см . в документации GitHub: работа с реестром контейнеров.

См. также

Следующие шаги