Add new packs input to init action

This input allows users to specify which packs to run. It works in
unison with the packs block of the config file and it is similar to
how `queries` works. They both use `+` in the same way.

Note that the `#TODO` in the pr check is still around, but the CLI
is available. I will remove the TODO in the next commit.
This commit is contained in:
Andrew Eisenberg 2021-06-23 15:41:52 -07:00
parent 7729b51956
commit 6e577cfca3
18 changed files with 535 additions and 59 deletions

82
lib/config-utils.js generated
View file

@ -269,7 +269,9 @@ function getPacksInvalid(configFile) {
}
exports.getPacksInvalid = getPacksInvalid;
function getPacksStrInvalid(packStr, configFile) {
return getConfigFilePropertyError(configFile, PACKS_PROPERTY, `"${packStr}" is not a valid pack`);
return configFile
? getConfigFilePropertyError(configFile, PACKS_PROPERTY, `"${packStr}" is not a valid pack`)
: `"${packStr}" is not a valid pack`;
}
exports.getPacksStrInvalid = getPacksStrInvalid;
function getLocalPathOutsideOfRepository(configFile, localPath) {
@ -409,7 +411,8 @@ function shouldAddConfigFileQueries(queriesInput) {
/**
* Get the default config for when the user has not supplied one.
*/
async function getDefaultConfig(languagesInput, queriesInput, dbLocation, repository, tempDir, toolCacheDir, codeQL, checkoutPath, gitHubVersion, apiDetails, logger) {
async function getDefaultConfig(languagesInput, queriesInput, packsInput, dbLocation, repository, tempDir, toolCacheDir, codeQL, checkoutPath, gitHubVersion, apiDetails, logger) {
var _a;
const languages = await getLanguages(codeQL, languagesInput, repository, apiDetails, logger);
const queries = {};
for (const language of languages) {
@ -422,12 +425,13 @@ async function getDefaultConfig(languagesInput, queriesInput, dbLocation, reposi
if (queriesInput) {
await addQueriesFromWorkflow(codeQL, queriesInput, languages, queries, tempDir, checkoutPath, apiDetails, logger);
}
const packs = (_a = parsePacksInput(packsInput, languages), (_a !== null && _a !== void 0 ? _a : {}));
return {
languages,
queries,
pathsIgnore: [],
paths: [],
packs: {},
packs,
originalUserInput: {},
tempDir,
toolCacheDir,
@ -440,7 +444,7 @@ exports.getDefaultConfig = getDefaultConfig;
/**
* Load the config from the given file.
*/
async function loadConfig(languagesInput, queriesInput, configFile, dbLocation, repository, tempDir, toolCacheDir, codeQL, checkoutPath, gitHubVersion, apiDetails, logger) {
async function loadConfig(languagesInput, queriesInput, packsInput, configFile, dbLocation, repository, tempDir, toolCacheDir, codeQL, checkoutPath, gitHubVersion, apiDetails, logger) {
var _a;
let parsedYAML;
if (isLocal(configFile)) {
@ -524,7 +528,7 @@ async function loadConfig(languagesInput, queriesInput, configFile, dbLocation,
paths.push(validateAndSanitisePath(includePath, PATHS_PROPERTY, configFile, logger));
}
}
const packs = parsePacks((_a = parsedYAML[PACKS_PROPERTY], (_a !== null && _a !== void 0 ? _a : {})), languages, configFile);
const packs = parsePacks((_a = parsedYAML[PACKS_PROPERTY], (_a !== null && _a !== void 0 ? _a : {})), packsInput, languages, configFile);
return {
languages,
queries,
@ -550,7 +554,7 @@ const PACK_IDENTIFIER_PATTERN = (function () {
return new RegExp(`^${component}/${component}$`);
})();
// Exported for testing
function parsePacks(packsByLanguage, languages, configFile) {
function parsePacksFromConfig(packsByLanguage, languages, configFile) {
const packs = {};
if (Array.isArray(packsByLanguage)) {
if (languages.length === 1) {
@ -579,12 +583,37 @@ function parsePacks(packsByLanguage, languages, configFile) {
}
return packs;
}
exports.parsePacks = parsePacks;
exports.parsePacksFromConfig = parsePacksFromConfig;
function parsePacksInput(packsInput, languages) {
var _a;
if (!((_a = packsInput) === null || _a === void 0 ? void 0 : _a.trim())) {
return undefined;
}
if (languages.length > 1) {
throw new Error("Cannot specify a 'packs' input in a multi-language analysis. Use a codeql-config.yml file instead and specify packs by library.");
}
else if (languages.length === 0) {
throw new Error("No languages specified. Cannot process the packs input.");
}
packsInput = packsInput.trim();
if (packsInput.startsWith("+")) {
packsInput = packsInput.substring(1).trim();
if (!packsInput) {
throw new Error("Remove the '+' from the packs input.");
}
}
return {
[languages[0]]: packsInput.split(",").reduce((packs, pack) => {
packs.push(toPackWithVersion(pack, ""));
return packs;
}, []),
};
}
function toPackWithVersion(packStr, configFile) {
if (typeof packStr !== "string") {
throw new Error(getPacksStrInvalid(packStr, configFile));
}
const nameWithVersion = packStr.split("@");
const nameWithVersion = packStr.trim().split("@");
let version;
if (nameWithVersion.length > 2 ||
!PACK_IDENTIFIER_PATTERN.test(nameWithVersion[0])) {
@ -597,10 +626,39 @@ function toPackWithVersion(packStr, configFile) {
}
}
return {
packName: nameWithVersion[0],
packName: nameWithVersion[0].trim(),
version,
};
}
// exported for testing
function parsePacks(rawPacksFromConfig, rawPacksInput, languages, configFile) {
const packsFromInput = parsePacksInput(rawPacksInput, languages);
const packsFomConfig = parsePacksFromConfig(rawPacksFromConfig, languages, configFile);
if (!packsFromInput) {
return packsFomConfig;
}
if (!shouldCombinePacks(rawPacksInput)) {
return packsFromInput;
}
return combinePacks(packsFromInput, packsFomConfig);
}
exports.parsePacks = parsePacks;
function shouldCombinePacks(packsInput) {
var _a;
return !!((_a = packsInput) === null || _a === void 0 ? void 0 : _a.trim().startsWith("+"));
}
function combinePacks(packs1, packs2) {
const packs = {};
for (const lang of Object.keys(packs1)) {
packs[lang] = packs1[lang].concat(packs2[lang] || []);
}
for (const lang of Object.keys(packs2)) {
if (!packs[lang]) {
packs[lang] = packs2[lang];
}
}
return packs;
}
function dbLocationOrDefault(dbLocation, tempDir) {
return dbLocation || path.resolve(tempDir, "codeql_databases");
}
@ -610,16 +668,16 @@ function dbLocationOrDefault(dbLocation, tempDir) {
* This will parse the config from the user input if present, or generate
* a default config. The parsed config is then stored to a known location.
*/
async function initConfig(languagesInput, queriesInput, configFile, dbLocation, repository, tempDir, toolCacheDir, codeQL, checkoutPath, gitHubVersion, apiDetails, logger) {
async function initConfig(languagesInput, queriesInput, packsInput, configFile, dbLocation, repository, tempDir, toolCacheDir, codeQL, checkoutPath, gitHubVersion, apiDetails, logger) {
var _a, _b, _c;
let config;
// If no config file was provided create an empty one
if (!configFile) {
logger.debug("No configuration file was provided");
config = await getDefaultConfig(languagesInput, queriesInput, dbLocation, repository, tempDir, toolCacheDir, codeQL, checkoutPath, gitHubVersion, apiDetails, logger);
config = await getDefaultConfig(languagesInput, queriesInput, packsInput, dbLocation, repository, tempDir, toolCacheDir, codeQL, checkoutPath, gitHubVersion, apiDetails, logger);
}
else {
config = await loadConfig(languagesInput, queriesInput, configFile, dbLocation, repository, tempDir, toolCacheDir, codeQL, checkoutPath, gitHubVersion, apiDetails, logger);
config = await loadConfig(languagesInput, queriesInput, packsInput, configFile, dbLocation, repository, tempDir, toolCacheDir, codeQL, checkoutPath, gitHubVersion, apiDetails, logger);
}
// The list of queries should not be empty for any language. If it is then
// it is a user configuration error.