Bagikan melalui


Tutorial: Membuat GitHub Action dengan .NET

Pelajari cara membuat aplikasi .NET yang dapat digunakan sebagai GitHub Action. GitHub Actions memungkinkan otomatisasi dan komposisi alur kerja. Dengan GitHub Actions, Anda dapat membuat, menguji, dan menyebarkan kode sumber dari GitHub. Selain itu, tindakan mengekspos kemampuan untuk berinteraksi secara terprogram dengan masalah, membuat permintaan pull, melakukan tinjauan kode, dan mengelola cabang. Untuk informasi selengkapnya tentang integrasi berkelanjutan dengan GitHub Actions, lihat Membangun dan menguji .NET.

Dalam tutorial ini, Anda akan belajar cara:

  • Menyiapkan aplikasi .NET untuk GitHub Actions
  • Menentukan input dan output tindakan
  • Menyusun alur kerja

Prasyarat

Niat aplikasi

Aplikasi dalam tutorial ini melakukan analisis metrik kode dengan:

  • Memindai dan menemukan file proyek *.csproj dan *.vbproj .

  • Menganalisis kode sumber yang ditemukan dalam proyek-proyek ini untuk:

    • Kompleksitas siklomatik
    • Indeks keberlanjutan
    • Kedalaman warisan
    • Penggandengan kelas
    • Jumlah baris kode sumber
    • Perkiraan baris kode yang dapat dieksekusi
  • Membuat (atau memperbarui) file CODE_METRICS.md .

Aplikasi ini tidak bertanggung jawab untuk melakukan pull request dengan perubahan pada file CODE_METRICS.md. Perubahan ini dikelola sebagai bagian dari komposisi alur kerja.

Referensi ke kode sumber dalam tutorial ini memiliki bagian aplikasi yang dihilangkan untuk membuatnya lebih singkat. Kode aplikasi lengkap tersedia di GitHub.

Menjelajahi aplikasi

Aplikasi konsol .NET menggunakan paket NuGet untuk mengurai argumen ke dalam objek 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]);
        }
    }
}

Kelas input tindakan sebelumnya menentukan beberapa input yang diperlukan agar aplikasi berhasil dijalankan. Konstruktor akan menulis "GREETINGS" nilai variabel lingkungan, jika tersedia di lingkungan eksekusi saat ini. Properti Name dan Branch diambil dan ditetapkan dari segmen terakhir sebuah string yang dipisahkan oleh "/".

Dengan kelas input tindakan yang ditentukan, fokus pada file 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);
}

File Program disederhanakan untuk kejelasan, untuk menjelajahi sumber kode lengkap, lihat pada Program.cs. Mekanisme yang sudah diterapkan menunjukkan kode boilerplate yang diperlukan untuk digunakan:

Proyek eksternal atau referensi paket dapat digunakan, dan terdaftar dengan injeksi dependensi. Get<TService> adalah fungsi lokal statis, yang memerlukan instansIHost, dan digunakan untuk menyelesaikan layanan yang diperlukan. Dengan menggunakan CommandLine.Parser.Default singleton, aplikasi memperoleh instans parser dari args. Ketika argumen tidak dapat diurai, aplikasi keluar dengan kode keluar bukan nol. Untuk informasi selengkapnya, lihat Mengatur kode keluar untuk tindakan.

Ketika arg berhasil diurai, aplikasi dipanggil dengan benar dengan input yang diperlukan. Dalam hal ini, panggilan ke fungsionalitas StartAnalysisAsync utama dilakukan.

Untuk menulis nilai output, Anda harus mengikuti format yang dikenali oleh GitHub Actions: Mengatur parameter output.

Menyiapkan aplikasi .NET untuk GitHub Actions

GitHub Actions mendukung dua variasi pengembangan aplikasi, baik... ataupun

  • JavaScript ( TypeScript opsional)
  • Kontainer Docker (aplikasi apa pun yang berjalan di Docker)

Lingkungan virtual tempat GitHub Action dihosting mungkin atau mungkin tidak menginstal .NET. Untuk informasi tentang apa yang telah diinstal sebelumnya di lingkungan target, lihat GitHub Actions Virtual Environments. Meskipun dimungkinkan untuk menjalankan perintah .NET CLI dari alur kerja GitHub Actions, untuk membuat GitHub Action berbasis .NET yang berfungsi sepenuhnya, kami menyarankan Anda untuk mengemas aplikasi dalam kontainer. Untuk informasi selengkapnya, lihat Membuat kontainer aplikasi .NET.

