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 1113bbe
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 43 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;
};
}
128 changes: 113 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,78 @@
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) {
throw new Error("GITHUB_REPOSITORY is missing");
}
if (!env.GITHUB_TOKEN) {
if (!env.DISABLE_GITHUB_TOKEN_WARNING) {
console.log(
`
Running argos from a "deployment_status" event requires a GITHUB_TOKEN.
Please add \`GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}\` as environment variable.
Read more at https://argos-ci.com/docs/run-on-preview-deployment
`.trim(),
);
}
return null;
}
try {
const result = await axios.get<GitHubPullRequest[]>(
`https://api.github.com/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,37 +93,62 @@ 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;
return JSON.parse(readFileSync(env.GITHUB_EVENT_PATH, "utf-8"));
};

/**
* Special token used to reference the reference branch of the project.
* Interpreted by the Argos backend.
*/
const REFERENCE_BRANCH = "__argos/reference-branch";

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 sha = process.env.GITHUB_SHA || null;

if (!sha) {
throw new Error(`GITHUB_SHA is missing`);
}

const commonConfig = {
commit: sha,
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 pullRequest = await getPullRequestFromHeadSha({ env }, sha);
// If the deployment is on production we use the reference branch
// specified on Argos as the reference branch.
const referenceBranch =
payload.deployment.environment.toLowerCase() === "production"
? REFERENCE_BRANCH
: null;
return {
...commonConfig,
branch: pullRequest?.head.ref || payload.deployment.environment || null,
referenceBranch,
prNumber: pullRequest?.number || null,
prHeadCommit: pullRequest?.head.sha || null,
};
}

return {
...commonConfig,
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
5 changes: 4 additions & 1 deletion packages/core/src/ci-environment/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ export interface CiEnvironment {
prNumber: number | null;
prHeadCommit: string | null;
nonce: string | null;
referenceBranch?: string | null;
}

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">>;
}
16 changes: 9 additions & 7 deletions packages/core/src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,27 @@ 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" })).rejects.toThrow(
"commit: Invalid commit",
);
});

it("throws with invalid token", () => {
expect(() =>
it("throws with invalid token", async () => {
await expect(() =>
readConfig({
commit: "f16f980bd17cccfa93a1ae7766727e67950773d0",
token: "invalid",
}),
).toThrow("token: Invalid Argos repository token (must be 40 characters)");
).rejects.toThrow(
"token: Invalid Argos repository token (must be 40 characters)",
);
});
});
11 changes: 7 additions & 4 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 All @@ -163,7 +163,10 @@ export const readConfig = (options: Partial<Config> = {}) => {
options.prNumber ?? config.get("prNumber") ?? ciEnv?.prNumber ?? null,
prHeadCommit: config.get("prHeadCommit") ?? ciEnv?.prHeadCommit ?? null,
referenceBranch:
options.referenceBranch ?? config.get("referenceBranch") ?? null,
options.referenceBranch ??
config.get("referenceBranch") ??
ciEnv?.referenceBranch ??
null,
referenceCommit:
options.referenceCommit ?? config.get("referenceCommit") ?? null,
ciService: ciEnv?.name ?? null,
Expand All @@ -183,4 +186,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 1113bbe

Please sign in to comment.