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

feat: Refactor runtime configuration to allow for non-node runtimes #348

Merged
merged 2 commits into from
Oct 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ node_modules/
/coverage/
.env*
.idea/

.vscode/launch.json
52 changes: 10 additions & 42 deletions src/armTemplates/resources/functionApp.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import { ArmResourceTemplate, ArmResourceTemplateGenerator, ArmParamType, ArmParameters, DefaultArmParams, ArmParameter } from "../../models/armTemplates";
import { FunctionAppConfig, ServerlessAzureConfig } from "../../models/serverless";
import { ArmParameter, ArmParameters, ArmParamType, ArmResourceTemplate, ArmResourceTemplateGenerator, DefaultArmParams } from "../../models/armTemplates";
import { FunctionAppConfig, ServerlessAzureConfig, SupportedRuntimeLanguage } from "../../models/serverless";
import { AzureNamingService, AzureNamingServiceOptions } from "../../services/namingService";

//Runtime versions found at " https://<sitename>.scm.azurewebsites.net/api/diagnostics/runtime".
import runtimeVersionsJson from "../../services/runtimeVersions.json";
import semver from "semver";

interface FunctionAppParams extends DefaultArmParams {
functionAppName: ArmParameter;
functionAppNodeVersion: ArmParameter;
Expand Down Expand Up @@ -132,18 +128,23 @@ export class FunctionAppResource implements ArmResourceTemplateGenerator {
public getParameters(config: ServerlessAzureConfig): ArmParameters {
const resourceConfig: FunctionAppConfig = {
...config.provider.functionApp,
nodeVersion: this.getRuntimeVersion(config.provider.runtime)
};
const { functionRuntime } = config.provider;


const params: FunctionAppParams = {
functionAppName: {
value: FunctionAppResource.getResourceName(config),
},
functionAppNodeVersion: {
value: resourceConfig.nodeVersion,
value: (functionRuntime.language === SupportedRuntimeLanguage.NODE)
?
functionRuntime.version
:
undefined
},
functionAppWorkerRuntime: {
value: resourceConfig.workerRuntime,
value: functionRuntime.language,
},
functionAppExtensionVersion: {
value: resourceConfig.extensionVersion,
Expand All @@ -152,37 +153,4 @@ export class FunctionAppResource implements ArmResourceTemplateGenerator {

return params as unknown as ArmParameters;
}

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")) {
let retrivedVersion: string;
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 selectedVersions = runtimeVersionsList.filter(({ version }) => {
return version.startsWith(extractedVersionNumber) && semver.valid(version)
}).map((item) => item.version);

if (!selectedVersions.length) {
throw new Error(`Could not find runtime version matching ${runtime}`)
}
return selectedVersions.sort(semver.rcompare)[0]
}
throw new Error(`Could not find runtime version matching ${runtime}`)
}
}
16 changes: 16 additions & 0 deletions src/models/serverless.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ export interface ServerlessAzureProvider {
armTemplate?: ArmTemplateConfig;
keyVaultConfig?: AzureKeyVaultConfig;
runtime: string;
functionRuntime?: FunctionRuntime;
}

export enum FunctionAppOS {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't look like this is being used anywhere? I'm assuming this will get used as Python support gets rolled in?

WINDOWS = "windows",
LINUX = "linux"
}

export interface FunctionRuntime {
language: SupportedRuntimeLanguage;
version: string;
}

export enum SupportedRuntimeLanguage {
PYTHON = "python",
NODE = "node"
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/services/apimService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ describe("APIM Service", () => {
resourceGroup: "test-sls-rg",
region: "West US",
apim: apimConfig,
runtime: "nodejs10.x"
},
};

Expand Down
35 changes: 0 additions & 35 deletions src/services/armService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,41 +92,6 @@ describe("Arm Service", () => {
await expect(service.createDeploymentFromType("not-found")).rejects.not.toBeNull();
});

it("throws error when invalid nodejs version in defined", async () => {
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";
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";
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";
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";
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";
await expect(service.createDeploymentFromType("premium")).resolves.not.toThrow();
});

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");
});

it("Premium template includes correct resources", async () => {
sls.service.provider.runtime = "nodejs10.14.1";
const deployment = await service.createDeploymentFromType(ArmTemplateType.Premium);
Expand Down
15 changes: 7 additions & 8 deletions src/services/armService.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { ResourceManagementClient } from "@azure/arm-resources";
import { Deployment, DeploymentExtended } from "@azure/arm-resources/esm/models";
import deepEqual from "deep-equal";
import fs from "fs";
import jsonpath from "jsonpath";
import path from "path";
import Serverless from "serverless";
import { ArmDeployment, ArmResourceTemplateGenerator, ArmTemplateType, ArmResourceTemplate, ArmParameters } from "../models/armTemplates";
import { ArmTemplateConfig, ServerlessAzureConfig, ServerlessAzureOptions } from "../models/serverless";
import { ArmDeployment, ArmParameters, ArmResourceTemplate, ArmResourceTemplateGenerator, ArmTemplateType } from "../models/armTemplates";
import { DeploymentExtendedError } from "../models/azureProvider";
import { ArmTemplateConfig, ServerlessAzureOptions } from "../models/serverless";
import { Guard } from "../shared/guard";
import { BaseService } from "./baseService";
import { ResourceService } from "./resourceService"
import { DeploymentExtendedError } from "../models/azureProvider";
import deepEqual from "deep-equal";
import { ResourceService } from "./resourceService";

export class ArmService extends BaseService {
private resourceClient: ResourceManagementClient;
Expand Down Expand Up @@ -39,13 +39,12 @@ export class ArmService extends BaseService {
throw new Error(`Unable to find template with name ${type} `);
}

const azureConfig: ServerlessAzureConfig = this.serverless.service as any;
const mergedTemplate = template.getTemplate();
let parameters = template.getParameters(azureConfig);
let parameters = template.getParameters(this.config);

if (this.config.provider.apim) {
const apimTemplate = apimResource.getTemplate();
const apimParameters = apimResource.getParameters(azureConfig);
const apimParameters = apimResource.getParameters(this.config);

mergedTemplate.parameters = {
...mergedTemplate.parameters,
Expand Down
3 changes: 2 additions & 1 deletion src/services/azureKeyVaultService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ describe("Azure Key Vault Service", () => {
name: "azure",
resourceGroup: "test-sls-rg",
region: "West US",
keyVault
keyVault,
runtime: "nodejs10.x",
}
};

Expand Down
1 change: 1 addition & 0 deletions src/services/baseService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ describe("Base Service", () => {
provider: {
resourceGroup: "My-Resource-Group",
deploymentName: "My-Deployment",
runtime: "nodejs10.x"
},
};

Expand Down
6 changes: 1 addition & 5 deletions src/services/baseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import fs from "fs";
import request from "request";
import Serverless from "serverless";
import { StorageAccountResource } from "../armTemplates/resources/storageAccount";
import {
ServerlessAzureConfig,
ServerlessAzureOptions,
ServerlessLogOptions
} from "../models/serverless";
import { ServerlessAzureConfig, ServerlessAzureOptions, ServerlessLogOptions } from "../models/serverless";
import { constants } from "../shared/constants";
import { Guard } from "../shared/guard";
import { Utils } from "../shared/utils";
Expand Down
80 changes: 77 additions & 3 deletions src/services/configService.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ConfigService } from "./configService";
import Serverless from "serverless";
import { MockFactory } from "../test/mockFactory";
import { ServerlessAzureConfig, DeploymentConfig } from "../models/serverless";
import configConstants from "../config";
import { DeploymentConfig, FunctionRuntime, ServerlessAzureConfig, SupportedRuntimeLanguage } from "../models/serverless";
import { MockFactory } from "../test/mockFactory";
import { ConfigService } from "./configService";
import { AzureNamingService } from "./namingService";

describe("Config Service", () => {
Expand Down Expand Up @@ -193,4 +193,78 @@ describe("Config Service", () => {
expect(serverless.service.provider["subscriptionId"]).toEqual(loginResultSubscriptionId);
});
});

describe("Runtime version", () => {
it("throws error when invalid nodejs version in defined", () => {
const sls = MockFactory.createTestServerless();
sls.service.provider.runtime = "nodejs10.6.1";
expect(() => new ConfigService(sls, {} as any)).toThrowError("Runtime nodejs10.6.1 is not supported");
expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toBeUndefined();
});

it("throws error when incomplete nodejs version in defined", () => {
const sls = MockFactory.createTestServerless();
sls.service.provider.runtime = "nodejs8";
expect(() => new ConfigService(sls, {} as any)).toThrowError("Invalid runtime: nodejs8");
expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toBeUndefined();
});

it("throws error when unsupported nodejs version in defined", () => {
const sls = MockFactory.createTestServerless();
sls.service.provider.runtime = "nodejs5.x";
expect(() => new ConfigService(sls, {} as any)).toThrowError("Runtime nodejs5.x is not supported");
expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toBeUndefined();
});

it("Does not throw an error when valid nodejs version is defined", () => {
const sls = MockFactory.createTestServerless();
sls.service.provider.runtime = "nodejs10.x";
expect(() => new ConfigService(sls, {} as any)).not.toThrow();
const expectedRuntime: FunctionRuntime = {
language: SupportedRuntimeLanguage.NODE,
version: "10.15.2"
}
expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toEqual(expectedRuntime);
});

it("Does not throw an error when nodejs version with major and minor is defined", () => {
const sls = MockFactory.createTestServerless();
sls.service.provider.runtime = "nodejs6.9.x";
expect(() => new ConfigService(sls, {} as any)).not.toThrow();
const expectedRuntime: FunctionRuntime = {
language: SupportedRuntimeLanguage.NODE,
version: "6.9.5"
}
expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toEqual(expectedRuntime);
});

it("Does not throw an error when specific nodejs version is defined", () => {
const sls = MockFactory.createTestServerless();
sls.service.provider.runtime = "nodejs10.6.0";
expect(() => new ConfigService(sls, {} as any)).not.toThrow();
const expectedRuntime: FunctionRuntime = {
language: SupportedRuntimeLanguage.NODE,
version: "10.6.0"
}
expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toEqual(expectedRuntime);
});

it("throws an error when no nodejs version is defined", () => {
const sls = MockFactory.createTestServerless();
sls.service.provider.runtime = undefined;
expect(() => new ConfigService(sls, {} as any)).toThrowError("Runtime version not specified in serverless.yml");
expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toBeUndefined();
});

it("does not throw an error with python3.6", () => {
const sls = MockFactory.createTestServerless();
sls.service.provider.runtime = "python3.6";
expect(() => new ConfigService(sls, {} as any)).not.toThrow();
const expectedRuntime: FunctionRuntime = {
language: SupportedRuntimeLanguage.PYTHON,
version: "3.6"
}
expect((sls.service as any as ServerlessAzureConfig).provider.functionRuntime).toEqual(expectedRuntime);
});
})
});
Loading