Skip to content
This repository was archived by the owner on Apr 13, 2020. It is now read-only.

Commit 61299d6

Browse files
committed
Add ring delete command
Added the `spk ring delete <ring-name>` command. closes microsoft/bedrock#971
1 parent b8a2aa7 commit 61299d6

File tree

7 files changed

+165
-50
lines changed

7 files changed

+165
-50
lines changed

src/commands/ring/create.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { create as createBedrockYaml } from "../../lib/bedrockYaml";
2-
import { read as loadBedrockFile } from "../../lib/bedrockYaml";
1+
import {
2+
create as createBedrockYaml,
3+
read as loadBedrockFile
4+
} from "../../lib/bedrockYaml";
5+
import * as fileUtils from "../../lib/fileutils";
36
import { createTempDir } from "../../lib/ioUtil";
47
import { disableVerboseLogging, enableVerboseLogging } from "../../logger";
5-
6-
import * as fileUtils from "../../lib/fileutils";
7-
88
import { IBedrockFile } from "../../types";
99
import { checkDependencies, execute } from "./create";
1010

src/commands/ring/delete.test.ts

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
import { create as createBedrockYaml } from "../../lib/bedrockYaml";
1+
import * as bedrock from "../../lib/bedrockYaml";
2+
import * as fileUtils from "../../lib/fileutils";
23
import { createTempDir } from "../../lib/ioUtil";
34
import { disableVerboseLogging, enableVerboseLogging } from "../../logger";
4-
5+
import { createTestBedrockYaml } from "../../test/mockFactory";
6+
import { IBedrockFile } from "../../types";
57
import { checkDependencies, execute } from "./delete";
68

9+
jest.mock("../../lib/fileutils");
10+
711
beforeAll(() => {
812
enableVerboseLogging();
913
});
@@ -12,15 +16,20 @@ afterAll(() => {
1216
disableVerboseLogging();
1317
});
1418

