Repository: serverless/serverless-azure-functions
Pull request #174 (2019-06-20 01:03:22)

feat: Dynamically generate ARM templates
Based on various well-known skus we can generate ARM templates with the correct resources and expose parameters.

Well Known variations:
1. Consumption 
2. Premium
3. AppServiceEnvironment

Consolidate ARM template deployment strategy into ArmService

pjlittle: Good stuff -- like the flexibility of options.

Patch:
diff --git a/.gitignore b/.gitignore
index 300f95f..ee4336e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,4 @@ node_modules/
 /lib/
 *.dll
 /coverage/
-.env
\ No newline at end of file
+.env*
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 98cfeca..b3cfd58 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1491,6 +1491,12 @@
       "integrity": "sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==",
       "dev": true
     },
+    "@types/jsonpath": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/@types/jsonpath/-/jsonpath-0.2.0.tgz",
+      "integrity": "sha512-v7qlPA0VpKUlEdhghbDqRoKMxFB3h3Ch688TApBJ6v+XLDdvWCGLJIYiPKGZnS6MAOie+IorCfNYVHOPIHSWwQ==",
+      "dev": true
+    },
     "@types/lodash": {
       "version": "4.14.133",
       "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.133.tgz",
diff --git a/package.json b/package.json
index 744b556..fa52a26 100644
--- a/package.json
+++ b/package.json
@@ -51,6 +51,7 @@
   "devDependencies": {
     "@babel/runtime": "^7.4.5",
     "@types/jest": "^24.0.13",
+    "@types/jsonpath": "^0.2.0",
     "@types/lodash": "^4.14.130",
     "@types/mock-fs": "^3.6.30",
     "@types/open": "^6.1.0",
diff --git a/scripts/generate-service-principal.sh b/scripts/generate-service-principal.sh
new file mode 100755
index 0000000..7df6adf
--- /dev/null
+++ b/scripts/generate-service-principal.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+###
+# This script generate a service principal using your currently set subscription
+#   then write out the credentials of your new sp in a file 
+#   that you can export as environment variables
+###
+set -e
+
+if ! [[ -x "$(command -v jq)" ]]; then
+  echo "Please install [jq] before continuing -> https://stedolan.github.io/jq/download/.  Aborting."
+  exit 1
+fi
+
+SP_NAME=$1
+
+if [[ -z $SP_NAME ]]; then
+  SP_NAME="FunctionsTestingPrincipal"
+  echo "You didn't pass in a name for the service principal.  We'll use the default: ${SP_NAME}"
+  echo
+fi
+
+SUBSCRIPTION=$(az account show | jq .name)
+echo "You're using subscription: ${SUBSCRIPTION}"
+echo "--> Creating service principal with name: ${SP_NAME}"
+SP_RESPONSE=$(az ad sp create-for-rbac --name "http://${SP_NAME}")
+
+FILE=".env.servicePrincipal"
+
+echo "--> Writing creds to '${FILE}'"
+cat <<EOF >${FILE}
+export azureSubId=$(az account show | jq .id)
+export azureServicePrincipalTenantId=$(echo "${SP_RESPONSE}" | jq .tenant)
+export azureServicePrincipalClientId=$(echo "${SP_RESPONSE}" | jq .name)
+export azureServicePrincipalPassword=$(echo "${SP_RESPONSE}" | jq .password)
+EOF
+
+echo "Run \"source ${FILE}\" to set your Azure account credentials "
diff --git a/src/armTemplates/ase.ts b/src/armTemplates/ase.ts
new file mode 100644
index 0000000..e79f406
--- /dev/null
+++ b/src/armTemplates/ase.ts
@@ -0,0 +1,53 @@
+import { FunctionAppResource } from "./resources/functionApp";
+import { AppInsightsResource } from "./resources/appInsights";
+import { StorageAccountResource } from "./resources/storageAccount";
+import { AppServicePlanResource } from "./resources/appServicePlan";
+import { HostingEnvironmentResource } from "./resources/hostingEnvironment";
+import { VirtualNetworkResource } from "./resources/virtualNetwork";
+import { CompositeArmTemplate } from "./compositeArmTemplate";
+import { ArmResourceTemplate } from "../models/armTemplates";
+
+class AppServiceEnvironmentTemplate extends CompositeArmTemplate {
+  public constructor() {
+    super([
+      new FunctionAppResource(),
+      new AppInsightsResource(),
+      new StorageAccountResource(),
+      new AppServicePlanResource(),
+      new HostingEnvironmentResource(),
+      new VirtualNetworkResource(),
+    ])
+  }
+
+  public getTemplate(): ArmResourceTemplate {
+    const template = super.getTemplate();
+
+    template.parameters.appServicePlanSkuName.defaultValue = "I1";
+    template.parameters.appServicePlanSkuTier.defaultValue = "Isolated";
+
+    // Update the app service plan to point to the hosting environment
+    const appServicePlan: any = template.resources.find((resource: any) => resource.type === "Microsoft.Web/serverfarms");
+    if (appServicePlan) {
+      appServicePlan.dependsOn = [...(appServicePlan.dependsOn || []), "[resourceId('Microsoft.Web/hostingEnvironments', parameters('hostingEnvironmentName'))]"];
+      appServicePlan.properties.hostingEnvironmentProfile = {
+        ...appServicePlan.properties.hostingEnvironmentProfile,
+        id: "[resourceId('Microsoft.Web/hostingEnvironments', parameters('hostingEnvironmentName'))]",
+      }
+    }
+
+    // Update the functionApp resource to include the app service plan references
+    const app: any = template.resources.find((resource: any) => resource.type === "Microsoft.Web/sites");
+    if (app) {
+      app.dependsOn = [...(app.dependsOn || []), "[concat('Microsoft.Web/serverfarms/', parameters('appServicePlanName'))]"];
+      app.properties.serverFarmId = "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]";
+      app.properties.hostingEnvironmentProfile = {
+        ...app.properties.hostingEnvironmentProfile,
+        id: "[resourceId('Microsoft.Web/hostingEnvironments', parameters('hostingEnvironmentName'))]",
+      }
+    }
+
+    return template;
+  }
+}
+
+export default new AppServiceEnvironmentTemplate();
\ No newline at end of file
diff --git a/src/armTemplates/compositeArmTemplate.ts b/src/armTemplates/compositeArmTemplate.ts
new file mode 100644
index 0000000..a56b547
--- /dev/null
+++ b/src/armTemplates/compositeArmTemplate.ts
@@ -0,0 +1,47 @@
+import { ArmResourceTemplateGenerator, ArmResourceTemplate } from "../models/armTemplates";
+import { Guard } from "../shared/guard";
+import { ServerlessAzureConfig } from "../models/serverless";
+
+export class CompositeArmTemplate implements ArmResourceTemplateGenerator {
+  public constructor(private childTemplates: ArmResourceTemplateGenerator[]) {
+    Guard.null(childTemplates);
+  }
+
+  public getTemplate(): ArmResourceTemplate {
+    const template: ArmResourceTemplate = {
+      "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+      "contentVersion": "1.0.0.0",
+      "parameters": {},
+      "resources": [],
+    };
+
+    this.childTemplates.forEach((resource) => {
+      const resourceTemplate = resource.getTemplate();
+      template.parameters = {
+        ...template.parameters,
+        ...resourceTemplate.parameters,
+      };
+
+      template.resources = [
+        ...template.resources,
+        ...resourceTemplate.resources,
+      ];
+    });
+
+    return template;
+  }
+
+  public getParameters(config: ServerlessAzureConfig) {
+    let parameters = {};
+
+    this.childTemplates.forEach((resource) => {
+      parameters = {
+        ...parameters,
+        ...resource.getParameters(config),
+        location: config.provider.region,
+      }
+    });
+
+    return parameters;
+  }
+}
\ No newline at end of file
diff --git a/src/armTemplates/consumption.ts b/src/armTemplates/consumption.ts
new file mode 100644
index 0000000..05b8316
--- /dev/null
+++ b/src/armTemplates/consumption.ts
@@ -0,0 +1,16 @@
+import { FunctionAppResource } from "./resources/functionApp";
+import { AppInsightsResource } from "./resources/appInsights";
+import { StorageAccountResource } from "./resources/storageAccount";
+import { CompositeArmTemplate } from "./compositeArmTemplate";
+
+class ConsumptionPlanTemplate extends CompositeArmTemplate {
+  public constructor() {
+    super([
+      new FunctionAppResource(),
+      new AppInsightsResource(),
+      new StorageAccountResource(),
+    ])
+  }
+}
+
+export default new ConsumptionPlanTemplate();
\ No newline at end of file
diff --git a/src/armTemplates/premium.ts b/src/armTemplates/premium.ts
new file mode 100644
index 0000000..c15daa2
--- /dev/null
+++ b/src/armTemplates/premium.ts
@@ -0,0 +1,35 @@
+import { FunctionAppResource } from "./resources/functionApp";
+import { AppInsightsResource } from "./resources/appInsights";
+import { StorageAccountResource } from "./resources/storageAccount";
+import { AppServicePlanResource } from "./resources/appServicePlan";
+import { ArmResourceTemplate } from "../models/armTemplates";
+import { CompositeArmTemplate } from "./compositeArmTemplate";
+
+class PremiumPlanTemplate extends CompositeArmTemplate {
+  public constructor() {
+    super([
+      new FunctionAppResource(),
+      new AppInsightsResource(),
+      new StorageAccountResource(),
+      new AppServicePlanResource(),
+    ])
+  }
+
+  public getTemplate(): ArmResourceTemplate {
+    const template = super.getTemplate();
+
+    template.parameters.appServicePlanSkuName.defaultValue = "EP1";
+    template.parameters.appServicePlanSkuTier.defaultValue = "ElasticPremium";
+
+    // Update the functionApp resource to include the app service plan references
+    const app: any = template.resources.find((resource: any) => resource.type === "Microsoft.Web/sites");
+    if (app) {
+      app.properties.serverFarmId = "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]";
+      app.dependsOn = [...(app.dependsOn || []), "[concat('Microsoft.Web/serverfarms/', parameters('appServicePlanName'))]"]
+    }
+
+    return template;
+  }
+}
+
+export default new PremiumPlanTemplate();
\ No newline at end of file
diff --git a/src/armTemplates/resources/apim.ts b/src/armTemplates/resources/apim.ts
new file mode 100644
index 0000000..46587db
--- /dev/null
+++ b/src/armTemplates/resources/apim.ts
@@ -0,0 +1,79 @@
+import { ServerlessAzureConfig } from "../../models/serverless";
+import { ArmResourceTemplateGenerator, ArmResourceTemplate } from "../../models/armTemplates";
+import { ApiManagementConfig } from "../../models/apiManagement";
+
+export class ApimResource implements ArmResourceTemplateGenerator {
+  public static getResourceName(config: ServerlessAzureConfig) {
+    return config.provider.apim && config.provider.apim.name
+      ? config.provider.apim.name
+      : `${config.provider.prefix}-${config.provider.region}-${config.provider.stage}-apim`;
+  }
+
+  public getTemplate(): ArmResourceTemplate {
+    return {
+      "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+      "contentVersion": "1.0.0.0",
+      "parameters": {
+        "apiManagementName": {
+          "defaultValue": "",
+          "type": "String"
+        },
+        "location": {
+          "defaultValue": "",
+          "type": "String"
+        },
+        "apimSkuName": {
+          "defaultValue": "Consumption",
+          "type": "String"
+        },
+        "apimCapacity": {
+          "defaultValue": 0,
+          "type": "int"
+        },
+        "apimPublisherEmail": {
+          "defaultValue": "contact@contoso.com",
+          "type": "String"
+        },
+        "apimPublisherName": {
+          "defaultValue": "Contoso",
+          "type": "String"
+        }
+      },
+      "variables": {},
+      "resources": [
+        {
+          "type": "Microsoft.ApiManagement/service",
+          "apiVersion": "2018-06-01-preview",
+          "name": "[parameters('apiManagementName')]",
+          "location": "[parameters('location')]",
+          "sku": {
+            "name": "[parameters('apimSkuName')]",
+            "capacity": "[parameters('apimCapacity')]"
+          },
+          "properties": {
+            "publisherEmail": "[parameters('apimPublisherEmail')]",
+            "publisherName": "[parameters('apimPublisherName')]",
+            "notificationSenderEmail": "apimgmt-noreply@mail.windowsazure.com",
+            "hostnameConfigurations": [],
+            "virtualNetworkType": "None"
+          }
+        }
+      ]
+    };
+  }
+
+  public getParameters(config: ServerlessAzureConfig) {
+    const apimConfig: ApiManagementConfig = {
+      sku: {},
+      ...config.provider.apim,
+    };
+
+    return {
+      apiManagementName: ApimResource.getResourceName(config),
+      apimSkuName: apimConfig.sku.name,
+      apimSkuCapacity: apimConfig.sku.capacity,
+      apimPublisherEmail: apimConfig.publisherEmail,
+      apimPublisherName: apimConfig.publisherName,
+    };
+  }
+}
\ No newline at end of file
diff --git a/src/armTemplates/resources/appInsights.ts b/src/armTemplates/resources/appInsights.ts
new file mode 100644
index 0000000..9e7df06
--- /dev/null
+++ b/src/armTemplates/resources/appInsights.ts
@@ -0,0 +1,47 @@
+import { ArmResourceTemplateGenerator, ArmResourceTemplate } from "../../models/armTemplates";
+import { ServerlessAzureConfig, ResourceConfig } from "../../models/serverless";
+
+export class AppInsightsResource implements ArmResourceTemplateGenerator {
+  public static getResourceName(config: ServerlessAzureConfig) {
+    return config.provider.appInsights && config.provider.appInsights.name
+      ? config.provider.appInsights.name
+      : `${config.provider.prefix}-${config.provider.region}-${config.provider.stage}-appinsights`;
+  }
+
+  public getTemplate(): ArmResourceTemplate {
+    return {
+      "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+      "contentVersion": "1.0.0.0",
+      "parameters": {
+        "appInsightsName": {
+          "defaultValue": "",
+          "type": "String"
+        },
+        "location": {
+          "defaultValue": "",
+          "type": "String"
+        }
+      },
+      "variables": {},
+      "resources": [
+        {
+          "apiVersion": "2015-05-01",
+          "name": "[parameters('appInsightsName')]",
+          "type": "microsoft.insights/components",
+          "location": "[parameters('location')]",
+          "properties": {
+            "Application_Type": "web",
+            "ApplicationId": "[parameters('appInsightsName')]",
+            "Request_Source": "IbizaWebAppExtensionCreate"
+          }
+        }
+      ]
+    }
+  }
+
+  public getParameters(config: ServerlessAzureConfig): any {
+    return {
+      appInsightsName: AppInsightsResource.getResourceName(config),
+    };
+  }
+}
\ No newline at end of file
diff --git a/src/armTemplates/resources/appServicePlan.ts b/src/armTemplates/resources/appServicePlan.ts
new file mode 100644
index 0000000..8a6a378
--- /dev/null
+++ b/src/armTemplates/resources/appServicePlan.ts
@@ -0,0 +1,68 @@
+import { ArmResourceTemplateGenerator, ArmResourceTemplate } from "../../models/armTemplates";
+import { ServerlessAzureConfig, ResourceConfig } from "../../models/serverless";
+
+export class AppServicePlanResource implements ArmResourceTemplateGenerator {
+  public static getResourceName(config: ServerlessAzureConfig) {
+    return config.provider.appServicePlan && config.provider.appServicePlan.name
+      ? config.provider.appServicePlan.name
+      : `${config.provider.prefix}-${config.provider.region}-${config.provider.stage}-asp`;
+  }
+
+  public getTemplate(): ArmResourceTemplate {
+    return {
+      "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+      "contentVersion": "1.0.0.0",
+      "parameters": {
+        "appServicePlanName": {
+          "defaultValue": "",
+          "type": "String"
+        },
+        "location": {
+          "defaultValue": "",
+          "type": "String"
+        },
+        "appServicePlanSkuName": {
+          "defaultValue": "EP1",
+          "type": "String"
+        },
+        "appServicePlanSkuTier": {
+          "defaultValue": "ElasticPremium",
+          "type": "String"
+        }
+      },
+      "variables": {},
+      "resources": [
+        {
+          "apiVersion": "2016-09-01",
+          "name": "[parameters('appServicePlanName')]",
+          "type": "Microsoft.Web/serverfarms",
+          "location": "[parameters('location')]",
+          "properties": {
+            "name": "[parameters('appServicePlanName')]",
+            "workerSizeId": "3",
+            "numberOfWorkers": "1",
+            "maximumElasticWorkerCount": "10",
+            "hostingEnvironment": ""
+          },
+          "sku": {
+            "name": "[parameters('appServicePlanSkuName')]",
+            "tier": "[parameters('appServicePlanSkuTier')]"
+          }
+        }
+      ]
+    };
+  }
+
+  public getParameters(config: ServerlessAzureConfig): any {
+    const resourceConfig: ResourceConfig = {
+      sku: {},
+      ...config.provider.storageAccount,
+    };
+
+    return {
+      appServicePlanName: AppServicePlanResource.getResourceName(config),
+      appServicePlanSkuName: resourceConfig.sku.name,
+      appServicePlanSkuTier: resourceConfig.sku.tier,
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/armTemplates/resources/functionApp.ts b/src/armTemplates/resources/functionApp.ts
new file mode 100644
index 0000000..50caba9
--- /dev/null
+++ b/src/armTemplates/resources/functionApp.ts
@@ -0,0 +1,115 @@
+import { ArmResourceTemplateGenerator, ArmResourceTemplate } from "../../models/armTemplates";
+import { ServerlessAzureConfig, FunctionAppConfig } from "../../models/serverless";
+
+export class FunctionAppResource implements ArmResourceTemplateGenerator {
+  public static getResourceName(config: ServerlessAzureConfig) {
+    return config.provider.functionApp && config.provider.functionApp.name
+      ? config.provider.functionApp.name
+      : `${config.provider.prefix}-${config.provider.region}-${config.provider.stage}-${config.service}`;
+  }
+
+  public getTemplate(): ArmResourceTemplate {
+    return {
+      "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+      "contentVersion": "1.0.0.0",
+      "parameters": {
+        "functionAppName": {
+          "defaultValue": "",
+          "type": "String"
+        },
+        "functionAppNodeVersion": {
+          "defaultValue": "10.14.1",
+          "type": "String"
+        },
+        "functionAppWorkerRuntime": {
+          "defaultValue": "node",
+          "type": "String"
+        },
+        "functionAppExtensionVersion": {
+          "defaultValue": "~2",
+          "type": "String"
+        },
+        "storageAccountName": {
+          "defaultValue": "",
+          "type": "String"
+        },
+        "appInsightsName": {
+          "defaultValue": "",
+          "type": "String"
+        },
+        "location": {
+          "defaultValue": "",
+          "type": "String"
+        }
+      },
+      "variables": {},
+      "resources": [
+        {
+          "type": "Microsoft.Web/sites",
+          "apiVersion": "2016-03-01",
+          "name": "[parameters('functionAppName')]",
+          "location": "[parameters('location')]",
+          "dependsOn": [
+            "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]",
+            "[concat('microsoft.insights/components/', parameters('appInsightsName'))]"
+          ],
+          "kind": "functionapp",
+          "properties": {
+            "siteConfig": {
+              "appSettings": [
+                {
+                  "name": "FUNCTIONS_WORKER_RUNTIME",
+                  "value": "[parameters('functionAppWorkerRuntime')]"
+                },
+                {
+                  "name": "FUNCTIONS_EXTENSION_VERSION",
+                  "value": "[parameters('functionAppExtensionVersion')]"
+                },
+                {
+                  "name": "AzureWebJobsStorage",
+                  "value": "[concat('DefaultEndpointsProtocol=https;AccountName=',parameters('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2016-01-01').keys[0].value)]"
+                },
+                {
+                  "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
+                  "value": "[concat('DefaultEndpointsProtocol=https;AccountName=',parameters('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName')), '2016-01-01').keys[0].value)]"
+                },
+                {
+                  "name": "WEBSITE_CONTENTSHARE",
+                  "value": "[toLower(parameters('functionAppName'))]"
+                },
+                {
+                  "name": "WEBSITE_NODE_DEFAULT_VERSION",
+                  "value": "[parameters('functionAppNodeVersion')]"
+                },
+                {
+                  "name": "WEBSITE_RUN_FROM_PACKAGE",
+                  "value": "1"
+                },
+                {
+                  "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
+                  "value": "[reference(concat('microsoft.insights/components/', parameters('appInsightsName'))).InstrumentationKey]"
+                }
+              ]
+            },
+            "name": "[parameters('functionAppName')]",
+            "clientAffinityEnabled": false,
+            "hostingEnvironment": ""
+          }
+        }
+      ]
+    };
+  }
+
+  public getParameters(config: ServerlessAzureConfig): any {
+    const resourceConfig: FunctionAppConfig = {
+      ...config.provider.functionApp,
+    };
+
+    return {
+      functionAppName: FunctionAppResource.getResourceName(config),
+      functionAppNodeVersion: resourceConfig.nodeVersion,
+      functionAppWorkerRuntime: resourceConfig.workerRuntime,
+      functionAppExtensionVersion: resourceConfig.extensionVersion,
+    };
+  }
+}
diff --git a/src/armTemplates/resources/hostingEnvironment.ts b/src/armTemplates/resources/hostingEnvironment.ts
new file mode 100644
index 0000000..f785485
--- /dev/null
+++ b/src/armTemplates/resources/hostingEnvironment.ts
@@ -0,0 +1,70 @@
+import { ArmResourceTemplateGenerator, ArmResourceTemplate } from "../../models/armTemplates";
+import { ServerlessAzureConfig, ResourceConfig } from "../../models/serverless";
+
+export class HostingEnvironmentResource implements ArmResourceTemplateGenerator {
+  public static getResourceName(config: ServerlessAzureConfig) {
+    return config.provider.hostingEnvironment && config.provider.hostingEnvironment.name
+      ? config.provider.hostingEnvironment.name
+      : `${config.provider.prefix}-${config.provider.region}-${config.provider.stage}-ase`;
+  }
+
+  public getTemplate(): ArmResourceTemplate {
+    return {
+      "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+      "contentVersion": "1.0.0.0",
+      "parameters": {
+        "hostingEnvironmentName": {
+          "defaultValue": "",
+          "type": "String"
+        },
+        "virtualNetworkName": {
+          "defaultValue": "",
+          "type": "String"
+        },
+        "location": {
+          "defaultValue": "",
+          "type": "String"
+        }
+      },
+      "variables": {},
+      "resources": [
+        {
+          "type": "Microsoft.Web/hostingEnvironments",
+          "apiVersion": "2016-09-01",
+          "name": "[parameters('hostingEnvironmentName')]",
+          "location": "[parameters('location')]",
+          "dependsOn": [
+            "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]"
+          ],
+          "kind": "ASEV2",
+          "zones": [],
+          "properties": {
+            "name": "[parameters('hostingEnvironmentName')]",
+            "location": "[parameters('location')]",
+            "vnetName": "[parameters('virtualNetworkName')]",
+            "vnetResourceGroupName": "[resourceGroup().name]",
+            "vnetSubnetName": "default",
+            "virtualNetwork": {
+              "id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworkName'))]",
+              "subnet": "default"
+            },
+            "internalLoadBalancingMode": "None",
+            "multiSize": "Standard_D1_V2",
+            "multiRoleCount": 2,
+            "ipsslAddressCount": 2,
+            "networkAccessControlList": [],
+            "frontEndScaleFactor": 15,
+            "suspended": false
+          }
+        }
+      ]
+    };
+  }
+
+  public getParameters(config: ServerlessAzureConfig): any {
+    return {
+      hostingEnvironmentName: HostingEnvironmentResource.getResourceName(config)
+    }
+  }
+}
+
diff --git a/src/armTemplates/resources/storageAccount.ts b/src/armTemplates/resources/storageAccount.ts
new file mode 100644
index 0000000..b786e98
--- /dev/null
+++ b/src/armTemplates/resources/storageAccount.ts
@@ -0,0 +1,65 @@
+import { ArmResourceTemplateGenerator, ArmResourceTemplate } from "../../models/armTemplates";
+import { ServerlessAzureConfig, ResourceConfig } from "../../models/serverless";
+
+export class StorageAccountResource implements ArmResourceTemplateGenerator {
+  public static getResourceName(config: ServerlessAzureConfig) {
+    return config.provider.storageAccount && config.provider.storageAccount.name
+      ? config.provider.storageAccount.name
+      : `${config.provider.prefix}${config.provider.region.substr(0, 3)}${config.provider.stage.substr(0, 3)}sa`.replace("-", "").toLocaleLowerCase();
+  }
+
+  public getTemplate(): ArmResourceTemplate {
+    return {
+      "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+      "contentVersion": "1.0.0.0",
+      "parameters": {
+        "storageAccountName": {
+          "defaultValue": "",
+          "type": "String"
+        },
+        "location": {
+          "defaultValue": "",
+          "type": "String"
+        },
+        "storageAccountSkuName": {
+          "defaultValue": "Standard_LRS",
+          "type": "String"
+        },
+        "storageAccoutSkuTier": {
+          "defaultValue": "Standard",
+          "type": "String"
+        }
+      },
+      "variables": {},
+      "resources": [
+        {
+          "apiVersion": "2018-07-01",
+          "name": "[parameters('storageAccountName')]",
+          "type": "Microsoft.Storage/storageAccounts",
+          "location": "[parameters('location')]",
+          "kind": "Storage",
+          "properties": {
+            "accountType": "[parameters('storageAccountSkuName')]"
+          },
+          "sku": {
+            "name": "[parameters('storageAccountSkuName')]",
+            "tier": "[parameters('storageAccoutSkuTier')]"
+          }
+        }
+      ]
+    }
+  }
+
+  public getParameters(config: ServerlessAzureConfig): any {
+    const resourceConfig: ResourceConfig = {
+      sku: {},
+      ...config.provider.storageAccount,
+    };
+
+    return {
+      storageAccountName: StorageAccountResource.getResourceName(config),
+      storageAccountSkuName: resourceConfig.sku.name,
+      storageAccoutSkuTier: resourceConfig.sku.tier,
+    };
+  }
+}
\ No newline at end of file
diff --git a/src/armTemplates/resources/virtualNetwork.ts b/src/armTemplates/resources/virtualNetwork.ts
new file mode 100644
index 0000000..b93448b
--- /dev/null
+++ b/src/armTemplates/resources/virtualNetwork.ts
@@ -0,0 +1,88 @@
+import { ArmResourceTemplateGenerator, ArmResourceTemplate } from "../../models/armTemplates";
+import { ServerlessAzureConfig, ResourceConfig } from "../../models/serverless";
+
+export class VirtualNetworkResource implements ArmResourceTemplateGenerator {
+  public static getResourceName(config: ServerlessAzureConfig) {
+    return config.provider.virtualNetwork && config.provider.virtualNetwork.name
+      ? config.provider.virtualNetwork.name
+      : `${config.provider.prefix}-${config.provider.region}-${config.provider.stage}-vnet`;
+  }
+
+  public getTemplate(): ArmResourceTemplate {
+    return {
+      "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+      "contentVersion": "1.0.0.0",
+      "parameters": {
+        "hostingEnvironmentName": {
+          "defaultValue": "",
+          "type": "String"
+        },
+        "virtualNetworkName": {
+          "defaultValue": "",
+          "type": "String"
+        },
+        "location": {
+          "defaultValue": "",
+          "type": "String"
+        }
+      },
+      "variables": {},
+      "resources": [
+        {
+          "type": "Microsoft.Network/virtualNetworks",
+          "apiVersion": "2018-12-01",
+          "name": "[parameters('virtualNetworkName')]",
+          "location": "[parameters('location')]",
+          "properties": {
+            "provisioningState": "Succeeded",
+            "resourceGuid": "b756ff30-43ac-4e83-9794-13011e7884ba",
+            "addressSpace": {
+              "addressPrefixes": [
+                "172.17.0.0/16"
+              ]
+            },
+            "subnets": [
+              {
+                "name": "default",
+                "etag": "W/\"73e9f4aa-86a9-478e-ad11-2db211c9c2e3\"",
+                "properties": {
+                  "provisioningState": "Succeeded",
+                  "addressPrefix": "172.17.0.0/24",
+                  "resourceNavigationLinks": [
+                    {
+                      "name": "[concat('MicrosoftWeb_HostingEnvironments_', parameters('hostingEnvironmentName'))]",
+                      "properties": {
+                        "linkedResourceType": "Microsoft.Web/hostingEnvironments",
+                        "link": "[resourceId('Microsoft.Web/hostingEnvironments', parameters('hostingEnvironmentName'))]"
+                      }
+                    }
+                  ],
+                  "serviceEndpoints": [
+                    {
+                      "provisioningState": "Succeeded",
+                      "service": "Microsoft.Web",
+                      "locations": [
+                        "*"
+                      ]
+                    }
+                  ],
+                  "delegations": []
+                }
+              }
+            ],
+            "virtualNetworkPeerings": [],
+            "enableDdosProtection": false,
+            "enableVmProtection": false
+          }
+        }
+      ]
+    };
+  }
+
+  public getParameters(config: ServerlessAzureConfig): any {
+    return {
+      virtualNetworkName: VirtualNetworkResource.getResourceName(config),
+    }
+  }
+}
+
diff --git a/src/models/apiManagement.ts b/src/models/apiManagement.ts
index 8c8abea..3fc656d 100644
--- a/src/models/apiManagement.ts
+++ b/src/models/apiManagement.ts
@@ -12,6 +12,12 @@ export interface ApiManagementConfig {
   backend?: BackendContract;
   /** The API's CORS policy */
   cors?: ApiCorsPolicy;
+  sku?: {
+    name?: string;
+    capacity?: number;
+  };
+  publisherEmail?: string;
+  publisherName?: string;
 }
 
 /**
diff --git a/src/models/armTemplates.ts b/src/models/armTemplates.ts
new file mode 100644
index 0000000..a35d050
--- /dev/null
+++ b/src/models/armTemplates.ts
@@ -0,0 +1,39 @@
+import { ServerlessAzureConfig } from "./serverless";
+
+/**
+ * ARM Resource Template Generator
+ */
+export interface ArmResourceTemplateGenerator {
+  getTemplate(): ArmResourceTemplate;
+  getParameters(config: ServerlessAzureConfig): any;
+}
+
+/**
+ * The well-known serverless Azure template types
+ */
+export enum ArmTemplateType {
+  Consumption = "consumption",
+  Premium = "premium",
+  AppServiceEnvironment = "ase",
+}
+
+/**
+ * Represents an Azure ARM template
+ */
+export interface ArmResourceTemplate {
+  $schema: string;
+  contentVersion: string;
+  parameters: {
+    [key: string]: any;
+  };
+  resources: any[];
+  variables?: any;
+}
+
+/**
+ * Represents an Azure ARM deployment
+ */
+export interface ArmDeployment {
+  template: ArmResourceTemplate;
+  parameters: { [key: string]: any };
+}
diff --git a/src/models/serverless.ts b/src/models/serverless.ts
index debd6df..b3f6d31 100644
--- a/src/models/serverless.ts
+++ b/src/models/serverless.ts
@@ -1,7 +1,48 @@
+import { ApiManagementConfig } from "./apiManagement";
+
+export interface ArmTemplateConfig {
+  file: string;
+  parameters:
+  {
+    [key: string]: string;
+  };
+}
+
+export interface ResourceConfig {
+  name: string;
+  sku?: {
+    name?: string;
+    tier?: string;
+  };
+  [key: string]: any;
+}
+
+export interface FunctionAppConfig extends ResourceConfig {
+  nodeVersion?: string;
+  workerRuntime?: string;
+  extensionVersion?;
+}
+
 export interface ServerlessAzureConfig {
+  service: string;
   provider: {
+    type?: string;
+    prefix?: string;
+    region: string;
+    stage: string;
     name: string;
-    location: string;
+    environment?: {
+      [key: string]: any;
+    };
+    resourceGroup?: string;
+    apim?: ApiManagementConfig;
+    functionApp?: FunctionAppConfig;
+    appInsights?: ResourceConfig;
+    appServicePlan?: ResourceConfig;
+    storageAccount?: ResourceConfig;
+    hostingEnvironment?: ResourceConfig;
+    virtualNetwork?: ResourceConfig;
+    armTemplate?: ArmTemplateConfig;
   };
   plugins: string[];
   functions: any;
diff --git a/src/plugins/deploy/azureDeployPlugin.ts b/src/plugins/deploy/azureDeployPlugin.ts
index 76c88eb..485e771 100644
--- a/src/plugins/deploy/azureDeployPlugin.ts
+++ b/src/plugins/deploy/azureDeployPlugin.ts
@@ -31,7 +31,7 @@ export class AzureDeployPlugin {
     const resourceService = new ResourceService(this.serverless, this.options);
     const deployments = await resourceService.getDeployments();
     if (!deployments || deployments.length === 0) {
-      this.serverless.cli.log(`No deployments found for resource group '${resourceService.getResourceGroup()}'`);
+      this.serverless.cli.log(`No deployments found for resource group '${resourceService.getResourceGroupName()}'`);
       return;
     }
     let stringDeployments = "\n\nDeployments";
diff --git a/src/provider/armTemplates/azuredeploy.json b/src/provider/armTemplates/azuredeploy.json
deleted file mode 100644
index 32df123..0000000
--- a/src/provider/armTemplates/azuredeploy.json
+++ /dev/null
@@ -1,116 +0,0 @@
-{
-    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
-    "contentVersion": "1.0.0.0",
-    "parameters": {
-        "functionAppName": {
-            "type": "string",
-            "metadata": {
-                "description": "The name of the function app that you wish to create."
-            }
-        },
-        "storageAccountType": {
-            "type": "string",
-            "defaultValue": "Standard_LRS",
-            "allowedValues": [
-                "Standard_LRS",
-                "Standard_GRS",
-                "Standard_ZRS",
-                "Premium_LRS"
-            ],
-            "metadata": {
-                "description": "Storage Account type"
-            }
-        }
-    },
-    "variables": {
-        "functionAppName": "[parameters('functionAppName')]",
-        "hostingPlanName": "[parameters('functionAppName')]",
-        "insightsName": "[concat(parameters('functionAppName'), '-insights')]",
-        "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'azfunctions')]",
-        "storageAccountid": "[concat(resourceGroup().id,'/providers/','Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]"
-    },
-    "resources": [
-        {
-            "type": "microsoft.insights/components",
-            "kind": "Node.JS",
-            "name": "[variables('insightsName')]",
-            "apiVersion": "2014-04-01",
-            "location": "eastus",
-            "properties": {
-                "ApplicationId": "[variables('insightsName')]"
-            }
-        },
-        {
-            "type": "Microsoft.Storage/storageAccounts",
-            "name": "[variables('storageAccountName')]",
-            "apiVersion": "2015-06-15",
-            "location": "[resourceGroup().location]",
-            "properties": {
-                "accountType": "[parameters('storageAccountType')]"
-            }
-        },
-        {
-            "type": "Microsoft.Web/serverfarms",
-            "apiVersion": "2015-04-01",
-            "name": "[variables('hostingPlanName')]",
-            "location": "[resourceGroup().location]",
-            "properties": {
-                "name": "[variables('hostingPlanName')]",
-                "computeMode": "Dynamic",
-                "sku": "Dynamic"
-            }
-        },
-        {
-            "apiVersion": "2015-08-01",
-            "type": "Microsoft.Web/sites",
-            "name": "[variables('functionAppName')]",
-            "location": "[resourceGroup().location]",
-            "kind": "functionapp",
-            "dependsOn": [
-                "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
-                "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]",
-                "[resourceId('Microsoft.insights/components', variables('insightsName'))]"
-            ],
-            "properties": {
-                "clientAffinityEnabled": false,
-                "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
-                "siteConfig": {
-                    "appSettings": [
-                        {
-                            "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
-                            "value": "[reference(variables('insightsName')).InstrumentationKey]"
-                        },
-                        {
-                            "name": "AzureWebJobsDashboard",
-                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]"
-                        },
-                        {
-                            "name": "AzureWebJobsStorage",
-                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]"
-                        },
-                        {
-                            "name": "FUNCTIONS_EXTENSION_VERSION",
-                            "value": "~2"
-                        },
-                        {
-                            "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
-                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]"
-                        },
-                        {
-                            "name": "WEBSITE_CONTENTSHARE",
-                            "value": "[toLower(variables('functionAppName'))]"
-                        },
-                        {
-                            "name": "WEBSITE_NODE_DEFAULT_VERSION",
-                            "value": "8.11.1"
-                        },
-                        {
-                            "name": "WEBSITE_RUN_FROM_PACKAGE",
-                            "value": "1"
-                        }
-                    ]
-                }
-            }
-        }
-    ]
-}
diff --git a/src/provider/armTemplates/azuredeployWithGit.json b/src/provider/armTemplates/azuredeployWithGit.json
deleted file mode 100644
index 651f918..0000000
--- a/src/provider/armTemplates/azuredeployWithGit.json
+++ /dev/null
@@ -1,131 +0,0 @@
-{
-    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
-    "contentVersion": "1.0.0.0",
-    "parameters": {
-        "functionAppName": {
-            "type": "string",
-            "metadata": {
-                "description": "The name of the function app that you wish to create."
-            }
-        },
-        "storageAccountType": {
-            "type": "string",
-            "defaultValue": "Standard_LRS",
-            "allowedValues": [
-                "Standard_LRS",
-                "Standard_GRS",
-                "Standard_ZRS",
-                "Premium_LRS"
-            ],
-            "metadata": {
-                "description": "Storage Account type"
-            }
-        },
-        "gitUrl": {
-            "type": "string",
-            "metadata": {
-                "description": "Git URL"
-            }
-        }
-    },
-    "variables": {
-        "functionAppName": "[parameters('functionAppName')]",
-        "hostingPlanName": "[parameters('functionAppName')]",
-        "insightsName": "[concat(parameters('functionAppName'), '-insights')]",
-        "storageAccountName": "[concat(uniquestring(resourceGroup().id), 'azfunctions')]",
-        "storageAccountid": "[concat(resourceGroup().id,'/providers/','Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]",
-        "gitRepoUrl": "[parameters('gitUrl')]"
-    },
-    "resources": [
-        {
-            "type": "microsoft.insights/components",
-            "kind": "Node.JS",
-            "name": "[variables('insightsName')]",
-            "apiVersion": "2014-04-01",
-            "location": "eastus",
-            "properties": {
-                "ApplicationId": "[variables('insightsName')]"
-            }
-        },
-        {
-            "type": "Microsoft.Storage/storageAccounts",
-            "name": "[variables('storageAccountName')]",
-            "apiVersion": "2015-06-15",
-            "location": "[resourceGroup().location]",
-            "properties": {
-                "accountType": "[parameters('storageAccountType')]"
-            }
-        },
-        {
-            "type": "Microsoft.Web/serverfarms",
-            "apiVersion": "2015-04-01",
-            "name": "[variables('hostingPlanName')]",
-            "location": "[resourceGroup().location]",
-            "properties": {
-                "name": "[variables('hostingPlanName')]",
-                "computeMode": "Dynamic",
-                "sku": "Dynamic"
-            }
-        },
-        {
-            "apiVersion": "2015-08-01",
-            "name": "web",
-            "type": "sourcecontrols",
-            "dependsOn": [
-                "[resourceId('Microsoft.Web/sites', variables('functionAppName'))]"
-            ],
-            "properties": {
-                "repoUrl": "[variables('gitRepoUrl')]",
-                "IsManualIntegration": true
-            }
-        },
-        {
-            "apiVersion": "2015-08-01",
-            "type": "Microsoft.Web/sites",
-            "name": "[variables('functionAppName')]",
-            "location": "[resourceGroup().location]",
-            "kind": "functionapp",
-            "dependsOn": [
-                "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
-                "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]",
-                "[resourceId('Microsoft.insights/components', variables('insightsName'))]"
-            ],
-            "properties": {
-                "clientAffinityEnabled": false,
-                "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
-                "siteConfig": {
-                    "appSettings": [
-                        {
-                            "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
-                            "value": "[reference(variables('insightsName')).InstrumentationKey]"
-                        },
-                        {
-                            "name": "AzureWebJobsDashboard",
-                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]"
-                        },
-                        {
-                            "name": "AzureWebJobsStorage",
-                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]"
-                        },
-                        {
-                            "name": "FUNCTIONS_EXTENSION_VERSION",
-                            "value": "~1"
-                        },
-                        {
-                            "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING",
-                            "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]"
-                        },
-                        {
-                            "name": "WEBSITE_CONTENTSHARE",
-                            "value": "[toLower(variables('functionAppName'))]"
-                        },
-                        {
-                            "name": "WEBSITE_NODE_DEFAULT_VERSION",
-                            "value": "6.5.0"
-                        }
-                    ]
-                }
-            }
-        }
-    ]
-}
diff --git a/src/services/apimService.ts b/src/services/apimService.ts
index a6452f9..6126a70 100644
--- a/src/services/apimService.ts
+++ b/src/services/apimService.ts
@@ -17,18 +17,22 @@ import { Guard } from "../shared/guard";
 export class ApimService extends BaseService {
   private apimClient: ApiManagementClient;
   private functionAppService: FunctionAppService;
-  private config: ApiManagementConfig;
+  private apimConfig: ApiManagementConfig;
 
   public constructor(serverless: Serverless, options?: Serverless.Options) {
     super(serverless, options);
 
-    this.config = this.serverless.service.provider["apim"];
-    if (!this.config) {
+    this.apimConfig = this.config.provider.apim;
+    if (!this.apimConfig) {
       return;
     }
 
-    if (!this.config.backend) {
-      this.config.backend = {} as any;
+    if (!this.apimConfig.name) {
+      this.apimConfig.name = `${this.config.provider.prefix}-${this.config.provider.region}-${this.config.provider.stage}-apim`
+    }
+
+    if (!this.apimConfig.backend) {
+      this.apimConfig.backend = {} as any;
     }
 
     this.apimClient = new ApiManagementClient(this.credentials, this.subscriptionId);
@@ -39,24 +43,24 @@ export class ApimService extends BaseService {
    * Gets the configured APIM resource
    */
   public async get(): Promise<ApiManagementServiceResource> {
-    if (!(this.config && this.config.name)) {
+    if (!(this.apimConfig && this.apimConfig.name)) {
       return null;
     }
 
     try {
-      return await this.apimClient.apiManagementService.get(this.resourceGroup, this.config.name);
+      return await this.apimClient.apiManagementService.get(this.resourceGroup, this.apimConfig.name);
     } catch (err) {
       return null;
     }
   }
 
   public async getApi(): Promise<ApiContract> {
-    if (!(this.config && this.config.api && this.config.api.name)) {
+    if (!(this.apimConfig && this.apimConfig.api && this.apimConfig.api.name)) {
       return null;
     }
 
     try {
-      return await this.apimClient.api.get(this.resourceGroup, this.config.name, this.config.api.name);
+      return await this.apimClient.api.get(this.resourceGroup, this.apimConfig.name, this.apimConfig.api.name);
     } catch (err) {
       return null;
     }
@@ -66,7 +70,7 @@ export class ApimService extends BaseService {
    * Deploys the APIM top level api
    */
   public async deployApi() {
-    if (!(this.config && this.config.name)) {
+    if (!(this.apimConfig && this.apimConfig.name)) {
       return null;
     }
 
@@ -86,7 +90,7 @@ export class ApimService extends BaseService {
     Guard.null(service);
     Guard.null(api);
 
-    if (!(this.config && this.config.name)) {
+    if (!(this.apimConfig && this.apimConfig.name)) {
       return null;
     }
 
@@ -131,21 +135,21 @@ export class ApimService extends BaseService {
     this.log("-> Deploying API");
 
     try {
-      const api = await this.apimClient.api.createOrUpdate(this.resourceGroup, this.config.name, this.config.api.name, {
+      const api = await this.apimClient.api.createOrUpdate(this.resourceGroup, this.apimConfig.name, this.apimConfig.api.name, {
         isCurrent: true,
-        subscriptionRequired: this.config.api.subscriptionRequired,
-        displayName: this.config.api.displayName,
-        description: this.config.api.description,
-        path: this.config.api.path,
-        protocols: this.config.api.protocols,
+        subscriptionRequired: this.apimConfig.api.subscriptionRequired,
+        displayName: this.apimConfig.api.displayName,
+        description: this.apimConfig.api.description,
+        path: this.apimConfig.api.path,
+        protocols: this.apimConfig.api.protocols,
       });
 
-      if (this.config.cors) {
+      if (this.apimConfig.cors) {
         this.log("-> Deploying CORS policy");
 
-        await this.apimClient.apiPolicy.createOrUpdate(this.resourceGroup, this.config.name, this.config.api.name, {
+        await this.apimClient.apiPolicy.createOrUpdate(this.resourceGroup, this.apimConfig.name, this.apimConfig.api.name, {
           format: "rawxml",
-          value: this.createCorsXmlPolicy(this.config.cors)
+          value: this.createCorsXmlPolicy(this.apimConfig.cors)
         });
       }
 
@@ -168,17 +172,17 @@ export class ApimService extends BaseService {
     try {
       const functionAppResourceId = `https://management.azure.com${functionApp.id}`;
 
-      return await this.apimClient.backend.createOrUpdate(this.resourceGroup, this.config.name, this.serviceName, {
+      return await this.apimClient.backend.createOrUpdate(this.resourceGroup, this.apimConfig.name, this.serviceName, {
         credentials: {
           header: {
             "x-functions-key": [`{{${this.serviceName}-key}}`],
           },
         },
-        title: this.config.backend.title || functionApp.name,
-        tls: this.config.backend.tls,
-        proxy: this.config.backend.proxy,
-        description: this.config.backend.description,
-        protocol: this.config.backend.protocol || "http",
+        title: this.apimConfig.backend.title || functionApp.name,
+        tls: this.apimConfig.backend.tls,
+        proxy: this.apimConfig.backend.proxy,
+        description: this.apimConfig.backend.description,
+        protocol: this.apimConfig.backend.protocol || "http",
         resourceId: functionAppResourceId,
         url: backendUrl,
       });
@@ -216,13 +220,13 @@ export class ApimService extends BaseService {
 
       const operation = await client.apiOperation.createOrUpdate(
         this.resourceGroup,
-        this.config.name,
-        this.config.api.name,
+        this.apimConfig.name,
+        this.apimConfig.api.name,
         options.function,
         operationConfig,
       );
 
-      await client.apiOperationPolicy.createOrUpdate(this.resourceGroup, this.config.name, this.config.api.name, options.function, {
+      await client.apiOperationPolicy.createOrUpdate(this.resourceGroup, this.apimConfig.name, this.apimConfig.api.name, options.function, {
         format: "rawxml",
         value: this.createApiOperationXmlPolicy(),
       });
@@ -245,7 +249,7 @@ export class ApimService extends BaseService {
       const masterKey = await this.functionAppService.getMasterKey(functionApp);
       const keyName = `${this.serviceName}-key`;
 
-      return await this.apimClient.property.createOrUpdate(this.resourceGroup, this.config.name, keyName, {
+      return await this.apimClient.property.createOrUpdate(this.resourceGroup, this.apimConfig.name, keyName, {
         displayName: keyName,
         secret: true,
         value: masterKey,
diff --git a/src/services/armService.ts b/src/services/armService.ts
new file mode 100644
index 0000000..ce13d0c
--- /dev/null
+++ b/src/services/armService.ts
@@ -0,0 +1,149 @@
+import Serverless from "serverless";
+import { Deployment, DeploymentExtended } from "@azure/arm-resources/esm/models";
+import { BaseService } from "./baseService";
+import { ResourceManagementClient } from "@azure/arm-resources";
+import { Guard } from "../shared/guard";
+import { ServerlessAzureConfig, ArmTemplateConfig } from "../models/serverless";
+import { ArmDeployment, ArmResourceTemplateGenerator, ArmTemplateType } from "../models/armTemplates";
+import fs from "fs";
+import path from "path";
+import jsonpath from "jsonpath";
+
+export class ArmService extends BaseService {
+  private resourceClient: ResourceManagementClient;
+
+  public constructor(serverless: Serverless) {
+    super(serverless);
+    this.resourceClient = new ResourceManagementClient(this.credentials, this.subscriptionId);
+  }
+
+  /**
+   * Creates an ARM deployment from a well-known configuration (consumption, premium, ase)
+   * @param type The well-known template type
+   */
+  public async createDeploymentFromType(type: ArmTemplateType | string): Promise<ArmDeployment> {
+    Guard.empty(type);
+
+    this.log(`-> Creating ARM template from type: ${type}`);
+
+    const { ApimResource } = await import("../armTemplates/resources/apim");
+    const apimResource = new ApimResource();
+    let template: ArmResourceTemplateGenerator;
+
+    try {
+      template = (await import(`../armTemplates/${type}`)).default;
+    } catch (e) {
+      throw new Error(`Unable to find template with name ${type} `);
+    }
+
+    const azureConfig: ServerlessAzureConfig = this.serverless.service as any;
+
+    const mergedTemplate = template.getTemplate();
+    let parameters = template.getParameters(azureConfig);
+
+    if (this.config.provider.apim) {
+      const apimTemplate = apimResource.getTemplate();
+      const apimParameters = apimResource.getParameters(azureConfig);
+
+      mergedTemplate.parameters = {
+        ...mergedTemplate.parameters,
+        ...apimTemplate.parameters,
+      };
+      mergedTemplate.resources = [
+        ...mergedTemplate.resources,
+        ...apimTemplate.resources,
+      ];
+
+      parameters = {
+        ...parameters,
+        ...apimParameters,
+      };
+    }
+
+    return {
+      template: mergedTemplate,
+      parameters,
+    };
+  }
+
+  /**
+   * Creates an ARM deployment from the serverless custom yaml configuration
+   * @param armTemplateConfig The serverless yaml ARM template configuration
+   */
+  public createDeploymentFromConfig(armTemplateConfig: ArmTemplateConfig): Promise<ArmDeployment> {
+    Guard.null(armTemplateConfig);
+
+    this.log(`-> Creating ARM template from file: ${armTemplateConfig.file}`);
+    const templateFilePath = path.join(this.serverless.config.servicePath, armTemplateConfig.file);
+    const template = JSON.parse(fs.readFileSync(templateFilePath, "utf8"));
+
+    return Promise.resolve({
+      template,
+      parameters: armTemplateConfig.parameters
+    });
+  }
+
+  /**
+   * Deploys the specified ARM template to Azure via REST service call
+   * @param deployment The ARM template to deploy
+   */
+  public async deployTemplate(deployment: ArmDeployment): Promise<DeploymentExtended> {
+    Guard.null(deployment);
+
+    this.applyEnvironmentVariables(deployment);
+
+    // Convert flat parameter list into ARM parameter format
+    const deploymentParameters = {};
+    Object.keys(deployment.parameters).forEach((key) => {
+      const parameterValue = deployment.parameters[key];
+      if (parameterValue) {
+        deploymentParameters[key] = { value: deployment.parameters[key] };
+      }
+    });
+
+    // Construct deployment object
+    const armDeployment: Deployment = {
+      properties: {
+        mode: "Incremental",
+        template: deployment.template,
+        parameters: deploymentParameters,
+      }
+    };
+
+    // Deploy ARM template
+    this.serverless.cli.log("-> Deploying ARM template...");
+    const result = await this.resourceClient.deployments.createOrUpdate(this.resourceGroup, this.deploymentName, armDeployment);
+    this.serverless.cli.log("-> ARM deployment complete");
+
+    return result;
+  }
+
+  /**
+   * Applies sls yaml environment variables into the appSettings section of the function app configuration
+   * @param deployment The ARM deployment
+   */
+  private applyEnvironmentVariables(deployment: ArmDeployment) {
+    // Check if there are custom environment variables defined that need to be
+    // added to the ARM template used in the deployment.
+    const environmentVariables = this.config.provider.environment;
+    if (environmentVariables) {
+      this.serverless.cli.log("-> Merging environment configuration");
+
+      // This is a json path expression
+      // Learn more @ https://goessner.net/articles/JsonPath/index.html#e2
+      const appSettingsPath = "$.resources[?(@.kind==\"functionapp\")].properties.siteConfig.appSettings";
+
+      // Merges serverless yaml environment configuration into the app settings of the template
+      jsonpath.apply(deployment.template, appSettingsPath, function (appSettingsList) {
+        Object.keys(environmentVariables).forEach(function (key) {
+          appSettingsList.push({
+            name: key,
+            value: environmentVariables[key]
+          });
+        });
+
+        return appSettingsList;
+      });
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/services/baseService.ts b/src/services/baseService.ts
index 4a81638..44b4231 100644
--- a/src/services/baseService.ts
+++ b/src/services/baseService.ts
@@ -3,6 +3,7 @@ import fs from "fs";
 import request from "request";
 import Serverless from "serverless";
 import { Guard } from "../shared/guard";
+import { ServerlessAzureConfig } from "../models/serverless";
 
 export abstract class BaseService {
   protected baseUrl: string;
@@ -11,6 +12,7 @@ export abstract class BaseService {
   protected subscriptionId: string;
   protected resourceGroup: string;
   protected deploymentName: string;
+  protected config: ServerlessAzureConfig;
 
   protected constructor(
     protected serverless: Serverless,
@@ -19,24 +21,35 @@ export abstract class BaseService {
   ) {
     Guard.null(serverless);
 
+    this.setDefaultValues();
+
     this.baseUrl = "https://management.azure.com";
+    this.config = serverless.service as any;
     this.serviceName = serverless.service["service"];
     this.credentials = serverless.variables["azureCredentials"];
     this.subscriptionId = serverless.variables["subscriptionId"];
     this.resourceGroup = this.getResourceGroupName();
     this.deploymentName = serverless.service.provider["deploymentName"] || `${this.resourceGroup}-deployment`;
 
-    this.setDefaultValues();
-
     if (!this.credentials && authenticate) {
       throw new Error(`Azure Credentials has not been set in ${this.constructor.name}`);
     }
   }
 
-  public getResourceGroup(): string {
-    return this.resourceGroup;
+  public getRegion(): string {
+    return this.options.region || this.serverless.service.provider.region;
   }
   
+  public getStage(): string {
+    return this.options.stage || this.serverless.service.provider.stage;
+  }
+
+  public getResourceGroupName(): string {
+    return this.options["resourceGroup"]
+      || this.serverless.service.provider["resourceGroup"]
+      || `${this.config.provider.prefix}-${this.getRegion()}-${this.getStage()}-${this.serviceName}-rg`;
+  }
+
   /**
    * Sends an API request using axios HTTP library
    * @param method The HTTP method
@@ -105,19 +118,9 @@ export abstract class BaseService {
     if (!this.serverless.service.provider.stage) {
       this.serverless.service.provider.stage = "dev";
     }
-  }
-
-  protected getRegion(): string {
-    return this.options.region || this.serverless.service.provider.region;
-  }
 
-  protected getStage(): string {
-    return this.options.stage || this.serverless.service.provider.stage;
-  }
-
-  protected getResourceGroupName(): string {
-    return this.options["resourceGroup"]
-      || this.serverless.service.provider["resourceGroup"]
-      || `${this.serviceName}-${this.getRegion()}-${this.getStage()}-rg`;
+    if (!this.serverless.service.provider["prefix"]) {
+      this.serverless.service.provider["prefix"] = "sls";
+    }
   }
 }
diff --git a/src/services/functionAppService.ts b/src/services/functionAppService.ts
index 4237f37..b4d26f6 100644
--- a/src/services/functionAppService.ts
+++ b/src/services/functionAppService.ts
@@ -1,29 +1,25 @@
 import fs from "fs";
 import path from "path";
 import { WebSiteManagementClient } from "@azure/arm-appservice";
-import { ResourceManagementClient } from "@azure/arm-resources";
-import { Deployment } from "@azure/arm-resources/esm/models";
-import jsonpath from "jsonpath";
-import _ from "lodash";
 import Serverless from "serverless";
 import { BaseService } from "./baseService";
 import { FunctionAppHttpTriggerConfig } from "../models/functionApp";
 import { Site, FunctionEnvelope } from "@azure/arm-appservice/esm/models";
 import { Guard } from "../shared/guard";
+import { ArmService } from "./armService";
+import { ArmDeployment } from "../models/armTemplates";
+import { FunctionAppResource } from "../armTemplates/resources/functionApp";
 
 export class FunctionAppService extends BaseService {
-  private resourceClient: ResourceManagementClient;
   private webClient: WebSiteManagementClient;
 
   public constructor(serverless: Serverless, options: Serverless.Options) {
     super(serverless, options);
-
-    this.resourceClient = new ResourceManagementClient(this.credentials, this.subscriptionId);
     this.webClient = new WebSiteManagementClient(this.credentials, this.subscriptionId);
   }
 
   public async get(): Promise<Site> {
-    const response: any = await this.webClient.webApps.get(this.resourceGroup, this.serviceName);
+    const response: any = await this.webClient.webApps.get(this.resourceGroup, FunctionAppResource.getResourceName(this.config));
     if (response.error && (response.error.code === "ResourceNotFound" || response.error.code === "ResourceGroupNotFound")) {
       return null;
     }
@@ -111,7 +107,7 @@ export class FunctionAppService extends BaseService {
   }
 
   public async uploadFunctions(functionApp: Site): Promise<any> {
-    Guard.null(functionApp);
+    Guard.null(functionApp, "functionApp");
 
     this.log("Deploying serverless functions...");
     await this.zipDeploy(functionApp);
@@ -123,67 +119,13 @@ export class FunctionAppService extends BaseService {
    */
   public async deploy() {
     this.log(`Creating function app: ${this.serviceName}`);
-    let parameters: any = { functionAppName: { value: this.serviceName } };
-
-    const gitUrl = this.serverless.service.provider["gitUrl"];
-
-    if (gitUrl) {
-      parameters = {
-        functionAppName: { value: this.serviceName },
-        gitUrl: { value: gitUrl }
-      };
-    }
-
-    let templateFilePath = path.join(__dirname, "..", "provider", "armTemplates", "azuredeploy.json");
-
-    if (gitUrl) {
-      templateFilePath = path.join(__dirname, "armTemplates", "azuredeployWithGit.json");
-    }
-
-    if (this.serverless.service.provider["armTemplate"]) {
-      this.log(`-> Deploying custom ARM template: ${this.serverless.service.provider["armTemplate"].file}`);
-      templateFilePath = path.join(this.serverless.config.servicePath, this.serverless.service.provider["armTemplate"].file);
-      const userParameters = this.serverless.service.provider["armTemplate"].parameters;
-      const userParametersKeys = Object.keys(userParameters);
-
-      for (let paramIndex = 0; paramIndex < userParametersKeys.length; paramIndex++) {
-        const item = {};
 
-        item[userParametersKeys[paramIndex]] = { "value": userParameters[userParametersKeys[paramIndex]] };
-        parameters = _.merge(parameters, item);
-      }
-    }
-
-    let template = JSON.parse(fs.readFileSync(templateFilePath, "utf8"));
-
-    // Check if there are custom environment variables defined that need to be
-    // added to the ARM template used in the deployment.
-    const environmentVariables = this.serverless.service.provider["environment"];
-    if (environmentVariables) {
-      const appSettingsPath = "$.resources[?(@.kind==\"functionapp\")].properties.siteConfig.appSettings";
-
-      jsonpath.apply(template, appSettingsPath, function (appSettingsList) {
-        Object.keys(environmentVariables).forEach(function (key) {
-          appSettingsList.push({
-            name: key,
-            value: environmentVariables[key]
-          });
-        });
-
-        return appSettingsList;
-      });
-    }
-
-    const deploymentParameters: Deployment = {
-      properties: {
-        mode: "Incremental",
-        parameters,
-        template
-      }
-    };
+    const armService = new ArmService(this.serverless);
+    let deployment: ArmDeployment = this.config.provider.armTemplate
+      ? await armService.createDeploymentFromConfig(this.config.provider.armTemplate)
+      : await armService.createDeploymentFromType(this.config.provider.type || "consumption");
 
-    // Deploy ARM template
-    await this.resourceClient.deployments.createOrUpdate(this.resourceGroup, this.deploymentName, deploymentParameters);
+    await armService.deployTemplate(deployment);
 
     // Return function app 
     return await this.get();


Test patch:
diff --git a/src/plugins/deploy/azureDeployPlugin.test.ts b/src/plugins/deploy/azureDeployPlugin.test.ts
index 3c3db2f..f4ae441 100644
--- a/src/plugins/deploy/azureDeployPlugin.test.ts
+++ b/src/plugins/deploy/azureDeployPlugin.test.ts
@@ -58,7 +58,7 @@ describe("Deploy plugin", () => {
     const sls = MockFactory.createTestServerless();
     const resourceGroup = "rg1";
     ResourceService.prototype.getDeployments = jest.fn(() => Promise.resolve([])) as any;
-    ResourceService.prototype.getResourceGroup = jest.fn(() => resourceGroup);
+    ResourceService.prototype.getResourceGroupName = jest.fn(() => resourceGroup);
     const options = MockFactory.createTestServerlessOptions();
     const plugin = new AzureDeployPlugin(sls, options);
     await invokeHook(plugin, "deploy:list:list");
diff --git a/src/services/apimService.test.ts b/src/services/apimService.test.ts
index 1b14fe4..028b8ec 100644
--- a/src/services/apimService.test.ts
+++ b/src/services/apimService.test.ts
@@ -1,7 +1,6 @@
 import Serverless from "serverless";
 import _ from "lodash";
 import { MockFactory } from "../test/mockFactory";
-import { ApiManagementConfig } from "../models/apiManagement";
 import { ApimService } from "./apimService";
 import { interpolateJson } from "../test/utils";
 import axios from "axios";
@@ -22,18 +21,7 @@ import {
 } from "@azure/arm-apimanagement/esm/models";
 
 describe("APIM Service", () => {
-  const apimConfig: ApiManagementConfig = {
-    name: "test-apim-resource",
-    api: {
-      name: "test-apim-api1",
-      subscriptionRequired: false,
-      displayName: "API 1",
-      description: "description of api 1",
-      protocols: ["https"],
-      path: "test-api1",
-    },
-  };
-
+  const apimConfig = MockFactory.createTestApimConfig();
   let serverless: Serverless;
 
   beforeEach(() => {
diff --git a/src/services/armService.test.ts b/src/services/armService.test.ts
new file mode 100644
index 0000000..b61c48c
--- /dev/null
+++ b/src/services/armService.test.ts
@@ -0,0 +1,196 @@
+import Serverless from "serverless";
+import { MockFactory } from "../test/mockFactory";
+import { ArmService } from "./armService";
+import { ArmResourceTemplate, ArmTemplateType } from "../models/armTemplates";
+import { ArmTemplateConfig } from "../models/serverless";
+import mockFs from "mock-fs";
+import jsonpath from "jsonpath";
+import { Deployments } from "@azure/arm-resources";
+import { Deployment } from "@azure/arm-resources/esm/models";
+
+describe("Arm Service", () => {
+  let sls: Serverless
+  let service: ArmService;
+
+  function createService() {
+    return new ArmService(sls);
+  }
+
+  beforeEach(() => {
+    sls = MockFactory.createTestServerless();
+    sls.service.provider["prefix"] = "myapp";
+    sls.service.provider.region = "westus";
+    sls.service.provider.stage = "dev";
+    sls.variables = {
+      ...sls.variables,
+      azureCredentials: MockFactory.createTestAzureCredentials(),
+      subscriptionId: "ABC123",
+    };
+
+    service = createService();
+  })
+
+  afterEach(() => {
+    mockFs.restore();
+  })
+
+  describe("Creating Templates", () => {
+    it("Creates an ARM template from a specified file", async () => {
+      const armTemplateConfig: ArmTemplateConfig = {
+        file: "armTemplates/custom-template.json",
+        parameters: {
+          param1: "1",
+          param2: "2",
+        },
+      };
+
+      const testTemplate: ArmResourceTemplate = MockFactory.createTestArmTemplate();
+
+      mockFs({
+        "armTemplates": {
+          "custom-template.json": JSON.stringify(testTemplate),
+        },
+      });
+
+      sls.service.provider["armTemplate"] = armTemplateConfig;
+      const deployment = await service.createDeploymentFromConfig(sls.service.provider["armTemplate"]);
+
+      expect(deployment).not.toBeNull();
+      expect(deployment.template.parameters).toEqual(testTemplate.parameters);
+      expect(deployment.template.resources).toEqual(testTemplate.resources);
+      expect(deployment.parameters).toEqual(armTemplateConfig.parameters);
+    });
+
+    it("Creates a custom ARM template from well-known type", async () => {
+      const deployment = await service.createDeploymentFromType("premium");
+
+      expect(deployment).not.toBeNull();
+      expect(Object.keys(deployment.parameters).length).toBeGreaterThan(0);
+      expect(deployment.template.resources.length).toBeGreaterThan(0);
+    });
+
+    it("Creates a custom ARM template (with APIM support) from well-known type", async () => {
+      sls.service.provider["apim"] = MockFactory.createTestApimConfig();
+      const deployment = await service.createDeploymentFromType(ArmTemplateType.Premium);
+
+      expect(deployment).not.toBeNull();
+      expect(Object.keys(deployment.parameters).length).toBeGreaterThan(0);
+      expect(deployment.template.resources.length).toBeGreaterThan(0);
+
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.ApiManagement/service")).not.toBeNull();
+    });
+
+    it("throws error when specified type is not found", async () => {
+      await expect(service.createDeploymentFromType("not-found")).rejects.not.toBeNull();
+    });
+
+    it("Premium template includes correct resources", async () => {
+      const deployment = await service.createDeploymentFromType(ArmTemplateType.Premium);
+
+      expect(deployment.template.parameters.appServicePlanSkuTier.defaultValue).toEqual("ElasticPremium");
+      expect(deployment.template.parameters.appServicePlanSkuName.defaultValue).toEqual("EP1");
+
+      // Should not contain
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Web/hostingEnvironments")).toBeUndefined();
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Network/virtualNetworks")).toBeUndefined();
+
+      // Should contain
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Web/serverfarms")).not.toBeNull();
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Web/sites")).not.toBeNull();
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Storage/storageAccounts")).not.toBeNull();
+      expect(deployment.template.resources.find((resource) => resource.type === "microsoft.insights/components")).not.toBeNull();
+
+      // Verify the ARM template includes the linkage to the correct server farm
+      const functionApp = deployment.template.resources.find((res) => res.type === "Microsoft.Web/sites");
+      expect(functionApp.dependsOn).toContain("[concat('Microsoft.Web/serverfarms/', parameters('appServicePlanName'))]");
+      expect(functionApp.properties.serverFarmId).toEqual("[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]");
+    });
+
+    it("ASE template includes correct resources", async () => {
+      const deployment = await service.createDeploymentFromType(ArmTemplateType.AppServiceEnvironment);
+
+      expect(deployment.template.parameters.appServicePlanSkuTier.defaultValue).toEqual("Isolated");
+      expect(deployment.template.parameters.appServicePlanSkuName.defaultValue).toEqual("I1");
+
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Web/hostingEnvironments")).not.toBeNull();
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Network/virtualNetworks")).not.toBeNull();
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Web/serverfarms")).not.toBeNull();
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Web/sites")).not.toBeNull();
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Storage/storageAccounts")).not.toBeNull();
+      expect(deployment.template.resources.find((resource) => resource.type === "microsoft.insights/components")).not.toBeNull();
+
+      // Verify the ARM template includes the linkage to the correct server farm
+      const appServicePlan = deployment.template.resources.find((res) => res.type === "Microsoft.Web/serverfarms");
+      expect(appServicePlan.dependsOn).toContain("[resourceId('Microsoft.Web/hostingEnvironments', parameters('hostingEnvironmentName'))]");
+      expect(appServicePlan.properties.hostingEnvironmentProfile.id).toEqual("[resourceId('Microsoft.Web/hostingEnvironments', parameters('hostingEnvironmentName'))]");
+
+      // Verify the ARM template includes the linkage to the correct hosting environment
+      const functionApp = deployment.template.resources.find((res) => res.type === "Microsoft.Web/sites");
+      expect(functionApp.dependsOn).toContain("[concat('Microsoft.Web/serverfarms/', parameters('appServicePlanName'))]");
+      expect(functionApp.properties.serverFarmId).toEqual("[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]");
+      expect(functionApp.properties.hostingEnvironmentProfile.id).toEqual("[resourceId('Microsoft.Web/hostingEnvironments', parameters('hostingEnvironmentName'))]");
+    });
+
+    it("Consumption template includes correct resources", async () => {
+      const deployment = await service.createDeploymentFromType(ArmTemplateType.Consumption);
+
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Web/hostingEnvironments")).toBeUndefined();
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Network/virtualNetworks")).toBeUndefined();
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Web/serverfarms")).toBeUndefined();
+
+      // Should contain
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Web/sites")).not.toBeNull();
+      expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Storage/storageAccounts")).not.toBeNull();
+      expect(deployment.template.resources.find((resource) => resource.type === "microsoft.insights/components")).not.toBeNull();
+    });
+  });
+
+  describe("Deploying Templates", () => {
+    beforeEach(() => {
+      Deployments.prototype.createOrUpdate = jest.fn(() => Promise.resolve(null));
+    });
+
+    it("Appends environment variables into app settings of ARM template", async () => {
+      const environmentConfig: any = {
+        PARAM_1: "1",
+        PARAM_2: "2",
+        PARAM_3: "3",
+      };
+
+      sls.service.provider["environment"] = environmentConfig
+
+      const deployment = await service.createDeploymentFromType(ArmTemplateType.Consumption);
+      await service.deployTemplate(deployment);
+
+      const appSettings: any[] = jsonpath.query(deployment.template, "$.resources[?(@.kind==\"functionapp\")].properties.siteConfig.appSettings[*]");
+      expect(appSettings.find((setting) => setting.name === "PARAM_1")).toEqual({ name: "PARAM_1", value: environmentConfig.PARAM_1 });
+      expect(appSettings.find((setting) => setting.name === "PARAM_2")).toEqual({ name: "PARAM_2", value: environmentConfig.PARAM_2 });
+      expect(appSettings.find((setting) => setting.name === "PARAM_3")).toEqual({ name: "PARAM_3", value: environmentConfig.PARAM_3 });
+    });
+
+    it("Deploys ARM template via resources REST API", async () => {
+      const deployment = await service.createDeploymentFromType(ArmTemplateType.Consumption);
+      const deploymentParameters = {};
+      Object.keys(deployment.parameters).forEach((key) => {
+        const parameterValue = deployment.parameters[key];
+        if (parameterValue) {
+          deploymentParameters[key] = { value: deployment.parameters[key] };
+        }
+      });
+
+      await service.deployTemplate(deployment);
+
+      const expectedResourceGroup = sls.service.provider["resourceGroup"];
+      const expectedDeploymentName = sls.service.provider["deploymentName"] || `${this.resourceGroup}-deployment`;
+      const expectedDeployment: Deployment = {
+        properties: {
+          mode: "Incremental",
+          template: deployment.template,
+          parameters: deploymentParameters,
+        },
+      };
+
+      expect(Deployments.prototype.createOrUpdate).toBeCalledWith(expectedResourceGroup, expectedDeploymentName, expectedDeployment);
+    });
+  });
+});
\ No newline at end of file
diff --git a/src/services/baseService.test.ts b/src/services/baseService.test.ts
index ac3f65a..f4c83da 100644
--- a/src/services/baseService.test.ts
+++ b/src/services/baseService.test.ts
@@ -21,18 +21,6 @@ class MockService extends BaseService {
     return this.sendFile(requestOptions, filePath);
   }
 
-  public getSlsResourceGroupName() {
-    return this.getResourceGroupName();
-  }
-
-  public getSlsRegion() {
-    return this.getRegion();
-  }
-
-  public getSlsStage() {
-    return this.getStage();
-  }
-
   public getProperties() {
     return {
       baseUrl: this.baseUrl,
@@ -61,7 +49,7 @@ describe("Base Service", () => {
     mockFs.restore();
   });
 
-  function createTestService(options?: Serverless.Options) {
+  function createMockService(options?: Serverless.Options) {
     sls = MockFactory.createTestServerless();
     sls.variables["azureCredentials"] = MockFactory.createTestAzureCredentials();
     sls.variables["subscriptionId"] = "ABC123";
@@ -71,7 +59,7 @@ describe("Base Service", () => {
   }
 
   beforeEach(() => {
-    service = createTestService();
+    service = createMockService();
   });
 
   it("Initializes common service properties", () => {
@@ -85,9 +73,28 @@ describe("Base Service", () => {
   });
 
   it("Sets default region and stage values if not defined", () => {
-    const testService = new MockService(sls);
+    const mockService = new MockService(sls);
+
+    expect(mockService).not.toBeNull();
+    expect(sls.service.provider.region).toEqual("westus");
+    expect(sls.service.provider.stage).toEqual("dev");
+  });
+
+  it("returns region and stage based on CLI options", () => {
+    const cliOptions = {
+      stage: "prod",
+      region: "eastus2"
+    };
+    const mockService = new MockService(sls, cliOptions);
+
+    expect(mockService.getRegion()).toEqual(cliOptions.region);
+    expect(mockService.getStage()).toEqual(cliOptions.stage);
+  });
+
+  it("Sets default region and stage values if not defined", () => {
+    const mockService = new MockService(sls);
 
-    expect(testService).not.toBeNull();
+    expect(mockService).not.toBeNull();
     expect(sls.service.provider.region).toEqual("westus");
     expect(sls.service.provider.stage).toEqual("dev");
   });
@@ -97,27 +104,27 @@ describe("Base Service", () => {
       stage: "prod",
       region: "eastus2"
     };
-    const testService = new MockService(sls, cliOptions);
+    const mockService = new MockService(sls, cliOptions);
 
-    expect(testService.getSlsRegion()).toEqual(cliOptions.region);
-    expect(testService.getSlsStage()).toEqual(cliOptions.stage);
+    expect(mockService.getRegion()).toEqual(cliOptions.region);
+    expect(mockService.getStage()).toEqual(cliOptions.stage);
   });
 
   it("Generates resource group name from sls yaml config", () => {
-    const testService = new MockService(sls);
-    const resourceGroupName = testService.getSlsResourceGroupName();
+    const mockService = new MockService(sls);
+    const resourceGroupName = mockService.getResourceGroupName();
 
     expect(resourceGroupName).toEqual(sls.service.provider["resourceGroup"]);
   });
 
   it("Generates resource group from convention when NOT defined in sls yaml", () => {
     sls.service.provider["resourceGroup"] = null;
-    const testService = new MockService(sls);
-    const resourceGroupName = testService.getSlsResourceGroupName();
-    const region = testService.getSlsRegion();
-    const stage = testService.getSlsStage();
+    const mockService = new MockService(sls);
+    const resourceGroupName = mockService.getResourceGroupName();
+    const region = mockService.getRegion();
+    const stage = mockService.getStage();
 
-    expect(resourceGroupName).toEqual(`${sls.service["service"]}-${region}-${stage}-rg`);
+    expect(resourceGroupName).toEqual(`sls-${region}-${stage}-${sls.service["service"]}-rg`);
   });
 
   it("Fails if credentials have not been set in serverless config", () => {
diff --git a/src/services/functionAppService.test.ts b/src/services/functionAppService.test.ts
index fce61cb..51aa527 100644
--- a/src/services/functionAppService.test.ts
+++ b/src/services/functionAppService.test.ts
@@ -4,9 +4,12 @@ import mockFs from "mock-fs";
 import Serverless from "serverless";
 import { MockFactory } from "../test/mockFactory";
 import { FunctionAppService } from "./functionAppService";
+import { ArmService } from "./armService";
+import { FunctionAppResource } from "../armTemplates/resources/functionApp";
 
 jest.mock("@azure/arm-appservice")
 import { WebSiteManagementClient } from "@azure/arm-appservice";
+import { ArmDeployment, ArmTemplateType } from "../models/armTemplates";
 jest.mock("@azure/arm-resources")
 
 describe("Function App Service", () => {
@@ -33,7 +36,7 @@ describe("Function App Service", () => {
 
   beforeAll(() => {
 
-    // TODO: How to spy on defaul exported function?
+    // TODO: How to spy on default exported function?
     const axiosMock = new MockAdapter(axios);
 
     // Master Key
@@ -86,7 +89,7 @@ describe("Function App Service", () => {
     const service = createService();
     const result = await service.get();
     expect(WebSiteManagementClient.prototype.webApps.get)
-      .toBeCalledWith(provider.resourceGroup, slsService["service"]);
+      .toBeCalledWith(provider.resourceGroup, FunctionAppResource.getResourceName(slsService as any));
     expect(result).toEqual(app)
   });
 
@@ -98,7 +101,7 @@ describe("Function App Service", () => {
     } as any;
     const result = await service.get();
     expect(WebSiteManagementClient.prototype.webApps.get)
-      .toBeCalledWith(provider.resourceGroup, slsService["service"]);
+      .toBeCalledWith(provider.resourceGroup, FunctionAppResource.getResourceName(slsService as any));
     expect(result).toBeNull();
   });
 
@@ -140,6 +143,59 @@ describe("Function App Service", () => {
     expect(await service.listFunctions(app)).toEqual(functionsResponse.map((f) => f.properties));
   });
 
+  describe("Deployments", () => {
+    const expectedDeployment: ArmDeployment = {
+      parameters: {},
+      template: MockFactory.createTestArmTemplate(),
+    };
+
+    const expectedSite = MockFactory.createTestSite();
+
+    beforeEach(() => {
+      FunctionAppService.prototype.get = jest.fn(() => Promise.resolve(expectedSite));
+      ArmService.prototype.createDeploymentFromConfig = jest.fn(() => Promise.resolve(expectedDeployment));
+      ArmService.prototype.createDeploymentFromType = jest.fn(() => Promise.resolve(expectedDeployment));
+      ArmService.prototype.deployTemplate = jest.fn(() => Promise.resolve(null));
+    });
+
+    it("deploys ARM templates with custom configuration", async () => {
+      slsService.provider["armTemplate"] = {};
+
+      const service = createService();
+      const site = await service.deploy();
+
+      expect(site).toEqual(expectedSite);
+      expect(ArmService.prototype.createDeploymentFromConfig).toBeCalledWith(slsService.provider["armTemplate"]);
+      expect(ArmService.prototype.createDeploymentFromType).not.toBeCalled();
+      expect(ArmService.prototype.deployTemplate).toBeCalledWith(expectedDeployment);
+    });
+
+    it("deploys ARM template from well-known (default) configuration", async () => {
+      slsService.provider["armTemplate"] = null;
+
+      const service = createService();
+      const site = await service.deploy();
+
+      expect(site).toEqual(expectedSite);
+      expect(ArmService.prototype.createDeploymentFromConfig).not.toBeCalled();
+      expect(ArmService.prototype.createDeploymentFromType).toBeCalledWith(ArmTemplateType.Consumption);
+      expect(ArmService.prototype.deployTemplate).toBeCalledWith(expectedDeployment);
+    });
+
+    it("deploys ARM template from well-known configuration", async () => {
+      slsService.provider["armTemplate"] = null;
+      slsService.provider["type"] = "premium";
+
+      const service = createService();
+      const site = await service.deploy();
+
+      expect(site).toEqual(expectedSite);
+      expect(ArmService.prototype.createDeploymentFromConfig).not.toBeCalled();
+      expect(ArmService.prototype.createDeploymentFromType).toBeCalledWith(ArmTemplateType.Premium);
+      expect(ArmService.prototype.deployTemplate).toBeCalledWith(expectedDeployment);
+    });
+  });
+
   it("uploads functions", async () => {
     const service = createService();
     await service.uploadFunctions(app);
diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts
index 2a7ede2..d6fa7a8 100644
--- a/src/test/mockFactory.ts
+++ b/src/test/mockFactory.ts
@@ -12,8 +12,9 @@ import PluginManager from "serverless/lib/classes/PluginManager";
 import { ServerlessAzureConfig } from "../models/serverless";
 import { AzureServiceProvider, ServicePrincipalEnvVariables } from "../models/azureProvider"
 import { Logger } from "../models/generic";
-import { ApiCorsPolicy } from "../models/apiManagement";
+import { ApiCorsPolicy, ApiManagementConfig } from "../models/apiManagement";
 import { DeploymentsListByResourceGroupResponse } from "@azure/arm-resources/esm/models";
+import { ArmResourceTemplate } from "../models/armTemplates";
 
 function getAttribute(object: any, prop: string, defaultValue: any): any {
   if (object && object[prop]) {
@@ -145,7 +146,7 @@ export class MockFactory {
     const result = [];
     for (let i = 0; i < count; i++) {
       result.push({
-        name: `deployment${i+1}`,
+        name: `deployment${i + 1}`,
         properties: {
           timestamp: new Date(),
         }
@@ -206,7 +207,21 @@ export class MockFactory {
       functions: functionMetadata || MockFactory.createTestSlsFunctionConfig(),
     }
     return (asYaml) ? yaml.dump(data) : data;
-  } 
+  }
+
+  public static createTestApimConfig(): ApiManagementConfig {
+    return {
+      name: "test-apim-resource",
+      api: {
+        name: "test-apim-api1",
+        subscriptionRequired: false,
+        displayName: "API 1",
+        description: "description of api 1",
+        protocols: ["https"],
+        path: "test-api1",
+      },
+    };
+  }
 
   public static createTestFunctionApimConfig(name: string) {
     return {
@@ -432,7 +447,26 @@ export class MockFactory {
       allowedOrigins: ["*"],
       allowedHeaders: ["*"],
       exposeHeaders: ["*"],
-      allowedMethods: ["GET","POST"],
+      allowedMethods: ["GET", "POST"],
+    };
+  }
+
+  public static createTestArmTemplate(): ArmResourceTemplate {
+    return {
+      "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+      "contentVersion": "1.0.0.0",
+      "parameters": {
+        "param1": {
+          "defaultValue": "",
+          "type": "String"
+        },
+        "param2": {
+          "defaultValue": "",
+          "type": "String"
+        },
+      },
+      "variables": {},
+      "resources": []
     };
   }