diff --git a/packages/@aws-cdk/aws-cloudformation/lib/cloud-formation-capabilities.ts b/packages/@aws-cdk/aws-cloudformation/lib/cloud-formation-capabilities.ts index 55a4cb1b29f75..e1437d1c13704 100644 --- a/packages/@aws-cdk/aws-cloudformation/lib/cloud-formation-capabilities.ts +++ b/packages/@aws-cdk/aws-cloudformation/lib/cloud-formation-capabilities.ts @@ -28,4 +28,13 @@ export enum CloudFormationCapabilities { * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities */ NamedIAM = 'CAPABILITY_NAMED_IAM', + + /** + * Capability to run CloudFormation macros + * + * Pass this capability if your template includes macros, for example AWS::Include or AWS::Serverless. + * + * @link https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_CreateStack.html + */ + AutoExpand = 'CAPABILITY_AUTO_EXPAND' } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts index 96db314388eae..a6dd58ef7d1a2 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/cloudformation/pipeline-actions.ts @@ -141,7 +141,7 @@ export interface CloudFormationDeployActionProps extends CloudFormationActionPro * @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities * @default None, unless `adminPermissions` is true */ - readonly capabilities?: cloudformation.CloudFormationCapabilities; + readonly capabilities?: cloudformation.CloudFormationCapabilities[]; /** * Whether to grant full permissions to CloudFormation while deploying this template. @@ -221,12 +221,12 @@ export abstract class CloudFormationDeployAction extends CloudFormationAction { constructor(props: CloudFormationDeployActionProps, configuration: any) { const capabilities = props.adminPermissions && props.capabilities === undefined - ? cloudformation.CloudFormationCapabilities.NamedIAM + ? [cloudformation.CloudFormationCapabilities.NamedIAM] : props.capabilities; super(props, { ...configuration, // None evaluates to empty string which is falsey and results in undefined - Capabilities: (capabilities && capabilities.toString()) || undefined, + Capabilities: parseCapabilities(capabilities), RoleArn: cdk.Lazy.stringValue({ produce: () => this.deploymentRole.roleArn }), ParameterOverrides: cdk.Lazy.stringValue({ produce: () => Stack.of(this.scope).toJsonString(props.parameterOverrides) }), TemplateConfiguration: props.templateConfiguration ? props.templateConfiguration.location : undefined, @@ -540,3 +540,13 @@ interface StatementTemplate { } type StatementCondition = { [op: string]: { [attribute: string]: string } }; + +function parseCapabilities(capabilities: any): any { + if (capabilities === undefined) { + return undefined; + } else if (capabilities.length === 1) { + return capabilities.toString(); + } else if (capabilities.length > 1) { + return capabilities.join(','); + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts index f9fcedf104f00..7c0e00e77aa68 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/cloudformation/test.cloudformation-pipeline-actions.ts @@ -1,4 +1,5 @@ import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; +import { CloudFormationCapabilities } from '@aws-cdk/aws-cloudformation'; import { CodePipelineBuildArtifacts, CodePipelineSource, Project } from '@aws-cdk/aws-codebuild'; import { Repository } from '@aws-cdk/aws-codecommit'; import codepipeline = require('@aws-cdk/aws-codepipeline'); @@ -415,7 +416,94 @@ export = { })); test.done(); - } + }, + + 'Single capability is passed to template'(test: Test) { + // GIVEN + const stack = new TestFixture(); + + // WHEN + stack.deployStage.addAction(new cpactions.CloudFormationCreateUpdateStackAction({ + actionName: 'CreateUpdate', + stackName: 'MyStack', + templatePath: stack.sourceOutput.atPath('template.yaml'), + adminPermissions: false, + capabilities: [ + CloudFormationCapabilities.NamedIAM + ] + })); + + const roleId = "PipelineDeployCreateUpdateRole515CB7D4"; + + // THEN: Action in Pipeline has named IAM capabilities + expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + "Stages": [ + { "Name": "Source" /* don't care about the rest */ }, + { + "Name": "Deploy", + "Actions": [ + { + "Configuration": { + "Capabilities": "CAPABILITY_NAMED_IAM", + "RoleArn": { "Fn::GetAtt": [ roleId, "Arn" ] }, + "ActionMode": "CREATE_UPDATE", + "StackName": "MyStack", + "TemplatePath": "SourceArtifact::template.yaml" + }, + "InputArtifacts": [{"Name": "SourceArtifact"}], + "Name": "CreateUpdate", + }, + ], + } + ] + })); + + test.done(); + }, + + 'Multiple capabilities are passed to template'(test: Test) { + // GIVEN + const stack = new TestFixture(); + + // WHEN + stack.deployStage.addAction(new cpactions.CloudFormationCreateUpdateStackAction({ + actionName: 'CreateUpdate', + stackName: 'MyStack', + templatePath: stack.sourceOutput.atPath('template.yaml'), + adminPermissions: false, + capabilities: [ + CloudFormationCapabilities.NamedIAM, + CloudFormationCapabilities.AutoExpand + ] + })); + + const roleId = "PipelineDeployCreateUpdateRole515CB7D4"; + + // THEN: Action in Pipeline has named IAM and AUTOEXPAND capabilities + expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { + "Stages": [ + { "Name": "Source" /* don't care about the rest */ }, + { + "Name": "Deploy", + "Actions": [ + { + "Configuration": { + "Capabilities": "CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND", + "RoleArn": { "Fn::GetAtt": [ roleId, "Arn" ] }, + "ActionMode": "CREATE_UPDATE", + "StackName": "MyStack", + "TemplatePath": "SourceArtifact::template.yaml" + }, + "InputArtifacts": [{"Name": "SourceArtifact"}], + "Name": "CreateUpdate", + }, + ], + } + ] + })); + + test.done(); + }, }; /**