Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CI API client - introduce configurable max polling attempts #16

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/commands/scan.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type TScanUserCliOptions = {
failOnIacScan?: boolean;
minimumSeverityLevel?: string;
pollInterval?: number;
maxPollingAttempts?: number;
};
declare function cli(repoId: string, baseCommitId: string, headCommitId: string, branchName: string, options: TScanUserCliOptions, command: string): Promise<void>;
export declare const scan: ({ repoId, baseCommitId, headCommitId, branchName, options, pollInterval, onStart, onStartComplete, onStartFail, onScanStart, onNextPoll, onScanComplete, onScanFail, }: TScanArguments) => Promise<void>;
Expand Down
28 changes: 26 additions & 2 deletions lib/commands/scan.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ async function cli(repoId, baseCommitId, headCommitId, branchName, options, comm
loader?.succeed(`Aikido Security scan started (id: ${startResult.scan_id})`);
};
const onNextPoll = () => {
if (pollCount > cliOptions.maxPollingAttempts) {
onPollTimeout();
return;
}
if (loader) {
loader.text = `Polling for Aikido Security scan to complete (${pollCount})`;
}
Expand Down Expand Up @@ -59,6 +63,11 @@ async function cli(repoId, baseCommitId, headCommitId, branchName, options, comm
}
process.exit(1);
};
const onPollTimeout = () => {
loader?.fail();
outputError(`Polling exceeded ${cliOptions.maxPollingAttempts} attempts. Unable to get scan results.`);
process.exit(1);
};
await scan({
repoId,
baseCommitId,
Expand Down Expand Up @@ -119,7 +128,7 @@ export const scan = async ({ repoId, baseCommitId, headCommitId, branchName, opt
};
const parseCliOptions = (userCliOptions) => {
const apiOptions = { version: '1.0.5' };
const cliOptions = { pollInterval: 5 };
const cliOptions = { pollInterval: 5, maxPollingAttempts: 75 };
if (userCliOptions.pullRequestTitle) {
apiOptions.pull_request_metadata = {
...(apiOptions.pull_request_metadata ?? {}),
Expand Down Expand Up @@ -157,6 +166,9 @@ const parseCliOptions = (userCliOptions) => {
else if (userCliOptions.pollInterval) {
cliOptions.pollInterval = userCliOptions.pollInterval;
}
if (userCliOptions.maxPollingAttempts) {
cliOptions.maxPollingAttempts = userCliOptions.maxPollingAttempts;
}
return { apiOptions, cliOptions };
};
const validateCommitId = (value) => {
Expand Down Expand Up @@ -185,11 +197,23 @@ export const cliSetup = (program) => program
.addOption(new Option('--no-fail-on-dependency-scan', "Don't fail when scanning depedencies..."))
.option('--fail-on-sast-scan', 'Let Aikido fail when new static code analysis issues have been detected...')
.option('--fail-on-iac-scan', 'Let Aikido fail when new infrastructure as code issues have been detected...')
.option('--fail-on-secrets-scan', 'Let Aikido fail when new leaked secrets have been detected...')
.option('--fail-on-secrets-scan', 'Let Aikido fail when new exposed secrets have been detected...')
.addOption(new Option('--minimum-severity-level <level>', 'Set the minimum severity level. Accepted options are: LOW, MEDIUM, HIGH and CRITICAL.').choices(['LOW', 'MEDIUM', 'HIGH', 'CRITICAL']))
.addOption(new Option('--poll-interval [interval]', 'The poll interval when checking for an updated scan result')
.preset(5)
.argParser(parseFloat))
.addOption(new Option('--max-polling-attempts <amount>', 'Amount of times to poll for scan results.')
.preset(75)
.argParser(value => {
const parsedValue = parseInt(value, 10);
if (isNaN(parsedValue)) {
throw new InvalidArgumentError('Not a number.');
}
if (parsedValue > 100) {
throw new InvalidArgumentError('Cannot be more than 300.');
}
return parsedValue;
}))
.description('Run a scan of an Aikido repo.')
.action(cli);
export default { cli, cliSetup, scan };
36 changes: 35 additions & 1 deletion src/commands/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type TScanArguments = {

type TScanCliOptions = {
pollInterval: number;
maxPollingAttempts: number;
};

type TScanUserCliOptions = {
Expand All @@ -63,6 +64,7 @@ type TScanUserCliOptions = {
failOnIacScan?: boolean;
minimumSeverityLevel?: string;
pollInterval?: number;
maxPollingAttempts?: number
};

async function cli(
Expand Down Expand Up @@ -97,6 +99,12 @@ async function cli(
};

const onNextPoll = () => {

if(pollCount > cliOptions.maxPollingAttempts) {
onPollTimeout()
return
}

if (loader) {
loader.text = `Polling for Aikido Security scan to complete (${pollCount})`;
}
Expand Down Expand Up @@ -157,6 +165,12 @@ async function cli(
process.exit(1);
};

const onPollTimeout = () => {
loader?.fail();
outputError(`Polling exceeded ${cliOptions.maxPollingAttempts} attempts. Unable to get scan results.`);
process.exit(1);
};

await scan({
repoId,
baseCommitId,
Expand Down Expand Up @@ -246,7 +260,7 @@ const parseCliOptions = (userCliOptions: TScanUserCliOptions) => {
// Version provided to the API corresponds with the version in package.json
// of the cli client
const apiOptions: TScanApiOptions = { version: '1.0.5' };
const cliOptions: TScanCliOptions = { pollInterval: 5 };
const cliOptions: TScanCliOptions = { pollInterval: 5, maxPollingAttempts: 75 };

if (userCliOptions.pullRequestTitle) {
apiOptions.pull_request_metadata = {
Expand Down Expand Up @@ -286,6 +300,9 @@ const parseCliOptions = (userCliOptions: TScanUserCliOptions) => {
} else if (userCliOptions.pollInterval) {
cliOptions.pollInterval = userCliOptions.pollInterval;
}
if(userCliOptions.maxPollingAttempts) {
cliOptions.maxPollingAttempts = userCliOptions.maxPollingAttempts
}

return { apiOptions, cliOptions };
};
Expand Down Expand Up @@ -375,6 +392,23 @@ export const cliSetup = (program: Command) =>
.preset(5)
.argParser(parseFloat)
)
.addOption(
new Option(
'--max-polling-attempts <amount>',
'Amount of times to poll for scan results.'
)
.preset(75) // around 6 minutes by default with 5 sec poll interval
.argParser(value => {
const parsedValue = parseInt(value, 10);
if (isNaN(parsedValue)) {
throw new InvalidArgumentError('Not a number.');
}
if(parsedValue > 100) {
throw new InvalidArgumentError('Cannot be more than 300.');
}
return parsedValue;
})
)
.description('Run a scan of an Aikido repo.')
.action(cli);

Expand Down