mirror of
https://github.com/github/codeql-action.git
synced 2026-10-03 17:41:28 +00:00
Merge pull request #3990 from github/mbg/action-state
Add `ActionState` type and make some initial use of it
This commit is contained in:
2386
lib/entry-points.js
generated
2386
lib/entry-points.js
generated
File diff suppressed because it is too large
Load Diff
95
src/action-common.ts
Normal file
95
src/action-common.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import * as core from "@actions/core";
|
||||||
|
|
||||||
|
import { ActionsEnv, getActionsEnv } from "./actions-util";
|
||||||
|
import { Env } from "./environment";
|
||||||
|
import { FeatureEnablement } from "./feature-flags";
|
||||||
|
import { getActionsLogger, Logger } from "./logging";
|
||||||
|
import {
|
||||||
|
ActionName,
|
||||||
|
getDisplayActionName,
|
||||||
|
sendUnhandledErrorStatusReport,
|
||||||
|
} from "./status-report";
|
||||||
|
import { getEnv, getErrorMessage } from "./util";
|
||||||
|
|
||||||
|
/** Common state that is always available in `ActionState`. */
|
||||||
|
export interface BaseState {
|
||||||
|
/** The name of the Action. */
|
||||||
|
name: ActionName;
|
||||||
|
/** When the Action was started. */
|
||||||
|
startedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Describes different state features that an Action may have. */
|
||||||
|
export interface FeatureState {
|
||||||
|
Logger: {
|
||||||
|
/** The logger that is in use. */
|
||||||
|
logger: Logger;
|
||||||
|
};
|
||||||
|
Env: {
|
||||||
|
/** Information about environment variables. */
|
||||||
|
env: Env;
|
||||||
|
};
|
||||||
|
Actions: {
|
||||||
|
/** Access to Actions-related functionality. */
|
||||||
|
actions: ActionsEnv;
|
||||||
|
};
|
||||||
|
FeatureFlags: {
|
||||||
|
/** Information about enabled feature flags. */
|
||||||
|
features: FeatureEnablement;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Identifies a type of state an Action may have. */
|
||||||
|
export type StateFeature = keyof FeatureState;
|
||||||
|
|
||||||
|
/** Constructs the intersection of all state types identifies by `Fs`. */
|
||||||
|
export type FieldsOf<Fs extends readonly StateFeature[]> = Fs extends []
|
||||||
|
? BaseState
|
||||||
|
: Fs extends [
|
||||||
|
infer Head extends StateFeature,
|
||||||
|
...infer Tail extends readonly StateFeature[],
|
||||||
|
]
|
||||||
|
? FeatureState[Head] & FieldsOf<Tail>
|
||||||
|
: never;
|
||||||
|
|
||||||
|
/** Describes the state of an Action that has access to the state corresponding to `Fs`. */
|
||||||
|
export type ActionState<Fs extends readonly StateFeature[]> = FieldsOf<Fs>;
|
||||||
|
|
||||||
|
/** The type of an Action's main entry point. This is a function that is provided
|
||||||
|
* with a basic `ActionState` object with features that are always available.
|
||||||
|
* Each Action can then augment the `state` further if additional features are required.
|
||||||
|
*/
|
||||||
|
export type ActionMain = (
|
||||||
|
state: ActionState<["Logger", "Env", "Actions"]>,
|
||||||
|
) => Promise<void>;
|
||||||
|
|
||||||
|
/** A specification for a CodeQL Action step. */
|
||||||
|
export interface Action {
|
||||||
|
/** The name of the Action. */
|
||||||
|
name: ActionName;
|
||||||
|
/** The entry point for the Action. */
|
||||||
|
run: ActionMain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A generic entry point that sets up the basic environment for the `action` and runs it. */
|
||||||
|
export async function runInActions(action: Action) {
|
||||||
|
const startedAt = new Date();
|
||||||
|
const logger = getActionsLogger();
|
||||||
|
const env = getEnv();
|
||||||
|
const actionsEnv = getActionsEnv();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await action.run({
|
||||||
|
name: action.name,
|
||||||
|
startedAt,
|
||||||
|
logger,
|
||||||
|
env,
|
||||||
|
actions: actionsEnv,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
core.setFailed(
|
||||||
|
`${getDisplayActionName(action.name)} action failed: ${getErrorMessage(error)}`,
|
||||||
|
);
|
||||||
|
await sendUnhandledErrorStatusReport(action.name, startedAt, error, logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import { performance } from "perf_hooks";
|
|||||||
|
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
|
|
||||||
|
import { Action, ActionState, runInActions } from "./action-common";
|
||||||
import * as actionsUtil from "./actions-util";
|
import * as actionsUtil from "./actions-util";
|
||||||
import * as analyses from "./analyses";
|
import * as analyses from "./analyses";
|
||||||
import {
|
import {
|
||||||
@@ -40,7 +41,6 @@ import {
|
|||||||
createStatusReportBase,
|
createStatusReportBase,
|
||||||
DatabaseCreationTimings,
|
DatabaseCreationTimings,
|
||||||
getActionsStatus,
|
getActionsStatus,
|
||||||
sendUnhandledErrorStatusReport,
|
|
||||||
StatusReportBase,
|
StatusReportBase,
|
||||||
} from "./status-report";
|
} from "./status-report";
|
||||||
import {
|
import {
|
||||||
@@ -212,7 +212,7 @@ async function runAutobuildIfLegacyGoWorkflow(config: Config, logger: Logger) {
|
|||||||
await runAutobuild(config, BuiltInLanguage.go, logger);
|
await runAutobuild(config, BuiltInLanguage.go, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function run(startedAt: Date) {
|
async function run({ startedAt, logger }: ActionState<["Logger"]>) {
|
||||||
// To capture errors appropriately, keep as much code within the try-catch as
|
// To capture errors appropriately, keep as much code within the try-catch as
|
||||||
// possible, and only use safe functions outside.
|
// possible, and only use safe functions outside.
|
||||||
|
|
||||||
@@ -228,7 +228,6 @@ async function run(startedAt: Date) {
|
|||||||
let didUploadTrapCaches = false;
|
let didUploadTrapCaches = false;
|
||||||
let dependencyCacheResults: DependencyCacheUploadStatusReport | undefined;
|
let dependencyCacheResults: DependencyCacheUploadStatusReport | undefined;
|
||||||
let databaseUploadResults: DatabaseUploadResult[] = [];
|
let databaseUploadResults: DatabaseUploadResult[] = [];
|
||||||
const logger = getActionsLogger();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
util.initializeEnvironment(actionsUtil.getActionVersion());
|
util.initializeEnvironment(actionsUtil.getActionVersion());
|
||||||
@@ -523,19 +522,13 @@ async function run(startedAt: Date) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Defines the `analyze` Action. */
|
||||||
|
const analyze: Action = {
|
||||||
|
name: ActionName.Analyze,
|
||||||
|
run,
|
||||||
|
};
|
||||||
|
|
||||||
export async function runWrapper() {
|
export async function runWrapper() {
|
||||||
const startedAt = new Date();
|
await runInActions(analyze);
|
||||||
const logger = getActionsLogger();
|
|
||||||
try {
|
|
||||||
await run(startedAt);
|
|
||||||
} catch (error) {
|
|
||||||
core.setFailed(`analyze action failed: ${util.getErrorMessage(error)}`);
|
|
||||||
await sendUnhandledErrorStatusReport(
|
|
||||||
ActionName.Analyze,
|
|
||||||
startedAt,
|
|
||||||
error,
|
|
||||||
logger,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await util.checkForTimeout();
|
await util.checkForTimeout();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
|
|
||||||
|
import { Action, ActionState, runInActions } from "./action-common";
|
||||||
import {
|
import {
|
||||||
getActionVersion,
|
getActionVersion,
|
||||||
getOptionalInput,
|
getOptionalInput,
|
||||||
@@ -11,13 +12,12 @@ import { getCodeQL } from "./codeql";
|
|||||||
import { Config, getConfig } from "./config-utils";
|
import { Config, getConfig } from "./config-utils";
|
||||||
import { EnvVar } from "./environment";
|
import { EnvVar } from "./environment";
|
||||||
import { Language } from "./languages";
|
import { Language } from "./languages";
|
||||||
import { Logger, getActionsLogger } from "./logging";
|
import { Logger } from "./logging";
|
||||||
import {
|
import {
|
||||||
StatusReportBase,
|
StatusReportBase,
|
||||||
getActionsStatus,
|
getActionsStatus,
|
||||||
createStatusReportBase,
|
createStatusReportBase,
|
||||||
sendStatusReport,
|
sendStatusReport,
|
||||||
sendUnhandledErrorStatusReport,
|
|
||||||
ActionName,
|
ActionName,
|
||||||
} from "./status-report";
|
} from "./status-report";
|
||||||
import { endTracingForCluster } from "./tracer-config";
|
import { endTracingForCluster } from "./tracer-config";
|
||||||
@@ -26,7 +26,6 @@ import {
|
|||||||
checkDiskUsage,
|
checkDiskUsage,
|
||||||
checkGitHubVersionInRange,
|
checkGitHubVersionInRange,
|
||||||
ConfigurationError,
|
ConfigurationError,
|
||||||
getErrorMessage,
|
|
||||||
initializeEnvironment,
|
initializeEnvironment,
|
||||||
wrapError,
|
wrapError,
|
||||||
} from "./util";
|
} from "./util";
|
||||||
@@ -69,11 +68,10 @@ async function sendCompletedStatusReport(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function run(startedAt: Date) {
|
async function run({ startedAt, logger }: ActionState<["Logger"]>) {
|
||||||
// To capture errors appropriately, keep as much code within the try-catch as
|
// To capture errors appropriately, keep as much code within the try-catch as
|
||||||
// possible, and only use safe functions outside.
|
// possible, and only use safe functions outside.
|
||||||
|
|
||||||
const logger = getActionsLogger();
|
|
||||||
let config: Config | undefined;
|
let config: Config | undefined;
|
||||||
let currentLanguage: Language | undefined;
|
let currentLanguage: Language | undefined;
|
||||||
let languages: Language[] | undefined;
|
let languages: Language[] | undefined;
|
||||||
@@ -142,18 +140,12 @@ async function run(startedAt: Date) {
|
|||||||
await sendCompletedStatusReport(config, logger, startedAt, languages ?? []);
|
await sendCompletedStatusReport(config, logger, startedAt, languages ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Defines the `autobuild` Action. */
|
||||||
|
const autobuild: Action = {
|
||||||
|
name: ActionName.Autobuild,
|
||||||
|
run,
|
||||||
|
};
|
||||||
|
|
||||||
export async function runWrapper() {
|
export async function runWrapper() {
|
||||||
const startedAt = new Date();
|
await runInActions(autobuild);
|
||||||
const logger = getActionsLogger();
|
|
||||||
try {
|
|
||||||
await run(startedAt);
|
|
||||||
} catch (error) {
|
|
||||||
core.setFailed(`autobuild action failed. ${getErrorMessage(error)}`);
|
|
||||||
await sendUnhandledErrorStatusReport(
|
|
||||||
ActionName.Autobuild,
|
|
||||||
startedAt,
|
|
||||||
error,
|
|
||||||
logger,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,19 @@
|
|||||||
import test from "ava";
|
import test from "ava";
|
||||||
import sinon from "sinon";
|
import sinon from "sinon";
|
||||||
|
|
||||||
|
import { Feature } from "../feature-flags";
|
||||||
import { RepositoryPropertyName } from "../feature-flags/properties";
|
import { RepositoryPropertyName } from "../feature-flags/properties";
|
||||||
import {
|
import { callee, setupTests } from "../testing-utils";
|
||||||
getTestActionsEnv,
|
|
||||||
RecordingLogger,
|
|
||||||
setupTests,
|
|
||||||
} from "../testing-utils";
|
|
||||||
|
|
||||||
import { getConfigFileInput } from "./file";
|
import { getConfigFileInput } from "./file";
|
||||||
|
|
||||||
setupTests(test);
|
setupTests(test);
|
||||||
|
|
||||||
test("getConfigFileInput returns undefined by default", async (t) => {
|
test("getConfigFileInput returns undefined by default", async (t) => {
|
||||||
const logger = new RecordingLogger();
|
await callee(getConfigFileInput)
|
||||||
const actionsEnv = getTestActionsEnv();
|
.withArgs({})
|
||||||
const result = getConfigFileInput(logger, actionsEnv, {}, true);
|
.withFeatures([Feature.ConfigFileRepositoryProperty])
|
||||||
t.is(result, undefined);
|
.passes(async (fn) => t.is(await fn(), undefined));
|
||||||
});
|
});
|
||||||
|
|
||||||
const repositoryProperties = {
|
const repositoryProperties = {
|
||||||
@@ -24,9 +21,12 @@ const repositoryProperties = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
test("getConfigFileInput returns input value", async (t) => {
|
test("getConfigFileInput returns input value", async (t) => {
|
||||||
const logger = new RecordingLogger();
|
|
||||||
const actionsEnv = getTestActionsEnv();
|
|
||||||
const testInput = "/some/path";
|
const testInput = "/some/path";
|
||||||
|
const target = callee(getConfigFileInput).withFeatures([
|
||||||
|
Feature.ConfigFileRepositoryProperty,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const actionsEnv = target.getState().actions;
|
||||||
sinon
|
sinon
|
||||||
.stub(actionsEnv, "getOptionalInput")
|
.stub(actionsEnv, "getOptionalInput")
|
||||||
.withArgs("config-file")
|
.withArgs("config-file")
|
||||||
@@ -34,76 +34,63 @@ test("getConfigFileInput returns input value", async (t) => {
|
|||||||
|
|
||||||
// Even though both an input and repository property are configured,
|
// Even though both an input and repository property are configured,
|
||||||
// we prefer the direct input to the Action.
|
// we prefer the direct input to the Action.
|
||||||
const result = getConfigFileInput(
|
const targetWithArgs = target
|
||||||
logger,
|
.withActions(actionsEnv)
|
||||||
actionsEnv,
|
.withArgs(repositoryProperties);
|
||||||
repositoryProperties,
|
await targetWithArgs.passes(async (fn) => t.is(await fn(), testInput));
|
||||||
true,
|
|
||||||
);
|
|
||||||
t.is(result, testInput);
|
|
||||||
|
|
||||||
// Check for the expected log message.
|
|
||||||
t.true(logger.hasMessage("Using configuration file input from workflow"));
|
|
||||||
});
|
|
||||||
|
|
||||||
test("getConfigFileInput returns repository property value", async (t) => {
|
|
||||||
const logger = new RecordingLogger();
|
|
||||||
const actionsEnv = getTestActionsEnv();
|
|
||||||
|
|
||||||
// Since there is no direct input, we should use the repository property.
|
|
||||||
const result = getConfigFileInput(
|
|
||||||
logger,
|
|
||||||
actionsEnv,
|
|
||||||
repositoryProperties,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
t.is(result, repositoryProperties[RepositoryPropertyName.CONFIG_FILE]);
|
|
||||||
|
|
||||||
// Check for the expected log message.
|
// Check for the expected log message.
|
||||||
t.true(
|
t.true(
|
||||||
logger.hasMessage(
|
targetWithArgs
|
||||||
"Using configuration file input from repository property",
|
.getLogger()
|
||||||
),
|
.hasMessage("Using configuration file input from workflow"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getConfigFileInput returns repository property value", async (t) => {
|
||||||
|
// Since there is no direct input, we should use the repository property.
|
||||||
|
const target = callee(getConfigFileInput)
|
||||||
|
.withFeatures([Feature.ConfigFileRepositoryProperty])
|
||||||
|
.withArgs(repositoryProperties);
|
||||||
|
|
||||||
|
await target.passes(async (fn) =>
|
||||||
|
t.is(await fn(), repositoryProperties[RepositoryPropertyName.CONFIG_FILE]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check for the expected log message.
|
||||||
|
t.true(
|
||||||
|
target
|
||||||
|
.getLogger()
|
||||||
|
.hasMessage("Using configuration file input from repository property"),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getConfigFileInput ignores empty repository property value", async (t) => {
|
test("getConfigFileInput ignores empty repository property value", async (t) => {
|
||||||
const logger = new RecordingLogger();
|
|
||||||
const actionsEnv = getTestActionsEnv();
|
|
||||||
|
|
||||||
// Since the repository property value is an empty/whitespace string, we should ignore it.
|
// Since the repository property value is an empty/whitespace string, we should ignore it.
|
||||||
const result = getConfigFileInput(
|
await callee(getConfigFileInput)
|
||||||
logger,
|
.withFeatures([Feature.ConfigFileRepositoryProperty])
|
||||||
actionsEnv,
|
.withArgs({ [RepositoryPropertyName.CONFIG_FILE]: " " })
|
||||||
{
|
.passes(async (fn) => t.is(await fn(), undefined));
|
||||||
[RepositoryPropertyName.CONFIG_FILE]: " ",
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
t.is(result, undefined);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("getConfigFileInput ignores repository property value when FF is off", async (t) => {
|
test("getConfigFileInput ignores repository property value when FF is off", async (t) => {
|
||||||
const logger = new RecordingLogger();
|
|
||||||
const actionsEnv = getTestActionsEnv();
|
|
||||||
|
|
||||||
// Since the FF is off, we should ignore the repository property value.
|
// Since the FF is off, we should ignore the repository property value.
|
||||||
const result = getConfigFileInput(
|
const target = callee(getConfigFileInput)
|
||||||
logger,
|
.withFeatures([])
|
||||||
actionsEnv,
|
.withArgs(repositoryProperties);
|
||||||
repositoryProperties,
|
|
||||||
false,
|
await target.passes(async (fn) => t.is(await fn(), undefined));
|
||||||
);
|
|
||||||
t.is(result, undefined);
|
|
||||||
|
|
||||||
t.false(
|
t.false(
|
||||||
logger.hasMessage(
|
target
|
||||||
"Using configuration file input from repository property",
|
.getLogger()
|
||||||
),
|
.hasMessage("Using configuration file input from repository property"),
|
||||||
);
|
);
|
||||||
t.true(
|
t.true(
|
||||||
logger.hasMessage(
|
target
|
||||||
"Ignoring configuration file input from repository property, because the corresponding feature flag is disabled.",
|
.getLogger()
|
||||||
),
|
.hasMessage(
|
||||||
|
"Ignoring configuration file input from repository property, because the corresponding feature flag is disabled.",
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
import { ActionsEnv } from "../actions-util";
|
import { ActionState } from "../action-common";
|
||||||
|
import { Feature } from "../feature-flags";
|
||||||
import {
|
import {
|
||||||
RepositoryProperties,
|
RepositoryProperties,
|
||||||
RepositoryPropertyName,
|
RepositoryPropertyName,
|
||||||
} from "../feature-flags/properties";
|
} from "../feature-flags/properties";
|
||||||
import { Logger } from "../logging";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the value that is configured for the configuration file, if any.
|
* Gets the value that is configured for the configuration file, if any.
|
||||||
*/
|
*/
|
||||||
export function getConfigFileInput(
|
export async function getConfigFileInput(
|
||||||
logger: Logger,
|
{
|
||||||
actions: ActionsEnv,
|
logger,
|
||||||
|
actions,
|
||||||
|
features,
|
||||||
|
}: ActionState<["Logger", "Actions", "FeatureFlags"]>,
|
||||||
repositoryProperties: Partial<RepositoryProperties>,
|
repositoryProperties: Partial<RepositoryProperties>,
|
||||||
useRepositoryProperty: boolean,
|
): Promise<string | undefined> {
|
||||||
): string | undefined {
|
|
||||||
const input = actions.getOptionalInput("config-file");
|
const input = actions.getOptionalInput("config-file");
|
||||||
|
|
||||||
if (input !== undefined) {
|
if (input !== undefined) {
|
||||||
@@ -26,6 +28,10 @@ export function getConfigFileInput(
|
|||||||
|
|
||||||
if (propertyValue !== undefined && propertyValue.trim().length > 0) {
|
if (propertyValue !== undefined && propertyValue.trim().length > 0) {
|
||||||
// Only use the repository property value if the FF is enabled.
|
// Only use the repository property value if the FF is enabled.
|
||||||
|
const useRepositoryProperty = await features.getValue(
|
||||||
|
Feature.ConfigFileRepositoryProperty,
|
||||||
|
);
|
||||||
|
|
||||||
if (useRepositoryProperty) {
|
if (useRepositoryProperty) {
|
||||||
logger.info(
|
logger.info(
|
||||||
`Using configuration file input from repository property: ${propertyValue}`,
|
`Using configuration file input from repository property: ${propertyValue}`,
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import * as io from "@actions/io";
|
|||||||
import * as semver from "semver";
|
import * as semver from "semver";
|
||||||
import { v4 as uuidV4 } from "uuid";
|
import { v4 as uuidV4 } from "uuid";
|
||||||
|
|
||||||
|
import { Action, ActionState, runInActions } from "./action-common";
|
||||||
import {
|
import {
|
||||||
FileCmdNotFoundError,
|
FileCmdNotFoundError,
|
||||||
getActionsEnv,
|
|
||||||
getActionVersion,
|
getActionVersion,
|
||||||
getFileType,
|
getFileType,
|
||||||
getOptionalInput,
|
getOptionalInput,
|
||||||
@@ -56,7 +56,7 @@ import {
|
|||||||
runDatabaseInitCluster,
|
runDatabaseInitCluster,
|
||||||
} from "./init";
|
} from "./init";
|
||||||
import { JavaEnvVars, BuiltInLanguage } from "./languages";
|
import { JavaEnvVars, BuiltInLanguage } from "./languages";
|
||||||
import { getActionsLogger, Logger, withGroupAsync } from "./logging";
|
import { Logger, withGroupAsync } from "./logging";
|
||||||
import {
|
import {
|
||||||
downloadOverlayBaseDatabaseFromCache,
|
downloadOverlayBaseDatabaseFromCache,
|
||||||
OverlayBaseDatabaseDownloadStats,
|
OverlayBaseDatabaseDownloadStats,
|
||||||
@@ -73,7 +73,6 @@ import {
|
|||||||
createStatusReportBase,
|
createStatusReportBase,
|
||||||
getActionsStatus,
|
getActionsStatus,
|
||||||
sendStatusReport,
|
sendStatusReport,
|
||||||
sendUnhandledErrorStatusReport,
|
|
||||||
} from "./status-report";
|
} from "./status-report";
|
||||||
import { ZstdAvailability } from "./tar";
|
import { ZstdAvailability } from "./tar";
|
||||||
import { ToolsDownloadStatusReport } from "./tools-download";
|
import { ToolsDownloadStatusReport } from "./tools-download";
|
||||||
@@ -204,12 +203,12 @@ async function sendCompletedStatusReport(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function run(startedAt: Date) {
|
async function run(actionState: ActionState<["Logger", "Actions"]>) {
|
||||||
// To capture errors appropriately, keep as much code within the try-catch as
|
// To capture errors appropriately, keep as much code within the try-catch as
|
||||||
// possible, and only use safe functions outside.
|
// possible, and only use safe functions outside.
|
||||||
|
|
||||||
const logger = getActionsLogger();
|
const startedAt = actionState.startedAt;
|
||||||
const actionsEnv = getActionsEnv();
|
const logger = actionState.logger;
|
||||||
|
|
||||||
let apiDetails: GitHubApiCombinedDetails;
|
let apiDetails: GitHubApiCombinedDetails;
|
||||||
let config: configUtils.Config | undefined;
|
let config: configUtils.Config | undefined;
|
||||||
@@ -263,14 +262,9 @@ async function run(startedAt: Date) {
|
|||||||
|
|
||||||
core.exportVariable(EnvVar.INIT_ACTION_HAS_RUN, "true");
|
core.exportVariable(EnvVar.INIT_ACTION_HAS_RUN, "true");
|
||||||
|
|
||||||
const useConfigFileProperty = await features.getValue(
|
configFile = await getConfigFileInput(
|
||||||
Feature.ConfigFileRepositoryProperty,
|
{ ...actionState, features },
|
||||||
);
|
|
||||||
configFile = getConfigFileInput(
|
|
||||||
logger,
|
|
||||||
actionsEnv,
|
|
||||||
repositoryProperties,
|
repositoryProperties,
|
||||||
useConfigFileProperty,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// path.resolve() respects the intended semantics of source-root. If
|
// path.resolve() respects the intended semantics of source-root. If
|
||||||
@@ -854,19 +848,13 @@ async function recordZstdAvailability(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Defines the `init` Action. */
|
||||||
|
const init: Action = {
|
||||||
|
name: ActionName.Init,
|
||||||
|
run,
|
||||||
|
};
|
||||||
|
|
||||||
export async function runWrapper() {
|
export async function runWrapper() {
|
||||||
const startedAt = new Date();
|
await runInActions(init);
|
||||||
const logger = getActionsLogger();
|
|
||||||
try {
|
|
||||||
await run(startedAt);
|
|
||||||
} catch (error) {
|
|
||||||
core.setFailed(`init action failed: ${getErrorMessage(error)}`);
|
|
||||||
await sendUnhandledErrorStatusReport(
|
|
||||||
ActionName.Init,
|
|
||||||
startedAt,
|
|
||||||
error,
|
|
||||||
logger,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await checkForTimeout();
|
await checkForTimeout();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import { v4 as uuidV4 } from "uuid";
|
import { v4 as uuidV4 } from "uuid";
|
||||||
|
|
||||||
|
import { Action, ActionState, runInActions } from "./action-common";
|
||||||
import {
|
import {
|
||||||
getActionVersion,
|
getActionVersion,
|
||||||
getOptionalInput,
|
getOptionalInput,
|
||||||
@@ -14,7 +15,7 @@ import { getRawLanguagesNoAutodetect } from "./config-utils";
|
|||||||
import { EnvVar } from "./environment";
|
import { EnvVar } from "./environment";
|
||||||
import { initFeatures } from "./feature-flags";
|
import { initFeatures } from "./feature-flags";
|
||||||
import { initCodeQL } from "./init";
|
import { initCodeQL } from "./init";
|
||||||
import { getActionsLogger, Logger } from "./logging";
|
import { Logger } from "./logging";
|
||||||
import { getRepositoryNwo } from "./repository";
|
import { getRepositoryNwo } from "./repository";
|
||||||
import { ToolsSource } from "./setup-codeql";
|
import { ToolsSource } from "./setup-codeql";
|
||||||
import {
|
import {
|
||||||
@@ -24,7 +25,6 @@ import {
|
|||||||
createStatusReportBase,
|
createStatusReportBase,
|
||||||
getActionsStatus,
|
getActionsStatus,
|
||||||
sendStatusReport,
|
sendStatusReport,
|
||||||
sendUnhandledErrorStatusReport,
|
|
||||||
} from "./status-report";
|
} from "./status-report";
|
||||||
import { ToolsDownloadStatusReport } from "./tools-download";
|
import { ToolsDownloadStatusReport } from "./tools-download";
|
||||||
import {
|
import {
|
||||||
@@ -36,7 +36,6 @@ import {
|
|||||||
ConfigurationError,
|
ConfigurationError,
|
||||||
wrapError,
|
wrapError,
|
||||||
checkActionVersion,
|
checkActionVersion,
|
||||||
getErrorMessage,
|
|
||||||
} from "./util";
|
} from "./util";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,12 +87,13 @@ async function sendCompletedStatusReport(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** The main behaviour of this action. */
|
/** The main behaviour of this action. */
|
||||||
async function run(startedAt: Date): Promise<void> {
|
async function run({
|
||||||
|
startedAt,
|
||||||
|
logger,
|
||||||
|
}: ActionState<["Logger"]>): Promise<void> {
|
||||||
// To capture errors appropriately, keep as much code within the try-catch as
|
// To capture errors appropriately, keep as much code within the try-catch as
|
||||||
// possible, and only use safe functions outside.
|
// possible, and only use safe functions outside.
|
||||||
|
|
||||||
const logger = getActionsLogger();
|
|
||||||
|
|
||||||
let codeql: CodeQL;
|
let codeql: CodeQL;
|
||||||
let toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined;
|
let toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined;
|
||||||
let toolsFeatureFlagsValid: boolean | undefined;
|
let toolsFeatureFlagsValid: boolean | undefined;
|
||||||
@@ -195,20 +195,14 @@ async function run(startedAt: Date): Promise<void> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Defines the `setup-codeql` Action. */
|
||||||
|
const setupCodeQL: Action = {
|
||||||
|
name: ActionName.SetupCodeQL,
|
||||||
|
run,
|
||||||
|
};
|
||||||
|
|
||||||
/** Run the action and catch any unhandled errors. */
|
/** Run the action and catch any unhandled errors. */
|
||||||
export async function runWrapper(): Promise<void> {
|
export async function runWrapper(): Promise<void> {
|
||||||
const startedAt = new Date();
|
await runInActions(setupCodeQL);
|
||||||
const logger = getActionsLogger();
|
|
||||||
try {
|
|
||||||
await run(startedAt);
|
|
||||||
} catch (error) {
|
|
||||||
core.setFailed(`setup-codeql action failed: ${getErrorMessage(error)}`);
|
|
||||||
await sendUnhandledErrorStatusReport(
|
|
||||||
ActionName.SetupCodeQL,
|
|
||||||
startedAt,
|
|
||||||
error,
|
|
||||||
logger,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await checkForTimeout();
|
await checkForTimeout();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,17 @@ export enum ActionName {
|
|||||||
UploadSarif = "upload-sarif",
|
UploadSarif = "upload-sarif",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps an `ActionName` to its display name. Usually that is the same, except
|
||||||
|
* for `ActionName.Analyze` where it is `"analyze"` instead of `"finish"`.
|
||||||
|
*/
|
||||||
|
export function getDisplayActionName(actionName: ActionName): string {
|
||||||
|
if (actionName === ActionName.Analyze) {
|
||||||
|
return "analyze";
|
||||||
|
}
|
||||||
|
return actionName;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns a boolean indicating whether the analysis is considered to be first party.
|
* @returns a boolean indicating whether the analysis is considered to be first party.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import test, {
|
|||||||
import nock from "nock";
|
import nock from "nock";
|
||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
|
|
||||||
|
import { ActionState, StateFeature } from "./action-common";
|
||||||
import { ActionsEnv, ActionsEnvVars, getActionVersion } from "./actions-util";
|
import { ActionsEnv, ActionsEnvVars, getActionVersion } from "./actions-util";
|
||||||
import { AnalysisKind } from "./analyses";
|
import { AnalysisKind } from "./analyses";
|
||||||
import * as apiClient from "./api-client";
|
import * as apiClient from "./api-client";
|
||||||
@@ -27,6 +28,7 @@ import {
|
|||||||
} from "./feature-flags";
|
} from "./feature-flags";
|
||||||
import { Logger } from "./logging";
|
import { Logger } from "./logging";
|
||||||
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
|
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
|
||||||
|
import { ActionName } from "./status-report";
|
||||||
import {
|
import {
|
||||||
DEFAULT_DEBUG_ARTIFACT_NAME,
|
DEFAULT_DEBUG_ARTIFACT_NAME,
|
||||||
DEFAULT_DEBUG_DATABASE_NAME,
|
DEFAULT_DEBUG_DATABASE_NAME,
|
||||||
@@ -188,6 +190,108 @@ export function getTestActionsEnv(): ActionsEnv {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** For testing purposes, we make all available state features accessible in `TestEnv`. */
|
||||||
|
type AllState = ["Logger", "Env", "Actions", "FeatureFlags"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a function that accepts an `ActionState` for testing in different environments.
|
||||||
|
*/
|
||||||
|
export class TestEnv<
|
||||||
|
Args extends readonly any[],
|
||||||
|
R,
|
||||||
|
Fs extends ReadonlyArray<AllState[number]>,
|
||||||
|
> {
|
||||||
|
private readonly fn: (state: ActionState<Fs>, ...args: Args) => R;
|
||||||
|
private args?: Args;
|
||||||
|
private logger: RecordingLogger;
|
||||||
|
private state: ActionState<AllState>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
fn: (state: ActionState<Fs>, ...args: Args) => R,
|
||||||
|
cloneFrom?: TestEnv<Args, R, Fs>,
|
||||||
|
) {
|
||||||
|
this.fn = fn;
|
||||||
|
this.args = cloneFrom?.args;
|
||||||
|
this.logger = new RecordingLogger();
|
||||||
|
this.state =
|
||||||
|
cloneFrom !== undefined
|
||||||
|
? { ...cloneFrom.state, logger: this.logger }
|
||||||
|
: {
|
||||||
|
name: ActionName.Init,
|
||||||
|
startedAt: new Date(),
|
||||||
|
logger: this.logger,
|
||||||
|
env: getTestEnv(),
|
||||||
|
actions: getTestActionsEnv(),
|
||||||
|
features: createFeatures([]),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private clone(): TestEnv<Args, R, Fs> {
|
||||||
|
return new TestEnv(this.fn, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLogger(): RecordingLogger {
|
||||||
|
return this.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getState(): ActionState<AllState> {
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getArgs(): Args | undefined {
|
||||||
|
return this.args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public withArgs(...args: Args) {
|
||||||
|
const result = this.clone();
|
||||||
|
result.args = args;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public withFeatures(enabled: Feature[]): TestEnv<Args, R, Fs> {
|
||||||
|
const result = this.clone();
|
||||||
|
result.state.features = createFeatures(enabled);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public withEnv(env: Env): TestEnv<Args, R, Fs> {
|
||||||
|
const result = this.clone();
|
||||||
|
result.state.env = env;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public withActions(actions: ActionsEnv): TestEnv<Args, R, Fs> {
|
||||||
|
const result = this.clone();
|
||||||
|
result.state.actions = actions;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
call(): R {
|
||||||
|
if (!this.args) {
|
||||||
|
throw new Error("Trying to call function in TestEnv without arguments.");
|
||||||
|
}
|
||||||
|
return this.fn(this.state as unknown as ActionState<Fs>, ...this.args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public passes<T>(
|
||||||
|
assertion: (makeCall: () => R) => T | Promise<T>,
|
||||||
|
): T | Promise<T> {
|
||||||
|
return assertion(() => {
|
||||||
|
const result = this.call();
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Utility function to construct a `TestEnv`. */
|
||||||
|
export function callee<
|
||||||
|
Args extends readonly any[],
|
||||||
|
R,
|
||||||
|
Fs extends readonly StateFeature[],
|
||||||
|
>(fn: (state: ActionState<Fs>, ...args: Args) => R): TestEnv<Args, R, Fs> {
|
||||||
|
return new TestEnv(fn);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default values for environment variables typically set in an Actions
|
* Default values for environment variables typically set in an Actions
|
||||||
* environment. Tests can override individual variables by passing them in the
|
* environment. Tests can override individual variables by passing them in the
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
|
|
||||||
|
import { Action, ActionState, runInActions } from "./action-common";
|
||||||
import * as actionsUtil from "./actions-util";
|
import * as actionsUtil from "./actions-util";
|
||||||
import { getActionVersion, getTemporaryDirectory } from "./actions-util";
|
import { getActionVersion, getTemporaryDirectory } from "./actions-util";
|
||||||
import * as analyses from "./analyses";
|
import * as analyses from "./analyses";
|
||||||
import { getGitHubVersion } from "./api-client";
|
import { getGitHubVersion } from "./api-client";
|
||||||
import { initFeatures } from "./feature-flags";
|
import { initFeatures } from "./feature-flags";
|
||||||
import { Logger, getActionsLogger } from "./logging";
|
import { Logger } from "./logging";
|
||||||
import { getRepositoryNwo } from "./repository";
|
import { getRepositoryNwo } from "./repository";
|
||||||
import { InvalidSarifUploadError } from "./sarif";
|
import { InvalidSarifUploadError } from "./sarif";
|
||||||
import {
|
import {
|
||||||
createStatusReportBase,
|
createStatusReportBase,
|
||||||
sendStatusReport,
|
sendStatusReport,
|
||||||
sendUnhandledErrorStatusReport,
|
|
||||||
StatusReportBase,
|
StatusReportBase,
|
||||||
getActionsStatus,
|
getActionsStatus,
|
||||||
ActionName,
|
ActionName,
|
||||||
@@ -23,7 +23,6 @@ import {
|
|||||||
ConfigurationError,
|
ConfigurationError,
|
||||||
checkActionVersion,
|
checkActionVersion,
|
||||||
checkDiskUsage,
|
checkDiskUsage,
|
||||||
getErrorMessage,
|
|
||||||
initializeEnvironment,
|
initializeEnvironment,
|
||||||
shouldSkipSarifUpload,
|
shouldSkipSarifUpload,
|
||||||
wrapError,
|
wrapError,
|
||||||
@@ -55,12 +54,9 @@ async function sendSuccessStatusReport(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function run(startedAt: Date) {
|
async function run({ startedAt, logger }: ActionState<["Logger"]>) {
|
||||||
// To capture errors appropriately, keep as much code within the try-catch as
|
// To capture errors appropriately, keep as much code within the try-catch as
|
||||||
// possible, and only use safe functions outside.
|
// possible, and only use safe functions outside.
|
||||||
|
|
||||||
const logger = getActionsLogger();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
initializeEnvironment(getActionVersion());
|
initializeEnvironment(getActionVersion());
|
||||||
|
|
||||||
@@ -165,20 +161,12 @@ async function run(startedAt: Date) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Defines the `upload-sarif` Action. */
|
||||||
|
const uploadSarif: Action = {
|
||||||
|
name: ActionName.UploadSarif,
|
||||||
|
run,
|
||||||
|
};
|
||||||
|
|
||||||
export async function runWrapper() {
|
export async function runWrapper() {
|
||||||
const startedAt = new Date();
|
await runInActions(uploadSarif);
|
||||||
const logger = getActionsLogger();
|
|
||||||
try {
|
|
||||||
await run(startedAt);
|
|
||||||
} catch (error) {
|
|
||||||
core.setFailed(
|
|
||||||
`codeql/upload-sarif action failed: ${getErrorMessage(error)}`,
|
|
||||||
);
|
|
||||||
await sendUnhandledErrorStatusReport(
|
|
||||||
ActionName.UploadSarif,
|
|
||||||
startedAt,
|
|
||||||
error,
|
|
||||||
logger,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user