Skip to content
This repository has been archived by the owner on Apr 29, 2022. It is now read-only.

Commit

Permalink
feat: Implement 'merge' command options --fail and --update-commit-st…
Browse files Browse the repository at this point in the history
…atus
  • Loading branch information
kgilpin committed Feb 7, 2022
1 parent 5144b3d commit 49706c2
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 61 deletions.
35 changes: 12 additions & 23 deletions src/cli/ci/command.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { glob as globCallback } from 'glob';
import { writeFile } from 'fs/promises';
import { promisify } from 'util';
import yargs, { Arguments, Argv } from 'yargs';
import { Arguments, Argv } from 'yargs';

import { FindingStatusListItem } from '@appland/client/dist/src';

Expand All @@ -10,7 +10,6 @@ import { AbortError, ValidationError } from '../../errors';
import { ScanResults } from '../../report/scanResults';
import { verbose } from '../../rules/lib/util';
import upload from '../../integration/appland/upload';
import postCommitStatus from '../../integration/github/commitStatus';
import { newFindings } from '../../findings';
import findingsReport from '../../report/findingsReport';
import summaryReport from '../../report/summaryReport';
Expand All @@ -22,6 +21,8 @@ import { default as buildScanner } from '../scan/scanner';

import CommandOptions from './options';
import scanArgs from '../scanArgs';
import updateCommitStatus from '../updateCommitStatus';
import reportUploadURL from '../reportUploadURL';