15-
describe("test valid function", () => {
16-
it("negative test", async () => {
17-
try {
18-
const tmpDir = createBedrockYaml();
19-
checkDependencies(tmpDir);
20-
expect(true).toBe(false);
21-
} catch (e) {
22-
expect(e).not.toBeNull();
23-
}
19+
beforeEach(() => {
20+
jest.clearAllMocks();
21+
});
22+
23+
describe("checkDependencies", () => {
24+
it("throws when project not initialized", () => {
25+
const tmpDir = createTempDir();
26+
expect(() => checkDependencies(tmpDir)).toThrow();
27+
});
28+
29+
it("does not throw when project initialized", () => {
30+
const tmpDir = createTempDir();
31+
bedrock.create(tmpDir, createTestBedrockYaml(false) as IBedrockFile);
32+
expect(() => checkDependencies(tmpDir)).not.toThrow();
2433
});
2534
});
2635

@@ -31,22 +40,34 @@ describe("test execute function and logic", () => {
3140
expect(exitFn).toBeCalledTimes(1);
3241
expect(exitFn.mock.calls).toEqual([[1]]);
3342
});
43+
3444
it("test execute function: working path with bedrock.yaml", async () => {
3545
const exitFn = jest.fn();
36-
3746
const tmpDir = createTempDir();
38-
createBedrockYaml(tmpDir, {
39-
rings: {
40-
master: {
41-
isDefault: true
42-
}
43-
},
44-
services: {},
45-
variableGroups: ["testvg"]
46-
});
47-
await execute("ring", tmpDir, exitFn);
47+
const bedrockConfig = createTestBedrockYaml(false) as IBedrockFile;
48+
bedrock.create(tmpDir, bedrockConfig);
4849

49-
expect(exitFn).toBeCalledTimes(1);
50+
// delete the first ring and write out the update
51+
jest.spyOn(bedrock, "create");
52+
const ringToDelete = Object.keys(bedrockConfig.rings).pop() as string;
53+
expect(ringToDelete).toBeDefined();
54+
await execute(ringToDelete, tmpDir, exitFn);
55+
expect(bedrock.create).toBeCalledTimes(1);
56+
57+
// updateTriggerBranchesForServiceBuildAndUpdatePipeline should be called
58+
// once per service
59+
const numberOfServices = Object.keys(bedrockConfig.services).length;
60+
const updatedRingList = Object.keys(
61+
bedrock.removeRing(bedrockConfig, ringToDelete).rings
62+
);
63+
expect(
64+
fileUtils.updateTriggerBranchesForServiceBuildAndUpdatePipeline
65+
).toBeCalledTimes(numberOfServices);
66+
for (const serviceName of Object.keys(bedrockConfig.services)) {
67+
expect(
68+
fileUtils.updateTriggerBranchesForServiceBuildAndUpdatePipeline
69+
).toBeCalledWith(updatedRingList, serviceName);
70+
}
5071
expect(exitFn.mock.calls).toEqual([[0]]);
5172
});
5273
});

src/commands/ring/delete.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import commander from "commander";
2-
import { fileInfo as bedrockFileInfo } from "../../lib/bedrockYaml";
2+
import * as bedrock from "../../lib/bedrockYaml";
33
import { build as buildCmd, exit as exitCmd } from "../../lib/commandBuilder";
44
import { PROJECT_INIT_DEPENDENCY_ERROR_MESSAGE } from "../../lib/constants";
5+
import { updateTriggerBranchesForServiceBuildAndUpdatePipeline } from "../../lib/fileutils";
56
import { hasValue } from "../../lib/validator";
67
import { logger } from "../../logger";
78
import { IBedrockFileInfo } from "../../types";
8-
99
import decorator from "./delete.decorator.json";
1010

1111
/**
@@ -27,12 +27,24 @@ export const execute = async (
2727
try {
2828
logger.info(`Project path: ${projectPath}`);
2929

30+
// Check if bedrock config exists, if not, warn and exit
3031
checkDependencies(projectPath);
3132

32-
// Check if ring exists, if not, warn and exit
33-
// Check if ring is default, if so, warn "Cannot delete default ring, set a new default via `spk ring set-default` first." and exit
34-
// Delete ring from bedrock.yaml
33+
// Remove the ring
34+
const bedrockConfig = bedrock.read(projectPath);
35+
const bedrockWithoutRing = bedrock.removeRing(bedrockConfig, ringName);
36+
37+
// Write out the updated bedrock.yaml
38+
bedrock.create(projectPath, bedrockWithoutRing);
39+
3540
// Delete ring from all linked service build pipelines' branch triggers
41+
const ringBranches = Object.keys(bedrockWithoutRing.rings);
42+
for (const servicePath of Object.keys(bedrockConfig.services)) {
43+
updateTriggerBranchesForServiceBuildAndUpdatePipeline(
44+
ringBranches,
45+
servicePath
46+
);
47+
}
3648

3749
logger.info(`Successfully deleted ring: ${ringName} from this project!`);
3850
await exitFn(0);
@@ -52,12 +64,12 @@ export const commandDecorator = (command: commander.Command): void => {
5264
};
5365

5466
/**
55-
* Check for bedrock.yaml
67+
* Check the bedrock.yaml and the target ring exists
5668
* @param projectPath
5769
*/
5870
export const checkDependencies = (projectPath: string) => {
59-
const fileInfo: IBedrockFileInfo = bedrockFileInfo(projectPath);
71+
const fileInfo: IBedrockFileInfo = bedrock.fileInfo(projectPath);
6072
if (fileInfo.exist === false) {
61-
throw new Error(PROJECT_INIT_DEPENDENCY_ERROR_MESSAGE);
73+
throw Error(PROJECT_INIT_DEPENDENCY_ERROR_MESSAGE);
6274
}
6375
};

src/lib/bedrockYaml.test.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import uuid from "uuid/v4";
1+
import uuid = require("uuid/v4");
22
import { createTempDir } from "../lib/ioUtil";
33
import { createTestBedrockYaml } from "../test/mockFactory";
44
import { IBedrockFile, IHelmConfig } from "../types";
@@ -10,7 +10,8 @@ import {
1010
fileInfo,
1111
isExists,
1212
read,
13-
setDefaultRing
13+
removeRing,
14+
setDefaultRing,
1415
} from "./bedrockYaml";
1516

1617
describe("Creation and Existence test on bedrock.yaml", () => {
@@ -76,11 +77,7 @@ describe("Adding a new service to a Bedrock file", () => {
7677
);
7778

7879
const expected: IBedrockFile = {
79-
rings: {
80-
master: {
81-
isDefault: true
82-
}
83-
},
80+
...defaultBedrockFileObject,
8481
services: {
8582
...(defaultBedrockFileObject as IBedrockFile).services,
8683
["./" + servicePath]: {
@@ -192,3 +189,34 @@ describe("Set default ring", () => {
192189
expect(result.rings.prod.isDefault).toBe(undefined);
193190
});
194191
});
192+
193+
describe("removeRing", () => {
194+
it("removes a valid matching ring", () => {
195+
const original = createTestBedrockYaml(false) as IBedrockFile;
196+
const ringToRemove = Object.keys(original.rings).pop() as string;
197+
expect(ringToRemove).toBeDefined();
198+
const updated = removeRing(original, ringToRemove);
199+
const originalWithoutRing = (() => {
200+
const copy: IBedrockFile = JSON.parse(JSON.stringify(original));
201+
delete copy.rings[ringToRemove];
202+
return copy;
203+
})();
204+
expect(Object.keys(updated.rings)).not.toContain(ringToRemove);
205+
expect(updated).toStrictEqual(originalWithoutRing);
206+
});
207+
208+
it("throws when the ring doesn't exist", () => {
209+
const original = createTestBedrockYaml(false) as IBedrockFile;
210+
expect(() => removeRing(original, uuid())).toThrow();
211+
});
212+
213+
it("throws when the ring is found but isDefault === true", () => {
214+
const original = createTestBedrockYaml(false) as IBedrockFile;
215+
const defaultRing = Object.entries(original.rings)
216+
.map(([name, config]) => ({ name, config }))
217+
.find(({ config }) => config.isDefault);
218+
const ringToRemove = defaultRing?.name;
219+
expect(ringToRemove).toBeDefined();
220+
expect(() => removeRing(original, ringToRemove as string)).toThrow();
221+
});
222+
});

src/lib/bedrockYaml.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import yaml from "js-yaml";
33
import path from "path";
44
import { createTempDir } from "../lib/ioUtil";
55
import { logger } from "../logger";
6-
import { IBedrockFile, IBedrockFileInfo, IHelmConfig } from "../types";
6+
import { IBedrockFile, IBedrockFileInfo, IHelmConfig, IRings } from "../types";
77

88
export const YAML_NAME = "bedrock.yaml";
99

@@ -183,3 +183,50 @@ export const fileInfo = (rootProjectPath?: string): IBedrockFileInfo => {
183183
};
184184
}
185185
};
186+
187+
/**
188+
* Deletes the target ring with name `ringToDelete` from the provided `bedrock`
189+
* config.
190+
*
191+
* @throws {Error} if ring is not found in `bedrock`
192+
* @throws {Error} if the matching ring is `isDefault === true`
193+
*
194+
* @param bedrock the bedrock file to remove the ring from
195+
* @param ringToDelete the name of the ring to remove
196+
*/
197+
export const removeRing = (
198+
bedrock: IBedrockFile,
199+
ringToDelete: string
200+
): IBedrockFile => {
201+
// Check if ring exists, if not, warn and exit
202+
const rings = Object.entries(bedrock.rings).map(([name, config]) => ({
203+
config,
204+
name
205+
}));
206+
const matchingRing = rings.find(({ name }) => name === ringToDelete);
207+
if (matchingRing === undefined) {
208+
throw Error(`Ring ${ringToDelete} not found in bedrock.yaml`);
209+
}
210+
211+
// Check if ring is default, if so, warn "Cannot delete default ring
212+
// set a new default via `spk ring set-default` first." and exit
213+
if (matchingRing.config.isDefault) {
214+
throw Error(
215+
`Ring ${matchingRing.name} is currently set to isDefault -- set another default ring with 'spk ring set-default' first before attempting to delete`
216+
);
217+
}
218+
219+
// Remove the ring
220+
const updatedRings: IRings = rings.reduce((updated, ring) => {
221+
if (ring.name === ringToDelete) {
222+
return updated;
223+
}
224+
return { ...updated, [ring.name]: ring.config };
225+
}, {});
226+
const bedrockWithoutRing: IBedrockFile = {
227+
...bedrock,
228+
rings: updatedRings
229+
};
230+
231+
return bedrockWithoutRing;
232+
};

src/test/mockFactory.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,9 +258,12 @@ export const createTestBedrockYaml = (
258258

259259
const data: IBedrockFile = {
260260
rings: {
261+
develop: {},
261262
master: {
262263
isDefault: true
263-
}
264+
},
265+
qa: {},
266+
testing: {}
264267
},
265268
services: {
266269
"./": {

src/types.d.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,20 @@ export interface IHelmConfig {
3939
));
4040
}
4141

42+
export interface IRings {
43+
[branchName: string]: IRingConfig;
44+
}
45+
46+
export interface IRingConfig {
47+
isDefault?: boolean; // indicates the branch is a default branch to PR against when creating a service revision
48+
}
49+
4250
/**
4351
* Bedrock config file
4452
* Used to capture service meta-information regarding how to deploy
4553
*/
4654
export interface IBedrockFile {
47-
rings: {
48-
[branchName: string]: {
49-
isDefault?: boolean; // indicates the branch is a default branch to PR against when creating a service revision
50-
};
51-
};
55+
rings: IRings;
5256
services: {
5357
[relativeDirectory: string]: IBedrockServiceConfig;
5458
};

0 commit comments

Comments
 (0)