diff --git a/src/plugins/deploy/azureDeployPlugin.test.ts b/src/plugins/deploy/azureDeployPlugin.test.ts index 52b98fb1..7b1f3e00 100644 --- a/src/plugins/deploy/azureDeployPlugin.test.ts +++ b/src/plugins/deploy/azureDeployPlugin.test.ts @@ -57,75 +57,11 @@ describe("Deploy plugin", () => { expect(uploadFunctions).toBeCalledWith(functionAppStub); }); - it("does not call deploy if zip does not exist", async () => { - const deployResourceGroup = jest.fn(); - const functionAppStub: Site = MockFactory.createTestSite(); - const deploy = jest.fn(() => Promise.resolve(functionAppStub)); - const uploadFunctions = jest.fn(); - - const zipFile = "fake.zip"; - - FunctionAppService.prototype.getFunctionZipFile = (() => zipFile); - ResourceService.prototype.deployResourceGroup = deployResourceGroup; - FunctionAppService.prototype.deploy = deploy; - FunctionAppService.prototype.uploadFunctions = uploadFunctions; - - await invokeHook(plugin, "deploy:deploy"); - - expect(deployResourceGroup).not.toBeCalled(); - expect(deploy).not.toBeCalled(); - expect(uploadFunctions).not.toBeCalled(); - expect(sls.cli.log).lastCalledWith(`Function app zip file '${zipFile}' does not exist`); - }); - - it("lists deployments with timestamps", async () => { - const deployments = MockFactory.createTestDeployments(5, true); - ResourceService.prototype.getDeployments = jest.fn(() => Promise.resolve(deployments)); - - await invokeHook(plugin, "deploy:list:list"); - expect(ResourceService.prototype.getDeployments).toBeCalled(); - - let expectedLogStatement = "\n\nDeployments"; - const originalTimestamp = +MockFactory.createTestTimestamp(); - let i = 0 - for (const dep of deployments) { - const timestamp = originalTimestamp + i - expectedLogStatement += "\n-----------\n" - expectedLogStatement += `Name: ${dep.name}\n` - expectedLogStatement += `Timestamp: ${timestamp}\n`; - expectedLogStatement += `Datetime: ${new Date(timestamp).toISOString()}\n` - i++ - } - expectedLogStatement += "-----------\n" - expect(sls.cli.log).lastCalledWith(expectedLogStatement); - }); - - it("lists deployments without timestamps", async () => { - const deployments = MockFactory.createTestDeployments(); - ResourceService.prototype.getDeployments = jest.fn(() => Promise.resolve(deployments)); - + it("lists deployments", async () => { + const deploymentString = "deployments"; + ResourceService.prototype.listDeployments = jest.fn(() => Promise.resolve(deploymentString)); await invokeHook(plugin, "deploy:list:list"); - expect(ResourceService.prototype.getDeployments).toBeCalled(); - - let expectedLogStatement = "\n\nDeployments"; - for (const dep of deployments) { - expectedLogStatement += "\n-----------\n" - expectedLogStatement += `Name: ${dep.name}\n` - expectedLogStatement += "Timestamp: None\n"; - expectedLogStatement += "Datetime: None\n" - } - expectedLogStatement += "-----------\n" - expect(sls.cli.log).lastCalledWith(expectedLogStatement); - }); - - it("logs empty deployment list", async () => { - const resourceGroup = "rg1"; - ResourceService.prototype.getDeployments = jest.fn(() => Promise.resolve([])) as any; - ResourceService.prototype.getResourceGroupName = jest.fn(() => resourceGroup); - - await invokeHook(plugin, "deploy:list:list"); - expect(ResourceService.prototype.getDeployments).toBeCalled(); - - expect(sls.cli.log).lastCalledWith(`No deployments found for resource group '${resourceGroup}'`); + expect(ResourceService.prototype.listDeployments).toBeCalled(); + expect(sls.cli.log).lastCalledWith(deploymentString); }); }); diff --git a/src/plugins/deploy/azureDeployPlugin.ts b/src/plugins/deploy/azureDeployPlugin.ts index 70f48227..8e385603 100644 --- a/src/plugins/deploy/azureDeployPlugin.ts +++ b/src/plugins/deploy/azureDeployPlugin.ts @@ -3,7 +3,6 @@ import Serverless from "serverless"; import { FunctionAppService } from "../../services/functionAppService"; import { AzureLoginOptions } from "../../services/loginService"; import { ResourceService } from "../../services/resourceService"; -import { Utils } from "../../shared/utils"; import { AzureBasePlugin } from "../azureBasePlugin"; export class AzureDeployPlugin extends AzureBasePlugin { @@ -49,25 +48,7 @@ export class AzureDeployPlugin extends AzureBasePlugin { private async list() { this.log("Listing deployments"); const resourceService = new ResourceService(this.serverless, this.options); - const deployments = await resourceService.getDeployments(); - if (!deployments || deployments.length === 0) { - this.log(`No deployments found for resource group '${resourceService.getResourceGroupName()}'`); - return; - } - let stringDeployments = "\n\nDeployments"; - - for (const dep of deployments) { - stringDeployments += "\n-----------\n" - stringDeployments += `Name: ${dep.name}\n` - const timestampFromName = Utils.getTimestampFromName(dep.name); - stringDeployments += `Timestamp: ${(timestampFromName) ? timestampFromName : "None"}\n`; - - const dateTime = timestampFromName ? new Date(+timestampFromName).toISOString() : "None"; - stringDeployments += `Datetime: ${dateTime}\n` - } - - stringDeployments += "-----------\n" - this.log(stringDeployments); + this.log(await resourceService.listDeployments()); } private async deploy() { diff --git a/src/services/resourceService.test.ts b/src/services/resourceService.test.ts index bd223149..c78df520 100644 --- a/src/services/resourceService.test.ts +++ b/src/services/resourceService.test.ts @@ -1,16 +1,17 @@ +import { DeploymentsListByResourceGroupResponse } from "@azure/arm-resources/esm/models"; +import { Utils } from "../shared/utils"; import { MockFactory } from "../test/mockFactory"; import { ResourceService } from "./resourceService"; - jest.mock("@azure/arm-resources") import { ResourceManagementClient } from "@azure/arm-resources"; -import { Utils } from "../shared/utils"; describe("Resource Service", () => { - const deployments = MockFactory.createTestDeployments(); + let deployments: DeploymentsListByResourceGroupResponse; const template = "myTemplate"; - beforeAll(() => { + beforeEach(() => { + deployments = MockFactory.createTestDeployments(5, true); ResourceManagementClient.prototype.resourceGroups = { createOrUpdate: jest.fn(), deleteMethod: jest.fn(), @@ -80,7 +81,7 @@ describe("Resource Service", () => { .toBeCalledWith(resourceGroup); }); - it("lists deployments", async () => { + it("gets deployments", async () => { const sls = MockFactory.createTestServerless(); const resourceGroup = "myResourceGroup"; sls.service.provider["resourceGroup"] = resourceGroup @@ -91,6 +92,53 @@ describe("Resource Service", () => { expect(deps).toEqual(deployments); }); + it("lists deployments as string with timestamps", 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 deploymentString = await service.listDeployments(); + let expectedDeploymentString = "\n\nDeployments"; + const originalTimestamp = +MockFactory.createTestTimestamp(); + let i = 0 + for (const dep of deployments) { + const timestamp = originalTimestamp + i + expectedDeploymentString += "\n-----------\n" + expectedDeploymentString += `Name: ${dep.name}\n` + expectedDeploymentString += `Timestamp: ${timestamp}\n`; + expectedDeploymentString += `Datetime: ${new Date(timestamp).toISOString()}\n` + i++ + } + expectedDeploymentString += "-----------\n" + expect(deploymentString).toEqual(expectedDeploymentString); + }); + + it("lists deployments as string without timestamps", async () => { + deployments = MockFactory.createTestDeployments(); + ResourceManagementClient.prototype.deployments = { + listByResourceGroup: jest.fn(() => Promise.resolve(deployments)), + } as any; + + 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 deploymentString = await service.listDeployments(); + let expectedDeploymentString = "\n\nDeployments"; + for (const dep of deployments) { + expectedDeploymentString += "\n-----------\n" + expectedDeploymentString += `Name: ${dep.name}\n` + expectedDeploymentString += "Timestamp: None\n"; + expectedDeploymentString += "Datetime: None\n" + } + expectedDeploymentString += "-----------\n" + expect(deploymentString).toEqual(expectedDeploymentString); + }); + it("gets deployment template",async () => { const sls = MockFactory.createTestServerless(); const resourceGroup = "myResourceGroup"; @@ -107,4 +155,4 @@ describe("Resource Service", () => { ); expect(result).toEqual(template); }); -}); \ No newline at end of file +}); diff --git a/src/services/resourceService.ts b/src/services/resourceService.ts index 4b7dca09..2d751814 100644 --- a/src/services/resourceService.ts +++ b/src/services/resourceService.ts @@ -20,6 +20,31 @@ export class ResourceService extends BaseService { return await this.resourceClient.deployments.listByResourceGroup(this.resourceGroup); } + /** + * Returns stringified list of deployments with timestamps + */ + public async listDeployments(): Promise { + const deployments = await this.getDeployments() + if (!deployments || deployments.length === 0) { + this.log(`No deployments found for resource group '${this.getResourceGroupName()}'`); + return; + } + let stringDeployments = "\n\nDeployments"; + + for (const dep of deployments) { + stringDeployments += "\n-----------\n" + stringDeployments += `Name: ${dep.name}\n` + const timestampFromName = Utils.getTimestampFromName(dep.name); + stringDeployments += `Timestamp: ${(timestampFromName) ? timestampFromName : "None"}\n`; + + const dateTime = timestampFromName ? new Date(+timestampFromName).toISOString() : "None"; + stringDeployments += `Datetime: ${dateTime}\n` + } + + stringDeployments += "-----------\n" + return stringDeployments + } + /** * Get ARM template for previous deployment * @param deploymentName Name of deployment @@ -45,4 +70,4 @@ export class ResourceService extends BaseService { this.log(`Deleting resource group: ${this.resourceGroup}`); return await this.resourceClient.resourceGroups.deleteMethod(this.resourceGroup); } -} \ No newline at end of file +} diff --git a/src/services/rollbackService.test.ts b/src/services/rollbackService.test.ts index 2b0e51da..547bbe28 100644 --- a/src/services/rollbackService.test.ts +++ b/src/services/rollbackService.test.ts @@ -31,6 +31,7 @@ describe("Rollback Service", () => { const artifactName = MockFactory.createTestDeployment().name.replace("deployment", "artifact") + ".zip"; const artifactPath = `.serverless${path.sep}${artifactName}` const armDeployment: ArmDeployment = { template, parameters }; + const deploymentString = "deployments"; function createOptions(timestamp?: string): Serverless.Options { return { @@ -58,6 +59,7 @@ describe("Rollback Service", () => { ResourceService.prototype.getDeploymentTemplate = jest.fn( () => Promise.resolve({ template }) ) as any; + ResourceService.prototype.listDeployments = jest.fn(() => Promise.resolve(deploymentString)) AzureBlobStorageService.prototype.generateBlobSasTokenUrl = jest.fn(() => sasURL) as any; FunctionAppService.prototype.get = jest.fn(() => appStub) as any; }); @@ -72,8 +74,7 @@ describe("Rollback Service", () => { const options = {} as any; const service = createService(sls, options); await service.rollback(); - const calls = (sls.cli.log as any).mock.calls; - expect(calls[0][0]).toEqual("Need to specify a timestamp for rollback. Run `sls deploy list` to see timestamps of deployments"); + expect(sls.cli.log).lastCalledWith(deploymentString); }); it("should return early with invalid timestamp", async () => { diff --git a/src/services/rollbackService.ts b/src/services/rollbackService.ts index 0adb0bd0..a8576938 100644 --- a/src/services/rollbackService.ts +++ b/src/services/rollbackService.ts @@ -102,12 +102,14 @@ export class RollbackService extends BaseService { /** * Get deployment specified by timestamp in Serverless options + * Lists deployments if no timestamp is provided */ private async getDeployment(): Promise { let timestamp = Utils.get(this.options, "timestamp"); if (!timestamp) { - this.log("Need to specify a timestamp for rollback. Run `sls deploy list` to see timestamps of deployments"); + this.log("Need to specify a timestamp for rollback."); this.log("Example usage:\n\nsls rollback -t 1562014362"); + this.log(await this.resourceService.listDeployments()); return null; } const deployments = await this.getArmDeploymentsByTimestamp();