CI/CD Pipeline

This is an example for having a MS DevOps repo with library and some unit testing project to trigger a DevOps CI/CD (continuous integration / continuous deployment) pipeline.

CI (continuous integration)

It’s the practice of merging all developers’ working copies to a shared mainline several times a day.

In our case, the CI pipeline builds and tests the code on commits to ensure that everything is buildable and the tests don’t fail. This also ensures that the code is also buildable and runnable not only on the developer’s computer, but also in the pipeline used minimal container for the execution.

CD (continuous deployment)

This would be an automatic process to grab the results from the CI pipeline and deploy them to targets, for example a webserver to host the built web API.

DevOps

Microsoft DevOps is a cloud hosted platform to save and manage the repositories and to run CI/CD pipelines. This is a service from Microsoft and runs on MS cloud infrastructure, hence not in our internal company network.

Create a CI pipe

DevOps Menu

DevOps create pipe

DevOps select pipe repo

DevOps select pipe repo type

Edit the YAML pipeline configuration and add or change the commands to be run:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# Configures the triggers, like branch names
trigger:
  branches:
    include:
      - '*'
    exclude:
      - 'Beta'
      - 'Alpha'
      - 'Alpha*'

# DevOps Azure Container:
pool:
  vmImage: 'windows-latest'

# # OnPremise Build machine:
# pool:
#   name: OnPremise Pipelines
#   demands: 
#     - Agent.Name -equals YOUR_BUILD_SERVER

# Variables for the following tasks
variables:
  - name: SolutionName
    value: YOUR_SOLUTION_NAME
  - name: solution
    value: '**/*.sln'
  - name: buildPlatform
    value: 'Any CPU'
  - name: buildConfiguration
    value: 'Release'

# List of tasks as steps
steps:
# git checkout the repository 
# fetchDepth: 0 makes is a complete copy with all commits
- checkout: self
  submodules: true
  persistCredentials: true
  fetchDepth: 0

- task: NuGetToolInstaller@1
  displayName: 'Install NuGet Tools'
  inputs:
    checkLatest: true

- task: NuGetCommand@2
  displayName: 'Restore NuGet Packages'
  inputs:
    restoreSolution: '$(solution)'

- task: VSBuild@1
  displayName: 'Build Solution'
  inputs:
    solution: '$(solution)'
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'

# Testing with specific path and <test[s]>.dll name
- task: VSTest@2
  inputs:
    testSelector: 'testAssemblies'
    testAssemblyVer2: |
      **\*\*test.dll
      !**\*TestAdapter.dll
      !**\obj\**
    searchFolder: '$(System.DefaultWorkingDirectory)'
    runInParallel: true
    runTestsInIsolation: true
    codeCoverageEnabled: true
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'

# Copy files to a staging dir, see article [DevOps - Build Agent Directories] for more details
- task: CopyFiles@2
  displayName: 'Copy build artifacts to staging folder'
  inputs:
    SourceFolder: '$(System.DefaultWorkingDirectory)'
    Contents: '$(SolutionName)*/bin/$(buildConfiguration)/**'
    TargetFolder: '$(Build.ArtifactStagingDirectory)'

# Upload the artifacts to the DevOps Cloud storage
- task: PublishPipelineArtifact@1
  displayName: 'Publish Pipeline Artifacts'
  inputs:
    targetPath: '$(Build.ArtifactStagingDirectory)'
    ArtifactName: 'drop'
    publishLocation: 'pipeline'

# Version.marker for extracting a semantic file version number and upload a text file with it
- powershell: |
    $searchPath = "$(Build.ArtifactStagingDirectory)"
    $searchExeName = "$(SolutionName).exe"

    Get-ChildItem -Path $searchPath -Filter $searchExeName -Recurse |
        ForEach-Object {
            try {
                $_ | Add-Member NoteProperty FileVersion ($_.VersionInfo.FileVersion)
            } catch {}
            $_
        } |
        Select-Object -ExpandProperty FileVersion -OutVariable BuildVersionNumber

    Write-Host "Extracted FileVersion Number: $($BuildVersionNumber)"
    $BuildVersionNumber | out-file -filepath "$(Build.ArtifactStagingDirectory)/version.marker"
    Write-Host "Stored the versionNumber in: $(Build.ArtifactStagingDirectory)/version.marker"
  displayName: Extract and store version number in version.marker

- task: PublishPipelineArtifact@1
  displayName: 'Publish Pipeline Artifacts - version marker'
  inputs:
    targetPath: '$(Build.ArtifactStagingDirectory)/version.marker'
    ArtifactName: 'version.marker'
    publishLocation: 'pipeline'

The yaml example pipeline has some comments for the single steps to be run.

Pipeline steps:

  • get a isolated container with specific OS to run the pipeline steps
  • get the code [with submodules]
  • install nuGet tools in the container
  • use nuGet tools to get the configured nuGet packages for the code project
  • build the code project
  • [run the unit tests for the code project]
  • [copy artifact files for publishing them]
  • [publish the artifact files for further processing, like CD pipeline]
  • [extract a file version number and write it to a text file to upload it]

Steps in [] are rather optional and dependent on the situation.

The pipeline editor also offers some help to inject some tasks on the right side:

DevOps pipeline editor task help

After clicking the Save and Run button you can configure what branch the pipe should be initially created in and start it:

DevOps pipeline run overview

You can check the single steps execution output:

DevOps pipeline single steps output

The pipeline yaml file will be committed and saved in the DevOps repository:

DevOps repo xaml file

The pipeline is executed on Microsoft cloud infrastructure and is isolated in the configured container. Dependent on the DevOps configuration of the company it will be run on a Worker at times and report its state after finishing.

The user who has triggered the pipe will get an eMail with a link and state report of the pipe:

DevOps pipeline eMail