Skip to content

Commit

Permalink
Merge pull request #1895 from snyk/feat/add-error-string-code-analytics
Browse files Browse the repository at this point in the history
chore: add the error string code to analytics [CC-786]
  • Loading branch information
teodora-sandu authored May 11, 2021
2 parents 15de5f8 + 5dc0140 commit 93624dc
Show file tree
Hide file tree
Showing 17 changed files with 61 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CustomError } from '../../../../lib/errors';
import { args } from '../../../args';
import { getErrorStringCode } from './error-utils';
import { IaCErrorCodes, IaCTestFlags, TerraformPlanScanMode } from './types';

const keys: (keyof IaCTestFlags)[] = [
Expand Down Expand Up @@ -38,6 +39,7 @@ class FlagError extends CustomError {
const msg = `Unsupported flag "${flag}" provided. Run snyk iac test --help for supported flags.`;
super(msg);
this.code = IaCErrorCodes.FlagError;
this.strCode = getErrorStringCode(this.code);
this.userMessage = msg;
}
}
Expand All @@ -50,6 +52,7 @@ export class FlagValueError extends CustomError {
)}`;
super(msg);
this.code = IaCErrorCodes.FlagValueError;
this.strCode = getErrorStringCode(this.code);
this.userMessage = msg;
}
}
Expand Down
13 changes: 13 additions & 0 deletions src/cli/commands/test/iac-local-execution/error-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { IaCErrorCodes } from './types';

export function getErrorStringCode(code: number): string {
const errorName = IaCErrorCodes[code];
if (!errorName) {
return 'INVALID_IAC_ERROR';
}
let result = errorName.replace(/([A-Z])/g, '_$1');
if (result.charAt(0) === '_') {
result = result.substring(1);
}
return result.toUpperCase();
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { UnsupportedFileTypeError } from './file-parser';
import * as analytics from '../../../../lib/analytics';
import * as Debug from 'debug';
import { getErrorStringCode } from './error-utils';
const debug = Debug('iac-extract-line-number');

function getFileTypeForLineNumber(fileType: string): CloudConfigFileTypes {
Expand Down Expand Up @@ -47,6 +48,7 @@ class FailedToExtractLineNumberError extends CustomError {
message || 'Parser library failed. Could not assign lineNumber to issue',
);
this.code = IaCErrorCodes.FailedToExtractLineNumberError;
this.strCode = getErrorStringCode(this.code);
this.userMessage = ''; // Not a user facing error.
}
}
3 changes: 3 additions & 0 deletions src/cli/commands/test/iac-local-execution/file-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getFileType } from '../../../../lib/iac/iac-parser';
import { IacFileTypes } from '../../../../lib/iac/constants';
import { isLocalFolder } from '../../../../lib/detect';
import { CustomError } from '../../../../lib/errors';
import { getErrorStringCode } from './error-utils';

const DEFAULT_ENCODING = 'utf-8';

Expand Down Expand Up @@ -82,6 +83,7 @@ export class NoFilesToScanError extends CustomError {
constructor(message?: string) {
super(message || 'Could not find any valid IaC files');
this.code = IaCErrorCodes.NoFilesToScanError;
this.strCode = getErrorStringCode(this.code);
this.userMessage =
'Could not find any valid infrastructure as code files. Supported file extensions are tf, yml, yaml & json.\nMore information can be found by running `snyk iac test --help` or through our documentation:\nhttps://support.snyk.io/hc/en-us/articles/360012429477-Test-your-Kubernetes-files-with-our-CLI-tool\nhttps://support.snyk.io/hc/en-us/articles/360013723877-Test-your-Terraform-files-with-our-CLI-tool';
}
Expand All @@ -90,6 +92,7 @@ export class FailedToLoadFileError extends CustomError {
constructor(filename: string) {
super('Failed to load file content');
this.code = IaCErrorCodes.FailedToLoadFileError;
this.strCode = getErrorStringCode(this.code);
this.userMessage = `We were unable to read file "${filename}" for scanning. Please ensure that it is readable.`;
}
}
5 changes: 5 additions & 0 deletions src/cli/commands/test/iac-local-execution/file-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from './types';
import * as analytics from '../../../../lib/analytics';
import { CustomError } from '../../../../lib/errors';
import { getErrorStringCode } from './error-utils';

export async function parseFiles(
filesData: IacFileData[],
Expand Down Expand Up @@ -121,6 +122,7 @@ export class UnsupportedFileTypeError extends CustomError {
constructor(fileType: string) {
super('Unsupported file extension');
this.code = IaCErrorCodes.UnsupportedFileTypeError;
this.strCode = getErrorStringCode(this.code);
this.userMessage = `Unable to process the file with extension ${fileType}. Supported file extensions are tf, yml, yaml & json.\nMore information can be found by running \`snyk iac test --help\` or through our documentation:\nhttps://support.snyk.io/hc/en-us/articles/360012429477-Test-your-Kubernetes-files-with-our-CLI-tool\nhttps://support.snyk.io/hc/en-us/articles/360013723877-Test-your-Terraform-files-with-our-CLI-tool`;
}
}
Expand All @@ -129,6 +131,7 @@ export class InvalidJsonFileError extends CustomError {
constructor(filename: string) {
super('Failed to parse JSON file');
this.code = IaCErrorCodes.InvalidJsonFileError;
this.strCode = getErrorStringCode(this.code);
this.userMessage = `We were unable to parse the JSON file "${filename}". Please ensure that it contains properly structured JSON`;
}
}
Expand All @@ -137,6 +140,7 @@ export class InvalidYamlFileError extends CustomError {
constructor(filename: string) {
super('Failed to parse YAML file');
this.code = IaCErrorCodes.InvalidYamlFileError;
this.strCode = getErrorStringCode(this.code);
this.userMessage = `We were unable to parse the YAML file "${filename}". Please ensure that it contains properly structured YAML`;
}
}
Expand All @@ -147,6 +151,7 @@ export class FailedToDetectJsonFileError extends CustomError {
'Failed to detect either a Kubernetes file or Terraform Plan, missing required fields',
);
this.code = IaCErrorCodes.FailedToDetectJsonFileError;
this.strCode = getErrorStringCode(this.code);
this.userMessage = `We were unable to detect whether the JSON file "${filename}" is a valid Kubernetes file or Terraform Plan. For Kubernetes it is missing the following fields: "${REQUIRED_K8S_FIELDS.join(
'", "',
)}". For Terraform Plan it was expected to contain fields "planned_values.root_module" and "resource_changes". Please contact support@snyk.io, if possible with a redacted version of the file`;
Expand Down
3 changes: 3 additions & 0 deletions src/cli/commands/test/iac-local-execution/file-scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { loadPolicy } from '@open-policy-agent/opa-wasm';
import * as fs from 'fs';
import { getLocalCachePath } from './local-cache';
import { CustomError } from '../../../../lib/errors';
import { getErrorStringCode } from './error-utils';