Dockerfile

Dockerfile adalah serangkaian instruksi untuk membangun gambar. Untuk aplikasi .NET, Dockerfile biasanya berada di akar direktori di samping file solusi.

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

Nota

Aplikasi .NET dalam tutorial ini bergantung pada .NET SDK sebagai bagian dari fungsionalitasnya. Dockerfile membuat sekumpulan lapisan Docker baru, independen dari yang sebelumnya. Dimulai dari nol dengan gambar SDK, dan menambahkan output build dari lapisan sebelumnya. Untuk aplikasi yang tidak memerlukan .NET SDK sebagai bagian dari fungsionalitasnya, mereka harus hanya mengandalkan .NET Runtime sebagai gantinya. Ini sangat mengurangi ukuran gambar.

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

Peringatan

Perhatikan dengan cermat setiap langkah dalam Dockerfile, karena berbeda dari Dockerfile standar yang dibuat dari fungsionalitas "tambahkan dukungan docker". Secara khusus, beberapa langkah terakhir bervariasi dengan tidak menentukan yang baru WORKDIR yang akan mengubah jalur ke aplikasi ENTRYPOINT.

Langkah-langkah Dockerfile sebelumnya meliputi:

  • Mengatur gambar dasar dari mcr.microsoft.com/dotnet/sdk:7.0 sebagai alias build-env.
  • Menyalin konten dan menerbitkan aplikasi .NET:
  • Menerapkan label ke kontainer.
  • Relayering gambar .NET SDK dari mcr.microsoft.com/dotnet/sdk:7.0
  • Menyalin hasil build yang diterbitkan dari build-env.
  • Menentukan titik masuk, yang mendelegasikan ke dotnet /DotNet.GitHubAction.dll.

Petunjuk / Saran

MCR adalah mcr.microsoft.com singkatan dari "Microsoft Container Registry", dan merupakan katalog kontainer sindikasi Microsoft dari hub Docker resmi. Untuk informasi selengkapnya, lihat Katalog kontainer sindikat Microsoft.

Perhatian

Jika Anda menggunakan file global.json untuk menyematkan versi SDK, Anda harus secara eksplisit merujuk ke versi tersebut di Dockerfile Anda. Misalnya, jika Anda telah menggunakan global.json untuk menyematkan versi 5.0.300SDK, Dockerfile Anda harus menggunakan mcr.microsoft.com/dotnet/sdk:5.0.300. Ini mencegah mengganggu GitHub Actions saat revisi minor baru dirilis.

Menentukan input dan output tindakan

Di bagian Jelajahi aplikasi, Anda mempelajari tentang kelasActionInputs. Objek ini mewakili input untuk GitHub Action. Agar GitHub mengenali bahwa repositori adalah GitHub Action, Anda harus memiliki file action.yml di akar repositori.

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

File action.yml sebelumnya mendefinisikan:

  • name dan description dalam GitHub Action
  • branding, yang digunakan di GitHub Marketplace untuk membantu mengidentifikasi tindakan Anda secara lebih unik
  • inputs, yang memetakan satu-ke-satu dengan kelas ActionInputs
  • outputs, yang dituliskan dalam Program dan digunakan sebagai bagian dari Komposisi Alur Kerja
  • Simpul runs, yang menginformasikan GitHub bahwa aplikasi docker dan argumen apa yang harus diteruskan kepada aplikasi tersebut

Untuk informasi selengkapnya, lihat Sintaks metadata untuk GitHub Actions.

Variabel lingkungan yang telah ditentukan sebelumnya

Dengan GitHub Actions, Anda akan mendapatkan banyak variabel lingkungan secara default. Misalnya, variabel GITHUB_REF akan selalu berisi referensi ke cabang atau tag yang memicu eksekusi alur kerja. GITHUB_REPOSITORY memiliki nama pemilik dan repositori, misalnya, dotnet/docs.

Anda harus menjelajahi variabel lingkungan yang telah ditentukan sebelumnya dan menggunakannya dengan sesuai.

Komposisi alur kerja

Dengan aplikasi .NET yang telah dikontainerkan, dan input dan output tindakan yang telah ditentukan, Anda siap untuk menggunakan tindakan. GitHub Actions tidak perlu diterbitkan di GitHub Marketplace untuk digunakan. Alur kerja didefinisikan dalam direktori .github/workflows repositori sebagai file 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.'

Penting

