mcpField
D
dotnet-gha-patterns

dotnet-gha-patterns

github.com/wshaddix云与 DevOps8673 次安装35 周前更新
代码仓库

关于 dotnet-gha-patterns

为 .NET 项目设计可组合 GitHub Actions 工作流的技能,侧重超越基础构建与测试模板的工作流组合及 CI/CD 控制。

主要功能

  1. 01使用 workflow_call 复用工作流
  2. 02用复合操作共享步骤
  3. 03构建多 TFM 与多操作系统矩阵
  4. 04按路径触发并管理并发组
  5. 05配置环境保护规则与密钥
  6. 06缓存 NuGet 和 SDK;设置手动输入

适用场景

  1. 01在多个仓库共享构建任务
  2. 02跨操作系统测试不同 .NET 版本
  3. 03文件无关时跳过 CI
  4. 04部署前要求人工审批

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-gha-patterns

安装后在 Claude Code 中输入/dotnet-gha-patterns确认已加载。

详细介绍

dotnet-gha-patterns

适用于 .NET 项目的可组合 GitHub Actions 工作流模式:使用 workflow_call 创建可复用工作流,使用复合操作封装共享步骤序列,跨 TFM 和操作系统进行矩阵构建,基于路径触发,使用并发组取消重复运行,设置环境保护规则,采用 NuGet 和 SDK 缓存策略,以及使用 workflow_dispatch 输入手动触发。

**版本假设:**GitHub Actions 工作流语法 v2。使用 actions/setup-dotnet@v4 支持 .NET 8/9/10。使用 actions/cache@v4 缓存依赖项。

**范围边界:**本技能负责 GitHub Actions 中可组合的 CI/CD 工作流设计模式。入门 CI 模板(基本构建/测试/打包)由 [skill:dotnet-add-ci] 负责——本技能通过高级组合扩展这些模板。CLI 专用发布流水线(用于 CLI 二进制文件的 build-package-release)由 [skill:dotnet-cli-release-pipeline] 负责——本技能涵盖 CLI 流水线所使用的通用工作流模式。基准测试 CI 集成由 [skill:dotnet-ci-benchmarking] 负责。

**不在范围内:**入门 CI/CD 模板——参见 [skill:dotnet-add-ci]。CLI 发布流水线(由标签触发、用于 CLI 工具的 build-package-release)——参见 [skill:dotnet-cli-release-pipeline]。基准测试 CI 工作流——参见 [skill:dotnet-ci-benchmarking]。Azure DevOps 流水线模式——参见 [skill:dotnet-ado-patterns]。构建/测试细节——参见 [skill:dotnet-gha-build-test]。发布工作流——参见 [skill:dotnet-gha-publish]。部署模式——参见 [skill:dotnet-gha-deploy]。

交叉引用:[skill:dotnet-add-ci] 提供本模式所扩展的入门模板;[skill:dotnet-cli-release-pipeline] 提供 CLI 专用发布自动化;[skill:dotnet-ci-benchmarking] 提供基准测试专用 CI 集成。


可复用工作流(workflow_call)

定义可复用工作流

可复用工作流允许调用方将整个工作流作为一个步骤调用。为明确约定定义输入、输出和密钥:

# .github/workflows/build-reusable.yml
name: Build (Reusable)

on:
  workflow_call:
    inputs:
      dotnet-version:
        description: '.NET SDK version to install'
        required: false
        type: string
        default: '8.0.x'
      configuration:
        description: 'Build configuration'
        required: false
        type: string
        default: 'Release'
      project-path:
        description: 'Path to solution or project file'
        required: true
        type: string
    outputs:
      artifact-name:
        description: 'Name of the uploaded build artifact'
        value: ${{ jobs.build.outputs.artifact-name }}
    secrets:
      NUGET_AUTH_TOKEN:
        description: 'NuGet feed authentication token'
        required: false

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      artifact-name: build-${{ github.sha }}
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: ${{ inputs.dotnet-version }}

      - name: Restore
        run: dotnet restore ${{ inputs.project-path }}

      - name: Build
        run: dotnet build ${{ inputs.project-path }} -c ${{ inputs.configuration }} --no-restore

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: build-${{ github.sha }}
          path: |
            **/bin/${{ inputs.configuration }}/**
          retention-days: 7
调用可复用工作流
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    uses: ./.github/workflows/build-reusable.yml
    with:
      dotnet-version: '8.0.x'
      project-path: MyApp.sln
    secrets:
      NUGET_AUTH_TOKEN: ${{ secrets.NUGET_AUTH_TOKEN }}

  test:
    needs: build
    uses: ./.github/workflows/test-reusable.yml
    with:
      dotnet-version: '8.0.x'
      project-path: MyApp.sln
跨仓库可复用工作流

使用完整路径引用其他仓库中的工作流:

jobs:
  build:
    uses: my-org/.github-workflows/.github/workflows/dotnet-build.yml@v1
    with:
      dotnet-version: '9.0.x'
    secrets: inherit  # pass all secrets from caller

如果可复用工作流需要访问与调用工作流相同的密钥,且不想逐一列出,请使用 secrets: inherit。


复合操作

创建复合操作

复合操作将多个步骤打包成一个可复用操作。对于多个工作流中共用的步骤序列,可使用复合操作:

# .github/actions/dotnet-setup/action.yml
name: 'Setup .NET Environment'
description: 'Install .NET SDK and restore NuGet packages with caching'

inputs:
  dotnet-version:
    description: '.NET SDK version'
    required: false
    default: '8.0.x'
  project-path:
    description: 'Path to solution or project'
    required: true

runs:
  using: 'composite'
  steps:
    - name: Setup .NET SDK
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: ${{ inputs.dotnet-version }}

    - name: Cache NuGet packages
      uses: actions/cache@v4
      with:
        path: ~/.nuget/packages
        key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
        restore-keys: |
          nuget-${{ runner.os }}-

    - name: Restore dependencies
      shell: bash
      run: dotnet restore ${{ inputs.project-path }}
使用复合操作
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET environment
        uses: ./.github/actions/dotnet-setup
        with:
          dotnet-version: '9.0.x'
          project-path: MyApp.sln

      - name: Build
        run: dotnet build MyApp.sln -c Release --no-restore
可复用工作流与复合操作的比较
特性可复用工作流复合操作
范围包含运行器的整个任务任务中的步骤
运行器选择自带 runs-on调用方的运行器
密钥访问显式指定或使用 inherit调用方的上下文
输出任务级输出步骤级输出
适用场景完整的构建/测试/部署任务共享的设置/清理步骤序列

矩阵构建

多 TFM 和多操作系统矩阵
jobs:
  test:
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        dotnet-version: ['8.0.x', '9.0.x']
        include:
          - os: ubuntu-latest
            dotnet-version: '10.0.x'
        exclude:
          - os: macos-latest
            dotnet-version: '8.0.x'
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET ${{ matrix.dotnet-version }}
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: ${{ matrix.dotnet-version }}

      - name: Test
        run: dotnet test --framework net${{ matrix.dotnet-version == '8.0.x' && '8.0' || matrix.dotnet-version == '9.0.x' && '9.0' || '10.0' }}

关键决策:

  • fail-fast: false 可确保即使某个组合失败,也会运行所有矩阵组合,从而全面了解哪些平台/TFM 存在问题
  • include 可添加笛卡尔积中没有的特定组合
  • exclude 可移除不必要或不受支持的组合
从 JSON 动态生成矩阵

针对复杂场景动态生成矩阵值:

jobs:
  compute-matrix:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
      - uses: actions/checkout@v4
      - id: set-matrix
        shell: bash
        run: |
          set -euo pipefail
          # Extract TFMs from Directory.Build.props or csproj files
          TFMS=$(grep -rh '<TargetFrameworks\?>' **/*.csproj | \
            sed 's/.*<TargetFrameworks\?>//' | sed 's/<.*//' | \
            tr ';' '\n' | sort -u | jq -R . | jq -sc .)
          echo "matrix={\"tfm\":$TFMS}" >> "$GITHUB_OUTPUT"

  test:
    needs: compute-matrix
    strategy:
      matrix: ${{ fromJson(needs.compute-matrix.outputs.matrix) }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: dotnet test --framework ${{ matrix.tfm }}

基于路径的触发器

选择性执行工作流

仅在相关文件发生更改时触发工作流,从而降低 CI 成本并缩短反馈时间:

on:
  push:
    branches: [main]
    paths:
      - 'src/**'
      - 'tests/**'
      - '*.sln'
      - 'Directory.Build.props'
      - 'Directory.Packages.props'
      - '.github/workflows/ci.yml'
  pull_request:
    branches: [main]
    paths:
      - 'src/**'
      - 'tests/**'
      - '*.sln'
      - 'Directory.Build.props'
      - 'Directory.Packages.props'
忽略非代码更改

使用 paths-ignore 跳过仅涉及文档更改的构建:

on:
  push:
    branches: [main]
    paths-ignore:
      - 'docs/**'
      - '*.md'
      - 'LICENSE'
      - '.editorconfig'

选择 paths 或 paths-ignore,不要同时使用。 在同一事件中同时指定两者时,paths-ignore 会被忽略。对于范围明确的工作流,请使用 paths(允许列表);对于范围较广的工作流,请使用 paths-ignore(拒绝列表)。


并发组

取消重复运行

当向同一分支或 PR 推送新提交时,取消正在进行的运行,避免浪费 CI 时间:

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true
按环境设置并发

避免同时向同一环境部署:

concurrency:
  group: deploy-production
  cancel-in-progress: false  # queue, do not cancel deployments

对于构建/测试,使用 cancel-in-progress: true(新提交会取代旧提交);对于部署,使用 cancel-in-progress: false(不要取消正在进行的部署)。


环境保护规则

配置环境
jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.example.com
    steps:
      - name: Deploy to staging
        run: echo "Deploying..."

  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com
    steps:
      - name: Deploy to production
        run: echo "Deploying..."

在 GitHub Settings > Environments 中配置保护规则:

规则用途
必需审核者部署前手动批准
等待时间冷却期(例如 15 分钟)
分支限制仅允许 main 或 release/* 分支部署
自定义部署保护规则第三方集成(监控检查)
环境机密

环境可以拥有自己的机密,并覆盖仓库级机密。使用环境级机密存储部署凭据:

jobs:
  deploy:
    environment: production
    runs-on: ubuntu-latest
    steps:
      - name: Deploy
        env:
          # These resolve to environment-specific values
          CONNECTION_STRING: ${{ secrets.CONNECTION_STRING }}
          API_KEY: ${{ secrets.API_KEY }}
        run: ./deploy.sh

缓存策略

NuGet 包缓存
- name: Cache NuGet packages
  uses: actions/cache@v4
  with:
    path: ~/.nuget/packages
    key: nuget-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/Directory.Packages.props') }}
    restore-keys: |
      nuget-${{ runner.os }}-

restore-keys 前缀匹配可确保在 csproj 文件发生变化时仍能部分命中缓存(大多数包仍会保留在缓存中)。

.NET SDK 缓存

对于自托管运行器,或 SDK 安装缓慢的场景:

- name: Setup .NET with cache
  uses: actions/setup-dotnet@v4
  with:
    dotnet-version: '8.0.x'
    cache: true
    cache-dependency-path: '**/packages.lock.json'

actions/setup-dotnet@v4 中的 cache: true 选项会启用内置 NuGet 缓存,并使用 packages.lock.json 作为缓存键。

构建输出缓存(.NET 9+)

.NET 9 引入了 MSBuild 构建检查缓存。对于增量 CI 构建:

- name: Cache build output
  uses: actions/cache@v4
  with:
    path: |
      **/bin/
      **/obj/
    key: build-${{ runner.os }}-${{ hashFiles('**/*.csproj', '**/*.cs') }}
    restore-keys: |
      build-${{ runner.os }}-

谨慎使用构建输出缓存——过期缓存可能掩盖构建错误。优先将 NuGet 缓存用作 CI 加速的主要优化方式。


workflow_dispatch 输入

带参数的手动触发
on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target deployment environment'
        required: true
        type: choice
        options:
          - staging
          - production
        default: staging
      version:
        description: 'Version to deploy (e.g., 1.2.3)'
        required: true
        type: string
      dry-run:
        description: 'Simulate deployment without applying changes'
        required: false
        type: boolean
        default: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: v${{ inputs.version }}

      - name: Deploy
        env:
          DRY_RUN: ${{ inputs.dry-run }}
        run: |
          set -euo pipefail
          if [ "$DRY_RUN" = "true" ]; then
            echo "DRY RUN: would deploy v${{ inputs.version }} to ${{ inputs.environment }}"
          else
            ./deploy.sh --version ${{ inputs.version }}
          fi

输入类型:string、boolean、choice、environment(从已配置的环境中选择)。


Agent 易错点

  1. 不要在同一事件中混用 paths 和 paths-ignore——同时指定时,paths-ignore 会被静默忽略。二者选其一。
  2. 在矩阵构建中设置 fail-fast: false——默认的 fail-fast: true 会在某个作业失败时取消其他关联作业,导致无法发现其他哪些组合也会失败。
  3. 在所有 bash 步骤中使用 set -euo pipefail——如果不使用 pipefail,管道命令中的非零退出状态(例如 script | tee)不会导致步骤失败。
  4. 可复用工作流的输入默认是字符串——必须在 workflow_call 输入中显式使用 type: 声明布尔值和数字类型。
  5. 缓存键必须包含 runner.os——NuGet 包依赖操作系统;在 Windows 上恢复由 Linux 构建的缓存会导致还原失败。
  6. 不要在工作流文件中硬编码 TFM——使用矩阵变量或从 csproj 中提取,以便工作流与项目配置保持同步。
  7. secrets: inherit 会传递调用方的所有机密——对于涉及安全的可复用工作流,应显式声明机密以限制暴露范围。
  8. 部署的并发组必须使用 cancel-in-progress: false——取消正在进行的部署可能导致基础设施处于不一致状态。

评论

支持 Markdown