mcpField
D
dotnet-ado-patterns

dotnet-ado-patterns

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

关于 dotnet-ado-patterns

为 .NET 项目提供 Azure DevOps YAML 管道的可组合设计模式,面向需要复用模板、集中配置和构建多阶段流程的开发团队。内容涵盖从模板层级组合到触发器及条件插入。

主要功能

  1. 01组合阶段、作业和步骤模板
  2. 02使用 extends 定义强制管道结构
  3. 03集中管理变量组和变量模板
  4. 04用条件表达式生成管道内容
  5. 05配置多阶段流程与触发器

适用场景

  1. 01跨多个 .NET 管道复用构建步骤
  2. 02为组织管道注入安全与合规阶段
  3. 03按环境条件生成部署流程
  4. 04设置 CI、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 架构。使用 DotNetCoreCLI@2 任务生成 .NET 8/9/10。模板表达式语法 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 关键字会强制使用由组织模板定义的必需管道结构。调用方无法绕过该结构:

# 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 Library 链接变量组:

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"

Agent 注意事项

  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 文件路径 -- 请使用 ADO 中显示的流水线名称,而不是 azure-pipelines.yml 文件路径。

评论

支持 Markdown