diff --git a/src/plugins/deploy/azureDeployPlugin.test.ts b/src/plugins/deploy/azureDeployPlugin.test.ts index 2818890a..3c3db2f2 100644 --- a/src/plugins/deploy/azureDeployPlugin.test.ts +++ b/src/plugins/deploy/azureDeployPlugin.test.ts @@ -10,6 +10,11 @@ import { ResourceService } from "../../services/resourceService"; 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(); @@ -30,4 +35,33 @@ describe("Deploy plugin", () => { expect(deploy).toBeCalled(); expect(uploadFunctions).toBeCalledWith(functionAppStub); }); + + it("lists deployments", async () => { + const deployments = MockFactory.createTestDeployments(); + ResourceService.prototype.getDeployments = 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); + }); + + 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}'`); + }); }); diff --git a/src/plugins/deploy/azureDeployPlugin.ts b/src/plugins/deploy/azureDeployPlugin.ts index 48f09674..76c88ebc 100644 --- a/src/plugins/deploy/azureDeployPlugin.ts +++ b/src/plugins/deploy/azureDeployPlugin.ts @@ -4,11 +4,46 @@ 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.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` + 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/baseService.ts b/src/services/baseService.ts index 3f3c73a4..4a81638e 100644 --- a/src/services/baseService.ts +++ b/src/services/baseService.ts @@ -33,6 +33,10 @@ export abstract class BaseService { } } + public getResourceGroup(): string { + return this.resourceGroup; + } + /** * Sends an API request using axios HTTP library * @param method The HTTP method diff --git a/src/services/resourceService.test.ts b/src/services/resourceService.test.ts index f664b32b..22ac5ad1 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.getDeployments(); + expect(deps).toEqual(deployments); + }); }); \ No newline at end of file diff --git a/src/services/resourceService.ts b/src/services/resourceService.ts index 8d47c01c..e36dd909 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 getDeployments() { + 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 ac72bc39..2a7ede24 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,