From 253d32ef468ae4396195cb89fee515d27d44ea79 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Tue, 9 Aug 2022 15:55:06 -0700 Subject: [PATCH] fix(codepipeline-actions): cross stack reference causes stack cycle in sources that use CloudWatch Events (#20149) When using a newly-created, CDK-managed resource, such as an S3 Bucket or a CodeCommit Repository, as the source of a CodePipeline, when it's in a different Stack than the pipeline (but in the same environment as it), and we use CloudWatch Events to trigger the pipeline from the source, that would result in a cycle: 1. Because the Event Rule is created in the source Stack, that Stack needs the name of the pipeline to trigger from the Rule, and also the Role to use for the trigger, which is created in the target (in this case, the pipeline) Stack. So, the source Stack depends on the pipeline Stack. 2. The pipeline Stack needs the name of the source resource. So, the pipeline Stack depends on the source Stack. The only way to break this cycle is to move the Event Rule to the target Stack. This PR adds an API to the Events module to make it possible for event-creating constructs to make that choice, and uses that capability in the CodePipeline `CodeCommitSourceAction` and `S3SourceAction`. Fixes #3087 Fixes #8042 Fixes #10896 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/master/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/codecommit/source-action.ts | 1 + .../lib/s3/source-action.ts | 1 + .../aws-codepipeline-actions/package.json | 1 + .../codecommit-source-action.test.ts | 63 +- ...urce-bucket-events-cross-stack-same-env.ts | 47 + .../BucketStack.template.json | 30 + .../PipelineStack.template.json | 854 +++++++++++ .../cdk.out | 1 + .../manifest.json | 156 ++ .../tree.json | 1275 +++++++++++++++++ .../aws-events/lib/on-event-options.ts | 33 +- packages/@aws-cdk/aws-events/lib/rule.ts | 57 +- 12 files changed, 2453 insertions(+), 66 deletions(-) create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/s3/integ.source-bucket-events-cross-stack-same-env.ts create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/BucketStack.template.json create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/PipelineStack.template.json create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/tree.json diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts index 884ada9221ddc..85feb51c001e5 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/codecommit/source-action.ts @@ -171,6 +171,7 @@ export class CodeCommitSourceAction extends Action { eventRole: this.props.eventRole, }), branches: [this.branch], + crossStackScope: stage.pipeline as unknown as Construct, }); } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts index dea699e3bea37..092759e6b1c29 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/s3/source-action.ts @@ -118,6 +118,7 @@ export class S3SourceAction extends Action { this.props.bucket.onCloudTrailWriteObject(id, { target: new targets.CodePipeline(stage.pipeline), paths: [this.props.bucketKey], + crossStackScope: stage.pipeline as unknown as Construct, }); } diff --git a/packages/@aws-cdk/aws-codepipeline-actions/package.json b/packages/@aws-cdk/aws-codepipeline-actions/package.json index c30b840c1c0fe..fefc33b7691ef 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/package.json +++ b/packages/@aws-cdk/aws-codepipeline-actions/package.json @@ -82,6 +82,7 @@ "@aws-cdk/aws-s3-assets": "0.0.0", "@aws-cdk/cdk-build-tools": "0.0.0", "@aws-cdk/integ-runner": "0.0.0", + "@aws-cdk/integ-tests": "0.0.0", "@aws-cdk/cx-api": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/codecommit-source-action.test.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/codecommit-source-action.test.ts index 2ca60b9d5c49e..b317e522fb1e5 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/codecommit-source-action.test.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/codecommit/codecommit-source-action.test.ts @@ -33,8 +33,6 @@ describe('CodeCommit Source Action', () => { }); Template.fromStack(stack).resourceCountIs('AWS::Events::Rule', 1); - - }); test('cross-account CodeCommit Repository Source does not use target role in source stack', () => { @@ -118,8 +116,6 @@ describe('CodeCommit Source Action', () => { }, ], }); - - }); test('does not poll for source changes and uses Events for CodeCommitTrigger.EVENTS', () => { @@ -143,8 +139,6 @@ describe('CodeCommit Source Action', () => { }); Template.fromStack(stack).resourceCountIs('AWS::Events::Rule', 1); - - }); test('polls for source changes and does not use Events for CodeCommitTrigger.POLL', () => { @@ -168,8 +162,6 @@ describe('CodeCommit Source Action', () => { }); Template.fromStack(stack).resourceCountIs('AWS::Events::Rule', 0); - - }); test('does not poll for source changes and does not use Events for CodeCommitTrigger.NONE', () => { @@ -193,8 +185,6 @@ describe('CodeCommit Source Action', () => { }); Template.fromStack(stack).resourceCountIs('AWS::Events::Rule', 0); - - }); test('cannot be created with an empty branch', () => { @@ -211,8 +201,6 @@ describe('CodeCommit Source Action', () => { branch: '', }); }).toThrow(/'branch' parameter cannot be an empty string/); - - }); test('allows using the same repository multiple times with different branches when trigger=EVENTS', () => { @@ -253,8 +241,6 @@ describe('CodeCommit Source Action', () => { }, ], }); - - }); test('exposes variables for other actions to consume', () => { @@ -308,8 +294,6 @@ describe('CodeCommit Source Action', () => { }, ], }); - - }); test('allows using a Token for the branch name', () => { @@ -351,8 +335,6 @@ describe('CodeCommit Source Action', () => { }, }, }); - - }); test('allows to enable full clone', () => { @@ -456,8 +438,6 @@ describe('CodeCommit Source Action', () => { ]), }, }); - - }); test('uses the role when passed', () => { @@ -512,8 +492,6 @@ describe('CodeCommit Source Action', () => { }, ], }); - - }); test('grants explicit s3:PutObjectAcl permissions when the Actions is cross-account', () => { @@ -569,8 +547,49 @@ describe('CodeCommit Source Action', () => { }]), }, }); + }); + test('allows using a new Repository from another Stack as a source of the Pipeline, with Events', () => { + const app = new App(); + + const repoStack = new Stack(app, 'RepositoryStack'); + const repo = new codecommit.Repository(repoStack, 'Repository', { + repositoryName: 'my-repo', + }); + + const pipelineStack = new Stack(app, 'PipelineStack'); + const sourceOutput = new codepipeline.Artifact(); + new codepipeline.Pipeline(pipelineStack, 'Pipeline', { + stages: [ + { + stageName: 'Source', + actions: [ + new cpactions.CodeCommitSourceAction({ + actionName: 'Source', + repository: repo, + output: sourceOutput, + }), + ], + }, + { + stageName: 'Build', + actions: [ + new cpactions.CodeBuildAction({ + actionName: 'Build', + project: codebuild.Project.fromProjectName(pipelineStack, 'Project', 'my-project'), + input: sourceOutput, + }), + ], + }, + ], + }); + // If the Event Rule was created in the repo's Stack, + // we would have a cycle + // (the repo's Stack would need the name of the CodePipeline to trigger through the Rule, + // while the pipeline's Stack would need the name of the Repository to use as a Source). + // By moving the Rule to pipeline's Stack, we get rid of the cycle. + Template.fromStack(pipelineStack).resourceCountIs('AWS::Events::Rule', 1); }); }); }); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/s3/integ.source-bucket-events-cross-stack-same-env.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/integ.source-bucket-events-cross-stack-same-env.ts new file mode 100644 index 0000000000000..8d7ac75267597 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/integ.source-bucket-events-cross-stack-same-env.ts @@ -0,0 +1,47 @@ +/// !cdk-integ PipelineStack + +import * as codebuild from '@aws-cdk/aws-codebuild'; +import * as codepipeline from '@aws-cdk/aws-codepipeline'; +import * as s3 from '@aws-cdk/aws-s3'; +import { App, RemovalPolicy, Stack } from '@aws-cdk/core'; +import * as integ from '@aws-cdk/integ-tests'; +import * as cpactions from '../../lib'; + +const app = new App(); +const bucketStack = new Stack(app, 'BucketStack'); +const bucket = new s3.Bucket(bucketStack, 'Bucket', { + removalPolicy: RemovalPolicy.DESTROY, +}); + +const pipelineStack = new Stack(app, 'PipelineStack'); +const sourceOutput = new codepipeline.Artifact(); +new codepipeline.Pipeline(pipelineStack, 'Pipeline', { + stages: [ + { + stageName: 'Source', + actions: [ + new cpactions.S3SourceAction({ + actionName: 'Source', + bucket, + trigger: cpactions.S3Trigger.EVENTS, + bucketKey: 'file.zip', + output: sourceOutput, + }), + ], + }, + { + stageName: 'Build', + actions: [ + new cpactions.CodeBuildAction({ + actionName: 'Build', + project: new codebuild.PipelineProject(pipelineStack, 'Project'), + input: sourceOutput, + }), + ], + }, + ], +}); + +new integ.IntegTest(app, 'CodePipelineS3SourceTest', { + testCases: [pipelineStack], +}); diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/BucketStack.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/BucketStack.template.json new file mode 100644 index 0000000000000..eca13e273e16e --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/BucketStack.template.json @@ -0,0 +1,30 @@ +{ + "Resources": { + "Bucket83908E77": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Outputs": { + "ExportsOutputRefBucket83908E7781C90AC0": { + "Value": { + "Ref": "Bucket83908E77" + }, + "Export": { + "Name": "BucketStack:ExportsOutputRefBucket83908E7781C90AC0" + } + }, + "ExportsOutputFnGetAttBucket83908E77Arn063C8555": { + "Value": { + "Fn::GetAtt": [ + "Bucket83908E77", + "Arn" + ] + }, + "Export": { + "Name": "BucketStack:ExportsOutputFnGetAttBucket83908E77Arn063C8555" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/PipelineStack.template.json b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/PipelineStack.template.json new file mode 100644 index 0000000000000..bf00c5c4bf2ef --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/PipelineStack.template.json @@ -0,0 +1,854 @@ +{ + "Resources": { + "ProjectRole4CCB274E": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codebuild.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "ProjectRoleDefaultPolicy7F29461B": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "ProjectC78D97AD" + }, + ":*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "ProjectC78D97AD" + } + ] + ] + } + ] + }, + { + "Action": [ + "codebuild:BatchPutCodeCoverages", + "codebuild:BatchPutTestCases", + "codebuild:CreateReport", + "codebuild:CreateReportGroup", + "codebuild:UpdateReport" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codebuild:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":report-group/", + { + "Ref": "ProjectC78D97AD" + }, + "-*" + ] + ] + } + }, + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "ProjectRoleDefaultPolicy7F29461B", + "Roles": [ + { + "Ref": "ProjectRole4CCB274E" + } + ] + } + }, + "ProjectC78D97AD": { + "Type": "AWS::CodeBuild::Project", + "Properties": { + "Artifacts": { + "Type": "CODEPIPELINE" + }, + "Environment": { + "ComputeType": "BUILD_GENERAL1_SMALL", + "Image": "aws/codebuild/standard:1.0", + "ImagePullCredentialsType": "CODEBUILD", + "PrivilegedMode": false, + "Type": "LINUX_CONTAINER" + }, + "ServiceRole": { + "Fn::GetAtt": [ + "ProjectRole4CCB274E", + "Arn" + ] + }, + "Source": { + "Type": "CODEPIPELINE" + }, + "Cache": { + "Type": "NO_CACHE" + }, + "EncryptionKey": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + } + }, + "PipelineArtifactsBucketEncryptionKey01D58D69": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": { + "Type": "AWS::KMS::Alias", + "Properties": { + "AliasName": "alias/codepipeline-pipelinestack-pipeline-9db740af", + "TargetKeyId": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "PipelineArtifactsBucket22248F97": { + "Type": "AWS::S3::Bucket", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + }, + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "BlockPublicPolicy": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "PipelineArtifactsBucketPolicyD4F9712A": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "Bucket": { + "Ref": "PipelineArtifactsBucket22248F97" + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineRoleD68726F7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codepipeline.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineRoleDefaultPolicyC7A05455": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineBuildCodePipelineActionRoleD77A08E6", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "PipelineSourceCodePipelineActionRoleC6F9E7F5", + "Arn" + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineRoleDefaultPolicyC7A05455", + "Roles": [ + { + "Ref": "PipelineRoleD68726F7" + } + ] + } + }, + "PipelineC660917D": { + "Type": "AWS::CodePipeline::Pipeline", + "Properties": { + "RoleArn": { + "Fn::GetAtt": [ + "PipelineRoleD68726F7", + "Arn" + ] + }, + "Stages": [ + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Source", + "Owner": "AWS", + "Provider": "S3", + "Version": "1" + }, + "Configuration": { + "S3Bucket": { + "Fn::ImportValue": "BucketStack:ExportsOutputRefBucket83908E7781C90AC0" + }, + "S3ObjectKey": "file.zip", + "PollForSourceChanges": false + }, + "Name": "Source", + "OutputArtifacts": [ + { + "Name": "Artifact_Source_Source" + } + ], + "RoleArn": { + "Fn::GetAtt": [ + "PipelineSourceCodePipelineActionRoleC6F9E7F5", + "Arn" + ] + }, + "RunOrder": 1 + } + ], + "Name": "Source" + }, + { + "Actions": [ + { + "ActionTypeId": { + "Category": "Build", + "Owner": "AWS", + "Provider": "CodeBuild", + "Version": "1" + }, + "Configuration": { + "ProjectName": { + "Ref": "ProjectC78D97AD" + } + }, + "InputArtifacts": [ + { + "Name": "Artifact_Source_Source" + } + ], + "Name": "Build", + "RoleArn": { + "Fn::GetAtt": [ + "PipelineBuildCodePipelineActionRoleD77A08E6", + "Arn" + ] + }, + "RunOrder": 1 + } + ], + "Name": "Build" + } + ], + "ArtifactStore": { + "EncryptionKey": { + "Id": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + }, + "Type": "KMS" + }, + "Location": { + "Ref": "PipelineArtifactsBucket22248F97" + }, + "Type": "S3" + } + }, + "DependsOn": [ + "PipelineRoleDefaultPolicyC7A05455", + "PipelineRoleD68726F7" + ] + }, + "PipelineSourceCodePipelineActionRoleC6F9E7F5": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineSourceCodePipelineActionRoleDefaultPolicy2D565925": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::ImportValue": "BucketStack:ExportsOutputFnGetAttBucket83908E77Arn063C8555" + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::ImportValue": "BucketStack:ExportsOutputFnGetAttBucket83908E77Arn063C8555" + }, + "/file.zip" + ] + ] + } + ] + }, + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineSourceCodePipelineActionRoleDefaultPolicy2D565925", + "Roles": [ + { + "Ref": "PipelineSourceCodePipelineActionRoleC6F9E7F5" + } + ] + } + }, + "PipelinePipelineStackPipeline9DB740AFSourceEventRulefilezipE8D1F0EF": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "source": [ + "aws.s3" + ], + "detail-type": [ + "AWS API Call via CloudTrail" + ], + "detail": { + "resources": { + "ARN": [ + { + "Fn::Join": [ + "", + [ + { + "Fn::ImportValue": "BucketStack:ExportsOutputFnGetAttBucket83908E77Arn063C8555" + }, + "/file.zip" + ] + ] + } + ] + }, + "eventName": [ + "CompleteMultipartUpload", + "CopyObject", + "PutObject" + ], + "requestParameters": { + "bucketName": [ + { + "Fn::ImportValue": "BucketStack:ExportsOutputRefBucket83908E7781C90AC0" + } + ], + "key": [ + "file.zip" + ] + } + } + }, + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codepipeline:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "PipelineC660917D" + } + ] + ] + }, + "Id": "Target0", + "RoleArn": { + "Fn::GetAtt": [ + "PipelineEventsRole46BEEA7C", + "Arn" + ] + } + } + ] + } + }, + "PipelineEventsRole46BEEA7C": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineEventsRoleDefaultPolicyFF4FCCE0": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "codepipeline:StartPipelineExecution", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codepipeline:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "PipelineC660917D" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineEventsRoleDefaultPolicyFF4FCCE0", + "Roles": [ + { + "Ref": "PipelineEventsRole46BEEA7C" + } + ] + } + }, + "PipelineBuildCodePipelineActionRoleD77A08E6": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "PipelineBuildCodePipelineActionRoleDefaultPolicyC9CB73F8": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "codebuild:BatchGetBuilds", + "codebuild:StartBuild", + "codebuild:StopBuild" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "ProjectC78D97AD", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "PipelineBuildCodePipelineActionRoleDefaultPolicyC9CB73F8", + "Roles": [ + { + "Ref": "PipelineBuildCodePipelineActionRoleD77A08E6" + } + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/cdk.out b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/cdk.out new file mode 100644 index 0000000000000..588d7b269d34f --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"20.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/manifest.json b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/manifest.json new file mode 100644 index 0000000000000..17c3ab579029e --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/manifest.json @@ -0,0 +1,156 @@ +{ + "version": "20.0.0", + "artifacts": { + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + }, + "BucketStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "BucketStack.template.json", + "validateOnSynth": false + }, + "metadata": { + "/BucketStack/Bucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Bucket83908E77" + } + ], + "/BucketStack/Exports/Output{\"Ref\":\"Bucket83908E77\"}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputRefBucket83908E7781C90AC0" + } + ], + "/BucketStack/Exports/Output{\"Fn::GetAtt\":[\"Bucket83908E77\",\"Arn\"]}": [ + { + "type": "aws:cdk:logicalId", + "data": "ExportsOutputFnGetAttBucket83908E77Arn063C8555" + } + ] + }, + "displayName": "BucketStack" + }, + "PipelineStack": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "PipelineStack.template.json", + "validateOnSynth": false + }, + "dependencies": [ + "BucketStack" + ], + "metadata": { + "/PipelineStack/Project/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ProjectRole4CCB274E" + } + ], + "/PipelineStack/Project/Role/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ProjectRoleDefaultPolicy7F29461B" + } + ], + "/PipelineStack/Project/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "ProjectC78D97AD" + } + ], + "/PipelineStack/Pipeline/ArtifactsBucketEncryptionKey/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineArtifactsBucketEncryptionKey01D58D69" + } + ], + "/PipelineStack/Pipeline/ArtifactsBucketEncryptionKeyAlias/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineArtifactsBucketEncryptionKeyAlias5C510EEE" + } + ], + "/PipelineStack/Pipeline/ArtifactsBucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineArtifactsBucket22248F97" + } + ], + "/PipelineStack/Pipeline/ArtifactsBucket/Policy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineArtifactsBucketPolicyD4F9712A" + } + ], + "/PipelineStack/Pipeline/Role/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineRoleD68726F7" + } + ], + "/PipelineStack/Pipeline/Role/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineRoleDefaultPolicyC7A05455" + } + ], + "/PipelineStack/Pipeline/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineC660917D" + } + ], + "/PipelineStack/Pipeline/Source/Source/CodePipelineActionRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineSourceCodePipelineActionRoleC6F9E7F5" + } + ], + "/PipelineStack/Pipeline/Source/Source/CodePipelineActionRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineSourceCodePipelineActionRoleDefaultPolicy2D565925" + } + ], + "/PipelineStack/Pipeline/PipelineStackPipeline9DB740AFSourceEventRulefile.zip/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelinePipelineStackPipeline9DB740AFSourceEventRulefilezipE8D1F0EF" + } + ], + "/PipelineStack/Pipeline/EventsRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineEventsRole46BEEA7C" + } + ], + "/PipelineStack/Pipeline/EventsRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineEventsRoleDefaultPolicyFF4FCCE0" + } + ], + "/PipelineStack/Pipeline/Build/Build/CodePipelineActionRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineBuildCodePipelineActionRoleD77A08E6" + } + ], + "/PipelineStack/Pipeline/Build/Build/CodePipelineActionRole/DefaultPolicy/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "PipelineBuildCodePipelineActionRoleDefaultPolicyC9CB73F8" + } + ] + }, + "displayName": "PipelineStack" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/tree.json b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/tree.json new file mode 100644 index 0000000000000..257932a27ad95 --- /dev/null +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/s3/source-bucket-events-cross-stack-same-env.integ.snapshot/tree.json @@ -0,0 +1,1275 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + }, + "BucketStack": { + "id": "BucketStack", + "path": "BucketStack", + "children": { + "Bucket": { + "id": "Bucket", + "path": "BucketStack/Bucket", + "children": { + "Resource": { + "id": "Resource", + "path": "BucketStack/Bucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucket", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.Bucket", + "version": "0.0.0" + } + }, + "Exports": { + "id": "Exports", + "path": "BucketStack/Exports", + "children": { + "Output{\"Ref\":\"Bucket83908E77\"}": { + "id": "Output{\"Ref\":\"Bucket83908E77\"}", + "path": "BucketStack/Exports/Output{\"Ref\":\"Bucket83908E77\"}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + }, + "Output{\"Fn::GetAtt\":[\"Bucket83908E77\",\"Arn\"]}": { + "id": "Output{\"Fn::GetAtt\":[\"Bucket83908E77\",\"Arn\"]}", + "path": "BucketStack/Exports/Output{\"Fn::GetAtt\":[\"Bucket83908E77\",\"Arn\"]}", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnOutput", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "PipelineStack": { + "id": "PipelineStack", + "path": "PipelineStack", + "children": { + "Project": { + "id": "Project", + "path": "PipelineStack/Project", + "children": { + "Role": { + "id": "Role", + "path": "PipelineStack/Project/Role", + "children": { + "Resource": { + "id": "Resource", + "path": "PipelineStack/Project/Role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codebuild.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "PipelineStack/Project/Role/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "PipelineStack/Project/Role/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "ProjectC78D97AD" + }, + ":*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:/aws/codebuild/", + { + "Ref": "ProjectC78D97AD" + } + ] + ] + } + ] + }, + { + "Action": [ + "codebuild:BatchPutCodeCoverages", + "codebuild:BatchPutTestCases", + "codebuild:CreateReport", + "codebuild:CreateReportGroup", + "codebuild:UpdateReport" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codebuild:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":report-group/", + { + "Ref": "ProjectC78D97AD" + }, + "-*" + ] + ] + } + }, + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "ProjectRoleDefaultPolicy7F29461B", + "roles": [ + { + "Ref": "ProjectRole4CCB274E" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "PipelineStack/Project/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CodeBuild::Project", + "aws:cdk:cloudformation:props": { + "artifacts": { + "type": "CODEPIPELINE" + }, + "environment": { + "type": "LINUX_CONTAINER", + "image": "aws/codebuild/standard:1.0", + "imagePullCredentialsType": "CODEBUILD", + "privilegedMode": false, + "computeType": "BUILD_GENERAL1_SMALL" + }, + "serviceRole": { + "Fn::GetAtt": [ + "ProjectRole4CCB274E", + "Arn" + ] + }, + "source": { + "type": "CODEPIPELINE" + }, + "cache": { + "type": "NO_CACHE" + }, + "encryptionKey": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codebuild.CfnProject", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codebuild.PipelineProject", + "version": "0.0.0" + } + }, + "Pipeline": { + "id": "Pipeline", + "path": "PipelineStack/Pipeline", + "children": { + "ArtifactsBucketEncryptionKey": { + "id": "ArtifactsBucketEncryptionKey", + "path": "PipelineStack/Pipeline/ArtifactsBucketEncryptionKey", + "children": { + "Resource": { + "id": "Resource", + "path": "PipelineStack/Pipeline/ArtifactsBucketEncryptionKey/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::KMS::Key", + "aws:cdk:cloudformation:props": { + "keyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-kms.CfnKey", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-kms.Key", + "version": "0.0.0" + } + }, + "ArtifactsBucketEncryptionKeyAlias": { + "id": "ArtifactsBucketEncryptionKeyAlias", + "path": "PipelineStack/Pipeline/ArtifactsBucketEncryptionKeyAlias", + "children": { + "Resource": { + "id": "Resource", + "path": "PipelineStack/Pipeline/ArtifactsBucketEncryptionKeyAlias/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::KMS::Alias", + "aws:cdk:cloudformation:props": { + "aliasName": "alias/codepipeline-pipelinestack-pipeline-9db740af", + "targetKeyId": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-kms.CfnAlias", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-kms.Alias", + "version": "0.0.0" + } + }, + "ArtifactsBucket": { + "id": "ArtifactsBucket", + "path": "PipelineStack/Pipeline/ArtifactsBucket", + "children": { + "Resource": { + "id": "Resource", + "path": "PipelineStack/Pipeline/ArtifactsBucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "bucketEncryption": { + "serverSideEncryptionConfiguration": [ + { + "serverSideEncryptionByDefault": { + "sseAlgorithm": "aws:kms", + "kmsMasterKeyId": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + } + } + ] + }, + "publicAccessBlockConfiguration": { + "blockPublicAcls": true, + "blockPublicPolicy": true, + "ignorePublicAcls": true, + "restrictPublicBuckets": true + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucket", + "version": "0.0.0" + } + }, + "Policy": { + "id": "Policy", + "path": "PipelineStack/Pipeline/ArtifactsBucket/Policy", + "children": { + "Resource": { + "id": "Resource", + "path": "PipelineStack/Pipeline/ArtifactsBucket/Policy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::BucketPolicy", + "aws:cdk:cloudformation:props": { + "bucket": { + "Ref": "PipelineArtifactsBucket22248F97" + }, + "policyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + }, + "Effect": "Deny", + "Principal": { + "AWS": "*" + }, + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.BucketPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.Bucket", + "version": "0.0.0" + } + }, + "Role": { + "id": "Role", + "path": "PipelineStack/Pipeline/Role", + "children": { + "Resource": { + "id": "Resource", + "path": "PipelineStack/Pipeline/Role/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "codepipeline.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "PipelineStack/Pipeline/Role/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "PipelineStack/Pipeline/Role/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineBuildCodePipelineActionRoleD77A08E6", + "Arn" + ] + }, + { + "Fn::GetAtt": [ + "PipelineSourceCodePipelineActionRoleC6F9E7F5", + "Arn" + ] + } + ] + } + ], + "Version": "2012-10-17" + }, + "policyName": "PipelineRoleDefaultPolicyC7A05455", + "roles": [ + { + "Ref": "PipelineRoleD68726F7" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "PipelineStack/Pipeline/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CodePipeline::Pipeline", + "aws:cdk:cloudformation:props": { + "roleArn": { + "Fn::GetAtt": [ + "PipelineRoleD68726F7", + "Arn" + ] + }, + "stages": [ + { + "name": "Source", + "actions": [ + { + "name": "Source", + "outputArtifacts": [ + { + "name": "Artifact_Source_Source" + } + ], + "actionTypeId": { + "category": "Source", + "version": "1", + "owner": "AWS", + "provider": "S3" + }, + "configuration": { + "S3Bucket": { + "Fn::ImportValue": "BucketStack:ExportsOutputRefBucket83908E7781C90AC0" + }, + "S3ObjectKey": "file.zip", + "PollForSourceChanges": false + }, + "runOrder": 1, + "roleArn": { + "Fn::GetAtt": [ + "PipelineSourceCodePipelineActionRoleC6F9E7F5", + "Arn" + ] + } + } + ] + }, + { + "name": "Build", + "actions": [ + { + "name": "Build", + "inputArtifacts": [ + { + "name": "Artifact_Source_Source" + } + ], + "actionTypeId": { + "category": "Build", + "version": "1", + "owner": "AWS", + "provider": "CodeBuild" + }, + "configuration": { + "ProjectName": { + "Ref": "ProjectC78D97AD" + } + }, + "runOrder": 1, + "roleArn": { + "Fn::GetAtt": [ + "PipelineBuildCodePipelineActionRoleD77A08E6", + "Arn" + ] + } + } + ] + } + ], + "artifactStore": { + "type": "S3", + "location": { + "Ref": "PipelineArtifactsBucket22248F97" + }, + "encryptionKey": { + "type": "KMS", + "id": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + } + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codepipeline.CfnPipeline", + "version": "0.0.0" + } + }, + "Source": { + "id": "Source", + "path": "PipelineStack/Pipeline/Source", + "children": { + "Source": { + "id": "Source", + "path": "PipelineStack/Pipeline/Source/Source", + "children": { + "CodePipelineActionRole": { + "id": "CodePipelineActionRole", + "path": "PipelineStack/Pipeline/Source/Source/CodePipelineActionRole", + "children": { + "Resource": { + "id": "Resource", + "path": "PipelineStack/Pipeline/Source/Source/CodePipelineActionRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "PipelineStack/Pipeline/Source/Source/CodePipelineActionRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "PipelineStack/Pipeline/Source/Source/CodePipelineActionRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "s3:GetBucket*", + "s3:GetObject*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::ImportValue": "BucketStack:ExportsOutputFnGetAttBucket83908E77Arn063C8555" + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::ImportValue": "BucketStack:ExportsOutputFnGetAttBucket83908E77Arn063C8555" + }, + "/file.zip" + ] + ] + } + ] + }, + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject*", + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging" + ], + "Effect": "Allow", + "Resource": [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "PipelineArtifactsBucket22248F97", + "Arn" + ] + }, + "/*" + ] + ] + } + ] + }, + { + "Action": [ + "kms:Decrypt", + "kms:Encrypt", + "kms:GenerateDataKey*", + "kms:ReEncrypt*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "PipelineArtifactsBucketEncryptionKey01D58D69", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "PipelineSourceCodePipelineActionRoleDefaultPolicy2D565925", + "roles": [ + { + "Ref": "PipelineSourceCodePipelineActionRoleC6F9E7F5" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + }, + "PipelineStackPipeline9DB740AFSourceEventRulefile.zip": { + "id": "PipelineStackPipeline9DB740AFSourceEventRulefile.zip", + "path": "PipelineStack/Pipeline/PipelineStackPipeline9DB740AFSourceEventRulefile.zip", + "children": { + "Resource": { + "id": "Resource", + "path": "PipelineStack/Pipeline/PipelineStackPipeline9DB740AFSourceEventRulefile.zip/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Events::Rule", + "aws:cdk:cloudformation:props": { + "eventPattern": { + "source": [ + "aws.s3" + ], + "detail-type": [ + "AWS API Call via CloudTrail" + ], + "detail": { + "resources": { + "ARN": [ + { + "Fn::Join": [ + "", + [ + { + "Fn::ImportValue": "BucketStack:ExportsOutputFnGetAttBucket83908E77Arn063C8555" + }, + "/file.zip" + ] + ] + } + ] + }, + "eventName": [ + "CompleteMultipartUpload", + "CopyObject", + "PutObject" + ], + "requestParameters": { + "bucketName": [ + { + "Fn::ImportValue": "BucketStack:ExportsOutputRefBucket83908E7781C90AC0" + } + ], + "key": [ + "file.zip" + ] + } + } + }, + "state": "ENABLED", + "targets": [ + { + "id": "Target0", + "arn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codepipeline:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "PipelineC660917D" + } + ] + ] + }, + "roleArn": { + "Fn::GetAtt": [ + "PipelineEventsRole46BEEA7C", + "Arn" + ] + } + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-events.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-events.Rule", + "version": "0.0.0" + } + }, + "EventsRole": { + "id": "EventsRole", + "path": "PipelineStack/Pipeline/EventsRole", + "children": { + "Resource": { + "id": "Resource", + "path": "PipelineStack/Pipeline/EventsRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "PipelineStack/Pipeline/EventsRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "PipelineStack/Pipeline/EventsRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": "codepipeline:StartPipelineExecution", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":codepipeline:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":", + { + "Ref": "PipelineC660917D" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "PipelineEventsRoleDefaultPolicyFF4FCCE0", + "roles": [ + { + "Ref": "PipelineEventsRole46BEEA7C" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + }, + "Build": { + "id": "Build", + "path": "PipelineStack/Pipeline/Build", + "children": { + "Build": { + "id": "Build", + "path": "PipelineStack/Pipeline/Build/Build", + "children": { + "CodePipelineActionRole": { + "id": "CodePipelineActionRole", + "path": "PipelineStack/Pipeline/Build/Build/CodePipelineActionRole", + "children": { + "Resource": { + "id": "Resource", + "path": "PipelineStack/Pipeline/Build/Build/CodePipelineActionRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnRole", + "version": "0.0.0" + } + }, + "DefaultPolicy": { + "id": "DefaultPolicy", + "path": "PipelineStack/Pipeline/Build/Build/CodePipelineActionRole/DefaultPolicy", + "children": { + "Resource": { + "id": "Resource", + "path": "PipelineStack/Pipeline/Build/Build/CodePipelineActionRole/DefaultPolicy/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Policy", + "aws:cdk:cloudformation:props": { + "policyDocument": { + "Statement": [ + { + "Action": [ + "codebuild:BatchGetBuilds", + "codebuild:StartBuild", + "codebuild:StopBuild" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "ProjectC78D97AD", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "policyName": "PipelineBuildCodePipelineActionRoleDefaultPolicyC9CB73F8", + "roles": [ + { + "Ref": "PipelineBuildCodePipelineActionRoleD77A08E6" + } + ] + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.CfnPolicy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Policy", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-iam.Role", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.63" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-codepipeline.Pipeline", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-events/lib/on-event-options.ts b/packages/@aws-cdk/aws-events/lib/on-event-options.ts index 57fad3514c2f8..ff3d57652b46e 100644 --- a/packages/@aws-cdk/aws-events/lib/on-event-options.ts +++ b/packages/@aws-cdk/aws-events/lib/on-event-options.ts @@ -1,17 +1,11 @@ +import { Construct } from 'constructs'; import { EventPattern } from './event-pattern'; import { IRuleTarget } from './target'; /** - * Standard set of options for `onXxx` event handlers on construct + * Common options for Events. */ -export interface OnEventOptions { - /** - * The target to register for the event - * - * @default - No target is added to the rule. Use `addTarget()` to add a target. - */ - readonly target?: IRuleTarget; - +export interface EventCommonOptions { /** * A description of the rule's purpose. * @@ -39,4 +33,25 @@ export interface OnEventOptions { * https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-and-event-patterns.html */ readonly eventPattern?: EventPattern; + + /** + * The scope to use if the source of the rule and its target are in different Stacks + * (but in the same account & region). + * This helps dealing with cycles that often arise in these situations. + * + * @default - none (the main scope will be used, even for cross-stack Events) + */ + readonly crossStackScope?: Construct; +} + +/** + * Standard set of options for `onXxx` event handlers on construct + */ +export interface OnEventOptions extends EventCommonOptions { + /** + * The target to register for the event + * + * @default - No target is added to the rule. Use `addTarget()` to add a target. + */ + readonly target?: IRuleTarget; } diff --git a/packages/@aws-cdk/aws-events/lib/rule.ts b/packages/@aws-cdk/aws-events/lib/rule.ts index 5ef09a21dc69d..3d912ec561254 100644 --- a/packages/@aws-cdk/aws-events/lib/rule.ts +++ b/packages/@aws-cdk/aws-events/lib/rule.ts @@ -1,9 +1,10 @@ import { IRole, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; -import { App, IResource, Lazy, Names, Resource, Stack, Token, PhysicalName, ArnFormat } from '@aws-cdk/core'; +import { App, IResource, Lazy, Names, Resource, Stack, Token, TokenComparison, PhysicalName, ArnFormat } from '@aws-cdk/core'; import { Node, Construct } from 'constructs'; import { IEventBus } from './event-bus'; import { EventPattern } from './event-pattern'; import { CfnEventBusPolicy, CfnRule } from './events.generated'; +import { EventCommonOptions } from './on-event-options'; import { IRule } from './rule-ref'; import { Schedule } from './schedule'; import { IRuleTarget } from './target'; @@ -12,22 +13,7 @@ import { mergeEventPattern, renderEventPattern, sameEnvDimension } from './util' /** * Properties for defining an EventBridge Rule */ -export interface RuleProps { - /** - * A description of the rule's purpose. - * - * @default - No description. - */ - readonly description?: string; - - /** - * A name for the rule. - * - * @default - AWS CloudFormation generates a unique physical ID and uses that ID - * for the rule name. For more information, see Name Type. - */ - readonly ruleName?: string; - +export interface RuleProps extends EventCommonOptions { /** * Indicates whether the rule is enabled. * @@ -50,23 +36,6 @@ export interface RuleProps { */ readonly schedule?: Schedule; - /** - * Describes which events EventBridge routes to the specified target. - * These routed events are matched events. - * - * You must specify this property (either via props or via - * `addEventPattern`), the `scheduleExpression` property, or both. The - * method `addEventPattern` can be used to add filter values to the event - * pattern. - * - * For more information, see Events and Event Patterns in the Amazon EventBridge User Guide. - * - * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-and-event-patterns.html - * - * @default - None. - */ - readonly eventPattern?: EventPattern; - /** * Targets to invoke when this rule matches an event. * @@ -121,7 +90,7 @@ export class Rule extends Resource implements IRule { private readonly _xEnvTargetsAdded = new Set(); constructor(scope: Construct, id: string, props: RuleProps = { }) { - super(scope, id, { + super(determineRuleScope(scope, props), id, { physicalName: props.ruleName, }); @@ -456,6 +425,24 @@ export class Rule extends Resource implements IRule { } } +function determineRuleScope(scope: Construct, props: RuleProps): Construct { + if (!props.crossStackScope) { + return scope; + } + const scopeStack = Stack.of(scope); + const targetStack = Stack.of(props.crossStackScope); + if (scopeStack === targetStack) { + return scope; + } + // cross-region/account Events require their own setup, + // so we use the base scope in that case + const regionComparison = Token.compareStrings(scopeStack.region, targetStack.region); + const accountComparison = Token.compareStrings(scopeStack.account, targetStack.account); + const stacksInSameAccountAndRegion = (regionComparison === TokenComparison.SAME || regionComparison === TokenComparison.BOTH_UNRESOLVED) && + (accountComparison === TokenComparison.SAME || accountComparison === TokenComparison.BOTH_UNRESOLVED); + return stacksInSameAccountAndRegion ? props.crossStackScope : scope; +} + /** * A rule that mirrors another rule */