introduce new syntax for built-in query suites

This commit is contained in:
Robert Brignull 2020-05-26 16:02:22 +01:00
parent 8b71cf3e5f
commit 1e600686e7
6 changed files with 101 additions and 10 deletions

41
lib/config-utils.js generated
View file

@ -21,12 +21,15 @@ class ExternalQuery {
}
}
exports.ExternalQuery = ExternalQuery;
// The set of acceptable values for built-in suites from the codeql bundle
const builtinSuites = ['security-experimental', 'security-and-quality'];
class Config {
constructor() {
this.name = "";
this.disableDefaultQueries = false;
this.additionalQueries = [];
this.externalQueries = [];
this.additionalSuites = [];
this.pathsIgnore = [];
this.paths = [];
}
@ -39,9 +42,31 @@ class Config {
}
// Check for the local path case before we start trying to parse the repository name
if (queryUses.startsWith("./")) {
this.additionalQueries.push(queryUses.slice(2));
const localQueryPath = queryUses.slice(2);
// 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 = util.getRequiredEnvParam('GITHUB_WORKSPACE');
const absoluteQueryPath = path.join(workspacePath, localQueryPath);
// Check the file exists
if (!fs.existsSync(absoluteQueryPath)) {
throw new Error(getLocalPathDoesNotExist(localQueryPath));
}
// Check the local path doesn't jump outside the repo using '..' or symlinks
if (!(fs.realpathSync(absoluteQueryPath) + path.sep).startsWith(workspacePath + path.sep)) {
throw new Error(getLocalPathOutsideOfRepository(localQueryPath));
}
this.additionalQueries.push(absoluteQueryPath);
return;
}
// Check for one of the builtin suites
if (queryUses.indexOf('/') === -1 && queryUses.indexOf('@') === -1) {
if (queryUses in builtinSuites) {
this.additionalSuites.push(queryUses);
}
else {
throw new Error(getQueryUsesIncorrect(queryUses));
}
}
let tok = queryUses.split('@');
if (tok.length !== 2) {
throw new Error(getQueryUsesIncorrect(queryUses));
@ -74,9 +99,21 @@ function getQueryUsesBlank() {
}
exports.getQueryUsesBlank = getQueryUsesBlank;
function getQueryUsesIncorrect(queryUses) {
return '"uses" value for queries must be a path, or owner/repo@ref \n Found: ' + queryUses;
return '"uses" value for queries must be a built-in suite (' + builtinSuites.join('or') +
'), a relative path, or of the form owner/repo@ref\n' +
'Found: ' + queryUses;
}
exports.getQueryUsesIncorrect = getQueryUsesIncorrect;
function getLocalPathOutsideOfRepository(localPath) {
return 'Unable to use queries from local path "' + localPath +
'" as it is outside of the repository';
}
exports.getLocalPathOutsideOfRepository = getLocalPathOutsideOfRepository;
function getLocalPathDoesNotExist(localPath) {
return 'Unable to use queries from local path "' + localPath +
'" as the path does not exist in the repository';
}
exports.getLocalPathDoesNotExist = getLocalPathDoesNotExist;
function getConfigFileOutsideWorkspaceErrorMessage(configFile) {
return 'The configuration file "' + configFile + '" is outside of the workspace';
}

File diff suppressed because one or more lines are too long

View file

@ -87,6 +87,7 @@ ava_1.default("load non-empty input", async (t) => {
name: my config
disable-default-queries: true
queries:
- uses: ./
- uses: ./foo
- uses: foo/bar@dev
paths-ignore:
@ -98,12 +99,14 @@ ava_1.default("load non-empty input", async (t) => {
const expectedConfig = new configUtils.Config();
expectedConfig.name = 'my config';
expectedConfig.disableDefaultQueries = true;
expectedConfig.additionalQueries.push('foo');
expectedConfig.additionalQueries.push(tmpDir);
expectedConfig.additionalQueries.push(path.join(tmpDir, 'foo'));
expectedConfig.externalQueries = [new configUtils.ExternalQuery('foo/bar', 'dev')];
expectedConfig.pathsIgnore = ['a', 'b'];
expectedConfig.paths = ['c/d'];
fs.writeFileSync(path.join(tmpDir, 'input'), inputFileContents, 'utf8');
setInput('config-file', 'input');
fs.mkdirSync(path.join(tmpDir, 'foo'));
const actualConfig = await configUtils.loadConfig();
// Should exactly equal the object we constructed earlier
t.deepEqual(actualConfig, expectedConfig);
@ -195,7 +198,9 @@ const testInputs = {
"foo/bar": configUtils.getQueryUsesIncorrect("foo/bar"),
"foo/bar@v1@v2": configUtils.getQueryUsesIncorrect("foo/bar@v1@v2"),
"foo@master": configUtils.getQueryUsesIncorrect("foo@master"),
"https://github.com/foo/bar@master": configUtils.getQueryUsesIncorrect("https://github.com/foo/bar@master")
"https://github.com/foo/bar@master": configUtils.getQueryUsesIncorrect("https://github.com/foo/bar@master"),
"./foo": configUtils.getLocalPathDoesNotExist("foo"),
"./..": configUtils.getLocalPathOutsideOfRepository(".."),
};
for (const [input, result] of Object.entries(testInputs)) {
ava_1.default("load invalid input - queries uses \"" + input + "\"", async (t) => {

File diff suppressed because one or more lines are too long

View file

@ -91,6 +91,7 @@ test("load non-empty input", async t => {
name: my config
disable-default-queries: true
queries:
- uses: ./
- uses: ./foo
- uses: foo/bar@dev
paths-ignore:
@ -103,7 +104,8 @@ test("load non-empty input", async t => {
const expectedConfig = new configUtils.Config();
expectedConfig.name = 'my config';
expectedConfig.disableDefaultQueries = true;
expectedConfig.additionalQueries.push('foo');
expectedConfig.additionalQueries.push(tmpDir);
expectedConfig.additionalQueries.push(path.join(tmpDir, 'foo'));
expectedConfig.externalQueries = [new configUtils.ExternalQuery('foo/bar', 'dev')];
expectedConfig.pathsIgnore = ['a', 'b'];
expectedConfig.paths = ['c/d'];
@ -111,6 +113,8 @@ test("load non-empty input", async t => {
fs.writeFileSync(path.join(tmpDir, 'input'), inputFileContents, 'utf8');
setInput('config-file', 'input');
fs.mkdirSync(path.join(tmpDir, 'foo'));
const actualConfig = await configUtils.loadConfig();
// Should exactly equal the object we constructed earlier
@ -222,7 +226,9 @@ const testInputs = {
"foo/bar": configUtils.getQueryUsesIncorrect("foo/bar"),
"foo/bar@v1@v2": configUtils.getQueryUsesIncorrect("foo/bar@v1@v2"),
"foo@master": configUtils.getQueryUsesIncorrect("foo@master"),
"https://github.com/foo/bar@master": configUtils.getQueryUsesIncorrect("https://github.com/foo/bar@master")
"https://github.com/foo/bar@master": configUtils.getQueryUsesIncorrect("https://github.com/foo/bar@master"),
"./foo": configUtils.getLocalPathDoesNotExist("foo"),
"./..": configUtils.getLocalPathOutsideOfRepository(".."),
};
for (const [input, result] of Object.entries(testInputs)) {

View file

@ -17,11 +17,17 @@ export class ExternalQuery {
}
}
// The set of acceptable values for built-in suites from the codeql bundle
const builtinSuites = ['security-experimental', 'security-and-quality'] as const;
// Derive the union type from the array values
type BuiltInSuite = typeof builtinSuites[number];
export class Config {
public name = "";
public disableDefaultQueries = false;
public additionalQueries: string[] = [];
public externalQueries: ExternalQuery[] = [];
public additionalSuites: BuiltInSuite[] = [];
public pathsIgnore: string[] = [];
public paths: string[] = [];
@ -35,10 +41,35 @@ export class Config {
// Check for the local path case before we start trying to parse the repository name
if (queryUses.startsWith("./")) {
this.additionalQueries.push(queryUses.slice(2));
const localQueryPath = queryUses.slice(2);
// 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 = util.getRequiredEnvParam('GITHUB_WORKSPACE');
const absoluteQueryPath = path.join(workspacePath, localQueryPath);
// Check the file exists
if (!fs.existsSync(absoluteQueryPath)) {
throw new Error(getLocalPathDoesNotExist(localQueryPath));
}
// Check the local path doesn't jump outside the repo using '..' or symlinks
if (!(fs.realpathSync(absoluteQueryPath) + path.sep).startsWith(workspacePath + path.sep)) {
throw new Error(getLocalPathOutsideOfRepository(localQueryPath));
}
this.additionalQueries.push(absoluteQueryPath);
return;
}
// Check for one of the builtin suites
if (queryUses.indexOf('/') === -1 && queryUses.indexOf('@') === -1) {
if (queryUses in builtinSuites) {
this.additionalSuites.push(queryUses as BuiltInSuite);
} else {
throw new Error(getQueryUsesIncorrect(queryUses));
}
}
let tok = queryUses.split('@');
if (tok.length !== 2) {
throw new Error(getQueryUsesIncorrect(queryUses));
@ -74,7 +105,19 @@ export function getQueryUsesBlank(): string {
}
export function getQueryUsesIncorrect(queryUses: string): string {
return '"uses" value for queries must be a path, or owner/repo@ref \n Found: ' + queryUses;
return '"uses" value for queries must be a built-in suite (' + builtinSuites.join('or') +
'), a relative path, or of the form owner/repo@ref\n' +
'Found: ' + queryUses;
}
export function getLocalPathOutsideOfRepository(localPath: string): string {
return 'Unable to use queries from local path "' + localPath +
'" as it is outside of the repository';
}
export function getLocalPathDoesNotExist(localPath: string): string {
return 'Unable to use queries from local path "' + localPath +
'" as the path does not exist in the repository';
}
export function getConfigFileOutsideWorkspaceErrorMessage(configFile: string): string {