Repository: serverless/serverless-azure-functions
Pull request #283 (2019-08-29 15:58:47)

feat: Wait longer for deploying a function app
Many times, especially when deploying a service for the first time, there is a considerable amount of time before the function app is ready and provisioned. In my experimenting, every single time, the app has failed before finishing. This increases the amount of time between each retry, and tells the user which number of retry they are on in the log


Patch:
diff --git a/package-lock.json b/package-lock.json
index ec6deae..ff7fc40 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "serverless-azure-functions",
-  "version": "1.0.0-22",
+  "version": "1.0.0-28",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -5019,6 +5019,17 @@
         "flatted": "^2.0.0",
         "rimraf": "2.6.3",
         "write": "1.0.3"
+      },
+      "dependencies": {
+        "rimraf": {
+          "version": "2.6.3",
+          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+          "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+          "dev": true,
+          "requires": {
+            "glob": "^7.1.3"
+          }
+        }
       }
     },
     "flatted": {
@@ -5086,17 +5097,6 @@
       "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
       "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
     },
-    "fs-extra": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
-      "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
-      "optional": true,
-      "requires": {
-        "graceful-fs": "^4.1.2",
-        "jsonfile": "^4.0.0",
-        "universalify": "^0.1.0"
-      }
-    },
     "fs.realpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -5119,7 +5119,8 @@
         },
         "ansi-regex": {
           "version": "2.1.1",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "aproba": {
           "version": "1.2.0",
@@ -5154,7 +5155,8 @@
         },
         "code-point-at": {
           "version": "1.1.0",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "concat-map": {
           "version": "0.0.1",
@@ -5162,7 +5164,8 @@
         },
         "console-control-strings": {
           "version": "1.1.0",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -5265,7 +5268,8 @@
         },
         "inherits": {
           "version": "2.0.3",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "ini": {
           "version": "1.3.5",
@@ -5275,6 +5279,7 @@
         "is-fullwidth-code-point": {
           "version": "1.0.0",
           "bundled": true,
+          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -5298,6 +5303,7 @@
         "minipass": {
           "version": "2.3.5",
           "bundled": true,
+          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.2",
             "yallist": "^3.0.0"
@@ -5314,6 +5320,7 @@
         "mkdirp": {
           "version": "0.5.1",
           "bundled": true,
+          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -5396,6 +5403,7 @@
         "once": {
           "version": "1.4.0",
           "bundled": true,
+          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -5471,7 +5479,8 @@
         },
         "safe-buffer": {
           "version": "5.1.2",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -5501,6 +5510,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",
@@ -5518,6 +5528,7 @@
         "strip-ansi": {
           "version": "3.0.1",
           "bundled": true,
+          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -5556,11 +5567,13 @@
         },
         "wrappy": {
           "version": "1.0.2",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         },
         "yallist": {
           "version": "3.0.3",
-          "bundled": true
+          "bundled": true,
+          "optional": true
         }
       }
     },
@@ -9501,9 +9514,9 @@
       }
     },
     "rimraf": {
-      "version": "2.6.3",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
-      "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+      "version": "2.7.1",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+      "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
       "requires": {
         "glob": "^7.1.3"
       }
@@ -9875,6 +9888,17 @@
         "ts-node": "^3.2.0"
       },
       "dependencies": {
+        "fs-extra": {
+          "version": "4.0.3",
+          "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz",
+          "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==",
+          "optional": true,
+          "requires": {
+            "graceful-fs": "^4.1.2",
+            "jsonfile": "^4.0.0",
+            "universalify": "^0.1.0"
+          }
+        },
         "semver": {
           "version": "5.7.1",
           "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
diff --git a/package.json b/package.json
index a8c93a8..1ae7a86 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "serverless-azure-functions",
-  "version": "1.0.0-22",
+  "version": "1.0.0-28",
   "description": "Provider plugin for the Serverless Framework v1.x which adds support for Azure Functions.",
   "license": "MIT",
   "main": "./lib/index.js",
@@ -55,7 +55,7 @@
     "md5": "^2.2.1",
     "open": "^6.3.0",
     "request": "^2.81.0",
-    "rimraf": "^2.6.3",
+    "rimraf": "^2.7.1",
     "semver": "^6.3.0",
     "xml": "^1.0.1"
   },
diff --git a/src/armTemplates/resources/appServicePlan.ts b/src/armTemplates/resources/appServicePlan.ts
index 50d99e6..d38531e 100644
--- a/src/armTemplates/resources/appServicePlan.ts
+++ b/src/armTemplates/resources/appServicePlan.ts
@@ -4,7 +4,7 @@ import { AzureNamingService } from "../../services/namingService";
 
 export class AppServicePlanResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
-    return AzureNamingService.getResourceName(config, config.provider.appInsights, "asp");
+    return AzureNamingService.getResourceName(config, config.provider.appServicePlan, "asp");
   }
 
   public getTemplate(): ArmResourceTemplate {
diff --git a/src/armTemplates/resources/functionApp.ts b/src/armTemplates/resources/functionApp.ts
index eab4a9e..3b281ac 100644
--- a/src/armTemplates/resources/functionApp.ts
+++ b/src/armTemplates/resources/functionApp.ts
@@ -10,7 +10,7 @@ export class FunctionAppResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
     const safeServiceName = config.service.replace(/\s/g, "-");
 
-    return AzureNamingService.getResourceName(config, config.provider.appInsights, safeServiceName);
+    return AzureNamingService.getResourceName(config, config.provider.functionApp, safeServiceName);
   }
 
   public getTemplate(): ArmResourceTemplate {
@@ -123,26 +123,26 @@ export class FunctionAppResource implements ArmResourceTemplateGenerator {
     };
   }
 
-  private getRuntimeVersion(runtime: string): string{ 
-    if(!runtime) {
+  private getRuntimeVersion(runtime: string): string {
+    if (!runtime) {
       throw new Error("Runtime version not specified in serverless.yml");
     }
     const extractedVersion = runtime.split("nodejs")[1];
     const runtimeVersionsList = runtimeVersionsJson["nodejs"];
 
     //Searches for a specific version. For example nodejs10.6.0.
-    if(!extractedVersion.endsWith(".x")) {
+    if (!extractedVersion.endsWith(".x")) {
       let retrivedVersion: string;
-      for(const version of runtimeVersionsList){
-        retrivedVersion = version["version"]; 
-        if(extractedVersion === retrivedVersion && semver.valid(retrivedVersion)){
-          return retrivedVersion; 
+      for (const version of runtimeVersionsList) {
+        retrivedVersion = version["version"];
+        if (extractedVersion === retrivedVersion && semver.valid(retrivedVersion)) {
+          return retrivedVersion;
         }
       }
     }
     else {
       // User specified something like nodejs10.14.x
-      const extractedVersionNumber = extractedVersion.replace(/[^0-9\.]/g,"");
+      const extractedVersionNumber = extractedVersion.replace(/[^0-9\.]/g, "");
 
       const selectedVersions = runtimeVersionsList.filter(({ version }) => {
         return version.startsWith(extractedVersionNumber) && semver.valid(version)
@@ -154,5 +154,5 @@ export class FunctionAppResource implements ArmResourceTemplateGenerator {
       return selectedVersions.sort(semver.rcompare)[0]
     }
     throw new Error(`Could not find runtime version matching ${runtime}`)
-  }  
+  }
 }
diff --git a/src/index.ts b/src/index.ts
index 9d61b76..9e8ceb8 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -7,7 +7,6 @@ whole provider implementation.
 import Serverless from "serverless";
 import AzureProvider from "./provider/azureProvider";
 import { AzureInvokePlugin } from "./plugins/invoke/azureInvokePlugin";
-import { AzureLogsPlugin } from "./plugins/logs/azureLogsPlugin";
 import { AzureRemovePlugin } from "./plugins/remove/azureRemovePlugin";
 import { AzurePackagePlugin } from "./plugins/package/azurePackagePlugin";
 import { AzureDeployPlugin } from "./plugins/deploy/azureDeployPlugin";
@@ -26,7 +25,6 @@ export default class AzureIndex {
     // To be refactored
     this.serverless.pluginManager.addPlugin(AzurePackagePlugin);
     this.serverless.pluginManager.addPlugin(AzureInvokePlugin);
-    this.serverless.pluginManager.addPlugin(AzureLogsPlugin);
     this.serverless.pluginManager.addPlugin(AzureRemovePlugin);
     // Refactored
     this.serverless.pluginManager.addPlugin(AzureLoginPlugin);
diff --git a/src/models/serverless.ts b/src/models/serverless.ts
index 6aba1a0..cfc161a 100644
--- a/src/models/serverless.ts
+++ b/src/models/serverless.ts
@@ -36,6 +36,7 @@ export interface ServerlessAzureProvider {
   region: string;
   stage: string;
   name: string;
+  subscriptionId?: string;
   environment?: {
     [key: string]: any;
   };
@@ -53,12 +54,71 @@ export interface ServerlessAzureProvider {
   runtime: string;
 }
 
+/**
+ * Expanding type of incomplete `Serverless` type.
+ */
+export interface ServerlessObject {
+  /** Input provided by user */
+  processedInput: {
+    /** Commands issued after `serverless` */
+    commands: ServerlessCliCommand[];
+    /** Flags provided in CLI */
+    options: any;
+  };
+  /** Provider specific configuration */
+  service: ServerlessAzureConfig;
+  /** Provider agnostic configuration */
+  config: ServerlessConfig;
+}
+
+/**
+ * Commands that can be issued in CLI
+ */
+export enum ServerlessCliCommand {
+  /** Package service */
+  PACKAGE = "package",
+  /** Deploy service */
+  DEPLOY = "deploy",
+  /** Invoke HTTP endpoint */
+  INVOKE = "invoke",
+  /** Roll back service */
+  ROLLBACK = "rollback",
+  /** Run service offline */
+  OFFLINE = "offline",
+  /** Start pre-built offline service - subcommand for offline*/
+  START = "start",
+  /** Build an offline service - subcommand for offline */
+  BUILD = "build",
+  /** Cleanup files from offline build - subcommand for offline */
+  CLEANUP = "cleanup",
+  /** List deployments - subcommand for deploy */
+  LIST = "list",
+  /** Command to add or remove functions */
+  FUNC = "func",
+  /** Add a function - subcommand for func */
+  ADD = "add",
+  /** Remove a function - subcommand for func */
+  REMOVE = "remove"
+}
+
+/** Vendor agnostic Serverless configuration */
+export interface ServerlessConfig {
+  /** Path to root Serverless service */
+  servicePath: string;
+  /** Path to packaged artifact of service */
+  packagePath: string;
+}
+
 export interface ServerlessAzureConfig {
   service: string;
   provider: ServerlessAzureProvider;
   plugins: string[];
   functions: any;
-  package: any;
+  package: {
+    individually: boolean;
+    artifactDirectoryName: string;
+    artifact: string;
+  };
 }
 
 export interface ServerlessAzureFunctionConfig {
diff --git a/src/plugins/azureBasePlugin.ts b/src/plugins/azureBasePlugin.ts
index d366814..f68b9fe 100644
--- a/src/plugins/azureBasePlugin.ts
+++ b/src/plugins/azureBasePlugin.ts
@@ -1,13 +1,15 @@
-import { Guard } from "../shared/guard";
 import Serverless from "serverless";
+import { ServerlessAzureConfig, ServerlessCliCommand,
+  ServerlessCommandMap, ServerlessHookMap, ServerlessObject } from "../models/serverless";
+import { Guard } from "../shared/guard";
 import { Utils } from "../shared/utils";
-import { ServerlessCommandMap, ServerlessHookMap, ServerlessAzureConfig } from "../models/serverless";
 
 export abstract class AzureBasePlugin<TOptions=Serverless.Options> {
 
   public hooks: ServerlessHookMap
   protected config: ServerlessAzureConfig;
   protected commands: ServerlessCommandMap;
+  protected processedCommands: ServerlessCliCommand[];
 
   public constructor(
     protected serverless: Serverless,
@@ -15,6 +17,7 @@ export abstract class AzureBasePlugin<TOptions=Serverless.Options> {
   ) {
     Guard.null(serverless);
     this.config = serverless.service as any;
+    this.processedCommands = (serverless as any as ServerlessObject).processedInput.commands;
   }
 
   protected log(message: string) {
diff --git a/src/plugins/login/azureLoginPlugin.ts b/src/plugins/login/azureLoginPlugin.ts
index e3f9692..feafe67 100644
--- a/src/plugins/login/azureLoginPlugin.ts
+++ b/src/plugins/login/azureLoginPlugin.ts
@@ -27,8 +27,13 @@ export class AzureLoginPlugin extends AzureBasePlugin<AzureLoginOptions> {
       this.serverless.variables["azureCredentials"] = authResult.credentials;
       // Use environment variable for sub ID or use the first subscription in the list (service principal can
       // have access to more than one subscription)
-      this.serverless.variables["subscriptionId"] = this.options.subscriptionId || process.env.azureSubId || this.serverless.service.provider["subscriptionId"] || authResult.subscriptions[0].id;
-      this.serverless.cli.log(`Using subscription ID: ${this.serverless.variables["subscriptionId"]}`);
+      if (!authResult.subscriptions.length) {
+        throw new Error("Authentication returned an empty list of subscriptions. " +
+          "Try another form of authentication. See the serverless-azure-functions README for more help");
+      }
+      const subId = authResult.subscriptions[0].id;
+      this.serverless.variables["subscriptionId"] = subId;
+      this.serverless.cli.log(`Using subscription ID: ${subId}`);
     }
     catch (e) {
       this.log("Error logging into azure");
diff --git a/src/plugins/logs/azureLogsPlugin.ts b/src/plugins/logs/azureLogsPlugin.ts
deleted file mode 100644
index 464bb1e..0000000
--- a/src/plugins/logs/azureLogsPlugin.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import Serverless from "serverless";
-import AzureProvider from "../../provider/azureProvider";
-import { AzureBasePlugin } from "../azureBasePlugin";
-
-export class AzureLogsPlugin extends AzureBasePlugin {
-
-  private provider: AzureProvider;
-
-  public constructor(serverless: Serverless, options: Serverless.Options) {
-    super(serverless, options);
-
-    this.provider = (this.serverless.getProvider("azure") as any) as AzureProvider;
-
-    this.hooks = {
-      "logs:logs": this.retrieveLogs.bind(this)
-    };
-  }
-
-  private async retrieveLogs() {
-    await this.provider.pingHostStatus(this.options.function);
-    await this.provider.getLogsStream(this.options.function);
-  }
-}
diff --git a/src/plugins/package/azurePackagePlugin.ts b/src/plugins/package/azurePackagePlugin.ts
index 77b8a64..2c30567 100644
--- a/src/plugins/package/azurePackagePlugin.ts
+++ b/src/plugins/package/azurePackagePlugin.ts
@@ -3,6 +3,7 @@ import Serverless from "serverless";
 import AzureProvider from "../../provider/azureProvider";
 import { PackageService } from "../../services/packageService";
 import { AzureBasePlugin } from "../azureBasePlugin";
+import { ServerlessCliCommand } from "../../models/serverless";
 
 export class AzurePackagePlugin extends AzureBasePlugin {
   private bindingsCreated: boolean = false;
@@ -21,14 +22,15 @@ export class AzurePackagePlugin extends AzureBasePlugin {
   }
 
   private async setupProviderConfiguration(): Promise<void> {
-    if (this.getOption("package")) {
-      this.log("No need to create bindings. Using pre-existing package");
-      return Promise.resolve();
+    if (this.processedCommands[0] === ServerlessCliCommand.DEPLOY && this.getOption("package")) {
+      this.log("Deploying pre-built package. No need to create bindings");
+      return;
     }
     if (this.config.package && this.config.package.individually) {
       throw new Error("Cannot package Azure Functions individually. " +
         "Remove `individually` attribute from the `package` section of the serverless config");
     }
+    this.packageService.cleanUpServerlessDir();
     await this.packageService.createBindings();
     this.bindingsCreated = true;
 
diff --git a/src/provider/azureProvider.ts b/src/provider/azureProvider.ts
index 3d31d02..a046aee 100644
--- a/src/provider/azureProvider.ts
+++ b/src/provider/azureProvider.ts
@@ -1,15 +1,7 @@
-import fs from "fs";
-import { join } from "path";
-import request from "request";
 import Serverless from "serverless";
 import config from "../config";
 
-let functionAppName;
-let functionsAdminKey;
-let invocationId;
-
 export default class AzureProvider {
-  public credentials: any;
   private serverless: any;
 
   public static getProviderName() {
@@ -20,214 +12,4 @@ export default class AzureProvider {
     this.serverless = serverless;
     this.serverless.setProvider(config.providerName, this);
   }
-
-  public getAdminKey(): Promise<any> {
-    const options = {
-      url: `https://${functionAppName}${config.scmDomain}${config.masterKeyApiPath}`,
-      json: true,
-      headers: {
-        Authorization: config.bearer + this.credentials.tokenCache._entries[0].accessToken
-      }
-    };
-
-    return new Promise((resolve, reject) => {
-      request(options, (err, response, body) => {
-        if (err) return reject(err);
-        if (response.statusCode !== 200) return reject(body);
-
-        functionsAdminKey = body.masterKey;
-
-        resolve(body.masterKey);
-      });
-    });
-  }
-
-  public pingHostStatus(functionName): Promise<any> {
-    const requestUrl = `https://${functionAppName}${config.functionAppDomain}/admin/functions/${functionName}/status`;
-    const options = {
-      host: functionAppName + config.functionAppDomain,
-      method: "get",
-      url: requestUrl,
-      json: true,
-      headers: {
-        "x-functions-key": functionsAdminKey,
-        Accept: "application/json,*/*"
-      }
-    };
-
-    return new Promise((resolve, reject) => {
-      this.serverless.cli.log("Pinging host status...");
-      request(options, (err, res, body) => {
-        if (err) return reject(err);
-        if (body && body.Error) return reject(body.Error);
-
-        if (res.statusCode !== 200) {
-          return body && body.Error ? reject(body.Error) : reject(body);
-        }
-
-        resolve(res);
-      });
-    });
-  }
-
-  public getLogsStream(functionName) {
-    const logOptions = {
-      url: `https://${functionAppName}${config.scmDomain}${config.logStreamApiPath}${functionName}`,
-      headers: {
-        Authorization: config.bearer + this.credentials.tokenCache._entries[0].accessToken,
-        Accept: "*/*"
-      }
-    };
-
-    request
-      .get(logOptions)
-      .on("error", () => {
-        console.error("Disconnected from log streaming.");
-      })
-      .on("end", () => this.getLogsStream(functionName))
-      .pipe(process.stdout);
-  }
-
-  public getInvocationId(functionName): Promise<any> {
-    const options = {
-      url: `https://${functionAppName}${config.scmDomain}${config.logInvocationsApiPath + functionAppName}-${functionName}/invocations?limit=5`,
-      method: "GET",
-      json: true,
-      headers: {
-        Authorization: config.bearer + this.credentials.tokenCache._entries[0].accessToken
-      }
-    };
-
-    return new Promise((resolve, reject) => {
-      request(options, (err, response, body) => {
-        if (err) return reject(err);
-        if (response.statusCode !== 200) return reject(body);
-
-        invocationId = body.entries[0].id;
-
-        resolve(body.entries[0].id);
-      });
-    });
-  }
-
-  public getLogsForInvocationId(): Promise<any> {
-    this.serverless.cli.log(`Logs for InvocationId: ${invocationId}`);
-    const options = {
-      url: `https://${functionAppName}${config.scmDomain}${config.logOutputApiPath}${invocationId}`,
-      method: "GET",
-      json: true,
-      headers: {
-        Authorization: config.bearer + this.credentials.tokenCache._entries[0].accessToken
-      }
-    };
-
-    return new Promise((resolve, reject) => {
-      request(options, (err, response, body) => {
-        if (err) return reject(err);
-        if (response.statusCode !== 200) return reject(body);
-
-        resolve(body);
-      });
-    });
-  }
-
-  public invoke(functionName, eventType, eventData): Promise<any> {
-    if (eventType === "http") {
-      let queryString = "";
-
-      if (eventData) {
-        if (typeof eventData === "string") {
-          try {
-            eventData = JSON.parse(eventData);
-          }
-          catch (error) {
-            return Promise.reject("The specified input data isn't a valid JSON string. " +
-              "Please correct it and try invoking the function again.");
-          }
-        }
-
-        queryString = Object.keys(eventData)
-          .map((key) => `${key}=${eventData[key]}`)
-          .join("&");
-      }
-
-      return new Promise((resolve, reject) => {
-        const options = {
-          headers: {
-            "x-functions-key": functionsAdminKey
-          },
-          url: `http://${functionAppName}${config.functionAppDomain}${config.functionAppApiPath + functionName}?${queryString}`,
-          method: "GET",
-          json: true,
-        };
-
-        this.serverless.cli.log(`Invoking function "${functionName}"`);
-        request(options, (err, response, body) => {
-          if (err) return reject(err);
-          if (response.statusCode !== 200) return reject(body);
-
-          console.log(body);
-
-          resolve(body);
-        });
-      });
-    }
-
-    const requestUrl = `https://${functionAppName}${config.functionAppDomain}${config.functionsAdminApiPath}${functionName}`;
-
-    const options = {
-      host: config.functionAppDomain,
-      method: "post",
-      body: eventData,
-      url: requestUrl,
-      json: true,
-      headers: {
-        "x-functions-key": functionsAdminKey,
-        Accept: "application/json,*/*"
-      }
-    };
-
-    return new Promise((resolve, reject) => {
-      request(options, (err, res) => {
-        if (err) {
-          reject(err);
-        }
-        this.serverless.cli.log(`Invoked function at: ${requestUrl}. \nResponse statuscode: ${res.statusCode}`);
-        resolve(res);
-      });
-    });
-  }
-
-  public uploadPackageJson(): Promise<any> {
-    const packageJsonFilePath = join(this.serverless.config.servicePath, "package.json");
-    this.serverless.cli.log("Uploading package.json...");
-
-    const requestUrl = `https://${functionAppName}${config.scmDomain}${config.scmVfsPath}package.json`;
-    const options = {
-      host: functionAppName + config.scmDomain,
-      method: "put",
-      url: requestUrl,
-      json: true,
-      headers: {
-        Authorization: config.bearer + this.credentials.tokenCache._entries[0].accessToken,
-        Accept: "*/*"
-      }
-    };
-
-    return new Promise((resolve, reject) => {
-      if (fs.existsSync(packageJsonFilePath)) {
-        fs.createReadStream(packageJsonFilePath)
-          .pipe(request.put(options, (err) => {
-            if (err) {
-              reject(err);
-            } else {
-              resolve("Package.json file uploaded");
-            }
-          }));
-      }
-      else {
-        resolve("Package.json file does not exist");
-      }
-    });
-  }
 }
diff --git a/src/services/baseService.ts b/src/services/baseService.ts
index f6797e2..e2da323 100644
--- a/src/services/baseService.ts
+++ b/src/services/baseService.ts
@@ -41,7 +41,7 @@ export abstract class BaseService {
     this.baseUrl = "https://management.azure.com";
     this.serviceName = this.getServiceName();
     this.credentials = serverless.variables["azureCredentials"];
-    this.subscriptionId = serverless.variables["subscriptionId"];
+    this.subscriptionId = this.config.provider.subscriptionId;
     this.resourceGroup = this.getResourceGroupName();
     this.deploymentConfig = this.getDeploymentConfig();
     this.deploymentName = this.getDeploymentName();
@@ -83,6 +83,13 @@ export abstract class BaseService {
     return this.config.provider.resourceGroup;
   }
 
+  /**
+   * Azure Subscription ID
+   */
+  public getSubscriptionId(): string {
+    return this.config.provider.subscriptionId;
+  }
+
   /**
    * Deployment config from `serverless.yml` or default.
    * Defaults can be found in the `config.ts` file
@@ -250,12 +257,16 @@ export abstract class BaseService {
    * in config if passed through CLI
    */
   private setConfigFromCli() {
-    const { prefix, region, stage } = this.config.provider;
+    const { prefix, region, stage, subscriptionId } = this.config.provider;
     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
+        || subscriptionId
+        || this.serverless.variables["subscriptionId"]
     }
     if (!this.config.provider.region && this.config.provider["location"]) {
       this.config.provider.region = this.config.provider["location"];
diff --git a/src/services/functionAppService.ts b/src/services/functionAppService.ts
index 353a658..06af8b2 100644
--- a/src/services/functionAppService.ts
+++ b/src/services/functionAppService.ts
@@ -15,7 +15,7 @@ import configConstants from "../config";
 
 export class FunctionAppService extends BaseService {
   private static readonly retryCount: number = 30;
-  private static readonly retryInterval: number = 5000;
+  private static readonly retryInterval: number = 30000;
   private webClient: WebSiteManagementClient;
   private blobService: AzureBlobStorageService;
 
@@ -94,13 +94,18 @@ export class FunctionAppService extends BaseService {
     Guard.null(functionApp);
 
     const getTokenUrl = `${this.baseUrl}${functionApp.id}/functions?api-version=2016-08-01`;
+    let retries = 0;
     try {
       const response = await Utils.runWithRetry(async () => {
         const listFunctionsResponse = await this.sendApiRequest("GET", getTokenUrl);
 
         if (listFunctionsResponse.status !== 200 || listFunctionsResponse.data.value.length === 0) {
-          this.log("-> Function App not ready. Retrying...");
-          throw new Error(JSON.stringify(listFunctionsResponse.data, null, 2));
+          this.log(`-> Function App not ready. Retry ${retries++} of ${FunctionAppService.retryCount}...`);
+          const response = JSON.stringify(listFunctionsResponse.data, null, 2);
+          throw new Error(
+            `The function app is taking longer than usual to be provisioned. Please try again soon.
+            Response error data: \n${response}`
+          );
         }
 
         return listFunctionsResponse;
diff --git a/src/services/packageService.ts b/src/services/packageService.ts
index e81ca47..4e340cc 100644
--- a/src/services/packageService.ts
+++ b/src/services/packageService.ts
@@ -1,5 +1,6 @@
 import fs from "fs";
 import path from "path";
+import rimraf from "rimraf";
 import Serverless from "serverless";
 import { FunctionMetadata, Utils } from "../shared/utils";
 import { BaseService } from "./baseService";
@@ -12,6 +13,14 @@ export class PackageService extends BaseService {
     super(serverless, options, false);
   }
 
+  public cleanUpServerlessDir() {
+    const serverlessDir = path.join(this.serverless.config.servicePath, ".serverless");
+    if (fs.existsSync(serverlessDir)) {
+      this.log("Removing .serverless directory")
+      rimraf.sync(serverlessDir);
+    }
+  }
+
   /**
    * Creates the function.json binding files required for the serverless service
    */
diff --git a/src/services/rollbackService.ts b/src/services/rollbackService.ts
index a857693..dd9c911 100644
--- a/src/services/rollbackService.ts
+++ b/src/services/rollbackService.ts
@@ -8,6 +8,7 @@ import { BaseService } from "./baseService";
 import { FunctionAppService } from "./functionAppService";
 import { ResourceService } from "./resourceService";
 import { ArmDeployment } from "../models/armTemplates";
+import fs from "fs";
 
 /**
  * Services for the Rollback Plugin
@@ -98,6 +99,9 @@ export class RollbackService extends BaseService {
     const functionAppService = new FunctionAppService(this.serverless, this.options);
     const functionApp = await functionAppService.get();
     await functionAppService.uploadZippedArfifactToFunctionApp(functionApp, artifactPath);
+    if (fs.existsSync(artifactPath)) {
+      fs.unlinkSync(artifactPath);
+    }
   }
 
   /**


Test patch:
diff --git a/pipelines/templates/unit-tests.yml b/pipelines/templates/unit-tests.yml
index 452862a..50cd3d3 100644
--- a/pipelines/templates/unit-tests.yml
+++ b/pipelines/templates/unit-tests.yml
@@ -19,6 +19,8 @@ jobs:
       versionSpec: ${{ parameters.nodeVersion }}.x
 
   - bash: |
+      set -e
+      
       npm ci
       npm run compile
       npm run test:coverage  # linting is run as part of pretest
diff --git a/src/index.test.ts b/src/index.test.ts
index 5b9947b..1eb39ab 100644
--- a/src/index.test.ts
+++ b/src/index.test.ts
@@ -1,7 +1,6 @@
 import AzureIndex from "./index";
 import { MockFactory } from "./test/mockFactory"
 import { AzureInvokePlugin } from "./plugins/invoke/azureInvokePlugin";
-import { AzureLogsPlugin } from "./plugins/logs/azureLogsPlugin";
 import { AzureRemovePlugin } from "./plugins/remove/azureRemovePlugin";
 import { AzurePackagePlugin } from "./plugins/package/azurePackagePlugin";
 import { AzureDeployPlugin } from "./plugins/deploy/azureDeployPlugin";
@@ -21,7 +20,6 @@ describe("Azure Index", () => {
 
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzurePackagePlugin);
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureInvokePlugin);
-    expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureLogsPlugin);
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureRemovePlugin);
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureLoginPlugin);
     expect(sls.pluginManager.addPlugin).toBeCalledWith(AzureDeployPlugin);
diff --git a/src/plugins/apim/azureApimServicePlugin.test.ts b/src/plugins/apim/azureApimServicePlugin.test.ts
index aaff0fb..b748395 100644
--- a/src/plugins/apim/azureApimServicePlugin.test.ts
+++ b/src/plugins/apim/azureApimServicePlugin.test.ts
@@ -12,7 +12,7 @@ describe("APIM Service Plugin", () => {
   });
 
   it("can be instantiated", () => {
-    const serverless = new Serverless();
+    const serverless = MockFactory.createTestServerless();
     const options: Serverless.Options = {
       stage: "",
       region: "",
diff --git a/src/plugins/login/azureLoginPlugin.test.ts b/src/plugins/login/azureLoginPlugin.test.ts
index 89c8a13..80f7461 100644
--- a/src/plugins/login/azureLoginPlugin.test.ts
+++ b/src/plugins/login/azureLoginPlugin.test.ts
@@ -7,7 +7,6 @@ import { loginHooks } from "./loginHooks";
 
 describe("Login Plugin", () => {
 
-  const authResponse = MockFactory.createTestAuthResponse();
   const envVariables = MockFactory.createTestServicePrincipalEnvVariables()
   const credentials = MockFactory.createTestVariables().azureCredentials;
 
@@ -19,8 +18,8 @@ describe("Login Plugin", () => {
     return new AzureLoginPlugin(sls, options || MockFactory.createTestServerlessOptions());
   }
 
-  function createMockLoginFunction() {
-    return jest.fn(() => Promise.resolve(authResponse));
+  function createMockLoginFunction(authResponse?) {
+    return jest.fn(() => Promise.resolve(authResponse || MockFactory.createTestAuthResponse()));
   }
 
   function setServicePrincipalEnvVariables() {
@@ -91,37 +90,27 @@ describe("Login Plugin", () => {
     });
     const sls = MockFactory.createTestServerless();
     await expect(invokeLoginHook(false, sls)).rejects.toThrow(error);
-    expect(AzureLoginService.interactiveLogin).toBeCalled()
+    expect(AzureLoginService.interactiveLogin).toBeCalled();
     expect(AzureLoginService.servicePrincipalLogin).not.toBeCalled();
     expect(sls.cli.log).lastCalledWith("Error logging into azure");
   });
 
-  it("Uses the user specified subscription ID", async () => {
-    const sls = MockFactory.createTestServerless();
-    const opt = MockFactory.createTestServerlessOptions();
-    opt["subscriptionId"] = "test-subs-id";
-    await invokeLoginHook(false, sls, opt);
-    expect(AzureLoginService.interactiveLogin).toBeCalled()
-    expect(sls.variables["subscriptionId"]).toEqual("test-subs-id");
-    expect(sls.cli.log).toBeCalledWith("Using subscription ID: test-subs-id");
-  });
-
   it("Uses the default subscription ID" , async () => {
     const sls = MockFactory.createTestServerless();
     const opt = MockFactory.createTestServerlessOptions();
     await invokeLoginHook(false, sls, opt);
-    expect(AzureLoginService.interactiveLogin).toBeCalled()
+    expect(AzureLoginService.interactiveLogin).toBeCalled();
     expect(sls.variables["subscriptionId"]).toEqual("azureSubId");
     expect(sls.cli.log).toBeCalledWith("Using subscription ID: azureSubId");
   });
 
-  it("Uses the subscription ID specified in serverless yaml", async () => {
+  it("Throws an error with empty subscription list", async () => {
+    const authResponse = MockFactory.createTestAuthResponse();
+    authResponse.subscriptions = [];
+    AzureLoginService.interactiveLogin = createMockLoginFunction(authResponse);
     const sls = MockFactory.createTestServerless();
     const opt = MockFactory.createTestServerlessOptions();
-    sls.service.provider["subscriptionId"] = "test-subs-id";
-    await invokeLoginHook(false, sls, opt);
-    expect(AzureLoginService.interactiveLogin).toBeCalled()
-    expect(sls.variables["subscriptionId"]).toEqual("test-subs-id");
-    expect(sls.cli.log).toBeCalledWith("Using subscription ID: test-subs-id");
+    await expect(invokeLoginHook(false, sls, opt)).rejects.toThrow();
+    expect(AzureLoginService.interactiveLogin).toBeCalled();
   });
 });
diff --git a/src/plugins/package/azurePackagePlugin.test.ts b/src/plugins/package/azurePackagePlugin.test.ts
index e789d44..45e3137 100644
--- a/src/plugins/package/azurePackagePlugin.test.ts
+++ b/src/plugins/package/azurePackagePlugin.test.ts
@@ -20,11 +20,13 @@ describe("Azure Package Plugin", () => {
   it("sets creates function bindings before package:setupProviderConfiguration life cycle event", async () => {
     await invokeHook(plugin, "before:package:setupProviderConfiguration");
     expect(PackageService.prototype.createBindings).toBeCalled();
+    expect(PackageService.prototype.cleanUpServerlessDir).toBeCalled();
   });
 
   it("prepares the package for webpack before webpack:package:packageModules life cycle event", async () => {
     await invokeHook(plugin, "before:webpack:package:packageModules");
     expect(PackageService.prototype.createBindings).toBeCalled();
+    expect(PackageService.prototype.cleanUpServerlessDir).toBeCalled();
     expect(PackageService.prototype.prepareWebpack).toBeCalled();
   });
 
@@ -34,6 +36,7 @@ describe("Azure Package Plugin", () => {
 
     expect(PackageService.prototype.createBindings).toBeCalledTimes(1);
     expect(PackageService.prototype.prepareWebpack).toBeCalledTimes(1);
+    expect(PackageService.prototype.cleanUpServerlessDir).toBeCalledTimes(1);
   });
 
   it("cleans up package after package:finalize", async () => {
@@ -63,19 +66,22 @@ describe("Azure Package Plugin", () => {
     it("does not call create bindings if package specified in options", async () => {
       await invokeHook(plugin, "before:package:setupProviderConfiguration");
       expect(PackageService.prototype.createBindings).not.toBeCalled();
-      expect(sls.cli.log).lastCalledWith("No need to create bindings. Using pre-existing package");
+      expect(sls.cli.log).lastCalledWith("Deploying pre-built package. No need to create bindings");
     });
 
     it("does not call webpack if package specified in options", async () => {
       await invokeHook(plugin, "before:webpack:package:packageModules");
       expect(PackageService.prototype.createBindings).not.toBeCalled();
       expect(PackageService.prototype.prepareWebpack).not.toBeCalled();
+      expect(PackageService.prototype.cleanUpServerlessDir).not.toBeCalled();
+
       expect(sls.cli.log).lastCalledWith("No need to perform webpack. Using pre-existing package");
     });
 
     it("does not call finalize if package specified in options", async () => {
       await invokeHook(plugin, "after:package:finalize");
       expect(PackageService.prototype.cleanUp).not.toBeCalled();
+      expect(PackageService.prototype.cleanUpServerlessDir).not.toBeCalled();
       expect(sls.cli.log).lastCalledWith("No need to clean up generated folders & files. Using pre-existing package");
     });
   })
diff --git a/src/provider/azureProvider.test.ts b/src/provider/azureProvider.test.ts
new file mode 100644
index 0000000..11ede69
--- /dev/null
+++ b/src/provider/azureProvider.test.ts
@@ -0,0 +1,12 @@
+import { MockFactory } from "../test/mockFactory";
+import AzureProvider from "./azureProvider"
+
+describe("Azure Provider", () => {
+
+  it("sets the provider name in initialization", () => {
+    const sls = MockFactory.createTestServerless();
+    new AzureProvider(sls);
+    expect(sls.setProvider).toBeCalledWith("azure", expect.anything());
+    expect(AzureProvider.getProviderName()).toEqual("azure");
+  });
+});
diff --git a/src/services/armService.test.ts b/src/services/armService.test.ts
index f09a2bd..d70cb46 100644
--- a/src/services/armService.test.ts
+++ b/src/services/armService.test.ts
@@ -70,7 +70,7 @@ describe("Arm Service", () => {
     });
 
     it("Creates a custom ARM template from well-known type", async () => {
-      sls.service.provider.runtime = "nodejs6.9.x";      
+      sls.service.provider.runtime = "nodejs6.9.x";
       const deployment = await service.createDeploymentFromType("premium");
 
       expect(deployment).not.toBeNull();
@@ -95,36 +95,37 @@ describe("Arm Service", () => {
     });
 
     it("throws error when invalid nodejs version in defined", async () => {
-      sls.service.provider.runtime = "nodejs10.6.1"; 
+      sls.service.provider.runtime = "nodejs10.6.1";
       await expect(service.createDeploymentFromType("premium")).rejects.toThrowError("Could not find runtime version matching nodejs10.6.1");
     });
 
     it("throws error when incomplete nodejs version in defined", async () => {
-      sls.service.provider.runtime = "nodejs8"; 
+      sls.service.provider.runtime = "nodejs8";
       await expect(service.createDeploymentFromType("premium")).rejects.toThrowError("Could not find runtime version matching nodejs8");
     });
 
     it("throws error when unsupported nodejs version in defined", async () => {
-      sls.service.provider.runtime = "nodejs5.x"; 
+      sls.service.provider.runtime = "nodejs5.x";
       await expect(service.createDeploymentFromType("premium")).rejects.toThrowError("Could not find runtime version matching nodejs5.x");
     });
 
     it("Does not throw an error when valid nodejs version is defined", async () => {
-      sls.service.provider.runtime = "nodejs10.x"; 
+      sls.service.provider.runtime = "nodejs10.x";
       await expect(service.createDeploymentFromType("premium")).resolves.not.toThrow();
     });
 
     it("Does not throw an error when nodejs version with major and minor is defined", async () => {
-      sls.service.provider.runtime = "nodejs6.9.x"; 
+      sls.service.provider.runtime = "nodejs6.9.x";
       await expect(service.createDeploymentFromType("premium")).resolves.not.toThrow();
     });
 
     it("Does not throw an error when specific nodejs version is defined", async () => {
-      sls.service.provider.runtime = "nodejs10.6.0"; 
+      sls.service.provider.runtime = "nodejs10.6.0";
       await expect(service.createDeploymentFromType("premium")).resolves.not.toThrow();
     });
 
-    it("throws an error when no nodejs version in defined", async () => {
+    it("throws an error when no nodejs version is defined", async () => {
+      sls.service.provider.runtime = undefined;
       await expect(service.createDeploymentFromType("premium")).rejects.toThrowError("Runtime version not specified in serverless.yml");
     });
 
@@ -178,7 +179,7 @@ describe("Arm Service", () => {
     });
 
     it("Consumption template includes correct resources", async () => {
-      sls.service.provider.runtime = "nodejs10.x";      
+      sls.service.provider.runtime = "nodejs10.x";
       const deployment = await service.createDeploymentFromType(ArmTemplateType.Consumption);
 
       expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Web/hostingEnvironments")).toBeUndefined();
diff --git a/src/services/baseService.test.ts b/src/services/baseService.test.ts
index 244eb9b..af6c23f 100644
--- a/src/services/baseService.test.ts
+++ b/src/services/baseService.test.ts
@@ -39,8 +39,11 @@ class MockService extends BaseService {
 
 describe("Base Service", () => {
   let service: MockService;
-  let sls: Serverless;
+  let serverless: Serverless;
   const serviceName = "my-custom-service"
+  const loginResultSubscriptionId = "ABC123";
+  const envVarSubscriptionId = "env var sub id";
+
 
   const slsConfig = {
     service: serviceName,
@@ -54,24 +57,24 @@ describe("Base Service", () => {
     mockFs.restore();
   });
 
-  function createMockService(options?: Serverless.Options) {
+  function createMockService(sls: Serverless, options?: Serverless.Options) {
     sls.variables["azureCredentials"] = MockFactory.createTestAzureCredentials();
-    sls.variables["subscriptionId"] = "ABC123";
+    sls.variables["subscriptionId"] = loginResultSubscriptionId;
     Object.assign(sls.service, slsConfig);
 
     return new MockService(sls, options);
   }
 
   beforeEach(() => {
-    sls = MockFactory.createTestServerless();
-    service = createMockService();
+    serverless = MockFactory.createTestServerless();
+    service = createMockService(serverless);
   });
 
   it("Initializes common service properties", () => {
     const props = service.getProperties();
     expect(props.baseUrl).toEqual("https://management.azure.com");
-    expect(props.credentials).toEqual(sls.variables["azureCredentials"]);
-    expect(props.subscriptionId).toEqual(sls.variables["subscriptionId"]);
+    expect(props.credentials).toEqual(serverless.variables["azureCredentials"]);
+    expect(props.subscriptionId).toEqual(serverless.variables["subscriptionId"]);
     expect(props.serviceName).toEqual(slsConfig.service);
     expect(props.resourceGroup).toEqual(slsConfig.provider.resourceGroup);
     const expectedDeploymentNameRegex = new RegExp(slsConfig.provider.deploymentName + "-t([0-9]+)")
@@ -79,11 +82,11 @@ describe("Base Service", () => {
   });
 
   it("Sets default region and stage values if not defined", () => {
-    const mockService = new MockService(sls);
+    const mockService = new MockService(serverless);
 
     expect(mockService).not.toBeNull();
-    expect(sls.service.provider.region).toEqual("westus");
-    expect(sls.service.provider.stage).toEqual("dev");
+    expect(serverless.service.provider.region).toEqual("westus");
+    expect(serverless.service.provider.stage).toEqual("dev");
   });
 
   it("returns region and stage based on CLI options", () => {
@@ -91,7 +94,7 @@ describe("Base Service", () => {
       stage: "prod",
       region: "eastus2",
     };
-    const mockService = new MockService(sls, cliOptions);
+    const mockService = new MockService(serverless, cliOptions);
 
     expect(mockService.getRegion()).toEqual(cliOptions.region);
     expect(mockService.getStage()).toEqual(cliOptions.stage);
@@ -105,48 +108,48 @@ describe("Base Service", () => {
       resourceGroup: resourceGroupName
     };
 
-    const mockService = new MockService(sls, cliOptions);
+    const mockService = new MockService(serverless, cliOptions);
     const actualResourceGroupName = mockService.getResourceGroupName();
 
     expect(actualResourceGroupName).toEqual(resourceGroupName);
   });
 
   it("use the resource group name from sls yaml config", () => {
-    const mockService = new MockService(sls);
+    const mockService = new MockService(serverless);
     const actualResourceGroupName = mockService.getResourceGroupName();
 
-    expect(actualResourceGroupName).toEqual(sls.service.provider["resourceGroup"]);
+    expect(actualResourceGroupName).toEqual(serverless.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);
+    serverless.service.provider["resourceGroup"] = null;
+    const mockService = new MockService(serverless);
     const actualResourceGroupName = mockService.getResourceGroupName();
     const expectedRegion = AzureNamingService.createShortAzureRegionName(mockService.getRegion());
     const expectedStage = AzureNamingService.createShortStageName(mockService.getStage());
-    const expectedResourceGroupName = `sls-${expectedRegion}-${expectedStage}-${sls.service["service"]}-rg`;
+    const expectedResourceGroupName = `sls-${expectedRegion}-${expectedStage}-${serverless.service["service"]}-rg`;
 
     expect(actualResourceGroupName).toEqual(expectedResourceGroupName);
   });
 
   it("set default prefix when one is not defined in yaml config", () => {
-    const mockService = new MockService(sls);
+    const mockService = new MockService(serverless);
     const actualPrefix = mockService.getPrefix();
     expect(actualPrefix).toEqual("sls");
   });
 
   it("use the prefix defined in sls yaml config", () => {
     const expectedPrefix = "testPrefix"
-    sls.service.provider["prefix"] = expectedPrefix;
-    const mockService = new MockService(sls);
+    serverless.service.provider["prefix"] = expectedPrefix;
+    const mockService = new MockService(serverless);
     const actualPrefix = mockService.getPrefix();
 
     expect(actualPrefix).toEqual(expectedPrefix);
   });
 
   it("Fails if credentials have not been set in serverless config", () => {
-    sls.variables["azureCredentials"] = null;
-    expect(() => new MockService(sls)).toThrow()
+    serverless.variables["azureCredentials"] = null;
+    expect(() => new MockService(serverless)).toThrow()
   });
 
   it("Makes HTTP request via axios", async () => {
@@ -202,36 +205,74 @@ describe("Base Service", () => {
 
   it("sets stage name from CLI", async () => {
     const stage = "test";
-    delete (sls.service as any as ServerlessAzureConfig).provider.resourceGroup;
-    expect(sls.service.provider.stage).not.toEqual(stage);
-    service = new MockService(sls, { stage } as any);
+    delete (serverless.service as any as ServerlessAzureConfig).provider.resourceGroup;
+    expect(serverless.service.provider.stage).not.toEqual(stage);
+    service = new MockService(serverless, { stage } as any);
     expect(service.getStage()).toEqual(stage);
     expect(service.getResourceGroupName()).toEqual(`sls-wus-${stage}-${serviceName}-rg`);
   });
 
   it("sets region name from CLI", async () => {
     const region = "East US";
-    delete (sls.service as any as ServerlessAzureConfig).provider.resourceGroup;
-    expect(sls.service.provider.region).not.toEqual(region);
-    service = new MockService(sls, { region } as any);
+    delete (serverless.service as any as ServerlessAzureConfig).provider.resourceGroup;
+    expect(serverless.service.provider.region).not.toEqual(region);
+    service = new MockService(serverless, { region } as any);
     expect(service.getRegion()).toEqual(region);
     expect(service.getResourceGroupName()).toEqual(`sls-eus-dev-${serviceName}-rg`);
   });
 
   it("sets prefix from CLI", async () => {
     const prefix = "prefix";
-    delete (sls.service as any as ServerlessAzureConfig).provider.resourceGroup;
-    expect(sls.service.provider["prefix"]).not.toEqual(prefix);
-    service = new MockService(sls, { prefix } as any);
+    delete (serverless.service as any as ServerlessAzureConfig).provider.resourceGroup;
+    expect(serverless.service.provider["prefix"]).not.toEqual(prefix);
+    service = new MockService(serverless, { prefix } as any);
     expect(service.getPrefix()).toEqual(prefix);
     expect(service.getResourceGroupName()).toEqual(`${prefix}-wus-dev-${serviceName}-rg`);
   });
 
   it("sets resource group from CLI", async () => {
     const resourceGroup = "resourceGroup";
-    delete (sls.service as any as ServerlessAzureConfig).provider.resourceGroup;
-    expect(sls.service.provider["resourceGroup"]).not.toEqual(resourceGroup);
-    service = new MockService(sls, { resourceGroup } as any);
+    delete (serverless.service as any as ServerlessAzureConfig).provider.resourceGroup;
+    expect(serverless.service.provider["resourceGroup"]).not.toEqual(resourceGroup);
+    service = new MockService(serverless, { resourceGroup } as any);
     expect(service.getResourceGroupName()).toEqual(resourceGroup);
   });
+
+  const cliSubscriptionId = "cli sub id";
+  const configSubscriptionId = "config sub id";
+
+  it("sets subscription ID from CLI", async () => {
+    process.env.azureSubId = envVarSubscriptionId;
+    serverless.service.provider["subscriptionId"] = configSubscriptionId;
+    serverless.variables["subscriptionId"] = loginResultSubscriptionId
+    service = new MockService(serverless, { subscriptionId: cliSubscriptionId } as any);
+    expect(service.getSubscriptionId()).toEqual(cliSubscriptionId);
+    expect(serverless.service.provider["subscriptionId"]).toEqual(cliSubscriptionId);
+  });
+
+  it("sets subscription ID from environment variable", async () => {
+    process.env.azureSubId = envVarSubscriptionId;
+    serverless.service.provider["subscriptionId"] = configSubscriptionId;
+    serverless.variables["subscriptionId"] = loginResultSubscriptionId
+    service = new MockService(serverless, { } as any);
+    expect(service.getSubscriptionId()).toEqual(envVarSubscriptionId);
+    expect(serverless.service.provider["subscriptionId"]).toEqual(envVarSubscriptionId);
+  });
+
+  it("sets subscription ID from config", async () => {
+    delete process.env.azureSubId;
+    serverless.service.provider["subscriptionId"] = configSubscriptionId;
+    serverless.variables["subscriptionId"] = loginResultSubscriptionId
+    service = new MockService(serverless, { } as any);
+    expect(service.getSubscriptionId()).toEqual(configSubscriptionId);
+    expect(serverless.service.provider["subscriptionId"]).toEqual(configSubscriptionId);
+  });
+
+  it("sets subscription ID from login result", async () => {
+    delete process.env.azureSubId;
+    serverless.variables["subscriptionId"] = loginResultSubscriptionId
+    service = new MockService(serverless, { } as any);
+    expect(service.getSubscriptionId()).toEqual(loginResultSubscriptionId);
+    expect(serverless.service.provider["subscriptionId"]).toEqual(loginResultSubscriptionId);
+  });
 });
diff --git a/src/services/functionAppService.test.ts b/src/services/functionAppService.test.ts
index b7cc9c8..aa70c80 100644
--- a/src/services/functionAppService.test.ts
+++ b/src/services/functionAppService.test.ts
@@ -16,6 +16,7 @@ jest.mock("@azure/arm-resources");
 jest.mock("./azureBlobStorageService");
 import { AzureBlobStorageService } from "./azureBlobStorageService"
 import configConstants from "../config";
+import { Utils } from "../shared/utils";
 
 describe("Function App Service", () => {
   const app = MockFactory.createTestSite();
@@ -41,8 +42,10 @@ describe("Function App Service", () => {
     setting2: "value2",
   }
 
-  beforeAll(() => {
-    const axiosMock = new MockAdapter(axios);
+  let axiosMock: MockAdapter;
+
+  beforeEach(() => {
+    axiosMock = new MockAdapter(axios);
 
     // Master Key
     axiosMock.onGet(masterKeyUrl).reply(200, { value: masterKey });
@@ -64,9 +67,7 @@ describe("Function App Service", () => {
         "serviceName.zip": "contents",
       }
     }, { createCwd: true, createTmp: true });
-  });
 
-  beforeEach(() => {
     WebSiteManagementClient.prototype.webApps = {
       get: jest.fn(() => app),
       deleteFunction: jest.fn(),
@@ -152,6 +153,30 @@ describe("Function App Service", () => {
     expect(await service.listFunctions(app)).toEqual(functionsResponse.map((f) => f.properties));
   });
 
+  it("list functions with retry", async () => {
+    axiosMock.onGet(listFunctionsUrl).reply(200, { value: [] });
+    const service = createService();
+
+    const originalRunWithRetry = Utils.runWithRetry;
+    Utils.runWithRetry = jest.fn(() => {
+      return {
+        data: {
+          value: []
+        }
+      }
+    }) as any;
+
+    await service.listFunctions(app);
+    const runWithRetryCalls = (Utils.runWithRetry as any).mock.calls;
+    Utils.runWithRetry = originalRunWithRetry;
+
+    expect(runWithRetryCalls).toHaveLength(1);
+    const call = runWithRetryCalls[0];
+    await expect(call[0]()).rejects.toThrow();
+    expect(call[1]).toEqual(30);
+    expect(call[2]).toEqual(30000);
+  });
+
   describe("Deployments", () => {
     const expectedDeployment: ArmDeployment = {
       parameters: {},
diff --git a/src/services/packageService.test.ts b/src/services/packageService.test.ts
index 9050927..8fac111 100644
--- a/src/services/packageService.test.ts
+++ b/src/services/packageService.test.ts
@@ -6,6 +6,9 @@ import { PackageService } from "./packageService";
 import { MockFactory } from "../test/mockFactory";
 import { FunctionMetadata } from "../shared/utils";
 
+jest.mock("rimraf");
+import rimraf from "rimraf";
+
 describe("Package Service", () => {
   let sls: Serverless;
   let packageService: PackageService;
@@ -25,6 +28,17 @@ describe("Package Service", () => {
     mockFs.restore();
   });
 
+  it("cleans up previous .serverless folder", async () => {
+    mockFs({
+      ".serverless": {
+        "artifact.zip": "contents"
+      }
+    });
+
+    packageService.cleanUpServerlessDir();
+    expect(rimraf.sync).toBeCalledWith(path.join(process.cwd(), ".serverless"));
+  });
+
   it("cleans up function.json and function folders", async () => {
     const fsConfig = {};
     const functionNames = sls.service.getAllFunctions();
diff --git a/src/services/rollbackService.test.ts b/src/services/rollbackService.test.ts
index e5e0ce1..502e03d 100644
--- a/src/services/rollbackService.test.ts
+++ b/src/services/rollbackService.test.ts
@@ -5,6 +5,7 @@ import { ArmDeployment } from "../models/armTemplates";
 import { DeploymentConfig } from "../models/serverless";
 import { MockFactory } from "../test/mockFactory";
 import { RollbackService } from "./rollbackService";
+import fs from "fs";
 
 jest.mock("./azureBlobStorageService");
 import { AzureBlobStorageService } from "./azureBlobStorageService";
@@ -31,9 +32,10 @@ describe("Rollback Service", () => {
   const containerName = "deployment-artifacts";
   const artifactName = MockFactory.createTestDeployment().name.replace(
     configConstants.naming.suffix.deployment, configConstants.naming.suffix.artifact) + ".zip";
-  const artifactPath = `.serverless${path.sep}${artifactName}`
+  const artifactPath = path.join(".serverless", artifactName);
   const armDeployment: ArmDeployment = { template, parameters };
   const deploymentString = "deployments";
+  let unlinkSpy: jest.SpyInstance;
 
   function createOptions(timestamp?: string): Serverless.Options {
     return {
@@ -64,10 +66,12 @@ describe("Rollback Service", () => {
     ResourceService.prototype.listDeployments = jest.fn(() => Promise.resolve(deploymentString))
     AzureBlobStorageService.prototype.generateBlobSasTokenUrl = jest.fn(() => sasURL) as any;
     FunctionAppService.prototype.get = jest.fn(() => appStub) as any;
+    unlinkSpy = jest.spyOn(fs, "unlinkSync");
   });
 
   afterEach(() => {
     mockFs.restore();
+    unlinkSpy.mockRestore();
     jest.resetAllMocks();
   });
 
@@ -94,6 +98,11 @@ describe("Rollback Service", () => {
   });
 
   it("should deploy blob package directly to function app", async () => {
+    const fsConfig = {};
+    fsConfig[artifactPath] = "contents";
+    // Mocking the existence of the downloaded artifact because the downloadBinary
+    // method won't write to the mock file system
+    mockFs(fsConfig);
     const service = createService();
     await service.rollback();
     expect(AzureBlobStorageService.prototype.initialize).toBeCalled();
@@ -107,7 +116,8 @@ describe("Rollback Service", () => {
     expect(FunctionAppService.prototype.uploadZippedArfifactToFunctionApp).toBeCalledWith(
       appStub,
       artifactPath
-    )
+    );
+    expect(unlinkSpy).toBeCalledWith(artifactPath);
   });
 
   it("should deploy function app with SAS URL", async () => {
@@ -133,5 +143,6 @@ describe("Rollback Service", () => {
     );
     expect(FunctionAppService.prototype.get).not.toBeCalled();
     expect(FunctionAppService.prototype.uploadZippedArfifactToFunctionApp).not.toBeCalled();
+    expect(unlinkSpy).not.toBeCalled();
   });
 });
diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts
index 64242c9..b91e4f8 100644
--- a/src/test/mockFactory.ts
+++ b/src/test/mockFactory.ts
@@ -15,7 +15,8 @@ import { ApiCorsPolicy, ApiManagementConfig } from "../models/apiManagement";
 import { ArmDeployment, ArmResourceTemplate, ArmTemplateProvisioningState } from "../models/armTemplates";
 import { ServicePrincipalEnvVariables } from "../models/azureProvider";
 import { Logger } from "../models/generic";
-import { ServerlessAzureConfig, ServerlessAzureProvider, ServerlessAzureFunctionConfig } from "../models/serverless";
+import { ServerlessAzureConfig, ServerlessAzureProvider, ServerlessAzureFunctionConfig, ServerlessCliCommand } from "../models/serverless";
+import configConstants from "../config";
 
 function getAttribute(object: any, prop: string, defaultValue: any): any {
   if (object && object[prop]) {
@@ -33,6 +34,11 @@ export class MockFactory {
     sls.variables = getAttribute(config, "variables", MockFactory.createTestVariables());
     sls.service = getAttribute(config, "service", MockFactory.createTestService());
     sls.config.servicePath = "";
+    sls.setProvider = jest.fn();
+    sls["processedInput"] = {
+      commands: [ ServerlessCliCommand.DEPLOY ],
+      options: {}
+    };
     return sls;
   }
 
@@ -169,7 +175,7 @@ export class MockFactory {
     const result = [];
     const originalTimestamp = +MockFactory.createTestTimestamp();
     for (let i = 0; i < count; i++) {
-      const name = (includeTimestamp) ? `deployment${i + 1}-t${originalTimestamp + i}` : `deployment${i + 1}`;
+      const name = (includeTimestamp) ? `${configConstants.naming.suffix.deployment}${i + 1}-t${originalTimestamp + i}` : `deployment${i + 1}`;
       result.push(
         MockFactory.createTestDeployment(name, i)
       )
@@ -189,7 +195,7 @@ export class MockFactory {
 
   public static createTestDeployment(name?: string, second: number = 0): DeploymentExtended {
     return {
-      name: name || `deployment1-t${MockFactory.createTestTimestamp()}`,
+      name: name || `${configConstants.naming.suffix.deployment}1-t${MockFactory.createTestTimestamp()}`,
       properties: {
         timestamp: new Date(2019, 1, 1, 0, 0, second),
         parameters: MockFactory.createTestParameters(),
@@ -359,6 +365,7 @@ export class MockFactory {
       deploymentName: "myDeploymentName",
       region: "eastus2",
       stage: "dev",
+      runtime: "nodejs10.x",
     }
   }