mcpField
D
dotnet-ado-patterns

dotnet-ado-patterns

github.com/wshaddixクラウド・DevOps8667 回インストール35 週間前に更新
リポジトリ

dotnet-ado-patterns について

.NET チーム向けに、Azure DevOps YAML パイプラインの構成パターンを扱うスキルです。再利用可能なテンプレート、設定の集約、条件挿入、複数ステージやトリガーの設計を解説します。

主な機能

  1. 01ステージ・ジョブ・ステップのテンプレート化
  2. 02extends による構造の強制
  3. 03変数グループとテンプレートの集約
  4. 04式によるパイプライン内容の生成
  5. 05複数ステージとトリガーの設定

活用シーン

  1. 01.NET パイプライン間でビルド手順を再利用
  2. 02必須のセキュリティ・コンプライアンス段階を追加
  3. 03環境に応じたデプロイフローを生成
  4. 04CI・PR・スケジュール実行を設定

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/dotnet-ado-patternsを入力して読み込まれたか確認します。

詳細

dotnet-ado-patterns

.NET プロジェクト向けのコンポーザブルな Azure DevOps YAML パイプラインパターン:extends、stages、jobs、steps キーワードを使ったテンプレート参照による階層的なパイプライン構成、一元管理された設定のための変数グループと変数テンプレート、組織全体へのポリシー注入に使用するパイプラインデコレーター、${{ if }} と ${{ each }} 式による条件付き挿入、マルチステージパイプライン(ビルド、テスト、デプロイ)、および CI、PR、スケジュール実行のためのパイプライントリガー。

バージョンの前提条件: Azure Pipelines YAML スキーマ。.NET 8/9/10 のビルドには DotNetCoreCLI@2 タスクを使用します。テンプレート式の構文は v2 です。

対象範囲: このスキルは Azure DevOps YAML のコンポーザブルなパイプライン設計パターンを担当します。スターター CI テンプレート(基本的なビルド/テスト/パック)は [skill:dotnet-add-ci] が担当します。このスキルは、これらのテンプレートを高度な構成で拡張します。CLI バイナリ向けの CLI 固有のリリースパイプライン(build-package-release)は [skill:dotnet-cli-release-pipeline] が担当します。このスキルでは、CLI パイプラインで使用する汎用的なパイプラインパターンを扱います。環境と承認、サービス接続、クラシックリリースなどの ADO 固有機能については [skill:dotnet-ado-unique] を参照してください。

対象外: スターター CI テンプレートについては [skill:dotnet-add-ci] を参照してください。CLI リリースパイプライン(CLI ツール向けのタグトリガー型 build-package-release)については [skill:dotnet-cli-release-pipeline] を参照してください。ADO 固有機能(環境、サービス接続、クラシックリリース)については [skill:dotnet-ado-unique] を参照してください。ビルド/テストの詳細については [skill:dotnet-ado-build-test] を参照してください。公開パイプラインについては [skill:dotnet-ado-publish] を参照してください。GitHub Actions のワークフローパターンについては [skill:dotnet-gha-patterns] を参照してください。

相互参照:[skill:dotnet-add-ci] では、これらのパターンで拡張するスターターテンプレートを扱います。[skill:dotnet-cli-release-pipeline] では、CLI 固有のリリース自動化を扱います。


テンプレート参照

ステージテンプレート

ステージテンプレートは、呼び出し元がマルチステージパイプラインに挿入する、再利用可能なパイプラインステージを定義します。

# templates/stages/build-test.yml
parameters:
  - name: dotnetVersion
    type: string
    default: '8.0.x'
  - name: buildConfiguration
    type: string
    default: 'Release'
  - name: projects
    type: string
    default: '**/*.sln'

stages:
  - stage: Build
    displayName: 'Build and Test'
    jobs:
      - job: BuildJob
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - task: UseDotNet@2
            displayName: 'Install .NET SDK'
            inputs:
              packageType: 'sdk'
              version: ${{ parameters.dotnetVersion }}

          - task: DotNetCoreCLI@2
            displayName: 'Restore'
            inputs:
              command: 'restore'
              projects: ${{ parameters.projects }}

          - task: DotNetCoreCLI@2
            displayName: 'Build'
            inputs:
              command: 'build'
              projects: ${{ parameters.projects }}
              arguments: '-c ${{ parameters.buildConfiguration }} --no-restore'
ステージテンプレートの呼び出し
# azure-pipelines.yml
trigger:
  branches:
    include:
      - main

stages:
  - template: templates/stages/build-test.yml
    parameters:
      dotnetVersion: '9.0.x'
      buildConfiguration: 'Release'
      projects: 'MyApp.sln'

  - template: templates/stages/deploy.yml
    parameters:
      environment: 'staging'
ジョブテンプレート

