Friday, December 15, 2017

Automated Builds and Deployments using TFS 2017 Release Management

TFS Release Management (RM) is Microsoft’s answer for DevOps. It can help you build, deploy and test your applications targeting various platforms and environments. RM also has a mini workflow approval process baked in for release automation.

Since last few weeks, I have been working on web app deployment using Visual Studio and Team Foundation Server 2017. I developed a solution and curated a number of resources that may provide some value to you.

Environment Setup

The environment we have in place is TFS 2017 Update 1, SQL Server 2016, and Visual Studio 2017. All of them in a virtual environment.

Overview

Conceptually you are touching three areas of TFS – source control, build and release. Here I have followed the tokenization approach instead of config transformation. So instead of creating multiple config transformations per environment, I opted to parametrize the configuration. This tokenized config lives in TFS version control system. TFS build then kicks in and produces a single WebDeploy (tokenized) package which can be deployed to various environments. The build is environment agnostic. TFS release management deploys the package using WebDeploy after replacing environment values for the tokens. This whole process ensures that deployed binary is same across environments.

TFS-Azure connection setup

  1. Setup and configure an Azure Resource Manager (ARM) service endpoint in your TFS environment.
  2. Make sure you are a member of project's "Endpoint Creators" group (if not Project Collection Administrators).
  3. Make sure you are part of “UserAccessAdmin” group defined by Azure. You can read more here: https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal 
  4. Open TFS web access and select the project's dashboard. 
  5. Click the Gear icon and select "Services" from the drop-down menu 
  6. Click "New Service Endpoint" and select "Azure Resource Manager" from the drop-down
  7. Enter the details in the Service Endpoint dialog as generated by PowerShell script. Read more here https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authenticate-service-principal
  8. Make sure verification is successful. Click OK.

TFS Version control setup


Create a publish profile for your web app

  1. Right-click web project in Solution Explorer and select Publish
  2. Click "Create new profile"
  3. Select "IIS, FTP etc." and click OK
  4. Select "Web Deploy Package" as publish method. Enter "WebDeployPkg" or any meaningful name for Package location
  5. Leave Site name blank
  6. Click Next
  7. Select Release from Configuration
  8. Leave File Publish Options unchecked
  9. Select "Use this connection string at runtime" checkbox for all Databases connection strings with custom placeholder values. I have prefixed a double underscore to the connection string name and also suffixed the same with double underscore too. For instance, in my web.config I have a connection string as AuthContext which becomes “__AuthContextStr__”
  10. Click Save
  11. Rename "CustomProfile" (newly created as above) as "WebPackage-Release" or any meaningful name.

Create Parameters file for your web app

In order to parameterize configurations defined in web.config other than connection strings, you need to use Parameters.xml file. This should be named exactly the same and must be located at the root level of your web project. MSBuild uses this parameters file to expose parameters/tokens. Please note MSBuild doesn’t fill in those tokens, it is MSDeploy which does that job. 
Here is a sample parameter.xml file:


Also, you can set the Build action of this parameters.xml file to “None”. This is needed to avoid the “Parameters.xml” getting deployed with your web application.
To test your solution, right-click and publish the web project using the publish profile created above (WebPackage-Release). Open the SetParameters.xml file, you should see tokens for the appSettings and the connection strings. Also, if you look into the zip file and view the web.config file, you’ll see there are placeholders for the connection strings. WebDeploy will inject the correct values for your appSettings and connection strings at deploy time.

