Repository: serverless/serverless-azure-functions (ts)

Issue:
Error when deploying linux funcapp node10/12
<!--
1. Please check if an issue already exists. This bug may have already been documented
2. Check out and follow our Guidelines: https://github.com/serverless/serverless-azure-functions/CONTRIBUTING.md
3. Fill out the whole template so we have a good overview on the issue
4. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the Issue
5. Please follow the template, otherwise we'll have to ask you to update it
-->

# This is a Bug Report
Yes
## Description
When using azure function 1.0.2-23 to deploy functionApp with linux os node10/12 getting error.
- What went wrong?
- What did you expect should have happened?
- What was the config you used?
    ```provider:
  name: azure
  os: linux
  region: Central US
  runtime: nodejs10
  prefix: "td-gnu"
  type: premium
- What stacktrace or error message from your provider did you see?
``` 
  Error --------------------------------------------------
 
  Error: DeploymentFailed - At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details.------------------------
  DEPLOYMENT ERROR DETAILS
  ------------------------
  
  BadRequest - {
    "Code": "BadRequest",
    "Message": "The parameter LinuxFxVersion has an invalid value.",
    "Target": null,
    "Details": [
      {
        "Message": "The parameter LinuxFxVersion has an invalid value."
      },
      {
        "Code": "BadRequest"
      },
      {
        "ErrorEntity": {
          "ExtendedCode": "01007",
          "MessageTemplate": "The parameter {0} has an invalid value.",
          "Parameters": [
            "LinuxFxVersion"
          ],
          "Code": "BadRequest",
          "Message": "The parameter LinuxFxVersion has an invalid value."
        }
      }
    ],
    "Innererror": null
  }
      at ArmService.<anonymous> (/Users/jp250090/Documents/teradata/draas/azure-agent-proxy/node_modules/serverless-azure-functions/lib/services/armService.js:235:39)
      at step (/Users/jp250090/Documents/teradata/draas/azure-agent-proxy/node_modules/serverless-azure-functions/lib/services/armService.js:56:23)
      at Object.next (/Users/jp250090/Documents/teradata/draas/azure-agent-proxy/node_modules/serverless-azure-functions/lib/services/armService.js:37:53)
      at fulfilled (/Users/jp250090/Documents/teradata/draas/azure-agent-proxy/node_modules/serverless-azure-functions/lib/services/armService.js:28:58)
      at processTicksAndRejections (internal/process/task_queues.js:97:5)
  From previous event:
      at PluginManager.invoke (/usr/local/lib/node_modules/serverless/lib/classes/PluginManager.js:490:22)
      at /usr/local/lib/node_modules/serverless/lib/classes/PluginManager.js:525:24
  From previous event:
      at PluginManager.run (/usr/local/lib/node_modules/serverless/lib/classes/PluginManager.js:525:8)
      at /usr/local/lib/node_modules/serverless/lib/Serverless.js:133:33
      at processImmediate (internal/timers.js:456:21)
      at process.topLevelDomainCallback (domain.js:137:15)
  From previous event:
      at Serverless.run (/usr/local/lib/node_modules/serverless/lib/Serverless.js:120:74)
      at /usr/local/lib/node_modules/serverless/bin/serverless.js:82:30
      at /usr/local/lib/node_modules/serverless/node_modules/graceful-fs/graceful-fs.js:136:16
      at /usr/local/lib/node_modules/serverless/node_modules/graceful-fs/graceful-fs.js:57:14
      at FSReqCallback.oncomplete (fs.js:159:23)
  From previous event:
      at /usr/local/lib/node_modules/serverless/bin/serverless.js:82:8
      at processImmediate (internal/timers.js:456:21)
      at process.topLevelDomainCallback (domain.js:137:15)
  From previous event:
      at Object.<anonymous> (/usr/local/lib/node_modules/serverless/bin/serverless.js:71:4)
      at Module._compile (internal/modules/cjs/loader.js:1147:30)
      at Object.Module._extensions..js (internal/modules/cjs/loader.js:1167:10)
      at Module.load (internal/modules/cjs/loader.js:996:32)
      at Function.Module._load (internal/modules/cjs/loader.js:896:14)
      at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
      at internal/main/run_main_module.js:17:47
 
