Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
908 changes: 460 additions & 448 deletions lib/entry-points.js

Large diffs are not rendered by default.

32 changes: 17 additions & 15 deletions src/action-common.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,26 @@ import { getErrorMessage } from "./util";

setupTests(test);

for (const [key, displayName] of Object.entries(
statusReport.ACTION_DISPLAY_NAMES,
)) {
test(`getDisplayActionName - '${displayName}' instead of '${key}'`, (t) => {
const expectedActionDisplayNames: Record<ActionName, string> = {
[ActionName.Analyze]: "analyze action",
[ActionName.AnalyzePost]: "analyze post-action",
[ActionName.Autobuild]: "autobuild action",
[ActionName.Init]: "init action",
[ActionName.InitPost]: "init post-action",
[ActionName.ResolveEnvironment]: "resolve-environment action",
[ActionName.SetupCodeQL]: "setup-codeql action",
[ActionName.StartProxy]: "start-proxy action",
[ActionName.StartProxyPost]: "start-proxy post-action",
[ActionName.UploadSarif]: "upload-sarif action",
[ActionName.UploadSarifPost]: "upload-sarif post-action",
};

for (const [key, displayName] of Object.entries(expectedActionDisplayNames)) {
test(`getDisplayActionName - '${displayName}' for '${key}'`, (t) => {
t.is(statusReport.getDisplayActionName(key as ActionName), displayName);
});
}

test("getDisplayActionName - returns action name", (t) => {
for (const actionName of Object.values(ActionName)) {
if (statusReport.ACTION_DISPLAY_NAMES[actionName]) {
continue;
}

t.is(statusReport.getDisplayActionName(actionName), actionName);
}
});

interface RunInActionsTestOpts {
runFn?: () => Promise<any>;
expectedErrorMessage?: string;
Expand Down Expand Up @@ -109,7 +111,7 @@ const runInActionsMacro = makeMacro({
if (expectFailure) {
t.true(
setFailed.calledOnceWithExactly(
`${statusReport.getDisplayActionName(name)} action failed: ${opts?.expectedErrorMessage}`,
`${statusReport.getDisplayActionName(name)} failed: ${opts?.expectedErrorMessage}`,
),
);
t.true(
Expand Down
12 changes: 10 additions & 2 deletions src/action-common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as core from "@actions/core";

import { ActionsEnv, getActionsEnv } from "./actions-util";
import { ActionsEnv, getActionsEnv, restoreInputs } from "./actions-util";
import type { ApiClient } from "./api-client";
import { Env, ReadOnlyEnv } from "./environment";
import type { FeatureEnablement } from "./feature-flags";
Expand All @@ -9,10 +9,13 @@ import {
ActionName,
getDisplayActionName,
getJobUUID,
isPostAction,
sendUnhandledErrorStatusReport,
} from "./status-report";
import { getEnv, getErrorMessage, wrapError } from "./util";

export { ActionName } from "./status-report";

/** Base state that is available to an Action on startup. */
export interface BaseState {
/** The name of the Action. */
Expand Down Expand Up @@ -178,13 +181,18 @@ export async function runInActions(action: Action) {
actions: actionsEnv,
};

// If this is a post-action, restore inputs from the main action.
if (isPostAction(action.name)) {
restoreInputs(actionState);
}

// Create a unique identifier for this run.
getJobUUID(actionState);

await action.run(actionState);
} catch (error) {
core.setFailed(
`${getDisplayActionName(action.name)} action failed: ${getErrorMessage(error)}`,
`${getDisplayActionName(action.name)} failed: ${getErrorMessage(error)}`,
);

const statusReportError =
Expand Down
26 changes: 20 additions & 6 deletions src/actions-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as toolrunner from "@actions/exec/lib/toolrunner";
import * as github from "@actions/github";
import * as io from "@actions/io";

import type { ActionState } from "./action-common";
import { hasConfigKey, type Config } from "./config/action-config";
import { Env, EnvVar, ActionsEnvVars, ReadOnlyEnv } from "./environment";
import { Logger } from "./logging";
Expand All @@ -30,6 +31,8 @@ declare const __CODEQL_ACTION_VERSION__: string;
export interface ActionsEnv {
getRequiredInput: (name: string) => string;
getOptionalInput: (name: string) => string | undefined;
setFailed: (message: string) => void;
getState: (name: string) => string;
}

/**
Expand All @@ -39,6 +42,8 @@ export function getActionsEnv(): ActionsEnv {
return {
getRequiredInput,
getOptionalInput,
setFailed: core.setFailed,
getState: core.getState,
};
}

Expand Down Expand Up @@ -403,6 +408,7 @@ export async function runTool(
return stdout;
}

type PersistedInputs = Array<[string, string | undefined]>;
const persistedInputsKey = "persisted_inputs";

/**
Expand All @@ -412,7 +418,7 @@ const persistedInputsKey = "persisted_inputs";
*/
export const persistInputs = function (env: Env = getEnv()) {
const entries = env.entries();
const inputEnvironmentVariables = entries.filter(([name]) =>
const inputEnvironmentVariables: PersistedInputs = entries.filter(([name]) =>
name.startsWith("INPUT_"),
);
core.saveState(persistedInputsKey, JSON.stringify(inputEnvironmentVariables));
Expand All @@ -421,18 +427,26 @@ export const persistInputs = function (env: Env = getEnv()) {
/**
* Restores all inputs to the action from the persisted state.
*/
export function restoreInputs(logger: Logger) {
export function restoreInputs(
action: ActionState<["Env", "Logger", "Actions"]>,
) {
try {
const persistedInputsValue = core.getState(persistedInputsKey);
const persistedInputsValue = action.actions.getState(persistedInputsKey);
if (persistedInputsValue) {
const persistedInputs = JSON.parse(persistedInputsValue);
const persistedInputs = JSON.parse(
persistedInputsValue,
) as PersistedInputs;

if (!Array.isArray(persistedInputs)) {
throw new Error("Restored value is not an array.");
}

for (const [name, value] of persistedInputs) {
process.env[name] = value;
action.env.set(name, value);
}
}
} catch (err) {
logger.error(`Unable to restore inputs: ${getErrorMessage(err)}`);
action.logger.error(`Unable to restore inputs: ${getErrorMessage(err)}`);
throw new Error(
"Failed to restore inputs from the state set by this action's main execution.",
);
Expand Down
33 changes: 25 additions & 8 deletions src/analyze-action-post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
*/
import * as fs from "fs";

import * as core from "@actions/core";

import {
ActionName,
runInActions,
type Action,
type ActionState,
} from "./action-common";
import * as actionsUtil from "./actions-util";
import { getGitHubVersion } from "./api-client";
import { getConfig } from "./config-utils";
Expand All @@ -16,22 +20,24 @@ import {
getJavaTempDependencyDir,
} from "./dependency-caching";
import { EnvVar } from "./environment";
import { getActionsLogger } from "./logging";
import { checkGitHubVersionInRange, getErrorMessage } from "./util";

export async function runWrapper() {
/**
* The main functionality of the `analyze` post-action is implemented by this function.
* This is called once by {@link runInActions} after it is called in {@link runWrapper}.
*/
export async function run(action: ActionState<["Logger", "Env", "Actions"]>) {
// To capture errors appropriately, keep as much code within the try-catch as
// possible, and only use safe functions outside.
const logger = action.logger;

try {
const logger = getActionsLogger();
actionsUtil.restoreInputs(logger);
const gitHubVersion = await getGitHubVersion();
checkGitHubVersionInRange(gitHubVersion, logger);

// Upload SARIF artifacts if we determine that this is a first-party analysis run.
// For third-party runs, this artifact will be uploaded in the `upload-sarif-post` step.
if (process.env[EnvVar.INIT_ACTION_HAS_RUN] === "true") {
if (action.env.getOptional(EnvVar.INIT_ACTION_HAS_RUN) === "true") {
const config = await getConfig(
actionsUtil.getTemporaryDirectory(),
logger,
Expand Down Expand Up @@ -63,8 +69,19 @@ export async function runWrapper() {
}
}
} catch (error) {
core.setFailed(
action.actions.setFailed(
`analyze post-action step failed: ${getErrorMessage(error)}`,
);
}
}

/** Defines the `analyze` post-action. */
const analyzePostAction: Action = {
name: ActionName.AnalyzePost,
run,
};

/** The main entry point for the `analyze` post-action. */
export async function runWrapper() {
await runInActions(analyzePostAction);
}
24 changes: 9 additions & 15 deletions src/init-action-post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,8 @@
* other `post:` hooks.
*/

import * as core from "@actions/core";

import { runInActions, type Action, type ActionState } from "./action-common";
import {
restoreInputs,
getOptionalInput,
getTemporaryDirectory,
printDebugLogs,
} from "./actions-util";
import { getTemporaryDirectory, printDebugLogs } from "./actions-util";
import { getGitHubVersion, wrapApiConfigurationError } from "./api-client";
import { CachingKind } from "./caching-utils";
import { getCodeQL } from "./codeql";
Expand Down Expand Up @@ -49,7 +42,11 @@ interface InitPostStatusReport
initActionPostHelper.JobStatusReport,
initActionPostHelper.DependencyCachingUsageReport {}

async function run(action: ActionState<["Base", "Logger", "Env"]>) {
/**
* The main functionality of the `init` post-action is implemented by this function.
* This is called once by {@link runInActions} after it is called in {@link runWrapper}.
*/
async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
// To capture errors appropriately, keep as much code within the try-catch as
// possible, and only use safe functions outside.
const logger = action.logger;
Expand All @@ -64,10 +61,7 @@ async function run(action: ActionState<["Base", "Logger", "Env"]>) {
// Read the job status before restoring inputs, since it is provided by the Actions runtime
// environment for this step and would otherwise be overwritten by the value that the `init`
// Action saw, which is always a success.
const jobStatus = getOptionalInput("job-status");

// Restore inputs from `init` Action.
restoreInputs(logger);
const jobStatus = action.actions.getOptionalInput("job-status");

const tmpDir = getTemporaryDirectory();
config = await getConfig(tmpDir, logger);
Expand All @@ -89,7 +83,7 @@ async function run(action: ActionState<["Base", "Logger", "Env"]>) {
const features = initFeatures(
gitHubVersion,
repositoryNwo,
getTemporaryDirectory(),
config?.tempDir ?? tmpDir,
logger,
);

Expand Down Expand Up @@ -129,7 +123,7 @@ async function run(action: ActionState<["Base", "Logger", "Env"]>) {
}
} catch (unwrappedError) {
const error = wrapApiConfigurationError(wrapError(unwrappedError));
core.setFailed(error.message);
action.actions.setFailed(error.message);

const statusReportBase = await createStatusReportBase(
action.name,
Expand Down
Loading
Loading