Skip to content

Commit

Permalink
feat(ci): support running on "deployment_status" event
Browse files Browse the repository at this point in the history
  • Loading branch information
gregberge committed Jan 24, 2024
1 parent b89289d commit e9bf085
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 41 deletions.
8 changes: 4 additions & 4 deletions packages/core/src/ci-environment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,21 @@ const services = [
git,
];

export const getCiEnvironment = ({
export async function getCiEnvironment({
env = process.env,
}: Options = {}): CiEnvironment | null => {
}: Options = {}): Promise<CiEnvironment | null> {
const ctx = { env };
debug("Detecting CI environment", { env });
const service = services.find((service) => service.detect(ctx));

// Service matched
if (service) {
debug("Internal service matched", service.name);
const variables = service.config(ctx);
const variables = await service.config(ctx);
const ciEnvironment = { name: service.name, ...variables };
debug("CI environment", ciEnvironment);
return ciEnvironment;
}

return null;
};
}
100 changes: 85 additions & 15 deletions packages/core/src/ci-environment/services/github-actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,66 @@
import { existsSync, readFileSync } from "node:fs";
import type { Service, Context } from "../types";
import axios from "axios";
import { debug } from "../../debug";

type EventPayload = {
pull_request?: {
head: {
sha: string;
ref: string;
};
number: number;
};
deployment?: {
sha: string;
environment: string;
};
};

type GitHubPullRequest = {
number: number;
head: {
ref: string;
sha: string;
};
};

/**
* When triggered by a deployment we try to get the pull request number from the
* deployment sha.
*/
async function getPullRequestFromHeadSha({ env }: Context, sha: string) {
debug("Fetching pull request number from head sha", sha);
if (!env.GITHUB_REPOSITORY || !env.GITHUB_TOKEN) {
debug("Aborting because GITHUB_REPOSITORY or GITHUB_TOKEN is missing");
return null;
}
try {
const result = await axios.get<GitHubPullRequest[]>(
`https://github.com/gitapi/repos/${env.GITHUB_REPOSITORY}/pulls`,
{
params: {
head: sha,
},
headers: {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
"X-GitHub-Api-Version": "2022-11-28",
},
},
);
if (result.data.length === 0) {
debug("Aborting because no pull request found");
return null;
}
const firstPr = result.data[0];
debug("PR found", firstPr);
return firstPr;
} catch (error) {
debug("Error while fetching pull request number from head sha", error);
return null;
}
}

