From 5b99544c63d4204b7629e8342a8144885c8a5f4f Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Fri, 21 Jun 2019 08:57:45 -0700 Subject: [PATCH 1/4] feat: List deployments subcommand for deploy plugin --- src/plugins/deploy/azureDeployPlugin.test.ts | 19 ++++++++++++ src/plugins/deploy/azureDeployPlugin.ts | 32 +++++++++++++++++++- src/plugins/login/loginPlugin.ts | 3 +- src/services/resourceService.test.ts | 15 ++++++++- src/services/resourceService.ts | 5 +++ src/test/mockFactory.ts | 14 +++++++++ 6 files changed, 85 insertions(+), 3 deletions(-) diff --git a/src/plugins/deploy/azureDeployPlugin.test.ts b/src/plugins/deploy/azureDeployPlugin.test.ts index 2818890a..cb3b7aca 100644 --- a/src/plugins/deploy/azureDeployPlugin.test.ts +++ b/src/plugins/deploy/azureDeployPlugin.test.ts @@ -10,6 +10,7 @@ import { ResourceService } from "../../services/resourceService"; import { Site } from "@azure/arm-appservice/esm/models"; describe("Deploy plugin", () => { + it("calls deploy hook", async () => { const deployResourceGroup = jest.fn(); const functionAppStub: Site = MockFactory.createTestSite(); @@ -30,4 +31,22 @@ describe("Deploy plugin", () => { expect(deploy).toBeCalled(); expect(uploadFunctions).toBeCalledWith(functionAppStub); }); + + it("lists deployments", async () => { + const deployments = MockFactory.createTestDeployments(); + ResourceService.prototype.listDeployments = jest.fn(() => Promise.resolve(deployments)); + const sls = MockFactory.createTestServerless(); + const options = MockFactory.createTestServerlessOptions(); + const plugin = new AzureDeployPlugin(sls, options); + await invokeHook(plugin, "deploy:list:list"); + let expectedLogStatement = "\n\nDeployments"; + for (const dep of deployments) { + expectedLogStatement += "\n-----------\n" + expectedLogStatement += `Name: ${dep.name}\n` + expectedLogStatement += `Timestamp: ${dep.properties.timestamp.getTime()}\n`; + expectedLogStatement += `Datetime: ${dep.properties.timestamp.toISOString()}\n` + } + expectedLogStatement += "-----------\n" + expect(sls.cli.log).lastCalledWith(expectedLogStatement); + }); }); diff --git a/src/plugins/deploy/azureDeployPlugin.ts b/src/plugins/deploy/azureDeployPlugin.ts index 48f09674..95d3b458 100644 --- a/src/plugins/deploy/azureDeployPlugin.ts +++ b/src/plugins/deploy/azureDeployPlugin.ts @@ -4,11 +4,41 @@ import { FunctionAppService } from "../../services/functionAppService"; export class AzureDeployPlugin { public hooks: { [eventName: string]: Promise }; + public commands: any; public constructor(private serverless: Serverless, private options: Serverless.Options) { this.hooks = { - "deploy:deploy": this.deploy.bind(this) + "deploy:deploy": this.deploy.bind(this), + "deploy:list:list": this.list.bind(this), }; + + this.commands = { + deploy: { + commands: { + list: { + usage: "List deployments", + lifecycleEvents: [ + "list" + ] + } + } + } + } + } + + private async list() { + this.serverless.cli.log("Listing deployments"); + const resourceService = new ResourceService(this.serverless, this.options); + const deployments = await resourceService.listDeployments(); + let stringDeployments = "\n\nDeployments"; + for (const dep of deployments) { + stringDeployments += "\n-----------\n" + stringDeployments += `Name: ${dep.name}\n` + stringDeployments += `Timestamp: ${dep.properties.timestamp.getTime()}\n`; + stringDeployments += `Datetime: ${dep.properties.timestamp.toISOString()}\n` + } + stringDeployments += "-----------\n" + this.serverless.cli.log(stringDeployments); } private async deploy() { diff --git a/src/plugins/login/loginPlugin.ts b/src/plugins/login/loginPlugin.ts index d64635e6..083831f6 100644 --- a/src/plugins/login/loginPlugin.ts +++ b/src/plugins/login/loginPlugin.ts @@ -10,7 +10,8 @@ export class AzureLoginPlugin { this.provider = (this.serverless.getProvider("azure") as any) as AzureProvider; this.hooks = { - "before:package:initialize": this.login.bind(this) + "before:package:initialize": this.login.bind(this), + "before:deploy:list:list": this.login.bind(this), }; } diff --git a/src/services/resourceService.test.ts b/src/services/resourceService.test.ts index 3ce6df36..cde675a7 100644 --- a/src/services/resourceService.test.ts +++ b/src/services/resourceService.test.ts @@ -6,6 +6,7 @@ jest.mock("@azure/arm-resources") import { ResourceManagementClient } from "@azure/arm-resources"; describe("Resource Service", () => { + const deployments = MockFactory.createTestDeployments(); beforeAll(() => { ResourceManagementClient.prototype.resourceGroups = { @@ -14,7 +15,8 @@ describe("Resource Service", () => { } as any; ResourceManagementClient.prototype.deployments = { - deleteMethod: jest.fn() + deleteMethod: jest.fn(), + listByResourceGroup: jest.fn(() => Promise.resolve(deployments)), } as any; }); @@ -70,4 +72,15 @@ describe("Resource Service", () => { expect(ResourceManagementClient.prototype.resourceGroups.deleteMethod) .toBeCalledWith(resourceGroup); }); + + it("lists deployments", async () => { + const sls = MockFactory.createTestServerless(); + const resourceGroup = "myResourceGroup"; + sls.service.provider["resourceGroup"] = resourceGroup + sls.variables["azureCredentials"] = "fake credentials" + const options = MockFactory.createTestServerlessOptions(); + const service = new ResourceService(sls, options); + const deps = await service.listDeployments(); + expect(deps).toEqual(deployments); + }); }); \ No newline at end of file diff --git a/src/services/resourceService.ts b/src/services/resourceService.ts index 7bd46530..632f7bb6 100644 --- a/src/services/resourceService.ts +++ b/src/services/resourceService.ts @@ -11,6 +11,11 @@ export class ResourceService extends BaseService { this.resourceClient = new ResourceManagementClient(this.credentials, this.subscriptionId); } + public async listDeployments() { + this.log(`Listing deployments for resource group '${this.resourceGroup}':`); + return await this.resourceClient.deployments.listByResourceGroup(this.resourceGroup); + } + public async deployResourceGroup() { this.log(`Creating resource group: ${this.resourceGroup}`); diff --git a/src/test/mockFactory.ts b/src/test/mockFactory.ts index 7efaaa32..cb1ebaf5 100644 --- a/src/test/mockFactory.ts +++ b/src/test/mockFactory.ts @@ -13,6 +13,7 @@ import { ServerlessAzureConfig } from "../models/serverless"; import { AzureServiceProvider, ServicePrincipalEnvVariables } from "../models/azureProvider" import { Logger } from "../models/generic"; import { ApiCorsPolicy } from "../models/apiManagement"; +import { DeploymentsListByResourceGroupResponse } from "@azure/arm-resources/esm/models"; function getAttribute(object: any, prop: string, defaultValue: any): any { if (object && object[prop]) { @@ -140,6 +141,19 @@ export class MockFactory { return credentials; } + public static createTestDeployments(count: number = 5): DeploymentsListByResourceGroupResponse { + const result = []; + for (let i = 0; i < count; i++) { + result.push({ + name: `deployment${i+1}`, + properties: { + timestamp: new Date(), + } + }) + } + return result as DeploymentsListByResourceGroupResponse + } + public static createTestAxiosResponse( config: AxiosRequestConfig, responseJson: T, From 518144ce0cac0b082048dff1681dca317c05954a Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Fri, 21 Jun 2019 09:19:51 -0700 Subject: [PATCH 2/4] fix: Respond to PR feedback --- src/plugins/deploy/azureDeployPlugin.test.ts | 2 +- src/plugins/deploy/azureDeployPlugin.ts | 7 ++++++- src/services/baseService.ts | 4 ++++ src/services/resourceService.test.ts | 2 +- src/services/resourceService.ts | 2 +- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/plugins/deploy/azureDeployPlugin.test.ts b/src/plugins/deploy/azureDeployPlugin.test.ts index cb3b7aca..ae0b14cb 100644 --- a/src/plugins/deploy/azureDeployPlugin.test.ts +++ b/src/plugins/deploy/azureDeployPlugin.test.ts @@ -34,7 +34,7 @@ describe("Deploy plugin", () => { it("lists deployments", async () => { const deployments = MockFactory.createTestDeployments(); - ResourceService.prototype.listDeployments = jest.fn(() => Promise.resolve(deployments)); + ResourceService.prototype.getDeployments = jest.fn(() => Promise.resolve(deployments)); const sls = MockFactory.createTestServerless(); const options = MockFactory.createTestServerlessOptions(); const plugin = new AzureDeployPlugin(sls, options); diff --git a/src/plugins/deploy/azureDeployPlugin.ts b/src/plugins/deploy/azureDeployPlugin.ts index 95d3b458..76c88ebc 100644 --- a/src/plugins/deploy/azureDeployPlugin.ts +++ b/src/plugins/deploy/azureDeployPlugin.ts @@ -29,8 +29,13 @@ export class AzureDeployPlugin { private async list() { this.serverless.cli.log("Listing deployments"); const resourceService = new ResourceService(this.serverless, this.options); - const deployments = await resourceService.listDeployments(); + const deployments = await resourceService.getDeployments(); + if (!deployments || deployments.length === 0) { + this.serverless.cli.log(`No deployments found for resource group '${resourceService.getResourceGroup()}'`); + return; + } let stringDeployments = "\n\nDeployments"; + for (const dep of deployments) { stringDeployments += "\n-----------\n" stringDeployments += `Name: ${dep.name}\n` diff --git a/src/services/baseService.ts b/src/services/baseService.ts index d95bd169..ccfa9a7b 100644 --- a/src/services/baseService.ts +++ b/src/services/baseService.ts @@ -26,6 +26,10 @@ export abstract class BaseService { throw new Error(`Azure Credentials has not been set in ${this.constructor.name}`); } } + + public getResourceGroup(): string { + return this.resourceGroup; + } /** * Sends an API request using axios HTTP library diff --git a/src/services/resourceService.test.ts b/src/services/resourceService.test.ts index cde675a7..652cb05e 100644 --- a/src/services/resourceService.test.ts +++ b/src/services/resourceService.test.ts @@ -80,7 +80,7 @@ describe("Resource Service", () => { sls.variables["azureCredentials"] = "fake credentials" const options = MockFactory.createTestServerlessOptions(); const service = new ResourceService(sls, options); - const deps = await service.listDeployments(); + const deps = await service.getDeployments(); expect(deps).toEqual(deployments); }); }); \ No newline at end of file diff --git a/src/services/resourceService.ts b/src/services/resourceService.ts index 632f7bb6..b8edd6de 100644 --- a/src/services/resourceService.ts +++ b/src/services/resourceService.ts @@ -11,7 +11,7 @@ export class ResourceService extends BaseService { this.resourceClient = new ResourceManagementClient(this.credentials, this.subscriptionId); } - public async listDeployments() { + public async getDeployments() { this.log(`Listing deployments for resource group '${this.resourceGroup}':`); return await this.resourceClient.deployments.listByResourceGroup(this.resourceGroup); } From c26effbee8337cd63297ccc1140112b8495b4fae Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Fri, 21 Jun 2019 09:23:09 -0700 Subject: [PATCH 3/4] fix: Fix lint error --- src/services/baseService.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/services/baseService.ts b/src/services/baseService.ts index faa5c0bf..4a81638e 100644 --- a/src/services/baseService.ts +++ b/src/services/baseService.ts @@ -33,13 +33,10 @@ export abstract class BaseService { } } -<<<<<<< HEAD public getResourceGroup(): string { return this.resourceGroup; } -======= ->>>>>>> 0d32d2d4f50ec4e126cf99e7707a99ffa65a29ed /** * Sends an API request using axios HTTP library * @param method The HTTP method From 90cc95866d0132b7d8113a733afbbf8767a1d043 Mon Sep 17 00:00:00 2001 From: Tanner Barlow Date: Fri, 21 Jun 2019 13:22:48 -0700 Subject: [PATCH 4/4] Add test for empty deployments --- src/plugins/deploy/azureDeployPlugin.test.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/plugins/deploy/azureDeployPlugin.test.ts b/src/plugins/deploy/azureDeployPlugin.test.ts index ae0b14cb..3c3db2f2 100644 --- a/src/plugins/deploy/azureDeployPlugin.test.ts +++ b/src/plugins/deploy/azureDeployPlugin.test.ts @@ -11,6 +11,10 @@ import { Site } from "@azure/arm-appservice/esm/models"; describe("Deploy plugin", () => { + afterEach(() => { + jest.resetAllMocks(); + }) + it("calls deploy hook", async () => { const deployResourceGroup = jest.fn(); const functionAppStub: Site = MockFactory.createTestSite(); @@ -49,4 +53,15 @@ describe("Deploy plugin", () => { expectedLogStatement += "-----------\n" expect(sls.cli.log).lastCalledWith(expectedLogStatement); }); + + it("logs empty deployment list", async () => { + const sls = MockFactory.createTestServerless(); + const resourceGroup = "rg1"; + ResourceService.prototype.getDeployments = jest.fn(() => Promise.resolve([])) as any; + ResourceService.prototype.getResourceGroup = jest.fn(() => resourceGroup); + const options = MockFactory.createTestServerlessOptions(); + const plugin = new AzureDeployPlugin(sls, options); + await invokeHook(plugin, "deploy:list:list"); + expect(sls.cli.log).lastCalledWith(`No deployments found for resource group '${resourceGroup}'`); + }); });