Convert rest of the actions
This commit is contained in:
parent
aac5eb2aea
commit
217483dfd6
59 changed files with 1630 additions and 915 deletions
126
lib/config-utils.js
generated
126
lib/config-utils.js
generated
|
|
@ -7,14 +7,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const core = __importStar(require("@actions/core"));
|
||||
const fs = __importStar(require("fs"));
|
||||
const yaml = __importStar(require("js-yaml"));
|
||||
const path = __importStar(require("path"));
|
||||
const api = __importStar(require("./api-client"));
|
||||
const externalQueries = __importStar(require("./external-queries"));
|
||||
const languages_1 = require("./languages");
|
||||
const util = __importStar(require("./util"));
|
||||
// Property names from the user-supplied config file.
|
||||
const NAME_PROPERTY = 'name';
|
||||
const DISABLE_DEFAULT_QUERIES_PROPERTY = 'disable-default-queries';
|
||||
|
|
@ -100,11 +98,10 @@ async function addBuiltinSuiteQueries(languages, codeQL, resultMap, suiteName, c
|
|||
/**
|
||||
* Retrieve the set of queries at localQueryPath and add them to resultMap.
|
||||
*/
|
||||
async function addLocalQueries(codeQL, resultMap, localQueryPath, configFile) {
|
||||
async function addLocalQueries(codeQL, resultMap, localQueryPath, checkoutPath, configFile) {
|
||||
// Resolve the local path against the workspace so that when this is
|
||||
// passed to codeql it resolves to exactly the path we expect it to resolve to.
|
||||
const workspacePath = fs.realpathSync(util.getRequiredEnvParam('GITHUB_WORKSPACE'));
|
||||
let absoluteQueryPath = path.join(workspacePath, localQueryPath);
|
||||
let absoluteQueryPath = path.join(checkoutPath, localQueryPath);
|
||||
// Check the file exists
|
||||
if (!fs.existsSync(absoluteQueryPath)) {
|
||||
throw new Error(getLocalPathDoesNotExist(configFile, localQueryPath));
|
||||
|
|
@ -112,17 +109,15 @@ async function addLocalQueries(codeQL, resultMap, localQueryPath, configFile) {
|
|||
// Call this after checking file exists, because it'll fail if file doesn't exist
|
||||
absoluteQueryPath = fs.realpathSync(absoluteQueryPath);
|
||||
// Check the local path doesn't jump outside the repo using '..' or symlinks
|
||||
if (!(absoluteQueryPath + path.sep).startsWith(workspacePath + path.sep)) {
|
||||
if (!(absoluteQueryPath + path.sep).startsWith(fs.realpathSync(checkoutPath) + path.sep)) {
|
||||
throw new Error(getLocalPathOutsideOfRepository(configFile, localQueryPath));
|
||||
}
|
||||
// Get the root of the current repo to use when resolving query dependencies
|
||||
const rootOfRepo = util.getRequiredEnvParam('GITHUB_WORKSPACE');
|
||||
await runResolveQueries(codeQL, resultMap, [absoluteQueryPath], rootOfRepo, true);
|
||||
await runResolveQueries(codeQL, resultMap, [absoluteQueryPath], checkoutPath, true);
|
||||
}
|
||||
/**
|
||||
* Retrieve the set of queries at the referenced remote repo and add them to resultMap.
|
||||
*/
|
||||
async function addRemoteQueries(codeQL, resultMap, queryUses, tempDir, configFile) {
|
||||
async function addRemoteQueries(codeQL, resultMap, queryUses, tempDir, githubUrl, configFile) {
|
||||
let tok = queryUses.split('@');
|
||||
if (tok.length !== 2) {
|
||||
throw new Error(getQueryUsesInvalid(configFile, queryUses));
|
||||
|
|
@ -141,11 +136,11 @@ async function addRemoteQueries(codeQL, resultMap, queryUses, tempDir, configFil
|
|||
}
|
||||
const nwo = tok[0] + '/' + tok[1];
|
||||
// Checkout the external repository
|
||||
const rootOfRepo = await externalQueries.checkoutExternalRepository(nwo, ref, tempDir);
|
||||
const checkoutPath = await externalQueries.checkoutExternalRepository(nwo, ref, githubUrl, tempDir);
|
||||
const queryPath = tok.length > 2
|
||||
? path.join(rootOfRepo, tok.slice(2).join('/'))
|
||||
: rootOfRepo;
|
||||
await runResolveQueries(codeQL, resultMap, [queryPath], rootOfRepo, true);
|
||||
? path.join(checkoutPath, tok.slice(2).join('/'))
|
||||
: checkoutPath;
|
||||
await runResolveQueries(codeQL, resultMap, [queryPath], checkoutPath, true);
|
||||
}
|
||||
/**
|
||||
* Parse a query 'uses' field to a discrete set of query files and update resultMap.
|
||||
|
|
@ -155,14 +150,14 @@ async function addRemoteQueries(codeQL, resultMap, queryUses, tempDir, configFil
|
|||
* local paths starting with './', or references to remote repos, or
|
||||
* a finite set of hardcoded terms for builtin suites.
|
||||
*/
|
||||
async function parseQueryUses(languages, codeQL, resultMap, queryUses, tempDir, configFile) {
|
||||
async function parseQueryUses(languages, codeQL, resultMap, queryUses, tempDir, checkoutPath, githubUrl, configFile) {
|
||||
queryUses = queryUses.trim();
|
||||
if (queryUses === "") {
|
||||
throw new Error(getQueryUsesInvalid(configFile));
|
||||
}
|
||||
// Check for the local path case before we start trying to parse the repository name
|
||||
if (queryUses.startsWith("./")) {
|
||||
await addLocalQueries(codeQL, resultMap, queryUses.slice(2), configFile);
|
||||
await addLocalQueries(codeQL, resultMap, queryUses.slice(2), checkoutPath, configFile);
|
||||
return;
|
||||
}
|
||||
// Check for one of the builtin suites
|
||||
|
|
@ -171,7 +166,7 @@ async function parseQueryUses(languages, codeQL, resultMap, queryUses, tempDir,
|
|||
return;
|
||||
}
|
||||
// Otherwise, must be a reference to another repo
|
||||
await addRemoteQueries(codeQL, resultMap, queryUses, tempDir, configFile);
|
||||
await addRemoteQueries(codeQL, resultMap, queryUses, tempDir, githubUrl, configFile);
|
||||
}
|
||||
// Regex validating stars in paths or paths-ignore entries.
|
||||
// The intention is to only allow ** to appear when immediately
|
||||
|
|
@ -182,7 +177,7 @@ const pathStarsRegex = /.*(?:\*\*[^/].*|\*\*$|[^/]\*\*.*)/;
|
|||
const filterPatternCharactersRegex = /.*[\?\+\[\]!].*/;
|
||||
// Checks that a paths of paths-ignore entry is valid, possibly modifying it
|
||||
// to make it valid, or if not possible then throws an error.
|
||||
function validateAndSanitisePath(originalPath, propertyName, configFile) {
|
||||
function validateAndSanitisePath(originalPath, propertyName, configFile, logger) {
|
||||
// Take a copy so we don't modify the original path, so we can still construct error messages
|
||||
let path = originalPath;
|
||||
// All paths are relative to the src root, so strip off leading slashes.
|
||||
|
|
@ -206,7 +201,7 @@ function validateAndSanitisePath(originalPath, propertyName, configFile) {
|
|||
// Check for other regex characters that we don't support.
|
||||
// Output a warning so the user knows, but otherwise continue normally.
|
||||
if (path.match(filterPatternCharactersRegex)) {
|
||||
core.warning(getConfigFilePropertyError(configFile, propertyName, '"' + originalPath + '" contains an unsupported character. ' +
|
||||
logger.warning(getConfigFilePropertyError(configFile, propertyName, '"' + originalPath + '" contains an unsupported character. ' +
|
||||
'The filter pattern characters ?, +, [, ], ! are not supported and will be matched literally.'));
|
||||
}
|
||||
// Ban any uses of backslash for now.
|
||||
|
|
@ -297,18 +292,18 @@ exports.getUnknownLanguagesError = getUnknownLanguagesError;
|
|||
/**
|
||||
* Gets the set of languages in the current repository
|
||||
*/
|
||||
async function getLanguagesInRepo() {
|
||||
async function getLanguagesInRepo(githubAuth, githubUrl, logger) {
|
||||
var _a;
|
||||
let repo_nwo = (_a = process.env['GITHUB_REPOSITORY']) === null || _a === void 0 ? void 0 : _a.split("/");
|
||||
if (repo_nwo) {
|
||||
let owner = repo_nwo[0];
|
||||
let repo = repo_nwo[1];
|
||||
core.debug(`GitHub repo ${owner} ${repo}`);
|
||||
const response = await api.getActionsApiClient(true).repos.listLanguages({
|
||||
logger.debug(`GitHub repo ${owner} ${repo}`);
|
||||
const response = await api.getApiClient(githubAuth, githubUrl, true).repos.listLanguages({
|
||||
owner,
|
||||
repo
|
||||
});
|
||||
core.debug("Languages API response: " + JSON.stringify(response));
|
||||
logger.debug("Languages API response: " + JSON.stringify(response));
|
||||
// The GitHub API is going to return languages in order of popularity,
|
||||
// When we pick a language to autobuild we want to pick the most popular traced language
|
||||
// Since sets in javascript maintain insertion order, using a set here and then splatting it
|
||||
|
|
@ -336,17 +331,17 @@ async function getLanguagesInRepo() {
|
|||
* If no languages could be detected from either the workflow or the repository
|
||||
* then throw an error.
|
||||
*/
|
||||
async function getLanguages() {
|
||||
async function getLanguages(languagesInput, githubAuth, githubUrl, logger) {
|
||||
// Obtain from action input 'languages' if set
|
||||
let languages = core.getInput('languages', { required: false })
|
||||
let languages = (languagesInput || "")
|
||||
.split(',')
|
||||
.map(x => x.trim())
|
||||
.filter(x => x.length > 0);
|
||||
core.info("Languages from configuration: " + JSON.stringify(languages));
|
||||
logger.info("Languages from configuration: " + JSON.stringify(languages));
|
||||
if (languages.length === 0) {
|
||||
// Obtain languages as all languages in the repo that can be analysed
|
||||
languages = await getLanguagesInRepo();
|
||||
core.info("Automatically detected languages: " + JSON.stringify(languages));
|
||||
languages = await getLanguagesInRepo(githubAuth, githubUrl, logger);
|
||||
logger.info("Automatically detected languages: " + JSON.stringify(languages));
|
||||
}
|
||||
// If the languages parameter was not given and no languages were
|
||||
// detected then fail here as this is a workflow configuration error.
|
||||
|
|
@ -374,24 +369,21 @@ async function getLanguages() {
|
|||
* Returns true if queries were provided in the workflow file
|
||||
* (and thus added), otherwise false
|
||||
*/
|
||||
async function addQueriesFromWorkflowIfRequired(codeQL, languages, resultMap, tempDir) {
|
||||
const queryUses = core.getInput('queries');
|
||||
if (queryUses) {
|
||||
for (const query of queryUses.split(',')) {
|
||||
await parseQueryUses(languages, codeQL, resultMap, query, tempDir);
|
||||
}
|
||||
return true;
|
||||
async function addQueriesFromWorkflow(codeQL, queriesInput, languages, resultMap, tempDir, checkoutPath, githubUrl) {
|
||||
for (const query of queriesInput.split(',')) {
|
||||
await parseQueryUses(languages, codeQL, resultMap, query, tempDir, checkoutPath, githubUrl);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Get the default config for when the user has not supplied one.
|
||||
*/
|
||||
async function getDefaultConfig(tempDir, toolCacheDir, codeQL) {
|
||||
const languages = await getLanguages();
|
||||
async function getDefaultConfig(languagesInput, queriesInput, tempDir, toolCacheDir, codeQL, checkoutPath, githubAuth, githubUrl, logger) {
|
||||
const languages = await getLanguages(languagesInput, githubAuth, githubUrl, logger);
|
||||
const queries = {};
|
||||
await addDefaultQueries(codeQL, languages, queries);
|
||||
await addQueriesFromWorkflowIfRequired(codeQL, languages, queries, tempDir);
|
||||
if (queriesInput) {
|
||||
await addQueriesFromWorkflow(codeQL, queriesInput, languages, queries, tempDir, checkoutPath, githubUrl);
|
||||
}
|
||||
return {
|
||||
languages: languages,
|
||||
queries: queries,
|
||||
|
|
@ -407,16 +399,15 @@ exports.getDefaultConfig = getDefaultConfig;
|
|||
/**
|
||||
* Load the config from the given file.
|
||||
*/
|
||||
async function loadConfig(configFile, tempDir, toolCacheDir, codeQL) {
|
||||
async function loadConfig(languagesInput, queriesInput, configFile, tempDir, toolCacheDir, codeQL, checkoutPath, githubAuth, githubUrl, logger) {
|
||||
let parsedYAML;
|
||||
if (isLocal(configFile)) {
|
||||
// Treat the config file as relative to the workspace
|
||||
const workspacePath = util.getRequiredEnvParam('GITHUB_WORKSPACE');
|
||||
configFile = path.resolve(workspacePath, configFile);
|
||||
parsedYAML = getLocalConfig(configFile, workspacePath);
|
||||
configFile = path.resolve(checkoutPath, configFile);
|
||||
parsedYAML = getLocalConfig(configFile, checkoutPath);
|
||||
}
|
||||
else {
|
||||
parsedYAML = await getRemoteConfig(configFile);
|
||||
parsedYAML = await getRemoteConfig(configFile, githubAuth, githubUrl);
|
||||
}
|
||||
// Validate that the 'name' property is syntactically correct,
|
||||
// even though we don't use the value yet.
|
||||
|
|
@ -428,7 +419,7 @@ async function loadConfig(configFile, tempDir, toolCacheDir, codeQL) {
|
|||
throw new Error(getNameInvalid(configFile));
|
||||
}
|
||||
}
|
||||
const languages = await getLanguages();
|
||||
const languages = await getLanguages(languagesInput, githubAuth, githubUrl, logger);
|
||||
const queries = {};
|
||||
const pathsIgnore = [];
|
||||
const paths = [];
|
||||
|
|
@ -444,8 +435,10 @@ async function loadConfig(configFile, tempDir, toolCacheDir, codeQL) {
|
|||
}
|
||||
// If queries were provided using `with` in the action configuration,
|
||||
// they should take precedence over the queries in the config file
|
||||
const addedQueriesFromAction = await addQueriesFromWorkflowIfRequired(codeQL, languages, queries, tempDir);
|
||||
if (!addedQueriesFromAction && QUERIES_PROPERTY in parsedYAML) {
|
||||
if (queriesInput) {
|
||||
await addQueriesFromWorkflow(codeQL, queriesInput, languages, queries, tempDir, checkoutPath, githubUrl);
|
||||
}
|
||||
else if (QUERIES_PROPERTY in parsedYAML) {
|
||||
if (!(parsedYAML[QUERIES_PROPERTY] instanceof Array)) {
|
||||
throw new Error(getQueriesInvalid(configFile));
|
||||
}
|
||||
|
|
@ -453,7 +446,7 @@ async function loadConfig(configFile, tempDir, toolCacheDir, codeQL) {
|
|||
if (!(QUERIES_USES_PROPERTY in query) || typeof query[QUERIES_USES_PROPERTY] !== "string") {
|
||||
throw new Error(getQueryUsesInvalid(configFile));
|
||||
}
|
||||
await parseQueryUses(languages, codeQL, queries, query[QUERIES_USES_PROPERTY], tempDir, configFile);
|
||||
await parseQueryUses(languages, codeQL, queries, query[QUERIES_USES_PROPERTY], tempDir, checkoutPath, githubUrl, configFile);
|
||||
}
|
||||
}
|
||||
if (PATHS_IGNORE_PROPERTY in parsedYAML) {
|
||||
|
|
@ -464,7 +457,7 @@ async function loadConfig(configFile, tempDir, toolCacheDir, codeQL) {
|
|||
if (typeof path !== "string" || path === '') {
|
||||
throw new Error(getPathsIgnoreInvalid(configFile));
|
||||
}
|
||||
pathsIgnore.push(validateAndSanitisePath(path, PATHS_IGNORE_PROPERTY, configFile));
|
||||
pathsIgnore.push(validateAndSanitisePath(path, PATHS_IGNORE_PROPERTY, configFile, logger));
|
||||
});
|
||||
}
|
||||
if (PATHS_PROPERTY in parsedYAML) {
|
||||
|
|
@ -475,7 +468,7 @@ async function loadConfig(configFile, tempDir, toolCacheDir, codeQL) {
|
|||
if (typeof path !== "string" || path === '') {
|
||||
throw new Error(getPathsInvalid(configFile));
|
||||
}
|
||||
paths.push(validateAndSanitisePath(path, PATHS_PROPERTY, configFile));
|
||||
paths.push(validateAndSanitisePath(path, PATHS_PROPERTY, configFile, logger));
|
||||
});
|
||||
}
|
||||
// The list of queries should not be empty for any language. If it is then
|
||||
|
|
@ -503,19 +496,18 @@ async function loadConfig(configFile, tempDir, toolCacheDir, codeQL) {
|
|||
* 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(tempDir, toolCacheDir, codeQL) {
|
||||
const configFile = core.getInput('config-file');
|
||||
async function initConfig(languagesInput, queriesInput, configFile, tempDir, toolCacheDir, codeQL, checkoutPath, githubAuth, githubUrl, logger) {
|
||||
let config;
|
||||
// If no config file was provided create an empty one
|
||||
if (configFile === '') {
|
||||
core.debug('No configuration file was provided');
|
||||
config = await getDefaultConfig(tempDir, toolCacheDir, codeQL);
|
||||
if (!configFile) {
|
||||
logger.debug('No configuration file was provided');
|
||||
config = await getDefaultConfig(languagesInput, queriesInput, tempDir, toolCacheDir, codeQL, checkoutPath, githubAuth, githubUrl, logger);
|
||||
}
|
||||
else {
|
||||
config = await loadConfig(configFile, tempDir, toolCacheDir, codeQL);
|
||||
config = await loadConfig(languagesInput, queriesInput, configFile, tempDir, toolCacheDir, codeQL, checkoutPath, githubAuth, githubUrl, logger);
|
||||
}
|
||||
// Save the config so we can easily access it again in the future
|
||||
await saveConfig(config);
|
||||
await saveConfig(config, logger);
|
||||
return config;
|
||||
}
|
||||
exports.initConfig = initConfig;
|
||||
|
|
@ -526,9 +518,9 @@ function isLocal(configPath) {
|
|||
}
|
||||
return (configPath.indexOf("@") === -1);
|
||||
}
|
||||
function getLocalConfig(configFile, workspacePath) {
|
||||
function getLocalConfig(configFile, checkoutPath) {
|
||||
// Error if the config file is now outside of the workspace
|
||||
if (!(configFile + path.sep).startsWith(workspacePath + path.sep)) {
|
||||
if (!(configFile + path.sep).startsWith(checkoutPath + path.sep)) {
|
||||
throw new Error(getConfigFileOutsideWorkspaceErrorMessage(configFile));
|
||||
}
|
||||
// Error if the file does not exist
|
||||
|
|
@ -537,7 +529,7 @@ function getLocalConfig(configFile, workspacePath) {
|
|||
}
|
||||
return yaml.safeLoad(fs.readFileSync(configFile, 'utf8'));
|
||||
}
|
||||
async function getRemoteConfig(configFile) {
|
||||
async function getRemoteConfig(configFile, githubAuth, githubUrl) {
|
||||
// retrieve the various parts of the config location, and ensure they're present
|
||||
const format = new RegExp('(?<owner>[^/]+)/(?<repo>[^/]+)/(?<path>[^@]+)@(?<ref>.*)');
|
||||
const pieces = format.exec(configFile);
|
||||
|
|
@ -545,7 +537,7 @@ async function getRemoteConfig(configFile) {
|
|||
if (pieces === null || pieces.groups === undefined || pieces.length < 5) {
|
||||
throw new Error(getConfigFileRepoFormatInvalidMessage(configFile));
|
||||
}
|
||||
const response = await api.getActionsApiClient(true).repos.getContents({
|
||||
const response = await api.getApiClient(githubAuth, githubUrl, true).repos.getContents({
|
||||
owner: pieces.groups.owner,
|
||||
repo: pieces.groups.repo,
|
||||
path: pieces.groups.path,
|
||||
|
|
@ -573,13 +565,13 @@ exports.getPathToParsedConfigFile = getPathToParsedConfigFile;
|
|||
/**
|
||||
* Store the given config to the path returned from getPathToParsedConfigFile.
|
||||
*/
|
||||
async function saveConfig(config) {
|
||||
async function saveConfig(config, logger) {
|
||||
const configString = JSON.stringify(config);
|
||||
const configFile = getPathToParsedConfigFile(config.tempDir);
|
||||
fs.mkdirSync(path.dirname(configFile), { recursive: true });
|
||||
fs.writeFileSync(configFile, configString, 'utf8');
|
||||
core.debug('Saved config:');
|
||||
core.debug(configString);
|
||||
logger.debug('Saved config:');
|
||||
logger.debug(configString);
|
||||
}
|
||||
/**
|
||||
* Get the config.
|
||||
|
|
@ -589,14 +581,14 @@ async function saveConfig(config) {
|
|||
* stored to a known location. On the second and further calls, this will
|
||||
* return the contents of the parsed config from the known location.
|
||||
*/
|
||||
async function getConfig(tempDir) {
|
||||
async function getConfig(tempDir, logger) {
|
||||
const configFile = getPathToParsedConfigFile(tempDir);
|
||||
if (!fs.existsSync(configFile)) {
|
||||
throw new Error("Config file could not be found at expected location. Has the 'init' action been called?");
|
||||
}
|
||||
const configString = fs.readFileSync(configFile, 'utf8');
|
||||
core.debug('Loaded config:');
|
||||
core.debug(configString);
|
||||
logger.debug('Loaded config:');
|
||||
logger.debug(configString);
|
||||
return JSON.parse(configString);
|
||||
}
|
||||
exports.getConfig = getConfig;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue