Skip to content
This repository has been archived by the owner on Jul 2, 2024. It is now read-only.

Commit

Permalink
pull main
Browse files Browse the repository at this point in the history
  • Loading branch information
SupaJoon committed Nov 1, 2023
2 parents 7efa168 + 1d33a16 commit e44171b
Show file tree
Hide file tree
Showing 24 changed files with 816 additions and 314 deletions.
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
EVG-NNNNN
DEVPROD-NNNNN

### Description
<!-- add description, context, thought process, etc -->
Expand Down
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "spruce",
"version": "3.0.160",
"version": "3.0.164",
"private": true,
"scripts": {
"bootstrap-logkeeper": "./scripts/bootstrap-logkeeper.sh",
Expand Down Expand Up @@ -64,9 +64,9 @@
"@leafygreen-ui/card": "10.0.5",
"@leafygreen-ui/checkbox": "12.0.5",
"@leafygreen-ui/code": "14.0.1",
"@leafygreen-ui/combobox": "5.0.7",
"@leafygreen-ui/combobox": "7.0.1",
"@leafygreen-ui/confirmation-modal": "5.0.6",
"@leafygreen-ui/emotion": "4.0.3",
"@leafygreen-ui/emotion": "4.0.7",
"@leafygreen-ui/expandable-card": "3.0.5",
"@leafygreen-ui/guide-cue": "5.0.4",
"@leafygreen-ui/icon": "11.12.1",
Expand Down Expand Up @@ -109,7 +109,7 @@
"deep-object-diff": "1.1.9",
"env-cmd": "10.1.0",
"graphql": "16.8.1",
"html-react-parser": "4.2.1",
"html-react-parser": "4.2.9",
"js-cookie": "3.0.5",
"linkify-html": "4.1.1",
"linkifyjs": "4.1.0",
Expand Down Expand Up @@ -150,7 +150,7 @@
"@styled/typescript-styled-plugin": "1.0.0",
"@testing-library/jest-dom": "6.1.3",
"@testing-library/react": "14.0.0",
"@testing-library/user-event": "14.4.3",
"@testing-library/user-event": "14.5.1",
"@types/jest": "29.4.0",
"@types/js-cookie": "^3.0.4",
"@types/lodash.debounce": "4.0.7",
Expand All @@ -164,7 +164,7 @@
"@typescript-eslint/eslint-plugin": "5.57.1",
"@typescript-eslint/parser": "5.57.1",
"@vitejs/plugin-react": "4.0.0",
"babel-jest": "29.5.0",
"babel-jest": "29.7.0",
"babel-loader": "^9.1.3",
"babel-plugin-import": "^1.13.6",
"babel-plugin-import-graphql": "^2.8.1",
Expand All @@ -178,7 +178,7 @@
"eslint-plugin-import": "2.26.0",
"eslint-plugin-jest": "27.2.1",
"eslint-plugin-jsdoc": "^46.2.6",
"eslint-plugin-jsx-a11y": "6.6.0",
"eslint-plugin-jsx-a11y": "6.7.1",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-react": "7.30.1",
"eslint-plugin-react-hooks": "4.6.0",
Expand Down
142 changes: 142 additions & 0 deletions scripts/deploy/deploy-production.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import prompts from "prompts";
import { evergreenDeploy, localDeploy, ciDeploy } from "./deploy-production";
import { runDeploy } from "./utils/deploy";
import { getCommitMessages, getCurrentlyDeployedCommit } from "./utils/git";
import { tagUtils } from "./utils/git/tag";
import { isRunningOnCI } from "./utils/environment";

jest.mock("prompts");
jest.mock("./utils/git/tag");
jest.mock("./utils/git");
jest.mock("./utils/deploy");
jest.mock("./utils/environment");

describe("deploy-production", () => {
let consoleLogMock;
let processExitMock;
let consoleErrorMock;

beforeEach(() => {
consoleLogMock = jest.spyOn(console, "log").mockImplementation();
consoleErrorMock = jest.spyOn(console, "error").mockImplementation();
processExitMock = jest
.spyOn(process, "exit")
.mockImplementation(() => undefined as never);
});

afterEach(() => {
jest.clearAllMocks();
});

describe("evergreenDeploy", () => {
it("should force deploy if no new commits and user confirms", async () => {
(getCommitMessages as jest.Mock).mockReturnValue("");
(prompts as unknown as jest.Mock).mockResolvedValue({ value: true });

await evergreenDeploy();

expect(tagUtils.deleteTag).toHaveBeenCalled();
expect(tagUtils.pushTags).toHaveBeenCalled();
expect(consoleLogMock).toHaveBeenCalledWith(
"Check Evergreen for deploy progress."
);
});

it("should cancel deploy if no new commits and user denies", async () => {
(getCommitMessages as jest.Mock).mockReturnValue("");
(prompts as unknown as jest.Mock).mockResolvedValue({ value: false });
await evergreenDeploy();
expect(tagUtils.deleteTag).not.toHaveBeenCalled();
expect(tagUtils.pushTags).not.toHaveBeenCalled();
expect(consoleLogMock).toHaveBeenCalledWith(
"Deploy canceled. If systems are experiencing an outage and you'd like to push the deploy directly to S3, run yarn deploy:prod --local."
);
});

it("should deploy if new commits, user confirms and create tag succeeds", async () => {
(getCommitMessages as jest.Mock).mockReturnValue(
"getCommitMessages result"
);
(prompts as unknown as jest.Mock).mockResolvedValue({ value: true });
(tagUtils.createTagAndPush as jest.Mock).mockResolvedValue(true);
const createTagAndPushMock = jest
.spyOn(tagUtils, "createTagAndPush")
.mockImplementation(() => true);
(getCurrentlyDeployedCommit as jest.Mock).mockReturnValue(
"getCurrentlyDeployedCommit mock"
);
await evergreenDeploy();
expect(consoleLogMock).toHaveBeenCalledTimes(2);
expect(consoleLogMock).toHaveBeenCalledWith(
"Currently Deployed Commit: getCurrentlyDeployedCommit mock"
);
expect(consoleLogMock).toHaveBeenCalledWith(
"Commit messages:\ngetCommitMessages result"
);
expect(createTagAndPushMock).toBeCalledTimes(1);
});

it("return exit code 1 if an error is thrown", async () => {
const e = new Error("test error", { cause: "cause of test error" });
(getCommitMessages as jest.Mock).mockReturnValue(
"getCommitMessages result"
);
(prompts as unknown as jest.Mock).mockResolvedValue({ value: true });
(tagUtils.createTagAndPush as jest.Mock).mockImplementation(() => {
throw e;
});
expect(await evergreenDeploy());
expect(consoleErrorMock).toHaveBeenCalledWith(e);
expect(consoleLogMock).toHaveBeenCalledWith("Deploy failed.");
expect(processExitMock).toHaveBeenCalledWith(1);
});
});

describe("localDeploy", () => {
it("should run deploy when user confirms", async () => {
(prompts as unknown as jest.Mock).mockResolvedValue({ value: true });
await localDeploy();
expect(runDeploy).toHaveBeenCalled();
});

it("should not run deploy when user denies", async () => {
(prompts as unknown as jest.Mock).mockResolvedValue({ value: false });
await localDeploy();
expect(runDeploy).not.toHaveBeenCalled();
});

it("logs and error and returns exit code 1 when error is thrown", async () => {
(prompts as unknown as jest.Mock).mockResolvedValue({ value: true });
const e = new Error("error mock");
(runDeploy as jest.Mock).mockImplementation(() => {
throw e;
});
await localDeploy();
expect(consoleErrorMock).toHaveBeenCalledWith(e);
expect(consoleErrorMock).toHaveBeenCalledWith(
"Local deploy failed. Aborting."
);
expect(processExitMock).toHaveBeenCalledWith(1);
});
});

describe("ciDeploy", () => {
it("returns exit code 1 when not running in CI", async () => {
(isRunningOnCI as jest.Mock).mockReturnValue(false);
await ciDeploy();
expect(consoleErrorMock).toHaveBeenCalledWith(
new Error("Not running on CI")
);
expect(consoleErrorMock).toHaveBeenCalledWith(
"CI deploy failed. Aborting."
);
expect(processExitMock).toHaveBeenCalledWith(1);
});

it("should run deploy when running on CI", async () => {
(isRunningOnCI as jest.Mock).mockReturnValue(true);
await ciDeploy();
expect(runDeploy).toHaveBeenCalled();
});
});
});
122 changes: 52 additions & 70 deletions scripts/deploy/deploy-production.ts
Original file line number Diff line number Diff line change
@@ -1,105 +1,87 @@
import prompts from "prompts";
import { tagUtils } from "./utils/git/tag";
import { green, underline } from "../utils/colors";
import { isRunningOnCI } from "./utils/environment";
import { getCommitMessages, getCurrentlyDeployedCommit } from "./utils/git";
import { runDeploy } from "./utils/deploy";

const { createNewTag, deleteTag, getLatestTag, pushTags } = tagUtils;
const { createTagAndPush, deleteTag, getLatestTag, pushTags } = tagUtils;
/* Deploy by pushing a git tag, to be picked up and built by Evergreen, and deployed to S3. */
const evergreenDeploy = async () => {
const currentlyDeployedCommit = getCurrentlyDeployedCommit();
console.log(`Currently Deployed Commit: ${currentlyDeployedCommit}`);
try {
const currentlyDeployedCommit = getCurrentlyDeployedCommit();
console.log(`Currently Deployed Commit: ${currentlyDeployedCommit}`);

const commitMessages = getCommitMessages(currentlyDeployedCommit);
const commitMessages = getCommitMessages(currentlyDeployedCommit);

// If there are no commit messages, ask the user if they want to delete and re-push the latest tag, thereby forcing a deploy with no new commits.
if (commitMessages.length === 0) {
const latestTag = getLatestTag();
const response = await prompts({
type: "confirm",
name: "value",
message: `No new commits. Do you want to trigger a deploy on the most recent existing tag? (${latestTag})`,
initial: false,
});
// If there are no commit messages, ask the user if they want to delete and re-push the latest tag, thereby forcing a deploy with no new commits.
if (commitMessages.length === 0) {
const latestTag = getLatestTag();
const { value: shouldForceDeploy } = await prompts({
type: "confirm",
name: "value",
message: `No new commits. Do you want to trigger a deploy on the most recent existing tag? (${latestTag})`,
initial: false,
});

const forceDeploy = response.value;
if (forceDeploy) {
console.log(`Deleting tag (${latestTag}) from remote...`);
deleteTag(latestTag);
console.log("Pushing tags...");
pushTags();
console.log("Check Evergreen for deploy progress.");
if (shouldForceDeploy) {
deleteTag(latestTag);
pushTags();
console.log("Check Evergreen for deploy progress.");
} else {
console.log(
"Deploy canceled. If systems are experiencing an outage and you'd like to push the deploy directly to S3, run yarn deploy:prod --local."
);
}
return;
}

console.log(
"Deploy canceled. If systems are experiencing an outage and you'd like to push the deploy directly to S3, run yarn deploy:prod --local."
);
return;
}

// Print all commits between the last tag and the current commit
console.log(`Commit messages:\n${commitMessages}`);
// Print all commits between the last tag and the current commit
console.log(`Commit messages:\n${commitMessages}`);

const response = await prompts({
type: "confirm",
name: "value",
message: "Are you sure you want to deploy to production?",
});
const { value: shouldCreateTagAndPush } = await prompts({
type: "confirm",
name: "value",
message: "Are you sure you want to deploy to production?",
});

if (response.value) {
try {
console.log("Creating new tag...");
createNewTag();
console.log("Pushing tags...");
pushTags();
console.log("Pushed to remote. Should be deploying soon...");
console.log(
green(
`Track deploy progress at ${underline(
"https://spruce.mongodb.com/commits/spruce?requester=git_tag_request"
)}`
)
);
} catch (err) {
console.error(err);
console.error("Creating tag failed. Aborting.");
process.exit(1);
if (shouldCreateTagAndPush) {
createTagAndPush();
}
} catch (err) {
console.error(err);
console.log("Deploy failed.");
process.exit(1);
}
};

/* Deploy by generating a production build locally and pushing it directly to S3. */
const localDeploy = async () => {
const response = await prompts({
type: "confirm",
name: "value",
message:
"Are you sure you'd like to build Spruce locally and push directly to S3? This is a high-risk operation that requires a correctly configured local environment.",
});

if (response.value) {
try {
try {
const response = await prompts({
type: "confirm",
name: "value",
message:
"Are you sure you'd like to build Spruce locally and push directly to S3? This is a high-risk operation that requires a correctly configured local environment.",
});
if (response.value) {
runDeploy();
} catch (err) {
console.error(err);
console.error("Local deploy failed. Aborting.");
process.exit(1);
}
} catch (err) {
console.error(err);
console.error("Local deploy failed. Aborting.");
process.exit(1);
}
};

/**
* `ciDeploy` is a special deploy function that is only run on CI. It does the actual deploy to S3.
*/
const ciDeploy = async () => {
if (!isRunningOnCI()) {
throw new Error("Not running on CI");
}
try {
const ciDeployOutput = runDeploy();
console.log(ciDeployOutput);
if (!isRunningOnCI()) {
throw new Error("Not running on CI");
}
runDeploy();
} catch (err) {
console.error(err);
console.error("CI deploy failed. Aborting.");
Expand Down
8 changes: 4 additions & 4 deletions scripts/deploy/utils/git/tag/mock-tag-utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { yellow } from "../../../../utils/colors";

/**
* `createNewTag` is a helper function that creates a new tag.
* `createTagAndPush` is a helper function that creates a new tag and pushes to remote.
*/
const createNewTag = () => {
console.log(yellow("Dry run mode enabled. Created new tag."));
const createTagAndPush = () => {
console.log(yellow("Dry run mode enabled. Created new tag and pushed."));
};

/**
Expand All @@ -31,4 +31,4 @@ const pushTags = () => {
console.log(yellow("Dry run mode enabled. Pushing tags."));
};

export { createNewTag, getLatestTag, deleteTag, pushTags };
export { createTagAndPush, getLatestTag, deleteTag, pushTags };
Loading

0 comments on commit e44171b

Please sign in to comment.