In the previous article I explained how to set up multiple environments for Azure Pipelines. In this article I will explain how you can deploy the artifacts to the on-premise VM’s. This are the artifacts you build in the pipeline. I am using an template for this, because we are using the same script three times. An template avoids code repetition.
I have three stages with a deployment job:
- acceptance
- test
- production.
By defining the name of the environment it knows to which VM it needs to deploy. You can add a job with a task by adding the following to the azure-pipelines.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | - stage : deployAcceptatie displayName: Deploy Acceptatie dependsOn: - Build jobs: - deployment : VMDeploy_Acceptatie displayName: deploy Acceptatie environment: name: Service Portaal VM acceptatie resourceType: VirtualMachine tags: acceptatie strategy: runOnce: deploy: steps: - template : deploy.yml |
Deploy with the deployment template
We use the task template. Create a new file called deploy.yml. I am using pm2 to run the node application. You could also use some other service like docker. This is very dependent on your VM and how your VM is configured (or what you want to use).
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 | steps: - download : current displayName: Download backend artifact: 'backend' - task : DownloadPipelineArtifact@2 displayName: Download frontend inputs: artifact: 'frontend' path: '$(Pipeline.Workspace)/frontend$' patterns: 'dist/**' - task : Bash@3 displayName: Stop application inputs: targetType: 'inline' script: | export HOME=/home/pm2 chown -R pm2 : pm2 /home/azure_pipeline/ su -c "pm2 stop all" -m "pm2" workingDirectory: '/opt/backend' - task : Bash@3 displayName: Backend deploy inputs: targetType: 'inline' script: | mv -v $(Pipeline.Workspace)/backend/dist ./dist mv -v $(Pipeline.Workspace)/backend/node_modules ./node_modules workingDirectory: '/opt/backend' - task : Bash@3 displayName: Frontend deploy inputs: targetType: 'inline' script: | mv -v $(Pipeline.Workspace)/frontend/* ./dist workingDirectory: '/opt/frontend' - task : Bash@3 displayName: Start application inputs: targetType: 'inline' script: | export HOME=/home/pm2 su -c "pm2 start all" -m "pm2" workingDirectory: '/opt/backend' |
The highlighted code is the code specific to your VM. Do this for all the three environments. The final azure-pipelines.yml will look like this:
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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | trigger: - master pool: vmImage: 'ubuntu-latest' stages: - stage : Build jobs: - job : Frontend steps: - task : Npm@1 inputs: command: 'ci' workingDir: '$(System.DefaultWorkingDirectory)/frontend' - task : Bash@3 inputs: targetType: 'inline' script: 'npm run ng build' workingDirectory: '$(System.DefaultWorkingDirectory)/frontend' - task : DeleteFiles@1 displayName: 'Delete JUnit files' inputs: SourceFolder: '$(System.DefaultWorkingDirectory)/frontend/junit' Contents: 'TESTS*.xml' - task : Npm@1 displayName: 'Test Angular' inputs: command: custom customCommand: run test -- --watch= false --code-coverage workingDir: '$(System.DefaultWorkingDirectory)/frontend' - task : Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4 displayName: ReportGenerator inputs: reports: '$(Build.SourcesDirectory)/frontend/coverage/cobertura-coverage.xml' targetdir: '$(Build.SourcesDirectory)/frontend/coverage' reporttypes: 'Cobertura' assemblyfilters: '-xunit*' - task : PublishTestResults@2 displayName: 'Publish Angular test results' condition: succeededOrFailed() inputs: testResultsFormat: 'JUnit' testResultsFiles: '$(System.DefaultWorkingDirectory)/frontend/junit/TESTS-*.xml' searchFolder: '$(System.DefaultWorkingDirectory)/frontend/junit' testRunTitle: 'Frontend mocha tests' - task : PublishPipelineArtifact@1 inputs: targetPath: '$(Build.SourcesDirectory)/frontend/coverage' artifact: 'frontendCoverage' - task : PublishPipelineArtifact@1 inputs: targetPath: '$(Pipeline.Workspace)/frontend' artifact: 'frontend' publishLocation: 'pipeline' - job : Backend steps: - task : Npm@1 inputs: command: 'ci' workingDir: '$(System.DefaultWorkingDirectory)/backend' - task : Bash@3 inputs: targetType: 'inline' script: 'npm run build' workingDirectory: '$(System.DefaultWorkingDirectory)/backend' - task : Npm@1 displayName: run test backend report inputs: command: 'custom' workingDir: 'backend' customCommand: 'run test-azure-report' - task : Npm@1 displayName: run test backend coverage inputs: command: 'custom' workingDir: 'backend' customCommand: 'run test-azure-coverage' - task : Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4 displayName: ReportGenerator inputs: reports: '$(Build.SourcesDirectory)/backend/coverage/cobertura-coverage.xml' targetdir: '$(Build.SourcesDirectory)/backend/coverage' reporttypes: 'Cobertura' assemblyfilters: '-xunit*' - task : PublishTestResults@2 inputs: testResultsFormat: 'JUnit' testResultsFiles: 'junit.xml' searchFolder: '$(Build.SourcesDirectory)/backend' testRunTitle: 'Run backend jest tests' - task : PublishPipelineArtifact@1 inputs: targetPath: '$(Build.SourcesDirectory)/backend/coverage' artifact: 'backendCoverage' - task : PublishPipelineArtifact@1 inputs: targetPath: '$(Pipeline.Workspace)/backend' artifact: 'backend' publishLocation: 'pipeline' - job : codeCoverage displayName: publish code coverage dependsOn: - Frontend - Backend steps: - checkout : none - task : DownloadPipelineArtifact@2 inputs: artifact: backend path: $(Pipeline.Workspace)/backend/backend itemPattern: '!node_modules/**' - task : DownloadPipelineArtifact@2 inputs: artifact: frontend path: $(Pipeline.Workspace)/frontend/frontend itemPattern: '!node_modules/**' - download : current artifact: backendCoverage - download : current artifact: frontendCoverage - task : Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4 displayName: ReportGenerator inputs: reports: '$(Pipeline.Workspace)/backendCoverage/Cobertura.xml;$(Pipeline.Workspace)/frontendCoverage/Cobertura.xml' targetdir: '$(Pipeline.Workspace)/coverage' reporttypes: 'HtmlInline_AzurePipelines;Cobertura' - task : PublishCodeCoverageResults@1 inputs: codeCoverageTool: 'Cobertura' summaryFileLocation: '$(Pipeline.Workspace)/coverage/*.xml' reportDirectory: '$(Pipeline.Workspace)/coverage' - stage : deployAcceptance displayName: Deploy Acceptance dependsOn: - Build jobs: - deployment : VMDeploy_Acceptance displayName: deploy Acceptance environment: name: Service Portaal VM acceptance resourceType: VirtualMachine strategy: runOnce: deploy: steps: - template : deploy.yml - stage : deployTest displayName: Deploy Test dependsOn: - Build - deployAcceptatie jobs: - deployment : VMDeploy_Test displayName: deploy Test environment: name: Service Portaal VM test resourceType: VirtualMachine strategy: runOnce: deploy: steps: - template : deploy.yml - stage : deployProduction displayName: Deploy Production dependsOn: - Build - deployAcceptance - deployTest jobs: - deployment : VMDeploy_Production displayName: deploy Production environment: name: Service Portaal VM productie resourceType: VirtualMachine strategy: runOnce: deploy: steps: - template : deploy.yml |
Conditions
Now if you only want to deploy production with a push on master, we can add a condition. I am using the next condition.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | - stage : deployProduction condition: and(succeeded() , eq(variables [ 'Build.SourceBranchName' ] , 'master' )) displayName: Deploy Production dependsOn: - Build - deployAcceptance - deployTest jobs: - deployment : VMDeploy_Production displayName: deploy Production environment: name: Service Portaal VM productie resourceType: VirtualMachine strategy: runOnce: deploy: steps: - template : deploy.yml |
And in your Azure Pipelines dashboard it should now look something like this. Some of the stages have been skipped here, because I didn’t want them to deploy yet.
Now you should have a pipeline in Azure Pipelines, with multiple deployment environment on self-hosted VM’s, using Node.js and Angular.
If you have any questions or suggestions, feel free to leave them below. I hope I helped some of you out!