mcpField
D
dotnet-ado-publish

dotnet-ado-publish

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

关于 dotnet-ado-publish

面向 .NET 团队的 Azure DevOps 发布流水线技能,涵盖 NuGet 包、容器镜像和构建产物的发布。内容包括 Azure Artifacts、nuget.org、ACR 与多阶段流水线的配置方式。

主要功能

  1. 01推送 NuGet 包到 Azure Artifacts
  2. 02发布包到 nuget.org
  3. 03构建并推送容器镜像到 ACR
  4. 04暂存和发布流水线产物
  5. 05在多阶段流水线间传递产物

适用场景

  1. 01从 Azure DevOps 发布 NuGet 包
  2. 02将容器镜像推送到 ACR
  3. 03为多阶段发布流程传递构建产物

安装

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

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

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

详细介绍

dotnet-ado-publish

Azure DevOps 中 .NET 项目的发布流水线:将 NuGet 包推送到 Azure Artifacts 和 nuget.org,使用 Docker@2 构建容器镜像并推送到 Azure Container Registry (ACR),使用 PublishBuildArtifacts@1 和 PublishPipelineArtifact@1 暂存构建产物,以及在多阶段发布流水线中使用流水线构建产物。

版本假设: 使用 DotNetCoreCLI@2 执行 pack/push 操作。使用 Docker@2 构建容器镜像。使用 NuGetCommand@2 将 NuGet 包推送到外部源。使用 PublishPipelineArtifact@1(优先于 PublishBuildArtifacts@1)。

范围边界: 此技能负责从 Azure DevOps 发布构建产物的流水线。容器镜像编写(Dockerfile 最佳实践、SDK 容器属性)由 [skill:dotnet-containers] 负责。Native AOT 编译配置由 [skill:dotnet-native-aot] 负责——此技能仅涉及 CI 流水线配置中的 AOT。CLI 专用发布流水线由 [skill:dotnet-cli-release-pipeline] 负责。入门 CI 模板由 [skill:dotnet-add-ci] 负责。

范围外: 容器镜像编写(Dockerfile、基础镜像选择)——请参阅 [skill:dotnet-containers]。Native AOT MSBuild 配置——请参阅 [skill:dotnet-native-aot]。CLI 发布流水线——请参阅 [skill:dotnet-cli-release-pipeline]。入门 CI 模板——请参阅 [skill:dotnet-add-ci]。GitHub Actions 发布——请参阅 [skill:dotnet-gha-publish]。ADO 专属功能(环境、服务连接)——请参阅 [skill:dotnet-ado-unique]。

交叉引用:[skill:dotnet-containers](容器镜像编写和 SDK 容器属性)、[skill:dotnet-native-aot](CI 中的 AOT 发布配置)、[skill:dotnet-cli-release-pipeline](CLI 专用发布自动化)、[skill:dotnet-add-ci](入门发布模板)。


将 NuGet 包推送到 Azure Artifacts

使用 DotNetCoreCLI@2 推送
trigger:
  tags:
    include:
      - 'v*'

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

          - task: DotNetCoreCLI@2
            displayName: 'Pack'
            inputs:
              command: 'pack'
              packagesToPack: 'src/**/*.csproj'
              configuration: 'Release'
              outputDir: '$(Build.ArtifactStagingDirectory)/nupkgs'
              versioningScheme: 'byEnvVar'
              versionEnvVar: 'PACKAGE_VERSION'
            env:
              PACKAGE_VERSION: $(Build.SourceBranchName)

          - task: PublishPipelineArtifact@1
            displayName: 'Upload NuGet packages'
            inputs:
              targetPath: '$(Build.ArtifactStagingDirectory)/nupkgs'
              artifactName: 'nupkgs'

  - stage: PushToFeed
    dependsOn: Pack
    jobs:
      - job: PushJob
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - download: current
            artifact: nupkgs

          - task: NuGetAuthenticate@1
            displayName: 'Authenticate NuGet'

          - task: DotNetCoreCLI@2
            displayName: 'Push to Azure Artifacts'
            inputs:
              command: 'push'
              packagesToPush: '$(Pipeline.Workspace)/nupkgs/*.nupkg'
              nuGetFeedType: 'internal'
              publishVstsFeed: 'MyProject/MyFeed'
