Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6d7a135

Browse files
Add a CLI interface to the upload-sarif action
1 parent bcf676e commit 6d7a135

15 files changed

Lines changed: 356 additions & 90 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/cli/
2+

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"description": "CodeQL action",
66
"scripts": {
77
"build": "tsc",
8+
"build-cli": "webpack --mode production",
89
"test": "ava src/** --serial --verbose",
910
"lint": "tslint -p . -c tslint.json 'src/**/*.ts'",
1011
"removeNPMAbsolutePaths": "removeNPMAbsolutePaths . --force"
@@ -23,6 +24,7 @@
2324
"@actions/github": "^2.2.0",
2425
"@actions/http-client": "^1.0.8",
2526
"@actions/tool-cache": "^1.5.5",
27+
"commander": "6.0.0",
2628
"console-log-level": "^1.4.1",
2729
"file-url": "^3.0.0",
2830
"fs": "0.0.1-security",
@@ -52,6 +54,9 @@
5254
"sinon": "^9.0.2",
5355
"tslint": "^6.1.0",
5456
"tslint-eslint-rules": "^5.4.0",
55-
"typescript": "^3.7.5"
57+
"ts-loader": "8.0.2",
58+
"typescript": "^3.7.5",
59+
"webpack": "4.44.1",
60+
"webpack-cli": "3.3.12"
5661
}
5762
}

src/api-client.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,45 @@ import * as core from "@actions/core";
22
import * as github from "@actions/github";
33
import consoleLogLevel from "console-log-level";
44

5-
import { isLocalRun } from "./util";
5+
import { getRequiredEnvParam, isLocalRun } from "./util";
66