const getBranch = ({ env }: Context) => {
if (env.GITHUB_HEAD_REF) {
Expand All @@ -20,16 +81,6 @@ const getRepository = ({ env }: Context) => {
return env.GITHUB_REPOSITORY.split("/")[1];
};

interface EventPayload {
pull_request?: {
head: {
sha: string;
ref: string;
};
number: number;
};
}

const readEventPayload = ({ env }: Context): EventPayload | null => {
if (!env.GITHUB_EVENT_PATH) return null;
if (!existsSync(env.GITHUB_EVENT_PATH)) return null;
Expand All @@ -39,18 +90,37 @@ const readEventPayload = ({ env }: Context): EventPayload | null => {
const service: Service = {
name: "GitHub Actions",
detect: ({ env }) => Boolean(env.GITHUB_ACTIONS),
config: ({ env }) => {
config: async ({ env }) => {
const payload = readEventPayload({ env });
return {
commit: process.env.GITHUB_SHA || null,
branch: payload?.pull_request?.head.ref || getBranch({ env }) || null,

const commonConfig = {
owner: env.GITHUB_REPOSITORY_OWNER || null,
repository: getRepository({ env }),
jobId: env.GITHUB_JOB || null,
runId: env.GITHUB_RUN_ID || null,
nonce: `${env.GITHUB_RUN_ID}-${env.GITHUB_RUN_ATTEMPT}` || null,
};

// If the job is triggered by from a "deployment" or a "deployment_status"
if (payload?.deployment) {
debug("Deployment event detected");
const { sha } = payload.deployment;
const pullRequest = await getPullRequestFromHeadSha({ env }, sha);
return {
...commonConfig,
commit: payload.deployment.sha,
branch: pullRequest?.head.ref || payload.deployment.environment || null,
prNumber: pullRequest?.number || null,
prHeadCommit: pullRequest?.head.sha || null,
};
}

return {
...commonConfig,
commit: process.env.GITHUB_SHA || null,
branch: payload?.pull_request?.head.ref || getBranch({ env }) || null,
prNumber: payload?.pull_request?.number || null,
prHeadCommit: payload?.pull_request?.head.sha ?? null,
nonce: `${env.GITHUB_RUN_ID}-${env.GITHUB_RUN_ATTEMPT}` || null,
};
},
};
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/ci-environment/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,7 @@ export interface CiEnvironment {
export interface Service {
name: string;
detect(ctx: Context): boolean;
config(ctx: Context): Omit<CiEnvironment, "name">;
config(
ctx: Context,
): Omit<CiEnvironment, "name"> | Promise<Omit<CiEnvironment, "name">>;
}
12 changes: 6 additions & 6 deletions packages/core/src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ import { describe, it, expect } from "vitest";
import { readConfig } from "./config";

describe("#createConfig", () => {
it("gets config", () => {
const config = readConfig({
it("gets config", async () => {
const config = await readConfig({
commit: "f16f980bd17cccfa93a1ae7766727e67950773d0",
});
expect(config.commit).toBe("f16f980bd17cccfa93a1ae7766727e67950773d0");
});

it("throws with invalid commit", () => {
expect(() => readConfig({ commit: "xx" })).toThrow(
it("throws with invalid commit", async () => {
await expect(() => readConfig({ commit: "xx" })).toThrow(
"commit: Invalid commit",
);
});

it("throws with invalid token", () => {
expect(() =>
it("throws with invalid token", async () => {
await expect(() =>
readConfig({
commit: "f16f980bd17cccfa93a1ae7766727e67950773d0",
token: "invalid",
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,10 @@ const createConfig = () => {
});
};

export const readConfig = (options: Partial<Config> = {}) => {
export async function readConfig(options: Partial<Config> = {}) {
const config = createConfig();

const ciEnv = getCiEnvironment();
const ciEnv = await getCiEnvironment();

config.load({
apiBaseUrl: options.apiBaseUrl ?? config.get("apiBaseUrl"),
Expand Down Expand Up @@ -183,4 +183,4 @@ export const readConfig = (options: Partial<Config> = {}) => {
config.validate();

return config.get();
};
}
16 changes: 9 additions & 7 deletions packages/core/src/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,17 @@ export interface UploadParameters {
referenceCommit?: string;
}

const getConfigFromOptions = ({ parallel, ...options }: UploadParameters) => {
const config = readConfig({
async function getConfigFromOptions({
parallel,
...options
}: UploadParameters) {
return readConfig({
...options,
parallel: Boolean(parallel),
parallelNonce: parallel ? parallel.nonce : null,
parallelTotal: parallel ? parallel.total : null,
});
return config;
};
}

async function uploadFilesToS3(
files: { url: string; path: string; contentType: string }[],
Expand Down Expand Up @@ -86,11 +88,11 @@ async function uploadFilesToS3(
/**
* Upload screenshots to argos-ci.com.
*/
export const upload = async (params: UploadParameters) => {
export async function upload(params: UploadParameters) {
debug("Starting upload with params", params);

// Read config
const config = getConfigFromOptions(params);
const config = await getConfigFromOptions(params);
const files = params.files ?? ["**/*.{png,jpg,jpeg}"];
debug("Using config and files", config, files);

Expand Down Expand Up @@ -209,4 +211,4 @@ export const upload = async (params: UploadParameters) => {
});

return { build: result.build, screenshots };
};
}
10 changes: 5 additions & 5 deletions packages/playwright/src/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ export type ArgosReporterOptions = Omit<UploadParameters, "files" | "root"> & {
uploadToArgos?: boolean;
};

const getParallelFromConfig = (
async function getParallelFromConfig(
config: FullConfig,
): null | UploadParameters["parallel"] => {
): Promise<null | UploadParameters["parallel"]> {
if (!config.shard) return null;
if (config.shard.total === 1) return null;
const argosConfig = readConfig();
const argosConfig = await readConfig();
if (!argosConfig.parallelNonce) {
throw new Error(
"Playwright shard mode detected. Please specify ARGOS_PARALLEL_NONCE env variable. Read /parallel-testing",
Expand All @@ -51,7 +51,7 @@ const getParallelFromConfig = (
total: config.shard.total,
nonce: argosConfig.parallelNonce,
};
};
}

class ArgosReporter implements Reporter {
uploadDir!: string;
Expand Down Expand Up @@ -130,7 +130,7 @@ class ArgosReporter implements Reporter {
async onEnd(_result: FullResult) {
if (!this.uploadToArgos) return;

const parallel = getParallelFromConfig(this.playwrightConfig);
const parallel = await getParallelFromConfig(this.playwrightConfig);

try {
const res = await upload({
Expand Down

0 comments on commit e9bf085

Please sign in to comment.