Repository: serverless/serverless-azure-functions
Pull request #357 (2019-10-05 00:49:43)

release: Merge dev into master for release
Merge dev into master to prep for patch release


Patch:
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 4e19c59..e5e74fa 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,6 +1,6 @@
 ---
 name: Feature request
-about: Suggest an idea for serverless framework
+about: Suggest an idea for serverless-azure-functions
 ---
 
 <!--
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index dc53426..f617443 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -28,7 +28,7 @@ to review a PR, the faster we can review and merge it.
 Examples:
 * serverless.yml - Fully functioning to easily deploy changes
 * Screenshots - Showing the difference between your output and the master
-* Cloud Configuration - List cloud resources and show that the correct configuration is in place (e.g. AWS CLI commands)
+* Cloud Configuration - List cloud resources and show that the correct configuration is in place
 * Other - Anything else that comes to mind to help us evaluate
 -->
 
@@ -36,13 +36,12 @@ Examples:
 
 _**Note: Run `npm run test:ci` to run all validation checks on proposed changes**_
 
-- [ ] Write tests and confirm existing functionality is not broken.  
-       **Validate via `npm test`**
-- [ ] Write documentation
 - [ ] Ensure there are no lint errors.  
        **Validate via `npm run lint`**  
        _Note: Some reported issues can be automatically fixed by running `npm run lint:fix`_
-- [ ] Make sure code coverage hasn't dropped
+- [ ] Write tests and confirm existing functionality is not broken.  
+       **Validate via `npm test`**
+- [ ] Write documentation
 - [ ] Provide verification config / commands / resources
 - [ ] Enable "Allow edits from maintainers" for this PR
 - [ ] Update the messages below
diff --git a/.gitignore b/.gitignore
index c93070f..f707dcf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@ node_modules/
 /coverage/
 .env*
 .idea/
+
+.vscode/launch.json
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..5418960
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,35 @@
+{
+    "version": "0.2.0",
+    "configurations": [
+      {
+        "type": "node",
+        "request": "launch",
+        "name": "Jest All",
+        "program": "${workspaceFolder}/node_modules/.bin/jest",
+        "args": ["--runInBand"],
+        "console": "integratedTerminal",
+        "internalConsoleOptions": "neverOpen",
+        "disableOptimisticBPs": true,
+        "windows": {
+          "program": "${workspaceFolder}/node_modules/jest/bin/jest",
+        }
+      },
+      {
+        "type": "node",
+        "request": "launch",
+        "name": "Jest Current File",
+        "program": "${workspaceFolder}/node_modules/.bin/jest",
+        "args": [
+          "${fileBasenameNoExtension}",
+          "--config",
+          "jest.config.js"
+        ],
+        "console": "integratedTerminal",
+        "internalConsoleOptions": "neverOpen",
+        "disableOptimisticBPs": true,
+        "windows": {
+          "program": "${workspaceFolder}/node_modules/jest/bin/jest",
+        }
+      }
+    ]
+  }
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 8022893..af4c512 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "serverless-azure-functions",
-  "version": "1.0.0",
+  "version": "1.0.2-8",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index 562a64d..e7cb6f8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "serverless-azure-functions",
-  "version": "1.0.0",
+  "version": "1.0.2-8",
   "description": "Provider plugin for the Serverless Framework v1.x which adds support for Azure Functions.",
   "license": "MIT",
   "main": "./lib/index.js",
diff --git a/pipelines/ci.yml b/pipelines/ci.yml
index 93c73f2..f6e692d 100644
--- a/pipelines/ci.yml
+++ b/pipelines/ci.yml
@@ -7,13 +7,15 @@ pr:
   - dev
 
 variables:
-- group: code-coverage-credentials  # variable group
+  - group: code-coverage-credentials # variable group
 
 jobs:
-- template: templates/unit-tests.yml  # path relative to this file
-  parameters:
-    nodeVersion: 8
+  - template: templates/unit-tests.yml # path relative to this file
+    parameters:
+      nodeVersion: "8.16.1"
+      name: "Node_8"
 
-- template: templates/unit-tests.yml
-  parameters:
-    nodeVersion: 10
+  - template: templates/unit-tests.yml
+    parameters:
+      nodeVersion: 10.x
+      name: "Node_10"
diff --git a/src/armTemplates/resources/apim.ts b/src/armTemplates/resources/apim.ts
index 2576046..6869c3a 100644
--- a/src/armTemplates/resources/apim.ts
+++ b/src/armTemplates/resources/apim.ts
@@ -1,8 +1,16 @@
 import { ApiManagementConfig } from "../../models/apiManagement";
-import { ArmResourceTemplate, ArmResourceTemplateGenerator, ArmParamType, ArmParameters } from "../../models/armTemplates";
+import { ArmResourceTemplate, ArmResourceTemplateGenerator, ArmParamType, ArmParameters, ArmParameter, DefaultArmParams } from "../../models/armTemplates";
 import { ServerlessAzureConfig } from "../../models/serverless";
 import { AzureNamingService, AzureNamingServiceOptions } from "../../services/namingService";
 
