From 339533665e860bfe630cc64f96aa1fcbd1ba7435 Mon Sep 17 00:00:00 2001 From: Mike Monteith Date: Tue, 31 Mar 2020 17:14:04 +0100 Subject: [PATCH 01/14] Set up CI with Azure Pipelines [skip ci] --- azure-pipelines.yml | 56 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..a26d048 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,56 @@ + +# Run pipeline on master branch, pull-requests into master, and tags +trigger: + branches: + include: + - master + - refs/tags/* +pr: +- master + + +pool: + vmImage: 'ubuntu-latest' + +stages: +- stage: Test + jobs: + - job: Test + + steps: + - task: NodeTool@0 + inputs: + versionSpec: '10.x' + displayName: 'Install Node.js' + + - script: | + npm install + npm test + displayName: 'npm test' + +- stage: Build + displayName: Build stage + jobs: + - job: Build + + steps: + - task: NodeTool@0 + inputs: + versionSpec: '10.x' + displayName: 'Install Node.js' + + - script: | + npm install --production + displayName: 'npm install' + + - task: ArchiveFiles@2 + displayName: 'Archive files' + inputs: + rootFolderOrFile: '$(System.DefaultWorkingDirectory)' + includeRootFolder: false + archiveType: zip + archiveFile: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip + replaceExistingArchive: true + + - upload: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip + artifact: drop From f37ba33b84f2d688139dbd9da774ba0431e146f2 Mon Sep 17 00:00:00 2001 From: Mike Monteith Date: Thu, 2 Apr 2020 14:44:04 +0100 Subject: [PATCH 02/14] Add ARM template for azure deployment --- AzureResourceGroup/template.json | 112 +++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 AzureResourceGroup/template.json diff --git a/AzureResourceGroup/template.json b/AzureResourceGroup/template.json new file mode 100644 index 0000000..bb5ea4c --- /dev/null +++ b/AzureResourceGroup/template.json @@ -0,0 +1,112 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "location": { + "type": "String", + "allowedValues": [ + "uksouth", + "ukwest" + ] + }, + "environment": { + "type": "String" + } + }, + "variables": { + "locationAbbreviation": "[substring(parameters('location'), 0, 3)]", + "storageAccountName": "[concat('nhsukuserfeedback', parameters('environment'), variables('locationAbbreviation'))]", + "functionAppName": "[concat('nhsuk-user-feedback-func-', parameters('environment'), '-', variables('locationAbbreviation'))]", + "hostingPlanName": "[concat('nhsuk-user-feedback-plan-', parameters('environment'), '-', variables('locationAbbreviation'))]", + "appInsightsName": "[variables('functionAppName')]" + }, + "resources": [ + { + "type": "Microsoft.Web/sites", + "apiVersion": "2019-08-01", + "name": "[variables('functionAppName')]", + "kind": "functionapp,linux", + "location": "[parameters('location')]", + "dependsOn": [ + "[concat('microsoft.insights/components/', variables('appInsightsName'))]", + "[concat('Microsoft.Web/serverfarms/', variables('hostingPlanName'))]", + "[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]" + ], + "tags": { + "Owner": "Mike Monteith" + }, + "properties": { + "name": "[variables('functionAppName')]", + "siteConfig": { + "appSettings": [ + { + "name": "FUNCTIONS_EXTENSION_VERSION", + "value": "~3" + }, + { + "name": "FUNCTIONS_WORKER_RUNTIME", + "value": "node" + }, + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "~12" + }, + { + "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", + "value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value,';EndpointSuffix=','core.windows.net')]" + }, + { + "name": "WEBSITE_CONTENTSHARE", + "value": "[toLower(variables('functionAppName'))]" + } + ] + }, + "reserved": true + } + }, + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2019-06-01", + "name": "[variables('storageAccountName')]", + "location": "[parameters('location')]", + "sku": { + "name": "Standard_LRS" + }, + "properties": { + "supportsHttpsTrafficOnly": true + } + }, + { + "type": "Microsoft.Web/serverfarms", + "apiVersion": "2019-08-01", + "name": "[variables('hostingPlanName')]", + "location": "[parameters('location')]", + "dependsOn": [], + "tags": { + "Owner": "Mike Monteith" + }, + "sku": { + "Tier": "Dynamic", + "Name": "Y1" + }, + "kind": "linux", + "properties": { + "reserved": true + } + }, + { + "type": "microsoft.insights/components", + "apiVersion": "2015-05-01", + "name": "[variables('appInsightsName')]", + "location": "[parameters('location')]", + "tags": { + "Owner": "Mike Monteith" + }, + "kind": "web", + "properties": { + "Request_Source": "rest", + "Application_Type": "web" + } + } + ] +} From 98cc65f257622d10e20c3880126fbf2543ef81ce Mon Sep 17 00:00:00 2001 From: Mike Monteith Date: Thu, 2 Apr 2020 15:31:28 +0100 Subject: [PATCH 03/14] storage account name must have <=24 chars --- AzureResourceGroup/template.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AzureResourceGroup/template.json b/AzureResourceGroup/template.json index bb5ea4c..914b88e 100644 --- a/AzureResourceGroup/template.json +++ b/AzureResourceGroup/template.json @@ -15,7 +15,7 @@ }, "variables": { "locationAbbreviation": "[substring(parameters('location'), 0, 3)]", - "storageAccountName": "[concat('nhsukuserfeedback', parameters('environment'), variables('locationAbbreviation'))]", + "storageAccountName": "[concat('nhsukfeedback', parameters('environment'), variables('locationAbbreviation'))]", "functionAppName": "[concat('nhsuk-user-feedback-func-', parameters('environment'), '-', variables('locationAbbreviation'))]", "hostingPlanName": "[concat('nhsuk-user-feedback-plan-', parameters('environment'), '-', variables('locationAbbreviation'))]", "appInsightsName": "[variables('functionAppName')]" From 0c2fff794c85f71479794ffceef4cc3d16703f8a Mon Sep 17 00:00:00 2001 From: Mike Monteith Date: Thu, 2 Apr 2020 15:32:06 +0100 Subject: [PATCH 04/14] Specify serverfarm ID for functionapp --- AzureResourceGroup/template.json | 1 + 1 file changed, 1 insertion(+) diff --git a/AzureResourceGroup/template.json b/AzureResourceGroup/template.json index 914b88e..cc3dd59 100644 --- a/AzureResourceGroup/template.json +++ b/AzureResourceGroup/template.json @@ -37,6 +37,7 @@ }, "properties": { "name": "[variables('functionAppName')]", + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", "siteConfig": { "appSettings": [ { From 00de5c29a1af3f79c65e58e7a3c8e33a892c1a71 Mon Sep 17 00:00:00 2001 From: Mike Monteith Date: Fri, 3 Apr 2020 09:00:21 +0100 Subject: [PATCH 05/14] Add AzureWebJobsStorage config - required for linux apps --- AzureResourceGroup/template.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/AzureResourceGroup/template.json b/AzureResourceGroup/template.json index cc3dd59..5131de9 100644 --- a/AzureResourceGroup/template.json +++ b/AzureResourceGroup/template.json @@ -52,6 +52,10 @@ "name": "WEBSITE_NODE_DEFAULT_VERSION", "value": "~12" }, + { + "name": "AzureWebJobsStorage", + "value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value,';EndpointSuffix=','core.windows.net')]" + }, { "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", "value": "[concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), '2019-06-01').keys[0].value,';EndpointSuffix=','core.windows.net')]" From adcb5182edb1d2cf5787bfe91282b5554dc126bd Mon Sep 17 00:00:00 2001 From: Mike Monteith Date: Fri, 3 Apr 2020 09:23:45 +0100 Subject: [PATCH 06/14] Return some useful values in ARM template output --- AzureResourceGroup/template.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/AzureResourceGroup/template.json b/AzureResourceGroup/template.json index 5131de9..574dcba 100644 --- a/AzureResourceGroup/template.json +++ b/AzureResourceGroup/template.json @@ -113,5 +113,15 @@ "Application_Type": "web" } } - ] + ], + "outputs": { + "functionAppName": { + "type": "string", + "value": "[variables('functionAppName')]" + }, + "storageAccountName": { + "type": "string", + "value": "[variables('storageAccountName')]" + } + } } From 065094e4d99426e217fc431a3de9266500075de7 Mon Sep 17 00:00:00 2001 From: Mike Monteith Date: Fri, 3 Apr 2020 10:28:36 +0100 Subject: [PATCH 07/14] Defined functionAppName as a parameter, not calculated value This is because of limitations within the azure devops platform. We need to be able to specify the functionAppName in pipelines instead of generating one via the ARM template. --- AzureResourceGroup/template.json | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/AzureResourceGroup/template.json b/AzureResourceGroup/template.json index 574dcba..846abb8 100644 --- a/AzureResourceGroup/template.json +++ b/AzureResourceGroup/template.json @@ -11,20 +11,22 @@ }, "environment": { "type": "String" + }, + "functionAppName": { + "type": "String" } }, "variables": { "locationAbbreviation": "[substring(parameters('location'), 0, 3)]", "storageAccountName": "[concat('nhsukfeedback', parameters('environment'), variables('locationAbbreviation'))]", - "functionAppName": "[concat('nhsuk-user-feedback-func-', parameters('environment'), '-', variables('locationAbbreviation'))]", "hostingPlanName": "[concat('nhsuk-user-feedback-plan-', parameters('environment'), '-', variables('locationAbbreviation'))]", - "appInsightsName": "[variables('functionAppName')]" + "appInsightsName": "[parameters('functionAppName')]" }, "resources": [ { "type": "Microsoft.Web/sites", "apiVersion": "2019-08-01", - "name": "[variables('functionAppName')]", + "name": "[parameters('functionAppName')]", "kind": "functionapp,linux", "location": "[parameters('location')]", "dependsOn": [ @@ -36,7 +38,7 @@ "Owner": "Mike Monteith" }, "properties": { - "name": "[variables('functionAppName')]", + "name": "[parameters('functionAppName')]", "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", "siteConfig": { "appSettings": [ @@ -62,7 +64,7 @@ }, { "name": "WEBSITE_CONTENTSHARE", - "value": "[toLower(variables('functionAppName'))]" + "value": "[toLower(parameters('functionAppName'))]" } ] }, @@ -117,7 +119,7 @@ "outputs": { "functionAppName": { "type": "string", - "value": "[variables('functionAppName')]" + "value": "[parameters('functionAppName')]" }, "storageAccountName": { "type": "string", From 6299e8d990b77280beb748f568057e59f61f12e5 Mon Sep 17 00:00:00 2001 From: Mike Monteith Date: Fri, 3 Apr 2020 10:32:59 +0100 Subject: [PATCH 08/14] Make hosting plan size configurable --- AzureResourceGroup/template.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/AzureResourceGroup/template.json b/AzureResourceGroup/template.json index 846abb8..fd3d899 100644 --- a/AzureResourceGroup/template.json +++ b/AzureResourceGroup/template.json @@ -14,6 +14,12 @@ }, "functionAppName": { "type": "String" + }, + "hostingPlanSkuTier": { + "type": "String" + }, + "hostingPlanSkuName": { + "type": "String" } }, "variables": { @@ -93,8 +99,8 @@ "Owner": "Mike Monteith" }, "sku": { - "Tier": "Dynamic", - "Name": "Y1" + "Tier": "[parameters('hostingPlanSkuTier')]", + "Name": "[parameters('hostingPlanSkuName')]" }, "kind": "linux", "properties": { From 60011f9b4c111e09661d7901e3d3a3a583fa7b7b Mon Sep 17 00:00:00 2001 From: Mike Monteith Date: Fri, 3 Apr 2020 15:51:09 +0100 Subject: [PATCH 09/14] Add mongo database resource and connection to functionapp --- AzureResourceGroup/template.json | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/AzureResourceGroup/template.json b/AzureResourceGroup/template.json index fd3d899..381cd85 100644 --- a/AzureResourceGroup/template.json +++ b/AzureResourceGroup/template.json @@ -26,7 +26,8 @@ "locationAbbreviation": "[substring(parameters('location'), 0, 3)]", "storageAccountName": "[concat('nhsukfeedback', parameters('environment'), variables('locationAbbreviation'))]", "hostingPlanName": "[concat('nhsuk-user-feedback-plan-', parameters('environment'), '-', variables('locationAbbreviation'))]", - "appInsightsName": "[parameters('functionAppName')]" + "appInsightsName": "[parameters('functionAppName')]", + "databaseName": "[concat('nhsuk-user-feedback-db-', parameters('environment'))]" }, "resources": [ { @@ -38,7 +39,8 @@ "dependsOn": [ "[concat('microsoft.insights/components/', variables('appInsightsName'))]", "[concat('Microsoft.Web/serverfarms/', variables('hostingPlanName'))]", - "[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]" + "[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]", + "[concat('Microsoft.DocumentDB/databaseAccounts/', variables('databaseName'))]" ], "tags": { "Owner": "Mike Monteith" @@ -71,6 +73,10 @@ { "name": "WEBSITE_CONTENTSHARE", "value": "[toLower(parameters('functionAppName'))]" + }, + { + "name": "MONGO_CONNECTION_STRING", + "value": "[listConnectionStrings(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('databaseName')), '2019-12-12').connectionStrings[0].connectionString]" } ] }, @@ -120,6 +126,27 @@ "Request_Source": "rest", "Application_Type": "web" } + }, + { + "type": "Microsoft.DocumentDB/databaseAccounts", + "apiVersion": "2020-03-01", + "name": "[variables('databaseName')]", + "location": "[parameters('location')]", + "tags": { + "Owner": "Mike Monteith" + }, + "kind": "MongoDB", + "properties": { + "locations": [{ + "locationName": "[parameters('location')]" + }], + "databaseAccountOfferType": "Standard", + "capabilities": [ + { + "name": "EnableMongo" + } + ] + } } ], "outputs": { From 537be39801087b3749c8dbfa5e843f38d6e3417a Mon Sep 17 00:00:00 2001 From: Mike Monteith Date: Fri, 3 Apr 2020 16:30:44 +0100 Subject: [PATCH 10/14] Add CORS option to ARM template Set disableCORS to allow all domains to make cross-origin requests. This setting is useful in dev environments but should not be used in production --- AzureResourceGroup/template.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/AzureResourceGroup/template.json b/AzureResourceGroup/template.json index 381cd85..620901d 100644 --- a/AzureResourceGroup/template.json +++ b/AzureResourceGroup/template.json @@ -20,6 +20,9 @@ }, "hostingPlanSkuName": { "type": "String" + }, + "disableCORS": { + "type": "Bool" } }, "variables": { @@ -78,7 +81,12 @@ "name": "MONGO_CONNECTION_STRING", "value": "[listConnectionStrings(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('databaseName')), '2019-12-12').connectionStrings[0].connectionString]" } - ] + ], + "cors": { + "allowedOrigins": [ + "[if(parameters('disableCORS'), '*', 'https://www.nhs.uk')]" + ] + } }, "reserved": true } From b43f7e6ef4f26a000e1094ee1bc1c908a8354b4d Mon Sep 17 00:00:00 2001 From: Mike Monteith Date: Fri, 3 Apr 2020 17:03:36 +0100 Subject: [PATCH 11/14] Connect application insights with function app --- AzureResourceGroup/template.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/AzureResourceGroup/template.json b/AzureResourceGroup/template.json index 620901d..867f3e0 100644 --- a/AzureResourceGroup/template.json +++ b/AzureResourceGroup/template.json @@ -61,6 +61,14 @@ "name": "FUNCTIONS_WORKER_RUNTIME", "value": "node" }, + { + "name": "APPINSIGHTS_INSTRUMENTATIONKEY", + "value": "[reference(concat('microsoft.insights/components/', variables('appInsightsName')), '2015-05-01').InstrumentationKey]" + }, + { + "name": "APPLICATIONINSIGHTS_CONNECTION_STRING", + "value": "[reference(concat('microsoft.insights/components/', variables('appInsightsName')), '2015-05-01').ConnectionString]" + }, { "name": "WEBSITE_NODE_DEFAULT_VERSION", "value": "~12" From c8273cefd44d40bd5fb3c3a98e09d3198672a128 Mon Sep 17 00:00:00 2001 From: Mike Monteith Date: Fri, 3 Apr 2020 17:34:11 +0100 Subject: [PATCH 12/14] Add neccessary mongo connection string parameters --- AzureResourceGroup/template.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AzureResourceGroup/template.json b/AzureResourceGroup/template.json index 867f3e0..d7ab025 100644 --- a/AzureResourceGroup/template.json +++ b/AzureResourceGroup/template.json @@ -87,7 +87,7 @@ }, { "name": "MONGO_CONNECTION_STRING", - "value": "[listConnectionStrings(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('databaseName')), '2019-12-12').connectionStrings[0].connectionString]" + "value": "[concat(listConnectionStrings(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('databaseName')), '2019-12-12').connectionStrings[0].connectionString, '&retrywrites=false&maxIdleTimeMS=120000')]" } ], "cors": { From df5749b9a37fb1977518e337a74082adf6a59b43 Mon Sep 17 00:00:00 2001 From: Mike Monteith Date: Mon, 6 Apr 2020 13:17:16 +0100 Subject: [PATCH 13/14] Add tags to all resources with some useful defaults --- AzureResourceGroup/template.json | 37 ++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/AzureResourceGroup/template.json b/AzureResourceGroup/template.json index d7ab025..b45dbfe 100644 --- a/AzureResourceGroup/template.json +++ b/AzureResourceGroup/template.json @@ -23,6 +23,16 @@ }, "disableCORS": { "type": "Bool" + }, + "buildTag": { + "type": "String" + }, + "expiresTag": { + "type": "String", + "defaultValue": "Never" + }, + "ownerTag": { + "type": "String" } }, "variables": { @@ -30,7 +40,14 @@ "storageAccountName": "[concat('nhsukfeedback', parameters('environment'), variables('locationAbbreviation'))]", "hostingPlanName": "[concat('nhsuk-user-feedback-plan-', parameters('environment'), '-', variables('locationAbbreviation'))]", "appInsightsName": "[parameters('functionAppName')]", - "databaseName": "[concat('nhsuk-user-feedback-db-', parameters('environment'))]" + "databaseName": "[concat('nhsuk-user-feedback-db-', parameters('environment'))]", + "tags": { + "Owner": "[parameters('ownerTag')]", + "Product": "User Feedback", + "Build": "[parameters('buildTag')]", + "Expires": "[parameters('expiresTag')]", + "Environment": "[parameters('environment')]" + } }, "resources": [ { @@ -45,9 +62,7 @@ "[concat('Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]", "[concat('Microsoft.DocumentDB/databaseAccounts/', variables('databaseName'))]" ], - "tags": { - "Owner": "Mike Monteith" - }, + "tags": "[variables('tags')]", "properties": { "name": "[parameters('functionAppName')]", "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]", @@ -104,6 +119,7 @@ "apiVersion": "2019-06-01", "name": "[variables('storageAccountName')]", "location": "[parameters('location')]", + "tags": "[variables('tags')]", "sku": { "name": "Standard_LRS" }, @@ -116,10 +132,7 @@ "apiVersion": "2019-08-01", "name": "[variables('hostingPlanName')]", "location": "[parameters('location')]", - "dependsOn": [], - "tags": { - "Owner": "Mike Monteith" - }, + "tags": "[variables('tags')]", "sku": { "Tier": "[parameters('hostingPlanSkuTier')]", "Name": "[parameters('hostingPlanSkuName')]" @@ -134,9 +147,7 @@ "apiVersion": "2015-05-01", "name": "[variables('appInsightsName')]", "location": "[parameters('location')]", - "tags": { - "Owner": "Mike Monteith" - }, + "tags": "[variables('tags')]", "kind": "web", "properties": { "Request_Source": "rest", @@ -148,9 +159,7 @@ "apiVersion": "2020-03-01", "name": "[variables('databaseName')]", "location": "[parameters('location')]", - "tags": { - "Owner": "Mike Monteith" - }, + "tags": "[variables('tags')]", "kind": "MongoDB", "properties": { "locations": [{ From 5e81f76b8854785c96aa22ba79df7172659511d4 Mon Sep 17 00:00:00 2001 From: Mike Monteith Date: Mon, 6 Apr 2020 13:18:15 +0100 Subject: [PATCH 14/14] Rename disableCORS to enableCORS This describes the parameter better --- AzureResourceGroup/template.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AzureResourceGroup/template.json b/AzureResourceGroup/template.json index b45dbfe..de829c2 100644 --- a/AzureResourceGroup/template.json +++ b/AzureResourceGroup/template.json @@ -21,7 +21,7 @@ "hostingPlanSkuName": { "type": "String" }, - "disableCORS": { + "enableCORS": { "type": "Bool" }, "buildTag": { @@ -107,7 +107,7 @@ ], "cors": { "allowedOrigins": [ - "[if(parameters('disableCORS'), '*', 'https://www.nhs.uk')]" + "[if(parameters('enableCORS'), '*', 'https://www.nhs.uk')]" ] } },