TFS Build Setup

  1. Create an empty TFS build definition.
  2. Select $/{TfsProject} from repository dropdown
  3. Select "Continuous integration" checkbox
  4. Select "{PoolName}" from queue list
  5. Click Create
  6. Build
    1. Nuget Restore
      1. Click "Add build step" > Package > Nuget Installer > Add. Click Close.
      2. Enter "{SolutionName}.sln" in Path to solution for packages.config field
      3. Select Restore from Installation type
      4. Edit task name as "Restore NuGet packages"
    2. Build
      1. Click "Add build step" > Build > MSBuild > Add. Click Close.
      2. Click … next to Project field and select $/{TfsProject}/Dev/{SolutionName}.sln
      3. Enter "/p:RunCodeAnalysis=true /p:DeployOnBuild=true /p:PublishProfile=Release /p:PackageLocation=$(build.artifactstagingdirectory)" in MSBuild arguments field
      4. Enter C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\MSBuild.exe for MSBuild location under Advanced.
      5. Edit task name as "Build solution"
    3. Publish to staging
      1. Click "Add build step" > Utility > Publish Build Artifacts > Add. Click Close.
      2. Enter $(build.artifactstagingdirectory) for Path to Publish field
      3. Enter drop for Artifact Name field
      4. Select "Server" from Artifact Type drop-down
      5. Edit task name as "Publish artifacts to staging"
    4. Publish to DFS
      1. Click "Add build step" > Utility > Publish Build Artifacts > Add. Click Close.
      2. Enter $(build.artifactstagingdirectory) for Path to Publish field
      3. Enter PublishedOutput for Artifact Name field
      4. Select "File share" from Artifact Type dropdown
      5. Enter \\{NetworkPath}\$(Build.DefinitionName)\$(Build.BuildNumber) in Path field
      6. Edit task name as "Publish artifacts to DFS build drop location"
    5. UpdateTfsBuildQuality
      1. Click "Add build step" > Utility > PowerShell (run a PowerShell script) > Add. Click Close.
      2. Select "File Path" from Type drop-down.
      3. Enter UpdateTfsBuildQuality-v2.0.ps1 for Script Path field
      4. Enter -BuildQuality 'ReadyForDeployment' for Arguments field.
      5. Edit task name as "UpdateTfsBuildQuality"
  7. Options
    1. Create Work Item on Failure
      1. Type: Bug
      2. Assign to requestor: selected
      3. Additional Fields
        1. System.Title: Build $(Build.BuildNumber) failed
        2. System.Reason: Build failure
  8. Repository
    1. Repository type: Team Foundation Version Control
    2. Repository name: {TfsProject}
    3. Label sources: On successful build
    4. Label format: $(build.buildNumber)
    5. Clean: true
    6. Clean options: All build directories
    7. Mappings
      1. Map: $/{TfsProject}/Dev with Local Path:$(build.sourcesDirectory)\
  9. Variables
  10. system.debug false
    BuildConfiguration Release
    BuildPlatform any CPU
    BuildDropLocation \\{DFS}\$(ProductCode)\$(Build.DefinitionName)\$(Build.BuildNumber)
    SharedDeploymentResourcesLocation \\{DFSPath}\ReleaseManagement
  11. Triggers
    1. Select Continuous Integration
    2. Select Batch changes
    3. Path Filters: Include $/{TfsProject}/Dev
  12. General
    1. Default agent queue: {PoolName}
    2. Build job authorization scope: Current Project
    3. Description: Dev Continuous Integration Build
    4. Build number format: $(BuildDefinitionName)_$(Date:yyyyMMdd)$(Rev:.rr)
    5. Build job timeout in minutes: 10
    6. Badge enabled: selected
    7. Demands
      1. BuildType Equals CI
      2. AgentType Equals BuildOnly
  13. Retention
    1. Days to keep: 15
    2. Min to keep: 3
    3. Delete build record: selected
    4. Delete source label: unchecked
    5. Delete file share: selected
    6. Delete symbols: selected
    7. Delete test results: selected
Run the build. When complete, the build drop should contain the site zip and SetParameters.xml file as well as some other files.

TFS Release Setup

  1. You need to install some extensions from the Marketplace before deploying release.
    1. Package Management [https://marketplace.visualstudio.com/items?itemName=ms.feed]
    2. Release Management Utility tasks [https://marketplace.visualstudio.com/items?itemName=ms-devlabs.utilitytasks]
  2. Ensure AzureRM PowerShell module is installed on release agent machine. If not, follow these instructions https://docs.microsoft.com/en-us/powershell/azure/install-azurerm-ps?view=azurermps-5.0.0
  3. Make sure service account doing releases has read-write permissions on build drop location.
  4. Provision release service accounts with the following project and build permissions:
    1. Project Permissions:
      1. View project-level information
      2. View test runs
    2. Build (All) permissions:
      1. Edit build quality
      2. Retain indefinitely
      3. View build definition
      4. View builds
  5. Create an agent queue for the release environment that has an agent that can reach the Azure server.
  6. Create a basic Team Release using the empty template 
  7. Environment
    1. Rename "Environment 1" to "DEV"
    2. Transform Tokenized Parameters File
      1. Click "Add task" > Utility > Tokenize with Xpath/Regular expressions > Add. Click Close.
      2. Enter "$(TfsBuildDropLocation)\$(ProductCode)\$(Build.DefinitionName)\$(Build.BuildNumber)\$(TfsBuildPublishFolder)\{ProjectName}.SetParameters.xml" for Source filename
      3. Enter "$(TfsBuildDropLocation)\$(ProductCode)\$(Build.DefinitionName)\$(Build.BuildNumber)\$(TfsBuildPublishFolder)\{ProjectName}.SetParameters.DEV.xml" for Destination filename
      4. Edit task name as "TransformTokenizedParametersFile"
    3. Deploy to Azure Web App
      1. Click "Add task" > Deploy > Azure App Service Deploy > Add. Click Close.
      2. Select "{AzrSubscription}" from Azure Subscription dropdown
      3. Select "{AzrWebAppName}" from App Service name dropdown
      4. Click "Deploy to slot"
      5. Select "{AzrResourceGroup}" where web app belongs from Resource group dropdown
      6. Select "dev" from Slot dropdown
      7. Enter "$(TfsBuildDropLocation)\$(ProductCode)\$(Build.DefinitionName)\$(Build.BuildNumber)\$(TfsBuildPublishFolder)\**\*.zip" for Package or Folder field
      8. Click "Publish using Web Deploy"
      9. Enter "$(TfsBuildDropLocation)\$(ProductCode)\$(Build.DefinitionName)\$(Build.BuildNumber)\$(TfsBuildPublishFolder)\ )\{ProjectName}.SetParameters.DEV.xml" for SetParameters File field
      10. Enter "-verbose -allowUntrusted" for Additional Arguments field
      11. Edit task name as "DeployToAzureAppServiceSlot"
    4. Delete Transformed Parameters File
      1. Click "Add task" > Utility > Delete files > Add. Click Close.
      2. Enter "$(TfsBuildDropLocation)\$(ProductCode)\$(Build.DefinitionName)\$(Build.BuildNumber)\$(TfsBuildPublishFolder)" for Source Folder field
      3. Enter "*.SetParameters.DEV.xml" for Contents field
      4. Click Always run
      5. Edit task name as "DeleteTransformedParametersFile"
    5. Set Tags for deployed slot
      1. Click "Add task" > Deploy > Azure PowerShell > Add. Click Close.
      2. Select "Azure Resource Manager" for Azure Connection type
      3. Select "{AzrSubscription}" for Azure RM Subscription
      4. Select "Script File Path" for Script Type
      5. Enter "\\DFSPath\SetAzureWebAppTags-v1.0.ps1" for Script Path
      6. Enter "-AzWebAppSlot 'DEV' -AzWebApp '{webappname}' -AzResourceGroup '{AzrResourceGroup}'" for Script Arguments
      7. Edit task name as "SetTagsForDeployedSlot"
    6. UpdateTfsBuildQuality
      1. Click "Add build step" > Utility > PowerShell (run a PowerShell script) > Add. Click Close.
      2. Select "File Path" from Type drop-down.
      3. Enter \\DFSpath\UpdateTfsBuildQuality-v2.0.ps1 for Script Path field
      4. Enter -BuildQuality 'Released' for Arguments field.
      5. Edit task name as "UpdateTfsBuildQuality"
  8. Variables
    1. Add variables as defined below
    2. TfsBuildDropLocation \\DFS\Builds
      ProductCode ABCD
      TfsBuildPublishFolder PublishedOutput
      SharedDeploymentResourcesLocation \\DFS\Builds\tfs\ReleaseManagement
  9. General
    1. Enter "$(Release.DefinitionName)_$(Date:yyyyMMdd)$(rev:.rr)" for Release name format
  10. Retention
    1. Days to retain a release: 15
    2. Min releases to keep: 3
  11. Click … next to DEV environment and select "Configure Variables" option from the context menu. Define variables as:
    1. AuthContextStr Data Source=inst.database.secure.windows.net; database=db;user id=sa;password={pwd};
      Environment DEVELOPMENT
      MachineKey-ValidationKey {key}
      MachineKey-DecryptionKey {key}
These variable values will be injected. For instance, “Environment” and then tokenized connection string with tokens for the server, database, user, and password. These values will be used by the “TransformTokenizedParametersFile” task to inject into the SetParameters file in the placeholders. When WebDeploy runs, it replaces placeholder values in the web.config (in the package zip) file with the values that are in the SetParameters.xml file. 

There are more powerful and valid use cases out there that you can explore. However, for my scenario, the above solution worked like a charm.

Hope this helps.

References: 

No comments: