From 485b6f9104f73f88a50fb342d7de52c1bf996e01 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 5 Jun 2020 23:11:40 +0200 Subject: [PATCH] chore(s3-assets): use jest for tests No new tests or expectations added. --- packages/@aws-cdk/aws-s3-assets/.gitignore | 1 + packages/@aws-cdk/aws-s3-assets/.npmignore | 1 + .../@aws-cdk/aws-s3-assets/jest.config.js | 2 + packages/@aws-cdk/aws-s3-assets/package.json | 14 +- .../@aws-cdk/aws-s3-assets/test/asset.test.ts | 328 ++++++++++++++++ .../@aws-cdk/aws-s3-assets/test/test.asset.ts | 355 ------------------ 6 files changed, 336 insertions(+), 365 deletions(-) create mode 100644 packages/@aws-cdk/aws-s3-assets/jest.config.js create mode 100644 packages/@aws-cdk/aws-s3-assets/test/asset.test.ts delete mode 100644 packages/@aws-cdk/aws-s3-assets/test/test.asset.ts diff --git a/packages/@aws-cdk/aws-s3-assets/.gitignore b/packages/@aws-cdk/aws-s3-assets/.gitignore index 84107ada8a317..743b39099999a 100644 --- a/packages/@aws-cdk/aws-s3-assets/.gitignore +++ b/packages/@aws-cdk/aws-s3-assets/.gitignore @@ -15,3 +15,4 @@ nyc.config.js .LAST_PACKAGE *.snk !.eslintrc.js +!jest.config.js diff --git a/packages/@aws-cdk/aws-s3-assets/.npmignore b/packages/@aws-cdk/aws-s3-assets/.npmignore index 174864d493a79..4e4f173de8358 100644 --- a/packages/@aws-cdk/aws-s3-assets/.npmignore +++ b/packages/@aws-cdk/aws-s3-assets/.npmignore @@ -19,3 +19,4 @@ dist tsconfig.json .eslintrc.js +jest.config.js diff --git a/packages/@aws-cdk/aws-s3-assets/jest.config.js b/packages/@aws-cdk/aws-s3-assets/jest.config.js new file mode 100644 index 0000000000000..cd664e1d069e5 --- /dev/null +++ b/packages/@aws-cdk/aws-s3-assets/jest.config.js @@ -0,0 +1,2 @@ +const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config'); +module.exports = baseConfig; diff --git a/packages/@aws-cdk/aws-s3-assets/package.json b/packages/@aws-cdk/aws-s3-assets/package.json index 3aea5a8f58626..3b8fe5bdebded 100644 --- a/packages/@aws-cdk/aws-s3-assets/package.json +++ b/packages/@aws-cdk/aws-s3-assets/package.json @@ -45,6 +45,9 @@ "build+test": "npm run build && npm test", "compat": "cdk-compat" }, + "cdk-build": { + "jest": true + }, "keywords": [ "aws", "cdk", @@ -60,16 +63,10 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/assert": "0.0.0", - "@types/nodeunit": "^0.0.31", - "@types/sinon": "^9.0.4", - "aws-cdk": "0.0.0", "cdk-build-tools": "0.0.0", "cdk-integ-tools": "0.0.0", - "nodeunit": "^0.11.3", "pkglint": "0.0.0", - "sinon": "^9.0.2", - "@aws-cdk/cloud-assembly-schema": "0.0.0", - "ts-mock-imports": "^1.3.0" + "@aws-cdk/cloud-assembly-schema": "0.0.0" }, "dependencies": { "@aws-cdk/assets": "0.0.0", @@ -93,9 +90,6 @@ }, "stability": "experimental", "maturity": "experimental", - "nyc": { - "statements": 75 - }, "awslint": { "exclude": [ "docs-public-apis:@aws-cdk/aws-s3-assets.AssetOptions", diff --git a/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts b/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts new file mode 100644 index 0000000000000..4da45143c59f8 --- /dev/null +++ b/packages/@aws-cdk/aws-s3-assets/test/asset.test.ts @@ -0,0 +1,328 @@ +import { ResourcePart, SynthUtils } from '@aws-cdk/assert'; +import '@aws-cdk/assert/jest'; +import * as iam from '@aws-cdk/aws-iam'; +import * as cxschema from '@aws-cdk/cloud-assembly-schema'; +import * as cdk from '@aws-cdk/core'; +import * as cxapi from '@aws-cdk/cx-api'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import { Asset } from '../lib/asset'; + +const SAMPLE_ASSET_DIR = path.join(__dirname, 'sample-asset-directory'); + +test('simple use case', () => { + const app = new cdk.App({ + context: { + [cxapi.DISABLE_ASSET_STAGING_CONTEXT]: 'true', + }, + }); + const stack = new cdk.Stack(app, 'MyStack'); + new Asset(stack, 'MyAsset', { + path: SAMPLE_ASSET_DIR, + }); + + // verify that metadata contains an "aws:cdk:asset" entry with + // the correct information + const entry = stack.node.metadata.find(m => m.type === 'aws:cdk:asset'); + expect(entry).toBeTruthy(); + + // verify that now the template contains parameters for this asset + const session = app.synth(); + + expect(stack.resolve(entry!.data)).toEqual({ + path: SAMPLE_ASSET_DIR, + id: '6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', + packaging: 'zip', + sourceHash: '6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', + s3BucketParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B', + s3KeyParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3VersionKey1F7D75F9', + artifactHashParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2ArtifactHash220DE9BD', + }); + + const template = JSON.parse(fs.readFileSync(path.join(session.directory, 'MyStack.template.json'), 'utf-8')); + + expect(template.Parameters.AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B.Type).toBe('String'); + expect(template.Parameters.AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3VersionKey1F7D75F9.Type).toBe('String'); +}); + +test('verify that the app resolves tokens in metadata', () => { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'my-stack'); + const dirPath = path.resolve(__dirname, 'sample-asset-directory'); + + new Asset(stack, 'MyAsset', { + path: dirPath, + }); + + const synth = app.synth().getStackByName(stack.stackName); + const meta = synth.manifest.metadata || {}; + expect(meta['/my-stack']).toBeTruthy(); + expect(meta['/my-stack'][0]).toBeTruthy(); + expect(meta['/my-stack'][0].data).toEqual({ + path: 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', + id: '6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', + packaging: 'zip', + sourceHash: '6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', + s3BucketParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B', + s3KeyParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3VersionKey1F7D75F9', + artifactHashParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2ArtifactHash220DE9BD', + }); +}); + +test('"file" assets', () => { + const stack = new cdk.Stack(); + const filePath = path.join(__dirname, 'file-asset.txt'); + new Asset(stack, 'MyAsset', { path: filePath }); + const entry = stack.node.metadata.find(m => m.type === 'aws:cdk:asset'); + expect(entry).toBeTruthy(); + + // synthesize first so "prepare" is called + const template = SynthUtils.synthesize(stack).template; + + expect(stack.resolve(entry!.data)).toEqual({ + path: 'asset.78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197.txt', + packaging: 'file', + id: '78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197', + sourceHash: '78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197', + s3BucketParameter: 'AssetParameters78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197S3Bucket2C60F94A', + s3KeyParameter: 'AssetParameters78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197S3VersionKey9482DC35', + artifactHashParameter: 'AssetParameters78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197ArtifactHash22BFFA67', + }); + + // verify that now the template contains parameters for this asset + expect(template.Parameters.AssetParameters78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197S3Bucket2C60F94A.Type).toBe('String'); + expect(template.Parameters.AssetParameters78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197S3VersionKey9482DC35.Type).toBe('String'); +}); + +test('"readers" or "grantRead" can be used to grant read permissions on the asset to a principal', () => { + const stack = new cdk.Stack(); + const user = new iam.User(stack, 'MyUser'); + const group = new iam.Group(stack, 'MyGroup'); + + const asset = new Asset(stack, 'MyAsset', { + path: path.join(__dirname, 'sample-asset-directory'), + readers: [ user ], + }); + + asset.grantRead(group); + + expect(stack).toHaveResource('AWS::IAM::Policy', { + PolicyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: ['s3:GetObject*', 's3:GetBucket*', 's3:List*'], + Effect: 'Allow', + Resource: [ + { 'Fn::Join': ['', ['arn:', {Ref: 'AWS::Partition'}, ':s3:::', {Ref: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B'} ] ] }, + { 'Fn::Join': ['', [ 'arn:', {Ref: 'AWS::Partition'}, ':s3:::', {Ref: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B'}, '/*' ] ] }, + ], + }, + ], + }, + }); +}); + +test('fails if directory not found', () => { + const stack = new cdk.Stack(); + expect(() => new Asset(stack, 'MyDirectory', { + path: '/path/not/found/' + Math.random() * 999999, + })).toThrow(); +}); + +test('multiple assets under the same parent', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + expect(() => new Asset(stack, 'MyDirectory1', { path: path.join(__dirname, 'sample-asset-directory') })).not.toThrow(); + expect(() => new Asset(stack, 'MyDirectory2', { path: path.join(__dirname, 'sample-asset-directory') })).not.toThrow(); +}); + +test('isZipArchive indicates if the asset represents a .zip file (either explicitly or via ZipDirectory packaging)', () => { + // GIVEN + const stack = new cdk.Stack(); + + // WHEN + const nonZipAsset = new Asset(stack, 'NonZipAsset', { + path: path.join(__dirname, 'sample-asset-directory', 'sample-asset-file.txt'), + }); + + const zipDirectoryAsset = new Asset(stack, 'ZipDirectoryAsset', { + path: path.join(__dirname, 'sample-asset-directory'), + }); + + const zipFileAsset = new Asset(stack, 'ZipFileAsset', { + path: path.join(__dirname, 'sample-asset-directory', 'sample-zip-asset.zip'), + }); + + const jarFileAsset = new Asset(stack, 'JarFileAsset', { + path: path.join(__dirname, 'sample-asset-directory', 'sample-jar-asset.jar'), + }); + + // THEN + expect(nonZipAsset.isZipArchive).toBe(false); + expect(zipDirectoryAsset.isZipArchive).toBe(true); + expect(zipFileAsset.isZipArchive).toBe(true); + expect(jarFileAsset.isZipArchive).toBe(true); +}); + +test('addResourceMetadata can be used to add CFN metadata to resources', () => { + // GIVEN + const stack = new cdk.Stack(); + stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); + + const location = path.join(__dirname, 'sample-asset-directory'); + const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' }); + const asset = new Asset(stack, 'MyAsset', { path: location }); + + // WHEN + asset.addResourceMetadata(resource, 'PropName'); + + // THEN + expect(stack).toHaveResource('My::Resource::Type', { + Metadata: { + 'aws:asset:path': 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', + 'aws:asset:property': 'PropName', + }, + }, ResourcePart.CompleteDefinition); +}); + +test('asset metadata is only emitted if ASSET_RESOURCE_METADATA_ENABLED_CONTEXT is defined', () => { + // GIVEN + const stack = new cdk.Stack(); + + const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' }); + const asset = new Asset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR }); + + // WHEN + asset.addResourceMetadata(resource, 'PropName'); + + // THEN + expect(stack).not.toHaveResource('My::Resource::Type', { + Metadata: { + 'aws:asset:path': SAMPLE_ASSET_DIR, + 'aws:asset:property': 'PropName', + }, + }, ResourcePart.CompleteDefinition); +}); + +describe('staging', () => { + test('copy file assets under /${fingerprint}.ext', () => { + const tempdir = mkdtempSync(); + process.chdir(tempdir); // change current directory to somewhere in /tmp + + // GIVEN + const app = new cdk.App({ outdir: tempdir }); + const stack = new cdk.Stack(app, 'stack'); + + // WHEN + new Asset(stack, 'ZipFile', { + path: path.join(SAMPLE_ASSET_DIR, 'sample-zip-asset.zip'), + }); + + new Asset(stack, 'TextFile', { + path: path.join(SAMPLE_ASSET_DIR, 'sample-asset-file.txt'), + }); + + // THEN + app.synth(); + expect(fs.existsSync(tempdir)).toBe(true); + expect(fs.existsSync(path.join(tempdir, 'asset.a7a79cdf84b802ea8b198059ff899cffc095a1b9606e919f98e05bf80779756b.zip'))).toBe(true); + }); + + test('copy directory under .assets/fingerprint/**', () => { + const tempdir = mkdtempSync(); + process.chdir(tempdir); // change current directory to somewhere in /tmp + + // GIVEN + const app = new cdk.App({ outdir: tempdir }); + const stack = new cdk.Stack(app, 'stack'); + + // WHEN + new Asset(stack, 'ZipDirectory', { + path: SAMPLE_ASSET_DIR, + }); + + // THEN + app.synth(); + expect(fs.existsSync(tempdir)).toBe(true); + const hash = 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2'; + expect(fs.existsSync(path.join(tempdir, hash, 'sample-asset-file.txt'))).toBe(true); + expect(fs.existsSync(path.join(tempdir, hash, 'sample-jar-asset.jar'))).toBe(true); + expect(() => fs.readdirSync(tempdir)).not.toThrow(); + }); + + test('staging path is relative if the dir is below the working directory', () => { + // GIVEN + const tempdir = mkdtempSync(); + process.chdir(tempdir); // change current directory to somewhere in /tmp + + const staging = '.my-awesome-staging-directory'; + const app = new cdk.App({ + outdir: staging, + context: { + [cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT]: 'true', + }, + }); + + const stack = new cdk.Stack(app, 'stack'); + + const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' }); + const asset = new Asset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR }); + + // WHEN + asset.addResourceMetadata(resource, 'PropName'); + + const template = SynthUtils.synthesize(stack).template; + expect(template.Resources.MyResource.Metadata).toEqual({ + 'aws:asset:path': 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', + 'aws:asset:property': 'PropName', + }); + }); + + test('if staging is disabled, asset path is absolute', () => { + // GIVEN + const staging = path.resolve(mkdtempSync()); + const app = new cdk.App({ + outdir: staging, + context: { + [cxapi.DISABLE_ASSET_STAGING_CONTEXT]: 'true', + [cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT]: 'true', + }, + }); + + const stack = new cdk.Stack(app, 'stack'); + + const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' }); + const asset = new Asset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR }); + + // WHEN + asset.addResourceMetadata(resource, 'PropName'); + + const template = SynthUtils.synthesize(stack).template; + expect(template.Resources.MyResource.Metadata).toEqual({ + 'aws:asset:path': SAMPLE_ASSET_DIR, + 'aws:asset:property': 'PropName', + }); + }); + + test('cdk metadata points to staged asset', () => { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'stack'); + new Asset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR }); + + // WHEN + const session = app.synth(); + const artifact = session.getStackByName(stack.stackName); + const metadata = artifact.manifest.metadata || {}; + const md = Object.values(metadata)[0]![0]!.data as cxschema.AssetMetadataEntry; + expect(md.path).toBe('asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2'); + }); +}); + +function mkdtempSync() { + return fs.mkdtempSync(path.join(os.tmpdir(), 'assets.test')); +} diff --git a/packages/@aws-cdk/aws-s3-assets/test/test.asset.ts b/packages/@aws-cdk/aws-s3-assets/test/test.asset.ts deleted file mode 100644 index 68ef08d863d76..0000000000000 --- a/packages/@aws-cdk/aws-s3-assets/test/test.asset.ts +++ /dev/null @@ -1,355 +0,0 @@ -import { expect, haveResource, ResourcePart, SynthUtils } from '@aws-cdk/assert'; -import * as iam from '@aws-cdk/aws-iam'; -import * as cxschema from '@aws-cdk/cloud-assembly-schema'; -import * as cdk from '@aws-cdk/core'; -import * as cxapi from '@aws-cdk/cx-api'; -import * as fs from 'fs'; -import { Test } from 'nodeunit'; -import * as os from 'os'; -import * as path from 'path'; -import { Asset } from '../lib/asset'; - -// tslint:disable:max-line-length - -const SAMPLE_ASSET_DIR = path.join(__dirname, 'sample-asset-directory'); - -export = { - 'simple use case'(test: Test) { - const app = new cdk.App({ - context: { - [cxapi.DISABLE_ASSET_STAGING_CONTEXT]: 'true', - }, - }); - const stack = new cdk.Stack(app, 'MyStack'); - new Asset(stack, 'MyAsset', { - path: SAMPLE_ASSET_DIR, - }); - - // verify that metadata contains an "aws:cdk:asset" entry with - // the correct information - const entry = stack.node.metadata.find(m => m.type === 'aws:cdk:asset'); - test.ok(entry, 'found metadata entry'); - - // verify that now the template contains parameters for this asset - const session = app.synth(); - - test.deepEqual(stack.resolve(entry!.data), { - path: SAMPLE_ASSET_DIR, - id: '6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', - packaging: 'zip', - sourceHash: '6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', - s3BucketParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B', - s3KeyParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3VersionKey1F7D75F9', - artifactHashParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2ArtifactHash220DE9BD', - }); - - const template = JSON.parse(fs.readFileSync(path.join(session.directory, 'MyStack.template.json'), 'utf-8')); - - test.equal(template.Parameters.AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B.Type, 'String'); - test.equal(template.Parameters.AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3VersionKey1F7D75F9.Type, 'String'); - - test.done(); - }, - - 'verify that the app resolves tokens in metadata'(test: Test) { - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'my-stack'); - const dirPath = path.resolve(__dirname, 'sample-asset-directory'); - - new Asset(stack, 'MyAsset', { - path: dirPath, - }); - - const synth = app.synth().getStackByName(stack.stackName); - const meta = synth.manifest.metadata || {}; - test.ok(meta['/my-stack']); - test.ok(meta['/my-stack'][0]); - test.deepEqual(meta['/my-stack'][0].data, { - path: 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', - id: '6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', - packaging: 'zip', - sourceHash: '6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', - s3BucketParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B', - s3KeyParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3VersionKey1F7D75F9', - artifactHashParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2ArtifactHash220DE9BD', - }); - - test.done(); - }, - - '"file" assets'(test: Test) { - const stack = new cdk.Stack(); - const filePath = path.join(__dirname, 'file-asset.txt'); - new Asset(stack, 'MyAsset', { path: filePath }); - const entry = stack.node.metadata.find(m => m.type === 'aws:cdk:asset'); - test.ok(entry, 'found metadata entry'); - - // synthesize first so "prepare" is called - const template = SynthUtils.synthesize(stack).template; - - test.deepEqual(stack.resolve(entry!.data), { - path: 'asset.78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197.txt', - packaging: 'file', - id: '78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197', - sourceHash: '78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197', - s3BucketParameter: 'AssetParameters78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197S3Bucket2C60F94A', - s3KeyParameter: 'AssetParameters78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197S3VersionKey9482DC35', - artifactHashParameter: 'AssetParameters78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197ArtifactHash22BFFA67', - }); - - // verify that now the template contains parameters for this asset - test.equal(template.Parameters.AssetParameters78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197S3Bucket2C60F94A.Type, 'String'); - test.equal(template.Parameters.AssetParameters78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197S3VersionKey9482DC35.Type, 'String'); - - test.done(); - }, - - '"readers" or "grantRead" can be used to grant read permissions on the asset to a principal'(test: Test) { - const stack = new cdk.Stack(); - const user = new iam.User(stack, 'MyUser'); - const group = new iam.Group(stack, 'MyGroup'); - - const asset = new Asset(stack, 'MyAsset', { - path: path.join(__dirname, 'sample-asset-directory'), - readers: [ user ], - }); - - asset.grantRead(group); - - expect(stack).to(haveResource('AWS::IAM::Policy', { - PolicyDocument: { - Version: '2012-10-17', - Statement: [ - { - Action: ['s3:GetObject*', 's3:GetBucket*', 's3:List*'], - Effect: 'Allow', - Resource: [ - { 'Fn::Join': ['', ['arn:', {Ref: 'AWS::Partition'}, ':s3:::', {Ref: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B'} ] ] }, - { 'Fn::Join': ['', [ 'arn:', {Ref: 'AWS::Partition'}, ':s3:::', {Ref: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B'}, '/*' ] ] }, - ], - }, - ], - }, - })); - - test.done(); - }, - 'fails if directory not found'(test: Test) { - const stack = new cdk.Stack(); - test.throws(() => new Asset(stack, 'MyDirectory', { - path: '/path/not/found/' + Math.random() * 999999, - })); - test.done(); - }, - - 'multiple assets under the same parent'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - new Asset(stack, 'MyDirectory1', { path: path.join(__dirname, 'sample-asset-directory') }); - new Asset(stack, 'MyDirectory2', { path: path.join(__dirname, 'sample-asset-directory') }); - - // THEN: no error - - test.done(); - }, - - 'isZipArchive indicates if the asset represents a .zip file (either explicitly or via ZipDirectory packaging)'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - // WHEN - const nonZipAsset = new Asset(stack, 'NonZipAsset', { - path: path.join(__dirname, 'sample-asset-directory', 'sample-asset-file.txt'), - }); - - const zipDirectoryAsset = new Asset(stack, 'ZipDirectoryAsset', { - path: path.join(__dirname, 'sample-asset-directory'), - }); - - const zipFileAsset = new Asset(stack, 'ZipFileAsset', { - path: path.join(__dirname, 'sample-asset-directory', 'sample-zip-asset.zip'), - }); - - const jarFileAsset = new Asset(stack, 'JarFileAsset', { - path: path.join(__dirname, 'sample-asset-directory', 'sample-jar-asset.jar'), - }); - - // THEN - test.equal(nonZipAsset.isZipArchive, false); - test.equal(zipDirectoryAsset.isZipArchive, true); - test.equal(zipFileAsset.isZipArchive, true); - test.equal(jarFileAsset.isZipArchive, true); - test.done(); - }, - - 'addResourceMetadata can be used to add CFN metadata to resources'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true); - - const location = path.join(__dirname, 'sample-asset-directory'); - const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' }); - const asset = new Asset(stack, 'MyAsset', { path: location }); - - // WHEN - asset.addResourceMetadata(resource, 'PropName'); - - // THEN - expect(stack).to(haveResource('My::Resource::Type', { - Metadata: { - 'aws:asset:path': 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', - 'aws:asset:property': 'PropName', - }, - }, ResourcePart.CompleteDefinition)); - test.done(); - }, - - 'asset metadata is only emitted if ASSET_RESOURCE_METADATA_ENABLED_CONTEXT is defined'(test: Test) { - // GIVEN - const stack = new cdk.Stack(); - - const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' }); - const asset = new Asset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR }); - - // WHEN - asset.addResourceMetadata(resource, 'PropName'); - - // THEN - expect(stack).notTo(haveResource('My::Resource::Type', { - Metadata: { - 'aws:asset:path': SAMPLE_ASSET_DIR, - 'aws:asset:property': 'PropName', - }, - }, ResourcePart.CompleteDefinition)); - - test.done(); - }, - - 'staging': { - - 'copy file assets under /${fingerprint}.ext'(test: Test) { - const tempdir = mkdtempSync(); - process.chdir(tempdir); // change current directory to somewhere in /tmp - - // GIVEN - const app = new cdk.App({ outdir: tempdir }); - const stack = new cdk.Stack(app, 'stack'); - - // WHEN - new Asset(stack, 'ZipFile', { - path: path.join(SAMPLE_ASSET_DIR, 'sample-zip-asset.zip'), - }); - - new Asset(stack, 'TextFile', { - path: path.join(SAMPLE_ASSET_DIR, 'sample-asset-file.txt'), - }); - - // THEN - app.synth(); - test.ok(fs.existsSync(tempdir)); - test.ok(fs.existsSync(path.join(tempdir, 'asset.a7a79cdf84b802ea8b198059ff899cffc095a1b9606e919f98e05bf80779756b.zip'))); - test.done(); - }, - - 'copy directory under .assets/fingerprint/**'(test: Test) { - const tempdir = mkdtempSync(); - process.chdir(tempdir); // change current directory to somewhere in /tmp - - // GIVEN - const app = new cdk.App({ outdir: tempdir }); - const stack = new cdk.Stack(app, 'stack'); - - // WHEN - new Asset(stack, 'ZipDirectory', { - path: SAMPLE_ASSET_DIR, - }); - - // THEN - app.synth(); - test.ok(fs.existsSync(tempdir)); - const hash = 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2'; - test.ok(fs.existsSync(path.join(tempdir, hash, 'sample-asset-file.txt'))); - test.ok(fs.existsSync(path.join(tempdir, hash, 'sample-jar-asset.jar'))); - fs.readdirSync(tempdir); - test.done(); - }, - - 'staging path is relative if the dir is below the working directory'(test: Test) { - // GIVEN - const tempdir = mkdtempSync(); - process.chdir(tempdir); // change current directory to somewhere in /tmp - - const staging = '.my-awesome-staging-directory'; - const app = new cdk.App({ - outdir: staging, - context: { - [cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT]: 'true', - }, - }); - - const stack = new cdk.Stack(app, 'stack'); - - const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' }); - const asset = new Asset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR }); - - // WHEN - asset.addResourceMetadata(resource, 'PropName'); - - const template = SynthUtils.synthesize(stack).template; - test.deepEqual(template.Resources.MyResource.Metadata, { - 'aws:asset:path': 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', - 'aws:asset:property': 'PropName', - }); - test.done(); - }, - - 'if staging is disabled, asset path is absolute'(test: Test) { - // GIVEN - const staging = path.resolve(mkdtempSync()); - const app = new cdk.App({ - outdir: staging, - context: { - [cxapi.DISABLE_ASSET_STAGING_CONTEXT]: 'true', - [cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT]: 'true', - }, - }); - - const stack = new cdk.Stack(app, 'stack'); - - const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' }); - const asset = new Asset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR }); - - // WHEN - asset.addResourceMetadata(resource, 'PropName'); - - const template = SynthUtils.synthesize(stack).template; - test.deepEqual(template.Resources.MyResource.Metadata, { - 'aws:asset:path': SAMPLE_ASSET_DIR, - 'aws:asset:property': 'PropName', - }); - test.done(); - }, - - 'cdk metadata points to staged asset'(test: Test) { - // GIVEN - const app = new cdk.App(); - const stack = new cdk.Stack(app, 'stack'); - new Asset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR }); - - // WHEN - const session = app.synth(); - const artifact = session.getStackByName(stack.stackName); - const metadata = artifact.manifest.metadata || {}; - const md = Object.values(metadata)[0]![0]!.data as cxschema.AssetMetadataEntry; - test.deepEqual(md.path, 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2'); - test.done(); - }, - - }, -}; - -function mkdtempSync() { - return fs.mkdtempSync(path.join(os.tmpdir(), 'test.assets')); -}