Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
346 changes: 170 additions & 176 deletions lib/entry-points.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
"@octokit/plugin-rest-endpoint-methods": "^18.0.0",
"@octokit/plugin-retry": "^8.1.1",
"archiver": "^8.0.0",
"fast-deep-equal": "^3.1.3",
"follow-redirects": "^1.16.0",
"get-folder-size": "^5.0.0",
"https-proxy-agent": "^7.0.6",
Expand Down
4 changes: 2 additions & 2 deletions src/actions-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export function getWorkflowEventName(env: Env = getEnv()) {
* Returns whether the current workflow is executing a local copy of the Action, e.g. we're running
* a workflow on the codeql-action repo itself.
*/
export function isRunningLocalAction(env: Env = getEnv()): boolean {
export function isRunningLocalAction(env: ReadOnlyEnv = getEnv()): boolean {
const relativeScriptPath = getRelativeScriptPath(env);
return (
relativeScriptPath.startsWith("..") || path.isAbsolute(relativeScriptPath)
Expand All @@ -113,7 +113,7 @@ export function isRunningLocalAction(env: Env = getEnv()): boolean {
*
* This can be used to get the Action's name or tell if we're running a local Action.
*/
function getRelativeScriptPath(env: Env): string {
function getRelativeScriptPath(env: ReadOnlyEnv): string {
const runnerTemp = env.getRequired(ActionsEnvVars.RUNNER_TEMP);
const actionsDirectory = path.join(path.dirname(runnerTemp), "_actions");
return path.relative(actionsDirectory, __filename);
Expand Down
17 changes: 17 additions & 0 deletions src/logging.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import test from "ava";

import { joinMessageStrings } from "./logging";
import { setupTests } from "./testing-utils";

setupTests(test);

test("joinMessageStrings", async (t) => {
// For strings and errors, it is the identity function.
t.deepEqual(joinMessageStrings("Hello"), "Hello");

const error = new Error("Some error");
t.deepEqual(joinMessageStrings(error), error);

// For arrays of strings, we get a join-ed string.
t.deepEqual(joinMessageStrings(["foo", "bar"]), "foo bar");
});
43 changes: 35 additions & 8 deletions src/logging.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,50 @@
import * as core from "@actions/core";

/** Types that all of our logging functions accept. */
export type Loggable = string | string[];
Comment on lines +3 to +4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking back at this after a commit-by-commit review, does anything use this? If not, let's remove it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used by other definitions in logging.ts below (same commit that introduced the definition).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I mean to say that none of our source code passes an array to a logger. We can keep it if you feel strongly about it though.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean to say that none of our source code passes an array to a logger

Ah. I did have at least one place that used this when I added it but maybe that went away while I was working on this PR. In any case, I have come across the same need in some of my other work, so I do think this is a useful addition whether this PR makes use of it or not.


/** Some logging functions also accept errors directly. */
export type LoggableError = Loggable | Error;

export interface Logger {
debug: (message: string) => void;
info: (message: string) => void;
warning: (message: string | Error) => void;
error: (message: string | Error) => void;
debug: (message: Loggable) => void;
info: (message: Loggable) => void;
warning: (message: LoggableError) => void;
error: (message: LoggableError) => void;

isDebug: () => boolean;

startGroup: (name: string) => void;
endGroup: () => void;
}

/** If `message` is an array of strings, the array is `join`-ed into one string separated by spaces. */
export const joinMessageStrings = <T>(
message: string[] | string | T,
): string | T => {
if (Array.isArray(message)) {
return message.join(" ");
} else {
return message;
}
};

/**
* Wraps a logging function so that `joinMessageStrings` is applied to the input before
* calling the wrapped logging function.
*/
const autoJoinMessageStrings = <T>(fn: (msg: string | T) => void) => {
return (message: string[] | string | T) => {
return fn(joinMessageStrings(message));
};
};

export function getActionsLogger(): Logger {
return {
debug: core.debug,
info: core.info,
warning: core.warning,
error: core.error,
debug: autoJoinMessageStrings(core.debug),
info: autoJoinMessageStrings(core.info),
warning: autoJoinMessageStrings(core.warning),
error: autoJoinMessageStrings(core.error),
isDebug: core.isDebug,
startGroup: core.startGroup,
endGroup: core.endGroup,
Expand Down
8 changes: 8 additions & 0 deletions src/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ export interface RepositoryNwo {
repo: string;
}

/** An approximation of what a repository NWO string looks like. */
export type RepositoryNwoStr = `${string}/${string}`;

/** A basic check that `val` looks like a `RepositoryNwoStr`. */
export function isRepositoryNwoStr(val: string): val is RepositoryNwoStr {
return val.includes("/");
}

/**
* Get the repository name with owner from the environment variable
* `GITHUB_REPOSITORY`.
Expand Down
18 changes: 0 additions & 18 deletions src/setup-codeql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,24 +126,6 @@ test.serial("convert to semver", (t) => {
}
});

test.serial("getCodeQLActionRepository", (t) => {
const logger = getRunnerLogger(true);

initializeEnvironment("1.2.3");

// isRunningLocalAction() === true
delete process.env["GITHUB_ACTION_REPOSITORY"];
process.env["RUNNER_TEMP"] = path.dirname(__dirname);
const repoLocalRunner = setupCodeql.getCodeQLActionRepository(logger);
t.deepEqual(repoLocalRunner, "github/codeql-action");

// isRunningLocalAction() === false
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
process.env["GITHUB_ACTION_REPOSITORY"] = "xxx/yyy";
const repoEnv = setupCodeql.getCodeQLActionRepository(logger);
t.deepEqual(repoEnv, "xxx/yyy");
});

test.serial(
"getCodeQLSource sets CLI version for a semver tagged bundle",
async (t) => {
Expand Down
82 changes: 6 additions & 76 deletions src/setup-codeql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import * as path from "path";
import { performance } from "perf_hooks";

import * as toolcache from "@actions/tool-cache";
import { default as deepEqual } from "fast-deep-equal";
import * as semver from "semver";
import { v4 as uuidV4 } from "uuid";

Expand All @@ -13,7 +12,6 @@ import {
isAnalyzingPullRequest,
isDynamicWorkflow,
isGitHubHostedRunner,
isRunningLocalAction,
} from "./actions-util";
import * as api from "./api-client";
import {
Expand Down Expand Up @@ -44,6 +42,7 @@ import {
logPerLanguageBundleFallback,
} from "./per-language-bundles";
import { getBundlePlatform } from "./platform";
import { getCodeQLAssetDownloadURL } from "./setup/repository";
import * as tar from "./tar";
import {
deleteToolcacheBundles,
Expand All @@ -65,83 +64,13 @@ export enum ToolsSource {
Download = "DOWNLOAD",
}

const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action";
const CODEQL_NIGHTLIES_REPOSITORY_OWNER = "dsp-testing";
const CODEQL_NIGHTLIES_REPOSITORY_NAME = "codeql-cli-nightlies";

const CODEQL_BUNDLE_VERSION_ALIAS: string[] = ["linked", "latest"];
const CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"];
const CODEQL_TOOLCACHE_INPUT = "toolcache";

export function getCodeQLActionRepository(logger: Logger): string {
if (isRunningLocalAction()) {
// This handles the case where the Action does not come from an Action repository,
// e.g. our integration tests which use the Action code from the current checkout.
// In these cases, the GITHUB_ACTION_REPOSITORY environment variable is not set.
logger.info(
"The CodeQL Action is checked out locally. Using the default CodeQL Action repository.",
);
return CODEQL_DEFAULT_ACTION_REPOSITORY;
}

return util.getRequiredEnvParam("GITHUB_ACTION_REPOSITORY");
}

async function getCodeQLBundleDownloadURL(
tagName: string,
apiDetails: api.GitHubApiDetails,
codeQLBundleName: string,
logger: Logger,
): Promise<string> {
const codeQLActionRepository = getCodeQLActionRepository(logger);
const potentialDownloadSources = [
// This GitHub instance, and this Action.
[apiDetails.url, codeQLActionRepository],
// This GitHub instance, and the canonical Action.
[apiDetails.url, CODEQL_DEFAULT_ACTION_REPOSITORY],
// GitHub.com, and the canonical Action.
[util.GITHUB_DOTCOM_URL, CODEQL_DEFAULT_ACTION_REPOSITORY],
];
// We now filter out any duplicates.
// Duplicates will happen either because the GitHub instance is GitHub.com, or because the Action is not a fork.
const uniqueDownloadSources = potentialDownloadSources.filter(
(source, index, self) => {
return !self.slice(0, index).some((other) => deepEqual(source, other));
},
);
for (const downloadSource of uniqueDownloadSources) {
const [apiURL, repository] = downloadSource;
// If we've reached the final case, short-circuit the API check since we know the bundle exists and is public.
if (
apiURL === util.GITHUB_DOTCOM_URL &&
repository === CODEQL_DEFAULT_ACTION_REPOSITORY
) {
break;
}
const [repositoryOwner, repositoryName] = repository.split("/");
try {
const release = await api.getApiClient().rest.repos.getReleaseByTag({
owner: repositoryOwner,
repo: repositoryName,
tag: tagName,
});
for (const asset of release.data.assets) {
if (asset.name === codeQLBundleName) {
logger.info(
`Found CodeQL bundle ${codeQLBundleName} in ${repository} on ${apiURL} with URL ${asset.url}.`,
);
return asset.url;
}
}
} catch (e) {
logger.info(
`Looked for CodeQL bundle ${codeQLBundleName} in ${repository} on ${apiURL} but got error ${e}.`,
);
}
}
return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${codeQLBundleName}`;
}

function tryGetBundleVersionFromTagName(
tagName: string,
logger: Logger,
Expand Down Expand Up @@ -720,9 +649,10 @@ export async function getCodeQLSource(
? "zstd"
: "gzip";

const action = { env: getEnv(), logger };
const platform = getBundlePlatform();
const perLanguageBundleLanguage = await getPerLanguageBundleLanguage(
{ env: getEnv(), features, logger },
{ ...action, features },
{
rawLanguages,
otherLanguagePacksReason,
Expand All @@ -735,11 +665,11 @@ export async function getCodeQLSource(

// Resolves the combined or per-language bundle URL for the requested release.
const resolveBundleURL = (language?: BuiltInLanguage) =>
getCodeQLBundleDownloadURL(
bundleTagName,
getCodeQLAssetDownloadURL(
action,
apiDetails,
bundleTagName,
getCodeQLBundleName(compressionMethod, platform, language),
logger,
);

const combinedBundleURL = await resolveBundleURL();
Expand Down
35 changes: 35 additions & 0 deletions src/setup/repository.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import * as path from "path";

import test from "ava";
import * as sinon from "sinon";

import * as actionsUtil from "../actions-util";
import { ActionsEnvVars } from "../environment";
import { callee, getTestEnv, setupTests } from "../testing-utils";
import { initializeEnvironment } from "../util";

import { getCodeQLActionRepository } from "./repository";

setupTests(test);

test.serial("getCodeQLActionRepository", async (t) => {
Comment thread
mbg marked this conversation as resolved.
const initialEnv = getTestEnv({
[ActionsEnvVars.RUNNER_TEMP]: path.dirname(__dirname),
});
initializeEnvironment("1.2.3", initialEnv);

const target = callee(getCodeQLActionRepository)
.withArgs()
.withEnv(initialEnv);

// isRunningLocalAction() === true
await target.passes(t.deepEqual, "github/codeql-action");

// isRunningLocalAction() === false
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
await target
.withEnv((env) => {
env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "xxx/yyy");
})
.passes(t.deepEqual, "xxx/yyy");
});
Loading
Loading