Skip to content

Commit

Permalink
feat: download rules bundle
Browse files Browse the repository at this point in the history
  • Loading branch information
YairZ101 committed Jun 26, 2022
1 parent 1be9719 commit c86ebf2
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 12 deletions.
3 changes: 2 additions & 1 deletion src/cli/commands/test/iac/local-execution/types.ts
Expand Up @@ -349,7 +349,8 @@ export enum IaCErrorCodes {

// Rules bundle errors.
InvalidUserRulesBundlePathError = 1130,
BundleNotFoundError = 1131,
FailedToDownloadRulesBundleError = 1131,
FailedToCacheRulesBundleError = 1132,

// Unified Policy Engine executable errors.
InvalidUserPolicyEnginePathError = 1140,
Expand Down
Expand Up @@ -34,7 +34,7 @@ export async function downloadPolicyEngine(
downloadDurationSeconds = Math.round((timer.getValue() as number) / 1000);

debugLog(
`Downladed Policy Engine successfully in ${downloadDurationSeconds} seconds`,
`Downloaded and cached Policy Engine successfully in ${downloadDurationSeconds} seconds`,
);

return cachedPolicyEnginePath;
Expand Down
96 changes: 96 additions & 0 deletions src/lib/iac/test/v2/setup/local-cache/rules-bundle/download.ts
@@ -0,0 +1,96 @@
import * as createDebugLogger from 'debug';
import * as path from 'path';

import { getErrorStringCode } from '../../../../../../../cli/commands/test/iac/local-execution/error-utils';
import { IaCErrorCodes } from '../../../../../../../cli/commands/test/iac/local-execution/types';
import { CustomError } from '../../../../../../errors';
import { TimerMetricInstance } from '../../../../../../metrics';
import { saveFile } from '../../../../../file-utils';
import { TestConfig } from '../../../types';
import { fetchCacheResource } from '../utils';
import { rulesBundleName } from './constants';

const debugLog = createDebugLogger('snyk-iac');

export async function downloadRulesBundle(
testConfig: TestConfig,
): Promise<string> {
let downloadDurationSeconds = 0;

const timer = new TimerMetricInstance('iac_rules_bundle_download');
timer.start();

const dataBuffer = await fetch();

const cachedRulesBundlePath = await cache(
dataBuffer,
testConfig.iacCachePath,
);

timer.stop();
downloadDurationSeconds = Math.round((timer.getValue() as number) / 1000);

debugLog(
`Downloaded and cached rules bundle successfully in ${downloadDurationSeconds} seconds`,
);

return cachedRulesBundlePath;
}

async function fetch(): Promise<Buffer> {
debugLog(`Fetching rules bundle from ${rulesBundleUrl}`);

let rulesBundleDataBuffer: Buffer;
try {
rulesBundleDataBuffer = await fetchCacheResource(rulesBundleUrl);
} catch (err) {
throw new FailedToDownloadRulesBundleError();
}
debugLog('Rules bundle was fetched successfully');

return rulesBundleDataBuffer;
}

export const rulesBundleUrl =
'https://static.snyk.io/cli/wasm/bundle-experimental.tar.gz';

export class FailedToDownloadRulesBundleError extends CustomError {
constructor() {
super(`Failed to download cache resource from ${rulesBundleUrl}`);
this.code = IaCErrorCodes.FailedToDownloadRulesBundleError;
this.strCode = getErrorStringCode(this.code);
this.userMessage =
`Could not fetch cache resource from: ${rulesBundleUrl}` +
'\nEnsure valid network connection.';
}
}

async function cache(
dataBuffer: Buffer,
iacCachePath: string,
): Promise<string> {
const savePath = path.join(iacCachePath, rulesBundleName);

debugLog(`Caching rules bundle to ${savePath}`);

try {
await saveFile(dataBuffer, savePath);
} catch (err) {
throw new FailedToCacheRulesBundleError(savePath);
}

debugLog(`Rules bundle was successfully cached`);

return savePath;
}

export class FailedToCacheRulesBundleError extends CustomError {
constructor(savePath: string) {
super(`Failed to cache rules bundle to ${savePath}`);
this.code = IaCErrorCodes.FailedToCacheRulesBundleError;
this.strCode = getErrorStringCode(this.code);
this.userMessage =
`Could not write the downloaded cache resource to: ${savePath}` +
'\nEnsure the cache directory is writable.';
}
}
14 changes: 4 additions & 10 deletions src/lib/iac/test/v2/setup/local-cache/rules-bundle/index.ts
@@ -1,27 +1,21 @@
import * as createDebugLogger from 'debug';

import { CustomError } from '../../../../../../errors';
import { TestConfig } from '../../../types';
import { downloadRulesBundle } from './download';
import { lookupLocalRulesBundle } from './lookup-local';

const debugLogger = createDebugLogger('snyk-iac');

export async function initRulesBundle(testConfig: TestConfig): Promise<string> {
debugLogger('Looking for rules bundle locally');
const rulesBundlePath = await lookupLocalRulesBundle(testConfig);
let rulesBundlePath = await lookupLocalRulesBundle(testConfig);

if (!rulesBundlePath) {
debugLogger(
`Downloading the rules bundle and saving it at ${testConfig.iacCachePath}`,
);
// Download the rules bundle
rulesBundlePath = await downloadRulesBundle(testConfig);
}

if (rulesBundlePath) {
return rulesBundlePath;
} else {
throw new CustomError(
'Could not find a valid rules bundle in the configured path',
);
}
return rulesBundlePath;
}
@@ -0,0 +1,108 @@
import * as pathLib from 'path';
import * as cloneDeep from 'lodash.clonedeep';

import * as localCacheUtils from '../../../../../../../../../../src/lib/iac/test/v2/setup/local-cache/utils';
import * as fileUtils from '../../../../../../../../../../src/lib/iac/file-utils';
import { TestConfig } from '../../../../../../../../../../src/lib/iac/test/v2/types';
import {
downloadRulesBundle,
FailedToDownloadRulesBundleError,
FailedToCacheRulesBundleError,
rulesBundleUrl,
} from '../../../../../../../../../../src/lib/iac/test/v2/setup/local-cache/rules-bundle/download';

jest.mock(
'../../../../../../../../../../src/lib/iac/test/v2/setup/local-cache/rules-bundle/constants',
() => ({
rulesBundleName: 'test-rules-bundle-file-name',
}),
);

describe('downloadRulesBundle', () => {
const testIacCachePath = pathLib.join('test', 'iac', 'cache', 'path');
const defaultTestTestConfig = {
iacCachePath: testIacCachePath,
};
const testRulesBundleFileName = 'test-rules-bundle-file-name';
const testCachedRulesBundlePath = pathLib.join(
testIacCachePath,
testRulesBundleFileName,
);

afterEach(() => {
jest.restoreAllMocks();
});

it('fetches the rules bundles', async () => {
// Arrange
const testTestConfig: TestConfig = cloneDeep(defaultTestTestConfig);
const testDataBuffer = Buffer.from('test-data-buffer');

const fetchCacheResourceSpy = jest
.spyOn(localCacheUtils, 'fetchCacheResource')
.mockResolvedValue(testDataBuffer);
jest.spyOn(fileUtils, 'saveFile').mockResolvedValue();

// Act
await downloadRulesBundle(testTestConfig);

// Assert
expect(fetchCacheResourceSpy).toHaveBeenCalledWith(rulesBundleUrl);
});

it('caches the fetched cache resource', async () => {
// Arrange
const testTestConfig: TestConfig = cloneDeep(defaultTestTestConfig);
const testDataBuffer = Buffer.from('test-data-buffer');

jest
.spyOn(localCacheUtils, 'fetchCacheResource')
.mockResolvedValue(testDataBuffer);
const saveCacheResourceSpy = jest
.spyOn(fileUtils, 'saveFile')
.mockResolvedValue();

// Act
await downloadRulesBundle(testTestConfig);

// Assert
expect(saveCacheResourceSpy).toHaveBeenCalledWith(
testDataBuffer,
testCachedRulesBundlePath,
);
});

describe('when the rules bundle fails to be fetched', () => {
it('throws an error', async () => {
// Arrange
const testTestConfig: TestConfig = cloneDeep(defaultTestTestConfig);
jest
.spyOn(localCacheUtils, 'fetchCacheResource')
.mockRejectedValue(new Error());
jest.spyOn(fileUtils, 'saveFile').mockResolvedValue();

// Act + Assert
await expect(downloadRulesBundle(testTestConfig)).rejects.toThrow(
FailedToDownloadRulesBundleError,
);
});
});

describe('when the rules bundle fails to be cached', () => {
it('throws an error', async () => {
// Arrange
const testTestConfig: TestConfig = cloneDeep(defaultTestTestConfig);
const testDataBuffer = Buffer.from('test-data-buffer');

jest
.spyOn(localCacheUtils, 'fetchCacheResource')
.mockResolvedValue(testDataBuffer);
jest.spyOn(fileUtils, 'saveFile').mockRejectedValue(new Error());

// Act + Assert
await expect(downloadRulesBundle(testTestConfig)).rejects.toThrow(
FailedToCacheRulesBundleError,
);
});
});
});

0 comments on commit c86ebf2

Please sign in to comment.