Skip to content
This repository has been archived by the owner on Dec 9, 2024. It is now read-only.

Commit

Permalink
test: Verify ARM deployments
Browse files Browse the repository at this point in the history
  • Loading branch information
wbreza committed Jun 21, 2019
1 parent db59819 commit c49b66c
Show file tree
Hide file tree
Showing 10 changed files with 284 additions and 60 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"devDependencies": {
"@babel/runtime": "^7.4.5",
"@types/jest": "^24.0.13",
"@types/jsonpath": "^0.2.0",
"@types/lodash": "^4.14.130",
"@types/mock-fs": "^3.6.30",
"@types/open": "^6.1.0",
Expand Down
16 changes: 15 additions & 1 deletion src/armTemplates/ase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,25 @@ const AseTemplate: ArmResourceTemplateGenerator = {
template.parameters.appServicePlanSkuName.defaultValue = "I1";
template.parameters.appServicePlanSkuTier.defaultValue = "Isolated";

// Update the app service plan to point to the hosting environment
const appServicePlan: any = template.resources.find((resource: any) => resource.type === "Microsoft.Web/serverfarms");
if (appServicePlan) {
appServicePlan.dependsOn = [...(appServicePlan.dependsOn || []), "[resourceId('Microsoft.Web/hostingEnvironments', parameters('hostingEnvironmentName'))]"];
appServicePlan.properties.hostingEnvironmentProfile = {
...appServicePlan.properties.hostingEnvironmentProfile,
id: "[resourceId('Microsoft.Web/hostingEnvironments', parameters('hostingEnvironmentName'))]",
}
}

// Update the functionApp resource to include the app service plan references
const app: any = template.resources.find((resource: any) => resource.type === "Microsoft.Web/sites");
if (app) {
app.dependsOn = [...(app.dependsOn || []), "[concat('Microsoft.Web/serverfarms/', parameters('appServicePlanName'))]"];
app.properties.serverFarmId = "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]";
app.dependsOn.push("[concat('Microsoft.Web/serverfarms/', parameters('appServicePlanName'))]")
app.properties.hostingEnvironmentProfile = {
...app.properties.hostingEnvironmentProfile,
id: "[resourceId('Microsoft.Web/hostingEnvironments', parameters('hostingEnvironmentName'))]",
}
}

return template;
Expand Down
2 changes: 1 addition & 1 deletion src/armTemplates/premium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const PremiumTemplate: ArmResourceTemplateGenerator = {
const app: any = template.resources.find((resource: any) => resource.type === "Microsoft.Web/sites");
if (app) {
app.properties.serverFarmId = "[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]";
app.dependsOn.push("[concat('Microsoft.Web/serverfarms/', parameters('appServicePlanName'))]")
app.dependsOn = [...(app.dependsOn || []), "[concat('Microsoft.Web/serverfarms/', parameters('appServicePlanName'))]"]
}

return template;
Expand Down
18 changes: 15 additions & 3 deletions src/armTemplates/resources/functionApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ export const FunctionAppResource: ArmResourceTemplateGenerator = {
"defaultValue": "",
"type": "String"
},
"functionAppNodeVersion": {
"defaultValue": "10.14.1",
"type": "String"
},
"functionAppWorkerRuntime": {
"defaultValue": "node",
"type": "String"
},
"functionAppExtensionVersion": {
"defaultValue": "~2",
"type": "String"
},
"storageAccountName": {
"defaultValue": "",
"type": "String"
Expand Down Expand Up @@ -41,11 +53,11 @@ export const FunctionAppResource: ArmResourceTemplateGenerator = {
"appSettings": [
{
"name": "FUNCTIONS_WORKER_RUNTIME",
"value": "node"
"value": "[parameters('functionAppWorkerRuntime')]"
},
{
"name": "FUNCTIONS_EXTENSION_VERSION",
"value": "~2"
"value": "[parameters('functionAppExtensionVersion')]"
},
{
"name": "AzureWebJobsStorage",
Expand All @@ -61,7 +73,7 @@ export const FunctionAppResource: ArmResourceTemplateGenerator = {
},
{
"name": "WEBSITE_NODE_DEFAULT_VERSION",
"value": "10.14.1"
"value": "[parameters('functionAppNodeVersion')]"
},
{
"name": "WEBSITE_RUN_FROM_PACKAGE",
Expand Down
4 changes: 3 additions & 1 deletion src/models/serverless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export enum DeploymentType {
}

export interface ArmTemplateConfig {
type: string;
file: string;
parameters:
{
Expand All @@ -31,6 +30,9 @@ export interface ServerlessAzureConfig {
region: string;
stage: string;
name: string;
environment?: {
[key: string]: any;
};
resourceGroup?: string;
apim?: ApiManagementConfig;
functionApp?: ResourceConfig;
Expand Down
117 changes: 109 additions & 8 deletions src/services/armService.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import Serverless from "serverless";
import { MockFactory } from "../test/mockFactory";
import { ArmService } from "./armService";
import { ArmService, ArmResourceTemplate, ArmTemplateType } from "./armService";
import { ArmTemplateConfig } from "../models/serverless";
import mockFs from "mock-fs";
import jsonpath from "jsonpath";
import { Deployments } from "@azure/arm-resources";
import { Deployment } from "@azure/arm-resources/esm/models";

describe("Arm Service", () => {
let sls: Serverless
Expand All @@ -24,9 +29,54 @@ describe("Arm Service", () => {
service = createService();
})

afterEach(() => {
mockFs.restore();
})

describe("Creating Templates", () => {
it("Creates an ARM template from a specified file", async () => {
const armTemplateConfig: ArmTemplateConfig = {
file: "armTemplates/custom-template.json",
parameters: {
param1: "1",
param2: "2",
},
};

const testTemplate: ArmResourceTemplate = {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"param1": {
"defaultValue": "",
"type": "String"
},
"param2": {
"defaultValue": "",
"type": "String"
},
},
"variables": {},
"resources": []
};

mockFs({
"armTemplates": {
"custom-template.json": JSON.stringify(testTemplate),
},
});

sls.service.provider["armTemplate"] = armTemplateConfig;
const deployment = await service.createDeploymentFromConfig(sls.service.provider["armTemplate"]);

expect(deployment).not.toBeNull();
expect(deployment.template.parameters).toEqual(testTemplate.parameters);
expect(deployment.template.resources).toEqual(testTemplate.resources);
expect(deployment.parameters).toEqual(armTemplateConfig.parameters);
});

it("Creates a custom ARM template from well-known type", async () => {
const deployment = await service.createDeployment("premium");
const deployment = await service.createDeploymentFromType("premium");

expect(deployment).not.toBeNull();
expect(Object.keys(deployment.parameters).length).toBeGreaterThan(0);
Expand All @@ -35,7 +85,7 @@ describe("Arm Service", () => {

it("Creates a custom ARM template (with APIM support) from well-known type", async () => {
sls.service.provider["apim"] = MockFactory.createTestApimConfig();
const deployment = await service.createDeployment("premium");
const deployment = await service.createDeploymentFromType(ArmTemplateType.Premium);

expect(deployment).not.toBeNull();
expect(Object.keys(deployment.parameters).length).toBeGreaterThan(0);
Expand All @@ -45,11 +95,11 @@ describe("Arm Service", () => {
});

it("throws error when specified type is not found", async () => {
await expect(service.createDeployment("not-found")).rejects.not.toBeNull();
await expect(service.createDeploymentFromType("not-found")).rejects.not.toBeNull();
});

it("Premium template includes correct resources", async () => {
const deployment = await service.createDeployment("premium");
const deployment = await service.createDeploymentFromType(ArmTemplateType.Premium);

expect(deployment.template.parameters.appServicePlanSkuTier.defaultValue).toEqual("ElasticPremium");
expect(deployment.template.parameters.appServicePlanSkuName.defaultValue).toEqual("EP1");
Expand All @@ -63,15 +113,15 @@ describe("Arm Service", () => {
expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Web/sites")).not.toBeNull();
expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Storage/storageAccounts")).not.toBeNull();
expect(deployment.template.resources.find((resource) => resource.type === "microsoft.insights/components")).not.toBeNull();

// Verify the ARM template includes the linkage to the correct server farm
const functionApp = deployment.template.resources.find((res) => res.type === "Microsoft.Web/sites");
expect(functionApp.dependsOn).toContain("[concat('Microsoft.Web/serverfarms/', parameters('appServicePlanName'))]");
expect(functionApp.properties.serverFarmId).toEqual("[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]");
});

it("ASE template includes correct resources", async () => {
const deployment = await service.createDeployment("ase");
const deployment = await service.createDeploymentFromType(ArmTemplateType.AppServiceEnvironment);

expect(deployment.template.parameters.appServicePlanSkuTier.defaultValue).toEqual("Isolated");
expect(deployment.template.parameters.appServicePlanSkuName.defaultValue).toEqual("I1");
Expand All @@ -84,13 +134,19 @@ describe("Arm Service", () => {
expect(deployment.template.resources.find((resource) => resource.type === "microsoft.insights/components")).not.toBeNull();

// Verify the ARM template includes the linkage to the correct server farm
const appServicePlan = deployment.template.resources.find((res) => res.type === "Microsoft.Web/serverfarms");
expect(appServicePlan.dependsOn).toContain("[resourceId('Microsoft.Web/hostingEnvironments', parameters('hostingEnvironmentName'))]");
expect(appServicePlan.properties.hostingEnvironmentProfile.id).toEqual("[resourceId('Microsoft.Web/hostingEnvironments', parameters('hostingEnvironmentName'))]");

// Verify the ARM template includes the linkage to the correct hosting environment
const functionApp = deployment.template.resources.find((res) => res.type === "Microsoft.Web/sites");
expect(functionApp.dependsOn).toContain("[concat('Microsoft.Web/serverfarms/', parameters('appServicePlanName'))]");
expect(functionApp.properties.serverFarmId).toEqual("[resourceId('Microsoft.Web/serverfarms', parameters('appServicePlanName'))]");
expect(functionApp.properties.hostingEnvironmentProfile.id).toEqual("[resourceId('Microsoft.Web/hostingEnvironments', parameters('hostingEnvironmentName'))]");
});

it("Consumption template includes correct resources", async () => {
const deployment = await service.createDeployment("consumption");
const deployment = await service.createDeploymentFromType(ArmTemplateType.Consumption);

expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Web/hostingEnvironments")).toBeUndefined();
expect(deployment.template.resources.find((resource) => resource.type === "Microsoft.Network/virtualNetworks")).toBeUndefined();
Expand All @@ -104,6 +160,51 @@ describe("Arm Service", () => {
});

describe("Deploying Templates", () => {
beforeEach(() => {
Deployments.prototype.createOrUpdate = jest.fn(() => Promise.resolve(null));
});

it("Appends environment variables into app settings of ARM template", async () => {
const environmentConfig: any = {
PARAM_1: "1",
PARAM_2: "2",
PARAM_3: "3",
};

sls.service.provider["environment"] = environmentConfig

const deployment = await service.createDeploymentFromType(ArmTemplateType.Consumption);
await service.deployTemplate(deployment);

const appSettings: any[] = jsonpath.query(deployment.template, "$.resources[?(@.kind==\"functionapp\")].properties.siteConfig.appSettings[*]");
expect(appSettings.find((setting) => setting.name === "PARAM_1")).toEqual({ name: "PARAM_1", value: environmentConfig.PARAM_1 });
expect(appSettings.find((setting) => setting.name === "PARAM_2")).toEqual({ name: "PARAM_2", value: environmentConfig.PARAM_2 });
expect(appSettings.find((setting) => setting.name === "PARAM_3")).toEqual({ name: "PARAM_3", value: environmentConfig.PARAM_3 });
});

it("Deploys ARM template via resources REST API", async () => {
const deployment = await service.createDeploymentFromType(ArmTemplateType.Consumption);
const deploymentParameters = {};
Object.keys(deployment.parameters).forEach((key) => {
const parameterValue = deployment.parameters[key];
if (parameterValue) {
deploymentParameters[key] = { value: deployment.parameters[key] };
}
});

await service.deployTemplate(deployment);

const expectedResourceGroup = sls.service.provider["resourceGroup"];
const expectedDeploymentName = sls.service.provider["deploymentName"] || `${this.resourceGroup}-deployment`;
const expectedDeployment: Deployment = {
properties: {
mode: "Incremental",
template: deployment.template,
parameters: deploymentParameters,
},
};

expect(Deployments.prototype.createOrUpdate).toBeCalledWith(expectedResourceGroup, expectedDeploymentName, expectedDeployment);
});
});
});
Loading

0 comments on commit c49b66c

Please sign in to comment.