Skip to content

Commit

Permalink
feat: github tokenless strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
jsfez committed Oct 11, 2022
1 parent 740c35b commit 8ee6251
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 60 deletions.
172 changes: 133 additions & 39 deletions packages/core/src/api-client.test.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,149 @@
import { setupJest } from "../mocks/server";
import { ArgosApiClient, createArgosApiClient } from "./api-client";
import {
ArgosApiClient,
createArgosApiClient,
getBearerToken,
} from "./api-client";

setupJest();

let apiClient: ArgosApiClient;
beforeAll(() => {
apiClient = createArgosApiClient({
baseUrl: "https://api.argos-ci.dev",
token: "92d832e0d22ab113c8979d73a87a11130eaa24a9",

describe("#createArgosApiClient", () => {
beforeAll(() => {
apiClient = createArgosApiClient({
baseUrl: "https://api.argos-ci.dev",
bearerToken: "Bearer 92d832e0d22ab113c8979d73a87a11130eaa24a9",
});
});
});

describe("#createBuild", () => {
it("creates build", async () => {
const result = await apiClient.createBuild({
commit: "f16f980bd17cccfa93a1ae7766727e67950773d0",
screenshotKeys: ["123", "456"],
});
expect(result).toEqual({
build: {
id: "123",
url: "https://app.argos-ci.dev/builds/123",
},
screenshots: [
{
key: "123",
putUrl: "https://api.s3.dev/upload/123",
describe("#createBuild", () => {
it("creates build", async () => {
const result = await apiClient.createBuild({
commit: "f16f980bd17cccfa93a1ae7766727e67950773d0",
screenshotKeys: ["123", "456"],
});
expect(result).toEqual({
build: {
id: "123",
url: "https://app.argos-ci.dev/builds/123",
},
{
key: "456",
putUrl: "https://api.s3.dev/upload/456",
screenshots: [
{
key: "123",
putUrl: "https://api.s3.dev/upload/123",
},
{
key: "456",
putUrl: "https://api.s3.dev/upload/456",
},
],
});
});
});

describe("#updateBuild", () => {
it("updates build", async () => {
const result = await apiClient.updateBuild({
buildId: "123",
screenshots: [
{ key: "123", name: "screenshot 1" },
{ key: "456", name: "screenshot 2" },
],
});
expect(result).toEqual({
build: {
id: "123",
url: "https://app.argos-ci.dev/builds/123",
},
],
});
});
});
});

describe("#updateBuild", () => {
it("updates build", async () => {
const result = await apiClient.updateBuild({
buildId: "123",
screenshots: [
{ key: "123", name: "screenshot 1" },
{ key: "456", name: "screenshot 2" },
],
});
expect(result).toEqual({
build: {
id: "123",
url: "https://app.argos-ci.dev/builds/123",
},
describe("#getBearerToken", () => {
describe("without CI", () => {
describe("without token", () => {
it("should throw", () => {
const config = {};
expect(() => getBearerToken(config)).toThrow(
"Missing Argos repository token 'ARGOS_TOKEN'"
);
});
});

describe("with token", () => {
it("should return bearer token", () => {
const config = { token: "this-token" };
expect(getBearerToken(config)).toBe(`Bearer this-token`);
});
});
});

describe("with unknown CI", () => {
const configProps = { ciService: "unknownCI" };

describe("without token", () => {
it("should throw", () => {
const config = { ...configProps };
expect(() => getBearerToken(config)).toThrow(
"Missing Argos repository token 'ARGOS_TOKEN'"
);
});
});

describe("with token", () => {
it("should return bearer token", () => {
const config = { ...configProps, token: "this-token" };
expect(getBearerToken(config)).toBe(`Bearer this-token`);
});
});
});

describe("with Github Actions CI", () => {
const configProps = { ciService: "GitHub Actions" };

describe("with token", () => {
it("should return bearer token", () => {
const config = { ...configProps, token: "this-token" };
expect(getBearerToken(config)).toBe(`Bearer this-token`);
});
});

describe("without token but with CI env variables", () => {
it("should return a composite token", () => {
const config = {
...configProps,
owner: "this-owner",
repository: "this-repository",
jobId: "this-jobId",
};

const base64 = Buffer.from(
JSON.stringify({
owner: config.owner,
repository: config.repository,
jobId: config.jobId,
}),
"utf8"
).toString("base64");

const bearerToken = getBearerToken(config);

expect(bearerToken).toBe(`Bearer tokenless-github-${base64}`);
expect(bearerToken).toBe(
"Bearer tokenless-github-eyJvd25lciI6InRoaXMtb3duZXIiLCJyZXBvc2l0b3J5IjoidGhpcy1yZXBvc2l0b3J5Iiwiam9iSWQiOiJ0aGlzLWpvYklkIn0="
);
});
});

describe("without token and without CI env variables", () => {
it("should throw", () => {
const config = { ...configProps };
expect(() => getBearerToken(config)).toThrow(
"Automatic GitHub Actions variables detection failed. Please add the 'ARGOS_TOKEN'"
);
});
});
});
});
42 changes: 40 additions & 2 deletions packages/core/src/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import axios from "axios";

export interface ApiClientOptions {
baseUrl: string;
token: string;
bearerToken: string;
}

export interface CreateBuildInput {
Expand Down Expand Up @@ -44,13 +44,51 @@ export interface ArgosApiClient {
updateBuild: (input: UpdateBuildInput) => Promise<UpdateBuildOutput>;
}

const base64Encode = (obj: any) =>
Buffer.from(JSON.stringify(obj), "utf8").toString("base64");

export const getBearerToken = ({
token,
ciService,
owner,
repository,
jobId,
}: {
token?: string | null;
ciService?: string | null;
owner?: string | null;
repository?: string | null;
jobId?: string | null;
}) => {
if (token) return `Bearer ${token}`;

switch (ciService) {
case "GitHub Actions": {
if (!owner || !repository || !jobId) {
throw new Error(
`Automatic ${ciService} variables detection failed. Please add the 'ARGOS_TOKEN'`
);
}

return `Bearer tokenless-github-${base64Encode({
owner,
repository,
jobId,
})}`;
}

default:
throw new Error("Missing Argos repository token 'ARGOS_TOKEN'");
}
};

export const createArgosApiClient = (
options: ApiClientOptions
): ArgosApiClient => {
const axiosInstance = axios.create({
baseURL: options.baseUrl,
headers: {
Authorization: `Bearer ${options.token}`,
Authorization: options.bearerToken,
"Content-Type": "application/json",
Accept: "application/json",
},
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/ci-environment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ export const getCiEnvironment = ({
: null;
const commit = ciContext.commit ?? null;
const branch = (ciContext.branch || ciContext.prBranch) ?? null;
const slug = ciContext.slug ? ciContext.slug.split("/") : null;
const owner = slug ? slug[0] : null;
const repository = slug ? slug[1] : null;
const jobId = ciContext.job ?? null;

return commit ? { name, commit, branch } : null;
return commit ? { name, commit, branch, owner, repository, jobId } : null;
};
5 changes: 4 additions & 1 deletion packages/core/src/ci-environment/services/github-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,12 @@ function getBranch({ env }: Context) {
const service: Service = {
detect: ({ env }) => Boolean(env.GITHUB_ACTIONS),
config: ({ env }) => ({
name: "GiHub Actions",
name: "GitHub Actions",
commit: getSha({ env }),
branch: getBranch({ env }),
owner: env.GITHUB_REPOSITORY_OWNER || null,
repository: env.GITHUB_REPOSITORY || null,
jobId: env.GITHUB_JOB || null,
}),
};

Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/ci-environment/services/heroku.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ const service: Service = {
name: "Heroku",
commit: env.HEROKU_TEST_RUN_COMMIT_VERSION || null,
branch: env.HEROKU_TEST_RUN_BRANCH || null,
owner: null,
repository: null,
jobId: env.HEROKU_TEST_RUN_ID || null,
}),
};

Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/ci-environment/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export interface CiEnvironment {
name: string | null;
commit: string | null;
branch: string | null;
owner: string | null;
repository: string | null;
jobId: string | null;
}

export interface Service {
Expand Down
14 changes: 6 additions & 8 deletions packages/core/src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,24 @@ import { createConfig } from "./config";
describe("#createConfig", () => {
it("gets config", () => {
const config = createConfig();
config.load({
commit: "f16f980bd17cccfa93a1ae7766727e67950773d0",
token: "92d832e0d22ab113c8979d73a87a11130eaa24a9",
});
config.load({ commit: "f16f980bd17cccfa93a1ae7766727e67950773d0" });
expect(config.get()).toEqual({
apiBaseUrl: "https://api.argos-ci.com/v2/",
commit: "f16f980bd17cccfa93a1ae7766727e67950773d0",
branch: null,
token: "92d832e0d22ab113c8979d73a87a11130eaa24a9",
token: null,
buildName: null,
parallel: false,
parallelNonce: null,
parallelTotal: null,
ciService: null,
jobId: null,
repository: null,
owner: null,
});
});

it("throws with invalid commit", () => {
expect(() => createConfig().validate()).toThrow(
"commit: Invalid commit\ntoken: Must be a valid Argos repository token"
);
expect(() => createConfig().validate()).toThrow("commit: Invalid commit");
});
});
24 changes: 21 additions & 3 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const mustBeCommit = (value: any) => {
};

const mustBeArgosToken = (value: any) => {
if (value.length !== 40) {
if (value && value.length !== 40) {
throw new Error("Must be a valid Argos repository token");
}
};
Expand All @@ -46,7 +46,7 @@ const schema = {
},
token: {
env: "ARGOS_TOKEN",
default: "",
default: null,
format: mustBeArgosToken,
},
buildName: {
Expand Down Expand Up @@ -77,17 +77,35 @@ const schema = {
default: null,
nullable: true,
},
jobId: {
format: String,
default: null,
nullable: true,
},
owner: {
format: String,
default: null,
nullable: true,
},
repository: {
format: String,
default: null,
nullable: true,
},
};

export interface Config {
apiBaseUrl: string;
commit: string;
branch: string | null;
token: string;
token: string | null;
buildName: string | null;
parallel: boolean;
parallelNonce: string | null;
parallelTotal: number | null;
owner: string | null;
repository: string | null;
jobId: string | null;
}

export const createConfig = () => {
Expand Down
Loading

0 comments on commit 8ee6251

Please sign in to comment.