7-
export const getApiClient = function(allowLocalRun = false) {
7+
export const getApiClient = function(githubAuth: string, githubApiUrl: string, allowLocalRun = false) {
88
if (isLocalRun() && !allowLocalRun) {
99
throw new Error('Invalid API call in local run');
1010
}
1111
return new github.GitHub(
12-
core.getInput('token'),
1312
{
13+
auth: parseAuth(githubAuth),
14+
baseUrl: githubApiUrl,
1415
userAgent: "CodeQL Action",
1516
log: consoleLogLevel({ level: "debug" })
1617
});
1718
};
19+
20+
// Parses the user input as either a single token,
21+
// or a username and password / PAT.
22+
function parseAuth(auth: string): string {
23+
// Check if it's a username:password pair
24+
const c = auth.indexOf(':');
25+
if (c !== -1) {
26+
return 'basic ' + Buffer.from(auth).toString('base64');
27+
// return {
28+
// username: auth.substring(0, c),
29+
// password: auth.substring(c + 1),
30+
// on2fa: async () => { throw new Error(''); }
31+
// };
32+
}
33+
34+
// Otherwise use the token as it is
35+
return auth;
36+
}
37+
38+
// Temporary function to aid in the transition to running on and off of github actions.
39+
// Once all code has been coverted this function should be removed or made canonical
40+
// and called only from the action entrypoints.
41+
export function getActionsApiClient(allowLocalRun = false) {
42+
return getApiClient(
43+
core.getInput('token'),
44+
getRequiredEnvParam('GITHUB_API_URL'),
45+
allowLocalRun);
46+
}

src/cli.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { Command } from 'commander';
2+
import * as path from 'path';
3+
4+
import { getCLILogger } from './logging';
5+
import { parseRepositoryNwo } from './repository';
6+
import * as upload_lib from './upload-lib';
7+
8+
const program = new Command();
9+
program.version('0.0.1');
10+
11+
interface UploadArgs {
12+
sarifFile: string;
13+
repository: string;
14+
commit: string;
15+
ref: string;
16+
analysisKey: string;
17+
githubUrl: string;
18+
githubAuth: string;
19+
analysisName: string | undefined;
20+
checkoutPath: string | undefined;
21+
environment: string | undefined;
22+
}
23+
24+
function parseGithubApiUrl(inputUrl: string): string {
25+
try {
26+
const url = new URL(inputUrl);
27+
28+
// If we detect this is trying to be to github.com
29+
// then return with a fixed canonical URL.
30+
if (url.hostname === 'github.com' || url.hostname === 'api.github.com') {
31+
return 'https://api.github.com';
32+
}
33+
34+
// Add the API path if it's not already present.
35+
if (url.pathname.indexOf('/api/v3') === -1) {
36+
url.pathname = path.join(url.pathname, 'api', 'v3');
37+
}
38+
39+
return url.toString();
40+
41+
} catch (e) {
42+
throw new Error(`"${inputUrl}" is not a valid URL`);
43+
}
44+
}
45+
46+
program
47+
.command('upload')
48+
.description('Uploads a SARIF file, or all SARIF files from a directory, to code scanning')
49+
.requiredOption('--sarif-file <file>', 'SARIF file to upload')
50+
.requiredOption('--repository <repository>', 'Repository name')
51+
.requiredOption('--commit <commit>', 'SHA of commit that was analyzed')
52+
.requiredOption('--ref <ref>', 'Name of ref that was analyzed')
53+
.requiredOption('--analysis-key <key>', 'Identifies the analysis, for use matching up equivalent analyses on different commits')
54+
.requiredOption('--github-url <url>', 'URL of GitHub instance')
55+
.requiredOption('--github-auth <auth>', 'GitHub Apps token, or of the form "username:token" if using a personal access token')
56+
.option('--checkout-path <path>', 'Checkout path (default: current working directory)')
57+
.option('--analysis-name <name>', 'Display name of the analysis (default: same as analysis-key')
58+
.option('--environment <env>', 'Environment (default: empty)')
59+
.action(async (cmd: UploadArgs) => {
60+
const logger = getCLILogger();
61+
try {
62+
await upload_lib.upload(
63+
cmd.sarifFile,
64+
parseRepositoryNwo(cmd.repository),
65+
cmd.commit,
66+
cmd.ref,
67+
cmd.analysisKey,
68+
cmd.analysisName || cmd.analysisKey,
69+
undefined,
70+
cmd.checkoutPath || process.cwd(),
71+
cmd.environment,
72+
cmd.githubAuth,
73+
parseGithubApiUrl(cmd.githubUrl),
74+
'cli',
75+
logger);
76+
} catch (e) {
77+
logger.error("Upload failed");
78+
logger.error(e);
79+
}
80+
});
81+
82+
program.parse(process.argv);

src/codeql.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ async function getCodeQLBundleDownloadURL(): Promise<string> {
121121
}
122122
let [repositoryOwner, repositoryName] = repository.split("/");
123123
try {
124-
const release = await api.getApiClient().repos.getReleaseByTag({
124+
const release = await api.getActionsApiClient().repos.getReleaseByTag({
125125
owner: repositoryOwner,
126126
repo: repositoryName,
127127
tag: CODEQL_BUNDLE_VERSION

src/config-utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ async function getLanguagesInRepo(): Promise<Language[]> {
441441
let repo = repo_nwo[1];
442442

443443
core.debug(`GitHub repo ${owner} ${repo}`);
444-
const response = await api.getApiClient(true).repos.listLanguages({
444+
const response = await api.getActionsApiClient(true).repos.listLanguages({
445445
owner,
446446
repo
447447
});
@@ -689,7 +689,7 @@ async function getRemoteConfig(configFile: string): Promise<UserConfig> {
689689
throw new Error(getConfigFileRepoFormatInvalidMessage(configFile));
690690
}
691691

692-
const response = await api.getApiClient(true).repos.getContents({
692+
const response = await api.getActionsApiClient(true).repos.getContents({
693693
owner: pieces.groups.owner,
694694
repo: pieces.groups.repo,
695695
path: pieces.groups.path,

src/finalize-db.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import * as path from 'path';
44

55
import { getCodeQL, isScannedLanguage } from './codeql';
66
import * as configUtils from './config-utils';
7+
import { getActionsLogger } from './logging';
8+
import { parseRepositoryNwo } from './repository';
79
import * as sharedEnv from './shared-environment';
810
import * as upload_lib from './upload-lib';
911
import * as util from './util';
@@ -144,7 +146,20 @@ async function run() {
144146
queriesStats = await runQueries(databaseFolder, sarifFolder, config);
145147

146148
if ('true' === core.getInput('upload')) {
147-
uploadStats = await upload_lib.upload(sarifFolder);
149+
uploadStats = await upload_lib.upload(
150+
sarifFolder,
151+
parseRepositoryNwo(util.getRequiredEnvParam('GITHUB_REPOSITORY')),
152+
await util.getCommitOid(),
153+
util.getRef(),
154+
await util.getAnalysisKey(),
155+
util.getRequiredEnvParam('GITHUB_WORKFLOW'),
156+
util.getWorkflowRunID(),
157+
core.getInput('checkout_path'),
158+
core.getInput('matrix'),
159+
core.getInput('token'),
160+
util.getRequiredEnvParam('GITHUB_API_URL'),
161+
'actions',
162+
getActionsLogger());
148163
}
149164

150165
} catch (error) {

src/logging.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as core from '@actions/core';
2+
3+
export interface Logger {
4+
debug: (message: string) => void;
5+
info: (message: string) => void;
6+
warning: (message: string) => void;
7+
error: (message: string) => void;
8+
9+
startGroup: (name: string) => void;
10+
endGroup: () => void;
11+
}
12+
13+
export function getActionsLogger(): Logger {
14+
return core;
15+
}
16+
17+
export function getCLILogger(): Logger {
18+
return {
19+
debug: console.debug,
20+
info: console.info,
21+
warning: console.warn,
22+
error: console.error,
23+
startGroup: () => undefined,
24+
endGroup: () => undefined,
25+
};
26+
}

src/repository.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// A repository name with owner, parsed into its two parts
2+
export interface RepositoryNwo {
3+
owner: string;
4+
repo: string;
5+
}
6+
7+
export function parseRepositoryNwo(input: string): RepositoryNwo {
8+
const parts = input.split('/');
9+
if (parts.length !== 2) {
10+
throw new Error(`"${input}" is not a valid repository name`);
11+
}
12+
return {
13+
owner: parts[0],
14+
repo: parts[1],
15+
};
16+
}

src/testing-utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ export function setupTests(test: TestInterface<any>) {
5555
// process.env only has strings fields, so a shallow copy is fine.
5656
t.context.env = {};
5757
Object.assign(t.context.env, process.env);
58+
59+
// Any test that runs code that expects to only be run on actions
60+
// will depend on various environment variables.
61+
process.env['GITHUB_API_URL'] = 'https://github.localhost/api/v3';
5862
});
5963

6064
typedTest.afterEach.always(t => {

0 commit comments

Comments
 (0)