Loading...
Loading...
Using ADO-exclusive features. Environments, approvals, service connections, classic releases.
npx skill4agent add wshaddix/dotnet-skills dotnet-ado-uniquestages:
- stage: DeployStaging
jobs:
- deployment: DeployToStaging
pool:
vmImage: 'ubuntu-latest'
environment: 'staging'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: app
- script: echo "Deploying to staging"
- stage: DeployProduction
dependsOn: DeployStaging
jobs:
- deployment: DeployToProduction
pool:
vmImage: 'ubuntu-latest'
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: app
- script: echo "Deploying to production"| Check Type | Purpose | Configuration |
|---|---|---|
| Approvals | Manual sign-off before deployment | Assign approver users/groups |
| Branch control | Restrict deployments to specific branches | Allow only |
| Business hours | Deploy only during allowed time windows | Define hours and timezone |
| Template validation | Require pipeline to extend a specific template | Specify required template path |
| Invoke Azure Function | Custom validation via Azure Function | Provide function URL and key |
| Invoke REST API | Custom validation via HTTP endpoint | Provide URL and success criteria |
| Required template | Enforce pipeline structure | Specify required extends template |
# Pipeline YAML -- environment reference triggers checks
- deployment: DeployToProduction
environment: 'production' # checks configured in UI
strategy:
runOnce:
deploy:
steps:
- script: echo "This runs only after all checks pass"# The environment's "Invoke Azure Function" check calls:
# https://myvalidation.azurewebsites.net/api/pre-deploy
# with the pipeline context as payload.
# Returns 200 to approve, non-200 to reject.
- deployment: DeployToProduction
environment: 'production' # Azure Function check configured in UI
strategy:
runOnce:
preDeploy:
steps:
- script: echo "Pre-deploy hook (in-pipeline)"
deploy:
steps:
- script: echo "Deploying"
routeTraffic:
steps:
- script: echo "Routing traffic"
postRouteTraffic:
steps:
- script: echo "Post-route validation"preDeployrouteTrafficpostRouteTraffic| Feature | Deployment Groups | Environments |
|---|---|---|
| Target | Physical/virtual machines with agents | Any target (VMs, Kubernetes, cloud services) |
| Agent model | Self-hosted agents on target machines | Pool agents or target-specific resources |
| Pipeline type | Classic release pipelines (legacy) | YAML multi-stage pipelines (modern) |
| Approvals | Per-stage in classic UI | Checks and approvals on environment |
| Rolling deployment | Built-in rolling strategy | |
| Recommendation | Legacy workloads only | All new projects |
# Classic release pipeline (not YAML) -- for reference only
# Deployment groups are configured in Project Settings > Deployment Groups
# Each target server runs the ADO agent registered to the group- deployment: DeployToK8s
environment: 'production.my-k8s-namespace'
strategy:
runOnce:
deploy:
steps:
- task: KubernetesManifest@1
inputs:
action: 'deploy'
manifests: 'k8s/*.yml'
containers: '$(ACR_LOGIN_SERVER)/myapp:$(Build.BuildId)'deploymentstrategy: rolling- task: AzureWebApp@1
displayName: 'Deploy to Azure App Service'
inputs:
azureSubscription: 'MyAzureServiceConnection'
appType: 'webAppLinux'
appName: 'myapp-staging'
package: '$(Pipeline.Workspace)/app'- task: Docker@2
displayName: 'Login to ACR'
inputs:
command: 'login'
containerRegistry: 'MyACRServiceConnection'
- task: Docker@2
displayName: 'Build and push'
inputs:
command: 'buildAndPush'
containerRegistry: 'MyACRServiceConnection'
repository: 'myapp'
dockerfile: 'src/MyApp/Dockerfile'- task: NuGetCommand@2
displayName: 'Push to nuget.org'
inputs:
command: 'push'
packagesToPush: '$(Pipeline.Workspace)/nupkgs/*.nupkg'
nuGetFeedType: 'external'
publishFeedCredentials: 'NuGetOrgServiceConnection'https://api.nuget.org/v3/index.jsonBuild Pipeline -> Release Pipeline
Stage 1: Dev (auto-deploy)
Stage 2: Staging (manual approval)
Stage 3: Production (scheduled + approval)trigger:
branches:
include:
- main
stages:
- stage: Build
jobs:
- job: BuildJob
pool:
vmImage: 'ubuntu-latest'
steps:
- task: DotNetCoreCLI@2
inputs:
command: 'publish'
projects: 'src/MyApp/MyApp.csproj'
arguments: '-c Release -o $(Build.ArtifactStagingDirectory)/app'
- task: PublishPipelineArtifact@1
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/app'
artifactName: 'app'
- stage: DeployDev
dependsOn: Build
jobs:
- deployment: DeployDev
environment: 'development'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: app
- script: echo "Deploy to dev"
- stage: DeployStaging
dependsOn: DeployDev
jobs:
- deployment: DeployStaging
environment: 'staging' # approvals configured in UI
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: app
- script: echo "Deploy to staging"
- stage: DeployProduction
dependsOn: DeployStaging
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployProduction
environment: 'production' # approvals + business hours in UI
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: app
- script: echo "Deploy to production"download: currentvariables:
- group: 'kv-production-secrets'
- group: 'build-settings'
- name: buildConfiguration
value: 'Release'
steps:
- script: |
echo "Building with configuration $(buildConfiguration)"
displayName: 'Build'
env:
SQL_CONNECTION: $(sql-connection-string) # from Key Vault
API_KEY: $(api-key) # from Key Vault$(secret-name)stages:
- stage: DeployStaging
variables:
- group: 'staging-config'
- group: 'kv-staging-secrets'
jobs:
- deployment: Deploy
environment: 'staging'
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying with staging config"
env:
CONNECTION_STRING: $(sql-connection-string)
- stage: DeployProduction
variables:
- group: 'production-config'
- group: 'kv-production-secrets'
jobs:
- deployment: Deploy
environment: 'production'
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying with production config"
env:
CONNECTION_STRING: $(sql-connection-string)- task: DownloadSecureFile@1
displayName: 'Download signing certificate'
name: signingCert
inputs:
secureFile: 'code-signing.pfx'
- script: |
dotnet nuget sign ./nupkgs/*.nupkg \
--certificate-path $(signingCert.secureFilePath) \
--certificate-password $(CERT_PASSWORD) \
--timestamper http://timestamp.digicert.com
displayName: 'Sign NuGet packages'| Use Case | Implementation |
|---|---|
| Mandatory security scanning | Inject credential scanner before every job |
| Compliance audit logging | Inject telemetry step after every job |
| Required code analysis | Inject SonarQube analysis on main branch builds |
| License compliance | Inject dependency license scanner |
# vss-extension.json (extension manifest)
{
"contributions": [
{
"id": "required-security-scan",
"type": "ms.azure-pipelines.pipeline-decorator",
"targets": ["ms.azure-pipelines-agent-job"],
"properties": {
"template": "decorator.yml",
"targetsExecutionOrder": "PreJob"
}
}
]
}# decorator.yml
steps:
- task: CredentialScanner@1
displayName: '[Policy] Credential scan'
condition: always()- task: UniversalPackages@0
displayName: 'Publish universal package'
inputs:
command: 'publish'
publishDirectory: '$(Build.ArtifactStagingDirectory)/tools'
feedsToUsePublish: 'internal'
vstsFeedPublish: 'MyProject/MyFeed'
vstsFeedPackagePublish: 'my-dotnet-tool'
versionOption: 'custom'
versionPublish: '$(Build.BuildNumber)'
packagePublishDescription: '.NET CLI tool binaries'- task: UniversalPackages@0
displayName: 'Download universal package'
inputs:
command: 'download'
feedsToUse: 'internal'
vstsFeed: 'MyProject/MyFeed'
vstsFeedPackage: 'my-dotnet-tool'
vstsPackageVersion: '*'
downloadDirectory: '$(Pipeline.Workspace)/tools'${{ }}$()