Skip to content

Commit

Permalink
Merge pull request #4190 from snyk/feat/improve_cloudcontext_errors
Browse files Browse the repository at this point in the history
[CTX-442] improve errors for cloud context
  • Loading branch information
Martin committed Nov 3, 2022
2 parents 221523d + 0ddc517 commit 8060d7e
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { CustomError } from '../../../../../lib/errors';
import { args } from '../../../../args';
import { getErrorStringCode } from './error-utils';
import { IaCErrorCodes, IaCTestFlags, TerraformPlanScanMode } from './types';
import {
IaCErrorCodes,
IacOrgSettings,
IaCTestFlags,
TerraformPlanScanMode,
} from './types';
import { Options, TestOptions } from '../../../../../lib/types';

const keys: (keyof IaCTestFlags)[] = [
Expand Down Expand Up @@ -38,7 +43,13 @@ const keys: (keyof IaCTestFlags)[] = [
'remote-repo-url',
'target-name',
];
const integratedKeys: (keyof IaCTestFlags)[] = [
'snyk-cloud-environment',
'cloud-context',
];

const allowed = new Set<string>(keys);
const integratedOnlyFlags = new Set<string>(integratedKeys);

function camelcaseToDash(key: string) {
return key.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
Expand All @@ -61,6 +72,17 @@ export class FlagError extends CustomError {
}
}

export class IntegratedFlagError extends CustomError {
constructor(key: string, org: string) {
const flag = getFlagName(key);
const msg = `Flag "${flag}" is only supported when using Integrated IaC. To enable it for your organisation "${org}", please contact Snyk support.`;
super(msg);
this.code = IaCErrorCodes.FlagError;
this.strCode = getErrorStringCode(this.code);
this.userMessage = msg;
}
}

export class FeatureFlagError extends CustomError {
constructor(key: string, featureFlag: string, hasSnykPreview?: boolean) {
const flag = getFlagName(key);
Expand Down Expand Up @@ -139,6 +161,28 @@ export function assertIaCOptionsFlags(argv: string[]): void {
}
}

/**
* Check that the flags used for the v1 flow do not contain any flag that are
* only usable with the new integrated iac (v2) flow
* @param settings organisation settings, used to get the org name
* @param argv command line args
*/
export function assertIntegratedIaCOnlyOptions(
settings: IacOrgSettings,
argv: string[],
): void {
// We process the process.argv so we don't get default values.
const parsed = args(argv);
for (const key of Object.keys(parsed.options)) {
// The _ property is a special case that contains non
// flag strings passed to the command line (usually files)
// and `iac` is the command provided.
if (key !== '_' && key !== 'iac' && integratedOnlyFlags.has(key)) {
throw new IntegratedFlagError(key, settings.meta.org);
}
}
}

const SUPPORTED_TF_PLAN_SCAN_MODES = [
TerraformPlanScanMode.DeltaScan,
TerraformPlanScanMode.FullScan,
Expand Down
6 changes: 5 additions & 1 deletion src/cli/commands/test/iac/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import * as utils from '../utils';
import { spinnerMessage } from '../../../../lib/formatters/iac-output/text';

import { test as iacTest } from './local-execution';
import { assertIaCOptionsFlags } from './local-execution/assert-iac-options-flag';
import {
assertIaCOptionsFlags,
assertIntegratedIaCOnlyOptions,
} from './local-execution/assert-iac-options-flag';
import { initRules } from './local-execution/rules/rules';
import { cleanLocalCache } from './local-execution/measurable-methods';
import * as ora from 'ora';
Expand Down Expand Up @@ -79,6 +82,7 @@ export async function scan(

let res: (TestResult | TestResult[]) | Error;
try {
assertIntegratedIaCOnlyOptions(iacOrgSettings, process.argv);
assertIaCOptionsFlags(process.argv);

if (pathLib.relative(projectRoot, path).includes('..')) {
Expand Down
16 changes: 10 additions & 6 deletions src/lib/iac/test/v2/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,26 @@ const snykIacTestErrorsUserMessages = {
UnableToReadPath: 'Unable to read path',
NoLoadableInput:
"The Snyk CLI couldn't find any valid IaC configuration files to scan",
FailedToMakeResourcesResolvers:
'An error occurred preparing the requested cloud context. Please run the command again with the `-d` flag for more information.',
ResourcesResolverError:
'An error occurred scanning cloud resources. Please run the command again with the `-d` flag for more information.',
FailedToProcessResults:
'An error occurred while processing results. Please run the command again with the `-d` flag for more information.',
};

export function getErrorUserMessage(code: number): string {
export function getErrorUserMessage(code: number, error: string): string {
if (code < 2000 || code >= 3000) {
return 'INVALID_SNYK_IAC_TEST_ERROR';
}
const errorName = IaCErrorCodes[code];
if (!errorName) {
return 'INVALID_IAC_ERROR';
}

if (
code == IaCErrorCodes.FailedToMakeResourcesResolvers ||
code == IaCErrorCodes.ResourcesResolverError
) {
return `${error}. Please run the command again with the \`-d\` flag for more information.`;
}

return snykIacTestErrorsUserMessages[errorName];
}

Expand All @@ -56,7 +60,7 @@ export class SnykIacTestError extends CustomError {
super(scanError.message);
this.code = scanError.code;
this.strCode = getErrorStringCode(this.code);
this.userMessage = getErrorUserMessage(this.code);
this.userMessage = getErrorUserMessage(this.code, scanError.message);
this.fields = Object.assign(
{
path: '',
Expand Down
51 changes: 51 additions & 0 deletions test/jest/unit/iac/assert-iac-options-flag.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,58 @@
import {
assertIaCOptionsFlags,
assertIntegratedIaCOnlyOptions,
FlagValueError,
IntegratedFlagError,
} from '../../../../src/cli/commands/test/iac/local-execution/assert-iac-options-flag';
import { IacOrgSettings } from '../../../../src/cli/commands/test/iac/local-execution/types';

describe('assertIntegratedIaCOnlyOptions()', () => {
const command = ['node', 'cli', 'iac', 'test'];
const files = ['input.tf'];
const org: IacOrgSettings = {
customPolicies: {},
meta: {
org: 'orgname',
orgPublicId: 'orgpublicid',
},
};

it('accepts all command line flags accepted by the iac command', () => {
const options = [
'--debug',
'--insecure',
'--detection-depth',
'--severity-threshold',
'--json',
'--sarif',
'--json-file-output',
'--sarif-file-output',
'-v',
'--version',
'-h',
'--help',
'-q',
'--quiet',
];
expect(() =>
assertIntegratedIaCOnlyOptions(org, [...command, ...options, ...files]),
).not.toThrow();
});

it('Refuses cloud-context flag', () => {
const options = ['--cloud-context', 'aws'];
expect(() =>
assertIntegratedIaCOnlyOptions(org, [...command, ...options, ...files]),
).toThrow(new IntegratedFlagError('cloud-context', org.meta.org));
});

it('Refuses snyk-cloud-environment flag', () => {
const options = ['--snyk-cloud-environment', 'envid'];
expect(() =>
assertIntegratedIaCOnlyOptions(org, [...command, ...options, ...files]),
).toThrow(new IntegratedFlagError('snyk-cloud-environment', org.meta.org));
});
});

describe('assertIaCOptionsFlags()', () => {
const command = ['node', 'cli', 'iac', 'test'];
Expand Down
10 changes: 6 additions & 4 deletions test/jest/unit/lib/iac/test/v2/errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { getErrorUserMessage } from '../../../../../../../src/lib/iac/test/v2/er

describe('getErrorUserMessage', () => {
it('returns INVALID_SNYK_IAC_TEST_ERROR for an invalid snyk-iac-test error code', () => {
expect(getErrorUserMessage(0)).toEqual('INVALID_SNYK_IAC_TEST_ERROR');
expect(getErrorUserMessage(3000)).toEqual('INVALID_SNYK_IAC_TEST_ERROR');
expect(getErrorUserMessage(0, '')).toEqual('INVALID_SNYK_IAC_TEST_ERROR');
expect(getErrorUserMessage(3000, '')).toEqual(
'INVALID_SNYK_IAC_TEST_ERROR',
);
});

it('returns INVALID_IAC_ERROR for an invalid error code', () => {
expect(getErrorUserMessage(2999)).toEqual('INVALID_IAC_ERROR');
expect(getErrorUserMessage(2999, '')).toEqual('INVALID_IAC_ERROR');
});

it.each`
Expand All @@ -34,7 +36,7 @@ describe('getErrorUserMessage', () => {
`(
'returns a user message for a valid snyk-iac-test error code - $expectedErrorCode',
({ expectedErrorCode }) => {
expect(typeof getErrorUserMessage(expectedErrorCode)).toBe('string');
expect(typeof getErrorUserMessage(expectedErrorCode, '')).toBe('string');
},
);
});

0 comments on commit 8060d7e

Please sign in to comment.