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

feat: List deployments for rollback when no timestamp given #208

Merged
merged 1 commit into from
Jul 22, 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
74 changes: 5 additions & 69 deletions src/plugins/deploy/azureDeployPlugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
21 changes: 1 addition & 20 deletions src/plugins/deploy/azureDeployPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<AzureLoginOptions> {
Expand Down Expand Up @@ -49,25 +48,7 @@ export class AzureDeployPlugin extends AzureBasePlugin<AzureLoginOptions> {
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() {
Expand Down
60 changes: 54 additions & 6 deletions src/services/resourceService.test.ts
Original file line number Diff line number Diff line change
@@ -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(),
Expand Down Expand Up @@ -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
Expand All @@ -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";
Expand All @@ -107,4 +155,4 @@ describe("Resource Service", () => {
);
expect(result).toEqual(template);
});
});
});
27 changes: 26 additions & 1 deletion src/services/resourceService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
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
Expand All @@ -45,4 +70,4 @@ export class ResourceService extends BaseService {
this.log(`Deleting resource group: ${this.resourceGroup}`);
return await this.resourceClient.resourceGroups.deleteMethod(this.resourceGroup);
}
}
}
5 changes: 3 additions & 2 deletions src/services/rollbackService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
});
Expand All @@ -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 () => {
Expand Down
4 changes: 3 additions & 1 deletion src/services/rollbackService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<DeploymentExtended> {
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();
Expand Down