DevOps Starter GitHub actions

Neil HaddleyApril 25, 2021

Everything you need for developing, deploying, and monitoring your application.

DevOps Starter makes it easy to get started on Azure using either GitHub actions or Azure DevOps

Select "DevOps Starter" in Azure Portal

Select "DevOps Starter" in Azure Portal

Create a Node app

Create a Node app

Select Express

Select Express

Select Web App for Containers or Windows Web App (free $)

Select Web App for Containers or Windows Web App (free $)

Enter GitHub credentials

Enter GitHub credentials

Select pricing tier "F1 Free"

Select pricing tier "F1 Free"

Deployment to Azure in progress

Deployment to Azure in progress

GitHub Actions workflow runs Unit Tests

GitHub Actions workflow runs Unit Tests

If Unit Tests pass deploy Node app to Azure

If Unit Tests pass deploy Node app to Azure

Once deployed to Azure run Functional Tests (Selenium)

Once deployed to Azure run Functional Tests (Selenium)

GitHub Actions workflow complete

GitHub Actions workflow complete

Summary of Azure resources

Summary of Azure resources

Navigate to production URL

Navigate to production URL

To add Factorial API... Open with GitHub Desktop

To add Factorial API... Open with GitHub Desktop

Clone repository

Clone repository

Open in Visual Studio Code

Open in Visual Studio Code

Run the updated app locally

$ cd Application

$ npm install haddley-factorial-cc

or

$ npm install haddley-factorial-js

$ npm install

$ npm run start

Updated code returns factorial of 9

Updated code returns factorial of 9

Run the unit tests locally

$ cd ../Tests

$ npm install

$ gulp unittest

Updated Unit Tests

Updated Unit Tests

Run the functional tests locally

$ xattr -d com.apple.quarantine chromedriver

$ npm run start&

$ cd ../Tests

$ gulp functionaltest --webAppUrl http://localhost:3000/

Functional tests

Functional tests

Push updates to GitHub... origin master

Push updates to GitHub... origin master

GitHub Actions workflow runs again

GitHub Actions workflow runs again

Test factorial API in production

Factorial API running in production

Factorial API running in production

devops-starter-workflow.yaml

