@@ -21,6 +21,8 @@ import {
2121} from './commandRunner' ;
2222import { logger } from './logging' ;
2323import { tmpDir } from './helpers' ;
24+ import { Credentials } from './authentication' ;
25+ import { REPO_REGEX } from './pure/helpers-pure' ;
2426
2527/**
2628 * Prompts a user to fetch a database from a remote location. Database is assumed to be an archive file.
@@ -46,6 +48,7 @@ export async function promptImportInternetDatabase(
4648
4749 const item = await databaseArchiveFetcher (
4850 databaseUrl ,
51+ { } ,
4952 databaseManager ,
5053 storagePath ,
5154 progress ,
@@ -61,6 +64,79 @@ export async function promptImportInternetDatabase(
6164
6265}
6366
67+ /**
68+ * Prompts a user to fetch a database from GitHub.
69+ * User enters a GitHub repository and then the user is asked which language
70+ * to download (if there is more than one)
71+ *
72+ * @param databaseManager the DatabaseManager
73+ * @param storagePath where to store the unzipped database.
74+ */
75+ export async function promptImportGithubDatabase (
76+ databaseManager : DatabaseManager ,
77+ storagePath : string ,
78+ credentials : Credentials ,
79+ progress : ProgressCallback ,
80+ token : CancellationToken ,
81+ cli ?: CodeQLCliServer
82+ ) : Promise < DatabaseItem | undefined > {
83+ progress ( {
84+ message : 'Choose repository' ,
85+ step : 1 ,
86+ maxStep : 2
87+ } ) ;
88+ const githubRepo = await window . showInputBox ( {
89+ title : 'Enter a GitHub repository in the format <owner>/<repo> (e.g. github/codeql)' ,
90+ placeHolder : '<owner>/<repo>' ,
91+ ignoreFocusOut : true ,
92+ } ) ;
93+ if ( ! githubRepo ) {
94+ return ;
95+ }
96+
97+ if ( ! REPO_REGEX . test ( githubRepo ) ) {
98+ throw new Error ( `Invalid GitHub repository: ${ githubRepo } ` ) ;
99+ }
100+
101+ const databaseUrl = await convertGithubNwoToDatabaseUrl ( githubRepo , credentials , progress ) ;
102+ if ( ! databaseUrl ) {
103+ return ;
104+ }
105+
106+ const octokit = await credentials . getOctokit ( ) ;
107+ /**
108+ * The 'token' property of the token object returned by `octokit.auth()`.
109+ * The object is undocumented, but looks something like this:
110+ * {
111+ * token: 'xxxx',
112+ * tokenType: 'oauth',
113+ * type: 'token',
114+ * }
115+ * We only need the actual token string.
116+ */
117+ const octokitToken = ( await octokit . auth ( ) as { token : string } ) ?. token ;
118+ if ( ! octokitToken ) {
119+ // Just print a generic error message for now. Ideally we could show more debugging info, like the
120+ // octokit object, but that would expose a user token.
121+ throw new Error ( 'Unable to get GitHub token.' ) ;
122+ }
123+ const item = await databaseArchiveFetcher (
124+ databaseUrl ,
125+ { 'Accept' : 'application/zip' , 'Authorization' : `Bearer ${ octokitToken } ` } ,
126+ databaseManager ,
127+ storagePath ,
128+ progress ,
129+ token ,
130+ cli
131+ ) ;
132+ if ( item ) {
133+ await commands . executeCommand ( 'codeQLDatabases.focus' ) ;
134+ void showAndLogInformationMessage ( 'Database downloaded and imported successfully.' ) ;
135+ return item ;
136+ }
137+ return ;
138+ }
139+
64140/**
65141 * Prompts a user to fetch a database from lgtm.
66142 * User enters a project url and then the user is asked which language
@@ -94,6 +170,7 @@ export async function promptImportLgtmDatabase(
94170 if ( databaseUrl ) {
95171 const item = await databaseArchiveFetcher (
96172 databaseUrl ,
173+ { } ,
97174 databaseManager ,
98175 storagePath ,
99176 progress ,
@@ -140,6 +217,7 @@ export async function importArchiveDatabase(
140217 try {
141218 const item = await databaseArchiveFetcher (
142219 databaseUrl ,
220+ { } ,
143221 databaseManager ,
144222 storagePath ,
145223 progress ,
@@ -166,13 +244,15 @@ export async function importArchiveDatabase(
166244 * or in the local filesystem.
167245 *
168246 * @param databaseUrl URL from which to grab the database
247+ * @param requestHeaders Headers to send with the request
169248 * @param databaseManager the DatabaseManager
170249 * @param storagePath where to store the unzipped database.
171250 * @param progress callback to send progress messages to
172251 * @param token cancellation token
173252 */
174253async function databaseArchiveFetcher (
175254 databaseUrl : string ,
255+ requestHeaders : { [ key : string ] : string } ,
176256 databaseManager : DatabaseManager ,
177257 storagePath : string ,
178258 progress : ProgressCallback ,
@@ -193,7 +273,7 @@ async function databaseArchiveFetcher(
193273 if ( isFile ( databaseUrl ) ) {
194274 await readAndUnzip ( databaseUrl , unzipPath , cli , progress ) ;
195275 } else {
196- await fetchAndUnzip ( databaseUrl , unzipPath , cli , progress ) ;
276+ await fetchAndUnzip ( databaseUrl , requestHeaders , unzipPath , cli , progress ) ;
197277 }
198278
199279 progress ( {
@@ -292,6 +372,7 @@ async function readAndUnzip(
292372
293373async function fetchAndUnzip (
294374 databaseUrl : string ,
375+ requestHeaders : { [ key : string ] : string } ,
295376 unzipPath : string ,
296377 cli ?: CodeQLCliServer ,
297378 progress ?: ProgressCallback
@@ -310,7 +391,10 @@ async function fetchAndUnzip(
310391 step : 1 ,
311392 } ) ;
312393
313- const response = await checkForFailingResponse ( await fetch ( databaseUrl ) , 'Error downloading database' ) ;
394+ const response = await checkForFailingResponse (
395+ await fetch ( databaseUrl , { headers : requestHeaders } ) ,
396+ 'Error downloading database'
397+ ) ;
314398 const archiveFileStream = fs . createWriteStream ( archivePath ) ;
315399
316400 const contentLength = response . headers . get ( 'content-length' ) ;
@@ -381,6 +465,37 @@ export async function findDirWithFile(
381465 return ;
382466}
383467
468+ export async function convertGithubNwoToDatabaseUrl (
469+ githubRepo : string ,
470+ credentials : Credentials ,
471+ progress : ProgressCallback ) : Promise < string | undefined > {
472+ try {
473+ // TODO: In future, we could accept GitHub URLs in addition to NWOs.
474+ // Similar to "looksLikeLgtmUrl".
475+ if ( ! REPO_REGEX . test ( githubRepo ) ) {
476+ throw new Error ( 'Invalid repository format. Must be in the format <owner>/<repo> (e.g. github/codeql)' ) ;
477+ }
478+
479+ const [ owner , repo ] = githubRepo . split ( '/' ) ;
480+
481+ const octokit = await credentials . getOctokit ( ) ;
482+ const response = await octokit . request ( 'GET /repos/:owner/:repo/code-scanning/codeql/databases' , { owner, repo } ) ;
483+
484+ const languages = response . data . map ( ( db : any ) => db . language ) ;
485+
486+ const language = await promptForLanguage ( languages , progress ) ;
487+ if ( ! language ) {
488+ return ;
489+ }
490+
491+ return `https://api.github.com/repos/${ owner } /${ repo } /code-scanning/codeql/databases/${ language } ` ;
492+
493+ } catch ( e ) {
494+ void logger . log ( `Error: ${ e . message } ` ) ;
495+ throw new Error ( `Unable to get database for '${ githubRepo } '` ) ;
496+ }
497+ }
498+
384499/**
385500 * The URL pattern is https://lgtm.com/projects/{provider}/{org}/{name}/{irrelevant-subpages}.
386501 * There are several possibilities for the provider: in addition to GitHub.com (g),
@@ -506,7 +621,7 @@ async function promptForLanguage(
506621 maxStep : 2
507622 } ) ;
508623 if ( ! languages . length ) {
509- return ;
624+ throw new Error ( 'No databases found' ) ;
510625 }
511626 if ( languages . length === 1 ) {
512627 return languages [ 0 ] ;
0 commit comments