Skip to content

Commit 2413b3c

Browse files
committed
CI/CD check for required variables:
Added a check in CI/CD environments for `account_id`, `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN`. If `account_id` exists in `wrangler.toml` then `CLOUDFLARE_ACCOUNT_ID` is not needed in CI/CD scope. The `CLOUDFLARE_API_TOKEN` is necessary in CI/CD scope and will always error if missing. resolves #827
1 parent 277b254 commit 2413b3c

File tree

8 files changed

+148
-16
lines changed

8 files changed

+148
-16
lines changed

.changeset/eighty-yaks-jump.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
feat: Added a check in CI/CD environments for `account_id`, `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN`. If `account_id` exists in `wrangler.toml`
6+
then `CLOUDFLARE_ACCOUNT_ID` is not needed in CI/CD scope. The `CLOUDFLARE_API_TOKEN` is necessary in CI/CD scope and will always error if missing.
7+
8+
resolves #827

package-lock.json

+5-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { mockConsoleMethods } from "./helpers/mock-console";
2+
import { useMockIsTTY } from "./helpers/mock-istty";
3+
import { runInTempDir } from "./helpers/run-in-tmp";
4+
import { runWrangler } from "./helpers/run-wrangler";
5+
import writeWranglerToml from "./helpers/write-wrangler-toml";
6+
7+
const ENV_COPY = process.env;
8+
mockConsoleMethods();
9+
runInTempDir();
10+
11+
afterEach(() => {
12+
process.env = ENV_COPY;
13+
});
14+
15+
describe("CI", () => {
16+
const { setIsTTY } = useMockIsTTY();
17+
setIsTTY(false);
18+
19+
it("should not throw an error in CI if 'CLOUDFLARE_API_TOKEN' & 'account_id' are in scope", async () => {
20+
writeWranglerToml({
21+
account_id: "IG-88",
22+
});
23+
24+
process.env = {
25+
CLOUDFLARE_API_TOKEN: "123456789",
26+
};
27+
28+
await runWrangler().catch((err) => {
29+
expect(err).toMatchInlineSnapshot(`""`);
30+
});
31+
});
32+
33+
it("should not throw an error if 'CLOUDFLARE_ACCOUNT_ID' & 'CLOUDFLARE_API_TOKEN' are in scope", async () => {
34+
process.env = {
35+
CLOUDFLARE_API_TOKEN: "hunter2",
36+
CLOUDFLARE_ACCOUNT_ID: "IG-88",
37+
};
38+
39+
await runWrangler().catch((err) => {
40+
expect(err).toMatchInlineSnapshot(`""`);
41+
});
42+
});
43+
44+
it("should throw an error in CI if 'account_id' & 'CLOUDFLARE_ACCOUNT_ID' is missing", async () => {
45+
writeWranglerToml({
46+
account_id: undefined,
47+
});
48+
49+
process.env = {
50+
CLOUDFLARE_API_TOKEN: "hunter2",
51+
CLOUDFLARE_ACCOUNT_ID: undefined,
52+
};
53+
54+
await runWrangler().catch((err) => {
55+
expect(err).toMatchInlineSnapshot(
56+
`[Error: Missing "account_id" from "wrangler.toml" and "CLOUDFLARE_ACCOUNT_ID" from CI environment, one is required, please see docs for more info: TBD]`
57+
);
58+
});
59+
});
60+
61+
it("should throw error in CI if 'CLOUDFLARE_API_TOKEN' is missing", async () => {
62+
writeWranglerToml({
63+
account_id: undefined,
64+
});
65+
66+
process.env = {
67+
CLOUDFLARE_API_TOKEN: undefined,
68+
CLOUDFLARE_ACCOUNT_ID: "badwolf",
69+
};
70+
await runWrangler().catch((err) => {
71+
expect(err).toMatchInlineSnapshot(
72+
`[Error: Missing "CLOUDFLARE_API_TOKEN" from CI environment, please see docs for more info: TBD]`
73+
);
74+
});
75+
});
76+
77+
it("should throw errors in CI if 'CLOUDFLARE_API_TOKEN', 'account_id' & 'CLOUDFLARE_ACCOUNT_ID is missing", async () => {
78+
await runWrangler().catch((err) => {
79+
expect(err).toMatchInlineSnapshot(
80+
`[Error: Missing "account_id" from "wrangler.toml" and "CLOUDFLARE_ACCOUNT_ID" "CLOUDFLARE_API_TOKEN" from CI environment, please see docs for more info: TBD]`
81+
);
82+
});
83+
});
84+
});

packages/wrangler/src/__tests__/jest.setup.ts