YAML
1name: Build and Deploy node Express app to azure
2on:
3  push:
4    branches:
5      - master
6
7env:
8  AZURE_WEBAPP_NAME: "haddley-factorial-api"    # set this to your application's name
9  AZURE_WEBAPP_PACKAGE_PATH: "Application"      # set this to the path to your web app project, defaults to the repository root
10  NODE_VERSION: '8.11.1'                        # set this to the node version to use
11  RESOURCEGROUPNAME: "haddley-factorial-api-rg"
12  LOCATION: "South Central US"
13  HOSTINGPLANNAME: "haddley-factorial-api-plan"
14  APPINSIGHTLOCATION: "South Central US"
15  SKU: "F1 Free"
16
17jobs:
18  build:
19    name: Build and Run tests
20    runs-on: ubuntu-latest
21    steps:
22    - uses: actions/checkout@v2
23
24    - name: Use Node.js ${{ env.NODE_VERSION }}
25      uses: actions/setup-node@v1
26      with:
27        node-version: ${{ env.NODE_VERSION }}
28
29    - name: npm install and build
30      continue-on-error: false
31      run: |
32        cd Application
33        npm install
34        npm run build --if-present
35
36    - name: Run unit tests
37      continue-on-error: false
38      run: |
39        cd Tests
40        npm install
41        gulp unittest
42
43    - name: Creating artifact directories
44      run: |
45        mkdir buildartifacts
46        mkdir deploymenttemplates
47
48    # Archive build artifact
49    - name: Archive Application
50      run: |
51        zip -qq -r ./buildartifacts/Application.zip ./Application
52
53    # Uploading application to build artifact
54    - name: Upload Application to Build Artifact
55      continue-on-error: false
56      uses: actions/upload-artifact@v2
57      with:
58        name: buildartifacts
59        path: buildartifacts
60
61    # Archive Arm templates
62    - name: Archive ArmTemplates
63      uses: montudor/action-zip@v0.1.0
64      with:
65        args: zip -qq -r ./deploymenttemplates/armtemplates.zip ./armTemplates
66
67    # Uploading Arm Templates to artifacts
68    - name: Upload Arm templates to Artifact
69      continue-on-error: false
70      uses: actions/upload-artifact@v2
71      with:
72        name: deploymenttemplates
73        path: deploymenttemplates
74
75  Deploy:
76    name: Deploy to azure web app
77    needs: build
78    runs-on: ubuntu-latest
79    steps:
80    # Downloading build artifact
81    - name: Download a Build Artifact
82      uses: actions/download-artifact@v2
83      continue-on-error: false
84      with:
85        name: buildartifacts
86        path: buildartifacts
87
88    # Uzipping build artifacts
89    - name: unzipping build artifact
90      run: |
91        unzip -qq ./buildartifacts/Application.zip -d .
92
93  # Downloading Arm Templates
94    - name: Download an Arm template
95      uses: actions/download-artifact@v2
96      continue-on-error: false
97      with:
98        name: deploymenttemplates
99        path: deploymenttemplates
100
101    # Uzipping Arm template directory
102    - name: unzipping arm artifact
103      run: |
104        unzip -qq ./deploymenttemplates/armtemplates.zip -d .
105
106    # Login to azure
107    - name: Login to Azure
108      uses: azure/login@v1
109      continue-on-error: false
110      with:
111        creds: ${{ secrets.AZURE_CREDENTIALS }}
112
113    # Deploy Arm template
114    - name: Deploy ARM Template
115      uses: azure/CLI@v1
116      continue-on-error: false
117      with:
118        inlineScript: |
119          az group create --name "${{ env.RESOURCEGROUPNAME }}" --location "${{ env.LOCATION }}"
120          az deployment group create --resource-group "${{ env.RESOURCEGROUPNAME }}" --template-file ./armTemplates/windows-webapp-template.json --parameters webAppName="${{ env.AZURE_WEBAPP_NAME }}" hostingPlanName="${{ env.HOSTINGPLANNAME }}" appInsightsLocation="${{ env.APPINSIGHTLOCATION }}" sku="${{ env.SKU }}"
121
122    # Deploy web app on azure
123    - name: 'Deploy to Azure WebApp'
124      uses: azure/webapps-deploy@v2
125      with: 
126        app-name: ${{ env.AZURE_WEBAPP_NAME }}
127        package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
128
129  # Job to run functional tests
130  FunctionalTests:
131    name: Run Functional tests
132    runs-on: windows-latest
133    needs: Deploy
134    steps:
135    - uses: actions/checkout@v2
136
137    - name: Use Node.js ${{ env.NODE_VERSION }}
138      uses: actions/setup-node@v1
139      with:
140        node-version: ${{ env.NODE_VERSION }}
141
142    # Run functional tests
143    - name: Run Functional Tests
144      continue-on-error: false
145      run: |
146        cd Application
147        npm install
148        cd ../Tests
149        npm install
150        gulp functionaltest --webAppUrl https://${{ env.AZURE_WEBAPP_NAME }}.azurewebsites.net/

routesindex.js

TEXT
1'use strict';
2var express = require('express');
3var router = express.Router();
4// var factorial = require('haddley-factorial-cc');
5var factorial = require('haddley-factorial-js');
6
7/* GET home page. */
8router.get('/', function (req, res) {
9    res.render('index', { title: 'Express' });
10});
11
12router.get('/factorial/:n', (request, response) => {
13    try {
14        const n = Number(request.params.n);
15        const result = factorial.factorial_it(n);
16        response.json(result);
17    }
18    catch (err) {
19        response.status(500).send(err);
20    }
21});
22
23module.exports = router;

unittestssampleUnitTests.js

TEXT
1var assert = require('assert');
2var app = require('../../Application/app');
3var http = require('http');
4 
5describe('sampleUnitTests', function () {
6    before(function () {
7    });
8  
9    after(function () {
10        app.close();
11    });
12
13    it('Should return 200', function (done) {
14        http.get('http://localhost:3000', function (res) {
15            assert.equal(200, res.statusCode, 'Result code should be 200.');
16            done();
17        });
18    });
19
20    it('Assert title', function (done) {
21        http.get('http://localhost:3000', function (res) {
22            assert.equal(200, res.statusCode, 'Result code should be 200.');
23            var data = '';
24
25            res.on('data', function (chunk) {
26                data += chunk;
27            });
28
29            res.on('end', function () {
30                assert.equal(true, data.includes('<title>Express - Node.js Express Application</title>'), 'Title should be Express - Node.js Express Application.');
31                done();
32            })
33        });
34    });
35
36
37    it('Assert factorial 9', function (done) {
38        http.get('http://localhost:3000/factorial/9', function (res) {
39            assert.equal(200, res.statusCode, 'Result code should be 200.');
40            var data = '';
41
42            res.on('data', function (chunk) {
43                data += chunk;
44            });
45
46            res.on('end', function () {
47                assert.equal(true, data.includes('362880'));
48                done();
49            })
50        });
51    });
52
53});

References