export async function scanFiles(
parsedFiles: Array<IacFileParsed>,
Expand Down Expand Up @@ -99,6 +100,7 @@ export class FailedToBuildPolicyEngine extends CustomError {
constructor(message?: string) {
super(message || 'Failed to build policy engine');
this.code = IaCErrorCodes.FailedToBuildPolicyEngine;
this.strCode = getErrorStringCode(this.code);
this.userMessage =
'We were unable run the test. Please run the command again with the `-d` flag and contact support@snyk.io with the contents of the output.';
}
Expand All @@ -107,6 +109,7 @@ export class FailedToExecutePolicyEngine extends CustomError {
constructor(message?: string) {
super(message || 'Failed to execute policy engine');
this.code = IaCErrorCodes.FailedToExecutePolicyEngine;
this.strCode = getErrorStringCode(this.code);
this.userMessage =
'We were unable run the test. Please run the command again with the `-d` flag and contact support@snyk.io with the contents of the output.';
}
Expand Down
3 changes: 3 additions & 0 deletions src/cli/commands/test/iac-local-execution/local-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as Debug from 'debug';
import { CustomError } from '../../../../lib/errors';
import * as analytics from '../../../../lib/analytics';
import ReadableStream = NodeJS.ReadableStream;
import { getErrorStringCode } from './error-utils';

const debug = Debug('iac-local-cache');

Expand Down Expand Up @@ -74,6 +75,7 @@ export class FailedToInitLocalCacheError extends CustomError {
constructor(message?: string) {
super(message || 'Failed to initialize local cache');
this.code = IaCErrorCodes.FailedToInitLocalCacheError;
this.strCode = getErrorStringCode(this.code);
this.userMessage =
'We were unable to create a local directory to store the test assets, please ensure that the current working directory is writable';
}
Expand All @@ -83,6 +85,7 @@ class FailedToCleanLocalCacheError extends CustomError {
constructor(message?: string) {
super(message || 'Failed to clean local cache');
this.code = IaCErrorCodes.FailedToCleanLocalCacheError;
this.strCode = getErrorStringCode(this.code);
this.userMessage = ''; // Not a user facing error.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { isCI } from '../../../../../lib/is-ci';
import { api } from '../../../../../lib/api-token';
import request = require('../../../../../lib/request');
import { CustomError } from '../../../../../lib/errors';
import { getErrorStringCode } from '../error-utils';

export function getIacOrgSettings(): Promise<IacOrgSettings> {
const payload: Payload = {
Expand Down Expand Up @@ -34,6 +35,7 @@ export class FailedToGetIacOrgSettingsError extends CustomError {
constructor(message?: string) {
super(message || 'Failed to fetch IaC organization settings');
this.code = IaCErrorCodes.FailedToGetIacOrgSettingsError;
this.strCode = getErrorStringCode(this.code);
this.userMessage =
'We failed to fetch your organization settings, including custom severity overrides for infrastructure-as-code policies. Please run the command again with the `-d` flag and contact support@snyk.io with the contents of the output.';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CustomError } from '../../../../../lib/errors';
import { getErrorStringCode } from '../error-utils';
import {
EngineType,
IaCErrorCodes,
Expand Down Expand Up @@ -47,6 +48,7 @@ export class HelmFileNotSupportedError extends CustomError {
constructor(filename: string) {
super('Failed to parse Helm file');
this.code = IaCErrorCodes.FailedToParseHelmError;
this.strCode = getErrorStringCode(this.code);
this.userMessage = `We were unable to parse the YAML file "${filename}" as we currently do not support scanning of Helm files. More information can be found through our documentation:\nhttps://support.snyk.io/hc/en-us/articles/360012429477-Test-your-Kubernetes-files-with-our-CLI-tool`;
}
}
Expand All @@ -55,6 +57,7 @@ export class MissingRequiredFieldsInKubernetesYamlError extends CustomError {
constructor(filename: string) {
super('Failed to detect Kubernetes file, missing required fields');
this.code = IaCErrorCodes.MissingRequiredFieldsInKubernetesYamlError;
this.strCode = getErrorStringCode(this.code);
this.userMessage = `We were unable to detect whether the YAML file "${filename}" is a valid Kubernetes file, it is missing the following fields: "${REQUIRED_K8S_FIELDS.join(
'", "',
)}"`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
IacFileParsed,
} from '../types';
import { CustomError } from '../../../../../lib/errors';
import { getErrorStringCode } from '../error-utils';

export function tryParsingTerraformFile(
fileData: IacFileData,
Expand All @@ -27,6 +28,7 @@ export class FailedToParseTerraformFileError extends CustomError {
constructor(filename: string) {
super('Failed to parse Terraform file');
this.code = IaCErrorCodes.FailedToParseTerraformFileError;
this.strCode = getErrorStringCode(this.code);
this.userMessage = `We were unable to parse the Terraform file "${filename}", please ensure it is valid HCL2. This can be done by running it through the 'terraform validate' command.`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
IaCErrorCodes,
} from '../types';
import { CustomError } from '../../../../../lib/errors';
import { getErrorStringCode } from '../error-utils';

function terraformPlanReducer(
scanInput: TerraformScanInput,
Expand Down Expand Up @@ -116,6 +117,7 @@ export class FailedToExtractResourcesInTerraformPlanError extends CustomError {
message || 'Failed to extract resources from Terraform plan JSON file',
);
this.code = IaCErrorCodes.FailedToExtractResourcesInTerraformPlanError;
this.strCode = getErrorStringCode(this.code);
this.userMessage =
'We failed to extract resource changes from the Terraform plan file, please contact support@snyk.io, if possible with a redacted version of the file';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '../../../../lib/iac/constants';
import { CustomError } from '../../../../lib/errors';
import { extractLineNumber } from './extract-line-number';
import { getErrorStringCode } from './error-utils';

const SEVERITIES = [SEVERITY.LOW, SEVERITY.MEDIUM, SEVERITY.HIGH];

Expand Down Expand Up @@ -145,6 +146,7 @@ export class FailedToFormatResults extends CustomError {
constructor(message?: string) {
super(message || 'Failed to format results');
this.code = IaCErrorCodes.FailedToFormatResults;
this.strCode = getErrorStringCode(this.code);
this.userMessage =
'We failed printing the results, please contact support@snyk.io';
}
Expand Down
1 change: 1 addition & 0 deletions src/cli/commands/test/iac-local-execution/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ export const VALID_RESOURCE_ACTIONS_FOR_FULL_SCAN: ResourceActions[] = [
];

// Error codes used for Analytics & Debugging
// Error names get converted to error string codes
// Within a single module, increments are in 1.
// Between modules, increments are in 10, according to the order of execution.
export enum IaCErrorCodes {
Expand Down
1 change: 1 addition & 0 deletions src/cli/commands/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ async function test(...args: MethodArgs): Promise<TestCommandResult> {
// first one
error.code = errorResults[0].code;
error.userMessage = errorResults[0].userMessage;
error.strCode = errorResults[0].strCode;
throw error;
}

Expand Down
1 change: 1 addition & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ async function handleError(args, error) {
// (see https://nodejs.org/api/errors.html#errors_error_stack)
analytics.add('error', analyticsError.stack);
analytics.add('error-code', error.code);
analytics.add('error-str-code', error.strCode);
analytics.add('command', args.command);
}

Expand Down
1 change: 1 addition & 0 deletions src/lib/errors/custom-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export class CustomError extends Error {
Error.captureStackTrace(this, this.constructor);
this.name = this.constructor.name;
this.code = undefined;
this.strCode = undefined;
this.innerError = undefined;
this.userMessage = undefined;
}
Expand Down
14 changes: 14 additions & 0 deletions test/jest/unit/iac-unit-tests/error-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { getErrorStringCode } from '../../../../src/cli/commands/test/iac-local-execution/error-utils';
import { IaCErrorCodes } from '../../../../src/cli/commands/test/iac-local-execution/types';

describe('getErrorStringCode', () => {
it('converts invalid IaCErrorCodes error to INVALID_IAC_ERROR', () => {
expect(getErrorStringCode(10)).toEqual('INVALID_IAC_ERROR');
});

it('converts IaCErrorCodes error to UPPER_CASE error string codes', () => {
expect(
getErrorStringCode(IaCErrorCodes.FailedToInitLocalCacheError),
).toEqual('FAILED_TO_INIT_LOCAL_CACHE_ERROR');
});
});

0 comments on commit 93624dc

Please sign in to comment.