Repository: serverless/serverless-azure-functions
Pull request #178 (2019-06-25 00:00:21)

fix: Ensures the correct SCM domain is found for uploading zip package
The SCM domain is different based on the type of function app deployment.  In a typical function app, the SCM domain is always {appName}.scm.azurewebsites.net.

When deploying a function into an App Service Environment (ASE) the SCM domain is a sub domain off of the ASE, ex) {appName}.scm.{asePath}.p.azurewebsites.net

This fix addresses the difference in finding the correct SCM domain to deploy the zip package.


Patch:
diff --git a/src/armTemplates/resources/appInsights.ts b/src/armTemplates/resources/appInsights.ts
index 9e7df06..1223062 100644
--- a/src/armTemplates/resources/appInsights.ts
+++ b/src/armTemplates/resources/appInsights.ts
@@ -1,5 +1,5 @@
 import { ArmResourceTemplateGenerator, ArmResourceTemplate } from "../../models/armTemplates";
-import { ServerlessAzureConfig, ResourceConfig } from "../../models/serverless";
+import { ServerlessAzureConfig } from "../../models/serverless";
 
 export class AppInsightsResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
diff --git a/src/armTemplates/resources/hostingEnvironment.ts b/src/armTemplates/resources/hostingEnvironment.ts
index f785485..0591002 100644
--- a/src/armTemplates/resources/hostingEnvironment.ts
+++ b/src/armTemplates/resources/hostingEnvironment.ts
@@ -1,5 +1,5 @@
 import { ArmResourceTemplateGenerator, ArmResourceTemplate } from "../../models/armTemplates";
-import { ServerlessAzureConfig, ResourceConfig } from "../../models/serverless";
+import { ServerlessAzureConfig } from "../../models/serverless";
 
 export class HostingEnvironmentResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
diff --git a/src/armTemplates/resources/virtualNetwork.ts b/src/armTemplates/resources/virtualNetwork.ts
index b93448b..9c1feff 100644
--- a/src/armTemplates/resources/virtualNetwork.ts
+++ b/src/armTemplates/resources/virtualNetwork.ts
@@ -1,5 +1,5 @@
 import { ArmResourceTemplateGenerator, ArmResourceTemplate } from "../../models/armTemplates";
-import { ServerlessAzureConfig, ResourceConfig } from "../../models/serverless";
+import { ServerlessAzureConfig } from "../../models/serverless";
 
 export class VirtualNetworkResource implements ArmResourceTemplateGenerator {
   public static getResourceName(config: ServerlessAzureConfig) {
diff --git a/src/services/functionAppService.ts b/src/services/functionAppService.ts
index b4d26f6..e83fd6d 100644
--- a/src/services/functionAppService.ts
+++ b/src/services/functionAppService.ts
@@ -241,7 +241,7 @@ export class FunctionAppService extends BaseService {
    */
   private getScmDomain(functionApp: Site) {
     return functionApp.enabledHostNames.find((hostName: string) => {
-      return hostName.endsWith("scm.azurewebsites.net");
+      return hostName.includes(".scm.") && hostName.endsWith(".azurewebsites.net");
     });
   }
 }


Test patch:
diff --git a/src/services/functionAppService.test.ts b/src/services/functionAppService.test.ts
index 51aa527..03b135e 100644
--- a/src/services/functionAppService.test.ts
+++ b/src/services/functionAppService.test.ts
@@ -13,7 +13,6 @@ import { ArmDeployment, ArmTemplateType } from "../models/armTemplates";
 jest.mock("@azure/arm-resources")
 
 describe("Function App Service", () => {
-
   const app = MockFactory.createTestSite();
   const slsService = MockFactory.createTestService();
   const variables = MockFactory.createTestVariables();
@@ -31,12 +30,8 @@ describe("Function App Service", () => {
   const authKeyUrl = `${baseUrl}${app.id}/functions/admin/token?api-version=2016-08-01`;
   const syncTriggersUrl = `${baseUrl}${app.id}/syncfunctiontriggers?api-version=2016-08-01`;
   const listFunctionsUrl = `${baseUrl}${app.id}/functions?api-version=2016-08-01`;
-  const scmDomain = app.enabledHostNames.find((hostname) => hostname.endsWith("scm.azurewebsites.net"));
-  const uploadUrl = `https://${scmDomain}/api/zipdeploy/`;
 
   beforeAll(() => {
-
-    // TODO: How to spy on default exported function?
     const axiosMock = new MockAdapter(axios);
 
     // Master Key
@@ -59,7 +54,6 @@ describe("Function App Service", () => {
   });
 
   beforeEach(() => {
-
     WebSiteManagementClient.prototype.webApps = {
       get: jest.fn(() => app),
       deleteFunction: jest.fn(),
@@ -197,11 +191,41 @@ describe("Function App Service", () => {
   });
 
   it("uploads functions", async () => {
+    const scmDomain = app.enabledHostNames.find((hostname) => hostname.endsWith("scm.azurewebsites.net"));
+    const expectedUploadUrl = `https://${scmDomain}/api/zipdeploy/`;
+
     const service = createService();
     await service.uploadFunctions(app);
+
+    expect((FunctionAppService.prototype as any).sendFile).toBeCalledWith({
+      method: "POST",
+      uri: expectedUploadUrl,
+      json: true,
+      headers: {
+        Authorization: `Bearer ${variables["azureCredentials"].tokenCache._entries[0].accessToken}`,
+        Accept: "*/*",
+        ContentType: "application/octet-stream",
+      }
+    }, slsService["artifact"])
+  });
+
+  it("uploads functions with custom SCM domain (aka App service environments)", async () => {
+    const customApp = {
+      ...MockFactory.createTestSite("CustomAppWithinASE"),
+      enabledHostNames: [
+        "myapi.customase.p.azurewebsites.net",
+        "myapi.scm.customase.p.azurewebsites.net"
+      ],
+    }
+
+    const expectedUploadUrl = `https://${customApp.enabledHostNames[1]}/api/zipdeploy/`;
+
+    const service = createService();
+    await service.uploadFunctions(customApp);
+
     expect((FunctionAppService.prototype as any).sendFile).toBeCalledWith({
       method: "POST",
-      uri: uploadUrl,
+      uri: expectedUploadUrl,
       json: true,
       headers: {
         Authorization: `Bearer ${variables["azureCredentials"].tokenCache._entries[0].accessToken}`,