Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
211 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
96 changes: 96 additions & 0 deletions
96
src/lib/iac/test/v2/setup/local-cache/rules-bundle/download.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
14
src/lib/iac/test/v2/setup/local-cache/rules-bundle/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
108 changes: 108 additions & 0 deletions
108
test/jest/unit/lib/iac/test/v2/setup/local-cache/rules-bundle/download.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, | ||
); | ||
}); | ||
}); | ||
}); |