从 Git 标签获取版本

使用脚本步骤从触发流水线的 Git 标签中提取版本。Build.SourceBranch 是运行时变量,因此请使用脚本解析,而不是使用编译时模板表达式:

steps:
  - script: |
      set -euo pipefail
      if [[ "$(Build.SourceBranch)" == refs/tags/v* ]]; then
        VERSION="${BUILD_SOURCEBRANCH#refs/tags/v}"
      else
        VERSION="0.0.0-ci.$(Build.BuildId)"
      fi
      echo "##vso[task.setvariable variable=packageVersion]$VERSION"
    displayName: 'Extract version from tag'

  - task: DotNetCoreCLI@2
    displayName: 'Pack'
    inputs:
      command: 'pack'
      packagesToPack: 'src/**/*.csproj'
      configuration: 'Release'
      outputDir: '$(Build.ArtifactStagingDirectory)/nupkgs'
      arguments: '-p:Version=$(packageVersion)'

将 NuGet 包推送到 nuget.org

使用 NuGetCommand@2 推送

要将 NuGet 包推送到外部源(nuget.org),请使用服务连接:

- task: NuGetCommand@2
  displayName: 'Push to nuget.org'
  inputs:
    command: 'push'
    packagesToPush: '$(Pipeline.Workspace)/nupkgs/*.nupkg'
    nuGetFeedType: 'external'
    publishFeedCredentials: 'NuGetOrgServiceConnection'

服务连接会安全地存储 nuget.org API 密钥。在“项目设置” > “服务连接” > “NuGet”中创建服务连接。

根据条件推送(稳定版与预发布版)
- task: NuGetCommand@2
  displayName: 'Push to nuget.org (stable only)'
  condition: and(succeeded(), not(contains(variables['packageVersion'], '-')))
  inputs:
    command: 'push'
    packagesToPush: '$(Pipeline.Workspace)/nupkgs/*.nupkg'
    nuGetFeedType: 'external'
    publishFeedCredentials: 'NuGetOrgServiceConnection'

- task: DotNetCoreCLI@2
  displayName: 'Push to Azure Artifacts (all versions)'
  inputs:
    command: 'push'
    packagesToPush: '$(Pipeline.Workspace)/nupkgs/*.nupkg'
    nuGetFeedType: 'internal'
    publishVstsFeed: 'MyProject/MyFeed'

预发布版本(包含 -,如 1.2.3-preview.1)仅推送到 Azure Artifacts;稳定版本则推送到两个源。

跳过重复包
- task: DotNetCoreCLI@2
  displayName: 'Push (skip duplicates)'
  inputs:
    command: 'push'
    packagesToPush: '$(Pipeline.Workspace)/nupkgs/*.nupkg'
    nuGetFeedType: 'internal'
    publishVstsFeed: 'MyProject/MyFeed'
  continueOnError: true  # Azure Artifacts returns 409 for duplicates

Azure Artifacts 会对重复的包版本返回 HTTP 409。可使用 continueOnError: true 以支持流水线幂等重跑,或在源设置中配置允许覆盖预发布版本。


构建容器镜像并推送到 ACR

Docker@2 任务

构建容器镜像并推送到 Azure Container Registry。Dockerfile 编写指南请参阅 [skill:dotnet-containers]:

stages:
  - stage: BuildContainer
    jobs:
      - job: DockerBuild
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - task: Docker@2
            displayName: 'Login to ACR'
            inputs:
              command: 'login'
              containerRegistry: 'MyACRServiceConnection'

          - task: Docker@2
            displayName: 'Build and push'
            inputs:
              command: 'buildAndPush'
              repository: 'myapp'
              containerRegistry: 'MyACRServiceConnection'
              dockerfile: 'src/MyApp/Dockerfile'
              buildContext: '.'
              tags: |
                $(Build.BuildId)
                latest
