Skip to content

Commit ae4c8d0

Browse files
authored
Merge pull request #3993 from github/mbg/env-abstraction
Allow abstracting over `process.env`
2 parents 3cf0a52 + 73b0850 commit ae4c8d0

6 files changed

Lines changed: 117 additions & 38 deletions

File tree

lib/entry-points.js

Lines changed: 24 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/actions-util.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,28 @@ import {
2121
*/
2222
declare const __CODEQL_ACTION_VERSION__: string;
2323

24+
/**
25+
* Enumerates known GitHub Actions environment variables that we expect
26+
* to be set in a GitHub Actions environment.
27+
*/
28+
export enum ActionsEnvVars {
29+
GITHUB_ACTION_REPOSITORY = "GITHUB_ACTION_REPOSITORY",
30+
GITHUB_API_URL = "GITHUB_API_URL",
31+
GITHUB_EVENT_NAME = "GITHUB_EVENT_NAME",
32+
GITHUB_EVENT_PATH = "GITHUB_EVENT_PATH",
33+
GITHUB_JOB = "GITHUB_JOB",
34+
GITHUB_REF = "GITHUB_REF",
35+
GITHUB_REPOSITORY = "GITHUB_REPOSITORY",
36+
GITHUB_RUN_ATTEMPT = "GITHUB_RUN_ATTEMPT",
37+
GITHUB_RUN_ID = "GITHUB_RUN_ID",
38+
GITHUB_SERVER_URL = "GITHUB_SERVER_URL",
39+
GITHUB_SHA = "GITHUB_SHA",
40+
GITHUB_WORKFLOW = "GITHUB_WORKFLOW",
41+
RUNNER_NAME = "RUNNER_NAME",
42+
RUNNER_OS = "RUNNER_OS",
43+
RUNNER_TEMP = "RUNNER_TEMP",
44+
}
45+
2446
/**
2547
* Abstracts over GitHub Actions functions so that we do not have to stub
2648
* global functions in tests.
@@ -65,7 +87,7 @@ export function getTemporaryDirectory(): string {
6587
const value = process.env["CODEQL_ACTION_TEMP"];
6688
return value !== undefined && value !== ""
6789
? value
68-
: getRequiredEnvParam("RUNNER_TEMP");
90+
: getRequiredEnvParam(ActionsEnvVars.RUNNER_TEMP);
6991
}
7092

7193
const PR_DIFF_RANGE_JSON_FILENAME = "pr-diff-range.json";
@@ -84,7 +106,7 @@ export function getActionVersion(): string {
84106
* This will be "dynamic" for default setup workflow runs.
85107
*/
86108
export function getWorkflowEventName() {
87-
return getRequiredEnvParam("GITHUB_EVENT_NAME");
109+
return getRequiredEnvParam(ActionsEnvVars.GITHUB_EVENT_NAME);
88110
}
89111

90112
/**
@@ -104,14 +126,14 @@ export function isRunningLocalAction(): boolean {
104126
* This can be used to get the Action's name or tell if we're running a local Action.
105127
*/
106128
function getRelativeScriptPath(): string {
107-
const runnerTemp = getRequiredEnvParam("RUNNER_TEMP");
129+
const runnerTemp = getRequiredEnvParam(ActionsEnvVars.RUNNER_TEMP);
108130
const actionsDirectory = path.join(path.dirname(runnerTemp), "_actions");
109131
return path.relative(actionsDirectory, __filename);
110132
}
111133

112134
/** Returns the contents of `GITHUB_EVENT_PATH` as a JSON object. */
113135
export function getWorkflowEvent(): any {
114-
const eventJsonFile = getRequiredEnvParam("GITHUB_EVENT_PATH");
136+
const eventJsonFile = getRequiredEnvParam(ActionsEnvVars.GITHUB_EVENT_PATH);
115137
try {
116138
return JSON.parse(fs.readFileSync(eventJsonFile, "utf-8"));
117139
} catch (e) {
@@ -181,16 +203,16 @@ export function getUploadValue(input: string | undefined): UploadKind {
181203
* Get the workflow run ID.
182204
*/
183205
export function getWorkflowRunID(): number {
184-
const workflowRunIdString = getRequiredEnvParam("GITHUB_RUN_ID");
206+
const workflowRunIdString = getRequiredEnvParam(ActionsEnvVars.GITHUB_RUN_ID);
185207
const workflowRunID = parseInt(workflowRunIdString, 10);
186208
if (Number.isNaN(workflowRunID)) {
187209
throw new Error(
188-
`GITHUB_RUN_ID must define a non NaN workflow run ID. Current value is ${workflowRunIdString}`,
210+
`${ActionsEnvVars.GITHUB_RUN_ID} must define a non NaN workflow run ID. Current value is ${workflowRunIdString}`,
189211
);
190212
}
191213
if (workflowRunID < 0) {
192214
throw new Error(
193-
`GITHUB_RUN_ID must be a non-negative integer. Current value is ${workflowRunIdString}`,
215+
`${ActionsEnvVars.GITHUB_RUN_ID} must be a non-negative integer. Current value is ${workflowRunIdString}`,
194216
);
195217
}
196218
return workflowRunID;
@@ -200,16 +222,18 @@ export function getWorkflowRunID(): number {
200222
* Get the workflow run attempt number.
201223
*/
202224
export function getWorkflowRunAttempt(): number {
203-
const workflowRunAttemptString = getRequiredEnvParam("GITHUB_RUN_ATTEMPT");
225+
const workflowRunAttemptString = getRequiredEnvParam(
226+
ActionsEnvVars.GITHUB_RUN_ATTEMPT,
227+
);
204228
const workflowRunAttempt = parseInt(workflowRunAttemptString, 10);
205229
if (Number.isNaN(workflowRunAttempt)) {
206230
throw new Error(
207-
`GITHUB_RUN_ATTEMPT must define a non NaN workflow run attempt. Current value is ${workflowRunAttemptString}`,
231+
`${ActionsEnvVars.GITHUB_RUN_ATTEMPT} must define a non NaN workflow run attempt. Current value is ${workflowRunAttemptString}`,
208232
);
209233
}
210234
if (workflowRunAttempt <= 0) {
211235
throw new Error(
212-
`GITHUB_RUN_ATTEMPT must be a positive integer. Current value is ${workflowRunAttemptString}`,
236+
`${ActionsEnvVars.GITHUB_RUN_ATTEMPT} must be a positive integer. Current value is ${workflowRunAttemptString}`,
213237
);
214238
}
215239
return workflowRunAttempt;

src/api-client.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import * as core from "@actions/core";
22
import * as githubUtils from "@actions/github/lib/utils";
33
import * as retry from "@octokit/plugin-retry";
44

5-
import { getActionVersion, getRequiredInput } from "./actions-util";
5+
import {
6+
ActionsEnvVars,
7+
getActionVersion,
8+
getRequiredInput,
9+
} from "./actions-util";
610
import { EnvVar } from "./environment";
711
import { Logger } from "./logging";
812
import { getRepositoryNwo, RepositoryNwo } from "./repository";
@@ -70,8 +74,8 @@ function createApiClientWithDetails(
7074
export function getApiDetails(): GitHubApiDetails {
7175
return {
7276
auth: getRequiredInput("token"),
73-
url: getRequiredEnvParam("GITHUB_SERVER_URL"),
74-
apiURL: getRequiredEnvParam("GITHUB_API_URL"),
77+
url: getRequiredEnvParam(ActionsEnvVars.GITHUB_SERVER_URL),
78+
apiURL: getRequiredEnvParam(ActionsEnvVars.GITHUB_API_URL),
7579
};
7680
}
7781

src/environment.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,11 @@ export enum EnvVar {
160160
/** Used by Code Scanning Risk Assessment to communicate the assessment ID to the CodeQL Action. */
161161
RISK_ASSESSMENT_ID = "CODEQL_ACTION_RISK_ASSESSMENT_ID",
162162
}
163+
164+
/** A wrapper around an environment, to allow abstracting away from `process.env` in tests. */
165+
export interface Env {
166+
/** Tries to get the value for `name` and throws if there isn't one. */
167+
getRequired(name: string): string;
168+
/** Gets the value for `name`, or `undefined` if it isn't set or empty. */
169+
getOptional(name: string): string | undefined;
170+
}

src/testing-utils.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ import test, {
1010
import nock from "nock";
1111
import * as sinon from "sinon";
1212

13-
import { ActionsEnv, getActionVersion } from "./actions-util";
13+
import { ActionsEnv, ActionsEnvVars, getActionVersion } from "./actions-util";
1414
import { AnalysisKind } from "./analyses";
1515
import * as apiClient from "./api-client";
1616
import { GitHubApiDetails } from "./api-client";
1717
import { CachingKind } from "./caching-utils";
1818
import * as codeql from "./codeql";
1919
import { Config } from "./config-utils";
2020
import * as defaults from "./defaults.json";
21+
import { Env } from "./environment";
2122
import {
2223
CodeQLDefaultVersionInfo,
2324
Feature,
@@ -29,6 +30,7 @@ import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
2930
import {
3031
DEFAULT_DEBUG_ARTIFACT_NAME,
3132
DEFAULT_DEBUG_DATABASE_NAME,
33+
getEnv,
3234
GitHubVariant,
3335
GitHubVersion,
3436
HTTPError,
@@ -172,6 +174,11 @@ export function makeMacro<Args extends unknown[]>(
172174
return wrapper;
173175
}
174176

177+
export function getTestEnv(): Env {
178+
const testEnv: NodeJS.ProcessEnv = {};
179+
return getEnv(testEnv);
180+
}
181+
175182
/**
176183
* Gets an `ActionsEnv` instance for use in tests.
177184
*/
@@ -200,7 +207,7 @@ export const DEFAULT_ACTIONS_VARS = {
200207
GITHUB_WORKFLOW: "test-workflow",
201208
RUNNER_NAME: "my-runner",
202209
RUNNER_OS: "Linux",
203-
} as const satisfies Record<string, string>;
210+
} as const satisfies Partial<Record<ActionsEnvVars, string>>;
204211

205212
/** Partial mappings from GitHub Actions environment variables to values. */
206213
export type ActionVarOverrides = Partial<

src/util.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import * as apiCompatibility from "./api-compatibility.json";
1313
import type { CodeQL, VersionInfo } from "./codeql";
1414
import type { Pack } from "./config/db-config";
1515
import type { Config } from "./config-utils";
16-
import { EnvVar } from "./environment";
16+
import { Env, EnvVar } from "./environment";
1717
import * as json from "./json";
1818
import { Language } from "./languages";
1919
import { Logger } from "./logging";
@@ -566,28 +566,56 @@ export function initializeEnvironment(version: string) {
566566
core.exportVariable(EnvVar.VERSION, version);
567567
}
568568

569+
/** Gets an `Env` instance for `env`, which is `process.env` by default. */
570+
export function getEnv(env: NodeJS.ProcessEnv = process.env): Env {
571+
return {
572+
getRequired: (name) => getRequiredEnvVar(env, name),
573+
getOptional: (name) => getOptionalEnvVarFrom(env, name),
574+
};
575+
}
576+
569577
/**
570-
* Get an environment parameter, but throw an error if it is not set.
578+
* Gets an environment variable, but throws an error if it is not set.
571579
*/
572-
export function getRequiredEnvParam(paramName: string): string {
573-
const value = process.env[paramName];
580+
export function getRequiredEnvVar(
581+
env: NodeJS.ProcessEnv,
582+
paramName: string,
583+
): string {
584+
const value = env[paramName];
574585
if (value === undefined || value.length === 0) {
575586
throw new Error(`${paramName} environment variable must be set`);
576587
}
577588
return value;
578589
}
579590

580591
/**
581-
* Get an environment variable, but return `undefined` if it is not set or empty.
592+
* Get an environment parameter, but throw an error if it is not set.
582593
*/
583-
export function getOptionalEnvVar(paramName: string): string | undefined {
584-
const value = process.env[paramName];
594+
export function getRequiredEnvParam(paramName: string): string {
595+
return getRequiredEnvVar(process.env, paramName);
596+
}
597+
598+
/**
599+
* Gets an environment variable, but returns `undefined` if it is not set or empty.
600+
*/
601+
export function getOptionalEnvVarFrom(
602+
env: NodeJS.ProcessEnv,
603+
paramName: string,
604+
): string | undefined {
605+
const value = env[paramName];
585606
if (value?.trim().length === 0) {
586607
return undefined;
587608
}
588609
return value;
589610
}
590611

612+
/**
613+
* Get an environment variable, but return `undefined` if it is not set or empty.
614+
*/
615+
export function getOptionalEnvVar(paramName: string): string | undefined {
616+
return getOptionalEnvVarFrom(process.env, paramName);
617+
}
618+
591619
export class HTTPError extends Error {
592620
public status: number;
593621

0 commit comments

Comments
 (0)