+3
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,6 @@ jest.mock("../dev/dev", () => {
7171
// Make sure that we don't accidentally try to open a browser window when running tests.
7272
// We will actually provide a mock implementation for `openInBrowser()` within relevant tests.
7373
jest.mock("../open-in-browser");
74+
75+
// Must mock `ciCheck()` implementation to prevent tests in actual CI throwing unnecessary errors.
76+
jest.mock("../ci-check.ts");

packages/wrangler/src/__tests__/sentry.test.ts

+8-12
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,27 @@ import * as Sentry from "@sentry/node";
77
import prompts from "prompts";
88

99
import { mockConsoleMethods } from "./helpers/mock-console";
10+
import { useMockIsTTY } from "./helpers/mock-istty";
1011
import { runInTempDir } from "./helpers/run-in-tmp";
1112
import { runWrangler } from "./helpers/run-wrangler";
1213
const { reportError } = jest.requireActual("../reporting");
1314

1415
describe("Error Reporting", () => {
16+
const { setIsTTY } = useMockIsTTY();
17+
1518
runInTempDir({ homedir: "./home" });
1619
mockConsoleMethods();
1720
const reportingTOMLPath = ".wrangler/config/reporting.toml";
1821

19-
const originalTTY = process.stdout.isTTY;
2022
beforeEach(() => {
2123
jest.mock("@sentry/node");
2224
jest.spyOn(Sentry, "captureException");
23-
process.stdout.isTTY = true;
25+
setIsTTY(true);
2426
});
2527

2628
afterEach(() => {
2729
jest.unmock("@sentry/node");
2830
jest.clearAllMocks();
29-
process.stdout.isTTY = originalTTY;
3031
});
3132

3233
it("should confirm user will allow error reporting usage", async () => {
@@ -127,20 +128,15 @@ describe("Error Reporting", () => {
127128
});
128129

129130
it("should not prompt in non-TTY environment", async () => {
130-
process.stdout.isTTY = false;
131-
131+
setIsTTY(false);
132132
await reportError(new Error("test error"), "testFalse");
133133

134-
const { error_tracking_opt, error_tracking_opt_date } = TOML.parse(
135-
await fsp.readFile(path.join(os.homedir(), reportingTOMLPath), "utf-8")
136-
);
137-
138-
expect(error_tracking_opt).toBe(false);
139-
expect(error_tracking_opt_date).toBeTruthy();
134+
expect(
135+
fs.existsSync(path.join(os.homedir(), reportingTOMLPath, "utf-8"))
136+
).toBe(false);
140137

141138
expect(Sentry.captureException).not.toHaveBeenCalledWith(
142139
new Error("test error")
143140
);
144-
process.stdout.isTTY = originalTTY;
145141
});
146142
});

packages/wrangler/src/ci-check.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { readConfig } from "./config";
2+
import type { State } from "./user";
3+
4+
/**
5+
* Inside a CI/CD environment, you want to check if all the required variables are in scope
6+
* and if not, throw a helpful error message with missing variables and documentation link.
7+
*/
8+
export function ciCheck(localState: State) {
9+
if (process.stdout.isTTY) {
10+
const config = readConfig(undefined, {});
11+
12+
if (
13+
!localState.accessToken &&
14+
!config.account_id &&
15+
!process.env.CLOUDFLARE_ACCOUNT_ID
16+
) {
17+
throw new Error(
18+
`Missing "account_id" from "wrangler.toml" and "CLOUDFLARE_ACCOUNT_ID" "CLOUDFLARE_API_TOKEN" from CI environment, please see docs for more info: TBD`
19+
);
20+
}
21+
22+
if (!process.env.CLOUDFLARE_API_TOKEN) {
23+
throw new Error(
24+
`Missing "CLOUDFLARE_API_TOKEN" from CI environment, please see docs for more info: TBD`
25+
);
26+
}
27+
28+
if (!config.account_id && !process.env.CLOUDFLARE_ACCOUNT_ID) {
29+
throw new Error(
30+
`Missing "account_id" from "wrangler.toml" and "CLOUDFLARE_ACCOUNT_ID" from CI environment, one is required, please see docs for more info: TBD`
31+
);
32+
}
33+
}
34+
}

packages/wrangler/src/reporting.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ function exceptionTransaction(error: Error, origin = "") {
9292
}
9393

9494
export async function reportError(err: Error, origin = "") {
95-
if (!process.stdout.isTTY) return await appendReportingDecision("false");
95+
// If the user has not opted in to error reporting, we don't want to do anything in CI or non-interactive environments
96+
if (!process.stdout.isTTY) return;
9697

9798
const errorTrackingOpt = await reportingPermission();
9899

packages/wrangler/src/user.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ import Table from "ink-table";
220220
import React from "react";
221221
import { fetch } from "undici";
222222
import { getCloudflareApiBaseUrl } from "./cfetch";
223+
import { ciCheck } from "./ci-check";
223224
import { getEnvironmentVariableFactory } from "./environment-variables";
224225
import openInBrowser from "./open-in-browser";
225226
import { parseTOML, readFileSync } from "./parse";
@@ -256,7 +257,7 @@ interface PKCECodes {
256257
/**
257258
* The module level state of the authentication flow.
258259
*/
259-
interface State extends AuthTokens {
260+
export interface State extends AuthTokens {
260261
authorizationCode?: string;
261262
codeChallenge?: string;
262263
codeVerifier?: string;
@@ -402,6 +403,7 @@ export function reinitialiseAuthTokens(config?: UserAuthConfig): void {
402403
}
403404

404405
export function getAPIToken(): string | undefined {
406+
ciCheck(LocalState);
405407
if (LocalState.apiToken) {
406408
console.warn(
407409
"It looks like you have used Wrangler 1's `config` command to login with an API token.\n" +
@@ -1071,6 +1073,7 @@ export function listScopes(message = "💁 Available scopes:"): void {
10711073
export async function getAccountId(
10721074
isInteractive = true
10731075
): Promise<string | undefined> {
1076+
ciCheck(LocalState);
10741077
const apiToken = getAPIToken();
10751078
if (!apiToken) return;
10761079

0 commit comments

Comments
 (0)