Repository: serverless/serverless-azure-functions
Pull request #189 (2019-07-03 18:19:29)

feat: add support for specifying resourceGroup in both cli and yaml
when a name is specified in cli or yaml, serverless will use this name to create the resourceGroup instead of creating a name from convention. 

all underlying resources will still be created with a convention name.



Patch:
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..ab561f3
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/package-lock.json b/package-lock.json
index e4fb69a..ddb886e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1794,6 +1794,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",
@@ -1966,6 +1967,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",
@@ -2435,7 +2437,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",
@@ -2540,7 +2543,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",
@@ -2569,6 +2573,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",
@@ -2605,6 +2610,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"
@@ -2701,7 +2707,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",
@@ -2883,6 +2890,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"
@@ -3260,6 +3268,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",
@@ -3272,6 +3281,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",
@@ -3730,6 +3740,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",
@@ -3791,6 +3802,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"
       }
@@ -4170,6 +4182,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"
@@ -4184,6 +4197,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"
@@ -4713,7 +4727,8 @@
         },
         "ansi-regex": {
           "version": "2.1.1",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "aproba": {
           "version": "1.2.0",
@@ -4731,11 +4746,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"
@@ -4748,15 +4765,18 @@
         },
         "code-point-at": {
           "version": "1.1.0",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "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",
@@ -4859,7 +4879,8 @@
         },
         "inherits": {
           "version": "2.0.3",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "ini": {
           "version": "1.3.5",
@@ -4869,6 +4890,7 @@
         "is-fullwidth-code-point": {
           "version": "1.0.0",
           "bundled": true,
+          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -4881,17 +4903,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"
@@ -4908,6 +4933,7 @@
         "mkdirp": {
           "version": "0.5.1",
           "bundled": true,
+          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -4980,7 +5006,8 @@
         },
         "number-is-nan": {
           "version": "1.0.1",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -4990,6 +5017,7 @@
         "once": {
           "version": "1.4.0",
           "bundled": true,
+          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -5065,7 +5093,8 @@
         },
         "safe-buffer": {
           "version": "5.1.2",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -5095,6 +5124,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",
@@ -5112,6 +5142,7 @@
         "strip-ansi": {
           "version": "3.0.1",
           "bundled": true,
+          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -5150,11 +5181,13 @@
         },
         "wrappy": {
           "version": "1.0.2",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "yallist": {
           "version": "3.0.3",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         }
       }
     },
@@ -5462,6 +5495,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"
@@ -5471,6 +5505,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"
@@ -5480,6 +5515,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",
@@ -5798,7 +5834,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",
@@ -7438,7 +7475,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",
@@ -7529,6 +7567,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",
@@ -7554,6 +7593,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"
@@ -7644,12 +7684,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",
@@ -8247,6 +8289,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",
@@ -8355,6 +8398,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",
@@ -8557,7 +8601,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",
@@ -8624,6 +8669,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"
       }
@@ -9036,6 +9082,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"
@@ -9425,6 +9472,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"
@@ -9620,7 +9668,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",
@@ -10177,7 +10226,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",
@@ -11027,6 +11077,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"
@@ -11035,7 +11086,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/src/models/serverless.ts b/src/models/serverless.ts
index f50d024..bdf0dba 100644
--- a/src/models/serverless.ts
+++ b/src/models/serverless.ts
@@ -1,4 +1,5 @@
 import { ApiManagementConfig } from "./apiManagement";
+import Serverless from "serverless";
 
 export interface ArmTemplateConfig {
   file: string;
@@ -52,3 +53,7 @@ export interface ServerlessAzureConfig {
   plugins: string[];
   functions: any;
 }
+
+export interface ServerlessAzureOptions extends Serverless.Options {
+  resourceGroup?: string;
+}
diff --git a/src/plugins/deploy/azureDeployPlugin.ts b/src/plugins/deploy/azureDeployPlugin.ts
index 40362ed..88e253c 100644
--- a/src/plugins/deploy/azureDeployPlugin.ts
+++ b/src/plugins/deploy/azureDeployPlugin.ts
@@ -21,6 +21,12 @@ export class AzureDeployPlugin {
               "list"
             ]
           }
+        },
+        options: {
+          "resourceGroup": {
+            usage: "Resource group for the service",
+            shortcut: "g",
+          }
         }
       }
     }
@@ -48,12 +54,11 @@ export class AzureDeployPlugin {
 
   private async deploy() {
     const resourceService = new ResourceService(this.serverless, this.options);
-
     await resourceService.deployResourceGroup();
 
     const functionAppService = new FunctionAppService(this.serverless, this.options);
-
     const functionApp = await functionAppService.deploy();
+
     await functionAppService.uploadFunctions(functionApp);
   }
 }
diff --git a/src/services/armService.ts b/src/services/armService.ts
index 90feef5..df1afe8 100644
--- a/src/services/armService.ts
+++ b/src/services/armService.ts
@@ -3,7 +3,7 @@ import { Deployment, DeploymentExtended } from "@azure/arm-resources/esm/models"
 import { BaseService } from "./baseService";
 import { ResourceManagementClient } from "@azure/arm-resources";
 import { Guard } from "../shared/guard";
-import { ServerlessAzureConfig, ArmTemplateConfig } from "../models/serverless";
+import { ServerlessAzureConfig, ArmTemplateConfig, ServerlessAzureOptions } from "../models/serverless";
 import { ArmDeployment, ArmResourceTemplateGenerator, ArmTemplateType } from "../models/armTemplates";
 import fs from "fs";
 import path from "path";
@@ -12,8 +12,8 @@ import jsonpath from "jsonpath";
 export class ArmService extends BaseService {
   private resourceClient: ResourceManagementClient;
 
-  public constructor(serverless: Serverless) {
-    super(serverless);
+  public constructor(serverless: Serverless, options: ServerlessAzureOptions) {
+    super(serverless, options);
     this.resourceClient = new ResourceManagementClient(this.credentials, this.subscriptionId);
   }
 
@@ -149,4 +149,4 @@ export class ArmService extends BaseService {
       });
     }
   }
