From 2114d442f4a52ba14def437cba9cd809d56ee6d3 Mon Sep 17 00:00:00 2001 From: Sophie Stadler Date: Wed, 3 Jul 2024 16:39:17 -0400 Subject: [PATCH] DEVPROD-5141: Fix commit check for prod deploy (#228) --- apps/parsley/src/gql/generated/types.ts | 17 ++++++ .../git/get-current-deployed-commit.test.ts | 57 +++++++++++++++++++ .../utils/git/get-current-deployed-commit.ts | 8 ++- .../deploy-utils/src/utils/git/git.test.ts | 6 +- .../deploy-utils/src/utils/git/tag.test.ts | 26 ++++----- packages/deploy-utils/src/utils/git/tag.ts | 10 ++++ 6 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 packages/deploy-utils/src/utils/git/get-current-deployed-commit.test.ts diff --git a/apps/parsley/src/gql/generated/types.ts b/apps/parsley/src/gql/generated/types.ts index ccb78aaf9..3c36ece29 100644 --- a/apps/parsley/src/gql/generated/types.ts +++ b/apps/parsley/src/gql/generated/types.ts @@ -622,6 +622,17 @@ export type GeneralSubscription = { triggerData?: Maybe; }; +export type GitHubDynamicTokenPermissionGroup = { + __typename?: "GitHubDynamicTokenPermissionGroup"; + name: Scalars["String"]["output"]; + permissions: Scalars["StringMap"]["output"]; +}; + +export type GitHubDynamicTokenPermissionGroupInput = { + name: Scalars["String"]["input"]; + permissions: Scalars["StringMap"]["input"]; +}; + export type GitTag = { __typename?: "GitTag"; pusher: Scalars["String"]["output"]; @@ -1690,6 +1701,7 @@ export type Project = { gitTagAuthorizedUsers?: Maybe>; gitTagVersionsEnabled?: Maybe; githubChecksEnabled?: Maybe; + githubDynamicTokenPermissionGroups: Array; githubTriggerAliases?: Maybe>; hidden?: Maybe; id: Scalars["String"]["output"]; @@ -1827,6 +1839,9 @@ export type ProjectInput = { gitTagAuthorizedUsers?: InputMaybe>; gitTagVersionsEnabled?: InputMaybe; githubChecksEnabled?: InputMaybe; + githubDynamicTokenPermissionGroups?: InputMaybe< + Array + >; githubTriggerAliases?: InputMaybe>; id: Scalars["String"]["input"]; identifier?: InputMaybe; @@ -1904,6 +1919,8 @@ export enum ProjectSettingsSection { Containers = "CONTAINERS", General = "GENERAL", GithubAndCommitQueue = "GITHUB_AND_COMMIT_QUEUE", + GithubAppSettings = "GITHUB_APP_SETTINGS", + GithubPermissions = "GITHUB_PERMISSIONS", Notifications = "NOTIFICATIONS", PatchAliases = "PATCH_ALIASES", PeriodicBuilds = "PERIODIC_BUILDS", diff --git a/packages/deploy-utils/src/utils/git/get-current-deployed-commit.test.ts b/packages/deploy-utils/src/utils/git/get-current-deployed-commit.test.ts new file mode 100644 index 000000000..3c8376ae3 --- /dev/null +++ b/packages/deploy-utils/src/utils/git/get-current-deployed-commit.test.ts @@ -0,0 +1,57 @@ +import Stream from "stream"; +import { tagIsValid } from "."; + +describe("getCurrentDeployedCommit without mocking https requests", () => { + it("fetches the commit from spruce", async () => { + const { getCurrentlyDeployedCommit } = await import( + "./get-current-deployed-commit" + ); + expect(await getCurrentlyDeployedCommit("spruce")).toHaveLength(40); + }); + + it("fetches the commit from parsley", async () => { + const { getCurrentlyDeployedCommit } = await import( + "./get-current-deployed-commit" + ); + expect(await getCurrentlyDeployedCommit("parsley")).toHaveLength(40); + }); +}); + +describe("when get request fails", () => { + beforeEach(() => { + vi.resetModules(); + vi.doMock("https", () => ({ + default: vi.fn(), + get: vi.fn().mockImplementation((_, cb) => { + const s = new Stream(); + cb(s); + s.emit("error", new Error("invalid")); + }), + })); + }); + + afterEach(() => { + vi.restoreAllMocks(); + vi.doUnmock("https"); + }); + + it("gets a local spruce tag", async () => { + const { getCurrentlyDeployedCommit } = await import( + "./get-current-deployed-commit" + ); + const app = "spruce"; + const commit = await getCurrentlyDeployedCommit(app); + expect(commit).not.toHaveLength(40); + expect(tagIsValid(app, commit)).toBe(true); + }); + + it("gets a local parsley tag", async () => { + const { getCurrentlyDeployedCommit } = await import( + "./get-current-deployed-commit" + ); + const app = "parsley"; + const commit = await getCurrentlyDeployedCommit(app); + expect(commit).not.toHaveLength(40); + expect(tagIsValid(app, commit)).toBe(true); + }); +}); diff --git a/packages/deploy-utils/src/utils/git/get-current-deployed-commit.ts b/packages/deploy-utils/src/utils/git/get-current-deployed-commit.ts index 64fed251f..c06068492 100644 --- a/packages/deploy-utils/src/utils/git/get-current-deployed-commit.ts +++ b/packages/deploy-utils/src/utils/git/get-current-deployed-commit.ts @@ -1,5 +1,5 @@ import { get } from "https"; -import { getLatestTag } from "."; +import { getLatestTag, tagIsValid } from "."; import { DeployableApp } from "../types"; /** @@ -47,9 +47,11 @@ export const getCurrentlyDeployedCommit = async (app: DeployableApp) => { } } - const commitIsCorrectLength = commit?.length === 40; + commit = commit?.trim(); - if (commitIsCorrectLength) { + const commitIsValid = commit?.length === 40 || tagIsValid(app, commit); + + if (commitIsValid) { return commit; } throw new Error("No valid commit found"); diff --git a/packages/deploy-utils/src/utils/git/git.test.ts b/packages/deploy-utils/src/utils/git/git.test.ts index 97a3a90c2..d676dc405 100644 --- a/packages/deploy-utils/src/utils/git/git.test.ts +++ b/packages/deploy-utils/src/utils/git/git.test.ts @@ -71,10 +71,8 @@ describe("getCurrentlyDeployedCommit", () => { it("returns a valid local commit", async () => { vi.mocked(get).mockImplementation(errorMock); - vi.mocked(execSync).mockReturnValue(validCommitString); - expect(await getCurrentlyDeployedCommit("spruce")).toEqual( - validCommitString, - ); + vi.mocked(execSync).mockReturnValue("spruce/v1.0.0"); + expect(await getCurrentlyDeployedCommit("spruce")).toEqual("spruce/v1.0.0"); expect(vi.mocked(execSync)).toHaveBeenCalledOnce(); }); diff --git a/packages/deploy-utils/src/utils/git/tag.test.ts b/packages/deploy-utils/src/utils/git/tag.test.ts index 49871f353..6f043cb44 100644 --- a/packages/deploy-utils/src/utils/git/tag.test.ts +++ b/packages/deploy-utils/src/utils/git/tag.test.ts @@ -1,31 +1,25 @@ -import { getLatestTag } from "."; -import { DeployableApp } from "../types"; +import { getLatestTag, tagIsValid } from "."; -describe("getLatestTag", () => { - const currentlyDeployedTagRegex = (app: DeployableApp) => - new RegExp(`${app}/v\\d+.\\d+.\\d+`); - - it("currentlyDeployedTagRegex should match on a known valid tag", () => { - const tag = currentlyDeployedTagRegex("parsley").test("parsley/v1.2.3"); - expect(tag).toBeTruthy(); +describe("tagIsValid", () => { + it("should match on a known valid tag", () => { + expect(tagIsValid("parsley", "parsley/v1.2.3")).toEqual(true); }); - it("currentlyDeployedTagRegex should not match on the wrong app's tag tag", () => { - const tag = currentlyDeployedTagRegex("parsley").test("spruce/v1.2.3"); - expect(tag).toBeFalsy(); + it("should not match on the wrong app's tag", () => { + expect(tagIsValid("parsley", "spruce/v1.2.3")).toEqual(false); }); +}); +describe("getLatestTag", () => { it("should return the latest spruce tag", () => { const app = "spruce"; const latestTag = getLatestTag(app); - const latestTagIsTag = currentlyDeployedTagRegex(app).test(latestTag); - expect(latestTagIsTag).toBeTruthy(); + expect(tagIsValid(app, latestTag)).toEqual(true); }); it("should return the latest parsley tag", () => { const app = "parsley"; const latestTag = getLatestTag(app); - const latestTagIsTag = currentlyDeployedTagRegex(app).test(latestTag); - expect(latestTagIsTag).toBeTruthy(); + expect(tagIsValid(app, latestTag)).toEqual(true); }); }); diff --git a/packages/deploy-utils/src/utils/git/tag.ts b/packages/deploy-utils/src/utils/git/tag.ts index 66933dec9..4bd73219f 100644 --- a/packages/deploy-utils/src/utils/git/tag.ts +++ b/packages/deploy-utils/src/utils/git/tag.ts @@ -87,10 +87,20 @@ const pushTags = () => { } }; +/** + * tagIsValid asserts whether a given string matches a tag for a specified app + * @param app - app with which tag is associated + * @param matchString - string to test against + * @returns - boolean indicating whether matchString is a valid tag + */ +const tagIsValid = (app: DeployableApp, matchString: string) => + new RegExp(`${app}/v\\d+.\\d+.\\d+`).test(matchString); + export { createTagAndPush, deleteTag, getLatestTag, getTagFromCommit, pushTags, + tagIsValid, };