Untuk GitHub Actions yang terkontainer, Anda harus menggunakan runs-on: ubuntu-latest. Untuk informasi selengkapnya, lihat Sintaks jobs.<job_id>.runs-onalur kerja .

File YAML alur kerja sebelumnya mendefinisikan tiga simpul utama:

  • Alur name kerja. Nama ini jugalah yang digunakan saat membuat lencana status alur kerja.
  • Simpul on menentukan kapan dan bagaimana tindakan dipicu.
  • Simpul jobs menguraikan berbagai pekerjaan dan langkah-langkah dalam setiap pekerjaan. Langkah-langkah individual menggunakan GitHub Actions.

Untuk informasi selengkapnya, lihat Membuat alur kerja pertama Anda.

Berfokus pada node steps , komposisinya lebih jelas:

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 mewakili komposisi alur kerja. Langkah-langkah diorkestrasi sedemikian rupa sehingga berurutan, komunikatif, dan tersusun. Dengan berbagai GitHub Actions yang mewakili langkah-langkah, masing-masing memiliki input dan output, alur kerja dapat dibuat.

Pada langkah-langkah sebelumnya, Anda dapat mengamati:

  1. Repositori dicek keluar.

  2. Pesan dicetak ke log alur kerja, saat dijalankan secara manual.

  3. Langkah yang diidentifikasi sebagai dotnet-code-metrics:

    • uses: dotnet/samples/github-actions/DotNet.GitHubAction@main adalah lokasi aplikasi .NET dalam kontainer dalam tutorial ini.
    • env membuat variabel lingkungan "GREETING", yang dicetak dalam pelaksanaan aplikasi.
    • with menentukan setiap input tindakan yang diperlukan.
  4. Langkah kondisional bernama Create pull request dijalankan ketika langkah dotnet-code-metrics menentukan sebuah parameter keluaran berupa updated-metrics dengan nilai true.

Penting

GitHub memungkinkan pembuatan rahasia terenkripsi. Rahasia dapat digunakan dalam komposisi alur kerja, menggunakan sintaks ${{ secrets.SECRET_NAME }}. Dalam konteks GitHub Action, ada token GitHub yang secara otomatis diisi secara default: ${{ secrets.GITHUB_TOKEN }}. Untuk informasi selengkapnya, lihat Sintaks konteks dan ekspresi untuk Tindakan GitHub.

Satukan semuanya

Repositori GitHub dotnet/samples adalah rumah untuk banyak proyek kode sumber sampel .NET, termasuk aplikasi dalam tutorial ini.

File CODE_METRICS.md yang dihasilkan dapat dinavigasi. File ini mewakili hierarki proyek yang dianalisisnya. Setiap proyek memiliki bagian tingkat atas, dan emoji yang mewakili status keseluruhan kompleksitas siklomatik tertinggi untuk objek berlapis. Saat Anda menavigasi file, setiap bagian memaparkan peluang penelusuran dengan ringkasan setiap area. Markdown memiliki bagian yang dapat dilipat untuk kemudahan tambahan.

Hierarki berkembang dari:

  • File proyek ke assembly
  • Rakitan ke namespace
  • Namespace ke tipe-bernama
  • Setiap jenis bernama memiliki tabel, dan setiap tabel memiliki:
    • Tautan ke nomor baris untuk bidang, metode, dan properti
    • Peringkat individual untuk metrik kode

Sedang beraksi

Alur kerja menentukan bahwa ketika on sebuah push ke cabang main, tindakan dipicu untuk dijalankan. Saat berjalan, tab Tindakan di GitHub akan melaporkan aliran log langsung dari eksekusinya. Berikut adalah contoh log dari .NET code metrics eksekusi:

Metrik kode .NET - Log Tindakan GitHub

Peningkatan performa

Jika Anda mengikuti sampel, Anda mungkin telah memperhatikan bahwa setiap kali tindakan ini digunakan, tindakan ini akan melakukan build docker untuk gambar tersebut. Jadi, setiap pemicu memerlukan waktu tertentu untuk membangun kontainer sebelum menjalankannya. Sebelum merilis GitHub Actions Anda ke marketplace, Anda harus:

  1. (otomatis) Membangun gambar Docker
  2. Dorong gambar docker ke GitHub Container Registry (atau registri kontainer publik lainnya)
  3. Ubah tindakan untuk tidak membuat gambar, tetapi untuk menggunakan gambar dari registri publik.
# 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!!

Untuk informasi selengkapnya, lihat Dokumen GitHub: Bekerja dengan registri Kontainer.

Lihat juga

Langkah selanjutnya