-}
\ No newline at end of file
+}
diff --git a/src/services/baseService.ts b/src/services/baseService.ts
index b99a5ce..1d53d74 100644
--- a/src/services/baseService.ts
+++ b/src/services/baseService.ts
@@ -2,6 +2,7 @@ import axios from "axios";
 import fs from "fs";
 import request from "request";
 import Serverless from "serverless";
+import { ServerlessAzureOptions } from "../models/serverless";
 import { StorageAccountResource } from "../armTemplates/resources/storageAccount";
 import { configConstants } from "../config";
 import { DeploymentConfig, ServerlessAzureConfig } from "../models/serverless";
@@ -21,11 +22,10 @@ export abstract class BaseService {
 
   protected constructor(
     protected serverless: Serverless,
-    protected options: Serverless.Options = { stage: null, region: null },
+    protected options: ServerlessAzureOptions = { stage: null, region: null },
     authenticate: boolean = true,
   ) {
     Guard.null(serverless);
-
     this.setDefaultValues();
 
     this.baseUrl = "https://management.azure.com";
@@ -44,17 +44,23 @@ export abstract class BaseService {
   }
 
   public getRegion(): string {
-    return this.options.region || this.serverless.service.provider.region;
+    return this.options.region || this.config.provider.region;
   }
-  
+
   public getStage(): string {
-    return this.options.stage || this.serverless.service.provider.stage;
+    return this.options.stage || this.config.provider.stage;
+  }
+
+  public getPrefix(): string {
+    return this.config.provider.prefix;
   }
 
   public getResourceGroupName(): string {
-    return this.options["resourceGroup"]
-      || this.serverless.service.provider["resourceGroup"]
-      || `${this.config.provider.prefix}-${this.getRegion()}-${this.getStage()}-${this.serviceName}-rg`;
+    const name = this.options.resourceGroup
+      || this.config.provider["resourceGroup"]
+      || `${this.getPrefix()}-${this.getRegion()}-${this.getStage()}-${this.serviceName}-rg`;
+
+    return name;
   }
 
   public getDeploymentConfig(): DeploymentConfig {
@@ -67,7 +73,7 @@ export abstract class BaseService {
   }
 
   public getDeploymentName(): string {
-    const name = this.serverless.service.provider["deploymentName"] || `${this.resourceGroup}-deployment`
+    const name = this.config.provider["deploymentName"] || `${this.resourceGroup}-deployment`
     return this.rollbackConfiguredName(name);
   }
 
@@ -78,7 +84,7 @@ export abstract class BaseService {
   /**
    * Get the access token from credentials token cache
    */
-  protected getAccessToken(): string{
+  protected getAccessToken(): string {
     return (this.credentials.tokenCache as any)._entries[0].accessToken;
   }
 
diff --git a/src/services/functionAppService.ts b/src/services/functionAppService.ts
index ab5e69b..c29b8da 100644
--- a/src/services/functionAppService.ts
+++ b/src/services/functionAppService.ts
@@ -18,6 +18,7 @@ export class FunctionAppService extends BaseService {
 
   public constructor(serverless: Serverless, options: Serverless.Options) {
     super(serverless, options);
+
     this.webClient = new WebSiteManagementClient(this.credentials, this.subscriptionId);
     this.blobService = new AzureBlobStorageService(serverless, options);
     this.functionZipFile = this.getFunctionZipFile();
@@ -114,7 +115,7 @@ export class FunctionAppService extends BaseService {
   public async uploadFunctions(functionApp: Site): Promise<any> {
     Guard.null(functionApp, "functionApp");
 
-    this.log("Deploying serverless functions...");    
+    this.log("Deploying serverless functions...");
 
     await this.uploadZippedArfifactToFunctionApp(functionApp);
     await this.uploadZippedArtifactToBlobStorage();
@@ -127,14 +128,14 @@ export class FunctionAppService extends BaseService {
   public async deploy() {
     this.log(`Creating function app: ${this.serviceName}`);
 
-    const armService = new ArmService(this.serverless);
+    const armService = new ArmService(this.serverless, this.options);
     let deployment: ArmDeployment = this.config.provider.armTemplate
       ? await armService.createDeploymentFromConfig(this.config.provider.armTemplate)
       : await armService.createDeploymentFromType(this.config.provider.type || "consumption");
 
     await armService.deployTemplate(deployment);
 
-    // Return function app 
+    // Return function app
     return await this.get();
   }
 
@@ -162,7 +163,7 @@ export class FunctionAppService extends BaseService {
     };
 
     await this.sendFile(requestOptions, this.functionZipFile);
-    
+
     this.log("-> Function package uploaded successfully");
     const serverlessFunctions = this.serverless.service.getAllFunctions();
     const deployedFunctions = await this.listFunctions(functionApp);
@@ -182,7 +183,7 @@ export class FunctionAppService extends BaseService {
   }
 
   /**
-   * Uploads artifact file to blob storage container 
+   * Uploads artifact file to blob storage container
    */
   private async uploadZippedArtifactToBlobStorage() {
     await this.blobService.initialize();


Test patch:
diff --git a/src/services/armService.test.ts b/src/services/armService.test.ts
index 5ff5cac..825599e 100644
--- a/src/services/armService.test.ts
+++ b/src/services/armService.test.ts
@@ -2,7 +2,7 @@ import Serverless from "serverless";
 import { MockFactory } from "../test/mockFactory";
 import { ArmService } from "./armService";
 import { ArmResourceTemplate, ArmTemplateType } from "../models/armTemplates";
-import { ArmTemplateConfig } from "../models/serverless";
+import { ArmTemplateConfig, ServerlessAzureOptions } from "../models/serverless";
 import mockFs from "mock-fs";
 import jsonpath from "jsonpath";
 import { Deployments } from "@azure/arm-resources";
@@ -11,9 +11,10 @@ import { Deployment } from "@azure/arm-resources/esm/models";
 describe("Arm Service", () => {
   let sls: Serverless
   let service: ArmService;
+  let options: ServerlessAzureOptions;
 
   function createService() {
-    return new ArmService(sls);
+    return new ArmService(sls, options);
   }
 
   beforeEach(() => {
@@ -197,4 +198,4 @@ describe("Arm Service", () => {
       expect(call[2]).toEqual(expectedDeployment);
     });
   });
-});
\ No newline at end of file
+});
diff --git a/src/services/baseService.test.ts b/src/services/baseService.test.ts
index 8f637e1..06e2de2 100644
--- a/src/services/baseService.test.ts
+++ b/src/services/baseService.test.ts
@@ -7,9 +7,10 @@ jest.mock("request", () => MockFactory.createTestMockRequestFactory());
 import request from "request";
 import fs from "fs";
 import { BaseService } from "./baseService";
+import { ServerlessAzureOptions } from "../models/serverless";
 
 class MockService extends BaseService {
-  public constructor(serverless: Serverless, options?: Serverless.Options) {
+  public constructor(serverless: Serverless, options?: ServerlessAzureOptions) {
     super(serverless, options);
   }
 
@@ -84,7 +85,7 @@ describe("Base Service", () => {
   it("returns region and stage based on CLI options", () => {
     const cliOptions = {
       stage: "prod",
-      region: "eastus2"
+      region: "eastus2",
     };
     const mockService = new MockService(sls, cliOptions);
 
@@ -92,40 +93,52 @@ describe("Base Service", () => {
     expect(mockService.getStage()).toEqual(cliOptions.stage);
   });
 
-  it("Sets default region and stage values if not defined", () => {
-    const mockService = new MockService(sls);
-
-    expect(mockService).not.toBeNull();
-    expect(sls.service.provider.region).toEqual("westus");
-    expect(sls.service.provider.stage).toEqual("dev");
-  });
-
-  it("returns region and stage based on CLI options", () => {
+  it("use the resource group name specified in CLI", () => {
+    const resourceGroupName = "cliResourceGroupName"
     const cliOptions = {
       stage: "prod",
-      region: "eastus2"
+      region: "eastus2",
+      resourceGroup: resourceGroupName
     };
+
     const mockService = new MockService(sls, cliOptions);
+    const actualResourceGroupName = mockService.getResourceGroupName();
 
-    expect(mockService.getRegion()).toEqual(cliOptions.region);
-    expect(mockService.getStage()).toEqual(cliOptions.stage);
+    expect(actualResourceGroupName).toEqual(resourceGroupName);
   });
 
-  it("Generates resource group name from sls yaml config", () => {
+  it("use the resource group name from sls yaml config", () => {
     const mockService = new MockService(sls);
-    const resourceGroupName = mockService.getResourceGroupName();
+    const actualResourceGroupName = mockService.getResourceGroupName();
 
-    expect(resourceGroupName).toEqual(sls.service.provider["resourceGroup"]);
+    expect(actualResourceGroupName).toEqual(sls.service.provider["resourceGroup"]);
   });
 
   it("Generates resource group from convention when NOT defined in sls yaml", () => {
     sls.service.provider["resourceGroup"] = null;
     const mockService = new MockService(sls);
-    const resourceGroupName = mockService.getResourceGroupName();
+    const actualResourceGroupName = mockService.getResourceGroupName();
+
     const region = mockService.getRegion();
     const stage = mockService.getStage();
+    const expectedResourceGroupName = `sls-${region}-${stage}-${sls.service["service"]}-rg`;
+
+    expect(actualResourceGroupName).toEqual(expectedResourceGroupName);
+  });
+
+  it("set default prefix when one is not defined in yaml config", () => {
+    const mockService = new MockService(sls);
+    const actualPrefix = mockService.getPrefix();
+    expect(actualPrefix).toEqual("sls");
+  });
+
+  it("use the prefix defined in sls yaml config", () => {
+    const expectedPrefix = "testPrefix"
+    sls.service.provider["prefix"] = expectedPrefix;
+    const mockService = new MockService(sls);
+    const actualPrefix = mockService.getPrefix();
 
-    expect(resourceGroupName).toEqual(`sls-${region}-${stage}-${sls.service["service"]}-rg`);
+    expect(actualPrefix).toEqual(expectedPrefix);
   });
 
   it("Fails if credentials have not been set in serverless config", () => {
@@ -183,4 +196,4 @@ describe("Base Service", () => {
 
     readStreamSpy.mockRestore();
   });
-});
\ No newline at end of file
+});