diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 8e52a80bf0e32..93777c3394a9c 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -685,6 +685,10 @@ export class Function extends FunctionBase { this.runtime = props.runtime; if (props.layers) { + if (props.runtime === Runtime.FROM_IMAGE) { + throw new Error('Layers are not supported for container image functions'); + } + this.addLayers(...props.layers); } diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index c8bd8ac3b9889..f86a3b6fb1ebb 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -2156,6 +2156,22 @@ describe('function', () => { }); }); }); + + test('error when layers set in a container function', () => { + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'Bucket'); + const code = new lambda.S3Code(bucket, 'ObjectKey'); + + const layer = new lambda.LayerVersion(stack, 'Layer', { + code, + }); + + expect(() => new lambda.DockerImageFunction(stack, 'MyLambda', { + code: lambda.DockerImageCode.fromImageAsset(path.join(__dirname, 'docker-lambda-handler')), + layers: [layer], + })).toThrow(/Layers are not supported for container image functions/); + }); + }); function newTestLambda(scope: constructs.Construct) { diff --git a/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts b/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts index 50c7705cc4528..53ddf1aa0352f 100644 --- a/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts +++ b/packages/@aws-cdk/pipelines/lib/actions/publish-assets-action.ts @@ -1,10 +1,12 @@ +import * as fs from 'fs'; +import * as path from 'path'; import * as codebuild from '@aws-cdk/aws-codebuild'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as events from '@aws-cdk/aws-events'; import * as iam from '@aws-cdk/aws-iam'; -import { Lazy } from '@aws-cdk/core'; +import { Lazy, ISynthesisSession, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; // v2 - keep this import as a separate section to reduce merge conflict when forward merging with the v2 branch. @@ -82,6 +84,14 @@ export interface PublishAssetsActionProps { * @default - All private subnets. */ readonly subnetSelection?: ec2.SubnetSelection; + + /** + * Use a file buildspec written to the cloud assembly instead of an inline buildspec. + * This prevents size limitation errors as inline specs have a max length of 25600 characters + * + * @default false + */ + readonly createBuildspecFile?: boolean; } /** @@ -97,11 +107,25 @@ export class PublishAssetsAction extends CoreConstruct implements codepipeline.I private readonly action: codepipeline.IAction; private readonly commands = new Array(); + private readonly buildSpec: codebuild.BuildSpec; + constructor(scope: Construct, id: string, private readonly props: PublishAssetsActionProps) { super(scope, id); const installSuffix = props.cdkCliVersion ? `@${props.cdkCliVersion}` : ''; + this.buildSpec = codebuild.BuildSpec.fromObject({ + version: '0.2', + phases: { + install: { + commands: `npm install -g cdk-assets${installSuffix}`, + }, + build: { + commands: Lazy.list({ produce: () => this.commands }), + }, + }, + }); + const project = new codebuild.PipelineProject(this, 'Default', { projectName: this.props.projectName, environment: { @@ -110,17 +134,7 @@ export class PublishAssetsAction extends CoreConstruct implements codepipeline.I }, vpc: props.vpc, subnetSelection: props.subnetSelection, - buildSpec: codebuild.BuildSpec.fromObject({ - version: '0.2', - phases: { - install: { - commands: `npm install -g cdk-assets${installSuffix}`, - }, - build: { - commands: Lazy.list({ produce: () => this.commands }), - }, - }, - }), + buildSpec: props.createBuildspecFile ? codebuild.BuildSpec.fromSourceFilename(this.getBuildSpecFileName()) : this.buildSpec, role: props.role, }); @@ -145,6 +159,20 @@ export class PublishAssetsAction extends CoreConstruct implements codepipeline.I }); } + private getBuildSpecFileName(): string { + return `buildspec-assets-${this.props.actionName}.yaml`; + } + + protected synthesize(session: ISynthesisSession): void { + super.synthesize(session); + + if (this.props.createBuildspecFile) { + const specFile = path.join(session.outdir, this.getBuildSpecFileName()); + fs.writeFileSync(specFile, Stack.of(this).resolve(this.buildSpec.toBuildSpec()), { encoding: 'utf-8' }); + } + } + + /** * Add a single publishing command * @@ -160,8 +188,7 @@ export class PublishAssetsAction extends CoreConstruct implements codepipeline.I /** * Exists to implement IAction */ - public bind(scope: CoreConstruct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): - codepipeline.ActionConfig { + public bind(scope: CoreConstruct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): codepipeline.ActionConfig { return this.action.bind(scope, stage, options); } diff --git a/packages/@aws-cdk/pipelines/lib/pipeline.ts b/packages/@aws-cdk/pipelines/lib/pipeline.ts index 9f6d4fbee3ebd..b1521324adeaf 100644 --- a/packages/@aws-cdk/pipelines/lib/pipeline.ts +++ b/packages/@aws-cdk/pipelines/lib/pipeline.ts @@ -483,6 +483,7 @@ class AssetPublishing extends CoreConstruct { role: this.assetRoles[command.assetType], vpc: this.props.vpc, subnetSelection: this.props.subnetSelection, + createBuildspecFile: this.props.singlePublisherPerType, }); this.stages[stageIndex].addAction(action); } diff --git a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json index 2bfe179472c52..2e46bbe95546b 100644 --- a/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.pipeline-with-assets-single-upload.expected.json @@ -1446,7 +1446,7 @@ ] }, "Source": { - "BuildSpec": "{\n \"version\": \"0.2\",\n \"phases\": {\n \"install\": {\n \"commands\": \"npm install -g cdk-assets\"\n },\n \"build\": {\n \"commands\": [\n \"cdk-assets --path \\\"assembly-PipelineStack-PreProd/PipelineStackPreProdStack65A0AD1F.assets.json\\\" --verbose publish \\\"8289faf53c7da377bb2b90615999171adef5e1d8f6b88810e5fef75e6ca09ba5:12345678-test-region\\\"\",\n \"cdk-assets --path \\\"assembly-PipelineStack-PreProd/PipelineStackPreProdStack65A0AD1F.assets.json\\\" --verbose publish \\\"ac76997971c3f6ddf37120660003f1ced72b4fc58c498dfd99c78fa77e721e0e:12345678-test-region\\\"\"\n ]\n }\n }\n}", + "BuildSpec": "buildspec-assets-FileAsset.yaml", "Type": "CODEPIPELINE" }, "EncryptionKey": { diff --git a/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts b/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts index b3da0fb5a5a59..dbbee2c32104c 100644 --- a/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts +++ b/packages/@aws-cdk/pipelines/test/pipeline-assets.test.ts @@ -1,5 +1,6 @@ +import * as fs from 'fs'; import * as path from 'path'; -import { arrayWith, deepObjectLike, encodedJson, notMatching, objectLike, stringLike } from '@aws-cdk/assert-internal'; +import { arrayWith, deepObjectLike, encodedJson, notMatching, objectLike, stringLike, SynthUtils } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; import * as cp from '@aws-cdk/aws-codepipeline'; import * as ec2 from '@aws-cdk/aws-ec2'; @@ -439,19 +440,13 @@ describe('pipeline with single asset publisher', () => { Image: 'aws/codebuild/standard:5.0', }, Source: { - BuildSpec: encodedJson(deepObjectLike({ - phases: { - build: { - // Both assets are uploaded in the same action - commands: arrayWith( - `cdk-assets --path "assembly-FileAssetApp/FileAssetAppStackEADD68C5.assets.json" --verbose publish "${FILE_ASSET_SOURCE_HASH}:current_account-current_region"`, - `cdk-assets --path "assembly-FileAssetApp/FileAssetAppStackEADD68C5.assets.json" --verbose publish "${FILE_ASSET_SOURCE_HASH2}:current_account-current_region"`, - ), - }, - }, - })), + BuildSpec: 'buildspec-assets-FileAsset.yaml', }, }); + const assembly = SynthUtils.synthesize(pipelineStack, { skipValidation: true }).assembly; + const buildSpec = JSON.parse(fs.readFileSync(path.join(assembly.directory, 'buildspec-assets-FileAsset.yaml')).toString()); + expect(buildSpec.phases.build.commands).toContain(`cdk-assets --path "assembly-FileAssetApp/FileAssetAppStackEADD68C5.assets.json" --verbose publish "${FILE_ASSET_SOURCE_HASH}:current_account-current_region"`); + expect(buildSpec.phases.build.commands).toContain(`cdk-assets --path "assembly-FileAssetApp/FileAssetAppStackEADD68C5.assets.json" --verbose publish "${FILE_ASSET_SOURCE_HASH2}:current_account-current_region"`); }); }); class PlainStackApp extends Stage {