Visual Studio Team Systems (VSTS) yml style builds
Assumptions:
- You are using VSTS for your source control and build systems.
- You have a hosted vs 2017 build
- You have a 2.0 .net core webapp
Yml style builds in VSTS
I don’t like asking someone/something to create a build for my applications. So I was supper excited when i heard Microsoft announce CI builds using yml files “How to use YAML builds”. It is currently in preview mode at the time of writing so make sure you have that preview feature enabled.
-
First cool thing: The yml file resides with your code base on your master branch. This allows gives you all the advantages of version control.
-
Second cool thing: Builds are kicked off automatically on check in to master branch (hello CI/CD I love you so)
First things first…
- create a file named .vsts-ci.yml it has to specifically be named this.
- It also has to reside in the root folder of the repo in the master branch (just like the git ignore file)
- deployment steps require a Service Endpoint to be created in VSTS under Default Team Settings » Services » New Service Endpoint for your Azure subscription information mine is named
ConnectedServiceName: "Azure1"
- multiple phases hasn’t been implemented yet.
- I have full debug turned on with the setting
"system.debug" : "true"
This is an example of CI/CD yml file (all the way to prod)
- extracts all environment variables to a mark down file and attaches it to build output
- nuget restore
- Donet restore - we had issues where donet restore wouldnt bring back all the packages needed thus we have both nuget and dotnet YMMV
- Compiles and packages the app for msdeploy style deployment
- runs all unit tests (you did do TDD for this app right)
- Stops staging slot in the webapp (with a 1 minute time out)
- Then we stop any webjobs that are running (app insights doesnt like to give up on us, so we forcefully stop the webjobs)
- Finally we can deploy the app
- We force app insights just in case someone forgot the nuget packages in the app
- Start the webjobs, and the site again
- then we swap the slots for the TESTing environment (allows for zero down time deployments)
- All integration tests are then run
- Prod deployment follows the same steps as test including the swap slots step.
- we then publish the pdb’s so we can debug if we need to.
- we publish the artifacts (basically everything in the deployment directory)
- My existing client wants all the artifacts published to Artifactory as well for their binary repository.
Example file,
.vsts-ci.yml
———-
phases:
- phase: Build
displayName: Build
variables:
"ApplicationBaseName" : "<APPNAME>"
"ApplicationType" : "<APPTYPE>"
"ApplicationTestDllPathUnit" : "**/*.Test.Unit.dll"
"ApplicationFrameWorkVersion" : "/framework:.NETCoreApp,Version=v2.0"
"ApplicationTestRunSettingsPath" : "Test.RunSettings"
"ApplicationBuildConfiguration" : "release"
"ApplicationBuildPlatform" : "Any CPU"
"ApplicationBlockCoverageThreshold": "80"
#Standard Names
"StandardNameTestPrefix" : "<COMPANY>-TEST-"
"StandardNameProdPrefix" : "<COMPANY>-PROD-"
"StandardNameResourceGroupSuffix" : "-rsg"
"StandardNameSourceSlot": "staging"
#Naming Formats
"FormattedAppName" : "$(ApplicationType)-$(ApplicationBaseName)"
"FormattedAppNameTestEnv": "$(StandardNameTestPrefix)$(FormattedAppName)"
"FormattedRsgNameTestEnv": "$(FormattedAppNameTestEnv)$(StandardNameResourceGroupSuffix)"
"FormattedAppNameProdEnv": "$(StandardNameProdPrefix)$(FormattedAppName)"
"FormattedRsgNameProdEnv": "$(FormattedAppNameProdEnv)$(StandardNameResourceGroupSuffix)"
#System Variables
"SystemPackageSystemUri" : "https://api.nuget.org"
"SystemPackageRemoteRepo" : "/v3/index.json"
"SystemPackageBaseTargetRepo" : "<COMPANY>-app"
"SystemVstestPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Enterprise\\Common7\\IDE\\Extensions\\TestPlatform\\"
"SystemTestDeployedUrl" : "TestDeployedUrl"
"SystemProdDeployedUrl" : "ProdDeployedUrl"
#Full Debug Logging
"system.debug" : "true"
queue:
name: "Hosted VS2017"
demands:
- msbuild
- visualstudio
- vstest
steps:
- task: PowerShell@2
displayName: List Environment Variables
inputs:
targetType: "inline"
script: |
Write-Host "This agent is running PowerShell v$($PSVersionTable.PSVersion.Major)"
$var = (gci env:*).GetEnumerator() | Sort-Object Name
$out = ""
Foreach ($v in $var) {$out = $out + "`t{0,-28} = {1,-28}`n" -f $v.Name, $v.Value}
write-output "dump variables on $env:BUILD_ARTIFACTSTAGINGDIRECTORY\environnment.md"
$fileName = "$env:AGENT_TEMPDIRECTORY\Environment.md"
set-content $fileName $out
write-output "##vso[task.addattachment type=Distributedtask.Core.Summary;name=Environment Variables;]$fileName"
errorActionPreference: "continue"
ignoreLASTEXITCODE: "true"
- task: NuGetToolInstaller@0
displayName: Install Nuget
inputs:
timeoutInMinutes: 1
- task: NuGetCommand@2
displayName: NuGet Restore
inputs:
arguments: 'restore -Source "$(SystemPackageSystemUri)$(SystemPackageRemoteRepo)"'
command: custom
includeNuGetOrg: "false"
vstsFeed: $(SystemPackageSystemUri)
- task: DotNetCoreCLI@1
displayName: DotNet Restore
inputs:
command: "restore"
projects: '**\*.csproj'
arguments: "-s $(SystemPackageSystemUri)$(SystemPackageRemoteRepo)"
- task: VSBuild@1
displayName: Build Solution
inputs:
configuration: "$(ApplicationBuildConfiguration)"
platform: "$(ApplicationBuildPlatform)"
msbuildArgs: "/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:PackageLocation=$(build.artifactstagingdirectory) /m"
solution: '**\*.sln'
- task: VSTest@2
displayName: Unit Tests
inputs:
codeCoverageEnabled: "true"
configuration: $(ApplicationBuildConfiguration)
platform: $(ApplicationBuildPlatform)
otherConsoleOptions: $(ApplicationFrameWorkVersion)
runInParallel: "true"
vstestLocation: $(SystemVstestPath)
vstestLocationMethod: location
testAssemblyVer2: |
$(ApplicationTestDllPathUnit)
!**/*TestAdapter.dll
!**/\obj\**
runSettingsFile: $(ApplicationTestRunSettingsPath)
#- phase: Deployment
# displayName: Deployment and Integration Test
# dependsOn: Build
# condition: and(succeeded(), eq(variables['build.sourceBranch'], 'refs/heads/master'))
# steps:
#Stop App Service
- task: AzureAppServiceManage@0
displayName: Stop App Service Test
continueOnError: true
inputs:
ConnectedServiceName: "Azure1"
Action: "Stop Azure App Service"
WebAppName: $(FormattedAppNameTestEnv)
SpecifySlot: "true"
ResourceGroupName: "$(FormattedRsgNameTestEnv)"
Slot: "$(StandardNameSourceSlot)"
timeoutInMinutes: 1
#Stop Webjobs Service
- task: AzureAppServiceManage@0
displayName: Stop Any Webjobs Service Test
continueOnError: true
inputs:
ConnectedServiceName: "Azure1"
Action: "Stop all continuous webjobs"
WebAppName: $(FormattedAppNameTestEnv)
SpecifySlot: "true"
ResourceGroupName: "$(FormattedRsgNameTestEnv)"
Slot: "$(StandardNameSourceSlot)"
timeoutInMinutes: 2
#Deploy
- task: AzureRmWebAppDeployment@3
displayName: Deploy Test
inputs:
ConnectedServiceName: Azure1
WebAppName: $(FormattedAppNameTestEnv)
DeployToSlotFlag: "true"
ResourceGroupName: $(FormattedRsgNameTestEnv)
SlotName: $(StandardNameSourceSlot)
WebAppUri: $(SystemTestDeployedUrl)
Package : "$(Build.ArtifactStagingDirectory)/**/*.zip"
TakeAppOfflineFlag: "true"
UseWebDeploy: "true"
RemoveAdditionalFilesFlag: "true"
AdditionalArguments: "-skip:objectName=filePath,absolutePath=parameters.xml -retryAttempts:20"
ExcludeFilesFromAppDataFlag: "true"
#Start App Service
- task: AzureAppServiceManage@0
displayName: Start App Service Test
inputs:
ConnectedServiceName: "Azure1"
Action: "Start Azure App Service"
WebAppName: $(FormattedAppNameTestEnv)
SpecifySlot: "true"
ResourceGroupName: "$(FormattedRsgNameTestEnv)"
Slot: "$(StandardNameSourceSlot)"
#Ensure App Insights - May restart site
- task: AzureAppServiceManage@0
displayName: Ensure App Insights Installed
inputs:
ConnectedServiceName: "Azure1"
Action: "Install Extensions"
WebAppName: "$(FormattedAppNameTestEnv)"
SpecifySlot: "true"
ResourceGroupName: "$(FormattedRsgNameTestEnv)"
Slot: "$(StandardNameSourceSlot)"
ExtensionsList: "Microsoft.ApplicationInsights.AzureWebSites"
#Start Web Jobs
- task: AzureAppServiceManage@0
displayName: Start Web Jobs Test
inputs:
ConnectedServiceName: "Azure1"
Action: "Start all continuous webjobs"
WebAppName: $(FormattedAppNameTestEnv)
SpecifySlot: "true"
ResourceGroupName: "$(FormattedRsgNameTestEnv)"
Slot: "$(StandardNameSourceSlot)"
#Swap slots
- task: AzureAppServiceManage@0
displayName: Swap Slot Test
inputs:
ConnectedServiceName: Azure1
WebAppName: $(FormattedAppNameTestEnv)
ResourceGroupName: $(FormattedRsgNameTestEnv)
SourceSlot: $(StandardNameSourceSlot)
#Integration Tests
- task: VSTest@2
displayName: Integration Tests
inputs:
codeCoverageEnabled: "true"
configuration: $(ApplicationBuildConfiguration)
platform: $(ApplicationBuildPlatform)
otherConsoleOptions: $(ApplicationFrameWorkVersion)
runInParallel: "false"
vstestLocation: $(SystemVstestPath)
vstestLocationMethod: location
testAssemblyVer2: |
$(ApplicationTestDllPathIntegration)
!**/*TestAdapter.dll
!**/\obj\**
runSettingsFile: $(ApplicationTestRunSettingsPath)
#- phase: ProdDeployment
#Stop App Service
- task: AzureAppServiceManage@0
displayName: Stop App Service Prod
continueOnError: true
inputs:
ConnectedServiceName: "Azure1"
Action: "Stop Azure App Service"
WebAppName: $(FormattedAppNameProdEnv)
SpecifySlot: "true"
ResourceGroupName: "$(FormattedRsgNameProdEnv)"
Slot: "$(StandardNameSourceSlot)"
timeoutInMinutes: 1
#Stop Webjobs Service
- task: AzureAppServiceManage@0
displayName: Stop Any Webjobs Service Prod
continueOnError: true
inputs:
ConnectedServiceName: "Azure1"
Action: "Stop all continuous webjobs"
WebAppName: $(FormattedAppNameProdEnv)
SpecifySlot: "true"
ResourceGroupName: "$(FormattedRsgNameProdEnv)"
Slot: "$(StandardNameSourceSlot)"
timeoutInMinutes: 2
#Deploy
- task: AzureRmWebAppDeployment@3
displayName: Deploy Prod
inputs:
ConnectedServiceName: Azure1
WebAppName: $(FormattedAppNameProdEnv)
DeployToSlotFlag: "true"
ResourceGroupName: $(FormattedRsgNameProdEnv)
SlotName: $(StandardNameSourceSlot)
WebAppUri: $(SystemProdDeployedUrl)
Package : "$(Build.ArtifactStagingDirectory)/**/*.zip"
TakeAppOfflineFlag: "true"
UseWebDeploy: "true"
RemoveAdditionalFilesFlag: "true"
AdditionalArguments: "-skip:objectName=filePath,absolutePath=parameters.xml -retryAttempts:20"
ExcludeFilesFromAppDataFlag: "true"
#Start App Service
- task: AzureAppServiceManage@0
displayName: Start App Service Prod
inputs:
ConnectedServiceName: "Azure1"
Action: "Start Azure App Service"
WebAppName: $(FormattedAppNameProdEnv)
SpecifySlot: "true"
ResourceGroupName: "$(FormattedRsgNameProdEnv)"
Slot: "$(StandardNameSourceSlot)"
#Ensure App Insights - May restart site
- task: AzureAppServiceManage@0
displayName: Ensure App Insights Installed
inputs:
ConnectedServiceName: "Azure1"
Action: "Install Extensions"
WebAppName: "$(FormattedAppNameProdEnv)"
SpecifySlot: "true"
ResourceGroupName: "$(FormattedRsgNameProdEnv)"
Slot: "$(StandardNameSourceSlot)"
ExtensionsList: "Microsoft.ApplicationInsights.AzureWebSites"
#Start Web Jobs
- task: AzureAppServiceManage@0
displayName: Start Web Jobs Prod
inputs:
ConnectedServiceName: "Azure1"
Action: "Start all continuous webjobs"
WebAppName: $(FormattedAppNameProdEnv)
SpecifySlot: "true"
ResourceGroupName: "$(FormattedRsgNameProdEnv)"
Slot: "$(StandardNameSourceSlot)"
#Swap slots
- task: AzureAppServiceManage@0
displayName: Swap Slot Prod
inputs:
ConnectedServiceName: Azure1
WebAppName: $(FormattedAppNameProdEnv)
ResourceGroupName: $(FormattedRsgNameProdEnv)
SourceSlot: $(StandardNameSourceSlot)
#- phase: PublishArtifacts
- task: PublishSymbols@1
displayName: Publishing Symbols
inputs:
SymbolsPath: "$(build.artifactstagingdirectory)"
SearchPattern: '**\bin\**\*.pdb'
- task: PublishBuildArtifacts@1
displayName: Artifacts for TFS
inputs:
PathtoPublish: '$(build.artifactstagingdirectory)'
ArtifactName: "drop"
ArtifactType: "Container"
- task: ArchiveFiles@1
displayName: Zipping Files
inputs:
rootFolder: "$(build.artifactstagingdirectory)"
includeRootFolder: "false"
archiveFile: '$(Build.ArtifactStagingDirectory)\$(Build.BuildNumber).zip'
- task: JFrog.jfrog-artifactory.jfrog-artifactory-deployer-build-task.JFrogArtifactoryDeployer@2
displayName: Artifactory Upload
inputs:
artifactoryEndpointName: Artifactory
targetRepo: '$(SystemPackageBaseTargetRepo)/$(Build.Repository.Name)/$(Build.BuildNumber).zip'
contents: '$(Build.ArtifactStagingDirectory)\$(Build.BuildNumber).zip'
# Schema: https://github.com/Microsoft/vsts-agent/blob/master/docs/preview/yamlgettingstarted-schema.md