diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/IntegTestDefaultTestDeployAssertE3E7D2A4.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/IntegTestDefaultTestDeployAssertE3E7D2A4.assets.json index 2220abb680bef..87dfbae32bf67 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/IntegTestDefaultTestDeployAssertE3E7D2A4.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/IntegTestDefaultTestDeployAssertE3E7D2A4.assets.json @@ -1,5 +1,5 @@ { - "version": "34.0.0", + "version": "36.0.0", "files": { "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { "source": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/asset.eb4b352e81f47c1880938665c15111e9aa20db0d42e929187a1b592a593ea713/main b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/asset.eb4b352e81f47c1880938665c15111e9aa20db0d42e929187a1b592a593ea713/main new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/asset.eb4b352e81f47c1880938665c15111e9aa20db0d42e929187a1b592a593ea713noext b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/asset.eb4b352e81f47c1880938665c15111e9aa20db0d42e929187a1b592a593ea713noext new file mode 100644 index 0000000000000..9daeafb9864cf --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/asset.eb4b352e81f47c1880938665c15111e9aa20db0d42e929187a1b592a593ea713noext @@ -0,0 +1 @@ +test diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/cdk-integ-assets-bundling.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/cdk-integ-assets-bundling.assets.json index ba0dc94f29d00..a0cfc7c0e297a 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/cdk-integ-assets-bundling.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/cdk-integ-assets-bundling.assets.json @@ -1,5 +1,5 @@ { - "version": "34.0.0", + "version": "36.0.0", "files": { "68c2f55451f9566ae0c08664249d08921ab032b2aea797bd8ba293885bf00d64": { "source": { @@ -14,7 +14,20 @@ } } }, - "b189049465db589ff992e6ed7347f54d2edf36f0c48c6277ea280abe864b031a": { + "eb4b352e81f47c1880938665c15111e9aa20db0d42e929187a1b592a593ea713": { + "source": { + "path": "asset.eb4b352e81f47c1880938665c15111e9aa20db0d42e929187a1b592a593ea713noext", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "eb4b352e81f47c1880938665c15111e9aa20db0d42e929187a1b592a593ea713", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + }, + "90c0edfc12d01dfffaee94d9f09e061a7db55c839a6105e5a94f9d96dc51e4fd": { "source": { "path": "cdk-integ-assets-bundling.template.json", "packaging": "file" @@ -22,7 +35,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "b189049465db589ff992e6ed7347f54d2edf36f0c48c6277ea280abe864b031a.json", + "objectKey": "90c0edfc12d01dfffaee94d9f09e061a7db55c839a6105e5a94f9d96dc51e4fd.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/cdk.out index 2313ab5436501..1f0068d32659a 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/cdk.out +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"34.0.0"} \ No newline at end of file +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/integ.json index e7ed476e08c94..db0feb0706bc8 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "34.0.0", + "version": "36.0.0", "testCases": { "IntegTest/DefaultTest": { "stacks": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/manifest.json index 4c5021f128fe3..a3d452104bce1 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "34.0.0", + "version": "36.0.0", "artifacts": { "cdk-integ-assets-bundling.assets": { "type": "cdk:asset-manifest", @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/b189049465db589ff992e6ed7347f54d2edf36f0c48c6277ea280abe864b031a.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/90c0edfc12d01dfffaee94d9f09e061a7db55c839a6105e5a94f9d96dc51e4fd.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/tree.json index 0550d8f1bdbde..766cc3f80d496 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.js.snapshot/tree.json @@ -66,7 +66,6 @@ "Action": [ "s3:GetBucket*", "s3:GetObject*", - "s3:HeadObject", "s3:List*" ], "Effect": "Allow", @@ -132,6 +131,32 @@ "version": "0.0.0" } }, + "BundledAssetWithoutExtension": { + "id": "BundledAssetWithoutExtension", + "path": "cdk-integ-assets-bundling/BundledAssetWithoutExtension", + "children": { + "Stage": { + "id": "Stage", + "path": "cdk-integ-assets-bundling/BundledAssetWithoutExtension/Stage", + "constructInfo": { + "fqn": "aws-cdk-lib.AssetStaging", + "version": "0.0.0" + } + }, + "AssetBucket": { + "id": "AssetBucket", + "path": "cdk-integ-assets-bundling/BundledAssetWithoutExtension/AssetBucket", + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3.BucketBase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_s3_assets.Asset", + "version": "0.0.0" + } + }, "BootstrapVersion": { "id": "BootstrapVersion", "path": "cdk-integ-assets-bundling/BootstrapVersion", @@ -167,7 +192,7 @@ "path": "IntegTest/DefaultTest/Default", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.2.70" + "version": "10.3.0" } }, "DeployAssert": { @@ -213,7 +238,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.2.70" + "version": "10.3.0" } } }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.ts index ec89949ca6fa5..a1cc1732141d2 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3-assets/test/integ.assets.file-bundling.lit.ts @@ -26,6 +26,17 @@ class TestStack extends Stack { const user = new iam.User(this, 'MyUser'); asset.grantRead(user); + + new assets.Asset(this, 'BundledAssetWithoutExtension', { + path: path.join(__dirname, 'markdown-asset'), + bundling: { + image: DockerImage.fromBuild(path.join(__dirname, 'alpine-markdown')), + outputType: BundlingOutput.SINGLE_FILE, + command: [ + 'sh', '-c', 'echo 123 > /asset-output/main', + ], + }, + }); } } diff --git a/packages/aws-cdk-lib/core/lib/asset-staging.ts b/packages/aws-cdk-lib/core/lib/asset-staging.ts index 11d8e648ab8f9..49e8c292805dd 100644 --- a/packages/aws-cdk-lib/core/lib/asset-staging.ts +++ b/packages/aws-cdk-lib/core/lib/asset-staging.ts @@ -278,9 +278,10 @@ export class AssetStaging extends Construct { */ private stageByCopying(): StagedAsset { const assetHash = this.calculateHash(this.hashType); - const stagedPath = this.stagingDisabled + const targetPath = this.stagingDisabled ? this.sourcePath : path.resolve(this.assetOutdir, renderAssetFilename(assetHash, getExtension(this.sourcePath))); + const stagedPath = this.renderStagedPath(this.sourcePath, targetPath); if (!this.sourceStats.isDirectory() && !this.sourceStats.isFile()) { throw new Error(`Asset ${this.sourcePath} is expected to be either a directory or a regular file`); @@ -338,7 +339,10 @@ export class AssetStaging extends Construct { // Calculate assetHash afterwards if we still must assetHash = assetHash ?? this.calculateHash(this.hashType, bundling, bundledAsset.path); - const stagedPath = path.resolve(this.assetOutdir, renderAssetFilename(assetHash, bundledAsset.extension)); + const stagedPath = this.renderStagedPath( + bundledAsset.path, + path.resolve(this.assetOutdir, renderAssetFilename(assetHash, bundledAsset.extension)), + ); this.stageAsset(bundledAsset.path, stagedPath, 'move'); @@ -388,7 +392,7 @@ export class AssetStaging extends Construct { } // Moving can be done quickly - if (style == 'move') { + if (style === 'move') { fs.renameSync(sourcePath, targetPath); return; } @@ -511,6 +515,17 @@ export class AssetStaging extends Construct { throw new Error('Unknown asset hash type.'); } } + + private renderStagedPath(sourcePath: string, targetPath: string): string { + // Add a suffix to the asset file name + // because when a file without extension is specified, the source directory name is the same as the staged asset file name. + // But when the hashType is `AssetHashType.OUTPUT`, the source directory name begins with `bundling-temp-` and the staged asset file name is different. + // We only need to add a suffix when the hashType is not `AssetHashType.OUTPUT`. + if (this.hashType !== AssetHashType.OUTPUT && path.dirname(sourcePath) === targetPath) { + targetPath = targetPath + '_noext'; + } + return targetPath; + } } function renderAssetFilename(assetHash: string, extension = '') { diff --git a/packages/aws-cdk-lib/core/test/docker-stub-cp.sh b/packages/aws-cdk-lib/core/test/docker-stub-cp.sh index fec4009896d63..47f29ff36854c 100755 --- a/packages/aws-cdk-lib/core/test/docker-stub-cp.sh +++ b/packages/aws-cdk-lib/core/test/docker-stub-cp.sh @@ -6,6 +6,17 @@ set -euo pipefail echo "$@" >> /tmp/docker-stub-cp.input.concat echo "$@" > /tmp/docker-stub-cp.input +# create a file without extension to emulate created files, fetch the target path from the "docker cp" command +if cat /tmp/docker-stub-cp.input.concat | grep "DOCKER_STUB_SINGLE_FILE_WITHOUT_EXT"; then + if echo "$@" | grep "cp"| grep "/asset-output"; then + outdir=$(echo "$@" | grep cp | grep "/asset-output" | xargs -n1 | grep "cdk.out" | head -n1 | cut -d":" -f1) + if [ -n "$outdir" ]; then + touch "${outdir}/test" # create a file witout extension + exit 0 + fi + fi +fi + # create a fake zip to emulate created files, fetch the target path from the "docker cp" command if echo "$@" | grep "cp"| grep "/asset-output"; then outdir=$(echo "$@" | grep cp | grep "/asset-output" | xargs -n1 | grep "cdk.out" | head -n1 | cut -d":" -f1) diff --git a/packages/aws-cdk-lib/core/test/docker-stub.sh b/packages/aws-cdk-lib/core/test/docker-stub.sh index f3bc37d187136..2bb0934e7d304 100755 --- a/packages/aws-cdk-lib/core/test/docker-stub.sh +++ b/packages/aws-cdk-lib/core/test/docker-stub.sh @@ -37,6 +37,12 @@ if echo "$@" | grep "DOCKER_STUB_SINGLE_ARCHIVE"; then exit 0 fi +if echo "$@" | grep "DOCKER_STUB_SINGLE_FILE_WITHOUT_EXT"; then + outdir=$(echo "$@" | xargs -n1 | grep "/asset-output" | head -n1 | cut -d":" -f1) + touch ${outdir}/test # create a file witout extension + exit 0 +fi + if echo "$@" | grep "DOCKER_STUB_SINGLE_FILE"; then outdir=$(echo "$@" | xargs -n1 | grep "/asset-output" | head -n1 | cut -d":" -f1) touch ${outdir}/test.txt diff --git a/packages/aws-cdk-lib/core/test/staging.test.ts b/packages/aws-cdk-lib/core/test/staging.test.ts index dff63c09a76f8..e7fae0974a940 100644 --- a/packages/aws-cdk-lib/core/test/staging.test.ts +++ b/packages/aws-cdk-lib/core/test/staging.test.ts @@ -20,6 +20,7 @@ enum DockerStubCommand { MULTIPLE_FILES = 'DOCKER_STUB_MULTIPLE_FILES', SINGLE_ARCHIVE = 'DOCKER_STUB_SINGLE_ARCHIVE', SINGLE_FILE = 'DOCKER_STUB_SINGLE_FILE', + SINGLE_FILE_WITHOUT_EXT = 'DOCKER_STUB_SINGLE_FILE_WITHOUT_EXT', VOLUME_SINGLE_ARCHIVE = 'DOCKER_STUB_VOLUME_SINGLE_ARCHIVE', } @@ -1450,6 +1451,68 @@ describe('staging', () => { expect(staging.isArchive).toEqual(false); }); + test('bundling that produces a single file with SINGLE_FILE_WITHOUT_EXT and hash type SOURCE', () => { + // GIVEN + const app = new App({ context: { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false } }); + const stack = new Stack(app, 'stack'); + const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + // WHEN + const staging = new AssetStaging(stack, 'Asset', { + sourcePath: directory, + bundling: { + image: DockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.SINGLE_FILE_WITHOUT_EXT], + outputType: BundlingOutput.SINGLE_FILE, + }, + assetHashType: AssetHashType.SOURCE, // default + }); + + // THEN + const assembly = app.synth(); + expect(fs.readdirSync(assembly.directory)).toEqual([ + 'asset.ef734136dc22840a94140575a2f98cbc061074e09535589d1cd2c11a4ac2fd75', + 'asset.ef734136dc22840a94140575a2f98cbc061074e09535589d1cd2c11a4ac2fd75_noext', + 'cdk.out', + 'manifest.json', + 'stack.template.json', + 'tree.json', + ]); + expect(staging.packaging).toEqual(FileAssetPackaging.FILE); + expect(staging.isArchive).toEqual(false); + }); + + test('bundling that produces a single file with SINGLE_FILE_WITHOUT_EXT and hash type CUSTOM', () => { + // GIVEN + const app = new App({ context: { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false } }); + const stack = new Stack(app, 'stack'); + const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + // WHEN + const staging = new AssetStaging(stack, 'Asset', { + sourcePath: directory, + bundling: { + image: DockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.SINGLE_FILE_WITHOUT_EXT], + outputType: BundlingOutput.SINGLE_FILE, + }, + assetHashType: AssetHashType.CUSTOM, + assetHash: 'custom', + }); + + // THEN + const assembly = app.synth(); + expect(fs.readdirSync(assembly.directory)).toEqual([ + 'asset.f81c5ba9e81eebb202881a8e61a83ab4b69f6bee261989eb93625c9cf5d35335', + 'asset.f81c5ba9e81eebb202881a8e61a83ab4b69f6bee261989eb93625c9cf5d35335_noext', + 'cdk.out', + 'manifest.json', + 'stack.template.json', + 'tree.json', + ]); + expect(staging.packaging).toEqual(FileAssetPackaging.FILE); + expect(staging.isArchive).toEqual(false); + }); }); describe('staging with docker cp', () => { @@ -1517,6 +1580,71 @@ describe('staging with docker cp', () => { expect.stringContaining('volume rm assetOutput'), ])); }); + + test('bundling that produces a single file with docker image copy variant and hash type SOURCE', () => { + // GIVEN + const app = new App({ context: { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false } }); + const stack = new Stack(app, 'stack'); + const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + // WHEN + const staging = new AssetStaging(stack, 'Asset', { + sourcePath: directory, + bundling: { + image: DockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.SINGLE_FILE_WITHOUT_EXT], + outputType: BundlingOutput.SINGLE_FILE, + bundlingFileAccess: BundlingFileAccess.VOLUME_COPY, + }, + assetHashType: AssetHashType.SOURCE, // default + }); + + // THEN + const assembly = app.synth(); + expect(fs.readdirSync(assembly.directory)).toEqual([ + 'asset.93bd4079bff7440a725991ecf249416ae9ad73cb639f4a8d9e8f3ad8d491e89f', + 'asset.93bd4079bff7440a725991ecf249416ae9ad73cb639f4a8d9e8f3ad8d491e89f_noext', + 'cdk.out', + 'manifest.json', + 'stack.template.json', + 'tree.json', + ]); + expect(staging.packaging).toEqual(FileAssetPackaging.FILE); + expect(staging.isArchive).toEqual(false); + }); + + test('bundling that produces a single file with docker image copy variant and hash type CUSTOM', () => { + // GIVEN + const app = new App({ context: { [cxapi.NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: false } }); + const stack = new Stack(app, 'stack'); + const directory = path.join(__dirname, 'fs', 'fixtures', 'test1'); + + // WHEN + const staging = new AssetStaging(stack, 'Asset', { + sourcePath: directory, + bundling: { + image: DockerImage.fromRegistry('alpine'), + command: [DockerStubCommand.SINGLE_FILE_WITHOUT_EXT], + outputType: BundlingOutput.SINGLE_FILE, + bundlingFileAccess: BundlingFileAccess.VOLUME_COPY, + }, + assetHashType: AssetHashType.CUSTOM, + assetHash: 'custom', + }); + + // THEN + const assembly = app.synth(); + expect(fs.readdirSync(assembly.directory)).toEqual([ + 'asset.53a51b4c68874a8e831e24e8982120be2a608f50b2e05edb8501143b3305baa8', + 'asset.53a51b4c68874a8e831e24e8982120be2a608f50b2e05edb8501143b3305baa8_noext', + 'cdk.out', + 'manifest.json', + 'stack.template.json', + 'tree.json', + ]); + expect(staging.packaging).toEqual(FileAssetPackaging.FILE); + expect(staging.isArchive).toEqual(false); + }); }); // Reads a docker stub and cleans the volume paths out of the stub.