Skip to content

Commit

Permalink
feat(build-workspace-matrix): add 'workspace_dependencies' argument (#…
Browse files Browse the repository at this point in the history
…266)

This argument allows specifying a pair of patterns 'dependent : dependency' that will include all workspace matching 'dependent' if anything matching 'dependency' is changed. This is a specialization of the 'global_dependencies' argument.

Also upgrade to jest 28.x and add testing to the build-workspace-matrix action.
  • Loading branch information
marcind authored May 11, 2022
1 parent 9a8fbdf commit 40f2c74
Show file tree
Hide file tree
Showing 9 changed files with 3,065 additions and 7,495 deletions.
4 changes: 4 additions & 0 deletions build-workspace-matrix/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ jobs:
# Unless something in global dependencies changes, in which case all workspaces are returned
global_dependencies: |
terraform/modules/**/*.tf
# Or if something in specific workspace dependencies changes, in which case those dependent workspaces get returned
workspace_dependencies: |
terraform/clusters/product-a-*/ : shared_modules/foo/*.tf
terraform/clusters/product-a-*/ : shared_modules/bar/**/*.tf
build:
needs: [determine-matrix]
Expand Down
163 changes: 163 additions & 0 deletions build-workspace-matrix/__tests__/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { changedFiles } from "../changedFiles";
import { getWorkspaces } from "../main";
import { promises as fs, existsSync } from "fs";
import * as path from "path";

jest.mock("../changedFiles");

const folders = [
"clusters/stream-a",
"clusters/stream-b",
"clusters/stream-c",
"clusters/origin-a",
"clusters/origin-b",
"clusters/broadcast",
"clusters/modules/stream",
"clusters/modules/origin-and-stream",
"clusters/modules/origin",
"clusters/modules/global",
];

const shared = {
dispatchWorkspace: "clusters/origin-a",
githubToken: "token",
workspaceGlobs: `
# a comment
clusters/*/
# the below is excluded
!clusters/modules/`,
workspaceDependencyGlobPatterns: `
# a comment
clusters/stream-*/ : clusters/modules/stream/**/*.txt
clusters/stream-*/ : clusters/modules/origin-and-stream/**/*.txt
clusters/origin-*/ : clusters/modules/origin/**/*.txt
clusters/origin-*/ : clusters/modules/origin-and-stream/**/*.txt
`,
globalDependencyGlobs: `
# a comment
clusters/modules/global/**/*.txt
`,
}
let wd: string;

beforeAll(async () => {
wd = process.cwd();
process.chdir(__dirname);
const folder = path.join(__dirname, "clusters");
if (existsSync(folder)) {
await fs.rmdir(folder, { recursive: true });
}
for (const f of folders) {
const folder = path.join(__dirname, f);
await fs.mkdir(folder, { recursive: true });
}
})

afterAll(async () => {
process.chdir(wd);
await fs.rmdir(path.join(__dirname, "clusters"), { recursive: true });
})

test("getWorkspaces event:workflow_dispatch returns dispatch workspace", async () => {
const workspaces = await getWorkspaces({
...shared,
eventName: "workflow_dispatch",
});
expect(workspaces).toEqual(["clusters/origin-a"]);
});

test("getWorkspaces event:schedule returns all workspaces", async () => {
const workspaces = await getWorkspaces({
...shared,
eventName: "schedule",
});
expect(workspaces.sort()).toEqual([
"clusters/stream-a",
"clusters/stream-b",
"clusters/stream-c",
"clusters/origin-a",
"clusters/origin-b",
"clusters/broadcast",
].sort());
});

test("getWorkspaces event:push/pull_request returns workspaces with changes", async () => {
const mockedChangedFiles = jest.mocked(changedFiles);
mockedChangedFiles.mockResolvedValue([
"clusters/stream-a/file.txt",
"clusters/origin-b/file.txt",
])
const workspaces = await getWorkspaces({
...shared,
eventName: "push",
});
expect(workspaces.sort()).toEqual([
"clusters/stream-a",
"clusters/origin-b",
].sort());
});

test("getWorkspaces event:push/pull_request returns all workspaces when global dependency has change", async () => {
const mockedChangedFiles = jest.mocked(changedFiles);
mockedChangedFiles.mockResolvedValue([
"clusters/modules/global/subdir/file.txt",
])
const workspaces = await getWorkspaces({
...shared,
eventName: "push",
});
expect(workspaces.sort()).toEqual([
"clusters/broadcast",
"clusters/origin-a",
"clusters/origin-b",
"clusters/stream-a",
"clusters/stream-b",
"clusters/stream-c",
].sort());
});

test("getWorkspaces event:push/pull_request returns no workspaces when global dependency has change to unmatched files", async () => {
const mockedChangedFiles = jest.mocked(changedFiles);
mockedChangedFiles.mockResolvedValue([
"clusters/modules/global/subdir/file.wrong_extension",
])
const workspaces = await getWorkspaces({
...shared,
eventName: "push",
});
expect(workspaces.sort()).toEqual([].sort());
});

test("getWorkspaces event:push/pull_request returns workspaces when dependency changes", async () => {
const mockedChangedFiles = jest.mocked(changedFiles);
mockedChangedFiles.mockResolvedValue([
"clusters/modules/stream/subdir/file.txt",
])
const workspaces = await getWorkspaces({
...shared,
eventName: "push",
});
expect(workspaces.sort()).toEqual([
"clusters/stream-a",
"clusters/stream-b",
"clusters/stream-c",
].sort());
});

test("getWorkspaces event:push/pull_request returns workspaces when shared dependency changes", async () => {
const mockedChangedFiles = jest.mocked(changedFiles);
mockedChangedFiles.mockResolvedValue([
"clusters/modules/origin-and-stream/subdir/file.txt",
])
const workspaces = await getWorkspaces({
...shared,
eventName: "push",
});
expect(workspaces.sort()).toEqual([
"clusters/origin-a",
"clusters/origin-b",
"clusters/stream-a",
"clusters/stream-b",
"clusters/stream-c",
].sort());
});
3 changes: 3 additions & 0 deletions build-workspace-matrix/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ inputs:
workspaces:
description: "A newline-separated list of globs representing specific workspaces"
required: true
workspace_dependencies:
description: "A newline-separated list workspace dependencies represented as 'workspace-glob : dependency-glob'"
required: false
workflow_dispatch_workspace:
description: "A particular workspace to return when the event type is 'workflow_dispatch'"
required: false
Expand Down
41 changes: 41 additions & 0 deletions build-workspace-matrix/changedFiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { setFailed } from "@actions/core";
import { context, getOctokit } from "@actions/github";
import { WebhookEventName, WebhookEventMap } from "@octokit/webhooks-types";

export async function changedFiles(eventName: WebhookEventName, token: string): Promise<string[]> {
const github = getOctokit(token);

let head: string;
let base: string;
switch (eventName) {
case "push":
const pushPayload = context.payload as WebhookEventMap[typeof eventName];
head = pushPayload.after;
base = pushPayload.before;
break;
case "pull_request":
const prPayload = context.payload as WebhookEventMap[typeof eventName];
head = prPayload.pull_request.head.sha;
base = prPayload.pull_request.base.sha;
break;
default:
setFailed(
`${eventName} is not supported when determining changed files.`
);
return [];
}

const { owner, repo } = context.repo;
const response = await github.rest.repos.compareCommits({
owner,
repo,
head,
base,
});

if (response.status !== 200) {
setFailed(`GitHub API returned response with status ${response.status}`);
return [];
}
return response.data.files.map((file) => file.filename);
}
Loading

0 comments on commit 40f2c74

Please sign in to comment.