ジョブテンプレートは、プールとステップを含むジョブ全体をカプセル化します。

# templates/jobs/dotnet-build.yml
parameters:
  - name: dotnetVersion
    type: string
    default: '8.0.x'
  - name: projects
    type: string

jobs:
  - job: Build
    pool:
      vmImage: 'ubuntu-latest'
    steps:
      - task: UseDotNet@2
        inputs:
          packageType: 'sdk'
          version: ${{ parameters.dotnetVersion }}

      - task: DotNetCoreCLI@2
        displayName: 'Build'
        inputs:
          command: 'build'
          projects: ${{ parameters.projects }}
          arguments: '-c Release'
ステップテンプレート

ステップテンプレートは、既存のジョブに挿入する再利用可能なステップシーケンスを定義します。

# templates/steps/dotnet-setup.yml
parameters:
  - name: dotnetVersion
    type: string
    default: '8.0.x'
  - name: nugetFeed
    type: string
    default: ''

steps:
  - task: UseDotNet@2
    displayName: 'Install .NET SDK ${{ parameters.dotnetVersion }}'
    inputs:
      packageType: 'sdk'
      version: ${{ parameters.dotnetVersion }}

  - ${{ if ne(parameters.nugetFeed, '') }}:
    - task: NuGetAuthenticate@1
      displayName: 'Authenticate NuGet feed'

  - task: DotNetCoreCLI@2
    displayName: 'Restore packages'
    inputs:
      command: 'restore'
      projects: '**/*.sln'
      ${{ if ne(parameters.nugetFeed, '') }}:
        feedsToUse: 'select'
        vstsFeed: ${{ parameters.nugetFeed }}
パイプラインでのステップテンプレートの使用
jobs:
  - job: Build
    pool:
      vmImage: 'ubuntu-latest'
    steps:
      - checkout: self

      - template: templates/steps/dotnet-setup.yml
        parameters:
          dotnetVersion: '9.0.x'
          nugetFeed: 'MyOrg/MyFeed'

      - task: DotNetCoreCLI@2
        displayName: 'Build'
        inputs:
          command: 'build'
          arguments: '-c Release --no-restore'
extends テンプレート(強制されるパイプライン構造)

extends キーワードは、組織テンプレートで定義された必須のパイプライン構造を強制します。呼び出し元がこの構造を回避することはできません。

# templates/pipeline-policy.yml
parameters:
  - name: stages
    type: stageList
    default: []

stages:
  - stage: SecurityScan
    displayName: 'Security Scan (Required)'
    jobs:
      - job: Scan
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - script: echo "Running mandatory security scan"

  - ${{ each stage in parameters.stages }}:
    - ${{ stage }}

  - stage: Compliance
    displayName: 'Compliance Check (Required)'
    dependsOn:
      - ${{ each stage in parameters.stages }}:
        - ${{ stage.stage }}
    jobs:
      - job: Check
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - script: echo "Running compliance checks"
# azure-pipelines.yml (caller)
extends:
  template: templates/pipeline-policy.yml
  parameters:
    stages:
      - stage: Build
        jobs:
          - job: BuildApp
            pool:
              vmImage: 'ubuntu-latest'
            steps:
              - script: dotnet build -c Release

extends テンプレートは、呼び出し元が定義するステージを、削除できない必須のセキュリティステージとコンプライアンスステージでラップします。


変数グループと変数テンプレート

変数グループ

変数グループは、複数のパイプラインで共有する設定を一元管理します。Azure Pipelines ライブラリからリンクします。

variables:
  - group: 'dotnet-build-settings'
  - group: 'nuget-feed-credentials'
  - name: buildConfiguration
    value: 'Release'
変数テンプレート

変数テンプレートは、YAML ファイルで再利用可能な変数セットを定義します。

# templates/variables/dotnet-defaults.yml
variables:
  dotnetVersion: '8.0.x'
  buildConfiguration: 'Release'
  testResultsDirectory: '$(Build.ArtifactStagingDirectory)/test-results'
  coverageDirectory: '$(Build.ArtifactStagingDirectory)/coverage'
# azure-pipelines.yml
variables:
  - template: templates/variables/dotnet-defaults.yml
  - name: projectPath
    value: 'MyApp.sln'
Key Vault 統合を使用する変数グループ

シークレットを管理するには、変数グループを Azure Key Vault にリンクします。シークレットはパイプラインの実行時に取得されます。

# Reference in pipeline
variables:
  - group: 'kv-production-secrets'  # linked to Azure Key Vault
  - name: nonSecretVar
    value: 'some-value'

steps:
  - script: |
      echo "Using secret from Key Vault"
      # $(sql-connection-string) resolves at runtime from Key Vault
    env:
      CONNECTION_STRING: $(sql-connection-string)

