Merge pull request #971 from marcnjaramillo/integrate-codeql-database-unbundle

Integrate codeql database unbundle
This commit is contained in:
Andrew Eisenberg
2021-10-19 10:01:40 -07:00
committed by GitHub
6 changed files with 68 additions and 28 deletions

View File

@@ -2,7 +2,8 @@
## [UNRELEASED] ## [UNRELEASED]
- Replace certain control codes (`U+0000` - `U+001F`) with their corresponding control labels (`U+2400` - `U+241F`) in the results view. [#963](https://github.com/github/vscode-codeql/pull/963) - Use the CodeQL CLI command `database unbundle` for the archive download option, which ensures even archives over 4GB in size can be downloaded. [#971](https://github.com/github/vscode-codeql/pull/971)
- Fix a bug with importing large databases. Databases over 4GB can now be imported directly from LGTM or from a zip file. This functionality is only available when using CodeQL CLI version 2.6.0 or later. [#963](https://github.com/github/vscode-codeql/pull/963)
## 1.5.6 - 07 October 2021 ## 1.5.6 - 07 October 2021

View File

@@ -600,6 +600,15 @@ export class CodeQLCliServer implements Disposable {
return await this.runJsonCodeQlCliCommand<BQRSInfo>(['bqrs', 'info'], subcommandArgs, 'Reading bqrs header'); return await this.runJsonCodeQlCliCommand<BQRSInfo>(['bqrs', 'info'], subcommandArgs, 'Reading bqrs header');
} }
async databaseUnbundle(archivePath: string, target: string, name?: string): Promise<string> {
const subcommandArgs = [];
if (target) subcommandArgs.push('--target', target);
if (name) subcommandArgs.push('--name', name);
subcommandArgs.push(archivePath);
return await this.runCodeQlCliCommand(['database', 'unbundle'], subcommandArgs, `Extracting ${archivePath} to directory ${target}`);
}
/** /**
* Gets the results from a bqrs. * Gets the results from a bqrs.
* @param bqrsPath The path to the bqrs. * @param bqrsPath The path to the bqrs.
@@ -1074,6 +1083,11 @@ export class CliVersionConstraint {
*/ */
public static CLI_VERSION_WITH_ALLOW_LIBRARY_PACKS_IN_RESOLVE_QUERIES = new SemVer('2.6.1'); public static CLI_VERSION_WITH_ALLOW_LIBRARY_PACKS_IN_RESOLVE_QUERIES = new SemVer('2.6.1');
/**
* CLI version where the `database unbundle` subcommand was introduced.
*/
public static CLI_VERSION_WITH_DATABASE_UNBUNDLE = new SemVer('2.6.0');
constructor(private readonly cli: CodeQLCliServer) { constructor(private readonly cli: CodeQLCliServer) {
/**/ /**/
} }
@@ -1105,4 +1119,9 @@ export class CliVersionConstraint {
async supportsDatabaseRegistration() { async supportsDatabaseRegistration() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DB_REGISTRATION); return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DB_REGISTRATION);
} }
async supportsDatabaseUnbundle() {
return this.isVersionAtLeast(CliVersionConstraint.CLI_VERSION_WITH_DATABASE_UNBUNDLE);
}
} }

View File