export default {
command: 'ci',
Expand All @@ -36,7 +37,7 @@ export default {
});
args.option('update-commit-status', {
describe: 'update commit status in SCM system',
default: true,
default: false,
type: 'boolean',
});
args.option('upload', {
Expand All @@ -55,11 +56,11 @@ export default {
appmapDir,
config,
verbose: isVerbose,
fail,
fail: failOption,
app: appIdArg,
reportFile,
upload: doUpload,
updateCommitStatus,
updateCommitStatus: updateCommitStatusOption,
mergeKey,
} = options as unknown as CommandOptions;

Expand Down Expand Up @@ -96,28 +97,16 @@ export default {
summaryReport(scanResults, true);

if (doUpload) {
await upload(rawScanResults, appId, mergeKey);
const uploadResponse = await upload(rawScanResults, appId, mergeKey);
reportUploadURL(uploadResponse.summary.numFindings, uploadResponse.url);
}

if (updateCommitStatus) {
if (scanResults.findings.length > 0) {
await postCommitStatus(
'failure',
`${scanResults.summary.numChecks} checks, ${scanResults.findings.length} findings. See CI job log for details.`
);
console.log(
`Commit status updated to: failure (${scanResults.findings.length} findings)`
);
} else {
await postCommitStatus('success', `${scanResults.summary.numChecks} checks passed`);
console.log(`Commit status updated to: success.`);
}
if (updateCommitStatusOption) {
await updateCommitStatus(scanResults.findings.length, scanResults.summary.numChecks);
}

if (fail) {
if (scanResults.findings.length > 0) {
yargs.exit(1, new Error(`${scanResults.findings.length} findings`));
}
if (failOption) {
fail(scanResults.findings.length);
}
} catch (err) {
if (err instanceof ValidationError) {
Expand Down
7 changes: 7 additions & 0 deletions src/cli/fail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import yargs from 'yargs';

export default function fail(numFindings: number): void {
if (numFindings > 0) {
yargs.exit(1, new Error(`${numFindings} findings`));
}
}
23 changes: 12 additions & 11 deletions src/cli/merge/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@ import { Arguments, Argv } from 'yargs';
import CommandOptions from './options';
import { ScanResults } from '../../integration/appland/scanResults';
import resolveAppId from '../resolveAppId';
import updateCommitStatus from '../updateCommitStatus';

export default {
command: 'merge <merge-key>',
describe: 'Merge scan results from parallel scans',
builder(args: Argv): Argv {
args.option('api-key', {
describe:
'AppMap server API key. Use of this option is discouraged; set APPLAND_API_KEY instead',
});
args.option('app', {
describe:
'name of the app to publish the findings for. By default, this is determined by looking in appmap.yml',
Expand All @@ -23,7 +20,7 @@ export default {
});
args.option('update-commit-status', {
describe: 'update commit status in SCM system',
default: true,
default: false,
type: 'boolean',
});

Expand All @@ -38,10 +35,8 @@ export default {
const {
verbose: isVerbose,
app: appIdArg,
/*
fail,
updateCommitStatus,
*/
fail: failOption,
updateCommitStatus: updateCommitStatusOption,
mergeKey,
} = options as unknown as CommandOptions;

Expand All @@ -52,8 +47,14 @@ export default {
const appId = await resolveAppId(appIdArg, '.');

const mergeResults = await ScanResults.merge(appId, mergeKey);
console.warn(mergeResults);
console.warn(`Merged results to ${mergeResults.url}`);

// It also has to collect the final findings, and fail the build if so instructed.
if (updateCommitStatusOption) {
await updateCommitStatus(mergeResults.summary.numFindings, mergeResults.summary.numChecks);
}

if (failOption) {
fail(mergeResults.summary.numFindings);
}
},
};
9 changes: 9 additions & 0 deletions src/cli/reportUploadURL.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { URL } from 'url';

export default function reportUploadURL(numFindings: number, url?: URL): void {
let message = `Uploaded ${numFindings} findings`;
if (url) {
message += ` to ${url}`;
}
console.log(message);
}
17 changes: 17 additions & 0 deletions src/cli/updateCommitStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import postCommitStatus from '../integration/github/commitStatus';

export default async function updateCommitStatus(
numFindings: number,
numChecks: number
): Promise<void> {
if (numFindings > 0) {
await postCommitStatus(
'failure',
`${numChecks} checks, ${numFindings} findings. See CI job log for details.`
);
console.log(`Commit status updated to: failure (${numFindings} findings)`);
} else {
await postCommitStatus('success', `${numChecks} checks passed`);
console.log(`Commit status updated to: success.`);
}
}
5 changes: 4 additions & 1 deletion src/cli/upload/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import validateFile from '../validateFile';

import CommandOptions from './options';
import resolveAppId from '../resolveAppId';
import reportUploadURL from '../reportUploadURL';

export default {
command: 'upload',
Expand Down Expand Up @@ -48,6 +49,8 @@ export default {
const appId = await resolveAppId(appIdArg, appmapDir);

const scanResults = JSON.parse((await readFile(reportFile)).toString()) as ScanResults;
await upload(scanResults, appId, mergeKey);
const uploadResponse = await upload(scanResults, appId, mergeKey);

reportUploadURL(uploadResponse.summary.numFindings, uploadResponse.url);
},
};
5 changes: 5 additions & 0 deletions src/integration/appland/location.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { URL } from 'url';

export default interface Location {
url: URL;
}
31 changes: 15 additions & 16 deletions src/integration/appland/scanResults.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { IncomingMessage } from 'http';

import { buildRequest, handleError } from '@appland/client/dist/src';
import { buildRequest, handleError, reportJSON } from '@appland/client/dist/src';
import Location from './location';
import ScannerJob from './scannerJob';
import { URL } from 'url';

export type MergeResponse = {
id: number;
};
export interface MergeResponse extends ScannerJob, Location {}

export class ScanResults {
static async merge(appId: string, mergeKey: string): Promise<MergeResponse> {
Expand All @@ -15,6 +16,7 @@ export class ScanResults {
merge_key: mergeKey,
});
const request = await buildRequest('api/scanner_jobs/merge');
let uploadURL: URL;
return new Promise<IncomingMessage>((resolve, reject) => {
const req = request.requestFunction(
request.url,
Expand All @@ -33,18 +35,15 @@ export class ScanResults {
req.end();
})
.then(handleError)
.then((response: IncomingMessage) => {
return new Promise<MergeResponse>((resolve, reject) => {
const responseData: Buffer[] = [];
response
.on('data', (chunk: Buffer) => {
responseData.push(Buffer.from(chunk));
})
.on('end', () => {
resolve(JSON.parse(Buffer.concat(responseData).toString()) as MergeResponse);
})
.on('error', reject);
});
.then((response) => {
if (response.headers.location) {
uploadURL = new URL(response.headers.location, request.url.href);
}
return reportJSON<MergeResponse>(response);
})
.then((uploadResponse) => {
uploadResponse.url = uploadURL;
return uploadResponse;
});
}
}
12 changes: 12 additions & 0 deletions src/integration/appland/scannerJob.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Configuration } from '@appland/client';
import { ScanSummary } from 'src/report/scanSummary';

export default interface ScannerJob {
id: number;
created_at: string;
updated_at: string;
mapset_id: number;
merge_key?: string;
summary: ScanSummary;
configuration: Configuration;
}
26 changes: 16 additions & 10 deletions src/integration/appland/upload.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { IncomingMessage } from 'http';
import { URL } from 'url';
import { queue } from 'async';
import { readFile } from 'fs/promises';

import { AppMap as AppMapStruct } from '@appland/models';
import { buildRequest, handleError } from '@appland/client/dist/src';
import { buildRequest, handleError, reportJSON } from '@appland/client/dist/src';

import { ScanResults } from '../../report/scanResults';
import { AppMap as AppMapClient, CreateOptions, UploadAppMapResponse } from './appMap';
import { Mapset as MapsetClient } from './mapset';
import { readFile } from 'fs/promises';
import Location from './location';
import ScannerJob from './scannerJob';
import { URL } from 'url';

type ScanResultsCreateOptions = {
scan_results: ScanResults;
Expand All @@ -17,11 +19,13 @@ type ScanResultsCreateOptions = {
merge_key?: string;
};

export interface UploadResponse extends ScannerJob, Location {}

export default async function (
scanResults: ScanResults,
appId: string,
mergeKey?: string
): Promise<URL> {
): Promise<UploadResponse> {
console.warn(`Uploading AppMaps and findings to application '${appId}'`);

const { findings } = scanResults;
Expand Down Expand Up @@ -97,6 +101,7 @@ export default async function (
const scanResultsData = JSON.stringify(scanResultsOptions);

const request = await buildRequest('api/scanner_jobs');
let uploadURL: URL;
return new Promise<IncomingMessage>((resolve, reject) => {
const req = request.requestFunction(
request.url,
Expand All @@ -115,13 +120,14 @@ export default async function (
req.end();
})
.then(handleError)
.then((response: IncomingMessage): URL => {
let message = `Uploaded ${scanResults.findings.length} findings`;
.then((response) => {
if (response.headers.location) {
const uploadURL = new URL(response.headers.location, request.url.href);
message += ` to ${uploadURL}`;
uploadURL = new URL(response.headers.location, request.url.href);
}
console.log(message);
return request.url;
return reportJSON<UploadResponse>(response);
})
.then((uploadResponse) => {
uploadResponse.url = uploadURL;
return uploadResponse;
});
}

0 comments on commit 49706c2

Please sign in to comment.