Skip to content
Back to Blog
1 min read

Azure DevOps in 2022: New Features and Best Practices

I wrote “Azure DevOps in 2022: New Features and Best Practices” to share practical, production-minded guidance on this topic.

YAML Pipelines Evolution

Multi-stage pipelines with environment approvals:

trigger:
  branches:
    include:
      - main
  paths:
    exclude:
      - docs/**
      - README.md

pool:
  vmImage: 'ubuntu-latest'

variables:
  - group: 'Production-Secrets'
  - name: buildConfiguration
    value: 'Release'

stages:
  - stage: Build
    jobs:
      - job: BuildJob
        steps:
          - task: UseDotNet@2
            inputs:
              version: '6.0.x'

          - task: DotNetCoreCLI@2
            displayName: 'Restore'
            inputs:
              command: 'restore'
              projects: '**/*.csproj'

          - task: DotNetCoreCLI@2
            displayName: 'Build'
            inputs:
              command: 'build'
              arguments: '--configuration $(buildConfiguration)'

          - task: DotNetCoreCLI@2
            displayName: 'Test'
            inputs:
              command: 'test'
              arguments: '--configuration $(buildConfiguration) --collect:"XPlat Code Coverage"'

          - task: PublishCodeCoverageResults@1
            inputs:
              codeCoverageTool: 'Cobertura'
              summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'

          - task: DotNetCoreCLI@2
            displayName: 'Publish'
            inputs:
              command: 'publish'
              arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)'

          - publish: $(Build.ArtifactStagingDirectory)
            artifact: 'drop'

  - stage: DeployDev
    dependsOn: Build
    condition: succeeded()
    jobs:
      - deployment: DeployDev
        environment: 'Development'
        strategy:
          runOnce:
            deploy:
              steps:
                - download: current
                  artifact: 'drop'
                - task: AzureWebApp@1
                  inputs:
                    azureSubscription: 'Azure-Dev'
                    appName: 'myapp-dev'
                    package: '$(Pipeline.Workspace)/drop/**/*.zip'

  - stage: DeployProd
    dependsOn: DeployDev
    condition: succeeded()
    jobs:
      - deployment: DeployProd
        environment: 'Production'
        strategy:
          runOnce:
            deploy:
              steps:
                - download: current
                  artifact: 'drop'
                - task: AzureWebApp@1
                  inputs:
                    azureSubscription: 'Azure-Prod'
                    appName: 'myapp-prod'
                    package: '$(Pipeline.Workspace)/drop/**/*.zip'

Reusable Templates

Create reusable pipeline templates:

# templates/dotnet-build.yml
parameters:
  - name: buildConfiguration
    default: 'Release'
  - name: dotnetVersion
    default: '6.0.x'
  - name: projects
    default: '**/*.csproj'

steps:
  - task: UseDotNet@2
    inputs:
      version: ${{ parameters.dotnetVersion }}

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

  - task: DotNetCoreCLI@2
    displayName: 'Build solution'
    inputs:
      command: 'build'
      projects: ${{ parameters.projects }}
      arguments: '--configuration ${{ parameters.buildConfiguration }} --no-restore'

Use the template:

# azure-pipelines.yml
stages:
  - stage: Build
    jobs:
      - job: Build
        steps:
          - template: templates/dotnet-build.yml
            parameters:
              buildConfiguration: 'Release'
              dotnetVersion: '6.0.x'

Service Connections with Workload Identity

# Using workload identity federation (no secrets!)
- task: AzureCLI@2
  inputs:
    azureSubscription: 'WorkloadIdentityConnection'
    scriptType: 'bash'
    scriptLocation: 'inlineScript'
    inlineScript: |
      az account show
      az webapp list --query "[].name"

Pull Request Validation

# pr-validation.yml
trigger: none

pr:
  branches:
    include:
      - main
      - release/*

jobs:
  - job: Validate
    steps:
      - task: DotNetCoreCLI@2
        displayName: 'Build'
        inputs:
          command: 'build'

      - task: DotNetCoreCLI@2
        displayName: 'Test'
        inputs:
          command: 'test'
          arguments: '--logger trx --results-directory $(Agent.TempDirectory)'

      - task: PublishTestResults@2
        inputs:
          testResultsFormat: 'VSTest'
          testResultsFiles: '**/*.trx'
          searchFolder: '$(Agent.TempDirectory)'

      - task: BuildQualityChecks@8
        inputs:
          checkCoverage: true
          coverageFailOption: 'fixed'
          coverageType: 'lines'
          coverageThreshold: '80'

Artifacts and Feeds

- task: NuGetCommand@2
  displayName: 'Push to feed'
  inputs:
    command: 'push'
    packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg'
    nuGetFeedType: 'internal'
    publishVstsFeed: 'MyOrg/MyFeed'

Azure DevOps in 2022 offers a mature, flexible platform for any DevOps workflow.\n\n## Takeaways\n\nAdd a concise, personal takeaway and recommended next steps here.\n

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.