标签策略
- task: Docker@2
  displayName: 'Build and push with semver tags'
  inputs:
    command: 'buildAndPush'
    repository: 'myapp'
    containerRegistry: 'MyACRServiceConnection'
    dockerfile: 'src/MyApp/Dockerfile'
    buildContext: '.'
    tags: |
      $(packageVersion)
      $(Build.SourceVersion)
      latest

发布镜像使用语义化版本标签,便于追踪的镜像使用提交 SHA 标签。latest 标签仅应应用于稳定版发布。

使用 SDK 发布容器(无需 Dockerfile)

对于没有 Dockerfile 的项目,可使用 .NET SDK 发布容器。PublishContainer MSBuild 配置请参阅 [skill:dotnet-containers]:

- task: Docker@2
  displayName: 'Login to ACR'
  inputs:
    command: 'login'
    containerRegistry: 'MyACRServiceConnection'

- task: UseDotNet@2
  inputs:
    packageType: 'sdk'
    version: '8.0.x'

- script: |
    dotnet publish src/MyApp/MyApp.csproj \
      -c Release \
      -p:PublishProfile=DefaultContainer \
      -p:ContainerRegistry=$(ACR_LOGIN_SERVER) \
      -p:ContainerRepository=myapp \
      -p:ContainerImageTags='"$(packageVersion);latest"'
  displayName: 'Publish container via SDK'
  env:
    ACR_LOGIN_SERVER: $(acrLoginServer)
发布 Native AOT 容器

将 Native AOT 二进制文件发布为容器镜像。AOT 配置由 [skill:dotnet-native-aot] 负责;此处仅展示 CI 流水线步骤:

- script: |
    dotnet publish src/MyApp/MyApp.csproj \
      -c Release \
      -r linux-x64 \
      -p:PublishAot=true \
      -p:PublishProfile=DefaultContainer \
      -p:ContainerRegistry=$(ACR_LOGIN_SERVER) \
      -p:ContainerRepository=myapp \
      -p:ContainerBaseImage=mcr.microsoft.com/dotnet/runtime-deps:8.0-noble-chiseled \
      -p:ContainerImageTags='"$(packageVersion)"'
  displayName: 'Publish AOT container'

AOT 二进制文件已包含运行时,因此使用 runtime-deps 基础镜像即可。AOT MSBuild 属性请参阅 [skill:dotnet-native-aot],基础镜像选择请参阅 [skill:dotnet-containers]。


构建产物暂存

PublishPipelineArtifact@1(推荐)

流水线构建产物是新一代构建产物,可提供更快的上传和下载速度,并支持重复数据删除:

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

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

  - task: PublishPipelineArtifact@1
    displayName: 'Upload NuGet packages'
    inputs:
      targetPath: '$(Build.ArtifactStagingDirectory)/nupkgs'
      artifactName: 'nupkgs'
PublishBuildArtifacts@1(旧版)

仅在与需要生成构建项目的经典发布管道集成时使用:

- task: PublishBuildArtifacts@1
  displayName: 'Upload build artifact (legacy)'
  inputs:
    pathToPublish: '$(Build.ArtifactStagingDirectory)/app'
    artifactName: 'app'
    publishLocation: 'Container'
在下游阶段下载项目
stages:
  - stage: Build
    jobs:
      - job: BuildJob
        steps:
          - script: dotnet publish -c Release -o $(Build.ArtifactStagingDirectory)/app
          - task: PublishPipelineArtifact@1
            inputs:
              targetPath: '$(Build.ArtifactStagingDirectory)/app'
              artifactName: 'app'

  - stage: Deploy
    dependsOn: Build
    jobs:
      - deployment: DeployJob
        environment: 'staging'
        strategy:
          runOnce:
            deploy:
              steps:
                - download: current
                  artifact: app
                - script: echo "Deploying from $(Pipeline.Workspace)/app"