+interface ApimArmParams extends DefaultArmParams {
+  apiManagementName: ArmParameter;
+  apimSkuName: ArmParameter;
+  apimSkuCapacity: ArmParameter;
+  apimPublisherEmail: ArmParameter;
+  apimPublisherName: ArmParameter;
+}
+
 export class ApimResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
     const options: AzureNamingServiceOptions = {
@@ -14,35 +22,36 @@ export class ApimResource implements ArmResourceTemplateGenerator {
   }
 
   public getTemplate(): ArmResourceTemplate {
+    const parameters: ApimArmParams = {
+      apiManagementName: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+      location: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+      apimSkuName: {
+        defaultValue: "Consumption",
+        type: ArmParamType.String
+      },
+      apimSkuCapacity: {
+        defaultValue: 0,
+        type: ArmParamType.Int
+      },
+      apimPublisherEmail: {
+        defaultValue: "contact@contoso.com",
+        type: ArmParamType.String
+      },
+      apimPublisherName: {
+        defaultValue: "Contoso",
+        type: ArmParamType.String
+      }
+    }
     return {
       "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
       "contentVersion": "1.0.0.0",
-      "parameters": {
-        "apiManagementName": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-        "location": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-        "apimSkuName": {
-          "defaultValue": "Consumption",
-          "type": ArmParamType.String
-        },
-        "apimCapacity": {
-          "defaultValue": 0,
-          "type": ArmParamType.Int
-        },
-        "apimPublisherEmail": {
-          "defaultValue": "contact@contoso.com",
-          "type": ArmParamType.String
-        },
-        "apimPublisherName": {
-          "defaultValue": "Contoso",
-          "type": ArmParamType.String
-        }
-      },
+      parameters,
       "variables": {},
       "resources": [
         {
@@ -52,7 +61,7 @@ export class ApimResource implements ArmResourceTemplateGenerator {
           "location": "[parameters('location')]",
           "sku": {
             "name": "[parameters('apimSkuName')]",
-            "capacity": "[parameters('apimCapacity')]"
+            "capacity": "[parameters('apimSkuCapacity')]"
           },
           "properties": {
             "publisherEmail": "[parameters('apimPublisherEmail')]",
@@ -72,7 +81,7 @@ export class ApimResource implements ArmResourceTemplateGenerator {
       ...config.provider.apim,
     };
 
-    return {
+    const parameters: ApimArmParams =  {
       apiManagementName: {
         value: ApimResource.getResourceName(config),
       },
@@ -89,5 +98,7 @@ export class ApimResource implements ArmResourceTemplateGenerator {
         value: apimConfig.publisherName,
       }
     };
+
+    return parameters as unknown as ArmParameters;
   }
 }
diff --git a/src/armTemplates/resources/appInsights.ts b/src/armTemplates/resources/appInsights.ts
index 0a4498a..1b02875 100644
--- a/src/armTemplates/resources/appInsights.ts
+++ b/src/armTemplates/resources/appInsights.ts
@@ -1,7 +1,11 @@
-import { ArmResourceTemplate, ArmResourceTemplateGenerator, ArmParamType, ArmParameters } from "../../models/armTemplates";
+import { ArmResourceTemplate, ArmResourceTemplateGenerator, ArmParamType, ArmParameters, ArmParameter, DefaultArmParams } from "../../models/armTemplates";
 import { ServerlessAzureConfig } from "../../models/serverless";
 import { AzureNamingService, AzureNamingServiceOptions } from "../../services/namingService";
 
+interface AppInsightsParams extends DefaultArmParams {
+  appInsightsName: ArmParameter;
+}
+
 export class AppInsightsResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
     const options: AzureNamingServiceOptions = {
@@ -13,19 +17,21 @@ export class AppInsightsResource implements ArmResourceTemplateGenerator {
   }
 
   public getTemplate(): ArmResourceTemplate {
+    const parameters: AppInsightsParams = {
+      appInsightsName: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+      location: {
+        defaultValue: "",
+        type: ArmParamType.String
+      }
+    }
+    
     return {
       "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
       "contentVersion": "1.0.0.0",
-      "parameters": {
-        "appInsightsName": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-        "location": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        }
-      },
+      parameters,
       "variables": {},
       "resources": [
         {
@@ -44,10 +50,12 @@ export class AppInsightsResource implements ArmResourceTemplateGenerator {
   }
 
   public getParameters(config: ServerlessAzureConfig): ArmParameters {
-    return {
+    const params: AppInsightsParams = {
       appInsightsName: {
         value: AppInsightsResource.getResourceName(config),
       }
     };
+
+    return params as unknown as ArmParameters;
   }
 }
diff --git a/src/armTemplates/resources/appServicePlan.ts b/src/armTemplates/resources/appServicePlan.ts
index 670aee7..c8e3457 100644
--- a/src/armTemplates/resources/appServicePlan.ts
+++ b/src/armTemplates/resources/appServicePlan.ts
@@ -1,7 +1,13 @@
-import { ArmResourceTemplate, ArmResourceTemplateGenerator, ArmParamType, ArmParameters } from "../../models/armTemplates";
+import { ArmResourceTemplate, ArmResourceTemplateGenerator, ArmParamType, ArmParameters, DefaultArmParams, ArmParameter } from "../../models/armTemplates";
 import { ResourceConfig, ServerlessAzureConfig } from "../../models/serverless";
 import { AzureNamingService, AzureNamingServiceOptions } from "../../services/namingService";
 
+interface AppServicePlanParams extends DefaultArmParams {
+  appServicePlanName: ArmParameter;
+  appServicePlanSkuName: ArmParameter;
+  appServicePlanSkuTier: ArmParameter;
+}
+
 export class AppServicePlanResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
     const options: AzureNamingServiceOptions = {
@@ -13,27 +19,29 @@ export class AppServicePlanResource implements ArmResourceTemplateGenerator {
   }
 
   public getTemplate(): ArmResourceTemplate {
+    const parameters: AppServicePlanParams = {
+      appServicePlanName: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+      location: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+      appServicePlanSkuName: {
+        defaultValue: "EP1",
+        type: ArmParamType.String
+      },
+      appServicePlanSkuTier: {
+        defaultValue: "ElasticPremium",
+        type: ArmParamType.String
+      }
+    };
+
     return {
       "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
       "contentVersion": "1.0.0.0",
-      "parameters": {
-        "appServicePlanName": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-        "location": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-        "appServicePlanSkuName": {
-          "defaultValue": "EP1",
-          "type": ArmParamType.String
-        },
-        "appServicePlanSkuTier": {
-          "defaultValue": "ElasticPremium",
-          "type": ArmParamType.String
-        }
-      },
+      parameters,
       "variables": {},
       "resources": [
         {
@@ -63,7 +71,7 @@ export class AppServicePlanResource implements ArmResourceTemplateGenerator {
       ...config.provider.storageAccount,
     };
 
-    return {
+    const params: AppServicePlanParams = {
       appServicePlanName: {
         value: AppServicePlanResource.getResourceName(config),
       },
@@ -74,5 +82,7 @@ export class AppServicePlanResource implements ArmResourceTemplateGenerator {
         value: resourceConfig.sku.tier,
       }
     }
+
+    return params as unknown as ArmParameters;
   }
 }
diff --git a/src/armTemplates/resources/functionApp.ts b/src/armTemplates/resources/functionApp.ts
index 9d16299..5d597c5 100644
--- a/src/armTemplates/resources/functionApp.ts
+++ b/src/armTemplates/resources/functionApp.ts
@@ -1,10 +1,16 @@
-import { ArmResourceTemplate, ArmResourceTemplateGenerator, ArmParamType, ArmParameters } from "../../models/armTemplates";
-import { FunctionAppConfig, ServerlessAzureConfig } from "../../models/serverless";
+import { ArmParameter, ArmParameters, ArmParamType, ArmResourceTemplate, ArmResourceTemplateGenerator, DefaultArmParams } from "../../models/armTemplates";
+import { FunctionAppConfig, ServerlessAzureConfig, SupportedRuntimeLanguage } from "../../models/serverless";
 import { AzureNamingService, AzureNamingServiceOptions } from "../../services/namingService";
 
-//Runtime versions found at " https://<sitename>.scm.azurewebsites.net/api/diagnostics/runtime".
-import runtimeVersionsJson from "../../services/runtimeVersions.json";
-import semver from "semver";
+interface FunctionAppParams extends DefaultArmParams {
+  functionAppName: ArmParameter;
+  functionAppNodeVersion: ArmParameter;
+  functionAppWorkerRuntime: ArmParameter;
+  functionAppExtensionVersion: ArmParameter;
+  appInsightsName?: ArmParameter;
+  functionAppRunFromPackage?: ArmParameter;
+  storageAccountName?: ArmParameter;
+}
 
 export class FunctionAppResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
@@ -20,43 +26,44 @@ export class FunctionAppResource implements ArmResourceTemplateGenerator {
   }
 
   public getTemplate(): ArmResourceTemplate {
+    const parameters: FunctionAppParams = {
+      functionAppRunFromPackage: {
+        defaultValue: "1",
+        type: ArmParamType.String
+      },
+      functionAppName: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+      functionAppNodeVersion: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+      functionAppWorkerRuntime: {
+        defaultValue: "node",
+        type: ArmParamType.String
+      },
+      functionAppExtensionVersion: {
+        defaultValue: "~2",
+        type: ArmParamType.String
+      },
+      storageAccountName: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+      appInsightsName: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+      location: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+    }
     return {
       "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
       "contentVersion": "1.0.0.0",
-      "parameters": {
-        "functionAppRunFromPackage": {
-          "defaultValue": "1",
-          "type": ArmParamType.String
-        },
-        "functionAppName": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-        "functionAppNodeVersion": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-        "functionAppWorkerRuntime": {
-          "defaultValue": "node",
-          "type": ArmParamType.String
-        },
-        "functionAppExtensionVersion": {
-          "defaultValue": "~2",
-          "type": ArmParamType.String
-        },
-        "storageAccountName": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-        "appInsightsName": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-        "location": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-      },
+      parameters,
       "variables": {},
       "resources": [
         {
@@ -121,55 +128,29 @@ export class FunctionAppResource implements ArmResourceTemplateGenerator {
   public getParameters(config: ServerlessAzureConfig): ArmParameters {
     const resourceConfig: FunctionAppConfig = {
       ...config.provider.functionApp,
-      nodeVersion: this.getRuntimeVersion(config.provider.runtime)
     };
+    const { functionRuntime } = config.provider;
 
-    return {
+
+    const params: FunctionAppParams = {
       functionAppName: {
         value: FunctionAppResource.getResourceName(config),
       },
       functionAppNodeVersion: {
-        value: resourceConfig.nodeVersion,
+        value: (functionRuntime.language === SupportedRuntimeLanguage.NODE)
+          ?
+          functionRuntime.version
+          :
+          undefined
       },
       functionAppWorkerRuntime: {
-        value: resourceConfig.workerRuntime,
+        value: functionRuntime.language,
       },
       functionAppExtensionVersion: {
         value: resourceConfig.extensionVersion,
       }
     };
-  }
 
-  private getRuntimeVersion(runtime: string): string {
-    if (!runtime) {
-      throw new Error("Runtime version not specified in serverless.yml");
-    }
-    const extractedVersion = runtime.split("nodejs")[1];
-    const runtimeVersionsList = runtimeVersionsJson["nodejs"];
-
-    //Searches for a specific version. For example nodejs10.6.0.
-    if (!extractedVersion.endsWith(".x")) {
-      let retrivedVersion: string;
-      for (const version of runtimeVersionsList) {
-        retrivedVersion = version["version"];
-        if (extractedVersion === retrivedVersion && semver.valid(retrivedVersion)) {
-          return retrivedVersion;
-        }
-      }
-    }
-    else {
-      // User specified something like nodejs10.14.x
-      const extractedVersionNumber = extractedVersion.replace(/[^0-9\.]/g, "");
-
-      const selectedVersions = runtimeVersionsList.filter(({ version }) => {
-        return version.startsWith(extractedVersionNumber) && semver.valid(version)
-      }).map((item) => item.version);
-
-      if (!selectedVersions.length) {
-        throw new Error(`Could not find runtime version matching ${runtime}`)
-      }
-      return selectedVersions.sort(semver.rcompare)[0]
-    }
-    throw new Error(`Could not find runtime version matching ${runtime}`)
+    return params as unknown as ArmParameters;
   }
 }
diff --git a/src/armTemplates/resources/hostingEnvironment.ts b/src/armTemplates/resources/hostingEnvironment.ts
index 06a08c9..606053d 100644
--- a/src/armTemplates/resources/hostingEnvironment.ts
+++ b/src/armTemplates/resources/hostingEnvironment.ts
@@ -1,7 +1,12 @@
-import { ArmResourceTemplate, ArmResourceTemplateGenerator, ArmParamType, ArmParameters } from "../../models/armTemplates";
+import { ArmResourceTemplate, ArmResourceTemplateGenerator, ArmParamType, ArmParameters, DefaultArmParams, ArmParameter } from "../../models/armTemplates";
 import { ServerlessAzureConfig } from "../../models/serverless";
 import { AzureNamingService, AzureNamingServiceOptions } from "../../services/namingService";
 
+export interface HostingEnvironmentParams extends DefaultArmParams {
+  hostingEnvironmentName?: ArmParameter;
+  virtualNetworkName?: ArmParameter;
+}
+
 export class HostingEnvironmentResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
     const options: AzureNamingServiceOptions = {
@@ -13,23 +18,25 @@ export class HostingEnvironmentResource implements ArmResourceTemplateGenerator
   }
 
   public getTemplate(): ArmResourceTemplate {
+    const parameters: HostingEnvironmentParams = {
+      hostingEnvironmentName: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+      virtualNetworkName: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+      location: {
+        defaultValue: "",
+        type: ArmParamType.String
+      }
+    }
+
     return {
       "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
       "contentVersion": "1.0.0.0",
-      "parameters": {
-        "hostingEnvironmentName": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-        "virtualNetworkName": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-        "location": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        }
-      },
+      parameters,
       "variables": {},
       "resources": [
         {
@@ -66,11 +73,13 @@ export class HostingEnvironmentResource implements ArmResourceTemplateGenerator
   }
 
   public getParameters(config: ServerlessAzureConfig): ArmParameters {
-    return {
+    const params: HostingEnvironmentParams = {
       hostingEnvironmentName: {
         value: HostingEnvironmentResource.getResourceName(config)
       }
     }
+
+    return params as unknown as ArmParameters;
   }
 }
 
diff --git a/src/armTemplates/resources/storageAccount.ts b/src/armTemplates/resources/storageAccount.ts
index cf8ea2f..d66a376 100644
--- a/src/armTemplates/resources/storageAccount.ts
+++ b/src/armTemplates/resources/storageAccount.ts
@@ -1,8 +1,14 @@
 import configConstants from "../../config";
-import { ArmParameters, ArmParamType, ArmResourceTemplate, ArmResourceTemplateGenerator } from "../../models/armTemplates";
+import { ArmParameters, ArmParamType, ArmResourceTemplate, ArmResourceTemplateGenerator, DefaultArmParams, ArmParameter } from "../../models/armTemplates";
 import { ResourceConfig, ServerlessAzureConfig } from "../../models/serverless";
 import { AzureNamingService, AzureNamingServiceOptions } from "../../services/namingService";
 
+interface StorageAccountParams extends DefaultArmParams {
+  storageAccountName: ArmParameter;
+  storageAccountSkuName: ArmParameter;
+  storageAccountSkuTier: ArmParameter;
+}
+
 export class StorageAccountResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
     const options: AzureNamingServiceOptions = {
@@ -17,27 +23,28 @@ export class StorageAccountResource implements ArmResourceTemplateGenerator {
   }
 
   public getTemplate(): ArmResourceTemplate {
+    const parameters: StorageAccountParams = {
+      storageAccountName: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+      location: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+      storageAccountSkuName: {
+        defaultValue: "Standard_LRS",
+        type: ArmParamType.String
+      },
+      storageAccountSkuTier: {
+        defaultValue: "Standard",
+        type: ArmParamType.String
+      }
+    }
     return {
       "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
       "contentVersion": "1.0.0.0",
-      "parameters": {
-        "storageAccountName": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-        "location": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-        "storageAccountSkuName": {
-          "defaultValue": "Standard_LRS",
-          "type": ArmParamType.String
-        },
-        "storageAccoutSkuTier": {
-          "defaultValue": "Standard",
-          "type": ArmParamType.String
-        }
-      },
+      parameters,
       variables: {},
       resources: [
         {
@@ -51,7 +58,7 @@ export class StorageAccountResource implements ArmResourceTemplateGenerator {
           },
           sku: {
             name: "[parameters('storageAccountSkuName')]",
-            tier: "[parameters('storageAccoutSkuTier')]"
+            tier: "[parameters('storageAccountSkuTier')]"
           }
         }
       ]
@@ -64,16 +71,18 @@ export class StorageAccountResource implements ArmResourceTemplateGenerator {
       ...config.provider.storageAccount
     };
 
-    return {
+    const params: StorageAccountParams = {
       storageAccountName: {
         value: StorageAccountResource.getResourceName(config),
       },
       storageAccountSkuName: {
         value: resourceConfig.sku.name,
       },
-      storageAccoutSkuTier: {
+      storageAccountSkuTier: {
         value: resourceConfig.sku.tier,
       }
     };
+
+    return params as unknown as ArmParameters;
   }
 }
diff --git a/src/armTemplates/resources/virtualNetwork.ts b/src/armTemplates/resources/virtualNetwork.ts
index 921d658..9d04fa4 100644
--- a/src/armTemplates/resources/virtualNetwork.ts
+++ b/src/armTemplates/resources/virtualNetwork.ts
@@ -1,6 +1,7 @@
 import { ArmResourceTemplate, ArmResourceTemplateGenerator, ArmParamType, ArmParameters } from "../../models/armTemplates";
 import { ServerlessAzureConfig } from "../../models/serverless";
 import { AzureNamingService, AzureNamingServiceOptions } from "../../services/namingService";
+import { HostingEnvironmentParams } from "./hostingEnvironment";
 
 export class VirtualNetworkResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
@@ -13,23 +14,24 @@ export class VirtualNetworkResource implements ArmResourceTemplateGenerator {
   }
 
   public getTemplate(): ArmResourceTemplate {
+    const parameters: HostingEnvironmentParams = {
+      hostingEnvironmentName: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+      virtualNetworkName: {
+        defaultValue: "",
+        type: ArmParamType.String
+      },
+      location: {
+        defaultValue: "",
+        type: ArmParamType.String
+      }
+    }
     return {
       "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
       "contentVersion": "1.0.0.0",
-      "parameters": {
-        "hostingEnvironmentName": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-        "virtualNetworkName": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        },
-        "location": {
-          "defaultValue": "",
-          "type": ArmParamType.String
-        }
-      },
+      parameters,
       "variables": {},
       "resources": [
         {
@@ -84,11 +86,13 @@ export class VirtualNetworkResource implements ArmResourceTemplateGenerator {
   }
 
   public getParameters(config: ServerlessAzureConfig): ArmParameters {
-    return {
+    const params: HostingEnvironmentParams = {
       virtualNetworkName: {
         value: VirtualNetworkResource.getResourceName(config),
       }
     }
+
+    return params as unknown as ArmParameters;
   }
 }
 
diff --git a/src/config.ts b/src/config.ts
index b1988e8..d7dbff7 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -3,7 +3,7 @@ export const configConstants = {
   deploymentConfig: {
     container: "deployment-artifacts",
     rollback: true,
-    runFromBlobUrl: false,
+    external: false,
   },
   naming: {
     maxLength: {
@@ -34,7 +34,13 @@ export const configConstants = {
   scmVfsPath: "/api/vfs/site/wwwroot/",
   scmZipDeployApiPath: "/api/zipdeploy",
   resourceGroupHashLength: 6,
-  defaultLocalPort: 7071,
+  defaults: {
+    awsRegion: "us-east-1",
+    region: "westus",
+    stage: "dev",
+    prefix: "sls",
+    localPort: 7071,
+  },
 };
 
 export default configConstants;
diff --git a/src/models/armTemplates.ts b/src/models/armTemplates.ts
index 6f10135..2461140 100644
--- a/src/models/armTemplates.ts
+++ b/src/models/armTemplates.ts
@@ -45,11 +45,17 @@ export interface ArmResourceTemplate {
 }
 
 export interface ArmParameters {
-  [key: string]: {
-    type?: ArmParamType;
-    value?: string | number;
-    defaultValue?: string | number;
-  };
+  [key: string]: ArmParameter;
+}
+
+export interface ArmParameter {
+  type?: ArmParamType;
+  value?: string | number;
+  defaultValue?: string | number;
+}
+
+export interface DefaultArmParams {
+  location?: ArmParameter;
 }
 
 /**
diff --git a/src/models/serverless.ts b/src/models/serverless.ts
index 91b77d4..16dfbe6 100644
--- a/src/models/serverless.ts
+++ b/src/models/serverless.ts
@@ -25,7 +25,7 @@ export interface FunctionAppConfig extends ResourceConfig {
 export interface DeploymentConfig {
   rollback?: boolean;
   container?: string;
-  runFromBlobUrl?: boolean;
+  external?: boolean;
 }
 
 export interface ServerlessAzureProvider {
@@ -35,6 +35,8 @@ export interface ServerlessAzureProvider {
   stage: string;
   name: string;
   subscriptionId?: string;
+  tenantId?: string;
+  appId?: string;
   environment?: {
     [key: string]: any;
   };
@@ -51,6 +53,22 @@ export interface ServerlessAzureProvider {
   armTemplate?: ArmTemplateConfig;
   keyVaultConfig?: AzureKeyVaultConfig;
   runtime: string;
+  functionRuntime?: FunctionRuntime;
+}
+
+export enum FunctionAppOS {
+  WINDOWS = "windows",
+  LINUX = "linux"
+}
+
+export interface FunctionRuntime {
+  language: SupportedRuntimeLanguage;
+  version: string;
+}
+
+export enum SupportedRuntimeLanguage {
+  PYTHON = "python",
+  NODE = "node"
 }
 
 /**
diff --git a/src/services/apimService.ts b/src/services/apimService.ts
index 932f734..110a489 100644
--- a/src/services/apimService.ts
+++ b/src/services/apimService.ts
@@ -231,7 +231,7 @@ export class ApimService extends BaseService {
       return api;
     } catch (e) {
       this.log("Error creating APIM API");
-      this.log(JSON.stringify(e.body, null, 4));
+      this.log(this.stringify(e.body));
       throw e;
     }
   }
@@ -264,7 +264,7 @@ export class ApimService extends BaseService {
       });
     } catch (e) {
       this.log("Error creating APIM Backend");
-      this.log(JSON.stringify(e.body, null, 4));
+      this.log(this.stringify(e.body));
       throw e;
     }
   }
@@ -309,7 +309,7 @@ export class ApimService extends BaseService {
       return result;
     } catch (e) {
       this.log(`Error deploying API operation ${functionName}`);
-      this.log(JSON.stringify(e.body, null, 4));
+      this.log(this.stringify(e.body));
       throw e;
     }
   }
@@ -331,7 +331,7 @@ export class ApimService extends BaseService {
       });
     } catch (e) {
       this.log("Error creating APIM Property");
-      this.log(JSON.stringify(e.body, null, 4));
+      this.log(this.stringify(e.body));
       throw e;
     }
   }
diff --git a/src/services/armService.ts b/src/services/armService.ts
index a574c01..f64f7e4 100644
--- a/src/services/armService.ts
+++ b/src/services/armService.ts
@@ -1,16 +1,16 @@
 import { ResourceManagementClient } from "@azure/arm-resources";
 import { Deployment, DeploymentExtended } from "@azure/arm-resources/esm/models";
+import deepEqual from "deep-equal";
 import fs from "fs";
 import jsonpath from "jsonpath";
 import path from "path";
 import Serverless from "serverless";
-import { ArmDeployment, ArmResourceTemplateGenerator, ArmTemplateType, ArmResourceTemplate, ArmParameters } from "../models/armTemplates";
-import { ArmTemplateConfig, ServerlessAzureConfig, ServerlessAzureOptions } from "../models/serverless";
+import { ArmDeployment, ArmParameters, ArmResourceTemplate, ArmResourceTemplateGenerator, ArmTemplateType } from "../models/armTemplates";
+import { DeploymentExtendedError } from "../models/azureProvider";
+import { ArmTemplateConfig, ServerlessAzureOptions } from "../models/serverless";
 import { Guard } from "../shared/guard";
 import { BaseService } from "./baseService";
-import { ResourceService } from "./resourceService"
-import { DeploymentExtendedError } from "../models/azureProvider";
-import deepEqual from "deep-equal";
+import { ResourceService } from "./resourceService";
 
 export class ArmService extends BaseService {
   private resourceClient: ResourceManagementClient;
@@ -39,13 +39,12 @@ export class ArmService extends BaseService {
       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);
+    let parameters = template.getParameters(this.config);
 
     if (this.config.provider.apim) {
       const apimTemplate = apimResource.getTemplate();
-      const apimParameters = apimResource.getParameters(azureConfig);
+      const apimParameters = apimResource.getParameters(this.config);
 
       mergedTemplate.parameters = {
         ...mergedTemplate.parameters,
diff --git a/src/services/baseService.ts b/src/services/baseService.ts
index 1e433b0..71ace8c 100644
--- a/src/services/baseService.ts
+++ b/src/services/baseService.ts
@@ -4,17 +4,11 @@ import fs from "fs";
 import request from "request";
 import Serverless from "serverless";
 import { StorageAccountResource } from "../armTemplates/resources/storageAccount";
-import { configConstants } from "../config";
-import {
-  DeploymentConfig,
-  ServerlessAzureConfig,
-  ServerlessAzureFunctionConfig,
-  ServerlessAzureOptions,
-  ServerlessLogOptions
-} from "../models/serverless";
+import { ServerlessAzureConfig, ServerlessAzureOptions, ServerlessLogOptions } from "../models/serverless";
+import { constants } from "../shared/constants";
 import { Guard } from "../shared/guard";
 import { Utils } from "../shared/utils";
-import { AzureNamingService, AzureNamingServiceOptions } from "./namingService";
+import { ConfigService } from "./configService";
 
 export abstract class BaseService {
   protected baseUrl: string;
@@ -24,9 +18,9 @@ export abstract class BaseService {
   protected resourceGroup: string;
   protected deploymentName: string;
   protected artifactName: string;
-  protected deploymentConfig: DeploymentConfig;
   protected storageAccountName: string;
   protected config: ServerlessAzureConfig;
+  protected configService: ConfigService;
 
   protected constructor(
     protected serverless: Serverless,
@@ -34,18 +28,16 @@ export abstract class BaseService {
     authenticate: boolean = true
   ) {
     Guard.null(serverless);
-    this.setDefaultValues();
-    this.config = serverless.service as any;
-    this.setupConfig();
+    this.configService = new ConfigService(serverless, options);
+    this.config = this.configService.getConfig();
 
     this.baseUrl = "https://management.azure.com";
-    this.serviceName = this.getServiceName();
-    this.credentials = serverless.variables["azureCredentials"];
+    this.serviceName = this.configService.getServiceName();
+    this.credentials = serverless.variables[constants.variableKeys.azureCredentials];
     this.subscriptionId = this.config.provider.subscriptionId;
-    this.resourceGroup = this.getResourceGroupName();
-    this.deploymentConfig = this.getDeploymentConfig();
-    this.deploymentName = this.getDeploymentName();
-    this.artifactName = this.getArtifactName(this.deploymentName);
+    this.resourceGroup = this.configService.getResourceGroupName();
+    this.deploymentName = this.configService.getDeploymentName();
+    this.artifactName = this.configService.getArtifactName(this.deploymentName);
     this.storageAccountName = StorageAccountResource.getResourceName(this.config);
 
     if (!this.credentials && authenticate) {
@@ -53,87 +45,6 @@ export abstract class BaseService {
     }
   }
 
-  /**
-   * Name of Azure Region for deployment
-   */
-  public getRegion(): string {
-    return this.config.provider.region;
-  }
-
-  /**
-   * Name of current deployment stage
-   */
-  public getStage(): string {
-    return this.config.provider.stage;
-  }
-
-  /**
-   * Prefix for service
-   */
-  public getPrefix(): string {
-    return this.config.provider.prefix;
-  }
-
-  /**
-   * Name of current resource group
-   */
-  public getResourceGroupName(): string {
-    return this.config.provider.resourceGroup;
-  }
-
-  /**
-   * Azure Subscription ID
-   */
-  public getSubscriptionId(): string {
-    return this.config.provider.subscriptionId;
-  }
-
-  /**
-   * Deployment config from `serverless.yml` or default.
-   * Defaults can be found in the `config.ts` file
-   */
-  public getDeploymentConfig(): DeploymentConfig {
-    return {
-      ...configConstants.deploymentConfig,
-      ...this.config.provider.deployment,
-    }
-  }
-
-  /**
-   * Name of current ARM deployment.
-   *
-   * Naming convention:
-   *
-   * {safeName (see naming service)}--{serviceName}(if rollback enabled: -t{timestamp})
-   *
-   * The string is guaranteed to be less than 64 characters, since that is the limit
-   * imposed by Azure deployment names. If a trim is needed, the service name will be trimmed
-   */
-  public getDeploymentName(): string {
-    return AzureNamingService.getDeploymentName(
-      this.config,
-      (this.deploymentConfig.rollback) ? `t${this.getTimestamp()}` : null
-    )
-  }
-
-  /**
-   * Name of Function App Service
-   */
-  public getServiceName(): string {
-    return this.serverless.service["service"];
-  }
-
-  /**
-   * Get rollback-configured artifact name. Contains `-t{timestamp}`
-   * Takes name of deployment and replaces `rg-deployment` or `deployment` with `artifact`
-   */
-  protected getArtifactName(deploymentName: string): string {
-    const { deployment, artifact } = configConstants.naming.suffix;
-    return `${deploymentName
-      .replace(`rg-${deployment}`, artifact)
-      .replace(deployment, artifact)}.zip`
-  }
-
   /**
    * Get the access token from credentials token cache
    */
@@ -181,7 +92,7 @@ export abstract class BaseService {
       fs.createReadStream(filePath).pipe(
         request(requestOptions, (err, response) => {
           if (err) {
-            this.log(JSON.stringify(err, null, 4));
+            this.log(this.stringify(err));
             return reject(err);
           }
           resolve(response);
@@ -196,85 +107,17 @@ export abstract class BaseService {
    */
   protected log(message: string, options?: ServerlessLogOptions, entity?: string) {
     (this.serverless.cli.log as any)(message, entity, options);
-  }
+  }  
 
-  /**
-   * Get function objects
-   */
-  protected slsFunctions(): { [functionName: string]: ServerlessAzureFunctionConfig } {
-    return this.serverless.service["functions"];
+  protected prettyPrint(object: any) {
+    this.log(this.stringify(object));
   }
 
-  protected slsConfigFile(): string {
-    return "config" in this.options ? this.options["config"] : "serverless.yml";
+  protected stringify(object: any): string {
+    return JSON.stringify(object, null, 2);
   }
 
   protected getOption(key: string, defaultValue?: any) {
     return Utils.get(this.options, key, defaultValue);
   }
-
-  protected prettyPrint(object: any) {
-    this.log(JSON.stringify(object, null, 2));
-  }
-
-  private setDefaultValues(): void {
-    // TODO: Right now the serverless core will always default to AWS default region if the
-    // region has not been set in the serverless.yml or CLI options
-    const awsDefault = "us-east-1";
-    const providerRegion = this.serverless.service.provider.region;
-
-    if (!providerRegion || providerRegion === awsDefault) {
-      // no region specified in serverless.yml
-      this.serverless.service.provider.region = this.serverless.service.provider["location"] || "westus";
-    }
-
-    if (!this.serverless.service.provider.stage) {
-      this.serverless.service.provider.stage = "dev";
-    }
-
-    if (!this.serverless.service.provider["prefix"]) {
-      this.serverless.service.provider["prefix"] = "sls";
-    }
-  }
-
-  /**
-   * Get timestamp from `packageTimestamp` serverless variable
-   * If not set, create timestamp, set variable and return timestamp
-   */
-  private getTimestamp(): number {
-    let timestamp = +this.serverless.variables["packageTimestamp"];
-    if (!timestamp) {
-      timestamp = Date.now();
-      this.serverless.variables["packageTimestamp"] = timestamp;
-    }
-    return timestamp;
-  }
-
-  /**
-   * Overwrite values for resourceGroup, prefix, region and stage
-   * in config if passed through CLI
-   */
-  private setupConfig() {
-    const { prefix, region, stage, subscriptionId } = this.config.provider;
-
-    const options: AzureNamingServiceOptions = {
-      config: this.config,
-      suffix: `${this.getServiceName()}-rg`,
-      includeHash: false,
-    }
-
-    this.config.provider = {
-      ...this.config.provider,
-      prefix: this.getOption("prefix") || prefix,
-      stage: this.getOption("stage") || stage,
-      region: this.getOption("region") || region,
-      subscriptionId: this.getOption("subscriptionId")
-        || process.env.AZURE_SUBSCRIPTION_ID
-        || subscriptionId
-        || this.serverless.variables["subscriptionId"]
-    }
-    this.config.provider.resourceGroup = (
-      this.getOption("resourceGroup", this.config.provider.resourceGroup)
-    ) || AzureNamingService.getResourceName(options);
-  }
 }
diff --git a/src/services/configService.ts b/src/services/configService.ts
new file mode 100644
index 0000000..a1c549a
--- /dev/null
+++ b/src/services/configService.ts
@@ -0,0 +1,287 @@
+import semver from "semver";
+import Serverless from "serverless";
+import Service from "serverless/classes/Service";
+import configConstants from "../config";
+import { DeploymentConfig, FunctionRuntime, ServerlessAzureConfig, 
+  ServerlessAzureFunctionConfig, SupportedRuntimeLanguage } from "../models/serverless";
+import { constants } from "../shared/constants";
+import { Guard } from "../shared/guard";
+import { Utils } from "../shared/utils";
+import { AzureNamingService, AzureNamingServiceOptions } from "./namingService";
+import runtimeVersionsJson from "./runtimeVersions.json";
+
+/**
+ * Handles all Service Configuration
+ */
+export class ConfigService {
+
+  /** Configuration for service */
+  private config: ServerlessAzureConfig;
+
+  public constructor(private serverless: Serverless, private options: Serverless.Options) {
+    this.config = this.initializeConfig(serverless.service);
+  }
+
+  /**
+   * Get Azure Provider Configuration
+   */
+  public getConfig(): ServerlessAzureConfig {
+    return this.config;
+  }
+
+  /**
+   * Name of Azure Region for deployment
+   */
+  public getRegion(): string {
+    return this.config.provider.region;
+  }
+
+  /**
+   * Name of current deployment stage
+   */
+  public getStage(): string {
+    return this.config.provider.stage;
+  }
+
+  /**
+   * Prefix for service
+   */
+  public getPrefix(): string {
+    return this.config.provider.prefix;
+  }
+
+  /**
+   * Name of current resource group
+   */
+  public getResourceGroupName(): string {
+    return this.config.provider.resourceGroup;
+  }
+
+  /**
+   * Azure Subscription ID
+   */
+  public getSubscriptionId(): string {
+    return this.config.provider.subscriptionId;
+  }
+
+  /**
+   * Name of current deployment
+   */
+  public getDeploymentName(): string {
+    return AzureNamingService.getDeploymentName(
+      this.config,
+      (this.config.provider.deployment.rollback) ? `t${this.getTimestamp()}` : null
+    )
+  }
+
+  public getDeploymentConfig(): DeploymentConfig {
+    return this.config.provider.deployment;
+  }
+
+  /**
+   * Get rollback-configured artifact name. Contains `-t{timestamp}`
+   * Takes name of deployment and replaces `rg-deployment` or `deployment` with `artifact`
+   */
+  public getArtifactName(deploymentName?: string): string {
+    deploymentName = deploymentName || this.getDeploymentName();
+    const { deployment, artifact } = configConstants.naming.suffix;
+    return `${deploymentName
+      .replace(`rg-${deployment}`, artifact)
+      .replace(deployment, artifact)}.zip`
+  }
+
+  /**
+   * Function configuration from serverless.yml
+   */
+  public getFunctionConfig(): { [functionName: string]: ServerlessAzureFunctionConfig } {
+    return this.config.functions;
+  }
+
+  /**
+   * Name of file containing serverless config
+   */
+  public getConfigFile(): string {
+    return this.getOption("config", "serverless.yml");
+  }
+
+  /**
+   * Name of Function App Service
+   */
+  public getServiceName(): string {
+    return this.config.service;
+  }
+
+  /**
+   * Function runtime configuration
+   */
+  public getFunctionRuntime(): FunctionRuntime {
+    return this.config.provider.functionRuntime;
+  }
+
+  /**
+   * Set any default values required for service
+   * @param config Current Serverless configuration
+   */
+  private setDefaultValues(config: ServerlessAzureConfig) {
+    const { awsRegion, region, stage, prefix } = configConstants.defaults;
+    const providerRegion = config.provider.region;
+
+    if (!providerRegion || providerRegion === awsRegion) {
+      config.provider.region = this.serverless.service.provider["location"] || region;
+    }
+ 
+    if (!config.provider.stage) {
+      config.provider.stage = stage;
+    }
+
+    if (!config.provider.prefix) {
+      config.provider.prefix = prefix;
+    }
+  }
+
+  /**
+   * Overwrite values for resourceGroup, prefix, region and stage
+   * in config if passed through CLI
+   */
+  private initializeConfig(service: Service): ServerlessAzureConfig {
+    const config: ServerlessAzureConfig = service as any;
+    this.setDefaultValues(config);
+
+    const {
+      prefix,
+      region,
+      stage,
+      subscriptionId,
+      tenantId,
+      appId,
+      deployment,
+      runtime
+    } = config.provider;
+
+    const options: AzureNamingServiceOptions = {
+      config: config,
+      suffix: `${config.service}-rg`,
+      includeHash: false,
+    }
+
+    config.provider = {
+      ...config.provider,
+      prefix: this.getOption("prefix") || prefix,
+      stage: this.getOption("stage") || stage,
+      region: this.getOption("region") || region,
+      subscriptionId: this.getOption(constants.variableKeys.subscriptionId)
+        || process.env.AZURE_SUBSCRIPTION_ID
+        || subscriptionId
+        || this.serverless.variables[constants.variableKeys.subscriptionId],
+      tenantId: this.getOption(constants.variableKeys.tenantId)
+        || process.env.AZURE_TENANT_ID
+        || tenantId,
+      appId: this.getOption(constants.variableKeys.appId)
+        || process.env.AZURE_CLIENT_ID
+        || appId
+    }
+    config.provider.resourceGroup = (
+      this.getOption("resourceGroup", config.provider.resourceGroup)
+    ) || AzureNamingService.getResourceName(options);
+
+    // Get property from `runFromBlobUrl` for backwards compatability
+    if (deployment && deployment.external === undefined && deployment["runFromBlobUrl"] !== undefined) {
+      deployment.external = deployment["runFromBlobUrl"];
+    }
+
+    config.provider.deployment = {
+      ...configConstants.deploymentConfig,
+      ...deployment
+    }
+
+    config.provider.functionRuntime = this.getRuntime(runtime)
+
+    return config;
+  }
+
+  private getRuntime(runtime: string): FunctionRuntime {
+    Guard.null(runtime, "runtime", "Runtime version not specified in serverless.yml");
+
+    const versionMatch = runtime.match(/([0-9]+\.)+[0-9]*x?/);
+    const languageMatch = runtime.match(/nodejs|python/);
+
+    let versionInput: string;
+    let languageInput: string;
+
+    // Extract version and language input
+    if (versionMatch && languageMatch) {
+      versionInput = versionMatch[0];
+      languageInput = languageMatch[0];
+    } else {
+      throw new Error(`Invalid runtime: ${runtime}. ${this.getRuntimeErrorMessage(null)}`);
+    }
+
+    const targetedVersionRegex = new RegExp(
+      `^${versionInput}`
+        .replace(".", "\.")
+        .replace("x", "[0-9]+"),
+    );
+
+    const matchingVersions: string[] = runtimeVersionsJson[languageInput]
+      .filter((item) => item.version.match(targetedVersionRegex))
+      .map((item) => item.version);
+
+    if (!matchingVersions.length) {
+      throw new Error(`Runtime ${runtime} is not supported. ${this.getRuntimeErrorMessage(null)}`);
+    }
+
+    const version = matchingVersions.sort(semver.rcompare)[0];
+
+    const language: SupportedRuntimeLanguage = {
+      "nodejs": SupportedRuntimeLanguage.NODE,
+      "python": SupportedRuntimeLanguage.PYTHON
+    }[languageInput];
+    
+    return {
+      language,
+      version
+    }
+  }
+
+  private getRuntimeErrorMessage(language: SupportedRuntimeLanguage) {
+    if (language) {
+      return `Supported versions for ${language} are: ${
+        runtimeVersionsJson[language]
+          .map((item) => item.version)
+          .join(",")}`
+    }
+    return `Supported versions: ${JSON.stringify(runtimeVersionsJson, null, 2)}`
+  }
+
+  /**
+   * Get timestamp from `packageTimestamp` serverless variable
+   * If not set, create timestamp, set variable and return timestamp
+   */
+  private getTimestamp(): number {
+    const key = constants.variableKeys.packageTimestamp
+    let timestamp = +this.getVariable(key);
+    if (!timestamp) {
+      timestamp = Date.now();
+      this.serverless.variables[key] = timestamp;
+    }
+    return timestamp;
+  }
+
+  /**
+   * Get value of option from Serverless CLI
+   * @param key Key of option
+   * @param defaultValue Default value if key not found in options object
+   */
+  protected getOption(key: string, defaultValue?: any) {
+    return Utils.get(this.options, key, defaultValue);
+  }
+
+  /**
+   * Get variable value from Serverless variables
+   * @param key Key for variable
+   * @param defaultValue Default value if key not found in variable object
+   */
+  protected getVariable(key: string, defaultValue?: any) {
+    return Utils.get(this.serverless.variables, key, defaultValue);
+  }
+}
diff --git a/src/services/funcService.ts b/src/services/funcService.ts
index b84cda7..580f155 100644
--- a/src/services/funcService.ts
+++ b/src/services/funcService.ts
@@ -44,7 +44,7 @@ export class FuncService extends BaseService {
   }
 
   private exists(functionName: string) {
-    return (functionName in this.slsFunctions());
+    return (functionName in this.configService.getFunctionConfig());
   }
 
   private createHandler(functionName: string) {
@@ -52,26 +52,26 @@ export class FuncService extends BaseService {
   }
 
   private addToServerlessYml(functionName: string) {
-    const functions = this.slsFunctions();
+    const functions = this.configService.getFunctionConfig();
     functions[functionName] = this.getFunctionSlsObject(functionName)
     this.updateFunctionsYml(functions)
   }
 
   private removeFromServerlessYml(functionName: string) {
-    const functions = this.slsFunctions();
+    const functions = this.configService.getFunctionConfig();
     delete functions[functionName];
     this.updateFunctionsYml(functions)
   }
 
   private getServerlessYml() {
-    return this.serverless.utils.readFileSync(this.slsConfigFile());
+    return this.serverless.utils.readFileSync(this.configService.getConfigFile());
   }
 
   private updateFunctionsYml(functionYml: any) {
     const serverlessYml = this.getServerlessYml();
     serverlessYml["functions"] = functionYml;
     this.serverless.utils.writeFileSync(
-      this.slsConfigFile(),
+      this.configService.getConfigFile(),
       yaml.dump(serverlessYml)
     );
   }
diff --git a/src/services/functionAppService.ts b/src/services/functionAppService.ts
index 6942f8b..9853302 100644
--- a/src/services/functionAppService.ts
+++ b/src/services/functionAppService.ts
@@ -63,13 +63,20 @@ export class FunctionAppService extends BaseService {
     return await this.sendApiRequest("DELETE", deleteFunctionUrl);
   }
 
-  public async syncTriggers(functionApp: Site) {
+  public async syncTriggers(functionApp: Site, properties: { [propertyName: string]: string }) {
     Guard.null(functionApp);
 
     this.log("Syncing function triggers");
 
-    const syncTriggersUrl = `${this.baseUrl}${functionApp.id}/syncfunctiontriggers?api-version=2016-08-01`;
-    return await this.sendApiRequest("POST", syncTriggersUrl);
+    const syncTriggersUrl = `${this.baseUrl}/subscriptions/${this.subscriptionId}` +
+      `/resourceGroups/${this.resourceGroup}/providers/Microsoft.Web/sites/${functionApp.name}` +
+      "/syncfunctiontriggers?api-version=2016-08-01";
+
+    try {
+      return await this.sendApiRequest("POST", syncTriggersUrl, { data: { properties } });
+    } catch (err) {
+      throw new Error(`Error syncing function app triggers: ${err}`)
+    }
   }
 
   public async cleanUp(functionApp: Site) {
@@ -101,7 +108,7 @@ export class FunctionAppService extends BaseService {
 
         if (listFunctionsResponse.status !== 200 || listFunctionsResponse.data.value.length === 0) {
           this.log(`-> Function App not ready. Retry ${retries++} of ${FunctionAppService.retryCount}...`);
-          const response = JSON.stringify(listFunctionsResponse.data, null, 2);
+          const response = this.stringify(listFunctionsResponse.data);
           throw new Error(
             `The function app is taking longer than usual to be provisioned. Please try again soon.
             Response error data: \n${response}`
@@ -136,7 +143,7 @@ export class FunctionAppService extends BaseService {
 
         if (getFunctionResponse.status !== 200) {
           this.log("-> Function app not ready. Retrying...")
-          throw new Error(JSON.stringify(response.data, null, 2));
+          throw new Error(this.stringify(response.data));
         }
 
         return getFunctionResponse;
@@ -155,22 +162,26 @@ export class FunctionAppService extends BaseService {
 
     const functionZipFile = this.getFunctionZipFile();
 
-    if (this.deploymentConfig.runFromBlobUrl) {
+    if (this.config.provider.deployment.external) {
       this.log("Updating function app setting to run from external package...");
       await this.uploadZippedArtifactToBlobStorage(functionZipFile);
 
       const sasUrl = await this.blobService.generateBlobSasTokenUrl(
-        this.deploymentConfig.container,
+        this.config.provider.deployment.container,
         this.artifactName
       );
 
-      await this.updateFunctionAppSetting(
+      const response = await this.updateFunctionAppSetting(
         functionApp,
         configConstants.runFromPackageSetting,
         sasUrl
       );
+
+      await this.syncTriggers(functionApp, response.properties);
     } else {
       await Promise.all([
+        // Can run in parallel if also uploading to function app
+        // Needs to happen first if `external` is true
         this.uploadZippedArtifactToBlobStorage(functionZipFile),
         this.uploadZippedArfifactToFunctionApp(functionApp, functionZipFile)
       ]);
@@ -250,10 +261,23 @@ export class FunctionAppService extends BaseService {
     return functionZipFile;
   }
 
+  public getDeploymentName(): string {
+    return this.configService.getDeploymentName();
+  }
+
   public async updateFunctionAppSetting(functionApp: Site, setting: string, value: string) {
     const { properties } = await this.webClient.webApps.listApplicationSettings(this.resourceGroup, functionApp.name);
     properties[setting] = value;
-    await this.webClient.webApps.updateApplicationSettings(this.resourceGroup, functionApp.name, properties);
+    try {
+      const url = `${this.baseUrl}/subscriptions/${this.subscriptionId}/resourceGroups/${this.resourceGroup}` +
+        `/providers/Microsoft.Web/sites/${functionApp.name}/config/appsettings?api-version=2016-08-01`;
+
+      const response = await this.sendApiRequest("PUT", url, { data: { properties } });
+      return response.data;
+      // return await this.webClient.webApps.updateApplicationSettings(this.resourceGroup, functionApp.name, properties);
+    } catch (err) {
+      throw new Error(`Failed to update function app settings: ${err}`)
+    }
   }
 
   /**
@@ -261,10 +285,10 @@ export class FunctionAppService extends BaseService {
    */
   private async uploadZippedArtifactToBlobStorage(functionZipFile: string): Promise<void> {
     await this.blobService.initialize();
-    await this.blobService.createContainerIfNotExists(this.deploymentConfig.container);
+    await this.blobService.createContainerIfNotExists(this.config.provider.deployment.container);
     await this.blobService.uploadFile(
       functionZipFile,
-      this.deploymentConfig.container,
+      this.config.provider.deployment.container,
       this.artifactName,
     );
   }
diff --git a/src/services/invokeService.ts b/src/services/invokeService.ts
index 00366e4..dbb4b52 100644
--- a/src/services/invokeService.ts
+++ b/src/services/invokeService.ts
@@ -24,7 +24,7 @@ export class InvokeService extends BaseService {
    */
   public async invoke(method: string, functionName: string, data?: any){
 
-    const functionObject = this.slsFunctions()[functionName];
+    const functionObject = this.configService.getFunctionConfig()[functionName];
     /* accesses the admin key */
     if (!functionObject) {
       this.serverless.cli.log(`Function ${functionName} does not exist`);
@@ -63,7 +63,7 @@ export class InvokeService extends BaseService {
   }
 
   private getLocalHost() {
-    return `http://localhost:${this.getOption("port", configConstants.defaultLocalPort)}`
+    return `http://localhost:${this.getOption("port", configConstants.defaults.localPort)}`
   }
 
   private getConfiguredFunctionRoute(functionName: string) {
diff --git a/src/services/loginService.ts b/src/services/loginService.ts
index 49bf602..3a2953e 100644
--- a/src/services/loginService.ts
+++ b/src/services/loginService.ts
@@ -27,7 +27,7 @@ export class AzureLoginService extends BaseService {
    * @param options Options for different authentication methods
    */
   public async login(options?: AzureTokenCredentialsOptions | InteractiveLoginOptions): Promise<AuthResponse> {
-    const subscriptionId = this.getSubscriptionId();
+    const subscriptionId = this.configService.getSubscriptionId();
     const clientId = process.env.AZURE_CLIENT_ID;
     const secret = process.env.AZURE_CLIENT_SECRET;
     const tenantId = process.env.AZURE_TENANT_ID;
@@ -57,4 +57,8 @@ export class AzureLoginService extends BaseService {
   public async servicePrincipalLogin(clientId: string, secret: string, tenantId: string, options: AzureTokenCredentialsOptions): Promise<AuthResponse> {
     return await loginWithServicePrincipalSecretWithAuthResponse(clientId, secret, tenantId, options);
   }
+
+  public getSubscriptionId() {
+    return this.configService.getSubscriptionId();
+  }
 }
diff --git a/src/services/offlineService.ts b/src/services/offlineService.ts
index 2664e82..656ea2a 100644
--- a/src/services/offlineService.ts
+++ b/src/services/offlineService.ts
@@ -15,7 +15,7 @@ export class OfflineService extends BaseService {
       IsEncrypted: false,
       Values: {
         AzureWebJobsStorage: "UseDevelopmentStorage=true",
-        FUNCTIONS_WORKER_RUNTIME: "node"
+        FUNCTIONS_WORKER_RUNTIME: this.config.provider.functionRuntime.language
       }
     }),
   }
diff --git a/src/services/packageService.ts b/src/services/packageService.ts
index 8ab9477..ef31e71 100644
--- a/src/services/packageService.ts
+++ b/src/services/packageService.ts
@@ -93,7 +93,7 @@ export class PackageService extends BaseService {
     const functionJSON = functionMetadata.params.functionsJson;
     functionJSON.entryPoint = functionMetadata.entryPoint;
     functionJSON.scriptFile = functionMetadata.handlerPath;
-    const functionObject = this.slsFunctions()[functionName];
+    const functionObject = this.configService.getFunctionConfig()[functionName];
     const bindingAzureSettings = Utils.getIncomingBindingConfig(functionObject)["x-azure-settings"];
 
     if (bindingAzureSettings.route) {
@@ -109,7 +109,7 @@ export class PackageService extends BaseService {
       fs.mkdirSync(functionDirPath);
     }
 
-    const functionJsonString = JSON.stringify(functionJSON, null, 2);
+    const functionJsonString = this.stringify(functionJSON);
     fs.writeFileSync(path.join(functionDirPath, "function.json"), functionJsonString);
 
     return Promise.resolve();
diff --git a/src/services/resourceService.ts b/src/services/resourceService.ts
index 6a03265..264aa8e 100644
--- a/src/services/resourceService.ts
+++ b/src/services/resourceService.ts
@@ -22,7 +22,7 @@ export class ResourceService extends BaseService {
     this.log(`Listing deployments for resource group '${this.resourceGroup}':`);
     const deployments = await this.resourceClient.deployments.listByResourceGroup(this.resourceGroup);
     return deployments.sort((a: DeploymentExtended, b: DeploymentExtended) => {
-      return (a.properties.timestamp > b.properties.timestamp) ? 1 : -1
+      return (a.properties.timestamp < b.properties.timestamp) ? 1 : -1
     });
   }
 
@@ -58,7 +58,7 @@ export class ResourceService extends BaseService {
   public async listDeployments(): Promise<string> {
     const deployments = await this.getDeployments()
     if (!deployments || deployments.length === 0) {
-      this.log(`No deployments found for resource group '${this.getResourceGroupName()}'`);
+      this.log(`No deployments found for resource group '${this.configService.getResourceGroupName()}'`);
       return;
     }
     let stringDeployments = "\n\nDeployments";
@@ -89,7 +89,7 @@ export class ResourceService extends BaseService {
     this.log(`Creating resource group: ${this.resourceGroup}`);
 
     return await this.resourceClient.resourceGroups.createOrUpdate(this.resourceGroup, {
-      location: AzureNamingService.getNormalizedRegionName(this.getRegion()),
+      location: AzureNamingService.getNormalizedRegionName(this.configService.getRegion()),
     });
   }
 
diff --git a/src/services/rollbackService.ts b/src/services/rollbackService.ts
index 9747196..6186d41 100644
--- a/src/services/rollbackService.ts
+++ b/src/services/rollbackService.ts
@@ -41,7 +41,7 @@ export class RollbackService extends BaseService {
       return;
     }
     // Name of artifact in blob storage
-    const artifactName = this.getArtifactName(deployment.name);
+    const artifactName = this.configService.getArtifactName(deployment.name);
     // Redeploy resource group (includes SAS token URL if running from blob URL)
     await this.redeployDeployment(deployment, artifactName);
   }
@@ -56,12 +56,12 @@ export class RollbackService extends BaseService {
     const armDeployment = await this.convertToArmDeployment(deployment);
     // Initialize blob service for either creating SAS token or downloading artifact to uplod to function app
     await this.blobService.initialize();
-    if (this.deploymentConfig.runFromBlobUrl) {
+    if (this.config.provider.deployment.external) {
       // Set functionRunFromPackage param to SAS URL of blob
       armDeployment.parameters.functionAppRunFromPackage = {
         type: ArmParamType.String,
         value: await this.blobService.generateBlobSasTokenUrl(
-          this.deploymentConfig.container,
+          this.config.provider.deployment.container,
           artifactName
         )
       }
@@ -71,7 +71,7 @@ export class RollbackService extends BaseService {
      * Cannot use an `else` statement just because deploying the artifact
      * depends on `deployTemplate` already being called
      */
-    if (!this.deploymentConfig.runFromBlobUrl) {
+    if (!this.config.provider.deployment.external) {
       const artifactPath = await this.downloadArtifact(artifactName);
       await this.redeployArtifact(artifactPath);
     }
@@ -132,7 +132,7 @@ export class RollbackService extends BaseService {
   private async downloadArtifact(artifactName: string): Promise<string> {
     const artifactPath = path.join(this.serverless.config.servicePath, ".serverless", artifactName)
     await this.blobService.downloadBinary(
-      this.deploymentConfig.container,
+      this.config.provider.deployment.container,
       artifactName,
       artifactPath
     );
diff --git a/src/services/runtimeVersions.json b/src/services/runtimeVersions.json
index 7e81b70..af11e33 100644
--- a/src/services/runtimeVersions.json
+++ b/src/services/runtimeVersions.json
@@ -1,63 +1,69 @@
 {
-    "nodejs": [{
-        "version": "0.10.40",
-        "npm": "1.4.28"
-    }, {
-        "version": "0.10.5",
-        "npm": "1.2.18"
-    }, {
-        "version": "0.12.6",
-        "npm": "2.11.2"
-    }, {
-        "version": "0.6.20",
-        "npm": "1.1.37"
-    }, {
-        "version": "0.8.28",
-        "npm": "1.2.30"
-    }, {
-        "version": "10.14.1",
-        "npm": "6.4.1"
-    }, {
-        "version": "10.15.2",
-        "npm": "6.4.1"
-    }, {
-        "version": "10.6.0",
-        "npm": "6.1.0"
-    }, {
-        "version": "4.8.4",
-        "npm": "2.15.11"
-    }, {
-        "version": "6.12.3",
-        "npm": "3.10.10"
-    }, {
-        "version": "6.5.0",
-        "npm": "3.10.3"
-    }, {
-        "version": "6.9.1",
-        "npm": "3.10.8"
-    }, {
-        "version": "6.9.5",
-        "npm": "3.10.10"
-    }, {
-        "version": "7.10.1",
-        "npm": "4.2.0"
-    }, {
-        "version": "8.1.4",
-        "npm": "5.0.3"
-    }, {
-        "version": "8.10.0",
-        "npm": "5.6.0"
-    }, {
-        "version": "8.11.1",
-        "npm": "5.6.0"
-    }, {
-        "version": "8.4.0",
-        "npm": "5.3.0"
-    }, {
-        "version": "8.5.0",
-        "npm": "5.3.0"
-    }, {
-        "version": "8.9.4",
-        "npm": "5.6.0"
-    }]
+    "nodejs": [
+        {
+            "version": "0.10.40"
+        },
+        {
+            "version": "0.10.5"
+        },
+        {
+            "version": "0.12.6"
+        },
+        {
+            "version": "0.6.20"
+        },
+        {
+            "version": "0.8.28"
+        },
+        {
+            "version": "10.14.1"
+        },
+        {
+            "version": "10.15.2"
+        },
+        {
+            "version": "10.6.0"
+        },
+        {
+            "version": "4.8.4"
+        },
+        {
+            "version": "6.12.3"
+        },
+        {
+            "version": "6.5.0"
+        },
+        {
+            "version": "6.9.1"
+        },
+        {
+            "version": "6.9.5"
+        },
+        {
+            "version": "7.10.1"
+        },
+        {
+            "version": "8.1.4"
+        },
+        {
+            "version": "8.10.0"
+        },
+        {
+            "version": "8.11.1"
+        },
+        {
+            "version": "8.4.0"
+        },
+        {
+            "version": "8.5.0"
+        },
+        {
+            "version": "8.9.4"
+        }
+    ],
+    "python": [
+        {
+            "version": "3.6"
+        }
+    ]
 }
\ No newline at end of file
diff --git a/src/shared/bindings.json b/src/shared/bindings.json
index b9d17ce..359f25f 100644
--- a/src/shared/bindings.json
+++ b/src/shared/bindings.json
@@ -1,1741 +1,1749 @@
 {
-    "$schema": "<TBD>",
-    "contentVersion": "2016-03-04-alpha",
-    "variables": {
-        "storageConnStringLabel": "$variables_storageConnStringLabel",
-        "appSettingsHelp": "$variables_appSettingsHelp",
-        "selectConnection": "$variables_selectConnection",
-        "parameterName": "$variables_parameterName",
-        "paramNameLabel": "$variables_paramNameLabel",
-        "paramNameInputHelp": "$variables_paramNameInputHelp",
-        "paramNameOutputHelp": "$variables_paramNameOutputHelp",
-        "apiHubTableDataSetLabel": "$variables_apiHubTableDataSetLabel",
-        "apiHubTableDataSetHelp": "$variables_apiHubTableDataSetHelp",
-        "apiHubTableNameLabel": "$variables_apiHubTableNameLabel",
-        "apiHubTableHelp": "$variables_apiHubTableHelp",
-        "apiHubTableEntityLabel": "$variables_apiHubTableEntityLabel",
-        "apiHubTableEntityHelp": "$variables_apiHubTableEntityHelp",
-        "apiHubTableConnectionLabel": "$variables_apiHubTableConnectionLabel",
-        "apiHubTableConnectionHelp": "$variables_apiHubTableConnectionHelp"
+  "$schema": "<TBD>",
+  "contentVersion": "2016-03-04-alpha",
+  "variables": {
+    "storageConnStringLabel": "$variables_storageConnStringLabel",
+    "appSettingsHelp": "$variables_appSettingsHelp",
+    "selectConnection": "$variables_selectConnection",
+    "parameterName": "$variables_parameterName",
+    "paramNameLabel": "$variables_paramNameLabel",
+    "paramNameInputHelp": "$variables_paramNameInputHelp",
+    "paramNameOutputHelp": "$variables_paramNameOutputHelp",
+    "apiHubTableDataSetLabel": "$variables_apiHubTableDataSetLabel",
+    "apiHubTableDataSetHelp": "$variables_apiHubTableDataSetHelp",
+    "apiHubTableNameLabel": "$variables_apiHubTableNameLabel",
+    "apiHubTableHelp": "$variables_apiHubTableHelp",
+    "apiHubTableEntityLabel": "$variables_apiHubTableEntityLabel",
+    "apiHubTableEntityHelp": "$variables_apiHubTableEntityHelp",
+    "apiHubTableConnectionLabel": "$variables_apiHubTableConnectionLabel",
+    "apiHubTableConnectionHelp": "$variables_apiHubTableConnectionHelp"
+  },
+  "bindings": [
+    {
+      "type": "timerTrigger",
+      "displayName": "$timerTrigger_displayName",
+      "direction": "trigger",
+      "enabledInTryMode": true,
+      "documentation": "$content=Documentation\\timerTrigger.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "myTimer",
+          "required": true,
+          "label": "$timerTrigger_name_label",
+          "help": "$timerTrigger_name_help",
+          "validators": [
+            {
+              "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        },
+        {
+          "name": "schedule",
+          "value": "string",
+          "defaultValue": "0 * * * * *",
+          "required": true,
+          "label": "$timerTrigger_schedule_label",
+          "help": "$timerTrigger_schedule_help",
+          "validators": [
+            {
+              "expression": "^(\\*|((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?)(,((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?)(,((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|(((1\\d)|(2[0-3])|\\d)(\\-((1\\d)|(2[0-3])|\\d)(\\/\\d+)?)?)(,(((1\\d)|(2[0-3])|\\d)(\\-((1\\d)|(2[0-3])|\\d)(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|((([1-2]\\d)|(3[0-1])|[1-9])(\\-(([1-2]\\d)|(3[0-1])|[1-9])(\\/\\d+)?)?)(,((([1-2]\\d)|(3[0-1])|[1-9])(\\-(([1-2]\\d)|(3[0-1])|[1-9])(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|(([A-Za-z]+|(1[0-2])|[1-9])(\\-([A-Za-z]+|(1[0-2])|[1-9])(\\/\\d+)?)?)(,(([A-Za-z]+|(1[0-2])|[1-9])(\\-([A-Za-z]+|(1[0-2])|[1-9])(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|(([A-Za-z]+|[0-6])(\\-([A-Za-z]+|[0-6])(\\/\\d+)?)?)(,(([A-Za-z]+|[0-6])(\\-([A-Za-z]+|[0-6])(\\/\\d+)?)?))*)(\\/\\d+)?$",
+              "errorText": "$timerTrigger_schedule_errorText"
+            }
+          ]
+        }
+      ]
     },
-    "bindings": [
-        {
-            "type": "timerTrigger",
-            "displayName": "$timerTrigger_displayName",
-            "direction": "trigger",
-            "enabledInTryMode": true,
-            "documentation": "$content=Documentation\\timerTrigger.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "myTimer",
-                    "required": true,
-                    "label": "$timerTrigger_name_label",
-                    "help": "$timerTrigger_name_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "schedule",
-                    "value": "string",
-                    "defaultValue": "0 * * * * *",
-                    "required": true,
-                    "label": "$timerTrigger_schedule_label",
-                    "help": "$timerTrigger_schedule_help",
-                    "validators": [
-                        {
-                            "expression": "^(\\*|((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?)(,((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?)(,((([1-5]\\d)|\\d)(\\-(([1-5]\\d)|\\d)(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|(((1\\d)|(2[0-3])|\\d)(\\-((1\\d)|(2[0-3])|\\d)(\\/\\d+)?)?)(,(((1\\d)|(2[0-3])|\\d)(\\-((1\\d)|(2[0-3])|\\d)(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|((([1-2]\\d)|(3[0-1])|[1-9])(\\-(([1-2]\\d)|(3[0-1])|[1-9])(\\/\\d+)?)?)(,((([1-2]\\d)|(3[0-1])|[1-9])(\\-(([1-2]\\d)|(3[0-1])|[1-9])(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|(([A-Za-z]+|(1[0-2])|[1-9])(\\-([A-Za-z]+|(1[0-2])|[1-9])(\\/\\d+)?)?)(,(([A-Za-z]+|(1[0-2])|[1-9])(\\-([A-Za-z]+|(1[0-2])|[1-9])(\\/\\d+)?)?))*)(\\/\\d+)? (\\*|(([A-Za-z]+|[0-6])(\\-([A-Za-z]+|[0-6])(\\/\\d+)?)?)(,(([A-Za-z]+|[0-6])(\\-([A-Za-z]+|[0-6])(\\/\\d+)?)?))*)(\\/\\d+)?$",
-                            "errorText": "$timerTrigger_schedule_errorText"
-                        }
-                    ]
-                }
-            ]
+    {
+      "type": "eventHubTrigger",
+      "displayName": "$eventHubTrigger_displayName",
+      "direction": "trigger",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\eventHubTrigger.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "myEventHubMessage",
+          "required": true,
+          "label": "$eventHubTrigger_name_label",
+          "help": "$eventHubTrigger_name_help",
+          "validators": [
+            {
+              "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
         },
         {
-            "type": "eventHubTrigger",
-            "displayName": "$eventHubTrigger_displayName",
-            "direction": "trigger",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\eventHubTrigger.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "myEventHubMessage",
-                    "required": true,
-                    "label": "$eventHubTrigger_name_label",
-                    "help": "$eventHubTrigger_name_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "eventHubName",
-                    "value": "string",
-                    "defaultValue": "myeventhub",
-                    "required": true,
-                    "label": "$eventHubOut_path_label",
-                    "help": "$eventHubTrigger_path_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-z0-9]$|^[a-z0-9][a-z0-9-_.]{0,48}[a-z0-9]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
-                            "errorText": "$eventHubTrigger_path_errorText"
-                        }
-                    ]
-                },
-                {
-                    "name": "consumerGroup",
-                    "value": "string",
-                    "defaultValue": "$Default",
-                    "required": false,
-                    "label": "$eventHubTrigger_consumerGroup_label",
-                    "help": "$eventHubTrigger_consumerGroup_help",
-                    "validators": [
-                        {
-                            "expression": "(^[a-z0-9]$|^[a-z0-9][a-z0-9-_.]{0,48}[a-z0-9]$)|^\\$Default$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
-                            "errorText": "$eventHubTrigger_consumerGroup_errorText"
-                        }
-                    ]
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "EventHub",
-                    "required": true,
-                    "label": "$eventHubTrigger_connection_label",
-                    "help": "$eventHubTrigger_connection_help",
-                    "placeholder": "[variables('selectConnection')]"
-                }
-            ]
+          "name": "eventHubName",
+          "value": "string",
+          "defaultValue": "myeventhub",
+          "required": true,
+          "label": "$eventHubOut_path_label",
+          "help": "$eventHubTrigger_path_help",
+          "validators": [
+            {
+              "expression": "^[a-z0-9]$|^[a-z0-9][a-z0-9-_.]{0,48}[a-z0-9]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
+              "errorText": "$eventHubTrigger_path_errorText"
+            }
+          ]
         },
         {
-            "type": "eventHub",
-            "displayName": "$eventHubOut_displayName",
-            "direction": "out",
-            "enabledInTryMode": false,
-            "actions": [
-                {
-                    "template": "EventHubTrigger",
-                    "binding": "eventHubTrigger",
-                    "settings": [
-                        "path",
-                        "connection"
-                    ]
-                }
-            ],
-            "documentation": "$content=Documentation\\eventHubOut.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "outputEventHubMessage",
-                    "required": true,
-                    "label": "$eventHubOut_name_label",
-                    "help": "$eventHubOut_name_help",
-                    "validators": [
-                        {
-                            "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "path",
-                    "value": "string",
-                    "defaultValue": "outeventhub",
-                    "required": true,
-                    "label": "$eventHubOut_path_label",
-                    "help": "$eventHubOut_path_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-z0-9]$|^[a-z0-9][a-z0-9-_.]{0,48}[a-z0-9]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
-                            "errorText": "$eventHubOut_path_errorText"
-                        }
-                    ]
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "EventHub",
-                    "required": true,
-                    "label": "$eventHubOut_connection_label",
-                    "help": "$eventHubOut_connection_help",
-                    "placeholder": "[variables('selectConnection')]"
-                }
-            ]
+          "name": "consumerGroup",
+          "value": "string",
+          "defaultValue": "$Default",
+          "required": false,
+          "label": "$eventHubTrigger_consumerGroup_label",
+          "help": "$eventHubTrigger_consumerGroup_help",
+          "validators": [
+            {
+              "expression": "(^[a-z0-9]$|^[a-z0-9][a-z0-9-_.]{0,48}[a-z0-9]$)|^\\$Default$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
+              "errorText": "$eventHubTrigger_consumerGroup_errorText"
+            }
+          ]
         },
         {
-            "type": "queue",
-            "displayName": "$queueOut_displayName",
-            "direction": "out",
-            "enabledInTryMode": true,
-            "actions": [
-                {
-                    "template": "QueueTrigger",
-                    "binding": "queueTrigger",
-                    "settings": [
-                        "queueName",
-                        "connection"
-                    ]
-                }
-            ],
-            "documentation": "$content=Documentation\\queueOut.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "outputQueueItem",
-                    "required": true,
-                    "label": "$queueOut_name_label",
-                    "help": "$queueOut_name_help",
-                    "validators": [
-                        {
-                            "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "queueName",
-                    "value": "string",
-                    "defaultValue": "outqueue",
-                    "required": true,
-                    "label": "$queueOut_queueName_label",
-                    "help": "$queueOut_queueName_help",
-                    "validators": [
-                        {
-                            "expression": "^[0-9a-z][a-z0-9-]{1,61}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
-                            "errorText": "$queueOut_queueName_errorText"
-                        }
-                    ]
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "Storage",
-                    "required": true,
-                    "label": "[variables('storageConnStringLabel')]",
-                    "help": "[variables('appSettingsHelp')]",
-                    "placeholder": "[variables('selectConnection')]"
-                }
-            ]
+          "name": "connection",
+          "value": "string",
+          "resource": "EventHub",
+          "required": true,
+          "label": "$eventHubTrigger_connection_label",
+          "help": "$eventHubTrigger_connection_help",
+          "placeholder": "[variables('selectConnection')]"
+        }
+      ]
+    },
+    {
+      "type": "eventHub",
+      "displayName": "$eventHubOut_displayName",
+      "direction": "out",
+      "enabledInTryMode": false,
+      "actions": [
+        {
+          "template": "EventHubTrigger",
+          "binding": "eventHubTrigger",
+          "settings": [
+            "path",
+            "connection"
+          ]
+        }
+      ],
+      "documentation": "$content=Documentation\\eventHubOut.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "outputEventHubMessage",
+          "required": true,
+          "label": "$eventHubOut_name_label",
+          "help": "$eventHubOut_name_help",
+          "validators": [
+            {
+              "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
         },
         {
-            "type": "queueTrigger",
-            "displayName": "$queueTrigger_displayName",
-            "direction": "trigger",
-            "enabledInTryMode": true,
-            "documentation": "$content=Documentation\\queueTrigger.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "myQueueItem",
-                    "required": true,
-                    "label": "$queueTrigger_name_label",
-                    "help": "$queueTrigger_name_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "queueName",
-                    "value": "string",
-                    "defaultValue": "myqueue",
-                    "required": true,
-                    "label": "$queueTrigger_queueName_label",
-                    "help": "$queueTrigger_queueName_help",
-                    "validators": [
-                        {
-                            "expression": "^[0-9a-z][a-z0-9-]{1,61}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
-                            "errorText": "$queueTrigger_queueName_errorText"
-                        }
-                    ]
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "Storage",
-                    "required": true,
-                    "label": "[variables('storageConnStringLabel')]",
-                    "help": "[variables('appSettingsHelp')]",
-                    "placeholder": "[variables('selectConnection')]"
-                }
-            ]
+          "name": "path",
+          "value": "string",
+          "defaultValue": "outeventhub",
+          "required": true,
+          "label": "$eventHubOut_path_label",
+          "help": "$eventHubOut_path_help",
+          "validators": [
+            {
+              "expression": "^[a-z0-9]$|^[a-z0-9][a-z0-9-_.]{0,48}[a-z0-9]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
+              "errorText": "$eventHubOut_path_errorText"
+            }
+          ]
         },
         {
-            "type": "blob",
-            "displayName": "$blobOut_displayName",
-            "direction": "out",
-            "enabledInTryMode": true,
-            "actions": [
-                {
-                    "template": "BlobTrigger",
-                    "binding": "blobTrigger",
-                    "settings": [
-                        "path",
-                        "connection"
-                    ]
-                }
-            ],
-            "documentation": "$content=Documentation\\blobOut.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "outputBlob",
-                    "required": true,
-                    "label": "$blobOut_name_label",
-                    "help": "$blobOut_name_help",
-                    "validators": [
-                        {
-                            "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "path",
-                    "value": "string",
-                    "defaultValue": "outcontainer/{rand-guid}",
-                    "required": true,
-                    "label": "$blobOut_path_label",
-                    "help": "$blobOut_path_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-z0-9{](?:[a-z0-9{}]|(?:\\-(?!\\-))){1,61}[a-z0-9{}][\\/](\\S){0,1023}[^\\/]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
-                            "errorText": "$blobOut_path_errorText"
-                        }
-                    ]
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "Storage",
-                    "required": true,
-                    "label": "[variables('storageConnStringLabel')]",
-                    "help": "[variables('appSettingsHelp')]",
-                    "placeholder": "[variables('selectConnection')]"
-                }
-            ]
+          "name": "connection",
+          "value": "string",
+          "resource": "EventHub",
+          "required": true,
+          "label": "$eventHubOut_connection_label",
+          "help": "$eventHubOut_connection_help",
+          "placeholder": "[variables('selectConnection')]"
+        }
+      ]
+    },
+    {
+      "type": "queue",
+      "displayName": "$queueOut_displayName",
+      "direction": "out",
+      "enabledInTryMode": true,
+      "actions": [
+        {
+          "template": "QueueTrigger",
+          "binding": "queueTrigger",
+          "settings": [
+            "queueName",
+            "connection"
+          ]
+        }
+      ],
+      "documentation": "$content=Documentation\\queueOut.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "outputQueueItem",
+          "required": true,
+          "label": "$queueOut_name_label",
+          "help": "$queueOut_name_help",
+          "validators": [
+            {
+              "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
         },
         {
-            "type": "blob",
-            "displayName": "$blobIn_displayName",
-            "direction": "in",
-            "enabledInTryMode": true,
-            "documentation": "$content=Documentation\\blobIn.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "inputBlob",
-                    "required": true,
-                    "label": "$blobIn_name_label",
-                    "help": "$blobIn_name_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "path",
-                    "value": "string",
-                    "defaultValue": "incontainer/{name}",
-                    "required": true,
-                    "label": "$blobIn_path_label",
-                    "help": "$blobIn_path_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-z0-9{](?:[a-z0-9{}]|(?:\\-(?!\\-))){1,61}[a-z0-9{}][\\/](\\S){0,1023}[^\\/]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
-                            "errorText": "$blobIn_patherrorText"
-                        }
-                    ]
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "Storage",
-                    "required": true,
-                    "label": "[variables('storageConnStringLabel')]",
-                    "help": "[variables('appSettingsHelp')]",
-                    "placeholder": "[variables('selectConnection')]"
-                }
-            ]
+          "name": "queueName",
+          "value": "string",
+          "defaultValue": "outqueue",
+          "required": true,
+          "label": "$queueOut_queueName_label",
+          "help": "$queueOut_queueName_help",
+          "validators": [
+            {
+              "expression": "^[0-9a-z][a-z0-9-]{1,61}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
+              "errorText": "$queueOut_queueName_errorText"
+            }
+          ]
         },
         {
-            "type": "blobTrigger",
-            "displayName": "$blobTrigger_displayName",
-            "direction": "trigger",
-            "enabledInTryMode": true,
-            "documentation": "$content=Documentation\\blobTrigger.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "myBlob",
-                    "required": true,
-                    "label": "$blobTrigger_name_label",
-                    "help": "$blobTrigger_name_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "path",
-                    "value": "string",
-                    "defaultValue": "mycontainer",
-                    "required": true,
-                    "label": "$blobTrigger_path_label",
-                    "help": "$blobTrigger_path_help",
-                    "validators": [
-                        {
-                            "expression": "(^[a-z0-9{](?:[a-z0-9{}]|(?:\\-(?!\\-))){1,61}[a-z0-9{}]$)|(^[a-z0-9{](?:[a-z0-9{}]|(?:\\-(?!\\-))){1,61}[a-z0-9{}][\\/](\\S){0,1023}[^\\/]$)|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
-                            "errorText": "$blobTrigger_path_errorText"
-                        }
-                    ]
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "Storage",
-                    "required": true,
-                    "label": "[variables('storageConnStringLabel')]",
-                    "help": "[variables('appSettingsHelp')]",
-                    "placeholder": "[variables('selectConnection')]"
-                }
-            ]
+          "name": "connection",
+          "value": "string",
+          "resource": "Storage",
+          "required": true,
+          "label": "[variables('storageConnStringLabel')]",
+          "help": "[variables('appSettingsHelp')]",
+          "placeholder": "[variables('selectConnection')]"
+        }
+      ]
+    },
+    {
+      "type": "queueTrigger",
+      "displayName": "$queueTrigger_displayName",
+      "direction": "trigger",
+      "enabledInTryMode": true,
+      "documentation": "$content=Documentation\\queueTrigger.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "myQueueItem",
+          "required": true,
+          "label": "$queueTrigger_name_label",
+          "help": "$queueTrigger_name_help",
+          "validators": [
+            {
+              "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
         },
         {
-            "type": "apiHubFile",
-            "displayName": "$apiHubFileIn_displayName",
-            "direction": "in",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\apiHubFileIn.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "inputFile",
-                    "required": true,
-                    "label": "$apiHubFileIn_name_label",
-                    "help": "$apiHubFileIn_name_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "path",
-                    "value": "string",
-                    "defaultValue": "path/{file}",
-                    "required": true,
-                    "label": "$apiHubFileIn_path_label",
-                    "help": "$apiHubFileIn_path_help",
-                    "validators": []
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "ApiHub",
-                    "required": true,
-                    "label": "$apiHubFileIn_connection_label",
-                    "help": "$apiHubFileIn_connection_help",
-                    "metadata": {
-                        "capability": "blob"
-                    }
-                }
-            ]
+          "name": "queueName",
+          "value": "string",
+          "defaultValue": "myqueue",
+          "required": true,
+          "label": "$queueTrigger_queueName_label",
+          "help": "$queueTrigger_queueName_help",
+          "validators": [
+            {
+              "expression": "^[0-9a-z][a-z0-9-]{1,61}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
+              "errorText": "$queueTrigger_queueName_errorText"
+            }
+          ]
         },
         {
-            "type": "apiHubFile",
-            "displayName": "$apiHubFileIn_displayName",
-            "direction": "out",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\apiHubFileOut.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "outputFile",
-                    "required": true,
-                    "label": "$apiHubFileIn_name_label",
-                    "help": "$apiHubFileIn_name_help",
-                    "validators": [
-                        {
-                            "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "path",
-                    "value": "string",
-                    "defaultValue": "path/{file}",
-                    "required": true,
-                    "label": "$apiHubFileIn_path_label",
-                    "help": "$apiHubFileIn_path_help",
-                    "validators": []
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "ApiHub",
-                    "required": true,
-                    "label": "$apiHubFileIn_connection_label",
-                    "help": "$apiHubFileIn_connection_help",
-                    "metadata": {
-                        "capability": "blob"
-                    }
-                }
-            ]
+          "name": "connection",
+          "value": "string",
+          "resource": "Storage",
+          "required": true,
+          "label": "[variables('storageConnStringLabel')]",
+          "help": "[variables('appSettingsHelp')]",
+          "placeholder": "[variables('selectConnection')]"
+        }
+      ]
+    },
+    {
+      "type": "blob",
+      "displayName": "$blobOut_displayName",
+      "direction": "out",
+      "enabledInTryMode": true,
+      "actions": [
+        {
+          "template": "BlobTrigger",
+          "binding": "blobTrigger",
+          "settings": [
+            "path",
+            "connection"
+          ]
+        }
+      ],
+      "documentation": "$content=Documentation\\blobOut.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "outputBlob",
+          "required": true,
+          "label": "$blobOut_name_label",
+          "help": "$blobOut_name_help",
+          "validators": [
+            {
+              "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
         },
         {
-            "type": "apiHubFileTrigger",
-            "displayName": "$apiHubFileTrigger_displayName",
-            "direction": "trigger",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\apiHubFileTrigger.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "inputFile",
-                    "required": true,
-                    "label": "$apiHubFileTrigger_name_label",
-                    "help": "$apiHubFileTrigger_name_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "path",
-                    "value": "string",
-                    "defaultValue": "path/{file}",
-                    "required": true,
-                    "label": "$apiHubFileTrigger_path_label",
-                    "help": "$apiHubFileTrigger_path_help",
-                    "validators": []
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "ApiHub",
-                    "required": true,
-                    "label": "$apiHubFileTrigger_connection_label",
-                    "help": "$apiHubFileTrigger_connection_help",
-                    "metadata": {
-                        "capability": "blob",
-                        "excluded": [
-                            "googledrive",
-                            "azureblob"
-                        ]
-                    }
-                }
-            ]
+          "name": "path",
+          "value": "string",
+          "defaultValue": "outcontainer/{rand-guid}",
+          "required": true,
+          "label": "$blobOut_path_label",
+          "help": "$blobOut_path_help",
+          "validators": [
+            {
+              "expression": "^[a-z0-9{](?:[a-z0-9{}]|(?:\\-(?!\\-))){1,61}[a-z0-9{}][\\/](\\S){0,1023}[^\\/]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
+              "errorText": "$blobOut_path_errorText"
+            }
+          ]
         },
         {
-            "type": "apiHubTable",
-            "displayName": "$apiHubTableIn_displayName",
-            "direction": "in",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\apiHubTable.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "inputTable",
-                    "required": true,
-                    "label": "[variables('paramNameLabel')]",
-                    "help": "[variables('paramNameInputHelp')]",
-                    "validators": [
-                        {
-                            "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "dataSetName",
-                    "value": "string",
-                    "defaultValue": "default",
-                    "required": false,
-                    "label": "[variables('apiHubTableDataSetLabel')]",
-                    "help": "[variables('apiHubTableDataSetHelp')]",
-                    "validators": []
-                },
-                {
-                    "name": "tableName",
-                    "value": "string",
-                    "defaultValue": null,
-                    "required": false,
-                    "label": "[variables('apiHubTableNameLabel')]",
-                    "help": "[variables('apiHubTableHelp')]",
-                    "validators": []
-                },
-                {
-                    "name": "entityId",
-                    "value": "string",
-                    "defaultValue": null,
-                    "required": false,
-                    "label": "[variables('apiHubTableEntityLabel')]",
-                    "help": "[variables('apiHubTableEntityHelp')]",
-                    "validators": []
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "ApiHub",
-                    "required": true,
-                    "label": "[variables('apiHubTableConnectionLabel')]",
-                    "help": "[variables('apiHubTableConnectionHelp')]",
-                    "metadata": {
-                        "capability": "tabular"
-                    }
-                }
-            ]
+          "name": "connection",
+          "value": "string",
+          "resource": "Storage",
+          "required": true,
+          "label": "[variables('storageConnStringLabel')]",
+          "help": "[variables('appSettingsHelp')]",
+          "placeholder": "[variables('selectConnection')]"
+        }
+      ]
+    },
+    {
+      "type": "blob",
+      "displayName": "$blobIn_displayName",
+      "direction": "in",
+      "enabledInTryMode": true,
+      "documentation": "$content=Documentation\\blobIn.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "inputBlob",
+          "required": true,
+          "label": "$blobIn_name_label",
+          "help": "$blobIn_name_help",
+          "validators": [
+            {
+              "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
         },
         {
-            "type": "apiHubTable",
-            "displayName": "$apiHubTableOut_displayName",
-            "direction": "out",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\apiHubTableOut.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "outputTable",
-                    "required": true,
-                    "label": "[variables('paramNameLabel')]",
-                    "help": "[variables('paramNameOutputHelp')]",
-                    "validators": [
-                        {
-                            "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "dataSetName",
-                    "value": "string",
-                    "defaultValue": "default",
-                    "required": false,
-                    "label": "[variables('apiHubTableDataSetLabel')]",
-                    "help": "[variables('apiHubTableDataSetHelp')]",
-                    "validators": []
-                },
-                {
-                    "name": "tableName",
-                    "value": "string",
-                    "defaultValue": null,
-                    "required": false,
-                    "label": "[variables('apiHubTableNameLabel')]",
-                    "help": "[variables('apiHubTableHelp')]",
-                    "validators": []
-                },
-                {
-                    "name": "entityId",
-                    "value": "string",
-                    "defaultValue": null,
-                    "required": false,
-                    "label": "[variables('apiHubTableEntityLabel')]",
-                    "help": "[variables('apiHubTableEntityHelp')]",
-                    "validators": []
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "ApiHub",
-                    "required": true,
-                    "label": "[variables('apiHubTableConnectionLabel')]",
-                    "help": "[variables('apiHubTableConnectionHelp')]",
-                    "metadata": {
-                        "capability": "tabular"
-                    }
-                }
-            ]
+          "name": "path",
+          "value": "string",
+          "defaultValue": "incontainer/{name}",
+          "required": true,
+          "label": "$blobIn_path_label",
+          "help": "$blobIn_path_help",
+          "validators": [
+            {
+              "expression": "^[a-z0-9{](?:[a-z0-9{}]|(?:\\-(?!\\-))){1,61}[a-z0-9{}][\\/](\\S){0,1023}[^\\/]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
+              "errorText": "$blobIn_patherrorText"
+            }
+          ]
         },
         {
-            "type": "httpTrigger",
-            "displayName": "$httpTrigger_displayName",
-            "direction": "trigger",
-            "enabledInTryMode": true,
-            "documentation": "$content=Documentation\\httpTrigger.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "req",
-                    "required": true,
-                    "label": "$httpTrigger_name_label",
-                    "help": "$httpTrigger_name_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "route",
-                    "value": "string",
-                    "required": false,
-                    "label": "$httpTrigger_route_label",
-                    "help": "$httpTrigger_route_help",
-                    "validators": []
-                },
-                {
-                    "name": "webHookType",
-                    "value": "enum",
-                    "enum": [
-                        {
-                            "value": "github",
-                            "display": "GitHub"
-                        },
-                        {
-                            "value": "genericJson",
-                            "display": "Generic JSON"
-                        },
-                        {
-                            "value":"slack",
-                            "display":"Slack"
-                        }
-                    ],
-                    "label": "$httpTrigger_webHookType_label",
-                    "help": "$httpTrigger_webHookType_help"
-                },
-                {
-                    "name": "authLevel",
-                    "value": "enum",
-                    "enum": [
-                        {
-                            "value": "function",
-                            "display": "Function"
-                        },
-                        {
-                            "value": "anonymous",
-                            "display": "Anonymous"
-                        },
-                        {
-                            "value": "admin",
-                            "display": "Admin"
-                        }
-                    ],
-                    "label": "$httpTrigger_authLevel_label",
-                    "help": "$httpTrigger_authLevel_help"
-                },
-                {
-                    "name": "methods",
-                    "value": "checkBoxList",
-                    "defaultValue": [
-                        "get",
-                        "post",
-                        "delete",
-                        "head",
-                        "patch",
-                        "put",
-                        "options",
-                        "trace"
-                    ],
-                    "enum": [
-                        {
-                            "value": "get",
-                            "display": "GET"
-                        },
-                        {
-                            "value": "post",
-                            "display": "POST"
-                        },
-                        {
-                            "value": "delete",
-                            "display": "DELETE"
-                        },
-                        {
-                            "value": "head",
-                            "display": "HEAD"
-                        },
-                        {
-                            "value": "patch",
-                            "display": "PATCH"
-                        },
-                        {
-                            "value": "put",
-                            "display": "PUT"
-                        },
-                        {
-                            "value": "options",
-                            "display": "OPTIONS"
-                        },
-                        {
-                            "value": "trace",
-                            "display": "TRACE"
-                        }
-                    ],
-                    "label": "$httpTrigger_methods_label",
-                    "help": "$httpTrigger_methods_help"
-                }
-            ],
-            "rules": [
-                {
-                    "name": "mode",
-                    "type": "exclusivity",
-                    "values": [
-                        {
-                            "value": "authLevel",
-                            "display": "Standard",
-                            "hiddenSettings": [
-                                "webHookType"
-                            ],
-                            "shownSettings": [
-                                "authLevel"
-                            ]
-                        },
-                        {
-                            "value": "webHookType",
-                            "display": "Webhook",
-                            "hiddenSettings": [
-                                "authLevel"
-                            ],
-                            "shownSettings": [
-                                "webHookType"
-                            ]
-                        }
-                    ],
-                    "label": "$httpTrigger_mode_label",
-                    "help": "$httpTrigger_mode_help"
-                },
-                {
-                    "name": "methodRule",
-                    "type": "exclusivity",
-                    "values": [
-                        {
-                            "value": "allMethods",
-                            "display": "All methods",
-                            "hiddenSettings": [
-                                "methods"
-                            ],
-                            "shownSettings": []
-                        },
-                        {
-                            "value": "methods",
-                            "display": "Selected methods",
-                            "hiddenSettings": [],
-                            "shownSettings": [
-                                "methods"
-                            ]
-                        }
-                    ],
-                    "label": "$httpTrigger_methodRule_label",
-                    "help": "$httpTrigger_methodRule_help"
-                }
-            ]
+          "name": "connection",
+          "value": "string",
+          "resource": "Storage",
+          "required": true,
+          "label": "[variables('storageConnStringLabel')]",
+          "help": "[variables('appSettingsHelp')]",
+          "placeholder": "[variables('selectConnection')]"
+        }
+      ]
+    },
+    {
+      "type": "blobTrigger",
+      "displayName": "$blobTrigger_displayName",
+      "direction": "trigger",
+      "enabledInTryMode": true,
+      "documentation": "$content=Documentation\\blobTrigger.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "myBlob",
+          "required": true,
+          "label": "$blobTrigger_name_label",
+          "help": "$blobTrigger_name_help",
+          "validators": [
+            {
+              "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
         },
         {
-            "type": "http",
-            "displayName": "$httpOut_displayName",
-            "direction": "out",
-            "enabledInTryMode": true,
-            "documentation": "$content=Documentation\\httpOut.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "res",
-                    "required": true,
-                    "label": "$httpOut_name_label",
-                    "help": "$httpOut_name_help",
-                    "validators": [
-                        {
-                            "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                }
-            ]
+          "name": "path",
+          "value": "string",
+          "defaultValue": "mycontainer",
+          "required": true,
+          "label": "$blobTrigger_path_label",
+          "help": "$blobTrigger_path_help",
+          "validators": [
+            {
+              "expression": "(^[a-z0-9{](?:[a-z0-9{}]|(?:\\-(?!\\-))){1,61}[a-z0-9{}]$)|(^[a-z0-9{](?:[a-z0-9{}]|(?:\\-(?!\\-))){1,61}[a-z0-9{}][\\/](\\S){0,1023}[^\\/]$)|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
+              "errorText": "$blobTrigger_path_errorText"
+            }
+          ]
         },
         {
-            "type": "serviceBusTrigger",
-            "displayName": "$serviceBusTrigger_displayName",
-            "direction": "trigger",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\serviceBusTrigger.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "mySbMsg",
-                    "required": true,
-                    "label": "$serviceBusTrigger_name_label",
-                    "help": "$serviceBusTrigger_name_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "queueName",
-                    "value": "string",
-                    "defaultValue": "mysbqueue",
-                    "required": false,
-                    "label": "$serviceBusTrigger_queueName_label",
-                    "help": "$serviceBusTrigger_queueName_help",
-                    "validators": [
-                        {
-                            "expression": "^[0-9a-z][a-z0-9_.-]{1,48}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
-                            "errorText": "$serviceBusTrigger_queueName_errorText"
-                        }
-                    ]
-                },
-                {
-                    "name": "topicName",
-                    "value": "string",
-                    "defaultValue": "mysbtopic",
-                    "required": false,
-                    "label": "$serviceBusTrigger_topicName_label",
-                    "help": "$serviceBusTrigger_topicName_help",
-                    "validators": [
-                        {
-                            "expression": "^[0-9a-z][a-z0-9_.-]{1,48}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
-                            "errorText": "$serviceBusTrigger_topicName_errorText"
-                        }
-                    ]
-                },
-                {
-                    "name": "subscriptionName",
-                    "value": "string",
-                    "defaultValue": "mysubscription",
-                    "required": false,
-                    "label": "$serviceBusTrigger_subscriptionName_label",
-                    "help": "$serviceBusTrigger_subscriptionName_help",
-                    "validators": [
-                        {
-                            "expression": "^[0-9a-zA-Z][a-zA-Z0-9_.-]{1,48}[0-9a-zA-Z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
-                            "errorText": "$serviceBusTrigger_subscriptionName_errorText"
-                        }
-                    ]
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "EventHub",
-                    "required": true,
-                    "label": "$serviceBusTrigger_connection_label",
-                    "help": "$serviceBusTrigger_connection_help",
-                    "placeholder": "[variables('selectConnection')]"
-                },
-                {
-                    "name": "accessRights",
-                    "value": "enum",
-                    "enum": [
-                        {
-                            "value": "Manage",
-                            "display": "Manage"
-                        },
-                        {
-                            "value": "Listen",
-                            "display": "Listen"
-                        }
-                    ],
-                    "label": "$serviceBusTrigger_accessRights_label",
-                    "help": "$serviceBusTrigger_accessRights_help"
-                }
-            ],
-            "rules": [
-                {
-                    "name": "messageType",
-                    "type": "exclusivity",
-                    "values": [
-                        {
-                            "value": "queueName",
-                            "display": "$serviceBusTrigger_messageType_queueName",
-                            "hiddenSettings": [
-                                "topicName",
-                                "subscriptionName"
-                            ],
-                            "shownSettings": [
-                                "queueName"
-                            ]
-                        },
-                        {
-                            "value": "topicName",
-                            "display": "$serviceBusTrigger_messageType_topicName",
-                            "hiddenSettings": [
-                                "queueName"
-                            ],
-                            "shownSettings": [
-                                "topicName",
-                                "subscriptionName"
-                            ]
-                        }
-                    ],
-                    "label": "$serviceBusTrigger_messageType_label",
-                    "help": "$serviceBusTrigger_messageType_help"
-                }
-            ]
+          "name": "connection",
+          "value": "string",
+          "resource": "Storage",
+          "required": true,
+          "label": "[variables('storageConnStringLabel')]",
+          "help": "[variables('appSettingsHelp')]",
+          "placeholder": "[variables('selectConnection')]"
+        }
+      ]
+    },
+    {
+      "type": "apiHubFile",
+      "displayName": "$apiHubFileIn_displayName",
+      "direction": "in",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\apiHubFileIn.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "inputFile",
+          "required": true,
+          "label": "$apiHubFileIn_name_label",
+          "help": "$apiHubFileIn_name_help",
+          "validators": [
+            {
+              "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
         },
         {
-            "type": "serviceBus",
-            "displayName": "$serviceBusOut_displayName",
-            "direction": "out",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\serviceBusOut.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "outputSbMsg",
-                    "required": true,
-                    "label": "$serviceBusOut_name_label",
-                    "help": "$serviceBusOut_name_help",
-                    "validators": [
-                        {
-                            "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "queueName",
-                    "value": "string",
-                    "defaultValue": "outqueue",
-                    "required": true,
-                    "label": "$serviceBusOut_queueName_label",
-                    "help": "$serviceBusOut_queueName_help",
-                    "validators": [
-                        {
-                            "expression": "^[0-9a-z][a-z0-9_.-]{1,48}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
-                            "errorText": "$serviceBusOut_queueName_errorText"
-                        }
-                    ]
-                },
-                {
-                    "name": "topicName",
-                    "value": "string",
-                    "defaultValue": "outtopic",
-                    "required": true,
-                    "label": "$serviceBusOut_topicName_label",
-                    "help": "$serviceBusOut_topicName_help",
-                    "validators": [
-                        {
-                            "expression": "^[0-9a-z][a-z0-9_.-]{1,48}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
-                            "errorText": "$serviceBusOut_topicName_errorText"
-                        }
-                    ]
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "EventHub",
-                    "required": true,
-                    "label": "$serviceBusOut_connection_label",
-                    "help": "$serviceBusOut_connection_help",
-                    "placeholder": "[variables('selectConnection')]"
-                },
-                {
-                    "name": "accessRights_",
-                    "value": "enum",
-                    "enum": [
-                        {
-                            "value": "Manage",
-                            "display": "Manage"
-                        },
-                        {
-                            "value": "Send",
-                            "display": "Send"
-                        }
-                    ],
-                    "label": "$serviceBusOut_accessRights_label",
-                    "help": "$serviceBusOut_accessRights_help"
-                }
-            ],
-            "rules": [
-                {
-                    "name": "messageType",
-                    "type": "exclusivity",
-                    "values": [
-                        {
-                            "value": "queueName",
-                            "display": "$serviceBusOut_messageType_queueName",
-                            "hiddenSettings": [
-                                "topicName"
-                            ],
-                            "shownSettings": [
-                                "queueName"
-                            ]
-                        },
-                        {
-                            "value": "topicName",
-                            "display": "$serviceBusOut_messageType_topicName",
-                            "hiddenSettings": [
-                                "queueName"
-                            ],
-                            "shownSettings": [
-                                "topicName"
-                            ]
-                        }
-                    ],
-                    "label": "$serviceBusOut_messageType_label",
-                    "help": "$serviceBusOut_messageType_help"
-                }
-            ]
+          "name": "path",
+          "value": "string",
+          "defaultValue": "path/{file}",
+          "required": true,
+          "label": "$apiHubFileIn_path_label",
+          "help": "$apiHubFileIn_path_help",
+          "validators": []
         },
         {
-            "type": "manualTrigger",
-            "displayName": "Manual",
-            "direction": "trigger",
-            "enabledInTryMode": true,
-            "documentation": "$content=Documentation\\manualTrigger.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "input",
-                    "required": true,
-                    "label": "$manualTrigger_name_label",
-                    "help": "$manualTrigger_name_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                }
-            ]
+          "name": "connection",
+          "value": "string",
+          "resource": "ApiHub",
+          "required": true,
+          "label": "$apiHubFileIn_connection_label",
+          "help": "$apiHubFileIn_connection_help",
+          "metadata": {
+            "capability": "blob"
+          }
+        }
+      ]
+    },
+    {
+      "type": "apiHubFile",
+      "displayName": "$apiHubFileIn_displayName",
+      "direction": "out",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\apiHubFileOut.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "outputFile",
+          "required": true,
+          "label": "$apiHubFileIn_name_label",
+          "help": "$apiHubFileIn_name_help",
+          "validators": [
+            {
+              "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
         },
         {
-            "type": "table",
-            "displayName": "$tableout_displayName",
-            "direction": "out",
-            "enabledInTryMode": true,
-            "documentation": "$content=Documentation\\tableOut.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "outputTable",
-                    "required": true,
-                    "label": "$tableout_name_label",
-                    "help": "$tableout_name_help",
-                    "validators": [
-                        {
-                            "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "tableName",
-                    "value": "string",
-                    "defaultValue": "outTable",
-                    "required": true,
-                    "label": "$table_tableName_label",
-                    "help": "$table_tableName_help",
-                    "validators": [
-                        {
-                            "expression": "^[A-Za-z][A-Za-z0-9]{2,62}$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
-                            "errorText": "$table_tableName_errorText"
-                        }
-                    ]
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "Storage",
-                    "required": true,
-                    "label": "[variables('storageConnStringLabel')]",
-                    "help": "[variables('appSettingsHelp')]",
-                    "placeholder": "[variables('selectConnection')]"
-                }
-            ]
+          "name": "path",
+          "value": "string",
+          "defaultValue": "path/{file}",
+          "required": true,
+          "label": "$apiHubFileIn_path_label",
+          "help": "$apiHubFileIn_path_help",
+          "validators": []
         },
         {
-            "type": "table",
-            "displayName": "$tableIn_displayName",
-            "direction": "in",
-            "enabledInTryMode": true,
-            "documentation": "$content=Documentation\\tableIn.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "inputTable",
-                    "required": true,
-                    "label": "$tableIn_name_label",
-                    "help": "$tableIn_name_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "tableName",
-                    "value": "string",
-                    "defaultValue": "inTable",
-                    "required": true,
-                    "label": "$tableIn_tableName_label",
-                    "help": "$tableIn_tableName_help",
-                    "validators": [
-                        {
-                            "expression": "^[A-Za-z][A-Za-z0-9]{2,62}$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
-                            "errorText": "$tableIn_tableName_errorText"
-                        }
-                    ]
-                },
-                {
-                    "name": "partitionKey",
-                    "value": "string",
-                    "required": false,
-                    "label": "$tableIn_partitionKey_label",
-                    "help": "$tableIn_partitionKey_help"
-                },
-                {
-                    "name": "rowKey",
-                    "value": "string",
-                    "required": false,
-                    "label": "$tableIn_rowKey_label",
-                    "help": "$tableIn_rowKey_help"
-                },
-                {
-                    "name": "take",
-                    "value": "int",
-                    "defaultValue": 50,
-                    "required": false,
-                    "label": "$tableIn_take_label",
-                    "help": "$tableIn_take_help"
-                },
-                {
-                    "name": "filter",
-                    "value": "string",
-                    "required": false,
-                    "label": "$tableIn_filter_label",
-                    "help": "$tableIn_filter_help"
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "Storage",
-                    "required": true,
-                    "label": "[variables('storageConnStringLabel')]",
-                    "help": "[variables('appSettingsHelp')]",
-                    "placeholder": "[variables('selectConnection')]"
-                }
-            ]
+          "name": "connection",
+          "value": "string",
+          "resource": "ApiHub",
+          "required": true,
+          "label": "$apiHubFileIn_connection_label",
+          "help": "$apiHubFileIn_connection_help",
+          "metadata": {
+            "capability": "blob"
+          }
+        }
+      ]
+    },
+    {
+      "type": "apiHubFileTrigger",
+      "displayName": "$apiHubFileTrigger_displayName",
+      "direction": "trigger",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\apiHubFileTrigger.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "inputFile",
+          "required": true,
+          "label": "$apiHubFileTrigger_name_label",
+          "help": "$apiHubFileTrigger_name_help",
+          "validators": [
+            {
+              "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
         },
         {
-            "type": "documentDB",
-            "displayName": "$documentDB_displayName",
-            "direction": "out",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\documentDBOut.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "outputDocument",
-                    "required": true,
-                    "label": "$documentDBOut_name_label",
-                    "help": "$documentDBOut_name_help",
-                    "validators": [
-                        {
-                            "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "databaseName",
-                    "value": "string",
-                    "defaultValue": "outDatabase",
-                    "required": true,
-                    "label": "$documentDBOut_databaseName_label",
-                    "help": "$documentDBOut_databaseName_help"
-                },
-                {
-                    "name": "collectionName",
-                    "value": "string",
-                    "defaultValue": "MyCollection",
-                    "required": true,
-                    "label": "$documentDBOut_collectionName_label",
-                    "help": "$documentDBOut_collectionName_help"
-                },
-                {
-                    "name": "createIfNotExists",
-                    "value": "boolean",
-                    "defaultValue": false,
-                    "required": true,
-                    "label": "$documentDBOut_createIfNotExists_label",
-                    "help": "$documentDBOut_createIfNotExists_help"
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "DocumentDB",
-                    "required": true,
-                    "label": "$documentDBOut_connection_label",
-                    "help": "$documentDBOut_connection_help",
-                    "placeholder": "[variables('selectConnection')]"
-                }
-            ]
+          "name": "path",
+          "value": "string",
+          "defaultValue": "path/{file}",
+          "required": true,
+          "label": "$apiHubFileTrigger_path_label",
+          "help": "$apiHubFileTrigger_path_help",
+          "validators": []
         },
         {
-            "type": "documentDB",
-            "displayName": "$documentDBIn_displayName",
-            "direction": "in",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\documentDBIn.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "inputDocument",
-                    "required": true,
-                    "label": "$documentDBIn_name_label",
-                    "help": "$documentDBIn_name_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "databaseName",
-                    "value": "string",
-                    "defaultValue": "inDatabase",
-                    "required": true,
-                    "label": "$documentDBIn_databaseName_label",
-                    "help": "$documentDBIn_databaseName_help"
-                },
-                {
-                    "name": "collectionName",
-                    "value": "string",
-                    "defaultValue": "MyCollection",
-                    "required": true,
-                    "label": "$documentDBIn_collectionName_label",
-                    "help": "$documentDBIn_collectionName_help"
-                },
-                {
-                    "name": "id",
-                    "value": "string",
-                    "defaultValue": "{documentId}",
-                    "required": true,
-                    "label": "$documentDBIn_id_label",
-                    "help": "$documentDBIn_id_help"
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "DocumentDB",
-                    "required": true,
-                    "label": "$documentDBIn_connection_label",
-                    "help": "$documentDBIn_connection_help",
-                    "placeholder": "[variables('selectConnection')]"
-                }
+          "name": "connection",
+          "value": "string",
+          "resource": "ApiHub",
+          "required": true,
+          "label": "$apiHubFileTrigger_connection_label",
+          "help": "$apiHubFileTrigger_connection_help",
+          "metadata": {
+            "capability": "blob",
+            "excluded": [
+              "googledrive",
+              "azureblob"
             ]
+          }
+        }
+      ]
+    },
+    {
+      "type": "apiHubTable",
+      "displayName": "$apiHubTableIn_displayName",
+      "direction": "in",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\apiHubTable.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "inputTable",
+          "required": true,
+          "label": "[variables('paramNameLabel')]",
+          "help": "[variables('paramNameInputHelp')]",
+          "validators": [
+            {
+              "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
         },
         {
-            "type": "mobileTable",
-            "displayName": "$mobileTableOut_displayName",
-            "direction": "out",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\mobileTableOut.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "outputRecord",
-                    "required": true,
-                    "label": "$mobileTableOut_name_label",
-                    "help": "$mobileTableOut_name_help",
-                    "validators": [
-                        {
-                            "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "tableName",
-                    "value": "string",
-                    "defaultValue": "outTable",
-                    "required": true,
-                    "label": "$mobileTableOut_tableName_label",
-                    "help": "$mobileTableOut_tableName_help"
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "defaultValue": "My_MobileApp_Uri",
-                    "required": true,
-                    "label": "$mobileTableOut_connection_label",
-                    "help": "$mobileTableOut_connection_help"
-                },
-                {
-                    "name": "apiKey",
-                    "value": "string",
-                    "required": false,
-                    "label": "$mobileTableOut_apiKey_label",
-                    "help": "$mobileTableOut_apiKey_help"
-                }
-            ]
+          "name": "dataSetName",
+          "value": "string",
+          "defaultValue": "default",
+          "required": false,
+          "label": "[variables('apiHubTableDataSetLabel')]",
+          "help": "[variables('apiHubTableDataSetHelp')]",
+          "validators": []
         },
         {
-            "type": "mobileTable",
-            "displayName": "$mobileTableIn_displayName",
-            "direction": "in",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\mobileTableIn.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "inputRecord",
-                    "required": true,
-                    "label": "$mobileTableIn_name_label",
-                    "help": "$mobileTableIn_name_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "tableName",
-                    "value": "string",
-                    "defaultValue": "inTable",
-                    "required": true,
-                    "label": "$mobileTableIn_tableName_label",
-                    "help": "$mobileTableIn_tableName_help"
-                },
-                {
-                    "name": "id",
-                    "value": "string",
-                    "defaultValue": "{itemId}",
-                    "required": true,
-                    "label": "$mobileTableIn_id_label",
-                    "help": "$mobileTableIn_id_help"
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "defaultValue": "My_MobileApp_Uri",
-                    "required": true,
-                    "label": "$mobileTableIn_connection_label",
-                    "help": "$mobileTableIn_connection_help"
-                },
-                {
-                    "name": "apiKey",
-                    "value": "string",
-                    "required": false,
-                    "label": "$mobileTableIn_apiKey_label",
-                    "help": "$mobileTableIn_apiKey_help"
-                }
-            ]
+          "name": "tableName",
+          "value": "string",
+          "defaultValue": null,
+          "required": false,
+          "label": "[variables('apiHubTableNameLabel')]",
+          "help": "[variables('apiHubTableHelp')]",
+          "validators": []
         },
         {
-            "type": "notificationHub",
-            "displayName": "$notificationHubOut_displayName",
-            "direction": "out",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\notificationHubOut.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "notification",
-                    "required": true,
-                    "label": "$notificationHubOut_name_label",
-                    "help": "$notificationHubOut_name_help",
-                    "validators": [
-                        {
-                            "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "hubName",
-                    "value": "string",
-                    "required": true,
-                    "label": "$notificationHubOut_hubName_label",
-                    "help": "$notificationHubOut_hubName_help"
-                },
-                {
-                    "name": "connection",
-                    "value": "string",
-                    "resource": "ServiceBus",
-                    "required": true,
-                    "label": "$notificationHubOut_connection_label",
-                    "help": "$notificationHubOut_connection_help",
-                    "placeholder": "[variables('selectConnection')]"
-                },
-                {
-                    "name": "tagExpression",
-                    "value": "string",
-                    "required": false,
-                    "label": "$notificationHubOut_tagExpression_label",
-                    "help": "$notificationHubOut_tagExpression_help"
-                },
-                {
-                    "name": "enableTestSend",
-                    "value": "boolean",
-                    "required": false,
-                    "label": "$notificationHubOut_enableTestSend_label",
-                    "help": "$notificationHubOut_enableTestSend_help"
-                },
-                {
-                    "name": "platform",
-                    "value": "enum",
-                    "enum": [
-                        {
-                            "value": "",
-                            "display": "Template"
-                        },
-                        {
-                            "value": "apns",
-                            "display": "Apple (APNS)"
-                        },
-                        {
-                            "value": "adm",
-                            "display": "Amazon (ADM)"
-                        },
-                        {
-                            "value": "gcm",
-                            "display": "Google (GCM)"
-                        },
-                        {
-                            "value": "wns",
-                            "display": "Windows (WNS)"
-                        },
-                        {
-                            "value": "mpns",
-                            "display": "Windows Phone (MPNS)"
-                        }
-                    ],
-                    "label": "$notificationHubOut_platform_label",
-                    "help": "$notificationHubOut_platform_help"
-                }
-            ]
+          "name": "entityId",
+          "value": "string",
+          "defaultValue": null,
+          "required": false,
+          "label": "[variables('apiHubTableEntityLabel')]",
+          "help": "[variables('apiHubTableEntityHelp')]",
+          "validators": []
         },
         {
-            "type": "sendGrid",
-            "displayName": "$sendGrid_displayName",
-            "direction": "out",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\sendGridOut.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "message",
-                    "required": true,
-                    "label": "$sendGrid_name_label",
-                    "help": "$sendGrid_name_help",
-                    "validators": [
-                        {
-                            "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "apiKey",
-                    "value": "string",
-                    "defaultValue": "SendGridApiKey",
-                    "required": true,
-                    "label": "$sendGrid_apiKey_label",
-                    "help": "$sendGrid_apiKey_help"
-                },
-                {
-                    "name": "to",
-                    "value": "string",
-                    "defaultValue": "",
-                    "required": false,
-                    "label": "$sendGrid_to_label",
-                    "help": "$sendGrid_to_help"
-                },
-                {
-                    "name": "from",
-                    "value": "string",
-                    "defaultValue": "",
-                    "required": false,
-                    "label": "$sendGrid_from_label",
-                    "help": "$sendGrid_from_help"
-                },
-                {
-                    "name": "subject",
-                    "value": "string",
-                    "defaultValue": "",
-                    "required": false,
-                    "label": "$sendGrid_subject_label",
-                    "help": "$sendGrid_subject_help"
-                },
-                {
-                    "name": "text",
-                    "value": "string",
-                    "defaultValue": "",
-                    "required": false,
-                    "label": "$sendGrid_text_label",
-                    "help": "$sendGrid_text_help"
-                }
-            ]
+          "name": "connection",
+          "value": "string",
+          "resource": "ApiHub",
+          "required": true,
+          "label": "[variables('apiHubTableConnectionLabel')]",
+          "help": "[variables('apiHubTableConnectionHelp')]",
+          "metadata": {
+            "capability": "tabular"
+          }
+        }
+      ]
+    },
+    {
+      "type": "apiHubTable",
+      "displayName": "$apiHubTableOut_displayName",
+      "direction": "out",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\apiHubTableOut.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "outputTable",
+          "required": true,
+          "label": "[variables('paramNameLabel')]",
+          "help": "[variables('paramNameOutputHelp')]",
+          "validators": [
+            {
+              "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
         },
         {
-            "type": "twilioSms",
-            "displayName": "$twilioSms_displayName",
-            "direction": "out",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\twilioSmsOut.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "message",
-                    "required": true,
-                    "label": "$twilioSms_name_label",
-                    "help": "$twilioSms_name_help",
-                    "validators": [
-                        {
-                            "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "accountSid",
-                    "value": "string",
-                    "defaultValue": "TwilioAccountSid",
-                    "required": true,
-                    "label": "$twilioSms_accountsid_label",
-                    "help": "$twilioSms_accountsid_help"
-                },
-                {
-                    "name": "authToken",
-                    "value": "string",
-                    "defaultValue": "TwilioAuthToken",
-                    "required": true,
-                    "label": "$twilioSms_authtoken_label",
-                    "help": "$twilioSms_authtoken_help"
-                },
-                {
-                    "name": "to",
-                    "value": "string",
-                    "defaultValue": "",
-                    "required": false,
-                    "label": "$twilioSms_to_label",
-                    "help": "$twilioSms_to_help"
-                },
-                {
-                    "name": "from",
-                    "value": "string",
-                    "defaultValue": "",
-                    "required": false,
-                    "label": "$twilioSms_from_label",
-                    "help": "$twilioSms_from_help"
-                },
-                {
-                    "name": "body",
-                    "value": "string",
-                    "defaultValue": "",
-                    "required": false,
-                    "label": "$twilioSms_body_label",
-                    "help": "$twilioSms_body_help"
-                }
-            ]
+          "name": "dataSetName",
+          "value": "string",
+          "defaultValue": "default",
+          "required": false,
+          "label": "[variables('apiHubTableDataSetLabel')]",
+          "help": "[variables('apiHubTableDataSetHelp')]",
+          "validators": []
         },
         {
-            "type": "bot",
-            "displayName": "$bot_out_displayName",
-            "direction": "out",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\botOut.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "$return",
-                    "required": true,
-                    "label": "$bot_out_name_label",
-                    "help": "$bot_out_name_help",
-                    "validators": [
-                        {
-                            "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "botId",
-                    "value": "string",
-                    "required": true,
-                    "label": "$bot_botId_label",
-                    "help": "$bot_botId_help"
-                },
-                {
-                    "name": "secret",
-                    "value": "string",
-                    "required": false,
-                    "label": "$bot_out_secret_label",
-                    "help": "$bot_out_secret_help"
-                }
-            ]
+          "name": "tableName",
+          "value": "string",
+          "defaultValue": null,
+          "required": false,
+          "label": "[variables('apiHubTableNameLabel')]",
+          "help": "[variables('apiHubTableHelp')]",
+          "validators": []
         },
         {
-            "type": "bot",
-            "displayName": "$bot_in_displayName",
-            "direction": "in",
-            "enabledInTryMode": false,
-            "documentation": "$content=Documentation\\botIn.md",
-            "settings": [
-                {
-                    "name": "name",
-                    "value": "string",
-                    "defaultValue": "bot",
-                    "required": true,
-                    "label": "$bot_in_name_label",
-                    "help": "$bot_in_name_help",
-                    "validators": [
-                        {
-                            "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
-                            "errorText": "[variables('parameterName')]"
-                        }
-                    ]
-                },
-                {
-                    "name": "secret",
-                    "value": "string",
-                    "required": false,
-                    "label": "$bot_in_secret_label",
-                    "help": "$bot_in_secret_help"
-                }
-            ]
+          "name": "entityId",
+          "value": "string",
+          "defaultValue": null,
+          "required": false,
+          "label": "[variables('apiHubTableEntityLabel')]",
+          "help": "[variables('apiHubTableEntityHelp')]",
+          "validators": []
+        },
+        {
+          "name": "connection",
+          "value": "string",
+          "resource": "ApiHub",
+          "required": true,
+          "label": "[variables('apiHubTableConnectionLabel')]",
+          "help": "[variables('apiHubTableConnectionHelp')]",
+          "metadata": {
+            "capability": "tabular"
+          }
+        }
+      ]
+    },
+    {
+      "type": "httpTrigger",
+      "displayName": "$httpTrigger_displayName",
+      "direction": "trigger",
+      "enabledInTryMode": true,
+      "documentation": "$content=Documentation\\httpTrigger.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "req",
+          "required": true,
+          "label": "$httpTrigger_name_label",
+          "help": "$httpTrigger_name_help",
+          "validators": [
+            {
+              "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        },
+        {
+          "name": "route",
+          "value": "string",
+          "required": false,
+          "label": "$httpTrigger_route_label",
+          "help": "$httpTrigger_route_help",
+          "validators": []
+        },
+        {
+          "name": "webHookType",
+          "value": "enum",
+          "enum": [
+            {
+              "value": "github",
+              "display": "GitHub"
+            },
+            {
+              "value": "genericJson",
+              "display": "Generic JSON"
+            },
+            {
+              "value": "slack",
+              "display": "Slack"
+            }
+          ],
+          "label": "$httpTrigger_webHookType_label",
+          "help": "$httpTrigger_webHookType_help"
+        },
+        {
+          "name": "authLevel",
+          "value": "enum",
+          "enum": [
+            {
+              "value": "function",
+              "display": "Function"
+            },
+            {
+              "value": "anonymous",
+              "display": "Anonymous"
+            },
+            {
+              "value": "admin",
+              "display": "Admin"
+            }
+          ],
+          "label": "$httpTrigger_authLevel_label",
+          "help": "$httpTrigger_authLevel_help"
+        },
+        {
+          "name": "methods",
+          "value": "checkBoxList",
+          "defaultValue": [
+            "get",
+            "post",
+            "delete",
+            "head",
+            "patch",
+            "put",
+            "options",
+            "trace"
+          ],
+          "enum": [
+            {
+              "value": "get",
+              "display": "GET"
+            },
+            {
+              "value": "post",
+              "display": "POST"
+            },
+            {
+              "value": "delete",
+              "display": "DELETE"
+            },
+            {
+              "value": "head",
+              "display": "HEAD"
+            },
+            {
+              "value": "patch",
+              "display": "PATCH"
+            },
+            {
+              "value": "put",
+              "display": "PUT"
+            },
+            {
+              "value": "options",
+              "display": "OPTIONS"
+            },
+            {
+              "value": "trace",
+              "display": "TRACE"
+            }
+          ],
+          "label": "$httpTrigger_methods_label",
+          "help": "$httpTrigger_methods_help"
+        }
+      ],
+      "rules": [
+        {
+          "name": "mode",
+          "type": "exclusivity",
+          "values": [
+            {
+              "value": "authLevel",
+              "display": "Standard",
+              "hiddenSettings": [
+                "webHookType"
+              ],
+              "shownSettings": [
+                "authLevel"
+              ]
+            },
+            {
+              "value": "webHookType",
+              "display": "Webhook",
+              "hiddenSettings": [
+                "authLevel"
+              ],
+              "shownSettings": [
+                "webHookType"
+              ]
+            }
+          ],
+          "label": "$httpTrigger_mode_label",
+          "help": "$httpTrigger_mode_help"
+        },
+        {
+          "name": "methodRule",
+          "type": "exclusivity",
+          "values": [
+            {
+              "value": "allMethods",
+              "display": "All methods",
+              "hiddenSettings": [
+                "methods"
+              ],
+              "shownSettings": []
+            },
+            {
+              "value": "methods",
+              "display": "Selected methods",
+              "hiddenSettings": [],
+              "shownSettings": [
+                "methods"
+              ]
+            }
+          ],
+          "label": "$httpTrigger_methodRule_label",
+          "help": "$httpTrigger_methodRule_help"
+        }
+      ]
+    },
+    {
+      "type": "http",
+      "displayName": "$httpOut_displayName",
+      "direction": "out",
+      "enabledInTryMode": true,
+      "documentation": "$content=Documentation\\httpOut.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "res",
+          "required": true,
+          "label": "$httpOut_name_label",
+          "help": "$httpOut_name_help",
+          "validators": [
+            {
+              "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "type": "serviceBusTrigger",
+      "displayName": "$serviceBusTrigger_displayName",
+      "direction": "trigger",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\serviceBusTrigger.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "mySbMsg",
+          "required": true,
+          "label": "$serviceBusTrigger_name_label",
+          "help": "$serviceBusTrigger_name_help",
+          "validators": [
+            {
+              "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        },
+        {
+          "name": "queueName",
+          "value": "string",
+          "defaultValue": "mysbqueue",
+          "required": false,
+          "label": "$serviceBusTrigger_queueName_label",
+          "help": "$serviceBusTrigger_queueName_help",
+          "validators": [
+            {
+              "expression": "^[0-9a-z][a-z0-9_.-]{1,48}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
+              "errorText": "$serviceBusTrigger_queueName_errorText"
+            }
+          ]
+        },
+        {
+          "name": "topicName",
+          "value": "string",
+          "defaultValue": "mysbtopic",
+          "required": false,
+          "label": "$serviceBusTrigger_topicName_label",
+          "help": "$serviceBusTrigger_topicName_help",
+          "validators": [
+            {
+              "expression": "^[0-9a-z][a-z0-9_.-]{1,48}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
+              "errorText": "$serviceBusTrigger_topicName_errorText"
+            }
+          ]
+        },
+        {
+          "name": "subscriptionName",
+          "value": "string",
+          "defaultValue": "mysubscription",
+          "required": false,
+          "label": "$serviceBusTrigger_subscriptionName_label",
+          "help": "$serviceBusTrigger_subscriptionName_help",
+          "validators": [
+            {
+              "expression": "^[0-9a-zA-Z][a-zA-Z0-9_.-]{1,48}[0-9a-zA-Z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
+              "errorText": "$serviceBusTrigger_subscriptionName_errorText"
+            }
+          ]
+        },
+        {
+          "name": "connection",
+          "value": "string",
+          "resource": "EventHub",
+          "required": true,
+          "label": "$serviceBusTrigger_connection_label",
+          "help": "$serviceBusTrigger_connection_help",
+          "placeholder": "[variables('selectConnection')]"
+        },
+        {
+          "name": "accessRights",
+          "value": "enum",
+          "enum": [
+            {
+              "value": "Manage",
+              "display": "Manage"
+            },
+            {
+              "value": "Listen",
+              "display": "Listen"
+            }
+          ],
+          "label": "$serviceBusTrigger_accessRights_label",
+          "help": "$serviceBusTrigger_accessRights_help"
+        }
+      ],
+      "rules": [
+        {
+          "name": "messageType",
+          "type": "exclusivity",
+          "values": [
+            {
+              "value": "queueName",
+              "display": "$serviceBusTrigger_messageType_queueName",
+              "hiddenSettings": [
+                "topicName",
+                "subscriptionName"
+              ],
+              "shownSettings": [
+                "queueName"
+              ]
+            },
+            {
+              "value": "topicName",
+              "display": "$serviceBusTrigger_messageType_topicName",
+              "hiddenSettings": [
+                "queueName"
+              ],
+              "shownSettings": [
+                "topicName",
+                "subscriptionName"
+              ]
+            }
+          ],
+          "label": "$serviceBusTrigger_messageType_label",
+          "help": "$serviceBusTrigger_messageType_help"
+        }
+      ]
+    },
+    {
+      "type": "serviceBus",
+      "displayName": "$serviceBusOut_displayName",
+      "direction": "out",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\serviceBusOut.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "outputSbMsg",
+          "required": true,
+          "label": "$serviceBusOut_name_label",
+          "help": "$serviceBusOut_name_help",
+          "validators": [
+            {
+              "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        },
+        {
+          "name": "queueName",
+          "value": "string",
+          "defaultValue": "outqueue",
+          "required": true,
+          "label": "$serviceBusOut_queueName_label",
+          "help": "$serviceBusOut_queueName_help",
+          "validators": [
+            {
+              "expression": "^[0-9a-z][a-z0-9_.-]{1,48}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
+              "errorText": "$serviceBusOut_queueName_errorText"
+            }
+          ]
+        },
+        {
+          "name": "topicName",
+          "value": "string",
+          "defaultValue": "outtopic",
+          "required": true,
+          "label": "$serviceBusOut_topicName_label",
+          "help": "$serviceBusOut_topicName_help",
+          "validators": [
+            {
+              "expression": "^[0-9a-z][a-z0-9_.-]{1,48}[0-9a-z]$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
+              "errorText": "$serviceBusOut_topicName_errorText"
+            }
+          ]
+        },
+        {
+          "name": "connection",
+          "value": "string",
+          "resource": "EventHub",
+          "required": true,
+          "label": "$serviceBusOut_connection_label",
+          "help": "$serviceBusOut_connection_help",
+          "placeholder": "[variables('selectConnection')]"
+        },
+        {
+          "name": "accessRights_",
+          "value": "enum",
+          "enum": [
+            {
+              "value": "Manage",
+              "display": "Manage"
+            },
+            {
+              "value": "Send",
+              "display": "Send"
+            }
+          ],
+          "label": "$serviceBusOut_accessRights_label",
+          "help": "$serviceBusOut_accessRights_help"
+        }
+      ],
+      "rules": [
+        {
+          "name": "messageType",
+          "type": "exclusivity",
+          "values": [
+            {
+              "value": "queueName",
+              "display": "$serviceBusOut_messageType_queueName",
+              "hiddenSettings": [
+                "topicName"
+              ],
+              "shownSettings": [
+                "queueName"
+              ]
+            },
+            {
+              "value": "topicName",
+              "display": "$serviceBusOut_messageType_topicName",
+              "hiddenSettings": [
+                "queueName"
+              ],
+              "shownSettings": [
+                "topicName"
+              ]
+            }
+          ],
+          "label": "$serviceBusOut_messageType_label",
+          "help": "$serviceBusOut_messageType_help"
+        }
+      ]
+    },
+    {
+      "type": "manualTrigger",
+      "displayName": "Manual",
+      "direction": "trigger",
+      "enabledInTryMode": true,
+      "documentation": "$content=Documentation\\manualTrigger.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "input",
+          "required": true,
+          "label": "$manualTrigger_name_label",
+          "help": "$manualTrigger_name_help",
+          "validators": [
+            {
+              "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        }
+      ]
+    },
+    {
+      "type": "table",
+      "displayName": "$tableout_displayName",
+      "direction": "out",
+      "enabledInTryMode": true,
+      "documentation": "$content=Documentation\\tableOut.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "outputTable",
+          "required": true,
+          "label": "$tableout_name_label",
+          "help": "$tableout_name_help",
+          "validators": [
+            {
+              "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        },
+        {
+          "name": "tableName",
+          "value": "string",
+          "defaultValue": "outTable",
+          "required": true,
+          "label": "$table_tableName_label",
+          "help": "$table_tableName_help",
+          "validators": [
+            {
+              "expression": "^[A-Za-z][A-Za-z0-9]{2,62}$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
+              "errorText": "$table_tableName_errorText"
+            }
+          ]
+        },
+        {
+          "name": "connection",
+          "value": "string",
+          "resource": "Storage",
+          "required": true,
+          "label": "[variables('storageConnStringLabel')]",
+          "help": "[variables('appSettingsHelp')]",
+          "placeholder": "[variables('selectConnection')]"
+        }
+      ]
+    },
+    {
+      "type": "table",
+      "displayName": "$tableIn_displayName",
+      "direction": "in",
+      "enabledInTryMode": true,
+      "documentation": "$content=Documentation\\tableIn.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "inputTable",
+          "required": true,
+          "label": "$tableIn_name_label",
+          "help": "$tableIn_name_help",
+          "validators": [
+            {
+              "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        },
+        {
+          "name": "tableName",
+          "value": "string",
+          "defaultValue": "inTable",
+          "required": true,
+          "label": "$tableIn_tableName_label",
+          "help": "$tableIn_tableName_help",
+          "validators": [
+            {
+              "expression": "^[A-Za-z][A-Za-z0-9]{2,62}$|^[{][a-zA-Z0-9]{1,126}[}]$|^[%][a-zA-Z0-9]{1,126}[%]$",
+              "errorText": "$tableIn_tableName_errorText"
+            }
+          ]
+        },
+        {
+          "name": "partitionKey",
+          "value": "string",
+          "required": false,
+          "label": "$tableIn_partitionKey_label",
+          "help": "$tableIn_partitionKey_help"
+        },
+        {
+          "name": "rowKey",
+          "value": "string",
+          "required": false,
+          "label": "$tableIn_rowKey_label",
+          "help": "$tableIn_rowKey_help"
+        },
+        {
+          "name": "take",
+          "value": "int",
+          "defaultValue": 50,
+          "required": false,
+          "label": "$tableIn_take_label",
+          "help": "$tableIn_take_help"
+        },
+        {
+          "name": "filter",
+          "value": "string",
+          "required": false,
+          "label": "$tableIn_filter_label",
+          "help": "$tableIn_filter_help"
+        },
+        {
+          "name": "connection",
+          "value": "string",
+          "resource": "Storage",
+          "required": true,
+          "label": "[variables('storageConnStringLabel')]",
+          "help": "[variables('appSettingsHelp')]",
+          "placeholder": "[variables('selectConnection')]"
+        }
+      ]
+    },
+    {
+      "type": "cosmosDB",
+      "displayName": "$cosmosDBOut_displayName",
+      "direction": "out",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\cosmosDBOut.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "outputDocument",
+          "required": true,
+          "label": "$cosmosDBOut_name_label",
+          "help": "$cosmosDBOut_name_help",
+          "validators": [
+            {
+              "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        },
+        {
+          "name": "databaseName",
+          "value": "string",
+          "defaultValue": "outDatabase",
+          "required": true,
+          "label": "$cosmosDBOut_databaseName_label",
+          "help": "$cosmosDBOut_databaseName_help"
+        },
+        {
+          "name": "collectionName",
+          "value": "string",
+          "defaultValue": "MyCollection",
+          "required": true,
+          "label": "$cosmosDBOut_collectionName_label",
+          "help": "$cosmosDBOut_collectionName_help"
+        },
+        {
+          "name": "createIfNotExists",
+          "value": "boolean",
+          "defaultValue": true,
+          "required": false,
+          "label": "$cosmosDBOut_createIfNotExists_label",
+          "help": "$cosmosDBOut_createIfNotExists_help"
+        },
+        {
+          "name": "connectionStringSetting",
+          "value": "string",
+          "resource": "CosmosDB",
+          "required": true,
+          "label": "$cosmosDBOut_connection_label",
+          "help": "$cosmosDBOut_connection_help",
+          "placeholder": "[variables('selectConnection')]"
+        }
+      ]
+    },
+    {
+      "type": "cosmosDBTrigger",
+      "displayName": "$cosmosDBIn_displayName",
+      "direction": "trigger",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\cosmosDBTrigger.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "inputDocument",
+          "required": true,
+          "label": "$cosmosDBIn_name_label",
+          "help": "$cosmosDBIn_name_help",
+          "validators": [
+            {
+              "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        },
+        {
+          "name": "databaseName",
+          "value": "string",
+          "defaultValue": "inDatabase",
+          "required": true,
+          "label": "$cosmosDBIn_databaseName_label",
+          "help": "$cosmosDBIn_databaseName_help"
+        },
+        {
+          "name": "collectionName",
+          "value": "string",
+          "defaultValue": "MyCollection",
+          "required": true,
+          "label": "$cosmosDBIn_collectionName_label",
+          "help": "$cosmosDBIn_collectionName_help"
+        },
+        {
+          "name": "leaseCollectionName",
+          "value": "string",
+          "defaultValue": "leases",
+          "required": true,
+          "label": "$cosmosDBIn_leaseCollectionName_label",
+          "help": "$cosmosDBIn_leaseCollectionName_help"
+        },
+        {
+          "name": "createLeaseCollectionIfNotExists",
+          "value": "boolean",
+          "required": false,
+          "defaultValue": true,
+          "label": "$cosmosDBIn_createLeaseCollectionIfNotExists_label",
+          "help": "$cosmosDBIn_createLeaseCollectionIfNotExists_help"
+        },
+        {
+          "name": "connectionStringSetting",
+          "value": "string",
+          "resource": "CosmosDB",
+          "required": true,
+          "label": "$cosmosDBIn_connection_label",
+          "help": "$cosmosDBIn_connection_help",
+          "placeholder": "[variables('selectConnection')]"
+        }
+      ]
+    },
+    {
+      "type": "mobileTable",
+      "displayName": "$mobileTableOut_displayName",
+      "direction": "out",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\mobileTableOut.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "outputRecord",
+          "required": true,
+          "label": "$mobileTableOut_name_label",
+          "help": "$mobileTableOut_name_help",
+          "validators": [
+            {
+              "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        },
+        {
+          "name": "tableName",
+          "value": "string",
+          "defaultValue": "outTable",
+          "required": true,
+          "label": "$mobileTableOut_tableName_label",
+          "help": "$mobileTableOut_tableName_help"
+        },
+        {
+          "name": "connection",
+          "value": "string",
+          "defaultValue": "My_MobileApp_Uri",
+          "required": true,
+          "label": "$mobileTableOut_connection_label",
+          "help": "$mobileTableOut_connection_help"
+        },
+        {
+          "name": "apiKey",
+          "value": "string",
+          "required": false,
+          "label": "$mobileTableOut_apiKey_label",
+          "help": "$mobileTableOut_apiKey_help"
+        }
+      ]
+    },
+    {
+      "type": "mobileTable",
+      "displayName": "$mobileTableIn_displayName",
+      "direction": "in",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\mobileTableIn.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "inputRecord",
+          "required": true,
+          "label": "$mobileTableIn_name_label",
+          "help": "$mobileTableIn_name_help",
+          "validators": [
+            {
+              "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        },
+        {
+          "name": "tableName",
+          "value": "string",
+          "defaultValue": "inTable",
+          "required": true,
+          "label": "$mobileTableIn_tableName_label",
+          "help": "$mobileTableIn_tableName_help"
+        },
+        {
+          "name": "id",
+          "value": "string",
+          "defaultValue": "{itemId}",
+          "required": true,
+          "label": "$mobileTableIn_id_label",
+          "help": "$mobileTableIn_id_help"
+        },
+        {
+          "name": "connection",
+          "value": "string",
+          "defaultValue": "My_MobileApp_Uri",
+          "required": true,
+          "label": "$mobileTableIn_connection_label",
+          "help": "$mobileTableIn_connection_help"
+        },
+        {
+          "name": "apiKey",
+          "value": "string",
+          "required": false,
+          "label": "$mobileTableIn_apiKey_label",
+          "help": "$mobileTableIn_apiKey_help"
+        }
+      ]
+    },
+    {
+      "type": "notificationHub",
+      "displayName": "$notificationHubOut_displayName",
+      "direction": "out",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\notificationHubOut.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "notification",
+          "required": true,
+          "label": "$notificationHubOut_name_label",
+          "help": "$notificationHubOut_name_help",
+          "validators": [
+            {
+              "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        },
+        {
+          "name": "hubName",
+          "value": "string",
+          "required": true,
+          "label": "$notificationHubOut_hubName_label",
+          "help": "$notificationHubOut_hubName_help"
+        },
+        {
+          "name": "connection",
+          "value": "string",
+          "resource": "ServiceBus",
+          "required": true,
+          "label": "$notificationHubOut_connection_label",
+          "help": "$notificationHubOut_connection_help",
+          "placeholder": "[variables('selectConnection')]"
+        },
+        {
+          "name": "tagExpression",
+          "value": "string",
+          "required": false,
+          "label": "$notificationHubOut_tagExpression_label",
+          "help": "$notificationHubOut_tagExpression_help"
+        },
+        {
+          "name": "enableTestSend",
+          "value": "boolean",
+          "required": false,
+          "label": "$notificationHubOut_enableTestSend_label",
+          "help": "$notificationHubOut_enableTestSend_help"
+        },
+        {
+          "name": "platform",
+          "value": "enum",
+          "enum": [
+            {
+              "value": "",
+              "display": "Template"
+            },
+            {
+              "value": "apns",
+              "display": "Apple (APNS)"
+            },
+            {
+              "value": "adm",
+              "display": "Amazon (ADM)"
+            },
+            {
+              "value": "gcm",
+              "display": "Google (GCM)"
+            },
+            {
+              "value": "wns",
+              "display": "Windows (WNS)"
+            },
+            {
+              "value": "mpns",
+              "display": "Windows Phone (MPNS)"
+            }
+          ],
+          "label": "$notificationHubOut_platform_label",
+          "help": "$notificationHubOut_platform_help"
+        }
+      ]
+    },
+    {
+      "type": "sendGrid",
+      "displayName": "$sendGrid_displayName",
+      "direction": "out",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\sendGridOut.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "message",
+          "required": true,
+          "label": "$sendGrid_name_label",
+          "help": "$sendGrid_name_help",
+          "validators": [
+            {
+              "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        },
+        {
+          "name": "apiKey",
+          "value": "string",
+          "defaultValue": "SendGridApiKey",
+          "required": true,
+          "label": "$sendGrid_apiKey_label",
+          "help": "$sendGrid_apiKey_help"
+        },
+        {
+          "name": "to",
+          "value": "string",
+          "defaultValue": "",
+          "required": false,
+          "label": "$sendGrid_to_label",
+          "help": "$sendGrid_to_help"
+        },
+        {
+          "name": "from",
+          "value": "string",
+          "defaultValue": "",
+          "required": false,
+          "label": "$sendGrid_from_label",
+          "help": "$sendGrid_from_help"
+        },
+        {
+          "name": "subject",
+          "value": "string",
+          "defaultValue": "",
+          "required": false,
+          "label": "$sendGrid_subject_label",
+          "help": "$sendGrid_subject_help"
+        },
+        {
+          "name": "text",
+          "value": "string",
+          "defaultValue": "",
+          "required": false,
+          "label": "$sendGrid_text_label",
+          "help": "$sendGrid_text_help"
+        }
+      ]
+    },
+    {
+      "type": "twilioSms",
+      "displayName": "$twilioSms_displayName",
+      "direction": "out",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\twilioSmsOut.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "message",
+          "required": true,
+          "label": "$twilioSms_name_label",
+          "help": "$twilioSms_name_help",
+          "validators": [
+            {
+              "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        },
+        {
+          "name": "accountSid",
+          "value": "string",
+          "defaultValue": "TwilioAccountSid",
+          "required": true,
+          "label": "$twilioSms_accountsid_label",
+          "help": "$twilioSms_accountsid_help"
+        },
+        {
+          "name": "authToken",
+          "value": "string",
+          "defaultValue": "TwilioAuthToken",
+          "required": true,
+          "label": "$twilioSms_authtoken_label",
+          "help": "$twilioSms_authtoken_help"
+        },
+        {
+          "name": "to",
+          "value": "string",
+          "defaultValue": "",
+          "required": false,
+          "label": "$twilioSms_to_label",
+          "help": "$twilioSms_to_help"
+        },
+        {
+          "name": "from",
+          "value": "string",
+          "defaultValue": "",
+          "required": false,
+          "label": "$twilioSms_from_label",
+          "help": "$twilioSms_from_help"
+        },
+        {
+          "name": "body",
+          "value": "string",
+          "defaultValue": "",
+          "required": false,
+          "label": "$twilioSms_body_label",
+          "help": "$twilioSms_body_help"
+        }
+      ]
+    },
+    {
+      "type": "bot",
+      "displayName": "$bot_out_displayName",
+      "direction": "out",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\botOut.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "$return",
+          "required": true,
+          "label": "$bot_out_name_label",
+          "help": "$bot_out_name_help",
+          "validators": [
+            {
+              "expression": "(^[a-zA-Z][a-zA-Z0-9]{0,127}$)|^\\$return$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        },
+        {
+          "name": "botId",
+          "value": "string",
+          "required": true,
+          "label": "$bot_botId_label",
+          "help": "$bot_botId_help"
+        },
+        {
+          "name": "secret",
+          "value": "string",
+          "required": false,
+          "label": "$bot_out_secret_label",
+          "help": "$bot_out_secret_help"
+        }
+      ]
+    },
+    {
+      "type": "bot",
+      "displayName": "$bot_in_displayName",
+      "direction": "in",
+      "enabledInTryMode": false,
+      "documentation": "$content=Documentation\\botIn.md",
+      "settings": [
+        {
+          "name": "name",
+          "value": "string",
+          "defaultValue": "bot",
+          "required": true,
+          "label": "$bot_in_name_label",
+          "help": "$bot_in_name_help",
+          "validators": [
+            {
+              "expression": "^[a-zA-Z][a-zA-Z0-9]{0,127}$",
+              "errorText": "[variables('parameterName')]"
+            }
+          ]
+        },
+        {
+          "name": "secret",
+          "value": "string",
+          "required": false,
+          "label": "$bot_in_secret_label",
+          "help": "$bot_in_secret_help"
         }
-    ]
-}
\ No newline at end of file
+      ]
+    }
+  ]
+}
diff --git a/src/shared/constants.ts b/src/shared/constants.ts
index 4ed98f7..4ef4d3f 100644
--- a/src/shared/constants.ts
+++ b/src/shared/constants.ts
@@ -21,4 +21,16 @@ export const constants = {
   queueName: "queueName",
   xAzureSettings: "x-azure-settings",
   entryPoint: "entryPoint",
-}
\ No newline at end of file
+  variableKeys: {
+    config: "serverlessAzureConfig",
+    subscriptionId: "subscriptionId",
+    tenantId: "tenantId",
+    appId: "appId",
+    packageTimestamp: "packageTimestamp",
+    azureCredentials: "azureCredentials",
+  },
+  runtimeExtensions: {
+    node: ".js",
+    python: ".py"
+  }
+}
diff --git a/src/shared/utils.ts b/src/shared/utils.ts
index 0a26b5a..59804a0 100644
--- a/src/shared/utils.ts
+++ b/src/shared/utils.ts
@@ -1,6 +1,6 @@
 import { relative } from "path";
 import Serverless from "serverless";
-import { ServerlessAzureFunctionConfig } from "../models/serverless";
+import { ServerlessAzureFunctionConfig, ServerlessAzureConfig } from "../models/serverless";
 import { BindingUtils } from "./bindings";
 import { constants } from "./constants";
 
@@ -12,6 +12,7 @@ export interface FunctionMetadata {
 
 export class Utils {
   public static getFunctionMetaData(functionName: string, serverless: Serverless): FunctionMetadata {
+    const config: ServerlessAzureConfig = serverless.service as any;
     const bindings = [];
     let bindingSettingsNames = [];
     let bindingSettings = [];
@@ -84,7 +85,7 @@ export class Utils {
     functionsJson.bindings = bindings;
     params.functionsJson = functionsJson;
 
-    let { handlerPath, entryPoint } = Utils.getEntryPointAndHandlerPath(handler);
+    let { handlerPath, entryPoint } = Utils.getEntryPointAndHandlerPath(handler, config);
     if (functionObject["scriptFile"]) {
       handlerPath = functionObject["scriptFile"];
     }
@@ -96,26 +97,18 @@ export class Utils {
     };
   }
 
-  public static getEntryPointAndHandlerPath(handler: string) {
-    let handlerPath = "handler.js";
-    let entryPoint = handler;
+  public static getEntryPointAndHandlerPath(handler: string, config: ServerlessAzureConfig) {
     const handlerSplit = handler.split(".");
 
-    if (handlerSplit.length > 1) {
-      entryPoint = handlerSplit[handlerSplit.length - 1];
-      handlerPath = `${handler.substring(0, handler.lastIndexOf("."))}.js`;
-    }
-
-    const metaData = {
-      entryPoint: entryPoint,
-      handlerPath: handlerPath
-    };
+    const entryPoint = (handlerSplit.length > 1) ? handlerSplit[handlerSplit.length - 1] : undefined;
 
-    return metaData;
-  }
+    const handlerPath = ((handlerSplit.length > 1) ? handlerSplit[0] : handler) 
+      + constants.runtimeExtensions[config.provider.functionRuntime.language]
 
-  public static prettyPrint(json) {
-    return JSON.stringify(json, null, 2);
+    return {
+      entryPoint,
+      handlerPath
+    };
   }
 
   /**


Test patch:
diff --git a/pipelines/templates/unit-tests.yml b/pipelines/templates/unit-tests.yml
index 50cd3d3..76ed1eb 100644
--- a/pipelines/templates/unit-tests.yml
+++ b/pipelines/templates/unit-tests.yml
@@ -1,44 +1,44 @@
 jobs:
-- job: "${{ parameters.os }}_${{ parameters.nodeVersion }}"
-  strategy:
-    matrix:
-      linux:
-        imageName: 'ubuntu-16.04'
-      mac:
-        imageName: 'macos-10.13'
-      windows:
-        imageName: 'vs2017-win2016'
-  displayName: "NodeJs_${{ parameters.nodeVersion }}"
-  pool:
-    vmImage: $(imageName)
+  - job: "${{ parameters.name }}"
+    strategy:
+      matrix:
+        linux:
+          imageName: "ubuntu-16.04"
+        mac:
+          imageName: "macos-10.13"
+        windows:
+          imageName: "vs2017-win2016"
+    displayName: "${{ parameters.name }}"
+    pool:
+      vmImage: $(imageName)
 
-  steps:
-  - task: NodeTool@0
-    displayName: 'Use Node ${{ parameters.nodeVersion }}'
-    inputs:
-      versionSpec: ${{ parameters.nodeVersion }}.x
+    steps:
+      - task: NodeTool@0
+        displayName: "Use Node ${{ parameters.nodeVersion }}"
+        inputs:
+          versionSpec: ${{ parameters.nodeVersion }}
 
-  - bash: |
-      set -e
-      
-      npm ci
-      npm run compile
-      npm run test:coverage  # linting is run as part of pretest
-    displayName: 'Run tests'
+      - bash: |
+          set -e
 
-  - ${{ if eq(parameters.nodeVersion, '8') }}:
-    - bash: |
-        set -e
+          npm ci
+          npm run compile
+          npm run test:coverage  # linting is run as part of pretest
+        displayName: "Run tests"
 
-        token=$(CODECOV_TOKEN)
-        if [[ -z "${token}" ]]; then
-          echo "Need to set CODECOV_TOKEN"
-          exit 1
-        fi
+      - ${{ if eq(parameters.nodeVersion, '8.16.1') }}:
+          - bash: |
+              set -e
 
-        # https://docs.codecov.io/docs/about-the-codecov-bash-uploader
-        bash <(curl -s https://codecov.io/bash) -t ${token}
-      displayName: 'Upload code coverage report'
-      env:
-        CODECOV_TOKEN: $(CODECOV_TOKEN)
-      condition: eq(variables['imageName'], 'ubuntu-16.04')
+              token=$(CODECOV_TOKEN)
+              if [[ -z "${token}" ]]; then
+                echo "Need to set CODECOV_TOKEN"
+                exit 1
+              fi
+
+              # https://docs.codecov.io/docs/about-the-codecov-bash-uploader
+              bash <(curl -s https://codecov.io/bash) -t ${token}
+            displayName: "Upload code coverage report"
+            env:
+              CODECOV_TOKEN: $(CODECOV_TOKEN)
+            condition: eq(variables['imageName'], 'ubuntu-16.04')
diff --git a/src/services/apimService.test.ts b/src/services/apimService.test.ts
index 23a4d8f..6e62453 100644
--- a/src/services/apimService.test.ts
+++ b/src/services/apimService.test.ts
@@ -43,6 +43,7 @@ describe("APIM Service", () => {
         resourceGroup: "test-sls-rg",
         region: "West US",
         apim: apimConfig,
+        runtime: "nodejs10.x"
       },
     };
 
@@ -70,8 +71,8 @@ describe("APIM Service", () => {
     const apimConfigName = MockFactory.createTestApimConfig(true);
     (serverless.service.provider as any).apim = apimConfigName;
 
-    const service = new ApimService(serverless);
-    const expectedRegionName = AzureNamingService.createShortAzureRegionName(service.getRegion());
+    new ApimService(serverless);
+    const expectedRegionName = AzureNamingService.createShortAzureRegionName(serverless.service.provider.region);
     expect(apimConfigName.name.includes(expectedRegionName)).toBeTruthy();
   });
 
diff --git a/src/services/armService.test.ts b/src/services/armService.test.ts
index 15e1347..d2baa30 100644
--- a/src/services/armService.test.ts
+++ b/src/services/armService.test.ts
@@ -92,41 +92,6 @@ describe("Arm Service", () => {
       await expect(service.createDeploymentFromType("not-found")).rejects.not.toBeNull();
     });
 
-    it("throws error when invalid nodejs version in defined", async () => {
-      sls.service.provider.runtime = "nodejs10.6.1";
-      await expect(service.createDeploymentFromType("premium")).rejects.toThrowError("Could not find runtime version matching nodejs10.6.1");
-    });
-
-    it("throws error when incomplete nodejs version in defined", async () => {
-      sls.service.provider.runtime = "nodejs8";
-      await expect(service.createDeploymentFromType("premium")).rejects.toThrowError("Could not find runtime version matching nodejs8");
-    });
-
-    it("throws error when unsupported nodejs version in defined", async () => {
-      sls.service.provider.runtime = "nodejs5.x";
-      await expect(service.createDeploymentFromType("premium")).rejects.toThrowError("Could not find runtime version matching nodejs5.x");
-    });
-
-    it("Does not throw an error when valid nodejs version is defined", async () => {
-      sls.service.provider.runtime = "nodejs10.x";
-      await expect(service.createDeploymentFromType("premium")).resolves.not.toThrow();
-    });
-
-    it("Does not throw an error when nodejs version with major and minor is defined", async () => {
-      sls.service.provider.runtime = "nodejs6.9.x";
-      await expect(service.createDeploymentFromType("premium")).resolves.not.toThrow();
-    });
-
-    it("Does not throw an error when specific nodejs version is defined", async () => {
-      sls.service.provider.runtime = "nodejs10.6.0";
-      await expect(service.createDeploymentFromType("premium")).resolves.not.toThrow();
-    });
-
-    it("throws an error when no nodejs version is defined", async () => {
-      sls.service.provider.runtime = undefined;
-      await expect(service.createDeploymentFromType("premium")).rejects.toThrowError("Runtime version not specified in serverless.yml");
-    });
-
     it("Premium template includes correct resources", async () => {
       sls.service.provider.runtime = "nodejs10.14.1";
       const deployment = await service.createDeploymentFromType(ArmTemplateType.Premium);
diff --git a/src/services/azureKeyVaultService.test.ts b/src/services/azureKeyVaultService.test.ts
index d06bba3..58ff881 100644
--- a/src/services/azureKeyVaultService.test.ts
+++ b/src/services/azureKeyVaultService.test.ts
@@ -72,7 +72,8 @@ describe("Azure Key Vault Service", () => {
         name: "azure",
         resourceGroup: "test-sls-rg",
         region: "West US",
-        keyVault
+        keyVault,
+        runtime: "nodejs10.x",
       }
     };
 
diff --git a/src/services/baseService.test.ts b/src/services/baseService.test.ts
index f37557a..90be581 100644
--- a/src/services/baseService.test.ts
+++ b/src/services/baseService.test.ts
@@ -1,10 +1,9 @@
 import fs from "fs";
 import mockFs from "mock-fs";
 import Serverless from "serverless";
-import { ServerlessAzureOptions, ServerlessAzureConfig } from "../models/serverless";
+import { ServerlessAzureOptions } from "../models/serverless";
 import { MockFactory } from "../test/mockFactory";
 import { BaseService } from "./baseService";
-import { AzureNamingService } from "./namingService";
 
 jest.mock("axios", () => jest.fn());
 import axios from "axios";
@@ -40,16 +39,15 @@ class MockService extends BaseService {
 describe("Base Service", () => {
   let service: MockService;
   let serverless: Serverless;
-  const serviceName = "my-custom-service"
-  const loginResultSubscriptionId = "ABC123";
-  const envVarSubscriptionId = "env var sub id";
-
 
+  const serviceName = "my-custom-service";
+  
   const slsConfig = {
     service: serviceName,
     provider: {
       resourceGroup: "My-Resource-Group",
       deploymentName: "My-Deployment",
+      runtime: "nodejs10.x"
     },
   };
 
@@ -59,7 +57,6 @@ describe("Base Service", () => {
 
   function createMockService(sls: Serverless, options?: Serverless.Options) {
     sls.variables["azureCredentials"] = MockFactory.createTestAzureCredentials();
-    sls.variables["subscriptionId"] = loginResultSubscriptionId;
     Object.assign(sls.service, slsConfig);
 
     return new MockService(sls, options);
@@ -87,65 +84,7 @@ describe("Base Service", () => {
     expect(mockService).not.toBeNull();
     expect(serverless.service.provider.region).toEqual("westus");
     expect(serverless.service.provider.stage).toEqual("dev");
-  });
-
-  it("returns region and stage based on CLI options", () => {
-    const cliOptions = {
-      stage: "prod",
-      region: "eastus2",
-    };
-    const mockService = new MockService(serverless, cliOptions);
-
-    expect(mockService.getRegion()).toEqual(cliOptions.region);
-    expect(mockService.getStage()).toEqual(cliOptions.stage);
-  });
-
-  it("use the resource group name specified in CLI", () => {
-    const resourceGroupName = "cliResourceGroupName"
-    const cliOptions = {
-      stage: "prod",
-      region: "eastus2",
-      resourceGroup: resourceGroupName
-    };
-
-    const mockService = new MockService(serverless, cliOptions);
-    const actualResourceGroupName = mockService.getResourceGroupName();
-
-    expect(actualResourceGroupName).toEqual(resourceGroupName);
-  });
-
-  it("use the resource group name from sls yaml config", () => {
-    const mockService = new MockService(serverless);
-    const actualResourceGroupName = mockService.getResourceGroupName();
-
-    expect(actualResourceGroupName).toEqual(serverless.service.provider["resourceGroup"]);
-  });
-
-  it("Generates resource group from convention when NOT defined in sls yaml", () => {
-    serverless.service.provider["resourceGroup"] = null;
-    const mockService = new MockService(serverless);
-    const actualResourceGroupName = mockService.getResourceGroupName();
-    const expectedRegion = AzureNamingService.createShortAzureRegionName(mockService.getRegion());
-    const expectedStage = AzureNamingService.createShortStageName(mockService.getStage());
-    const expectedResourceGroupName = `sls-${expectedRegion}-${expectedStage}-${serverless.service["service"]}-rg`;
-
-    expect(actualResourceGroupName).toEqual(expectedResourceGroupName);
-  });
-
-  it("set default prefix when one is not defined in yaml config", () => {
-    const mockService = new MockService(serverless);
-    const actualPrefix = mockService.getPrefix();
-    expect(actualPrefix).toEqual("sls");
-  });
-
-  it("use the prefix defined in sls yaml config", () => {
-    const expectedPrefix = "testPrefix"
-    serverless.service.provider["prefix"] = expectedPrefix;
-    const mockService = new MockService(serverless);
-    const actualPrefix = mockService.getPrefix();
-
-    expect(actualPrefix).toEqual(expectedPrefix);
-  });
+  });  
 
   it("Fails if credentials have not been set in serverless config", () => {
     serverless.variables["azureCredentials"] = null;
@@ -202,91 +141,4 @@ describe("Base Service", () => {
 
     readStreamSpy.mockRestore();
   });
-
-  it("sets stage name from CLI", async () => {
-    const stage = "test";
-    delete (serverless.service as any as ServerlessAzureConfig).provider.resourceGroup;
-    expect(serverless.service.provider.stage).not.toEqual(stage);
-    service = new MockService(serverless, { stage } as any);
-    expect(service.getStage()).toEqual(stage);
-    expect(service.getResourceGroupName()).toEqual(`sls-wus-${stage}-${serviceName}-rg`);
-  });
-
-  it("sets region name from CLI", async () => {
-    const region = "East US";
-    delete (serverless.service as any as ServerlessAzureConfig).provider.resourceGroup;
-    expect(serverless.service.provider.region).not.toEqual(region);
-    service = new MockService(serverless, { region } as any);
-    expect(service.getRegion()).toEqual(region);
-    expect(service.getResourceGroupName()).toEqual(`sls-eus-dev-${serviceName}-rg`);
-  });
-
-  it("sets prefix from CLI", async () => {
-    const prefix = "prefix";
-    delete (serverless.service as any as ServerlessAzureConfig).provider.resourceGroup;
-    expect(serverless.service.provider["prefix"]).not.toEqual(prefix);
-    service = new MockService(serverless, { prefix } as any);
-    expect(service.getPrefix()).toEqual(prefix);
-    expect(service.getResourceGroupName()).toEqual(`${prefix}-wus-dev-${serviceName}-rg`);
-  });
-
-  it("sets resource group from CLI", async () => {
-    const resourceGroup = "resourceGroup";
-    delete (serverless.service as any as ServerlessAzureConfig).provider.resourceGroup;
-    expect(serverless.service.provider["resourceGroup"]).not.toEqual(resourceGroup);
-    service = new MockService(serverless, { resourceGroup } as any);
-    expect(service.getResourceGroupName()).toEqual(resourceGroup);
-  });
-
-  const cliSubscriptionId = "cli sub id";
-  const configSubscriptionId = "config sub id";
-
-  it("sets subscription ID from CLI", async () => {
-    process.env.AZURE_SUBSCRIPTION_ID = envVarSubscriptionId;
-    serverless.service.provider["subscriptionId"] = configSubscriptionId;
-    serverless.variables["subscriptionId"] = loginResultSubscriptionId
-    service = new MockService(serverless, { subscriptionId: cliSubscriptionId } as any);
-    expect(service.getSubscriptionId()).toEqual(cliSubscriptionId);
-    expect(serverless.service.provider["subscriptionId"]).toEqual(cliSubscriptionId);
-  });
-
-  it("sets subscription ID from environment variable", async () => {
-    process.env.AZURE_SUBSCRIPTION_ID = envVarSubscriptionId;
-    serverless.service.provider["subscriptionId"] = configSubscriptionId;
-    serverless.variables["subscriptionId"] = loginResultSubscriptionId
-    service = new MockService(serverless, { } as any);
-    expect(service.getSubscriptionId()).toEqual(envVarSubscriptionId);
-    expect(serverless.service.provider["subscriptionId"]).toEqual(envVarSubscriptionId);
-  });
-
-  it("sets subscription ID from config", async () => {
-    delete process.env.AZURE_SUBSCRIPTION_ID;
-    serverless.service.provider["subscriptionId"] = configSubscriptionId;
-    serverless.variables["subscriptionId"] = loginResultSubscriptionId
-    service = new MockService(serverless, { } as any);
-    expect(service.getSubscriptionId()).toEqual(configSubscriptionId);
-    expect(serverless.service.provider["subscriptionId"]).toEqual(configSubscriptionId);
-  });
-
-  it("sets subscription ID from login result", async () => {
-    delete process.env.AZURE_SUBSCRIPTION_ID;
-    serverless.variables["subscriptionId"] = loginResultSubscriptionId
-    service = new MockService(serverless, { } as any);
-    expect(service.getSubscriptionId()).toEqual(loginResultSubscriptionId);
-    expect(serverless.service.provider["subscriptionId"]).toEqual(loginResultSubscriptionId);
-  });
-
-  it("sets region to be value from location property if region not set", () => {
-    const slsService = MockFactory.createTestService();
-    delete slsService.provider.region;
-    const location = "East US";
-    slsService.provider["location"] = location;
-
-    const sls = MockFactory.createTestServerless({
-      service: slsService
-    });
-
-    service = new MockService(sls, {} as any);
-    expect(service.getRegion()).toEqual(location);
-  });
 });
diff --git a/src/services/configService.test.ts b/src/services/configService.test.ts
new file mode 100644
index 0000000..ccd7aa8
--- /dev/null
+++ b/src/services/configService.test.ts
@@ -0,0 +1,270 @@
+import Serverless from "serverless";
+import configConstants from "../config";
+import { DeploymentConfig, FunctionRuntime, ServerlessAzureConfig, SupportedRuntimeLanguage } from "../models/serverless";
+import { MockFactory } from "../test/mockFactory";
+import { ConfigService } from "./configService";
+import { AzureNamingService } from "./namingService";
+
+describe("Config Service", () => {
+  const serviceName = "my-custom-service"
+
+  let serverless: Serverless;
+
+  beforeEach(() => {
+    serverless = MockFactory.createTestServerless();
+    const config = (serverless.service as any as ServerlessAzureConfig);
+    config.service = serviceName;
+
+    delete config.provider.resourceGroup;
+    delete config.provider.region;
+    delete config.provider.stage;
+    delete config.provider.prefix;
+  });
+
+  describe("Configurable Variables", () => {
+    it("returns default values if not specified", () => {
+      const service = new ConfigService(serverless, {} as any);
+      const { prefix, region, stage } = configConstants.defaults;
+      expect(service.getPrefix()).toEqual(prefix);
+      expect(service.getStage()).toEqual(stage);
+      expect(service.getRegion()).toEqual(region);
+    });
+
+    it("use prefix from the CLI over the SLS yml config", () => {
+      const prefix = "prefix";
+      const config = (serverless.service as any as ServerlessAzureConfig);
+      delete config.provider.resourceGroup;
+      config.provider.prefix = "not the prefix";    
+      expect(serverless.service.provider["prefix"]).not.toEqual(prefix);
+      const service = new ConfigService(serverless, { prefix } as any);
+      expect(service.getPrefix()).toEqual(prefix);
+      expect(service.getResourceGroupName()).toEqual(`${prefix}-wus-dev-${serviceName}-rg`);
+    });
+
+    it("use region name from the CLI over the SLS yml config", () => {
+      const region = "East US";
+      const config = (serverless.service as any as ServerlessAzureConfig);
+      delete config.provider.resourceGroup;
+
+      expect(serverless.service.provider.region).not.toEqual(region);
+      const service = new ConfigService(serverless, { region } as any);
+      expect(service.getRegion()).toEqual(region);
+      expect(service.getResourceGroupName()).toEqual(`sls-eus-dev-${serviceName}-rg`);
+    });
+
+    it("use stage name from the CLI over the SLS yml config", () => {
+      const stage = "test";
+      expect(serverless.service.provider.stage).not.toEqual(stage);
+      const service = new ConfigService(serverless, { stage } as any);
+      expect(service.getStage()).toEqual(stage);
+      expect(service.getResourceGroupName()).toEqual(`sls-wus-${stage}-${serviceName}-rg`);
+    });
+
+    it("use the resource group name from the CLI over the SLS yml config", () => {
+      const resourceGroup = "resourceGroup";
+      const config = (serverless.service as any as ServerlessAzureConfig);
+      config.provider.resourceGroup = "not the resource group";
+      expect(serverless.service.provider["resourceGroup"]).not.toEqual(resourceGroup);
+      const service = new ConfigService(serverless, { resourceGroup } as any);
+      expect(service.getResourceGroupName()).toEqual(resourceGroup);
+    });
+
+    it("use the prefix defined in SLS yml config", () => {
+      const expectedPrefix = "testPrefix"
+      serverless.service.provider["prefix"] = expectedPrefix;
+      const service = new ConfigService(serverless, { } as any);
+      expect(service.getPrefix()).toEqual(expectedPrefix);
+    });
+
+    it("use region from SLS yml config", () => {
+      const expectedRegion = "eastus2"
+      serverless.service.provider["region"] = expectedRegion;
+      const service = new ConfigService(serverless, { } as any);
+      expect(service.getRegion()).toEqual(expectedRegion);
+    });
+
+    it("use stage name from SLS yml config", () => {
+      const expectedStage = "testStage"
+      serverless.service.provider["stage"] = expectedStage;
+      const service = new ConfigService(serverless, { } as any);
+      expect(service.getStage()).toEqual(expectedStage);
+    });
+
+    it("use the resource group name from SLS yml config", () => {
+      const service = new ConfigService(serverless, { } as any);
+      expect(service.getResourceGroupName()).toEqual(serverless.service.provider["resourceGroup"]);
+    });
+
+    it("use location property as region if region not set", () => {
+      const slsService = MockFactory.createTestService();
+      delete slsService.provider.region;
+      const location = "East US";
+      slsService.provider["location"] = location;
+  
+      const sls = MockFactory.createTestServerless({
+        service: slsService
+      });
+  
+      const service = new ConfigService(sls, {} as any);
+      expect(service.getRegion()).toEqual(location);
+    });
+  
+    it("Generates resource group from convention when NOT defined in sls yaml", () => {
+      serverless.service.provider["resourceGroup"] = null;
+      const service = new ConfigService(serverless, { } as any);
+      const actualResourceGroupName = service.getResourceGroupName();
+      const expectedRegion = AzureNamingService.createShortAzureRegionName(service.getRegion());
+      const expectedStage = AzureNamingService.createShortStageName(service.getStage());
+      const expectedResourceGroupName = `sls-${expectedRegion}-${expectedStage}-${serverless.service["service"]}-rg`;
+      expect(actualResourceGroupName).toEqual(expectedResourceGroupName);
+    });
+
+    it("Gets deployment config from SLS yaml using external property", () => {
+      const deploymentConfig1: DeploymentConfig = {
+        external: true
+      }
+      serverless.service.provider["deployment"] = deploymentConfig1
+      const service1 = new ConfigService(serverless, { } as any);
+      expect(service1.getDeploymentConfig().external).toBe(true);
+
+      const deploymentConfig2: DeploymentConfig = {
+        external: false
+      }
+      serverless.service.provider["deployment"] = deploymentConfig2
+      const service2 = new ConfigService(serverless, { } as any);
+      expect(service2.getDeploymentConfig().external).toBe(false);
+    });
+
+    it("Gets deployment config from SLS yaml using runFromBlobUrl property", () => {
+      const deploymentConfig1 = {
+        runFromBlobUrl: true
+      }
+      serverless.service.provider["deployment"] = deploymentConfig1
+      const service1 = new ConfigService(serverless, { } as any);
+      expect(service1.getDeploymentConfig().external).toBe(true);
+
+      const deploymentConfig2 = {
+        runFromBlobUrl: false
+      }
+      serverless.service.provider["deployment"] = deploymentConfig2
+      const service2 = new ConfigService(serverless, { } as any);
+      expect(service2.getDeploymentConfig().external).toBe(false);
+    });
+  });
+  
+  describe("Service Principal Configuration", () => {
+    const cliSubscriptionId = "cli sub id";
+    const envVarSubscriptionId = "env var sub id";
+    const configSubscriptionId = "config sub id";
+    const loginResultSubscriptionId = "ABC123";
+  
+    it("use subscription ID from the CLI", () => {
+      process.env.AZURE_SUBSCRIPTION_ID = envVarSubscriptionId;
+      serverless.service.provider["subscriptionId"] = configSubscriptionId;
+      serverless.variables["subscriptionId"] = loginResultSubscriptionId
+      const service = new ConfigService(serverless, { subscriptionId: cliSubscriptionId } as any);
+      expect(service.getSubscriptionId()).toEqual(cliSubscriptionId);
+      expect(serverless.service.provider["subscriptionId"]).toEqual(cliSubscriptionId);
+    });
+  
+    it("use subscription ID from environment variable", () => {
+      process.env.AZURE_SUBSCRIPTION_ID = envVarSubscriptionId;
+      serverless.service.provider["subscriptionId"] = configSubscriptionId;
+      serverless.variables["subscriptionId"] = loginResultSubscriptionId
+      const service = new ConfigService(serverless, { } as any);
+      expect(service.getSubscriptionId()).toEqual(envVarSubscriptionId);
+      expect(serverless.service.provider["subscriptionId"]).toEqual(envVarSubscriptionId);
+    });
+  
+    it("use subscription ID from config", () => {
+      delete process.env.AZURE_SUBSCRIPTION_ID;
+      serverless.service.provider["subscriptionId"] = configSubscriptionId;
+      serverless.variables["subscriptionId"] = loginResultSubscriptionId
+      const service = new ConfigService(serverless, { } as any);
+      expect(service.getSubscriptionId()).toEqual(configSubscriptionId);
+      expect(serverless.service.provider["subscriptionId"]).toEqual(configSubscriptionId);
+    });
+  
+    it("use subscription ID from login result", () => {
+      delete process.env.AZURE_SUBSCRIPTION_ID;
+      serverless.variables["subscriptionId"] = loginResultSubscriptionId
+      const service = new ConfigService(serverless, { } as any);
+      expect(service.getSubscriptionId()).toEqual(loginResultSubscriptionId);
+      expect(serverless.service.provider["subscriptionId"]).toEqual(loginResultSubscriptionId);
+    });
+  });
+
+  describe("Runtime version", () => {
+    it("throws error when invalid nodejs version in defined", () => {
+      const sls = MockFactory.createTestServerless();
+      sls.service.provider.runtime = "nodejs10.6.1";
+      expect(() => new ConfigService(sls, {} as any)).toThrowError("Runtime nodejs10.6.1 is not supported");
+      expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toBeUndefined();
+    });
+  
+    it("throws error when incomplete nodejs version in defined", () => {
+      const sls = MockFactory.createTestServerless();
+      sls.service.provider.runtime = "nodejs8";
+      expect(() => new ConfigService(sls, {} as any)).toThrowError("Invalid runtime: nodejs8");
+      expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toBeUndefined();
+    });
+  
+    it("throws error when unsupported nodejs version in defined", () => {
+      const sls = MockFactory.createTestServerless();
+      sls.service.provider.runtime = "nodejs5.x";
+      expect(() => new ConfigService(sls, {} as any)).toThrowError("Runtime nodejs5.x is not supported");
+      expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toBeUndefined();
+    });
+  
+    it("Does not throw an error when valid nodejs version is defined", () => {
+      const sls = MockFactory.createTestServerless();
+      sls.service.provider.runtime = "nodejs10.x";
+      expect(() => new ConfigService(sls, {} as any)).not.toThrow();
+      const expectedRuntime: FunctionRuntime = {
+        language: SupportedRuntimeLanguage.NODE,
+        version: "10.15.2"
+      }
+      expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toEqual(expectedRuntime);
+    });
+  
+    it("Does not throw an error when nodejs version with major and minor is defined", () => {
+      const sls = MockFactory.createTestServerless();
+      sls.service.provider.runtime = "nodejs6.9.x";
+      expect(() => new ConfigService(sls, {} as any)).not.toThrow();
+      const expectedRuntime: FunctionRuntime = {
+        language: SupportedRuntimeLanguage.NODE,
+        version: "6.9.5"
+      }
+      expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toEqual(expectedRuntime);
+    });
+  
+    it("Does not throw an error when specific nodejs version is defined", () => {
+      const sls = MockFactory.createTestServerless();
+      sls.service.provider.runtime = "nodejs10.6.0";
+      expect(() => new ConfigService(sls, {} as any)).not.toThrow();
+      const expectedRuntime: FunctionRuntime = {
+        language: SupportedRuntimeLanguage.NODE,
+        version: "10.6.0"
+      }
+      expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toEqual(expectedRuntime);
+    });
+  
+    it("throws an error when no nodejs version is defined", () => {
+      const sls = MockFactory.createTestServerless();
+      sls.service.provider.runtime = undefined;
+      expect(() => new ConfigService(sls, {} as any)).toThrowError("Runtime version not specified in serverless.yml");
+      expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toBeUndefined();
+    });
+  
+    it("does not throw an error with python3.6", () => {
+      const sls = MockFactory.createTestServerless();
+      sls.service.provider.runtime = "python3.6";
+      expect(() => new ConfigService(sls, {} as any)).not.toThrow();
+      const expectedRuntime: FunctionRuntime = {
+        language: SupportedRuntimeLanguage.PYTHON,
+        version: "3.6"
+      }
+      expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toEqual(expectedRuntime);
+    });
+  })
+});
diff --git a/src/services/functionAppService.test.ts b/src/services/functionAppService.test.ts
index aa70c80..9fff364 100644
--- a/src/services/functionAppService.test.ts
+++ b/src/services/functionAppService.test.ts
@@ -17,15 +17,18 @@ jest.mock("./azureBlobStorageService");
 import { AzureBlobStorageService } from "./azureBlobStorageService"
 import configConstants from "../config";
 import { Utils } from "../shared/utils";
+import { ServerlessAzureConfig } from "../models/serverless";
 
 describe("Function App Service", () => {
   const app = MockFactory.createTestSite();
   const slsService = MockFactory.createTestService();
+  const config: ServerlessAzureConfig = slsService as any;
   const variables = MockFactory.createTestVariables();
   const provider = MockFactory.createTestAzureServiceProvider();
 
   const masterKey = "masterKey";
   const authKey = "authKey";
+  const subscriptionId = "subId";
   const syncTriggersMessage = "sync triggers success";
   const deleteFunctionMessage = "delete function success";
   const functions = MockFactory.createTestSlsFunctionConfig();
@@ -34,8 +37,13 @@ describe("Function App Service", () => {
   const baseUrl = "https://management.azure.com"
   const masterKeyUrl = `https://${app.defaultHostName}/admin/host/systemkeys/_master`;
   const authKeyUrl = `${baseUrl}${app.id}/functions/admin/token?api-version=2016-08-01`;
-  const syncTriggersUrl = `${baseUrl}${app.id}/syncfunctiontriggers?api-version=2016-08-01`;
+  const syncTriggersUrl = `${baseUrl}/subscriptions/${subscriptionId}` +
+    `/resourceGroups/${config.provider.resourceGroup}/providers/Microsoft.Web/sites/${app.name}` +
+    "/syncfunctiontriggers?api-version=2016-08-01";
   const listFunctionsUrl = `${baseUrl}${app.id}/functions?api-version=2016-08-01`;
+  const appSettingsUrl = `${baseUrl}/subscriptions/${subscriptionId}/resourceGroups/${config.provider.resourceGroup}` +
+    `/providers/Microsoft.Web/sites/${app.name}/config/appsettings?api-version=2016-08-01`;
+
 
   const appSettings = {
     setting1: "value1",
@@ -53,6 +61,8 @@ describe("Function App Service", () => {
     axiosMock.onGet(authKeyUrl).reply(200, authKey);
     // Sync Triggers
     axiosMock.onPost(syncTriggersUrl).reply(200, syncTriggersMessage);
+    // Update app settings
+    axiosMock.onPut(appSettingsUrl).reply(200, appSettings)
     // List Functions
     axiosMock.onGet(listFunctionsUrl).reply(200, { value: functionsResponse });
     // Delete Function
@@ -86,12 +96,16 @@ describe("Function App Service", () => {
   });
 
   function createService(sls?: Serverless, options?: Serverless.Options) {
+    options = options || MockFactory.createTestServerlessOptions();
     return new FunctionAppService(
       sls || MockFactory.createTestServerless({
         service: slsService,
         variables: variables,
       }),
-      options || MockFactory.createTestServerlessOptions()
+      {
+        ...options,
+        subscriptionId: subscriptionId
+      } as any
     )
   }
 
@@ -119,7 +133,6 @@ describe("Function App Service", () => {
     const service = createService();
     const result = await service.getMasterKey();
     expect(result).toEqual(masterKey);
-
   });
 
   it("deletes function", async () => {
@@ -131,7 +144,7 @@ describe("Function App Service", () => {
 
   it("syncs triggers", async () => {
     const service = createService();
-    const result = await service.syncTriggers(app);
+    const result = await service.syncTriggers(app, {});
     expect(result.data).toEqual(syncTriggersMessage)
   });
 
@@ -344,29 +357,45 @@ describe("Function App Service", () => {
   it("adds a new function app setting", async () => {
     const service = createService();
     const settingName = "TEST_SETTING";
-    const settingValue = "TEST_VALUE"
+    const settingValue = "TEST_VALUE";
+    const sendApiSpy = jest.spyOn(FunctionAppService.prototype as any, "sendApiRequest")
     await service.updateFunctionAppSetting(app, settingName, settingValue);
-    expect(WebSiteManagementClient.prototype.webApps.updateApplicationSettings).toBeCalledWith(
-      "myResourceGroup",
-      "Test",
+
+    const expectedAppSettings = {
+      ...appSettings,
+      TEST_SETTING: settingValue
+    }
+
+    expect(sendApiSpy).toBeCalledWith(
+      "PUT",
+      appSettingsUrl,
       {
-        ...appSettings,
-        TEST_SETTING: settingValue
+        data: {
+          properties: expectedAppSettings
+        }
       }
-    )
+    );
   });
 
   it("updates an existing function app setting", async () => {
     const service = createService();
     const settingName = "setting1";
     const settingValue = "TEST_VALUE"
+    const sendApiSpy = jest.spyOn(FunctionAppService.prototype as any, "sendApiRequest")
+
     await service.updateFunctionAppSetting(app, settingName, settingValue);
-    expect(WebSiteManagementClient.prototype.webApps.updateApplicationSettings).toBeCalledWith(
-      "myResourceGroup",
-      "Test",
+
+    const expectedAppSettings = {
+      setting1: settingValue,
+      setting2: appSettings.setting2
+    }
+    expect(sendApiSpy).toBeCalledWith(
+      "PUT",
+      appSettingsUrl,
       {
-        setting1: settingValue,
-        setting2: appSettings.setting2
+        data: {
+          properties: expectedAppSettings
+        }
       }
     );
   });
@@ -376,7 +405,11 @@ describe("Function App Service", () => {
     const sasUrl = "sasUrl"
 
     beforeEach(() => {
-      FunctionAppService.prototype.updateFunctionAppSetting = jest.fn();
+      FunctionAppService.prototype.updateFunctionAppSetting = jest.fn(() => {
+        return {
+          properties: appSettings
+        }
+      });
       AzureBlobStorageService.prototype.generateBlobSasTokenUrl = jest.fn(() => Promise.resolve(sasUrl));
     });
 
@@ -387,7 +420,7 @@ describe("Function App Service", () => {
     it("updates WEBSITE_RUN_FROM_PACKAGE with SAS URL if configured to run from blob", async () => {
       const newSlsService = MockFactory.createTestService();
       newSlsService.provider["deployment"] = {
-        runFromBlobUrl: true,
+        external: true,
       }
       const service = createService(MockFactory.createTestServerless({
         service: newSlsService,
@@ -404,7 +437,7 @@ describe("Function App Service", () => {
     it("does not upload directly to function app if configured to run from blob", async () => {
       const newSlsService = MockFactory.createTestService();
       newSlsService.provider["deployment"] = {
-        runFromBlobUrl: true,
+        external: true,
       }
       const service = createService(MockFactory.createTestServerless({
         service: newSlsService,
@@ -424,7 +457,7 @@ describe("Function App Service", () => {
     it("uploads directly to function app if not configured to run from blob", async () => {
       const newSlsService = MockFactory.createTestService();
       newSlsService.provider["deployment"] = {
-        runFromBlobUrl: false,
+        external: false,
       }
       const sls = MockFactory.createTestServerless({
         service: newSlsService,
diff --git a/src/services/invokeService.test.ts b/src/services/invokeService.test.ts
index ab97587..f4b6b8a 100644
--- a/src/services/invokeService.test.ts
+++ b/src/services/invokeService.test.ts
@@ -20,7 +20,7 @@ describe("Invoke Service ", () => {
   const functionName = "hello";
   const urlPOST = `http://${app.defaultHostName}/api/${functionName}`;
   const urlGET = `http://${app.defaultHostName}/api/${functionName}?name%3D${testData}`;
-  const localUrl = `http://localhost:${configConstants.defaultLocalPort}/api/${functionName}`
+  const localUrl = `http://localhost:${configConstants.defaults.localPort}/api/${functionName}`
   let masterKey: string;
   let sls = MockFactory.createTestServerless();
   let options = {
diff --git a/src/services/resourceService.test.ts b/src/services/resourceService.test.ts
index dffaa4e..9482e83 100644
--- a/src/services/resourceService.test.ts
+++ b/src/services/resourceService.test.ts
@@ -7,11 +7,14 @@ import { ResourceService } from "./resourceService";
 jest.mock("@azure/arm-resources")
 
 describe("Resource Service", () => {
-  let deployments: DeploymentsListByResourceGroupResponse;
+  
+  /**
+   * List of deployments in ASCENDING order (oldest first)
+   */
+  const deployments: DeploymentsListByResourceGroupResponse = MockFactory.createTestDeployments(5, true);
   const template = "myTemplate";
 
   beforeEach(() => {
-    deployments = MockFactory.createTestDeployments(5, true);
     ResourceManagementClient.prototype.resourceGroups = {
       createOrUpdate: jest.fn(),
       deleteMethod: jest.fn(),
@@ -19,7 +22,7 @@ describe("Resource Service", () => {
 
     ResourceManagementClient.prototype.deployments = {
       deleteMethod: jest.fn(),
-      listByResourceGroup: jest.fn(() => Promise.resolve(deployments)),
+      listByResourceGroup: jest.fn(() => Promise.resolve([...deployments])),
       exportTemplate: jest.fn(() => Promise.resolve(template)),
     } as any;
   });
@@ -90,7 +93,7 @@ describe("Resource Service", () => {
     const service = new ResourceService(sls, options);
     const deps = await service.getDeployments();
     // Make sure deps are in correct order
-    expect(deps).toEqual(deployments);
+    expect(deps).toEqual([...deployments].reverse());
   });
 
   it("lists deployments as string with timestamps", async () => {
@@ -102,10 +105,10 @@ describe("Resource Service", () => {
     const service = new ResourceService(sls, options);
     const deploymentString = await service.listDeployments();
     let expectedDeploymentString = "\n\nDeployments";
-    const originalTimestamp = +MockFactory.createTestTimestamp();
-    let i = 0
-    for (const dep of deployments) {
-      const timestamp = originalTimestamp + i
+    const originalTimestamp = +MockFactory.createTestTimestamp() + 4;
+    let i = 0;
+    for (const dep of [...deployments].reverse()) {
+      const timestamp = originalTimestamp - i;
       expectedDeploymentString += "\n-----------\n"
       expectedDeploymentString += `Name: ${dep.name}\n`
       expectedDeploymentString += `Timestamp: ${timestamp}\n`;
@@ -117,9 +120,9 @@ describe("Resource Service", () => {
   });
 
   it("lists deployments as string without timestamps", async () => {
-    deployments = MockFactory.createTestDeployments();
+    const deployments = MockFactory.createTestDeployments();
     ResourceManagementClient.prototype.deployments = {
-      listByResourceGroup: jest.fn(() => Promise.resolve(deployments)),
+      listByResourceGroup: jest.fn(() => Promise.resolve([...deployments])),
     } as any;
 
     const sls = MockFactory.createTestServerless();
@@ -130,7 +133,7 @@ describe("Resource Service", () => {
     const service = new ResourceService(sls, options);
     const deploymentString = await service.listDeployments();
     let expectedDeploymentString = "\n\nDeployments";
-    for (const dep of deployments) {
+    for (const dep of [...deployments].reverse()) {
       expectedDeploymentString += "\n-----------\n"
       expectedDeploymentString += `Name: ${dep.name}\n`
       expectedDeploymentString += "Timestamp: None\n";
@@ -156,4 +159,15 @@ describe("Resource Service", () => {
       );
     expect(result).toEqual(template);
   });
+
+  it("gets previous deployment template", async () => {
+    ResourceManagementClient.prototype.deployments = {
+      listByResourceGroup: jest.fn(() => Promise.resolve([...deployments])),
+    } as any;
+    const sls = MockFactory.createTestServerless();
+    const service = new ResourceService(sls, {} as any);
+    const deployment = await service.getPreviousDeployment();
+    const expected = deployments[deployments.length - 1];
+    expect(deployment).toEqual(expected);
+  });
 });
diff --git a/src/services/rollbackService.test.ts b/src/services/rollbackService.test.ts
index 6a77eda..d8b2482 100644
--- a/src/services/rollbackService.test.ts
+++ b/src/services/rollbackService.test.ts
@@ -117,7 +117,7 @@ describe("Rollback Service", () => {
   it("should deploy function app with SAS URL", async () => {
     const sls = MockFactory.createTestServerless();
     const deploymentConfig: DeploymentConfig = {
-      runFromBlobUrl: true
+      external: true
     }
     sls.service.provider["deployment"] = deploymentConfig;
     const service = createService(sls);
diff --git a/src/shared/utils.test.ts b/src/shared/utils.test.ts
index 517c60b..23754cc 100644
--- a/src/shared/utils.test.ts
+++ b/src/shared/utils.test.ts
@@ -2,24 +2,28 @@ import path from "path";
 import Serverless from "serverless";
 import { MockFactory } from "../test/mockFactory";
 import { FunctionMetadata, Utils } from "./utils";
+import { ConfigService } from "../services/configService";
 
 describe("utils", () => {
   let sls: Serverless;
 
   beforeEach(() => {
-    const slsConfig = {
-      service: "my test service",
-      provider: "azure",
-      functions: MockFactory.createTestSlsFunctionConfig(),
-    };
-
     sls = MockFactory.createTestServerless();
-    Object.assign(sls.service, slsConfig);
+    const configService = new ConfigService(sls, {} as any);
+    Object.assign(sls.service, configService.getConfig());
   });
 
   it("resolves handler when handler code is outside function folders", () => {
-    sls.service["functions"].hello.handler = "src/handlers/hello.handler";
-    MockFactory.updateService(sls);
+    const handler = "src/handlers/hello.handler";
+    const slsFunctions = sls.service["functions"];
+    MockFactory.updateService(sls, {
+      ...slsFunctions,
+      hello: {
+        ...slsFunctions.hello,
+        handler
+      }
+    });
+    expect(sls.service["functions"].hello.handler).toEqual(handler);
 
     const functions = sls.service.getAllFunctions();
     const metadata = Utils.getFunctionMetaData(functions[0], sls);
@@ -34,8 +38,15 @@ describe("utils", () => {
   });
 
   it("resolves handler when code is in function folder", () => {
-    sls.service["functions"].hello.handler = "hello/index.handler";
-    MockFactory.updateService(sls);
+    const handler = "hello/index.handler";
+    const slsFunctions = sls.service["functions"];
+    MockFactory.updateService(sls, {
+      ...slsFunctions,
+      hello: {
+        ...slsFunctions.hello,
+        handler
+      }
+    });
 
     const functions = sls.service.getAllFunctions();
     const metadata = Utils.getFunctionMetaData(functions[0], sls);
@@ -50,8 +61,15 @@ describe("utils", () => {
   });
 
   it("resolves handler when code is at the project root", () => {
-    sls.service["functions"].hello.handler = "hello.handler";
-    MockFactory.updateService(sls);
+    const handler = "hello.handler";
+    const slsFunctions = sls.service["functions"];
+    MockFactory.updateService(sls, {
+      ...slsFunctions,
+      hello: {
+        ...slsFunctions.hello,
+        handler
+      }
+    });
 
     const functions = sls.service.getAllFunctions();
     const metadata = Utils.getFunctionMetaData(functions[0], sls);
diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts
index 55e3321..298f9a8 100644
--- a/src/test/mockFactory.ts
+++ b/src/test/mockFactory.ts
@@ -67,8 +67,14 @@ export class MockFactory {
     } as any as Service;
   }
 
-  public static updateService(sls: Serverless) {
-    sls.service = MockFactory.createTestService(sls.service["functions"]);
+  public static updateService(serverless: Serverless, functions) {
+    Object.assign(serverless.service, {
+      ...serverless.service,
+      getAllFunctions: jest.fn(() => Object.keys(functions)),
+      getFunction: jest.fn((name: string) => functions[name]),
+      getAllFunctionsNames: jest.fn(() => Object.keys(functions)),
+      functions
+    });
   }
 
   public static createTestServerlessOptions(options?: any): Serverless.Options {