@@ -1,12 +1,13 @@
import fetch, { Response } from 'node-fetch'; import fetch, { Response } from 'node-fetch';
import * as unzipper from 'unzipper';
import { zip } from 'zip-a-folder'; import { zip } from 'zip-a-folder';
import * as unzipper from 'unzipper';
import { import {
Uri, Uri,
CancellationToken, CancellationToken,
commands, commands,
window, window,
} from 'vscode'; } from 'vscode';
import { CodeQLCliServer } from './cli';
import * as fs from 'fs-extra'; import * as fs from 'fs-extra';
import * as path from 'path'; import * as path from 'path';
@@ -32,6 +33,7 @@ export async function promptImportInternetDatabase(
storagePath: string, storagePath: string,
progress: ProgressCallback, progress: ProgressCallback,
token: CancellationToken, token: CancellationToken,
cli?: CodeQLCliServer
): Promise<DatabaseItem | undefined> { ): Promise<DatabaseItem | undefined> {
const databaseUrl = await window.showInputBox({ const databaseUrl = await window.showInputBox({
prompt: 'Enter URL of zipfile of database to download', prompt: 'Enter URL of zipfile of database to download',
@@ -47,7 +49,8 @@ export async function promptImportInternetDatabase(
databaseManager, databaseManager,
storagePath, storagePath,
progress, progress,
token token,
cli
); );
if (item) { if (item) {
@@ -70,7 +73,8 @@ export async function promptImportLgtmDatabase(
databaseManager: DatabaseManager, databaseManager: DatabaseManager,
storagePath: string, storagePath: string,
progress: ProgressCallback, progress: ProgressCallback,
token: CancellationToken token: CancellationToken,
cli?: CodeQLCliServer
): Promise<DatabaseItem | undefined> { ): Promise<DatabaseItem | undefined> {
progress({ progress({
message: 'Choose project', message: 'Choose project',
@@ -93,7 +97,8 @@ export async function promptImportLgtmDatabase(
databaseManager, databaseManager,
storagePath, storagePath,
progress, progress,
token token,
cli
); );
if (item) { if (item) {
await commands.executeCommand('codeQLDatabases.focus'); await commands.executeCommand('codeQLDatabases.focus');
@@ -120,6 +125,7 @@ export async function importArchiveDatabase(
storagePath: string, storagePath: string,
progress: ProgressCallback, progress: ProgressCallback,
token: CancellationToken, token: CancellationToken,
cli?: CodeQLCliServer,
): Promise<DatabaseItem | undefined> { ): Promise<DatabaseItem | undefined> {
try { try {
const item = await databaseArchiveFetcher( const item = await databaseArchiveFetcher(
@@ -127,7 +133,8 @@ export async function importArchiveDatabase(
databaseManager, databaseManager,
storagePath, storagePath,
progress, progress,
token token,
cli
); );
if (item) { if (item) {
await commands.executeCommand('codeQLDatabases.focus'); await commands.executeCommand('codeQLDatabases.focus');
@@ -159,7 +166,8 @@ async function databaseArchiveFetcher(
databaseManager: DatabaseManager, databaseManager: DatabaseManager,
storagePath: string, storagePath: string,
progress: ProgressCallback, progress: ProgressCallback,
token: CancellationToken token: CancellationToken,
cli?: CodeQLCliServer,
): Promise<DatabaseItem> { ): Promise<DatabaseItem> {
progress({ progress({
message: 'Getting database', message: 'Getting database',
@@ -173,9 +181,9 @@ async function databaseArchiveFetcher(
const unzipPath = await getStorageFolder(storagePath, databaseUrl); const unzipPath = await getStorageFolder(storagePath, databaseUrl);
if (isFile(databaseUrl)) { if (isFile(databaseUrl)) {
await readAndUnzip(databaseUrl, unzipPath, progress); await readAndUnzip(databaseUrl, unzipPath, cli, progress);
} else { } else {
await fetchAndUnzip(databaseUrl, unzipPath, progress); await fetchAndUnzip(databaseUrl, unzipPath, cli, progress);
} }
progress({ progress({
@@ -249,6 +257,7 @@ function validateHttpsUrl(databaseUrl: string) {
async function readAndUnzip( async function readAndUnzip(
zipUrl: string, zipUrl: string,
unzipPath: string, unzipPath: string,
cli?: CodeQLCliServer,
progress?: ProgressCallback progress?: ProgressCallback
) { ) {
// TODO: Providing progress as the file is unzipped is currently blocked // TODO: Providing progress as the file is unzipped is currently blocked
@@ -259,16 +268,22 @@ async function readAndUnzip(
step: 9, step: 9,
message: `Unzipping into ${path.basename(unzipPath)}` message: `Unzipping into ${path.basename(unzipPath)}`
}); });
// Must get the zip central directory since streaming the if (cli && await cli.cliConstraints.supportsDatabaseUnbundle()) {
// zip contents may not have correct local file headers. // Use the `database unbundle` command if the installed cli version supports it
// Instead, we can only rely on the central directory. await cli.databaseUnbundle(zipFile, unzipPath);
const directory = await unzipper.Open.file(zipFile); } else {
await directory.extract({ path: unzipPath }); // Must get the zip central directory since streaming the
// zip contents may not have correct local file headers.
// Instead, we can only rely on the central directory.
const directory = await unzipper.Open.file(zipFile);
await directory.extract({ path: unzipPath });
}
} }
async function fetchAndUnzip( async function fetchAndUnzip(
databaseUrl: string, databaseUrl: string,
unzipPath: string, unzipPath: string,
cli?: CodeQLCliServer,
progress?: ProgressCallback progress?: ProgressCallback
) { ) {
// Although it is possible to download and stream directly to an unzipped directory, // Although it is possible to download and stream directly to an unzipped directory,
@@ -298,7 +313,8 @@ async function fetchAndUnzip(
.on('error', reject) .on('error', reject)
); );
await readAndUnzip(Uri.file(archivePath).toString(true), unzipPath, progress); await readAndUnzip(Uri.file(archivePath).toString(true), unzipPath, cli, progress);
// remove archivePath eagerly since these archives can be large. // remove archivePath eagerly since these archives can be large.
await fs.remove(archivePath); await fs.remove(archivePath);

View File

@@ -451,14 +451,13 @@ export class DatabaseUI extends DisposableObject {
handleChooseDatabaseInternet = async ( handleChooseDatabaseInternet = async (
progress: ProgressCallback, progress: ProgressCallback,
token: CancellationToken token: CancellationToken
): Promise< ): Promise<DatabaseItem | undefined> => {
DatabaseItem | undefined
> => {
return await promptImportInternetDatabase( return await promptImportInternetDatabase(
this.databaseManager, this.databaseManager,
this.storagePath, this.storagePath,
progress, progress,
token token,
this.queryServer?.cliServer
); );
}; };
@@ -470,7 +469,8 @@ export class DatabaseUI extends DisposableObject {
this.databaseManager, this.databaseManager,
this.storagePath, this.storagePath,
progress, progress,
token token,
this.queryServer?.cliServer
); );
}; };
@@ -580,7 +580,8 @@ export class DatabaseUI extends DisposableObject {
this.databaseManager, this.databaseManager,
this.storagePath, this.storagePath,
progress, progress,
token token,
this.queryServer?.cliServer
); );
} else { } else {
await this.setCurrentDatabase(progress, token, uri); await this.setCurrentDatabase(progress, token, uri);
@@ -696,7 +697,6 @@ export class DatabaseUI extends DisposableObject {
token: CancellationToken, token: CancellationToken,
): Promise<DatabaseItem | undefined> { ): Promise<DatabaseItem | undefined> {
const uri = await chooseDatabaseDir(byFolder); const uri = await chooseDatabaseDir(byFolder);
if (!uri) { if (!uri) {
return undefined; return undefined;
} }
@@ -713,7 +713,8 @@ export class DatabaseUI extends DisposableObject {
this.databaseManager, this.databaseManager,
this.storagePath, this.storagePath,
progress, progress,
token token,
this.queryServer?.cliServer
); );
} }
} }

View File

@@ -5,6 +5,7 @@ import { expect } from 'chai';
import { extensions, CancellationToken, Uri, window } from 'vscode'; import { extensions, CancellationToken, Uri, window } from 'vscode';
import { CodeQLExtensionInterface } from '../../extension'; import { CodeQLExtensionInterface } from '../../extension';
import { CodeQLCliServer } from '../../cli';
import { DatabaseManager } from '../../databases'; import { DatabaseManager } from '../../databases';
import { promptImportLgtmDatabase, importArchiveDatabase, promptImportInternetDatabase } from '../../databaseFetcher'; import { promptImportLgtmDatabase, importArchiveDatabase, promptImportInternetDatabase } from '../../databaseFetcher';
import { ProgressCallback } from '../../commandRunner'; import { ProgressCallback } from '../../commandRunner';
@@ -17,10 +18,11 @@ describe('Databases', function() {
this.timeout(60000); this.timeout(60000);
const LGTM_URL = 'https://lgtm.com/projects/g/aeisenberg/angular-bind-notifier/'; const LGTM_URL = 'https://lgtm.com/projects/g/aeisenberg/angular-bind-notifier/';
let databaseManager: DatabaseManager; let databaseManager: DatabaseManager;
let sandbox: sinon.SinonSandbox; let sandbox: sinon.SinonSandbox;
let inputBoxStub: sinon.SinonStub; let inputBoxStub: sinon.SinonStub;
let cli: CodeQLCliServer;
let progressCallback: ProgressCallback; let progressCallback: ProgressCallback;
beforeEach(async () => { beforeEach(async () => {
@@ -53,7 +55,7 @@ describe('Databases', function() {
it('should add a database from a folder', async () => { it('should add a database from a folder', async () => {
const progressCallback = sandbox.spy() as ProgressCallback; const progressCallback = sandbox.spy() as ProgressCallback;
const uri = Uri.file(dbLoc); const uri = Uri.file(dbLoc);
let dbItem = await importArchiveDatabase(uri.toString(true), databaseManager, storagePath, progressCallback, {} as CancellationToken); let dbItem = await importArchiveDatabase(uri.toString(true), databaseManager, storagePath, progressCallback, {} as CancellationToken, cli);
expect(dbItem).to.be.eq(databaseManager.currentDatabaseItem); expect(dbItem).to.be.eq(databaseManager.currentDatabaseItem);
expect(dbItem).to.be.eq(databaseManager.databaseItems[0]); expect(dbItem).to.be.eq(databaseManager.databaseItems[0]);
expect(dbItem).not.to.be.undefined; expect(dbItem).not.to.be.undefined;
@@ -64,7 +66,7 @@ describe('Databases', function() {
it('should add a database from lgtm with only one language', async () => { it('should add a database from lgtm with only one language', async () => {
inputBoxStub.resolves(LGTM_URL); inputBoxStub.resolves(LGTM_URL);
let dbItem = await promptImportLgtmDatabase(databaseManager, storagePath, progressCallback, {} as CancellationToken); let dbItem = await promptImportLgtmDatabase(databaseManager, storagePath, progressCallback, {} as CancellationToken, cli);
expect(dbItem).not.to.be.undefined; expect(dbItem).not.to.be.undefined;
dbItem = dbItem!; dbItem = dbItem!;
expect(dbItem.name).to.eq('aeisenberg_angular-bind-notifier_106179a'); expect(dbItem.name).to.eq('aeisenberg_angular-bind-notifier_106179a');
@@ -74,7 +76,7 @@ describe('Databases', function() {
it('should add a database from a url', async () => { it('should add a database from a url', async () => {
inputBoxStub.resolves(DB_URL); inputBoxStub.resolves(DB_URL);
let dbItem = await promptImportInternetDatabase(databaseManager, storagePath, progressCallback, {} as CancellationToken); let dbItem = await promptImportInternetDatabase(databaseManager, storagePath, progressCallback, {} as CancellationToken, cli);
expect(dbItem).not.to.be.undefined; expect(dbItem).not.to.be.undefined;
dbItem = dbItem!; dbItem = dbItem!;
expect(dbItem.name).to.eq('db'); expect(dbItem.name).to.eq('db');

View File

@@ -68,7 +68,8 @@ describe('Queries', function() {
databaseManager, databaseManager,
storagePath, storagePath,
progress, progress,
token token,
cli
); );
if (!maybeDbItem) { if (!maybeDbItem) {