download: current 关键字会从当前管道运行中下载项目。对于来自其他管道的项目,请使用 download: pipelineName。


用于发布管道的管道项目

使用项目提升的多阶段发布
trigger:
  tags:
    include:
      - 'v*'

stages:
  - stage: Build
    jobs:
      - job: BuildAndPack
        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: DotNetCoreCLI@2
            displayName: 'Pack'
            inputs:
              command: 'pack'
              packagesToPack: 'src/MyLibrary/MyLibrary.csproj'
              configuration: 'Release'
              outputDir: '$(Build.ArtifactStagingDirectory)/nupkgs'

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

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

  - stage: DeployStaging
    dependsOn: Build
    jobs:
      - deployment: DeployStaging
        environment: 'staging'
        pool:
          vmImage: 'ubuntu-latest'
        strategy:
          runOnce:
            deploy:
              steps:
                - download: current
                  artifact: app
                - script: echo "Deploying to staging from $(Pipeline.Workspace)/app"

  - stage: PublishPackages
    dependsOn: DeployStaging
    jobs:
      - job: PushPackages
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - download: current
            artifact: nupkgs

          - task: NuGetAuthenticate@1

          - task: NuGetCommand@2
            displayName: 'Push to nuget.org'
            inputs:
              command: 'push'
              packagesToPush: '$(Pipeline.Workspace)/nupkgs/*.nupkg'
              nuGetFeedType: 'external'
              publishFeedCredentials: 'NuGetOrgServiceConnection'

  - stage: DeployProduction
    dependsOn: DeployStaging
    condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
    jobs:
      - deployment: DeployProduction
        environment: 'production'
        pool:
          vmImage: 'ubuntu-latest'
        strategy:
          runOnce:
            deploy:
              steps:
                - download: current
                  artifact: app
                - script: echo "Deploying to production from $(Pipeline.Workspace)/app"
跨管道使用项目

使用来自其他管道(例如共享构建管道)的项目:

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

stages:
  - stage: Deploy
    jobs:
      - deployment: DeployFromBuild
        environment: 'staging'
        strategy:
          runOnce:
            deploy:
              steps:
                - download: buildPipeline
                  artifact: app
                - script: echo "Deploying from $(Pipeline.Workspace)/buildPipeline/app"

Agent 注意事项

  1. 优先使用 PublishPipelineArtifact@1,而不是 PublishBuildArtifacts@1 ——管道项目速度更快、支持重复数据删除,并且适用于多阶段 YAML 管道;构建项目已过时,仅经典发布管道需要使用。
  2. Azure Artifacts 遇到重复的包版本时会返回 409 ——可使用 continueOnError: true 支持幂等重跑,或在源设置中允许覆盖预发布版本,以处理重复项。
  3. NuGetCommand@2 使用 external 源类型时需要服务连接 ——不要在管道 YAML 中硬编码 API 密钥;请在“项目设置”中创建 NuGet 服务连接,以安全存储密钥。
  4. SDK 容器发布需要 Agent 上安装 Docker ——使用 PublishProfile=DefaultContainer 的 dotnet publish 需要 Docker;托管的 ubuntu-latest Agent 已包含 Docker,但可自托管的 Agent 可能没有。
  5. AOT 发布要求 RID 匹配 ——dotnet publish -r linux-x64 必须与 Agent 操作系统匹配;不要在 Linux Agent 上使用 -r win-x64。
  6. download: current 使用的是 $(Pipeline.Workspace),而非 $(Build.ArtifactStagingDirectory) ——在部署作业中下载的项目位于 $(Pipeline.Workspace)/artifactName,而不是暂存目录。
  7. 切勿在管道 YAML 中硬编码注册表凭据 ——使用 Docker 服务连接进行 ACR/DockerHub 身份验证;服务连接会安全存储凭据,并可单独轮换。
  8. 标记触发器需要在触发器部分显式设置 tags.include ——默认 CI 触发器不包含标记;添加 tags: include: ['v*'] 以在版本标记上触发。

评论

支持 Markdown