Deploy on Code Checkin (CI)

This is known as ‘Continuous Integration’ (CI). But how do you do this in Azure Devops?

This article succintly introduces the concept of Pipelines in Azure Devops.

Pipelines = CI

Azure Devops calls this ‘Pipelines’ - the two main sections are ‘Builds’ and ‘Releases’. Both are ‘pipelines’

A ‘Build pipeline’ means to check out the code and have the compiler (or whatever exe) build the code. You can specify what the build task is using a PowerShell script. A build pipeline can be auto-triggered when code is checked in.

A build pipeline generates an “artifact” (a zip file of the compiled source).

A ‘Release pipeline’ means to take the output of the build and actually deploy it on a server. A release pipeline can be triggered after an artifact is created (from the build pipeline).

Example pipeline (this site)

Let’s take this site as an example. It’s a bunch of .md files and images which need to be run through Hugo.exe (a static site generator).

A build pipeline is created that checks out the repository (of .md files) and calls a Hugo task to output the statically generated html into an “artifact”.

The release pipeline takes the artifact and deploys it (my deploy task is a PowerShell script, you can also use Bash scripts too if that’s your thing).

Deployment Secrets

Generally, there are tokens that are contained within the deploy script in the release pipeline. This is so the “artifact” can be deployed on an unrelated server. You should always use tokens (as opposed to credentials) as tokens can be changed and updated easily.

Be very careful of who can view your Azure Devops Pipelines!

Even if you mask variables, there are ways to print out the variables into the log files.

What does a build involve? How do you checkout submodules?

By default, git does not check out submodules (a reference to another git repo), You have to add submodules: true in the checkout step.

Here is what my build definition looks like - you can go to https://aka.ms/yaml for the specifications.

trigger:
- master

pool:
  vmImage: 'vs2017-win2016'

steps:
- checkout: self
  submodules: true
  displayName: 'Checkout repository including submodules'

- task: HugoTask@1
  displayName: 'Generate Hugo site'
  inputs:
    destination: '$(Build.ArtifactStagingDirectory)'

- task: PublishPipelineArtifact@0
  displayName: 'Publish Hugo site as artifact'
  inputs:
    artifactName: 'docs'    #Note the artifact name, which you might need to reference in your scripts in the 'release' pipeline
    targetPath: '$(Build.ArtifactStagingDirectory)'

Ref: Official doc for predefined variables.

Notes

In 2019, there is a ‘classic editor’ version of this where you can pick the tasks from a list, not sure if this will be removed, but it is more user friendly.

When you create a new build pipeline, you have to select where the source is coming from, here is what’s supported (2019):

What does a release look like?

When you edit a release, it looks like this: This says “Take the output artifact from the build pipeline, and run a task.”. “Continuous deployment trigger” means that this will automatically happen when an artifact is available.

When you click on 1 job, 1 task, you will be able to see exactly what the release does.

In my case, it takes the artifact contents and pushes changes into my “github pages” repository (which is a totally different account than Azure Devops).

The PowerShell script is as follows - (note that I hardcode the variables here to make things simpler):

$docPath = '$(System.DefaultWorkingDirectory)\_coderdocs\*'
$githubusername = 'efficienthacks'
$githubemail = 'email@doesnt-matter.com'
$githubaccesstoken = '[generate from github]'
$repositoryname = 'coderdocs'
$commitMessage = 'Automated Release $(Release.ReleaseId)'
$branchName = 'master'

$defaultWorkingDirectory = '$(System.DefaultWorkingDirectory)'

Write-Host "Cloning existing GitHub repository" # these will be seen in the logs

git clone https://${githubusername}:$githubaccesstoken@github.com/$githubusername/$repositoryname.git --branch=$branchName $defaultWorkingDirectory\coderdocs

if ($lastexitcode -gt 0)
{
    Write-Host "##vso[task.logissue type=error;]Unable to clone repository - check username, access token and repository name. Error code $lastexitcode"
    [Environment]::Exit(1)
}

$to = "$defaultWorkingDirectory\coderdocs"  

Write-Host "Copying new documentation into branch"

Copy-Item $docPath $to -recurse -Force  #this copies my artifact which I named 'docs' (from the build pipeline)  so I will have all the html files in $defaultWorkingDirectory\coderdocs\docs

Write-Host "Committing the GitHub repository"

cd $defaultWorkingDirectory\coderdocs
git config core.autocrlf false
git config user.email $githubemail
git config user.name $githubusername
git add *
git commit --allow-empty -m $commitMessage

if ($lastexitcode -gt 0)
{
    Write-Host "##vso[task.logissue type=error;]Error committing - see earlier log, error code $lastexitcode"
    [Environment]::Exit(1)
}

git push --quiet

if ($lastexitcode -gt 0)
{
    Write-Host "##vso[task.logissue type=error;]Error pushing to $branchName branch, probably an incorrect Personal Access Token, error code $lastexitcode"
    [Environment]::Exit(1)
}

Credit: JamesRandall

Notes

When you create a release, you will choose the artifact(s), note that the artifact does not need to be generated from your Build Pipeline. As of 2019, here are the supported artifact sources:

get yourself on the email list

//Powered by MailChimp - low volume, no spam

comments powered by Disqus