Key Vault にリンクされた変数グループには、Key Vault へのアクセス権を持つサービス接続が必要です。Key Vault 内のシークレット名は変数名に対応します(ハイフンは有効な変数文字に変換されます)。


パイプラインデコレーター

パイプラインデコレーターは、個々のパイプラインファイルを変更せずにポリシーを適用できるよう、組織またはプロジェクト内のすべてのパイプラインにステップを注入します。デコレーターは ADO 専用の機能で、GitHub Actions に相当する機能はありません。拡張機能マニフェスト、デプロイのガイダンス、ユースケースの例など、実装の詳細については [skill:dotnet-ado-unique] を参照してください。


条件付き挿入

${{ if }} 式
parameters:
  - name: runIntegrationTests
    type: boolean
    default: false
  - name: targetEnvironment
    type: string
    default: 'development'
    values:
      - development
      - staging
      - production

stages:
  - stage: Build
    jobs:
      - job: BuildJob
        steps:
          - script: dotnet build -c Release

  - ${{ if eq(parameters.runIntegrationTests, true) }}:
    - stage: IntegrationTests
      dependsOn: Build
      jobs:
        - job: IntegrationTestJob
          steps:
            - script: dotnet test --filter Category=Integration

  - ${{ if eq(parameters.targetEnvironment, 'production') }}:
    - stage: ApprovalGate
      dependsOn: Build
      jobs:
        - job: WaitForApproval
          pool: server
          steps:
            - task: ManualValidation@0
              inputs:
                notifyUsers: '[email protected]'
                instructions: 'Approve production deployment'
${{ each }} による反復処理
parameters:
  - name: environments
    type: object
    default:
      - name: development
        pool: 'ubuntu-latest'
        approvals: false
      - name: staging
        pool: 'ubuntu-latest'
        approvals: true
      - name: production
        pool: 'ubuntu-latest'
        approvals: true

stages:
  - stage: Build
    jobs:
      - job: BuildJob
        steps:
          - script: dotnet build -c Release

  - ${{ each env in parameters.environments }}:
    - stage: Deploy_${{ env.name }}
      displayName: 'Deploy to ${{ env.name }}'
      dependsOn: Build
      jobs:
        - ${{ if eq(env.approvals, true) }}:
          - job: Approve
            pool: server
            steps:
              - task: ManualValidation@0
                inputs:
                  instructions: 'Approve deployment to ${{ env.name }}'

        - deployment: DeployApp
          pool:
            vmImage: ${{ env.pool }}
          environment: ${{ env.name }}
          strategy:
            runOnce:
              deploy:
                steps:
                  - script: echo "Deploying to ${{ env.name }}"
テンプレート内での条件付きステップ挿入
# templates/steps/dotnet-test.yml
parameters:
  - name: collectCoverage
    type: boolean
    default: false

steps:
  - task: DotNetCoreCLI@2
    displayName: 'Run tests'
    inputs:
      command: 'test'
      projects: '**/*Tests.csproj'
      ${{ if eq(parameters.collectCoverage, true) }}:
        arguments: '-c Release --collect:"XPlat Code Coverage"'
      ${{ else }}:
        arguments: '-c Release'

  - ${{ if eq(parameters.collectCoverage, true) }}:
    - task: PublishCodeCoverageResults@2
      displayName: 'Publish coverage'
      inputs:
        summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'

マルチステージパイプライン

ビルド、テスト、デプロイのパターン
trigger:
  branches:
    include:
      - main
      - release/*

stages:
  - stage: Build
    displayName: 'Build'
    jobs:
      - job: BuildJob
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - task: UseDotNet@2
            inputs:
              packageType: 'sdk'
              version: '8.0.x'

          - task: DotNetCoreCLI@2
            displayName: 'Build'
            inputs:
              command: 'build'
              projects: 'MyApp.sln'
              arguments: '-c Release'

          - task: DotNetCoreCLI@2
            displayName: 'Publish'
            inputs:
              command: 'publish'
              projects: 'src/MyApp/MyApp.csproj'
              arguments: '-c Release -o $(Build.ArtifactStagingDirectory)/app'

          - task: PublishPipelineArtifact@1
            displayName: 'Upload artifact'
            inputs:
              targetPath: '$(Build.ArtifactStagingDirectory)/app'
              artifactName: 'app'

  - stage: Test
    displayName: 'Test'
    dependsOn: Build
    jobs:
      - job: UnitTests
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - task: UseDotNet@2
            inputs:
              packageType: 'sdk'
              version: '8.0.x'

          - task: DotNetCoreCLI@2
            displayName: 'Run tests'
            inputs:
              command: 'test'
              projects: '**/*Tests.csproj'
              arguments: '-c Release --logger "trx;LogFileName=results.trx"'

          - task: PublishTestResults@2
            displayName: 'Publish test results'
            condition: always()
            inputs:
              testResultsFormat: 'VSTest'
              testResultsFiles: '**/results.trx'

  - stage: DeployStaging
    displayName: 'Deploy to Staging'
    dependsOn: Test
    jobs:
      - deployment: DeployStaging
        pool:
          vmImage: 'ubuntu-latest'
        environment: 'staging'
        strategy:
          runOnce:
            deploy:
              steps:
                - download: current
                  artifact: app
                - script: echo "Deploying to staging"

  - stage: DeployProduction
    displayName: 'Deploy to Production'
    dependsOn: DeployStaging
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
      - deployment: DeployProduction
        pool:
          vmImage: 'ubuntu-latest'
        environment: 'production'
        strategy:
          runOnce:
            deploy:
              steps:
                - download: current
                  artifact: app
                - script: echo "Deploying to production"
ステージの依存関係と条件
stages:
  - stage: Build
    jobs:
      - job: BuildJob
        steps:
          - script: dotnet build -c Release

  - stage: UnitTests
    dependsOn: Build
    jobs:
      - job: UnitTestJob
        steps:
          - script: dotnet test --filter Category!=Integration

  - stage: IntegrationTests
    dependsOn: Build
    jobs:
      - job: IntegrationTestJob
        steps:
          - script: dotnet test --filter Category=Integration

  # Deploy only if BOTH test stages succeed
  - stage: Deploy
    dependsOn:
      - UnitTests
      - IntegrationTests
    condition: and(succeeded('UnitTests'), succeeded('IntegrationTests'))
    jobs:
      - deployment: DeployApp
        environment: 'production'
        strategy:
          runOnce:
            deploy:
              steps:
                - script: echo "Deploying"

パイプライントリガー

CI トリガー
trigger:
  branches:
    include:
      - main
      - release/*
    exclude:
      - feature/experimental/*
  paths:
    include:
      - src/**
      - tests/**
      - '*.sln'
      - Directory.Build.props
      - Directory.Packages.props
    exclude:
      - docs/**
      - '*.md'
  tags:
    include:
      - 'v*'
PR トリガー
pr:
  branches:
    include:
      - main
      - release/*
  paths:
    include:
      - src/**
      - tests/**
    exclude:
      - docs/**
  drafts: false  # do not trigger on draft PRs
スケジュールトリガー
schedules:
  - cron: '0 6 * * 1-5'
    displayName: 'Weekday nightly build'
    branches:
      include:
        - main
    always: false  # only run if there are changes since last run

  - cron: '0 0 * * 0'
    displayName: 'Weekly full validation'
    branches:
      include:
        - main
    always: true  # run even without changes
パイプラインリソーストリガー

別のパイプラインの完了時にパイプラインをトリガーします。

resources:
  pipelines:
    - pipeline: buildPipeline
      source: 'MyApp-Build'
      trigger:
        branches:
          include:
            - main

stages:
  - stage: DeployAfterBuild
    jobs:
      - deployment: Deploy
        environment: 'staging'
        strategy:
          runOnce:
            deploy:
              steps:
                - download: buildPipeline
                  artifact: app
                - script: echo "Deploying build from upstream pipeline"

エージェント利用時の注意点

  1. テンプレートパラメーターの型はコンパイル時に適用されます -- type: boolean が期待される箇所に文字列を渡すと、パイプラインの実行前に検証エラーが発生します。型は必ず正確に一致させてください。
  2. extends テンプレートは上書きできません -- 呼び出し元から、必須ステージの前後にステップを挿入することはできません。これはポリシーを適用するための仕様です。
  3. 変数グループのシークレットはテンプレート式では利用できません -- シークレットがまだ利用可能でないコンパイル時に ${{ variables.mySecret }} を評価すると、解決されません。代わりに $(mySecret) の実行時構文を使用してください。
  4. ${{ each }} はコンパイル時に反復処理されます -- ループはパイプラインの実行前に YAML を生成します。実行時変数を反復処理の対象として使用することはできません。
  5. CI トリガーと PR トリガーは trigger: none および pr: none とは併用できません -- trigger セクションと pr セクションの両方を省略すると、すべてのブランチで既定の CI トリガーが有効になります。無効にするには、明示的に trigger: none を設定してください。
  6. トリガーのパスフィルターは、リポジトリのルートからの相対パスを使用します -- パスの先頭に / や ./ を付けないでください。./src/** ではなく src/** を使用してください。
  7. スケジュールトリガーは、最初に必ず既定のブランチで実行されます -- branches.include フィルターはスケジュールが発火した後に適用されます。スケジュール自体は既定のブランチの YAML からのみ評価されます。
  8. パイプラインリソーストリガーでは、YAML ファイルのパスではなくソースパイプラインの名前が必要です -- azure-pipelines.yml ファイルのパスではなく、ADO に表示されるパイプライン名を使用してください。

コメント