```
Similar or dependent issues:

- #12345

## Additional Data
  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
     Issues:        forum.serverless.com
 
  Your Environment Information ---------------------------
     Operating System:          darwin
     Node Version:              13.12.0
     Framework Version:         1.67.3
     Plugin Version:            3.6.6
     SDK Version:               2.3.0
     Components Version:        2.29.3

- **_Serverless Framework Version you're using_**: 1.67.3
- **_Serverless CLI Version you're using_**:
- **_Serverless Azure Plugin Version you're using_**: 1.0.2-23 
- **_Operating System_**:darwin
- **_Stack Trace_**:
- **_Provider Error messages_**:


Pull request description:
fix: Move reserved prop to App Service Plan
<!--
1. Please check out and follow our Contributing Guidelines: https://github.com/serverless/serverless-azure-functions/CONTRIBUTING.md
2. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the PR
3. Please follow the template, otherwise we'll have to ask you to update it and it will take longer until your PR is merged
-->

## What did you implement:

Small update to move `reserved` property from site resource to app service plan resource.

Closes #437 

<!--
Briefly describe the feature if no issue exists for this PR
-->

## How did you implement it:

See above.

<!--
If this is a nontrivial change please briefly describe your implementation so its easy for us to understand and review your code.
-->

## How can we verify it:

Deploy a premium node linux function app. It shouldn't fail

<!--
Add any applicable config, commands, screenshots or other resources
to make it easy for us to verify this works. The easier you make it for us
to review a PR, the faster we can review and merge it.

Examples:
* serverless.yml - Fully functioning to easily deploy changes
* Screenshots - Showing the difference between your output and the master
* Cloud Configuration - List cloud resources and show that the correct configuration is in place
* Other - Anything else that comes to mind to help us evaluate
-->

## Todos:

_**Note: Run `npm run test:ci` to run all validation checks on proposed changes**_

- [x] Ensure there are no lint errors.  
       **Validate via `npm run lint`**  
       _Note: Some reported issues can be automatically fixed by running `npm run lint:fix`_
- [x] Write tests and confirm existing functionality is not broken.  
       **Validate via `npm test`**
- [x] Write documentation
- [x] Provide verification config / commands / resources
- [x] Enable "Allow edits from maintainers" for this PR
- [x] Update the messages below

**_Is this ready for review?:_** YES
**_Is it a breaking change?:_** NO


Patch:
diff --git a/src/armTemplates/resources/appServicePlan.ts b/src/armTemplates/resources/appServicePlan.ts
index 6b0225c..7209e56 100644
--- a/src/armTemplates/resources/appServicePlan.ts
+++ b/src/armTemplates/resources/appServicePlan.ts
@@ -12,6 +12,8 @@ interface AppServicePlanParams extends DefaultArmParams {
   appServicePlanMinWorkerCount: ArmParameter;
   appServicePlanMaxWorkerCount: ArmParameter;
   appServicePlanHostingEnvironment: ArmParameter;
+  /** Needs to be true for Linux App Service Plans */
+  appServicePlanReserved: ArmParameter;
 }
 
 export class AppServicePlanResource implements ArmResourceTemplateGenerator {
@@ -61,7 +63,11 @@ export class AppServicePlanResource implements ArmResourceTemplateGenerator {
       appServicePlanHostingEnvironment: {
         defaultValue: "",
         type: ArmParamType.String
-      }
+      },
+      appServicePlanReserved: {
+        defaultValue: false,
+        type: ArmParamType.Bool,
+      },
     };
 
     return {
@@ -81,7 +87,8 @@ export class AppServicePlanResource implements ArmResourceTemplateGenerator {
             "workerSizeId": "[parameters('appServicePlanWorkerSizeId')]",
             "numberOfWorkers": "[parameters('appServicePlanMinWorkerCount')]",
             "maximumElasticWorkerCount": "[parameters('appServicePlanMaxWorkerCount')]",
-            "hostingEnvironment": "[parameters('appServicePlanHostingEnvironment')]"
+            "hostingEnvironment": "[parameters('appServicePlanHostingEnvironment')]",
+            "reserved": "[parameters('appServicePlanReserved')]",
           },
           "sku": {
             "name": "[parameters('appServicePlanSkuName')]",
@@ -100,6 +107,7 @@ export class AppServicePlanResource implements ArmResourceTemplateGenerator {
     };
 
     const { os } = config.provider;
+    const linux = os === FunctionAppOS.LINUX;
 
     const params: AppServicePlanParams = {
       appServicePlanName: {
@@ -111,7 +119,7 @@ export class AppServicePlanResource implements ArmResourceTemplateGenerator {
        * before deployment
        */
       kind: {
-        value: (os === FunctionAppOS.LINUX) ? "Linux" : undefined,
+        value: (linux) ? "Linux" : undefined,
       },
       appServicePlanSkuName: {
         value: resourceConfig.sku.name,
@@ -130,6 +138,9 @@ export class AppServicePlanResource implements ArmResourceTemplateGenerator {
       },
       appServicePlanHostingEnvironment: {
         value: resourceConfig.hostingEnvironment
+      },
+      appServicePlanReserved: {
+        value: (linux) ? true : undefined
       }
     }
 
diff --git a/src/armTemplates/resources/functionApp.ts b/src/armTemplates/resources/functionApp.ts
index e6b2644..b278022 100644
--- a/src/armTemplates/resources/functionApp.ts
+++ b/src/armTemplates/resources/functionApp.ts
@@ -18,10 +18,6 @@ interface FunctionAppParams extends DefaultArmParams {
    * `functionapp,linux` for Linux function app
    */
   functionAppKind: ArmParameter;
-  /**
-   * Needs to be `true` for Linux function apps
-   */
-  functionAppReserved: ArmParameter;
   /**
    * Docker image for Linux function app
    */
@@ -98,7 +94,6 @@ export class FunctionAppResource implements ArmResourceTemplateGenerator {
               appSettings: this.getFunctionAppSettings(config), 
               "linuxFxVersion": "[parameters('linuxFxVersion')]",
             },
-            "reserved": "[parameters('functionAppReserved')]",
             name: "[parameters('functionAppName')]",
             "clientAffinityEnabled": false,
             "hostingEnvironment": ""
@@ -132,9 +127,6 @@ export class FunctionAppResource implements ArmResourceTemplateGenerator {
       functionAppKind: {
         value: (isLinuxRuntime) ? "functionapp,linux" : undefined,
       },
-      functionAppReserved: {
-        value: (isLinuxRuntime) ? true : undefined,
-      },
       linuxFxVersion: {
         value: (isLinuxRuntime) ? this.getLinuxFxVersion(config) : undefined,
       },
@@ -170,10 +162,6 @@ export class FunctionAppResource implements ArmResourceTemplateGenerator {
         defaultValue: "functionapp",
         type: ArmParamType.String,
       },
-      functionAppReserved: {
-        defaultValue: false,
-        type: ArmParamType.Bool
-      },
       linuxFxVersion: {
         defaultValue: "",
         type: ArmParamType.String,


Test patch:
diff --git a/src/armTemplates/resources/appServicePlan.test.ts b/src/armTemplates/resources/appServicePlan.test.ts
index 87095f0..b548b54 100644
--- a/src/armTemplates/resources/appServicePlan.test.ts
+++ b/src/armTemplates/resources/appServicePlan.test.ts
@@ -71,7 +71,7 @@ describe("App Service Plan Resource", () => {
     const appServicePlanResource = new AppServicePlanResource();
     const params = appServicePlanResource.getParameters(config);
 
-    expect(Object.keys(params)).toHaveLength(8);
+    expect(Object.keys(params)).toHaveLength(9);
     expect(params.appServicePlanName.value).toEqual(AppServicePlanResource.getResourceName(config));
     expect(params.appServicePlanSkuName.value).toBeUndefined();
     expect(params.appServicePlanSkuTier.value).toBeUndefined();
@@ -79,6 +79,37 @@ describe("App Service Plan Resource", () => {
     expect(params.appServicePlanMinWorkerCount.value).toBeUndefined();
     expect(params.appServicePlanMaxWorkerCount.value).toBeUndefined();
     expect(params.appServicePlanHostingEnvironment.value).toBeUndefined();
+    expect(params.appServicePlanReserved.value).toBeUndefined();
+  });
+
+  it("generates correct default parameters with linux function app", () => {
+    const config: ServerlessAzureConfig = {
+      provider: {
+        name: "azure",
+        prefix,
+        region,
+        stage,
+        resourceGroup: resourceGroupName,
+        runtime: Runtime.NODE10,
+        os: FunctionAppOS.LINUX,
+      },
+      plugins: [],
+      functions: {},
+      service: "myapp",
+    };
+
+    const appServicePlanResource = new AppServicePlanResource();
+    const params = appServicePlanResource.getParameters(config);
+
+    expect(Object.keys(params)).toHaveLength(9);
+    expect(params.appServicePlanName.value).toEqual(AppServicePlanResource.getResourceName(config));
+    expect(params.appServicePlanSkuName.value).toBeUndefined();
+    expect(params.appServicePlanSkuTier.value).toBeUndefined();
+    expect(params.appServicePlanWorkerSizeId.value).toBeUndefined();
+    expect(params.appServicePlanMinWorkerCount.value).toBeUndefined();
+    expect(params.appServicePlanMaxWorkerCount.value).toBeUndefined();
+    expect(params.appServicePlanHostingEnvironment.value).toBeUndefined();
+    expect(params.appServicePlanReserved.value).toBe(true);
   });
 
   it("generates correct specified parameters", () => {
@@ -112,7 +143,7 @@ describe("App Service Plan Resource", () => {
     const appServicePlanResource = new AppServicePlanResource();
     const params = appServicePlanResource.getParameters(config);
 
-    expect(Object.keys(params)).toHaveLength(8);
+    expect(Object.keys(params)).toHaveLength(9);
     expect(params.appServicePlanName.value).toEqual(config.provider.appServicePlan.name);
     expect(params.appServicePlanSkuName.value).toEqual(config.provider.appServicePlan.sku.name);
     expect(params.appServicePlanSkuTier.value).toEqual(config.provider.appServicePlan.sku.tier);
@@ -120,6 +151,7 @@ describe("App Service Plan Resource", () => {
     expect(params.appServicePlanMinWorkerCount.value).toEqual(config.provider.appServicePlan.scale.minWorkerCount);
     expect(params.appServicePlanMaxWorkerCount.value).toEqual(config.provider.appServicePlan.scale.maxWorkerCount);
     expect(params.appServicePlanHostingEnvironment.value).toEqual(config.provider.appServicePlan.hostingEnvironment);
+    expect(params.appServicePlanReserved.value).toBeUndefined();
   });
 
   it("generates the expected template", () => {
diff --git a/src/armTemplates/resources/functionApp.test.ts b/src/armTemplates/resources/functionApp.test.ts
index f422639..6b8e6db 100644
--- a/src/armTemplates/resources/functionApp.test.ts
+++ b/src/armTemplates/resources/functionApp.test.ts
@@ -239,12 +239,10 @@ describe("Function App Resource", () => {
       function assertLinuxParams(parameters: ArmParameters) {
         const { 
           functionAppKind,
-          functionAppReserved,
           functionAppEnableRemoteBuild,
         } = parameters;
 
         expect(functionAppKind.value).toEqual("functionapp,linux");
-        expect(functionAppReserved.value).toBe(true)
         expect(functionAppEnableRemoteBuild.value).toBe(true);
       }
     });
@@ -285,12 +283,10 @@ describe("Function App Resource", () => {
         const {
           linuxFxVersion,
           functionAppKind,
-          functionAppReserved,
         } = parameters;
 
         expect(functionAppKind.value).toBeUndefined();
         expect(linuxFxVersion.value).toBeUndefined();
-        expect(functionAppReserved.value).toBeUndefined();
       }
     });
   });