Repository: serverless/serverless-azure-functions
Pull request #310 (2019-09-05 20:44:03)

fix: add resource group name hash to resource names
fix: add resource group name hash to resource names

Azure has naming requirements for resources, and for some, requires the names to be unique across azure. Currently on deployments, the plugin doesn't add a more unique hash to the name, so it is possible collisions can occur. By adding the resource group name hash to the resource name, we can avoid this.

AB#1002


Patch:
diff --git a/README.md b/README.md
index 1e359c2..19a2994 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 This plugin enables Azure Functions support within the Serverless Framework.
 
-[![Coverage Status](https://coveralls.io/repos/github/serverless/serverless-azure-functions/badge.svg)](https://coveralls.io/github/serverless/serverless-azure-functions)
+[![Code Coverage](https://codecov.io/gh/serverless/serverless-azure-functions/branch/dev/graph/badge.svg)](https://codecov.io/gh/serverless/serverless-azure-functions)
 
 ## Quickstart
 
@@ -186,21 +186,22 @@ The getting started walkthrough illustrates the interactive login experience, wh
 
     **Bash**
     ```bash
-    $ export azureSubId='<subscriptionId>'
-    $ export azureServicePrincipalTenantId='<tenantId>'
-    $ export azureServicePrincipalClientId='<servicePrincipalId>'
-    $ export azureServicePrincipalPassword='<password>'
+    $ export AZURE_SUBSCRIPTION_ID='<subscriptionId>'
+    $ export AZURE_TENANT_ID='<tenantId>'
+    $ export AZURE_CLIENT_ID='<servicePrincipalId>'
+    $ export AZURE_CLIENT_SECRET='<password>'
     ```
 
     **Powershell**
     ```powershell
-    $env:azureSubId='<subscriptionId>'
-    $env:azureServicePrincipalTenantId='<tenantId>'
-    $env:azureServicePrincipalClientId='<servicePrincipalName>'
-    $env:azureServicePrincipalPassword='<password>'
+    $env:AZURE_SUBSCRIPTION_ID='<subscriptionId>'
+    $env:AZURE_TENANT_ID='<tenantId>'
+    $env:AZURE_CLIENT_ID='<servicePrincipalName>'
+    $env:AZURE_CLIENT_SECRET='<password>'
     ```
 
 ### Example Usage
+- **[Visit our sample repos](docs/examples/samples.md) for full projects with different use cases**
 - [Configuring API Management](docs/examples/apim.md) that sits in front of function app
 
 ### Contributing
diff --git a/docs/examples/apim.md b/docs/examples/apim.md
index 7faf378..ab6c57b 100644
--- a/docs/examples/apim.md
+++ b/docs/examples/apim.md
@@ -2,11 +2,44 @@
 
 [API Management](https://azure.microsoft.com/en-us/services/api-management/) is an Azure Service for publishing, managing, securing and monitoring APIs. It can be deployed along with your Serverless function app by specifying its configuration in `serverless.yml`. Here is a basic example of how to configure API Mangement:
 
-## Simple Handler
+## Simple Configuration
+Simply setting `apim: true` in your configuration will automatically deploy a consumption based APIM resource to Azure.  By default it will create a API with path of `/api` in your APIM instance and will map all operations defined in the serverless yaml to your function app.
 
 ```yaml
 service: greeter
 
+provider:
+  prefix: greeter
+  name: azure
+  # Default to West US, allow for command line arg --region to override
+  region: ${opt:region, 'westus'}
+  # Default to dev, allow for command line arg -- stage to override
+  stage: ${opt:stage, 'dev'}
+  # Azure subscription ID for deployment
+  subscriptionId: 00000000-0000-0000-0000-000000000000
+
+  # Start of your API Management configuration
+  apim: true
+
+plugins:
+  - serverless-azure-functions
+
+functions:
+  hello:
+    handler: src/handlers/hello.handler
+    events:
+      - http: true
+        x-azure-settings:
+          methods:
+            - GET
+          authLevel : function
+```
+
+## Full Configuration
+In this example you can see the configuration support is quite verbose.  You have the ability to create multiple APIs and Backends as well as associate an operation to a specific api/backend.  If the operation is not specifically defined it will default to the first API / Backend that has been defined.
+```yaml
+service: greeter
+
 provider:
   prefix: greeter
   name: azure
@@ -20,25 +53,48 @@ provider:
   # Start of your API Management configuration
   apim:
     # API specifications
-    api:
-      # Name of the API
-      name: v1
-      subscriptionRequired: false
-      # Display name
-      displayName: v1
-      # Description of API
-      description: V1 sample app APIs
-      # HTTP protocols allowed
-      protocols:
-        - https
-      # Base path of API calls
-      path: v1
-      # Tags for ARM resource
-      tags:
-        - tag1
-        - tag2
-      # No authorization
-      authorization: none
+    apis:
+        # Name of the API
+      - name: products-api
+        subscriptionRequired: false
+        # Display name
+        displayName: Products API
+        # Description of API
+        description: The Products REST API
+        # HTTP protocols allowed
+        protocols:
+          - https
+        # Base path of API calls
+        path: products
+        # Tags for ARM resource
+        tags:
+          - tag1
+          - tag2
+        # No authorization
+        authorization: none
+        # Name of the API
+      - name: categories-api
+        subscriptionRequired: false
+        # Display name
+        displayName: Categories API
+        # Description of API
+        description: The Categories REST API
+        # HTTP protocols allowed
+        protocols:
+          - https
+        # Base path of API calls
+        path: categories
+        # Tags for ARM resource
+        tags:
+          - tag1
+          - tag2
+        # No authorization
+        authorization: none
+    backends:
+      - name: products-backend
+        url: api/products
+      - name: categories-backend
+        url: api/categories
     # CORS Settings for APIM
     cors:
       allowCredentials: false
@@ -59,26 +115,48 @@ plugins:
   - serverless-azure-functions
 
 functions:
-  hello:
-    handler: src/handlers/hello.handler
+  getProducts:
+    handler: src/handlers/getProducts.handler
 
     # API Management configuration for `hello` handler
     apim:
+      # The API to attach this operation
+      api: products-api
+      # The Backend use for the operation
+      backend: products-backend
       operations:
-        # GET operation for `hello` handler
+        # GET operation for `getProducts` handler
         - method: get
           # URL path for accessing handler
-          urlTemplate: /hello
+          urlTemplate: /
           # Display name inside Azure Portal
-          displayName: Hello
+          displayName: GetProducts
     events:
       - http: true
         x-azure-settings:
           methods:
             - GET
           authLevel : function
+  getCategories:
+    handler: src/handlers/getCategories.handler
+
+    # API Management configuration for `getCategories` handler
+    apim:
+      # The API to attach this operation
+      api: categories-api
+      # The Backend use for the operation
+      backend: categories-backend
+      operations:
+        # GET operation for `getCategories` handler
+        - method: get
+          # URL path for accessing handler
+          urlTemplate: /
+          # Display name inside Azure Portal
+          displayName: GetCategories
+    events:
       - http: true
         x-azure-settings:
-          direction: out
-          name: res
+          methods:
+            - GET
+          authLevel : function
 ```
diff --git a/docs/examples/samples.md b/docs/examples/samples.md
new file mode 100644
index 0000000..3ec4b2c
--- /dev/null
+++ b/docs/examples/samples.md
@@ -0,0 +1,5 @@
+# Sample Repos
+
+Please visit any one of our sample repositories, or open a pull request with a link to your own sample!
+- [Simple REST API with API Management](https://github.com/tbarlow12/sls-az-func-rest-api)
+- [Serverless Multi-Cloud Demo](https://github.com/wbreza/multicloud-demo)
diff --git a/package-lock.json b/package-lock.json
index efa405c..1a0de53 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "serverless-azure-functions",
-  "version": "1.0.0-36",
+  "version": "1.0.0-39",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -1832,6 +1832,7 @@
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
       "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
+      "optional": true,
       "requires": {
         "kind-of": "^3.0.2",
         "longest": "^1.0.1",
@@ -2004,6 +2005,7 @@
       "version": "4.10.1",
       "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
       "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
+      "optional": true,
       "requires": {
         "bn.js": "^4.0.0",
         "inherits": "^2.0.1",
@@ -2850,7 +2852,8 @@
     "bn.js": {
       "version": "4.11.8",
       "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
-      "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
+      "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+      "optional": true
     },
     "body-parser": {
       "version": "1.19.0",
@@ -2955,7 +2958,8 @@
     "brorand": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
-      "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
+      "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+      "optional": true
     },
     "browser-process-hrtime": {
       "version": "0.1.3",
@@ -2984,6 +2988,7 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
       "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+      "optional": true,
       "requires": {
         "buffer-xor": "^1.0.3",
         "cipher-base": "^1.0.0",
@@ -3020,6 +3025,7 @@
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
       "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+      "optional": true,
       "requires": {
         "bn.js": "^4.1.0",
         "randombytes": "^2.0.1"
@@ -3116,7 +3122,8 @@
     "buffer-xor": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
-      "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk="
+      "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+      "optional": true
     },
     "builtin-modules": {
       "version": "1.1.1",
@@ -3303,6 +3310,7 @@
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
       "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+      "optional": true,
       "requires": {
         "inherits": "^2.0.1",
         "safe-buffer": "^5.0.1"
@@ -3666,6 +3674,7 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
       "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+      "optional": true,
       "requires": {
         "cipher-base": "^1.0.1",
         "inherits": "^2.0.1",
@@ -3678,6 +3687,7 @@
       "version": "1.1.7",
       "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
       "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+      "optional": true,
       "requires": {
         "cipher-base": "^1.0.3",
         "create-hash": "^1.1.0",
@@ -4140,6 +4150,7 @@
       "version": "6.4.1",
       "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz",
       "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==",
+      "optional": true,
       "requires": {
         "bn.js": "^4.4.0",
         "brorand": "^1.0.1",
@@ -4201,6 +4212,7 @@
       "version": "0.1.7",
       "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
       "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
+      "optional": true,
       "requires": {
         "prr": "~1.0.1"
       }
@@ -4586,6 +4598,7 @@
       "version": "0.3.5",
       "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
       "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
+      "optional": true,
       "requires": {
         "d": "1",
         "es5-ext": "~0.10.14"
@@ -4600,6 +4613,7 @@
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
       "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+      "optional": true,
       "requires": {
         "md5.js": "^1.3.4",
         "safe-buffer": "^5.1.1"
@@ -5148,11 +5162,13 @@
         },
         "balanced-match": {
           "version": "1.0.0",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
+          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -5170,11 +5186,13 @@
         },
         "concat-map": {
           "version": "0.0.1",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "console-control-strings": {
           "version": "1.1.0",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -5277,7 +5295,8 @@
         },
         "inherits": {
           "version": "2.0.3",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "ini": {
           "version": "1.3.5",
@@ -5300,17 +5319,20 @@
         "minimatch": {
           "version": "3.0.4",
           "bundled": true,
+          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
         },
         "minimist": {
           "version": "0.0.8",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "minipass": {
           "version": "2.3.5",
           "bundled": true,
+          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.2",
             "yallist": "^3.0.0"
@@ -5327,6 +5349,7 @@
         "mkdirp": {
           "version": "0.5.1",
           "bundled": true,
+          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -5410,6 +5433,7 @@
         "once": {
           "version": "1.4.0",
           "bundled": true,
+          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -5485,7 +5509,8 @@
         },
         "safe-buffer": {
           "version": "5.1.2",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -5515,6 +5540,7 @@
         "string-width": {
           "version": "1.0.2",
           "bundled": true,
+          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -5571,11 +5597,13 @@
         },
         "wrappy": {
           "version": "1.0.2",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "yallist": {
           "version": "3.0.3",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         }
       }
     },
@@ -5877,6 +5905,7 @@
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
       "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
+      "optional": true,
       "requires": {
         "inherits": "^2.0.1",
         "safe-buffer": "^5.0.1"
@@ -5886,6 +5915,7 @@
       "version": "1.1.7",
       "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
       "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+      "optional": true,
       "requires": {
         "inherits": "^2.0.3",
         "minimalistic-assert": "^1.0.1"
@@ -5895,6 +5925,7 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
       "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+      "optional": true,
       "requires": {
         "hash.js": "^1.0.3",
         "minimalistic-assert": "^1.0.0",
@@ -6213,7 +6244,8 @@
     "is-extglob": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
+      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+      "optional": true
     },
     "is-fullwidth-code-point": {
       "version": "1.0.0",
@@ -7879,7 +7911,8 @@
     "longest": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
-      "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc="
+      "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
+      "optional": true
     },
     "loose-envify": {
       "version": "1.4.0",
@@ -7986,6 +8019,7 @@
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
       "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
+      "optional": true,
       "requires": {
         "hash-base": "^3.0.0",
         "inherits": "^2.0.1",
@@ -8011,6 +8045,7 @@
       "version": "0.4.1",
       "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
       "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
+      "optional": true,
       "requires": {
         "errno": "^0.1.3",
         "readable-stream": "^2.0.1"
@@ -8101,12 +8136,14 @@
     "minimalistic-assert": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
-      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
+      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+      "optional": true
     },
     "minimalistic-crypto-utils": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
-      "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
+      "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+      "optional": true
     },
     "minimatch": {
       "version": "3.0.4",
@@ -8744,6 +8781,7 @@
       "version": "5.1.4",
       "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz",
       "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==",
+      "optional": true,
       "requires": {
         "asn1.js": "^4.0.0",
         "browserify-aes": "^1.0.0",
@@ -8852,6 +8890,7 @@
       "version": "3.0.17",
       "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
       "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
+      "optional": true,
       "requires": {
         "create-hash": "^1.1.2",
         "create-hmac": "^1.1.4",
@@ -9054,7 +9093,8 @@
     "prr": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
-      "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY="
+      "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
+      "optional": true
     },
     "pseudomap": {
       "version": "1.0.2",
@@ -9121,6 +9161,7 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
       "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+      "optional": true,
       "requires": {
         "safe-buffer": "^5.1.0"
       }
@@ -9533,6 +9574,7 @@
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
       "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+      "optional": true,
       "requires": {
         "hash-base": "^3.0.0",
         "inherits": "^2.0.1"
@@ -9963,6 +10005,7 @@
       "version": "2.4.11",
       "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
       "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+      "optional": true,
       "requires": {
         "inherits": "^2.0.1",
         "safe-buffer": "^5.0.1"
@@ -10158,7 +10201,8 @@
     "source-list-map": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
-      "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw=="
+      "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==",
+      "optional": true
     },
     "source-map": {
       "version": "0.7.3",
@@ -10715,7 +10759,8 @@
     "tapable": {
       "version": "0.2.9",
       "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz",
-      "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A=="
+      "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==",
+      "optional": true
     },
     "tar-stream": {
       "version": "1.6.2",
@@ -11571,6 +11616,7 @@
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz",
       "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==",
+      "optional": true,
       "requires": {
         "source-list-map": "^2.0.0",
         "source-map": "~0.6.1"
@@ -11579,7 +11625,8 @@
         "source-map": {
           "version": "0.6.1",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "optional": true
         }
       }
     },
diff --git a/package.json b/package.json
index 9d6c4d0..ce34154 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "serverless-azure-functions",
-  "version": "1.0.0-36",
+  "version": "1.0.0-39",
   "description": "Provider plugin for the Serverless Framework v1.x which adds support for Azure Functions.",
   "license": "MIT",
   "main": "./lib/index.js",
diff --git a/scripts/generate-service-principal.sh b/scripts/generate-service-principal.sh
index 7df6adf..e8c67ff 100755
--- a/scripts/generate-service-principal.sh
+++ b/scripts/generate-service-principal.sh
@@ -2,7 +2,7 @@
 
 ###
 # This script generate a service principal using your currently set subscription
-#   then write out the credentials of your new sp in a file 
+#   then write out the credentials of your new sp in a file
 #   that you can export as environment variables
 ###
 set -e
@@ -29,10 +29,10 @@ FILE=".env.servicePrincipal"
 
 echo "--> Writing creds to '${FILE}'"
 cat <<EOF >${FILE}
-export azureSubId=$(az account show | jq .id)
-export azureServicePrincipalTenantId=$(echo "${SP_RESPONSE}" | jq .tenant)
-export azureServicePrincipalClientId=$(echo "${SP_RESPONSE}" | jq .name)
-export azureServicePrincipalPassword=$(echo "${SP_RESPONSE}" | jq .password)
+export AZURE_SUBSCRIPTION_ID=$(az account show | jq .id)
+export AZURE_TENANT_ID=$(echo "${SP_RESPONSE}" | jq .tenant)
+export AZURE_CLIENT_ID=$(echo "${SP_RESPONSE}" | jq .name)
+export AZURE_CLIENT_SECRET=$(echo "${SP_RESPONSE}" | jq .password)
 EOF
 
 echo "Run \"source ${FILE}\" to set your Azure account credentials "
diff --git a/src/armTemplates/resources/apim.ts b/src/armTemplates/resources/apim.ts
index 2bd6652..dbc37cf 100644
--- a/src/armTemplates/resources/apim.ts
+++ b/src/armTemplates/resources/apim.ts
@@ -1,11 +1,16 @@
 import { ApiManagementConfig } from "../../models/apiManagement";
 import { ArmResourceTemplate, ArmResourceTemplateGenerator } from "../../models/armTemplates";
 import { ServerlessAzureConfig } from "../../models/serverless";
-import { AzureNamingService } from "../../services/namingService";
+import { AzureNamingService, AzureNamingServiceOptions } from "../../services/namingService";
 
 export class ApimResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
-    return AzureNamingService.getResourceName(config, config.provider.apim, "apim");
+    const options: AzureNamingServiceOptions = {
+      config,
+      resourceConfig: config.provider.apim,
+      suffix: "apim",
+    }
+    return AzureNamingService.getResourceName(options);
   }
 
   public getTemplate(): ArmResourceTemplate {
diff --git a/src/armTemplates/resources/appInsights.ts b/src/armTemplates/resources/appInsights.ts
index ab72cc9..cdb450f 100644
--- a/src/armTemplates/resources/appInsights.ts
+++ b/src/armTemplates/resources/appInsights.ts
@@ -1,10 +1,15 @@
 import { ArmResourceTemplate, ArmResourceTemplateGenerator } from "../../models/armTemplates";
 import { ServerlessAzureConfig } from "../../models/serverless";
-import { AzureNamingService } from "../../services/namingService";
+import { AzureNamingService, AzureNamingServiceOptions } from "../../services/namingService";
 
 export class AppInsightsResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
-    return AzureNamingService.getResourceName(config, config.provider.appInsights, "appinsights");
+    const options: AzureNamingServiceOptions = {
+      config,
+      resourceConfig: config.provider.appInsights,
+      suffix: "appinsights",
+    }
+    return AzureNamingService.getResourceName(options);
   }
 
   public getTemplate(): ArmResourceTemplate {
diff --git a/src/armTemplates/resources/appServicePlan.ts b/src/armTemplates/resources/appServicePlan.ts
index d38531e..c452fb8 100644
--- a/src/armTemplates/resources/appServicePlan.ts
+++ b/src/armTemplates/resources/appServicePlan.ts
@@ -1,10 +1,15 @@
 import { ArmResourceTemplate, ArmResourceTemplateGenerator } from "../../models/armTemplates";
 import { ResourceConfig, ServerlessAzureConfig } from "../../models/serverless";
-import { AzureNamingService } from "../../services/namingService";
+import { AzureNamingService, AzureNamingServiceOptions } from "../../services/namingService";
 
 export class AppServicePlanResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
-    return AzureNamingService.getResourceName(config, config.provider.appServicePlan, "asp");
+    const options: AzureNamingServiceOptions = {
+      config,
+      resourceConfig: config.provider.appServicePlan,
+      suffix: "asp",
+    }
+    return AzureNamingService.getResourceName(options);
   }
 
   public getTemplate(): ArmResourceTemplate {
diff --git a/src/armTemplates/resources/functionApp.ts b/src/armTemplates/resources/functionApp.ts
index fb022dd..a05b211 100644
--- a/src/armTemplates/resources/functionApp.ts
+++ b/src/armTemplates/resources/functionApp.ts
@@ -1,6 +1,6 @@
 import { ArmResourceTemplate, ArmResourceTemplateGenerator } from "../../models/armTemplates";
 import { FunctionAppConfig, ServerlessAzureConfig } from "../../models/serverless";
-import { AzureNamingService } from "../../services/namingService";
+import { AzureNamingService, AzureNamingServiceOptions } from "../../services/namingService";
 
 //Runtime versions found at " https://<sitename>.scm.azurewebsites.net/api/diagnostics/runtime".
 import runtimeVersionsJson from "../../services/runtimeVersions.json";
@@ -9,8 +9,14 @@ import semver from "semver";
 export class FunctionAppResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
     const safeServiceName = config.service.replace(/\s/g, "-");
+    const options: AzureNamingServiceOptions = {
+      config,
+      resourceConfig: config.provider.functionApp,
+      suffix: safeServiceName,
+      includeHash: false,
+    }
 
-    return AzureNamingService.getResourceName(config, config.provider.functionApp, safeServiceName);
+    return AzureNamingService.getResourceName(options);
   }
 
   public getTemplate(): ArmResourceTemplate {
diff --git a/src/armTemplates/resources/hostingEnvironment.ts b/src/armTemplates/resources/hostingEnvironment.ts
index 0cf8671..87caa16 100644
--- a/src/armTemplates/resources/hostingEnvironment.ts
+++ b/src/armTemplates/resources/hostingEnvironment.ts
@@ -1,10 +1,15 @@
 import { ArmResourceTemplate, ArmResourceTemplateGenerator } from "../../models/armTemplates";
 import { ServerlessAzureConfig } from "../../models/serverless";
-import { AzureNamingService } from "../../services/namingService";
+import { AzureNamingService, AzureNamingServiceOptions } from "../../services/namingService";
 
 export class HostingEnvironmentResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
-    return AzureNamingService.getResourceName(config, config.provider.hostingEnvironment, "ase");
+    const options: AzureNamingServiceOptions = {
+      config,
+      resourceConfig: config.provider.hostingEnvironment,
+      suffix: "ase",
+    }
+    return AzureNamingService.getResourceName(options);
   }
 
   public getTemplate(): ArmResourceTemplate {
diff --git a/src/armTemplates/resources/storageAccount.ts b/src/armTemplates/resources/storageAccount.ts
index 238eae7..dc1a768 100644
--- a/src/armTemplates/resources/storageAccount.ts
+++ b/src/armTemplates/resources/storageAccount.ts
@@ -1,71 +1,80 @@
-import { ArmResourceTemplate, ArmResourceTemplateGenerator } from "../../models/armTemplates";
+import {
+  ArmResourceTemplate,
+  ArmResourceTemplateGenerator
+} from "../../models/armTemplates";
 import { ResourceConfig, ServerlessAzureConfig } from "../../models/serverless";
-import { AzureNamingService } from "../../services/namingService";
+import {
+  AzureNamingService,
+  AzureNamingServiceOptions
+} from "../../services/namingService";
 import configConstants from "../../config";
 
 export class StorageAccountResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
-    return AzureNamingService.getSafeResourceName(
+    const options: AzureNamingServiceOptions = {
       config,
-      configConstants.naming.maxLength.storageAccount,
-      config.provider.storageAccount,
-      "",
-      true
-    );
+      resourceConfig: config.provider.storageAccount,
+      suffix: ""
+    };
+    return AzureNamingService.getSafeResourceName({
+      ...options,
+      maxLength: configConstants.naming.maxLength.storageAccount
+    });
   }
 
   public getTemplate(): ArmResourceTemplate {
     return {
-      "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
-      "contentVersion": "1.0.0.0",
-      "parameters": {
-        "storageAccountName": {
-          "defaultValue": "",
-          "type": "String"
+      $schema:
+        "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+      contentVersion: "1.0.0.0",
+      parameters: {
+        storageAccountName: {
+          defaultValue: "",
+          type: "String"
         },
-        "location": {
-          "defaultValue": "",
-          "type": "String"
+        location: {
+          defaultValue: "",
+          type: "String"
         },
-        "storageAccountSkuName": {
-          "defaultValue": "Standard_LRS",
-          "type": "String"
+        storageAccountSkuName: {
+          defaultValue: "Standard_LRS",
+          type: "String"
         },
-        "storageAccoutSkuTier": {
-          "defaultValue": "Standard",
-          "type": "String"
+        storageAccoutSkuTier: {
+          defaultValue: "Standard",
+          type: "String"
         }
       },
-      "variables": {},
-      "resources": [
+      variables: {},
+      resources: [
         {
-          "apiVersion": "2018-07-01",
-          "name": "[parameters('storageAccountName')]",
-          "type": "Microsoft.Storage/storageAccounts",
-          "location": "[parameters('location')]",
-          "kind": "Storage",
-          "properties": {
-            "accountType": "[parameters('storageAccountSkuName')]"
+          apiVersion: "2018-07-01",
+          name: "[parameters('storageAccountName')]",
+          type: "Microsoft.Storage/storageAccounts",
+          location: "[parameters('location')]",
+          kind: "Storage",
+          properties: {
+            accountType: "[parameters('storageAccountSkuName')]"
           },
-          "sku": {
-            "name": "[parameters('storageAccountSkuName')]",
-            "tier": "[parameters('storageAccoutSkuTier')]"
+          sku: {
+            name: "[parameters('storageAccountSkuName')]",
+            tier: "[parameters('storageAccoutSkuTier')]"
           }
         }
       ]
-    }
+    };
   }
 
   public getParameters(config: ServerlessAzureConfig): any {
     const resourceConfig: ResourceConfig = {
       sku: {},
-      ...config.provider.storageAccount,
+      ...config.provider.storageAccount
     };
 
     return {
       storageAccountName: StorageAccountResource.getResourceName(config),
       storageAccountSkuName: resourceConfig.sku.name,
-      storageAccoutSkuTier: resourceConfig.sku.tier,
+      storageAccoutSkuTier: resourceConfig.sku.tier
     };
   }
 }
diff --git a/src/armTemplates/resources/virtualNetwork.ts b/src/armTemplates/resources/virtualNetwork.ts
index 454c500..dd3202b 100644
--- a/src/armTemplates/resources/virtualNetwork.ts
+++ b/src/armTemplates/resources/virtualNetwork.ts
@@ -1,14 +1,15 @@
 import { ArmResourceTemplate, ArmResourceTemplateGenerator } from "../../models/armTemplates";
 import { ServerlessAzureConfig } from "../../models/serverless";
-import { AzureNamingService } from "../../services/namingService";
+import { AzureNamingService, AzureNamingServiceOptions } from "../../services/namingService";
 
 export class VirtualNetworkResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
-    return AzureNamingService.getResourceName(
+    const options: AzureNamingServiceOptions = {
       config,
-      config.provider.hostingEnvironment,
-      "vnet"
-    );
+      resourceConfig: config.provider.hostingEnvironment,
+      suffix: "vnet",
+    }
+    return AzureNamingService.getResourceName(options);
   }
 
   public getTemplate(): ArmResourceTemplate {
diff --git a/src/index.ts b/src/index.ts
index f628de4..5946338 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -11,14 +11,12 @@ import { AzureRemovePlugin } from "./plugins/remove/azureRemovePlugin";
 import { AzurePackagePlugin } from "./plugins/package/azurePackagePlugin";
 import { AzureDeployPlugin } from "./plugins/deploy/azureDeployPlugin";
 import { AzureLoginPlugin } from "./plugins/login/azureLoginPlugin";
-import { AzureApimServicePlugin } from "./plugins/apim/azureApimServicePlugin";
-import { AzureApimFunctionPlugin } from "./plugins/apim/azureApimFunctionPlugin";
+import { AzureApimPlugin } from "./plugins/apim/azureApimPlugin";
 import { AzureFuncPlugin } from "./plugins/func/azureFuncPlugin";
 import { AzureOfflinePlugin } from "./plugins/offline/azureOfflinePlugin"
 import { AzureRollbackPlugin } from "./plugins/rollback/azureRollbackPlugin"
 import { AzureKeyVaultPlugin } from "./plugins/identity/azureKeyVaultPlugin"
 
-
 export default class AzureIndex {
   public constructor(private serverless: Serverless, private options) {
     this.serverless.setProvider(AzureProvider.getProviderName(), new AzureProvider(serverless) as any);
@@ -30,8 +28,7 @@ export default class AzureIndex {
     // Refactored
     this.serverless.pluginManager.addPlugin(AzureLoginPlugin);
     this.serverless.pluginManager.addPlugin(AzureDeployPlugin);
-    this.serverless.pluginManager.addPlugin(AzureApimServicePlugin);
-    this.serverless.pluginManager.addPlugin(AzureApimFunctionPlugin);
+    this.serverless.pluginManager.addPlugin(AzureApimPlugin);
     this.serverless.pluginManager.addPlugin(AzureFuncPlugin);
     this.serverless.pluginManager.addPlugin(AzureOfflinePlugin);
     this.serverless.pluginManager.addPlugin(AzureRollbackPlugin);
diff --git a/src/models/apiManagement.ts b/src/models/apiManagement.ts
index 3fc656d..d11270a 100644
--- a/src/models/apiManagement.ts
+++ b/src/models/apiManagement.ts
@@ -1,4 +1,4 @@
-import { OperationContract, ApiContract, BackendContract } from "@azure/arm-apimanagement/esm/models";
+import { ApiContract, BackendContract } from "@azure/arm-apimanagement/esm/models";
 
 /**
  * Defines the serverless APIM configuration
@@ -7,9 +7,9 @@ export interface ApiManagementConfig {
   /** The name of the APIM azure resource */
   name: string;
   /** The API contract configuration */
-  api: ApiContract;
+  apis: ApiContract[];
   /** The API's backend contract configuration */
-  backend?: BackendContract;
+  backends?: BackendContract[];
   /** The API's CORS policy */
   cors?: ApiCorsPolicy;
   sku?: {
@@ -20,16 +20,6 @@ export interface ApiManagementConfig {
   publisherName?: string;
 }
 
-/**
- * Defines the APIM API Operation configuration
- */
-export interface ApiOperationOptions {
-  /** The name of the serverless function */
-  function: string;
-  /** The APIM operation contract configuration */
-  operation: OperationContract;
-}
-
 /**
  * Defines an APIM API CORS (cross origin resource sharing) policy
  */
@@ -44,4 +34,4 @@ export interface ApiCorsPolicy {
   allowedHeaders: string[];
   /** A list of headers exposed during OPTION preflight requests */
   exposeHeaders: string[];
-}
\ No newline at end of file
+}
diff --git a/src/models/azureProvider.ts b/src/models/azureProvider.ts
index 33c0e64..30372f4 100644
--- a/src/models/azureProvider.ts
+++ b/src/models/azureProvider.ts
@@ -8,10 +8,10 @@ export interface FunctionEvent {
 }
 
 export interface ServicePrincipalEnvVariables {
-  azureSubId: string;
-  azureServicePrincipalClientId: string;
-  azureServicePrincipalPassword: string;
-  azureServicePrincipalTenantId: string;
+  AZURE_SUBSCRIPTION_ID: string;
+  AZURE_CLIENT_ID: string;
+  AZURE_CLIENT_SECRET: string;
+  AZURE_TENANT_ID: string;
 }
 
 export interface FunctionMetadata {
diff --git a/src/plugins/apim/azureApimFunctionPlugin.ts b/src/plugins/apim/azureApimFunctionPlugin.ts
deleted file mode 100644
index c81300c..0000000
--- a/src/plugins/apim/azureApimFunctionPlugin.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import Serverless from "serverless";
-import { ApimService } from "../../services/apimService";
-import { AzureBasePlugin } from "../azureBasePlugin";
-
-export class AzureApimFunctionPlugin extends AzureBasePlugin {
-
-  public constructor(serverless: Serverless, options: Serverless.Options) {
-    super(serverless, options);
-    this.hooks = {
-      "after:deploy:function:deploy": this.deploy.bind(this)
-    };
-  }
-
-  private async deploy() {
-    this.serverless.cli.log("Starting APIM function deployment");
-
-    const apimService = new ApimService(this.serverless, this.options);
-    const service = await apimService.get();
-    const api = await apimService.getApi();
-
-    await apimService.deployFunction(service, api, this.options);
-
-    this.log("Finished APIM function deployment");
-  }
-}
diff --git a/src/plugins/apim/azureApimServicePlugin.ts b/src/plugins/apim/azureApimPlugin.ts
similarity index 71%
rename from src/plugins/apim/azureApimServicePlugin.ts
rename to src/plugins/apim/azureApimPlugin.ts
index d900fc5..500f441 100644
--- a/src/plugins/apim/azureApimServicePlugin.ts
+++ b/src/plugins/apim/azureApimPlugin.ts
@@ -2,12 +2,12 @@ import Serverless from "serverless";
 import { ApimService } from "../../services/apimService";
 import { AzureBasePlugin } from "../azureBasePlugin";
 
-export class AzureApimServicePlugin extends AzureBasePlugin {
+export class AzureApimPlugin extends AzureBasePlugin {
 
   public constructor(serverless: Serverless, options: Serverless.Options) {
     super(serverless, options);
     this.hooks = {
-      "after:deploy:deploy": this.deploy.bind(this)
+      "after:deploy:deploy": this.deploy.bind(this),
     };
   }
 
@@ -20,9 +20,7 @@ export class AzureApimServicePlugin extends AzureBasePlugin {
     this.serverless.cli.log("Starting APIM service deployment");
 
     const apimService = new ApimService(this.serverless, this.options);
-    const service = await apimService.get();
-    const api = await apimService.deployApi();
-    await apimService.deployFunctions(service, api);
+    await apimService.deploy();
 
     this.log("Finished APIM service deployment");
   }
diff --git a/src/plugins/deploy/azureDeployPlugin.ts b/src/plugins/deploy/azureDeployPlugin.ts
index 4b1b8ea..8d7cf3e 100644
--- a/src/plugins/deploy/azureDeployPlugin.ts
+++ b/src/plugins/deploy/azureDeployPlugin.ts
@@ -4,6 +4,7 @@ import { FunctionAppService } from "../../services/functionAppService";
 import { AzureLoginOptions } from "../../services/loginService";
 import { ResourceService } from "../../services/resourceService";
 import { AzureBasePlugin } from "../azureBasePlugin";
+import { ApimService } from "../../services/apimService";
 
 export class AzureDeployPlugin extends AzureBasePlugin<AzureLoginOptions> {
   public commands: any;
@@ -14,65 +15,49 @@ export class AzureDeployPlugin extends AzureBasePlugin<AzureLoginOptions> {
     this.hooks = {
       "deploy:deploy": this.deploy.bind(this),
       "deploy:list:list": this.list.bind(this),
+      "deploy:apim:apim": this.deployApim.bind(this),
     };
 
+    const deployOptions = {
+      resourceGroup: {
+        usage: "Resource group for the service",
+        shortcut: "g",
+      },
+      stage: {
+        usage: "Stage of service",
+        shortcut: "s"
+      },
+      region: {
+        usage: "Region of service",
+        shortcut: "r"
+      },
+      subscriptionId: {
+        usage: "Sets the Azure subscription ID",
+        shortcut: "i",
+      },
+      function: {
+        usage: "Deployment of individual function - NOT SUPPORTED",
+        shortcut: "f",
+      }
+    }
+
     this.commands = {
       deploy: {
         commands: {
           list: {
             usage: "List deployments",
-            lifecycleEvents: [
-              "list"
-            ]
-          },
-          options: {
-            resourceGroup: {
-              usage: "Resource group for the service",
-              shortcut: "g",
-            },
-            stage: {
-              usage: "Stage of service",
-              shortcut: "s"
-            },
-            region: {
-              usage: "Region of service",
-              shortcut: "r"
-            },
-            subscriptionId: {
-              usage: "Sets the Azure subscription ID",
-              shortcut: "i",
-            },
-            function: {
-              usage: "Deployment of individual function - NOT SUPPORTED",
-              shortcut: "f",
+            lifecycleEvents: ["list"],
+            options: {
+              ...deployOptions
             }
+          },
+          apim: {
+            usage: "Deploys APIM",
+            lifecycleEvents: ["apim"]
           }
         },
         options: {
-          resourceGroup: {
-            usage: "Resource group for the service",
-            shortcut: "g",
-          },
-          stage: {
-            usage: "Stage of service",
-            shortcut: "s"
-          },
-          region: {
-            usage: "Region of service",
-            shortcut: "r"
-          },
-          subscriptionId: {
-            usage: "Sets the Azure subscription ID",
-            shortcut: "i",
-          },
-          package: {
-            usage: "Package to deploy",
-            shortcut: "p",
-          },
-          function: {
-            usage: "Deployment of individual function - NOT SUPPORTED",
-            shortcut: "f",
-          }
+          ...deployOptions
         }
       }
     }
@@ -98,6 +83,24 @@ export class AzureDeployPlugin extends AzureBasePlugin<AzureLoginOptions> {
     await functionAppService.uploadFunctions(functionApp);
   }
 
+  /**
+   * Deploys APIM if configured
+   */
+  private async deployApim() {
+    const apimConfig = this.serverless.service.provider["apim"];
+    if (!apimConfig) {
+      this.log("No APIM configuration found");
+      return Promise.resolve();
+    }
+
+    this.serverless.cli.log("Starting APIM service deployment");
+
+    const apimService = new ApimService(this.serverless, this.options);
+    await apimService.deploy();
+
+    this.log("Finished APIM service deployment");
+  }
+
   /**
    * Check to see if user tried to target an individual function for deployment or deployment list
    * Throws error if `function` is specified
diff --git a/src/plugins/identity/azureKeyVaultPlugin.ts b/src/plugins/identity/azureKeyVaultPlugin.ts
index 999d3d3..8be800e 100644
--- a/src/plugins/identity/azureKeyVaultPlugin.ts
+++ b/src/plugins/identity/azureKeyVaultPlugin.ts
@@ -15,13 +15,11 @@ export class AzureKeyVaultPlugin extends AzureBasePlugin {
     if (!keyVaultConfig) {
       return Promise.resolve();
     }
-    this.serverless.cli.log("Starting KeyVault service setup");
+    this.log("Starting KeyVault service setup");
 
     const keyVaultService = new AzureKeyVaultService(this.serverless, this.options);
-    const result = await keyVaultService.setPolicy(keyVaultConfig);
+    await keyVaultService.setPolicy(keyVaultConfig);
 
-    this.serverless.cli.log("Finished KeyVault service setup");
-
-    return result;
+    this.log("Finished KeyVault service setup");
   }
 }
diff --git a/src/plugins/login/loginHooks.ts b/src/plugins/login/loginHooks.ts
index 97db4a3..44f4475 100644
--- a/src/plugins/login/loginHooks.ts
+++ b/src/plugins/login/loginHooks.ts
@@ -4,6 +4,7 @@
 export const loginHooks = [
   "deploy:list:list",
   "deploy:deploy",
+  "deploy:apim:apim",
   "invoke:invoke",
   "rollback:rollback",
   "remove:remove",
diff --git a/src/services/apimService.ts b/src/services/apimService.ts
index f66a722..22ca474 100644
--- a/src/services/apimService.ts
+++ b/src/services/apimService.ts
@@ -3,10 +3,10 @@ import xml from "xml";
 import { ApiManagementClient } from "@azure/arm-apimanagement";
 import { FunctionAppService } from "./functionAppService";
 import { BaseService } from "./baseService";
-import { ApiManagementConfig, ApiOperationOptions, ApiCorsPolicy } from "../models/apiManagement";
+import { ApiManagementConfig, ApiCorsPolicy } from "../models/apiManagement";
 import {
-  ApiContract, BackendContract, OperationContract,
-  PropertyContract, ApiManagementServiceResource,
+  ApiContract, OperationContract,
+  PropertyContract, ApiManagementServiceResource, BackendContract,
 } from "@azure/arm-apimanagement/esm/models";
 import { Site } from "@azure/arm-appservice/esm/models";
 import { Guard } from "../shared/guard";
@@ -28,12 +28,23 @@ export class ApimService extends BaseService {
       return;
     }
 
+    if (typeof (this.apimConfig) === "boolean") {
+      this.apimConfig = {
+        name: null,
+        apis: [],
+      };
+    }
+
     if (!this.apimConfig.name) {
       this.apimConfig.name = ApimResource.getResourceName(this.config);
     }
 
-    if (!this.apimConfig.backend) {
-      this.apimConfig.backend = {} as any;
+    if (!this.apimConfig.apis) {
+      this.apimConfig.apis = [];
+    }
+
+    if (!this.apimConfig.backends) {
+      this.apimConfig.backends = [];
     }
 
     this.apimClient = new ApiManagementClient(this.credentials, this.subscriptionId);
@@ -55,13 +66,19 @@ export class ApimService extends BaseService {
     }
   }
 
-  public async getApi(): Promise<ApiContract> {
-    if (!(this.apimConfig && this.apimConfig.api && this.apimConfig.api.name)) {
+  /**
+   * Gets the APIM API by API name
+   * @param apiName The API to retrieve
+   */
+  public async getApi(apiName: string): Promise<ApiContract> {
+    Guard.empty(apiName);
+
+    if (!(this.apimConfig && this.apimConfig.name)) {
       return null;
     }
 
     try {
-      return await this.apimClient.api.get(this.resourceGroup, this.apimConfig.name, this.apimConfig.api.name);
+      return await this.apimClient.api.get(this.resourceGroup, this.apimConfig.name, apiName);
     } catch (err) {
       return null;
     }
@@ -70,26 +87,30 @@ export class ApimService extends BaseService {
   /**
    * Deploys the APIM top level api
    */
-  public async deployApi() {
+  public async deploy() {
     if (!(this.apimConfig && this.apimConfig.name)) {
       return null;
     }
 
     const functionApp = await this.functionAppService.get();
-
-    const api = await this.ensureApi();
+    this.setApimDefaults(functionApp);
     await this.ensureFunctionAppKeys(functionApp);
-    await this.ensureBackend(functionApp);
 
-    return api;
+    const resource = await this.get();
+    const apiTasks = this.apimConfig.apis.map((api) => this.ensureApi(api));
+    const backendTasks = this.apimConfig.backends.map((backend) => this.ensureBackend(functionApp, backend));
+
+    await Promise.all(apiTasks);
+    await Promise.all(backendTasks);
+
+    await this.deployFunctions(functionApp, resource);
   }
 
   /**
    * Deploys all the functions of the serverless service to APIM
    */
-  public async deployFunctions(service: ApiManagementServiceResource, api: ApiContract) {
-    Guard.null(service);
-    Guard.null(api);
+  public async deployFunctions(functionApp: Site, resource: ApiManagementServiceResource) {
+    Guard.null(resource);
 
     if (!(this.apimConfig && this.apimConfig.name)) {
       return null;
@@ -99,56 +120,103 @@ export class ApimService extends BaseService {
 
     const deployApiTasks = this.serverless.service
       .getAllFunctions()
-      .map((functionName) => this.deployFunction(service, api, { function: functionName }));
+      .map((functionName) => this.deployFunction(functionApp, resource, functionName));
 
-    return Promise.all(deployApiTasks);
+    return await Promise.all(deployApiTasks);
   }
 
   /**
    * Deploys the specified serverless function  to APIM
    * @param options
    */
-  public async deployFunction(service: ApiManagementServiceResource, api: ApiContract, options) {
-    Guard.null(service);
-    Guard.null(api);
-    Guard.null(options);
+  public async deployFunction(functionApp: Site, resource: ApiManagementServiceResource, functionName: string) {
+    Guard.null(functionApp);
+    Guard.null(resource);
+    Guard.empty(functionName);
 
-    const functionConfig = this.serverless.service["functions"][options.function];
+    const functionConfig = this.serverless.service["functions"][functionName];
+    functionConfig.name = functionName;
 
-    if (!(functionConfig && functionConfig.apim)) {
+    const httpEvent = functionConfig.events.find((event) => event.http);
+    if (!httpEvent) {
       return;
     }
 
-    const tasks = functionConfig.apim.operations.map((operation) => {
-      return this.deployOperation(service, api, {
-        function: options.function,
-        operation,
-      });
-    });
+    const httpConfig = httpEvent["x-azure-settings"];
+
+    // Infer APIM operation configuration from HTTP event if not already set
+    if (!functionConfig.apim) {
+      const operations = httpConfig.methods.map((method) => {
+        return {
+          name: functionConfig.name,
+          displayName: functionConfig.name,
+          urlTemplate: httpConfig.route || functionConfig.name,
+          method: method,
+          templateParameters: this.getTemplateParameters(httpConfig.route)
+        };
+      })
+
+      functionConfig.apim = { operations };
+    }
+
+    // Lookup api mapping
+    const apiContract = functionConfig.apim.api
+      ? this.apimConfig.apis.find((api) => api.name === functionConfig.apim.api)
+      : this.apimConfig.apis[0];
+
+    // Lookup backend mapping
+    const backendContract = functionConfig.apim.backend
+      ? this.apimConfig.backends.find((backend) => backend.name === functionConfig.apim.backend)
+      : this.apimConfig.backends[0];
+
+    const tasks = functionConfig.apim.operations
+      .map((operation) => this.deployOperation(resource, apiContract, backendContract, operation, functionName));
 
     await Promise.all(tasks);
   }
 
+  /**
+   * Retrieves the template parameter referenced in the route template
+   * @param route The route template to inspect
+   */
+  private getTemplateParameters(route: string) {
+    const regex = new RegExp(/{(\w+)}/g);
+    const matches = [];
+    while (true) {
+      const match = regex.exec(route);
+      if (!match) {
+        break;
+      }
+
+      matches.push(match);
+    };
+
+    if (matches.length === 0) {
+      return null;
+    }
+
+    return matches.map((match) => ({
+      name: match[1],
+      type: "string",
+    }));
+  }
+
   /**
    * Deploys the APIM API referenced by the serverless service
    */
-  private async ensureApi(): Promise<ApiContract> {
-    this.log("-> Deploying API");
+  private async ensureApi(apiContract: ApiContract): Promise<ApiContract> {
+    this.log(`-> Deploying API: ${apiContract.name}`);
 
     try {
-      const api = await this.apimClient.api.createOrUpdate(this.resourceGroup, this.apimConfig.name, this.apimConfig.api.name, {
+      const api = await this.apimClient.api.createOrUpdate(this.resourceGroup, this.apimConfig.name, apiContract.name, {
+        ...apiContract,
         isCurrent: true,
-        subscriptionRequired: this.apimConfig.api.subscriptionRequired,
-        displayName: this.apimConfig.api.displayName,
-        description: this.apimConfig.api.description,
-        path: this.apimConfig.api.path,
-        protocols: this.apimConfig.api.protocols,
       });
 
       if (this.apimConfig.cors) {
         this.log("-> Deploying CORS policy");
 
-        await this.apimClient.apiPolicy.createOrUpdate(this.resourceGroup, this.apimConfig.name, this.apimConfig.api.name, {
+        await this.apimClient.apiPolicy.createOrUpdate(this.resourceGroup, this.apimConfig.name, apiContract.name, {
           format: "rawxml",
           value: this.createCorsXmlPolicy(this.apimConfig.cors)
         });
@@ -166,24 +234,25 @@ export class ApimService extends BaseService {
    * Deploys the APIM Backend referenced by the serverless service
    * @param functionAppUrl The host name for the deployed function app
    */
-  private async ensureBackend(functionApp: Site): Promise<BackendContract> {
-    const backendUrl = `https://${functionApp.defaultHostName}/api`;
+  private async ensureBackend(functionApp: Site, backendContract: BackendContract): Promise<BackendContract> {
+    const backendUrl = `https://${functionApp.defaultHostName}/${backendContract.url}`;
 
-    this.log(`-> Deploying API Backend ${functionApp.name} = ${backendUrl}`);
+    this.log(`-> Deploying API Backend: ${backendContract.name} => ${backendUrl}`);
     try {
       const functionAppResourceId = `https://management.azure.com${functionApp.id}`;
 
-      return await this.apimClient.backend.createOrUpdate(this.resourceGroup, this.apimConfig.name, this.serviceName, {
+      return await this.apimClient.backend.createOrUpdate(this.resourceGroup, this.apimConfig.name, backendContract.name, {
         credentials: {
           header: {
             "x-functions-key": [`{{${this.serviceName}-key}}`],
           },
         },
-        title: this.apimConfig.backend.title || functionApp.name,
-        tls: this.apimConfig.backend.tls,
-        proxy: this.apimConfig.backend.proxy,
-        description: this.apimConfig.backend.description,
-        protocol: this.apimConfig.backend.protocol || "http",
+        name: backendContract.name,
+        title: backendContract.title || functionApp.name,
+        tls: backendContract.tls,
+        proxy: backendContract.proxy,
+        description: backendContract.description,
+        protocol: backendContract.protocol || "http",
         resourceId: functionAppResourceId,
         url: backendUrl,
       });
@@ -199,44 +268,40 @@ export class ApimService extends BaseService {
    * @param serverless The serverless framework
    * @param options The plugin options
    */
-  private async deployOperation(
-    service: ApiManagementServiceResource,
-    api: ApiContract,
-    options: ApiOperationOptions,
-  ): Promise<OperationContract> {
+  private async deployOperation(resource: ApiManagementServiceResource, api: ApiContract, backend: BackendContract, operation: OperationContract, functionName: string): Promise<OperationContract> {
     try {
       const client = new ApiManagementClient(this.credentials, this.subscriptionId);
 
       const operationConfig: OperationContract = {
-        displayName: options.operation.displayName || options.function,
-        description: options.operation.description || "",
-        method: options.operation.method,
-        urlTemplate: options.operation.urlTemplate,
-        templateParameters: options.operation.templateParameters || [],
-        responses: options.operation.responses || [],
+        displayName: operation.displayName || functionName,
+        description: operation.description || "",
+        method: operation.method,
+        urlTemplate: operation.urlTemplate,
+        templateParameters: operation.templateParameters || [],
+        responses: operation.responses || [],
       };
 
       // Ensure a single path seperator in the operation path
       const operationPath = `/${api.path}/${operationConfig.urlTemplate}`.replace(/\/+/g, "/");
-      const operationUrl = `${service.gatewayUrl}${operationPath}`;
-      this.log(`--> Deploying API operation ${options.function}: ${operationConfig.method.toUpperCase()} ${operationUrl}`);
+      const operationUrl = `${resource.gatewayUrl}${operationPath}`;
+      this.log(`--> ${functionName}: [${operationConfig.method.toUpperCase()}] ${operationUrl}`);
 
-      const operation = await client.apiOperation.createOrUpdate(
+      const result = await client.apiOperation.createOrUpdate(
         this.resourceGroup,
         this.apimConfig.name,
-        this.apimConfig.api.name,
-        options.function,
+        api.name,
+        functionName,
         operationConfig,
       );
 
-      await client.apiOperationPolicy.createOrUpdate(this.resourceGroup, this.apimConfig.name, this.apimConfig.api.name, options.function, {
+      await client.apiOperationPolicy.createOrUpdate(this.resourceGroup, this.apimConfig.name, api.name, functionName, {
         format: "rawxml",
-        value: this.createApiOperationXmlPolicy(),
+        value: this.createApiOperationXmlPolicy(backend.name),
       });
 
-      return operation;
+      return result;
     } catch (e) {
-      this.log(`Error deploying API operation ${options.function}`);
+      this.log(`Error deploying API operation ${functionName}`);
       this.log(JSON.stringify(e.body, null, 4));
       throw e;
     }
@@ -267,7 +332,7 @@ export class ApimService extends BaseService {
   /**
    * Creates the XML payload that defines the API operation policy to link to the configured backend
    */
-  private createApiOperationXmlPolicy(): string {
+  private createApiOperationXmlPolicy(backendId: string): string {
     const operationPolicy = [{
       policies: [
         {
@@ -278,7 +343,7 @@ export class ApimService extends BaseService {
                 {
                   "_attr": {
                     "id": "apim-generated-policy",
-                    "backend-id": this.serviceName,
+                    "backend-id": backendId,
                   }
                 },
               ],
@@ -328,4 +393,45 @@ export class ApimService extends BaseService {
 
     return xml(policy, { indent: "\t" });
   }
+
+  /**
+   * Sets up APIM defaults if not explicitly defined
+   * @param functionApp The function app resource
+   */
+  private setApimDefaults(functionApp: Site) {
+    const defaultApi: ApiContract = {
+      isCurrent: true,
+      name: `${this.serviceName}-api`,
+      subscriptionRequired: false,
+      displayName: "API",
+      description: "",
+      path: "api",
+      protocols: ["http", "https"]
+    }
+
+    if (this.apimConfig.apis.length === 0) {
+      this.apimConfig.apis.push(defaultApi);
+    }
+
+    const functionAppResourceId = `https://management.azure.com${functionApp.id}`;
+
+    // Configure a default backend link if not explicity defined
+    const defaultBackend: BackendContract = {
+      credentials: {
+        header: {
+          "x-functions-key": [`{{${this.serviceName}-key}}`],
+        },
+      },
+      name: `${this.serviceName}-backend`,
+      title: functionApp.name,
+      description: "Function App Backend",
+      protocol: "http",
+      resourceId: functionAppResourceId,
+      url: "api"
+    };
+
+    if (this.apimConfig.backends.length === 0) {
+      this.apimConfig.backends.push(defaultBackend);
+    }
+  }
 }
diff --git a/src/services/azureKeyVaultService.ts b/src/services/azureKeyVaultService.ts
index a8056f5..8bb0c5a 100644
--- a/src/services/azureKeyVaultService.ts
+++ b/src/services/azureKeyVaultService.ts
@@ -2,7 +2,7 @@ import Serverless from "serverless";
 import { BaseService } from "./baseService";
 import { FunctionAppService } from "./functionAppService";
 import { KeyVaultManagementClient } from "@azure/arm-keyvault";
-import { KeyPermissions, SecretPermissions, Vault } from "@azure/arm-keyvault/esm/models/index";
+import { Vault, SecretPermissions } from "@azure/arm-keyvault/esm/models";
 
 /**
  * Defines the Azure Key Vault configuration
@@ -18,8 +18,6 @@ export interface AzureKeyVaultConfig {
  * Services for the Key Vault Plugin
  */
 export class AzureKeyVaultService extends BaseService {
-  private funcApp: FunctionAppService;
-
   /**
    * Initialize key vault service and get function app
    * @param serverless Serverless object
@@ -27,7 +25,6 @@ export class AzureKeyVaultService extends BaseService {
    */
   public constructor(serverless: Serverless, options: Serverless.Options) {
     super(serverless, options);
-    this.funcApp = new FunctionAppService(serverless, options);
   }
 
   /**
@@ -36,13 +33,15 @@ export class AzureKeyVaultService extends BaseService {
    */
   public async setPolicy(keyVaultConfig: AzureKeyVaultConfig) {
     const subscriptionID = this.subscriptionId;
+    const functionAppService = new FunctionAppService(this.serverless, this.options);
+    const keyVaultClient = new KeyVaultManagementClient(this.credentials, subscriptionID);
 
-    const func = await this.funcApp.get();
-    const identity = func.identity;
+    const functionApp = await functionAppService.get();
+    const identity = functionApp.identity;
     let vault: Vault;
-    const keyVaultClient = new KeyVaultManagementClient(this.credentials, subscriptionID);
+
     try {
-      vault = await keyVaultClient.vaults.get(keyVaultConfig.resourceGroup, keyVaultConfig.name)
+      vault = await keyVaultClient.vaults.get(keyVaultConfig.resourceGroup, keyVaultConfig.name);
     } catch (error) {
       throw new Error("Error: Specified vault not found")
     }
@@ -56,6 +55,6 @@ export class AzureKeyVaultService extends BaseService {
     }
     vault.properties.accessPolicies.push(newEntry);
 
-    return keyVaultClient.vaults.createOrUpdate(keyVaultConfig.resourceGroup, keyVaultConfig.name, {location: vault.location, properties: vault.properties})
+    await keyVaultClient.vaults.createOrUpdate(keyVaultConfig.resourceGroup, keyVaultConfig.name, {location: vault.location, properties: vault.properties});
   }
 }
diff --git a/src/services/baseService.ts b/src/services/baseService.ts
index e1a513a..1e433b0 100644
--- a/src/services/baseService.ts
+++ b/src/services/baseService.ts
@@ -14,7 +14,7 @@ import {
 } from "../models/serverless";
 import { Guard } from "../shared/guard";
 import { Utils } from "../shared/utils";
-import { AzureNamingService } from "./namingService";
+import { AzureNamingService, AzureNamingServiceOptions } from "./namingService";
 
 export abstract class BaseService {
   protected baseUrl: string;
@@ -49,9 +49,7 @@ export abstract class BaseService {
     this.storageAccountName = StorageAccountResource.getResourceName(this.config);
 
     if (!this.credentials && authenticate) {
-      throw new Error(
-        `Azure Credentials has not been set in ${this.constructor.name}`
-      );
+      throw new Error(`Azure Credentials has not been set in ${this.constructor.name}`);
     }
   }
 
@@ -196,7 +194,7 @@ export abstract class BaseService {
    * Log message to Serverless CLI
    * @param message Message to log
    */
-  protected log(message: string, options?: ServerlessLogOptions, entity?: string,) {
+  protected log(message: string, options?: ServerlessLogOptions, entity?: string) {
     (this.serverless.cli.log as any)(message, entity, options);
   }
 
@@ -259,18 +257,24 @@ export abstract class BaseService {
   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.azureSubId
+        || process.env.AZURE_SUBSCRIPTION_ID
         || subscriptionId
         || this.serverless.variables["subscriptionId"]
     }
     this.config.provider.resourceGroup = (
       this.getOption("resourceGroup", this.config.provider.resourceGroup)
-    ) || AzureNamingService.getResourceName(this.config, null, `${this.getServiceName()}-rg`);
+    ) || AzureNamingService.getResourceName(options);
   }
 }
diff --git a/src/services/functionAppService.ts b/src/services/functionAppService.ts
index 0045018..6942f8b 100644
--- a/src/services/functionAppService.ts
+++ b/src/services/functionAppService.ts
@@ -154,23 +154,26 @@ export class FunctionAppService extends BaseService {
     this.log("Deploying serverless functions...");
 
     const functionZipFile = this.getFunctionZipFile();
-    const blobUpload = this.uploadZippedArtifactToBlobStorage(functionZipFile);
 
     if (this.deploymentConfig.runFromBlobUrl) {
       this.log("Updating function app setting to run from external package...");
-      await blobUpload;
+      await this.uploadZippedArtifactToBlobStorage(functionZipFile);
+
       const sasUrl = await this.blobService.generateBlobSasTokenUrl(
         this.deploymentConfig.container,
         this.artifactName
-      )
+      );
+
       await this.updateFunctionAppSetting(
         functionApp,
         configConstants.runFromPackageSetting,
         sasUrl
-      )
+      );
     } else {
-      const functionAppUpload = this.uploadZippedArfifactToFunctionApp(functionApp, functionZipFile);
-      Promise.all([blobUpload, functionAppUpload]);
+      await Promise.all([
+        this.uploadZippedArtifactToBlobStorage(functionZipFile),
+        this.uploadZippedArfifactToFunctionApp(functionApp, functionZipFile)
+      ]);
     }
 
     this.log("Deployed serverless functions:")
@@ -184,7 +187,7 @@ export class FunctionAppService extends BaseService {
 
         if (httpConfig) {
           const method = httpConfig.methods[0].toUpperCase();
-          this.log(`-> ${functionConfig.name}: ${method} ${httpConfig.url}`);
+          this.log(`-> ${functionConfig.name}: [${method}] ${httpConfig.url}`);
         }
       }
     });
@@ -256,7 +259,7 @@ export class FunctionAppService extends BaseService {
   /**
    * Uploads artifact file to blob storage container
    */
-  private async uploadZippedArtifactToBlobStorage(functionZipFile: string) {
+  private async uploadZippedArtifactToBlobStorage(functionZipFile: string): Promise<void> {
     await this.blobService.initialize();
     await this.blobService.createContainerIfNotExists(this.deploymentConfig.container);
     await this.blobService.uploadFile(
diff --git a/src/services/loginService.ts b/src/services/loginService.ts
index 8ed84dc..49bf602 100644
--- a/src/services/loginService.ts
+++ b/src/services/loginService.ts
@@ -26,11 +26,11 @@ export class AzureLoginService extends BaseService {
    * set or via interactive login if environment variables are not set
    * @param options Options for different authentication methods
    */
-  public async login(options?: AzureTokenCredentialsOptions|InteractiveLoginOptions): Promise<AuthResponse> {
-    const subscriptionId = process.env.azureSubId;
-    const clientId = process.env.azureServicePrincipalClientId;
-    const secret = process.env.azureServicePrincipalPassword;
-    const tenantId = process.env.azureServicePrincipalTenantId;
+  public async login(options?: AzureTokenCredentialsOptions | InteractiveLoginOptions): Promise<AuthResponse> {
+    const subscriptionId = this.getSubscriptionId();
+    const clientId = process.env.AZURE_CLIENT_ID;
+    const secret = process.env.AZURE_CLIENT_SECRET;
+    const tenantId = process.env.AZURE_TENANT_ID;
 
     if (subscriptionId && clientId && secret && tenantId) {
       return await this.servicePrincipalLogin(clientId, secret, tenantId, options);
@@ -40,11 +40,11 @@ export class AzureLoginService extends BaseService {
   }
 
   public async interactiveLogin(options?: InteractiveLoginOptions): Promise<AuthResponse> {
-    let authResp: AuthResponse = {credentials: undefined, subscriptions: []};
+    let authResp: AuthResponse = { credentials: undefined, subscriptions: [] };
     const fileTokenCache = new SimpleFileTokenCache();
-    if(fileTokenCache.isEmpty()){
+    if (fileTokenCache.isEmpty()) {
       await open("https://microsoft.com/devicelogin");
-      authResp = await interactiveLoginWithAuthResponse({...options, tokenCache: fileTokenCache});
+      authResp = await interactiveLoginWithAuthResponse({ ...options, tokenCache: fileTokenCache });
       fileTokenCache.addSubs(authResp.subscriptions);
     } else {
       authResp.credentials = new DeviceTokenCredentials(undefined, undefined, fileTokenCache.first().userId, undefined, undefined, fileTokenCache);
@@ -52,10 +52,9 @@ export class AzureLoginService extends BaseService {
     }
 
     return authResp;
-
   }
 
   public async servicePrincipalLogin(clientId: string, secret: string, tenantId: string, options: AzureTokenCredentialsOptions): Promise<AuthResponse> {
-    return loginWithServicePrincipalSecretWithAuthResponse(clientId, secret, tenantId, options);
+    return await loginWithServicePrincipalSecretWithAuthResponse(clientId, secret, tenantId, options);
   }
 }
diff --git a/src/services/namingService.ts b/src/services/namingService.ts
index f53bf0f..041e5ff 100644
--- a/src/services/namingService.ts
+++ b/src/services/namingService.ts
@@ -3,6 +3,14 @@ import { Guard } from "../shared/guard"
 import configConstants from "../config";
 import md5 from "md5";
 
+export interface AzureNamingServiceOptions {
+  config: ServerlessAzureConfig;
+  resourceConfig?: ResourceConfig;
+  suffix?: string;
+  includeHash?: boolean;
+  maxLength?: number;
+}
+
 export class AzureNamingService {
 
   /**
@@ -14,20 +22,28 @@ export class AzureNamingService {
    * @param resourceConfig The serverless resource configuration
    * @param suffix Optional suffix to append on the end of the generated name
    */
-  public static getResourceName(config: ServerlessAzureConfig, resourceConfig?: ResourceConfig, suffix?: string) {
-    if (resourceConfig && resourceConfig.name) {
-      return resourceConfig.name;
+  public static getResourceName(options: AzureNamingServiceOptions) {
+    if (options.resourceConfig && options.resourceConfig.name) {
+      return options.resourceConfig.name;
     }
 
-    const { prefix, region, stage } = config.provider
+    if (options.includeHash === undefined) {
+      options.includeHash = true;
+    }
+
+    const { prefix, region, stage } = options.config.provider
     let name = [
       prefix,
       this.createShortAzureRegionName(region),
       this.createShortStageName(stage),
     ].join("-");
 
-    if (suffix) {
-      name += `-${suffix}`;
+    if(options.includeHash) {
+      name += `-${md5(options.config.provider.resourceGroup).substr(0, configConstants.resourceGroupHashLength)}`
+    }
+
+    if (options.suffix) {
+      name += `-${options.suffix}`;
     }
 
     return name.toLowerCase();
@@ -46,28 +62,32 @@ export class AzureNamingService {
    * @param forbidden Regex for characters to remove from name. Defaults to non-alpha-numerics
    * @param replaceWith String to replace forbidden characters. Defaults to empty string
    */
-  public static getSafeResourceName(config: ServerlessAzureConfig, maxLength: number, resourceConfig?: ResourceConfig, suffix: string = "", includeHash = false) {
+  public static getSafeResourceName(options: AzureNamingServiceOptions) {
     const nonAlphaNumeric = /\W+/g;
 
-    if (resourceConfig && resourceConfig.name) {
-      const { name } = resourceConfig;
+    if (options.resourceConfig && options.resourceConfig.name) {
+      const { name } = options.resourceConfig;
 
-      if (name.length > maxLength) {
-        throw new Error(`Name '${name}' invalid. Should be shorter than ${maxLength} characters`);
+      if (name.length > options.maxLength) {
+        throw new Error(`Name '${name}' invalid. Should be shorter than ${options.maxLength} characters`);
       }
 
       return name.replace(nonAlphaNumeric, "");
     }
 
-    const { prefix, region, stage } = config.provider;
+    if (options.includeHash === undefined) {
+      options.includeHash = true;
+    }
+
+    const { prefix, region, stage } = options.config.provider;
 
     let safePrefix = prefix.replace(nonAlphaNumeric, "");
     const safeRegion = this.createShortAzureRegionName(region);
     let safeStage = this.createShortStageName(stage);
-    let safeSuffix = suffix.replace(nonAlphaNumeric, "");
+    let safeSuffix = options.suffix.replace(nonAlphaNumeric, "");
 
-    const hashLength = (includeHash) ? configConstants.resourceGroupHashLength : 0;
-    const remaining = maxLength - (safePrefix.length + safeRegion.length + safeStage.length + safeSuffix.length + hashLength);
+    const hashLength = (options.includeHash) ? configConstants.resourceGroupHashLength : 0;
+    const remaining = options.maxLength - (safePrefix.length + safeRegion.length + safeStage.length + safeSuffix.length + hashLength);
 
     // Dynamically adjust the substring based on space needed
     if (remaining < 0) {
@@ -81,14 +101,14 @@ export class AzureNamingService {
       safeSuffix = safeSuffix.substr(0, partLength);
     }
 
-    const safeHash = md5(config.provider.resourceGroup).substr(0, hashLength);
+    const safeHash = md5(options.config.provider.resourceGroup).substr(0, hashLength);
 
     const name = [safePrefix, safeRegion, safeStage, safeHash, safeSuffix]
       .join("")
       .toLowerCase();
 
-    if (name.length > maxLength) {
-      throw new Error(`Name ${name} is too long. Should be shorter than ${maxLength} characters`)
+    if (name.length > options.maxLength) {
+      throw new Error(`Name ${name} is too long. Should be shorter than ${options.maxLength} characters`)
     }
 
     return name;
@@ -108,9 +128,16 @@ export class AzureNamingService {
     if (timestamp) {
       maxLength -= timestamp.length + suffix.length;
 
+      const options: AzureNamingServiceOptions = {
+        config,
+        suffix: config.service,
+        includeHash: false,
+        maxLength,
+      }
+
       const name = (deploymentName)
         ? deploymentName.substr(0, maxLength)
-        : [AzureNamingService.getSafeResourceName(config, maxLength, null, config.service), suffix].join("-");
+        : [AzureNamingService.getSafeResourceName(options), suffix].join("-");
 
       return [name, timestamp].join("-");
     }
diff --git a/src/services/packageService.ts b/src/services/packageService.ts
index 4e340cc..8ab9477 100644
--- a/src/services/packageService.ts
+++ b/src/services/packageService.ts
@@ -24,14 +24,14 @@ export class PackageService extends BaseService {
   /**
    * Creates the function.json binding files required for the serverless service
    */
-  public createBindings(): Promise<any[]> {
+  public async createBindings(): Promise<void> {
     const createEventsPromises = this.serverless.service.getAllFunctions()
       .map((functionName) => {
         const metaData = Utils.getFunctionMetaData(functionName, this.serverless);
         return this.createBinding(functionName, metaData);
       });
 
-    return Promise.all(createEventsPromises);
+    await Promise.all(createEventsPromises);
   }
 
   /**
@@ -95,10 +95,12 @@ export class PackageService extends BaseService {
     functionJSON.scriptFile = functionMetadata.handlerPath;
     const functionObject = this.slsFunctions()[functionName];
     const bindingAzureSettings = Utils.getIncomingBindingConfig(functionObject)["x-azure-settings"];
+
     if (bindingAzureSettings.route) {
       // Find incoming binding within functionJSON and set the route
       const index = (functionJSON.bindings as any[])
         .findIndex((binding) => (!binding.direction || binding.direction === "in"));
+
       functionJSON.bindings[index].route = bindingAzureSettings.route;
     }
 
@@ -108,7 +110,6 @@ export class PackageService extends BaseService {
     }
 
     const functionJsonString = JSON.stringify(functionJSON, null, 2);
-
     fs.writeFileSync(path.join(functionDirPath, "function.json"), functionJsonString);
 
     return Promise.resolve();
diff --git a/src/shared/bindings.ts b/src/shared/bindings.ts
index 7022b72..d9c9d22 100644
--- a/src/shared/bindings.ts
+++ b/src/shared/bindings.ts
@@ -57,15 +57,12 @@ export class BindingUtils {
     return bindingUserSettingsMetaData;
   }
 
-  public static getHttpOutBinding(bindingUserSettings) {
+  public static getHttpOutBinding() {
     const binding = {};
 
     binding[constants.type] = "http";
     binding[constants.direction] = constants.outDirection;
-    binding[constants.name] = "$return";
-    if (bindingUserSettings[constants.webHookType]) {
-      binding[constants.name] = "res";
-    }
+    binding[constants.name] = "res";
 
     return binding;
   }
diff --git a/src/shared/utils.ts b/src/shared/utils.ts
index 3d091fc..0a26b5a 100644
--- a/src/shared/utils.ts
+++ b/src/shared/utils.ts
@@ -78,7 +78,7 @@ export class Utils {
     }
 
     if (bindingType === constants.httpTrigger) {
-      bindings.push(BindingUtils.getHttpOutBinding(bindingUserSettings));
+      bindings.push(BindingUtils.getHttpOutBinding());
     }
 
     functionsJson.bindings = bindings;


Test patch:
diff --git a/src/armTemplates/resources/apim.test.ts b/src/armTemplates/resources/apim.test.ts
new file mode 100644
index 0000000..beeff77
--- /dev/null
+++ b/src/armTemplates/resources/apim.test.ts
@@ -0,0 +1,55 @@
+import { ApimResource } from "./apim";
+import { ServerlessAzureConfig } from "../../models/serverless";
+import md5 from "md5";
+import configConstants from "../../config";
+
+describe("APIM Resource", () => {
+  const resourceGroupName = "myResourceGroup";
+  const prefix = "prefix";
+  const region = "eastus2";
+  const stage = "prod";
+
+  it("generates the correct resource name", () => {
+    const resourceGroupHash = md5(resourceGroupName).substr(
+      0,
+      configConstants.resourceGroupHashLength
+    );
+
+    const config: ServerlessAzureConfig = {
+      provider: {
+        name: "azure",
+        prefix,
+        region,
+        stage,
+        resourceGroup: resourceGroupName,
+        runtime: "nodejs10.x"
+      },
+      service: ""
+    } as any;
+
+    expect(ApimResource.getResourceName(config)).toEqual(
+      `${prefix}-eus2-${stage}-${resourceGroupHash}-apim`
+    );
+  });
+
+  it("uses the specified name from the azure provider", () => {
+    const apimName = "myAPIM";
+
+    const config: ServerlessAzureConfig = {
+      provider: {
+        apim: {
+          name: apimName
+        },
+        name: "azure",
+        prefix,
+        region,
+        stage,
+        resourceGroup: resourceGroupName,
+        runtime: "nodejs10.x"
+      },
+      service: ""
+    } as any;
+
+    expect(ApimResource.getResourceName(config)).toEqual(apimName);
+  });
+});
diff --git a/src/armTemplates/resources/appInsights.test.ts b/src/armTemplates/resources/appInsights.test.ts
new file mode 100644
index 0000000..ac8118f
--- /dev/null
+++ b/src/armTemplates/resources/appInsights.test.ts
@@ -0,0 +1,55 @@
+import { AppInsightsResource } from "./appInsights";
+import { ServerlessAzureConfig } from "../../models/serverless";
+import md5 from "md5";
+import configConstants from "../../config";
+
+describe("App Insights Resource", () => {
+  const resourceGroupName = "myResourceGroup";
+  const prefix = "prefix";
+  const region = "eastus2";
+  const stage = "prod";
+
+  it("generates the correct resource name", () => {
+    const resourceGroupHash = md5(resourceGroupName).substr(
+      0,
+      configConstants.resourceGroupHashLength
+    );
+
+    const config: ServerlessAzureConfig = {
+      provider: {
+        name: "azure",
+        prefix,
+        region,
+        stage,
+        resourceGroup: resourceGroupName,
+        runtime: "nodejs10.x"
+      },
+      service: ""
+    } as any;
+
+    expect(AppInsightsResource.getResourceName(config)).toEqual(
+      `${prefix}-eus2-${stage}-${resourceGroupHash}-appinsights`
+    );
+  });
+
+  it("uses the specified name from the azure provider", () => {
+    const appInsightsName = "myAppInsights";
+
+    const config: ServerlessAzureConfig = {
+      provider: {
+        name: "azure",
+        prefix,
+        region,
+        stage,
+        resourceGroup: resourceGroupName,
+        runtime: "nodejs10.x",
+        appInsights: {
+          name: appInsightsName,
+        },
+      },
+      service: "myapp",
+    } as any;
+
+    expect(AppInsightsResource.getResourceName(config)).toEqual(appInsightsName);
+  });
+});
\ No newline at end of file
diff --git a/src/armTemplates/resources/appServicePlan.test.ts b/src/armTemplates/resources/appServicePlan.test.ts
new file mode 100644
index 0000000..d3a76ef
--- /dev/null
+++ b/src/armTemplates/resources/appServicePlan.test.ts
@@ -0,0 +1,55 @@
+import { AppServicePlanResource } from "./appServicePlan";
+import { ServerlessAzureConfig } from "../../models/serverless";
+import md5 from "md5";
+import configConstants from "../../config";
+
+describe("App Service Plan Resource", () => {
+  const resourceGroupName = "myResourceGroup";
+  const prefix = "prefix";
+  const region = "eastus2";
+  const stage = "prod";
+
+  it("generates the correct resource name", () => {
+    const resourceGroupHash = md5(resourceGroupName).substr(
+      0,
+      configConstants.resourceGroupHashLength
+    );
+
+    const config: ServerlessAzureConfig = {
+      provider: {
+        name: "azure",
+        prefix,
+        region,
+        stage,
+        resourceGroup: resourceGroupName,
+        runtime: "nodejs10.x"
+      },
+      service: ""
+    } as any;
+
+    expect(AppServicePlanResource.getResourceName(config)).toEqual(
+      `${prefix}-eus2-${stage}-${resourceGroupHash}-asp`
+    );
+  });
+
+  it("uses the specified name from the azure provider", () => {
+    const appServicePlanName = "myAppServicePlan";
+
+    const config: ServerlessAzureConfig = {
+      provider: {
+        name: "azure",
+        prefix,
+        region,
+        stage,
+        resourceGroup: resourceGroupName,
+        runtime: "nodejs10.x",
+        appServicePlan: {
+          name: appServicePlanName,
+        },
+      },
+      service: "myapp",
+    } as any;
+
+    expect(AppServicePlanResource.getResourceName(config)).toEqual(appServicePlanName);
+  });
+});
diff --git a/src/armTemplates/resources/functionApp.test.ts b/src/armTemplates/resources/functionApp.test.ts
new file mode 100644
index 0000000..f15e4f4
--- /dev/null
+++ b/src/armTemplates/resources/functionApp.test.ts
@@ -0,0 +1,51 @@
+import { FunctionAppResource } from "./functionApp";
+import { ServerlessAzureConfig } from "../../models/serverless";
+
+describe("Function App Resource", () => {
+  const resourceGroupName = "myResourceGroup";
+  const prefix = "prefix";
+  const region = "westus";
+  const stage = "prod";
+
+  it("generates the correct resource name", () => {
+    const config: ServerlessAzureConfig = {
+      provider: {
+        name: "azure",
+        prefix,
+        region,
+        stage,
+        resourceGroup: resourceGroupName,
+        runtime: "nodejs10.x"
+      },
+      service: ""
+    } as any;
+
+    expect(FunctionAppResource.getResourceName(config)).toEqual(
+      `${config.provider.prefix}-wus-${config.provider.stage}`
+    );
+  });
+
+  it("uses the specified name from the azure provider", () => {
+    const serviceName = "myapp";
+
+    const config: ServerlessAzureConfig = {
+      provider: {
+        apim: {
+          name: ""
+        },
+        name: "azure",
+        prefix,
+        region,
+        stage,
+        resourceGroup: resourceGroupName,
+        runtime: "nodejs10.x",
+        functionApp: {
+          name: serviceName,
+        },
+      },
+      service: serviceName
+    } as any;
+
+    expect(FunctionAppResource.getResourceName(config)).toEqual(serviceName);
+  });
+});
diff --git a/src/armTemplates/resources/hostingEnvironment.test.ts b/src/armTemplates/resources/hostingEnvironment.test.ts
new file mode 100644
index 0000000..b07ec60
--- /dev/null
+++ b/src/armTemplates/resources/hostingEnvironment.test.ts
@@ -0,0 +1,55 @@
+import { HostingEnvironmentResource } from "./hostingEnvironment";
+import { ServerlessAzureConfig } from "../../models/serverless";
+import md5 from "md5";
+import configConstants from "../../config";
+
+describe("Azure Hosting Environment Resource", () => {
+  const resourceGroupName = "myResourceGroup";
+  const prefix = "prefix";
+  const region = "eastus2";
+  const stage = "prod";
+
+  it("generates the correct resource name", () => {
+    const resourceGroupHash = md5(resourceGroupName).substr(
+      0,
+      configConstants.resourceGroupHashLength
+    );
+
+    const config: ServerlessAzureConfig = {
+      provider: {
+        name: "azure",
+        prefix,
+        region,
+        stage,
+        resourceGroup: resourceGroupName,
+        runtime: "nodejs10.x"
+      },
+      service: ""
+    } as any;
+
+    expect(HostingEnvironmentResource.getResourceName(config)).toEqual(
+      `${prefix}-eus2-${stage}-${resourceGroupHash}-ase`
+    );
+  });
+
+  it("uses the specified name from the azure provider", () => {
+    const hostingEnvironmentName = "myHostingEnv";
+
+    const config: ServerlessAzureConfig = {
+      provider: {
+        hostingEnvironment: {
+          name: hostingEnvironmentName
+        },
+        name: "azure",
+        prefix,
+        region,
+        stage,
+        resourceGroup: resourceGroupName,
+        runtime: "nodejs10.x"
+      },
+      service: ""
+    } as any;
+
+    expect(HostingEnvironmentResource.getResourceName(config)).toEqual(hostingEnvironmentName);
+  });
+});
diff --git a/src/armTemplates/resources/storageAccount.test.ts b/src/armTemplates/resources/storageAccount.test.ts
index 7ee5cae..db24a7b 100644
--- a/src/armTemplates/resources/storageAccount.test.ts
+++ b/src/armTemplates/resources/storageAccount.test.ts
@@ -10,15 +10,18 @@ describe("Storage Account Resource", () => {
   const config: ServerlessAzureConfig = {
     functions: [],
     plugins: [],
+    package: null,
     provider: {
+      runtime: "nodejs10.x",
       prefix: "sls",
       name: "azure",
       region: "westus",
       stage: "dev",
-      resourceGroup
+      resourceGroup,
+      runtime: "nodejs10.x"
     },
     service: "test-api"
-  }
+  } as any;
 
   it("Generates safe storage account name with short parts", () => {
     const testConfig: ServerlessAzureConfig = {
diff --git a/src/armTemplates/resources/virtualNetwork.test.ts b/src/armTemplates/resources/virtualNetwork.test.ts
new file mode 100644
index 0000000..8863610
--- /dev/null
+++ b/src/armTemplates/resources/virtualNetwork.test.ts
@@ -0,0 +1,55 @@
+import { VirtualNetworkResource } from "./virtualNetwork";
+import { ServerlessAzureConfig } from "../../models/serverless";
+import md5 from "md5";
+import configConstants from "../../config";
+
+describe("Virtual Network Resource", () => {
+  const resourceGroupName = "myResourceGroup";
+  const prefix = "prefix";
+  const region = "eastus2";
+  const stage = "prod";
+
+  it("generates the correct resource name", () => {
+    const resourceGroupHash = md5(resourceGroupName).substr(
+      0,
+      configConstants.resourceGroupHashLength
+    );
+
+    const config: ServerlessAzureConfig = {
+      provider: {
+        name: "azure",
+        prefix,
+        region,
+        stage,
+        resourceGroup: resourceGroupName,
+        runtime: "nodejs10.x"
+      },
+      service: ""
+    } as any;
+
+    expect(VirtualNetworkResource.getResourceName(config)).toEqual(
+      `${prefix}-eus2-${stage}-${resourceGroupHash}-vnet`
+    );
+  });
+
+  it("uses the specified name from the azure provider", () => {
+    const virtualNetworkName = "myVNET";
+
+    const config: ServerlessAzureConfig = {
+      provider: {
+        hostingEnvironment: {
+          name: virtualNetworkName
+        },
+        name: "azure",
+        prefix,
+        region,
+        stage,
+        resourceGroup: resourceGroupName,
+        runtime: "nodejs10.x"
+      },
+      service: ""
+    } as any;
+
+    expect(VirtualNetworkResource.getResourceName(config)).toEqual(virtualNetworkName);
+  });
+});
diff --git a/src/index.test.ts b/src/index.test.ts
index 76e999c..c54e7ab 100644
--- a/src/index.test.ts
+++ b/src/index.test.ts
@@ -5,8 +5,7 @@ import { AzureRemovePlugin } from "./plugins/remove/azureRemovePlugin";
 import { AzurePackagePlugin } from "./plugins/package/azurePackagePlugin";
 import { AzureDeployPlugin } from "./plugins/deploy/azureDeployPlugin";
 import { AzureLoginPlugin } from "./plugins/login/azureLoginPlugin";
-import { AzureApimServicePlugin } from "./plugins/apim/azureApimServicePlugin";
-import { AzureApimFunctionPlugin } from "./plugins/apim/azureApimFunctionPlugin";
+import { AzureApimPlugin } from "./plugins/apim/azureApimPlugin";
 import AzureProvider from "./provider/azureProvider";
 import { AzureFuncPlugin } from "./plugins/func/azureFuncPlugin";
 import { AzureOfflinePlugin } from "./plugins/offline/azureOfflinePlugin";
@@ -30,8 +29,7 @@ describe("Azure Index", () => {
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureRemovePlugin);
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureLoginPlugin);
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureDeployPlugin);
-    expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureApimServicePlugin);
-    expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureApimFunctionPlugin);
+    expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureApimPlugin);
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureFuncPlugin);
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureOfflinePlugin);
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureRollbackPlugin);
diff --git a/src/plugins/apim/azureApimFunctionPlugin.test.ts b/src/plugins/apim/azureApimFunctionPlugin.test.ts
deleted file mode 100644
index 0e81528..0000000
--- a/src/plugins/apim/azureApimFunctionPlugin.test.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { MockFactory } from "../../test/mockFactory";
-import { invokeHook } from "../../test/utils";
-import { AzureApimFunctionPlugin } from "./azureApimFunctionPlugin";
-
-jest.mock("../../services/apimService");
-import { ApimService } from "../../services/apimService";
-
-describe("APIM Function Plugin", () => {
-  it("calls deploy function", async () => {
-    const deployFunction = jest.fn();
-
-    ApimService.prototype.deployFunction = deployFunction;
-
-    const sls = MockFactory.createTestServerless();
-    sls.service.provider["apim"] = "apim config"
-    const options = MockFactory.createTestServerlessOptions();
-    const plugin = new AzureApimFunctionPlugin(sls, options);
-
-    await invokeHook(plugin, "after:deploy:function:deploy");
-
-    expect(sls.cli.log).toBeCalledWith("Starting APIM function deployment")
-    expect(deployFunction).toBeCalled();
-    expect(sls.cli.log).lastCalledWith("Finished APIM function deployment")
-  });
-});
diff --git a/src/plugins/apim/azureApimServicePlugin.test.ts b/src/plugins/apim/azureApimPlugin.test.ts
similarity index 58%
rename from src/plugins/apim/azureApimServicePlugin.test.ts
rename to src/plugins/apim/azureApimPlugin.test.ts
index b748395..3ff6cec 100644
--- a/src/plugins/apim/azureApimServicePlugin.test.ts
+++ b/src/plugins/apim/azureApimPlugin.test.ts
@@ -1,14 +1,14 @@
 import Serverless from "serverless";
 import { MockFactory } from "../../test/mockFactory";
 import { invokeHook } from "../../test/utils";
-import { AzureApimServicePlugin } from "./azureApimServicePlugin";
+import { AzureApimPlugin } from "./azureApimPlugin";
 
 jest.mock("../../services/apimService");
 import { ApimService } from "../../services/apimService";
 
 describe("APIM Service Plugin", () => {
   it("is defined", () => {
-    expect(AzureApimServicePlugin).toBeDefined();
+    expect(AzureApimPlugin).toBeDefined();
   });
 
   it("can be instantiated", () => {
@@ -17,46 +17,40 @@ describe("APIM Service Plugin", () => {
       stage: "",
       region: "",
     }
-    const plugin = new AzureApimServicePlugin(serverless, options);
+    const plugin = new AzureApimPlugin(serverless, options);
 
     expect(plugin).not.toBeNull();
   });
 
-  it("calls deploy API and deploy functions", async () => {
-    const deployApi = jest.fn();
-    const deployFunctions = jest.fn();
+  it("calls APIM service deploy", async () => {
+    const deploy = jest.fn();
 
-    ApimService.prototype.deployApi = deployApi;
-    ApimService.prototype.deployFunctions = deployFunctions;
+    ApimService.prototype.deploy = deploy;
 
     const sls = MockFactory.createTestServerless();
     sls.service.provider["apim"] = "apim config"
     const options = MockFactory.createTestServerlessOptions();
-    const plugin = new AzureApimServicePlugin(sls, options);
+    const plugin = new AzureApimPlugin(sls, options);
 
     await invokeHook(plugin, "after:deploy:deploy");
 
     expect(sls.cli.log).toBeCalledWith("Starting APIM service deployment")
-    expect(deployApi).toBeCalled();
-    expect(deployFunctions).toBeCalled();
+    expect(deploy).toBeCalled();
     expect(sls.cli.log).lastCalledWith("Finished APIM service deployment")
   });
 
   it("does not call deploy API or deploy functions when \"apim\" not included in config", async () => {
-    const deployApi = jest.fn();
-    const deployFunctions = jest.fn();
+    const deploy = jest.fn();
 
-    ApimService.prototype.deployApi = deployApi;
-    ApimService.prototype.deployFunctions = deployFunctions;
+    ApimService.prototype.deploy = deploy;
 
     const sls = MockFactory.createTestServerless();
     const options = MockFactory.createTestServerlessOptions();
-    const plugin = new AzureApimServicePlugin(sls, options);
+    const plugin = new AzureApimPlugin(sls, options);
 
     await invokeHook(plugin, "after:deploy:deploy");
 
     expect(sls.cli.log).not.toBeCalled()
-    expect(deployApi).not.toBeCalled();
-    expect(deployFunctions).not.toBeCalled();
+    expect(deploy).not.toBeCalled();
   });
 });
diff --git a/src/plugins/deploy/azureDeployPlugin.test.ts b/src/plugins/deploy/azureDeployPlugin.test.ts
index 208670c..dbf2bc8 100644
--- a/src/plugins/deploy/azureDeployPlugin.test.ts
+++ b/src/plugins/deploy/azureDeployPlugin.test.ts
@@ -1,6 +1,6 @@
 import { Site } from "@azure/arm-appservice/esm/models";
 import Serverless from "serverless";
-import { ServerlessAzureOptions } from "../../models/serverless";
+import { ServerlessAzureOptions, ServerlessAzureConfig } from "../../models/serverless";
 import { MockFactory } from "../../test/mockFactory";
 import { invokeHook } from "../../test/utils";
 import { AzureDeployPlugin } from "./azureDeployPlugin";
@@ -11,6 +11,7 @@ import { FunctionAppService } from "../../services/functionAppService";
 
 jest.mock("../../services/resourceService");
 import { ResourceService } from "../../services/resourceService";
+import { ApimService } from "../../services/apimService";
 
 describe("Deploy plugin", () => {
   let sls: Serverless;
@@ -25,6 +26,7 @@ describe("Deploy plugin", () => {
 
   beforeEach(() => {
     FunctionAppService.prototype.getFunctionZipFile = jest.fn(() => "serviceName.zip");
+    ApimService.prototype.deploy = jest.fn();
 
     sls = MockFactory.createTestServerless();
     options = MockFactory.createTestServerlessOptions();
@@ -64,7 +66,7 @@ describe("Deploy plugin", () => {
         "Azure Functions are zipped up as a package and deployed together as a unit");
   });
 
-  it("lists deployments", async () => {
+  it("lists deployments from sub-command", async () => {
     const deploymentString = "deployments";
     ResourceService.prototype.listDeployments = jest.fn(() => Promise.resolve(deploymentString));
     await invokeHook(plugin, "deploy:list:list");
@@ -72,7 +74,21 @@ describe("Deploy plugin", () => {
     expect(sls.cli.log).lastCalledWith(deploymentString);
   });
 
-  it("Crashes deploy list if function is specified", async () => {
+  it("deploys APIM from sub-command if configured", async () => {
+    (sls.service as any as ServerlessAzureConfig).provider.apim = {} as any;
+    plugin = new AzureDeployPlugin(sls, {} as any);
+    await invokeHook(plugin, "deploy:apim:apim");
+    expect(ApimService.prototype.deploy).toBeCalled();
+  });
+
+  it("skips deployment of APIM from sub-command if not configured", async () => {
+    delete (sls.service as any as ServerlessAzureConfig).provider.apim;
+    plugin = new AzureDeployPlugin(sls, {} as any);
+    await invokeHook(plugin, "deploy:apim:apim");
+    expect(ApimService.prototype.deploy).not.toBeCalled();
+  });
+
+  it("crashes deploy list if function is specified", async () => {
     plugin = new AzureDeployPlugin(sls, { function: "myFunction" } as any);
     await expect(invokeHook(plugin, "deploy:list:list"))
       .rejects.toThrow("The Azure Functions plugin does not currently support deployments of individual functions. " +
diff --git a/src/plugins/login/azureLoginPlugin.test.ts b/src/plugins/login/azureLoginPlugin.test.ts
index 292b1c4..6de767f 100644
--- a/src/plugins/login/azureLoginPlugin.test.ts
+++ b/src/plugins/login/azureLoginPlugin.test.ts
@@ -63,14 +63,14 @@ describe("Login Plugin", () => {
     const sls = MockFactory.createTestServerless();
     await invokeLoginHook(false, sls);
     expect(AzureLoginService.prototype.servicePrincipalLogin).toBeCalledWith(
-      "azureServicePrincipalClientId",
-      "azureServicePrincipalPassword",
-      "azureServicePrincipalTenantId",
+      "AZURE_CLIENT_ID",
+      "AZURE_CLIENT_SECRET",
+      "AZURE_TENANT_ID",
       undefined // would be options
     )
     expect(AzureLoginService.prototype.interactiveLogin).not.toBeCalled();
     expect(JSON.stringify(sls.variables["azureCredentials"])).toEqual(JSON.stringify(credentials));
-    expect(sls.variables["subscriptionId"]).toEqual("azureSubId");
+    expect(sls.variables["subscriptionId"]).toEqual("AZURE_SUBSCRIPTION_ID");
   });
 
   it("calls interactive login if environment variables are not set", async () => {
@@ -80,7 +80,7 @@ describe("Login Plugin", () => {
     expect(AzureLoginService.prototype.servicePrincipalLogin).not.toBeCalled();
     expect(AzureLoginService.prototype.interactiveLogin).toBeCalled();
     expect(JSON.stringify(sls.variables["azureCredentials"])).toEqual(JSON.stringify(credentials));
-    expect(sls.variables["subscriptionId"]).toEqual("azureSubId");
+    expect(sls.variables["subscriptionId"]).toEqual("AZURE_SUBSCRIPTION_ID");
   });
 
   it("logs an error from authentication and crashes with it", async () => {
@@ -101,8 +101,8 @@ describe("Login Plugin", () => {
     const opt = MockFactory.createTestServerlessOptions();
     await invokeLoginHook(false, sls, opt);
     expect(AzureLoginService.prototype.interactiveLogin).toBeCalled();
-    expect(sls.variables["subscriptionId"]).toEqual("azureSubId");
-    expect(sls.cli.log).toBeCalledWith("Using subscription ID: azureSubId");
+    expect(sls.variables["subscriptionId"]).toEqual("AZURE_SUBSCRIPTION_ID");
+    expect(sls.cli.log).toBeCalledWith("Using subscription ID: AZURE_SUBSCRIPTION_ID");
   });
 
   it("Throws an error with empty subscription list", async () => {
diff --git a/src/services/apimService.test.ts b/src/services/apimService.test.ts
index 4711370..52665b8 100644
--- a/src/services/apimService.test.ts
+++ b/src/services/apimService.test.ts
@@ -10,7 +10,6 @@ import apimGetService200 from "../test/responses/apim-get-service-200.json";
 import apimGetApi200 from "../test/responses/apim-get-api-200.json";
 import apimGetApi404 from "../test/responses/apim-get-api-404.json";
 import { FunctionAppService } from "./functionAppService";
-import { Site } from "@azure/arm-appservice/esm/models";
 import {
   PropertyContract, BackendContract, BackendCreateOrUpdateResponse,
   ApiCreateOrUpdateResponse, PropertyCreateOrUpdateResponse, ApiContract,
@@ -20,12 +19,22 @@ import {
   ApiPolicyCreateOrUpdateResponse,
 } from "@azure/arm-apimanagement/esm/models";
 import { AzureNamingService } from "./namingService";
+import { ApiManagementConfig } from "../models/apiManagement";
 
 describe("APIM Service", () => {
-  const apimConfig = MockFactory.createTestApimConfig();
+  let apimConfig: ApiManagementConfig;
+
+  const functionApp = {
+    id: "/testapp1",
+    name: "Test Site",
+    location: "West US",
+    defaultHostName: "testsite.azurewebsites.net",
+  };
+
   let serverless: Serverless;
 
   beforeEach(() => {
+    apimConfig = MockFactory.createTestApimConfig();
     const slsConfig: any = {
       ...MockFactory.createTestService(MockFactory.createTestSlsFunctionConfig()),
       service: "test-sls",
@@ -114,7 +123,7 @@ describe("APIM Service", () => {
       axios.request = jest.fn((requestConfig) => MockFactory.createTestAxiosResponse(requestConfig, apimGetApi404, 404));
 
       const service = new ApimService(serverless);
-      const api = await service.getApi();
+      const api = await service.getApi("unknown");
       expect(api).toBeNull();
     });
 
@@ -122,12 +131,14 @@ describe("APIM Service", () => {
       serverless.service.provider["apim"] = null;
 
       const service = new ApimService(serverless);
-      const api = await service.getApi();
+      const api = await service.getApi("unknown");
 
       expect(api).toBeNull();
     });
 
     it("returns the API reference", async () => {
+      const defaultApi = apimConfig.apis[0];
+
       const expectedResponse = interpolateJson(apimGetApi200, {
         resourceGroup: {
           name: serverless.service.provider["resourceGroup"],
@@ -137,23 +148,23 @@ describe("APIM Service", () => {
           name: apimConfig.name,
         },
         resource: {
-          name: apimConfig.api.name,
-          displayName: apimConfig.api.displayName,
-          description: apimConfig.api.description,
-          path: apimConfig.api.path,
+          name: defaultApi.name,
+          displayName: defaultApi.displayName,
+          description: defaultApi.description,
+          path: defaultApi.path,
         },
       });
 
       axios.request = jest.fn((requestConfig) => MockFactory.createTestAxiosResponse(requestConfig, expectedResponse));
 
       const service = new ApimService(serverless);
-      const api = await service.getApi();
+      const api = await service.getApi(defaultApi.name);
 
       expect(api).not.toBeNull();
       expect(api).toMatchObject({
-        displayName: apimConfig.api.displayName,
-        description: apimConfig.api.description,
-        path: apimConfig.api.path,
+        displayName: defaultApi.displayName,
+        description: defaultApi.description,
+        path: defaultApi.path,
       });
     });
   });
@@ -165,7 +176,6 @@ describe("APIM Service", () => {
     let serviceName: string;
     let apiName: string;
     let backendName: string;
-    let functionApp: Site;
     let masterKey: string;
     let expectedApi: ApiContract;
     let expectedApiResult: ApiContract;
@@ -173,19 +183,13 @@ describe("APIM Service", () => {
     let expectedProperty: PropertyContract;
 
     beforeEach(() => {
-      backendConfig = apimConfig.backend || {} as BackendContract;
+      backendConfig = apimConfig.backends[0] || {} as BackendContract;
       resourceGroupName = serverless.service.provider["resourceGroup"];
       appName = serverless.service["service"];
       serviceName = apimConfig.name;
-      apiName = apimConfig.api.name;
-      backendName = backendConfig.name || appName;
+      apiName = apimConfig.apis[0].name;
+      backendName = backendConfig[0] ? backendConfig[0].name : `${appName}-backend`;
 
-      functionApp = {
-        id: "/testapp1",
-        name: "Test Site",
-        location: "West US",
-        defaultHostName: "testsite.azurewebsites.net",
-      };
       masterKey = "ABC123";
 
       FunctionAppService.prototype.get = jest.fn(() => Promise.resolve(functionApp));
@@ -193,22 +197,23 @@ describe("APIM Service", () => {
 
       expectedApi = {
         isCurrent: true,
-        subscriptionRequired: apimConfig.api.subscriptionRequired,
-        displayName: apimConfig.api.displayName,
-        description: apimConfig.api.description,
-        path: apimConfig.api.path,
-        protocols: apimConfig.api.protocols,
+        name: apimConfig.apis[0].name,
+        subscriptionRequired: apimConfig.apis[0].subscriptionRequired,
+        displayName: apimConfig.apis[0].displayName,
+        description: apimConfig.apis[0].description,
+        path: apimConfig.apis[0].path,
+        protocols: apimConfig.apis[0].protocols,
       };
 
       expectedApiResult = {
-        id: apimConfig.api.name,
-        name: apimConfig.api.name,
+        id: apimConfig.apis[0].name,
+        name: apimConfig.apis[0].name,
         isCurrent: true,
-        subscriptionRequired: apimConfig.api.subscriptionRequired,
-        displayName: apimConfig.api.displayName,
-        description: apimConfig.api.description,
-        path: apimConfig.api.path,
-        protocols: apimConfig.api.protocols,
+        subscriptionRequired: apimConfig.apis[0].subscriptionRequired,
+        displayName: apimConfig.apis[0].displayName,
+        description: apimConfig.apis[0].description,
+        path: apimConfig.apis[0].path,
+        protocols: apimConfig.apis[0].protocols,
       };
 
       expectedBackend = {
@@ -217,10 +222,11 @@ describe("APIM Service", () => {
             "x-functions-key": [`{{${serverless.service["service"]}-key}}`],
           },
         },
+        name: backendConfig[0] ? backendConfig[0].name : `${appName}-backend`,
         title: backendConfig.title || functionApp.name,
         tls: backendConfig.tls,
         proxy: backendConfig.proxy,
-        description: backendConfig.description,
+        description: backendConfig.description || "Function App Backend",
         protocol: backendConfig.protocol || "http",
         resourceId: `https://management.azure.com${functionApp.id}`,
         url: `https://${functionApp.defaultHostName}/api`,
@@ -243,9 +249,8 @@ describe("APIM Service", () => {
       ApiPolicy.prototype.createOrUpdate = jest.fn(() => Promise.resolve(null));
 
       const apimService = new ApimService(serverless);
-      const result = await apimService.deployApi();
 
-      expect(result).toMatchObject(expectedApiResult);
+      await expect(apimService.deploy()).resolves.not.toBeNull();
       expect(Api.prototype.createOrUpdate).toBeCalledWith(
         resourceGroupName,
         serviceName,
@@ -285,7 +290,7 @@ describe("APIM Service", () => {
       serverless.service.provider["apim"]["cors"] = corsPolicy;
 
       const apimService = new ApimService(serverless);
-      const result = await apimService.deployApi();
+      const result = await apimService.deploy();
 
       expect(result).not.toBeNull();
       expect(ApiPolicy.prototype.createOrUpdate).toBeCalledWith(
@@ -303,7 +308,7 @@ describe("APIM Service", () => {
       serverless.service.provider["apim"] = null;
 
       const service = new ApimService(serverless);
-      const api = await service.deployApi();
+      const api = await service.deploy();
 
       expect(api).toBeNull();
     });
@@ -313,7 +318,7 @@ describe("APIM Service", () => {
       Api.prototype.createOrUpdate = jest.fn(() => Promise.reject(apiError));
 
       const apimService = new ApimService(serverless);
-      await expect(apimService.deployApi()).rejects.toEqual(apiError);
+      await expect(apimService.deploy()).rejects.toEqual(apiError);
     });
 
     it("fails when Backend deployment fails", async () => {
@@ -324,7 +329,7 @@ describe("APIM Service", () => {
       Backend.prototype.createOrUpdate = jest.fn(() => Promise.reject(apiError));
 
       const apimService = new ApimService(serverless);
-      await expect(apimService.deployApi()).rejects.toEqual(apiError);
+      await expect(apimService.deploy()).rejects.toEqual(apiError);
     });
 
     it("fails when Property deployment fails", async () => {
@@ -337,7 +342,113 @@ describe("APIM Service", () => {
       Property.prototype.createOrUpdate = jest.fn(() => Promise.reject(apiError));
 
       const apimService = new ApimService(serverless);
-      await expect(apimService.deployApi()).rejects.toEqual(apiError);
+      await expect(apimService.deploy()).rejects.toEqual(apiError);
+    });
+
+    it("automatically creates API and Backend if not explicitly defined", async () => {
+      apimConfig.apis = [];
+      apimConfig.backends = [];
+
+      Api.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<ApiCreateOrUpdateResponse>(expectedApiResult, 201));
+      Backend.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<BackendCreateOrUpdateResponse>(expectedBackend, 201));
+      Property.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<PropertyCreateOrUpdateResponse>(expectedProperty, 201));
+      ApiPolicy.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<ApiPolicyCreateOrUpdateResponse>(expectedProperty, 201));
+
+      const apimService = new ApimService(serverless);
+      await apimService.deploy();
+
+      expect(Api.prototype.createOrUpdate).toBeCalled();
+      expect(Backend.prototype.createOrUpdate).toBeCalled();
+    });
+
+    it("creates multiple API's & Backends", async () => {
+      apimConfig.apis = MockFactory.createTestApimApis(3);
+      apimConfig.backends = MockFactory.createTestApimBackends(3);
+
+      Api.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<ApiCreateOrUpdateResponse>(expectedApiResult, 201));
+      Backend.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<BackendCreateOrUpdateResponse>(expectedBackend, 201));
+      Property.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<PropertyCreateOrUpdateResponse>(expectedProperty, 201));
+      ApiPolicy.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<ApiPolicyCreateOrUpdateResponse>(expectedProperty, 201));
+
+      const apimService = new ApimService(serverless);
+      await apimService.deploy();
+
+      expect(Api.prototype.createOrUpdate).toBeCalledTimes(apimConfig.apis.length);
+      expect(Backend.prototype.createOrUpdate).toBeCalledTimes(apimConfig.apis.length);
+    });
+
+    it("infers APIM operation configuration from HTTP binding", async () => {
+      const functions = MockFactory.createTestSlsFunctionConfig();
+      Object.assign(serverless.service, { functions });
+
+      let apimResource: ApiManagementServiceResource = {
+        name: apimConfig.name,
+        location: "West US",
+        gatewayUrl: "https://you.url.com",
+        publisherEmail: "someone@example.com",
+        publisherName: "Someone",
+        sku: {
+          capacity: 1,
+          name: "Consumption",
+        },
+      };
+
+      ApiManagementService.prototype.get =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<ApiManagementServiceGetResponse>(apimResource, 200));
+      Api.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<ApiCreateOrUpdateResponse>(expectedApiResult, 201));
+      Backend.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<BackendCreateOrUpdateResponse>(expectedBackend, 201));
+      Property.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<PropertyCreateOrUpdateResponse>(expectedProperty, 201));
+      ApiPolicy.prototype.createOrUpdate =
+        jest.fn(() => MockFactory.createTestArmSdkResponse<ApiPolicyCreateOrUpdateResponse>(expectedProperty, 201));
+      ApiOperation.prototype.createOrUpdate =
+        jest.fn((resourceGroup, serviceName, apiName, operationName, operationContract) => {
+          const response = MockFactory.createTestArmSdkResponse<ApiOperationCreateOrUpdateResponse>(operationContract, 201);
+          return Promise.resolve(response);
+        });
+
+      const apimService = new ApimService(serverless);
+      await apimService.deploy();
+
+      expect(ApiOperation.prototype.createOrUpdate).toBeCalledTimes(2)
+      expect(ApiOperation.prototype.createOrUpdate).toBeCalledWith(
+        resourceGroupName,
+        serviceName,
+        apiName,
+        "hello",
+        {
+          displayName: "hello",
+          description: "",
+          method: "get",
+          urlTemplate: "hello",
+          templateParameters: [],
+          responses: [],
+        }
+      );
+      expect(ApiOperation.prototype.createOrUpdate).toBeCalledWith(
+        resourceGroupName,
+        serviceName,
+        apiName,
+        "goodbye",
+        {
+          displayName: "goodbye",
+          description: "",
+          method: "get",
+          urlTemplate: "goodbye",
+          templateParameters: [],
+          responses: [],
+        }
+      );
     });
   });
 
@@ -349,9 +460,7 @@ describe("APIM Service", () => {
       const deploySpy = jest.spyOn(apimService, "deployFunction");
 
       const serviceResource: ApiManagementServiceResource = MockFactory.createTestApimService();
-      const api: ApiContract = MockFactory.createTestApimApi();
-
-      await apimService.deployFunctions(serviceResource, api);
+      await apimService.deployFunctions(functionApp, serviceResource);
 
       expect(deploySpy).not.toBeCalled();
     });
@@ -359,7 +468,7 @@ describe("APIM Service", () => {
     it("ensures all serverless functions have been deployed into specified API", async () => {
       const slsFunctions = _.values(serverless.service["functions"]);
 
-      const apimResource: ApiManagementServiceResource = {
+      let apimResource: ApiManagementServiceResource = {
         name: apimConfig.name,
         location: "West US",
         publisherEmail: "someone@example.com",
@@ -370,9 +479,9 @@ describe("APIM Service", () => {
         },
       };
 
-      const apiContract = apimConfig.api;
+      const apiContracts = apimConfig.apis;
       ApiManagementService.prototype.get = jest.fn(() => MockFactory.createTestArmSdkResponse<ApiManagementServiceGetResponse>(apimResource, 200));
-      Api.prototype.get = jest.fn(() => MockFactory.createTestArmSdkResponse<ApiGetResponse>(apiContract, 200));
+      Api.prototype.get = jest.fn(() => MockFactory.createTestArmSdkResponse<ApiGetResponse>(apiContracts[0], 200));
 
       ApiOperation.prototype.createOrUpdate =
         jest.fn((resourceGroup, serviceName, apiName, operationName, operationContract) => {
@@ -385,11 +494,12 @@ describe("APIM Service", () => {
       const deployFunctionSpy = jest.spyOn(ApimService.prototype, "deployFunction");
 
       const service = new ApimService(serverless);
-      const apimInstance = await service.get();
-      const api = await service.getApi();
+      apimResource = await service.get();
+      const api = await service.getApi(apimConfig.apis[0].name);
 
-      await service.deployFunctions(apimInstance, api);
+      await service.deploy();
 
+      expect(api).not.toBeNull();
       expect(deployFunctionSpy).toBeCalledTimes(slsFunctions.length);
 
       const createOperationCall = ApiOperation.prototype.createOrUpdate as jest.Mock;
diff --git a/src/services/baseService.test.ts b/src/services/baseService.test.ts
index 402ff1e..f37557a 100644
--- a/src/services/baseService.test.ts
+++ b/src/services/baseService.test.ts
@@ -242,7 +242,7 @@ describe("Base Service", () => {
   const configSubscriptionId = "config sub id";
 
   it("sets subscription ID from CLI", async () => {
-    process.env.azureSubId = envVarSubscriptionId;
+    process.env.AZURE_SUBSCRIPTION_ID = envVarSubscriptionId;
     serverless.service.provider["subscriptionId"] = configSubscriptionId;
     serverless.variables["subscriptionId"] = loginResultSubscriptionId
     service = new MockService(serverless, { subscriptionId: cliSubscriptionId } as any);
@@ -251,7 +251,7 @@ describe("Base Service", () => {
   });
 
   it("sets subscription ID from environment variable", async () => {
-    process.env.azureSubId = envVarSubscriptionId;
+    process.env.AZURE_SUBSCRIPTION_ID = envVarSubscriptionId;
     serverless.service.provider["subscriptionId"] = configSubscriptionId;
     serverless.variables["subscriptionId"] = loginResultSubscriptionId
     service = new MockService(serverless, { } as any);
@@ -260,7 +260,7 @@ describe("Base Service", () => {
   });
 
   it("sets subscription ID from config", async () => {
-    delete process.env.azureSubId;
+    delete process.env.AZURE_SUBSCRIPTION_ID;
     serverless.service.provider["subscriptionId"] = configSubscriptionId;
     serverless.variables["subscriptionId"] = loginResultSubscriptionId
     service = new MockService(serverless, { } as any);
@@ -269,7 +269,7 @@ describe("Base Service", () => {
   });
 
   it("sets subscription ID from login result", async () => {
-    delete process.env.azureSubId;
+    delete process.env.AZURE_SUBSCRIPTION_ID;
     serverless.variables["subscriptionId"] = loginResultSubscriptionId
     service = new MockService(serverless, { } as any);
     expect(service.getSubscriptionId()).toEqual(loginResultSubscriptionId);
diff --git a/src/services/loginService.test.ts b/src/services/loginService.test.ts
index 032d904..3259601 100644
--- a/src/services/loginService.test.ts
+++ b/src/services/loginService.test.ts
@@ -22,10 +22,10 @@ describe("Login Service", () => {
 
   it("logs in interactively with no cached login", async () => {
     // Ensure env variables are not set
-    delete process.env.azureSubId;
-    delete process.env.azureServicePrincipalClientId;
-    delete process.env.azureServicePrincipalPassword;
-    delete process.env.azureServicePrincipalTenantId;
+    delete process.env.AZURE_SUBSCRIPTION_ID;
+    delete process.env.AZURE_CLIENT_ID;
+    delete process.env.AZURE_CLIENT_SECRET;
+    delete process.env.AZURE_TENANT_ID;
 
     SimpleFileTokenCache.prototype.isEmpty = jest.fn(() => true);
 
@@ -44,10 +44,10 @@ describe("Login Service", () => {
 
   it("logs in with a cached login", async () => {
     // Ensure env variables are not set
-    delete process.env.azureSubId;
-    delete process.env.azureServicePrincipalClientId;
-    delete process.env.azureServicePrincipalPassword;
-    delete process.env.azureServicePrincipalTenantId;
+    delete process.env.AZURE_SUBSCRIPTION_ID;
+    delete process.env.AZURE_CLIENT_ID;
+    delete process.env.AZURE_CLIENT_SECRET;
+    delete process.env.AZURE_TENANT_ID;
 
     SimpleFileTokenCache.prototype.isEmpty = jest.fn(() => false);
     SimpleFileTokenCache.prototype.first = jest.fn(() => ({ userId: "" }));
@@ -60,16 +60,16 @@ describe("Login Service", () => {
 
   it("logs in with a service principal", async () => {
     // Set environment variables
-    process.env.azureSubId = "azureSubId";
-    process.env.azureServicePrincipalClientId = "azureServicePrincipalClientId";
-    process.env.azureServicePrincipalPassword = "azureServicePrincipalPassword";
-    process.env.azureServicePrincipalTenantId = "azureServicePrincipalTenantId";
+    process.env.AZURE_SUBSCRIPTION_ID = "AZURE_SUBSCRIPTION_ID";
+    process.env.AZURE_CLIENT_ID = "AZURE_CLIENT_ID";
+    process.env.AZURE_CLIENT_SECRET = "AZURE_CLIENT_SECRET";
+    process.env.AZURE_TENANT_ID = "AZURE_TENANT_ID";
 
     await loginService.login();
     expect(nodeauth.loginWithServicePrincipalSecretWithAuthResponse).toBeCalledWith(
-      "azureServicePrincipalClientId",
-      "azureServicePrincipalPassword",
-      "azureServicePrincipalTenantId",
+      "AZURE_CLIENT_ID",
+      "AZURE_CLIENT_SECRET",
+      "AZURE_TENANT_ID",
       undefined // would be options
     );
   });
diff --git a/src/services/namingService.test.ts b/src/services/namingService.test.ts
index 2e7cf7e..5c8a87b 100644
--- a/src/services/namingService.test.ts
+++ b/src/services/namingService.test.ts
@@ -1,9 +1,56 @@
-import { AzureNamingService } from "./namingService"
+import { AzureNamingService, AzureNamingServiceOptions } from "./namingService"
 import { ServerlessAzureConfig } from "../models/serverless";
+import md5 from "md5";
 
 describe("Naming Service", () => {
 
   const resourceGroup = "myResourceGroup";
+  const resourceGroupHash = md5(resourceGroup).substr(0, 6);
+  const defaultConfig: ServerlessAzureConfig = {
+    functions: [],
+    plugins: [],
+    provider: {
+      prefix: "sls",
+      name: "azure",
+      region: "westus",
+      stage: "dev",
+      resourceGroup,
+      runtime: "nodejs10.x"
+    },
+    service: "test-api",
+    package: {
+      artifact: "",
+      artifactDirectoryName: "",
+      individually: false,
+    }
+  };
+
+  it("Gets resource name with hash by default", () => {
+    const result = AzureNamingService.getResourceName({config: defaultConfig});
+
+    expect(result).toEqual(`${defaultConfig.provider.prefix}-wus-${defaultConfig.provider.stage}-${resourceGroupHash}`);
+  });
+
+  it("Gets resource name without hash when specified", () => {
+    const options: AzureNamingServiceOptions = {config: defaultConfig, includeHash: false};
+    const result = AzureNamingService.getResourceName(options);
+
+    expect(result).toEqual(`${defaultConfig.provider.prefix}-wus-${defaultConfig.provider.stage}`);
+  });
+
+  it("Gets resource name with suffix", () => {
+    const options: AzureNamingServiceOptions = {config: defaultConfig, suffix: "suf"};
+    const result = AzureNamingService.getResourceName(options);
+
+    expect(result).toEqual(`${defaultConfig.provider.prefix}-wus-${defaultConfig.provider.stage}-${resourceGroupHash}-suf`);
+  });
+
+  it("Uses resource name from config if specified", () => {
+    const options: AzureNamingServiceOptions = {config: defaultConfig, resourceConfig: {name: "test-resource"} as any};
+    const result = AzureNamingService.getResourceName(options);
+
+    expect(result).toEqual("test-resource");
+  });
 
   it("Creates a short name for an azure region", () => {
     const expected = "ausse";
@@ -91,36 +138,19 @@ describe("Naming Service", () => {
   });
 
   it("deployment name is generated correctly", () => {
-    const config: ServerlessAzureConfig = {
-      functions: [],
-      plugins: [],
-      provider: {
-        prefix: "sls",
-        name: "azure",
-        region: "westus",
-        stage: "dev",
-        resourceGroup,
-      },
-      service: "test-api"
-    };
-
     const timestamp = Date.now();
-    const deploymentName = AzureNamingService.getDeploymentName(config, `t${timestamp}`);
+    const deploymentName = AzureNamingService.getDeploymentName(defaultConfig, `t${timestamp}`);
 
     expect(deploymentName).toEqual(`slswusdevtestapi-DEPLOYMENT-t${timestamp}`);
-    assertValidDeploymentName(config, deploymentName, timestamp);
+    assertValidDeploymentName(defaultConfig, deploymentName, timestamp);
   });
 
   it("deployment name with long suffix or service name generated correctly", () => {
     const config: ServerlessAzureConfig = {
-      functions: [],
-      plugins: [],
+      ...defaultConfig,
       provider: {
-        prefix: "sls-long-prefix-name",
-        name: "azure",
-        region: "westus",
-        stage: "multicloud",
-        resourceGroup,
+        ...defaultConfig.provider,
+        prefix: "sls-long-prefix-name"
       },
       service: "extra-long-service-name"
     };
@@ -128,7 +158,7 @@ describe("Naming Service", () => {
     const timestamp = Date.now();
     const deploymentName = AzureNamingService.getDeploymentName(config, `t${timestamp}`);
 
-    expect(deploymentName).toEqual(`slswusmulext-DEPLOYMENT-t${timestamp}`);
+    expect(deploymentName).toEqual(`slswusdevext-DEPLOYMENT-t${timestamp}`);
     assertValidDeploymentName(config, deploymentName, timestamp);
   });
 
diff --git a/src/shared/binding.test.ts b/src/shared/binding.test.ts
index 870385d..6ff6930 100644
--- a/src/shared/binding.test.ts
+++ b/src/shared/binding.test.ts
@@ -21,4 +21,51 @@ describe("Bindings", () => {
     BindingUtils.getBindingsMetaData(sls);
     expect(sls.cli.log).toBeCalledWith("Parsing Azure Functions Bindings.json...");
   });
+
+  it("Http output bindings should default to 'res'", () => {
+    const binding = BindingUtils.getHttpOutBinding();
+
+    expect(binding).toMatchObject({
+      type: "http",
+      direction: "out",
+      name: "res"
+    });
+  });
+
+  it("Gets the http binding with default settings", () => {
+    const serverless = MockFactory.createTestServerless();
+    const parsedBindings = BindingUtils.getBindingsMetaData(serverless);
+    const bindingType = "http";
+
+    const bindingTypes = parsedBindings.bindingTypes;
+    const bindingTypeIndex = bindingTypes.indexOf(bindingType);
+    const bindingSettings = parsedBindings.bindingSettings[bindingTypeIndex];
+
+    const binding = BindingUtils.getBinding(bindingType, bindingSettings, {});
+
+    expect(binding).toMatchObject({
+      type: "http",
+      direction: "out",
+      name: "res",
+    });
+  });
+
+  it("Gets the http binding with custom name", () => {
+    const serverless = MockFactory.createTestServerless();
+    const parsedBindings = BindingUtils.getBindingsMetaData(serverless);
+    const bindingType = "http";
+    const userSettings = { name: "custom" };
+
+    const bindingTypes = parsedBindings.bindingTypes;
+    const bindingTypeIndex = bindingTypes.indexOf(bindingType);
+    const bindingSettings = parsedBindings.bindingSettings[bindingTypeIndex];
+
+    const binding = BindingUtils.getBinding(bindingType, bindingSettings, userSettings);
+
+    expect(binding).toMatchObject({
+      type: "http",
+      direction: "out",
+      name: userSettings.name,
+    });
+  });
 });
diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts
index c02d327..da9771e 100644
--- a/src/test/mockFactory.ts
+++ b/src/test/mockFactory.ts
@@ -1,4 +1,4 @@
-import { ApiContract, ApiManagementServiceResource } from "@azure/arm-apimanagement/esm/models";
+import { ApiContract, ApiManagementServiceResource, BackendContract } from "@azure/arm-apimanagement/esm/models";
 import { FunctionEnvelope, Site } from "@azure/arm-appservice/esm/models";
 import { DeploymentExtended, DeploymentsListByResourceGroupResponse } from "@azure/arm-resources/esm/models";
 import { HttpHeaders, HttpOperationResponse, HttpResponse, WebResource } from "@azure/ms-rest-js";
@@ -36,7 +36,7 @@ export class MockFactory {
     sls.config.servicePath = "";
     sls.setProvider = jest.fn();
     sls["processedInput"] = {
-      commands: [ ServerlessCliCommand.DEPLOY ],
+      commands: [ServerlessCliCommand.DEPLOY],
       options: {}
     };
     return sls;
@@ -104,7 +104,7 @@ export class MockFactory {
         .azureCredentials as any as TokenCredentialsBase,
       subscriptions: [
         {
-          id: "azureSubId",
+          id: "AZURE_SUBSCRIPTION_ID",
         },
       ] as any as LinkedSubscription[],
     };
@@ -184,13 +184,14 @@ export class MockFactory {
   }
 
   public static createTestParameters(wrap = true) {
-    return (wrap) ? {
-      param1: { value: "1", type: "String" },
-      param2: { value: "2", type: "String" },
-    } : {
-      param1: "1",
-      param2: "2",
-    }
+    return (wrap)
+      ? {
+        param1: { value: "1", type: "String" },
+        param2: { value: "2", type: "String" },
+      } : {
+        param1: "1",
+        param2: "2",
+      }
   }
 
   public static createTestDeployment(name?: string, second: number = 0): DeploymentExtended {
@@ -298,14 +299,15 @@ export class MockFactory {
   public static createTestApimConfig(generateName: boolean = false): ApiManagementConfig {
     return {
       name: generateName ? null : "test-apim-resource",
-      api: {
+      apis: [{
         name: "test-apim-api1",
         subscriptionRequired: false,
         displayName: "API 1",
         description: "description of api 1",
         protocols: ["https"],
         path: "test-api1",
-      },
+      }],
+      backends: [],
     };
   }
 
@@ -378,17 +380,17 @@ export class MockFactory {
 
   public static createTestServicePrincipalEnvVariables(): ServicePrincipalEnvVariables {
     return {
-      azureSubId: "azureSubId",
-      azureServicePrincipalClientId: "azureServicePrincipalClientId",
-      azureServicePrincipalPassword: "azureServicePrincipalPassword",
-      azureServicePrincipalTenantId: "azureServicePrincipalTenantId",
+      AZURE_SUBSCRIPTION_ID: "AZURE_SUBSCRIPTION_ID",
+      AZURE_CLIENT_ID: "AZURE_CLIENT_ID",
+      AZURE_CLIENT_SECRET: "AZURE_CLIENT_SECRET",
+      AZURE_TENANT_ID: "AZURE_TENANT_ID",
     }
   }
 
   public static createTestVariables() {
     return {
       azureCredentials: this.createTestAzureCredentials(),
-      subscriptionId: "azureSubId",
+      subscriptionId: "AZURE_SUBSCRIPTION_ID",
     }
   }
 
@@ -521,10 +523,42 @@ export class MockFactory {
     };
   }
 
-  public static createTestApimApi(): ApiContract {
+  public static createTestApimApis(count: number): ApiContract[] {
+    const apis: ApiContract[] = [];
+    for (let i = 1; i <= count; i++) {
+      const api = MockFactory.createTestApimApi(i);
+      apis.push(api);
+    }
+
+    return apis;
+  }
+
+  public static createTestApimApi(index: number = 1): ApiContract {
+    return {
+      displayName: `API ${index}`,
+      description: `Description for API ${index}`,
+      name: `Api${index}`,
+      path: `/api${index}`,
+    };
+  }
+
+  public static createTestApimBackends(count: number): BackendContract[] {
+    const backends: BackendContract[] = [];
+    for (let i = 1; i <= count; i++) {
+      const backend = MockFactory.createTestApimBackend(i);
+      backends.push(backend);
+    }
+
+    return backends;
+  }
+
+  public static createTestApimBackend(index: number = 1): BackendContract {
     return {
-      name: "Api1",
-      path: "/api1",
+      name: `backend-${index}`,
+      description: `Description for Backend ${index}`,
+      title: `Backend ${index}`,
+      url: `/backend${index}`,
+      protocol: "http",
     };
   }