From be3be367efc8ffc1339d96a8c234b81db9a0d5de Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Thu, 4 Nov 2021 00:42:21 +0900 Subject: [PATCH 1/7] feat(iot-actions): Add the action to put s3 bucket objects 1. add the action 2. add tests 3. describe to README --- package.json | 2 + packages/@aws-cdk/aws-iot-actions/NOTICE | 30 +++ packages/@aws-cdk/aws-iot-actions/README.md | 19 ++ .../@aws-cdk/aws-iot-actions/lib/index.ts | 1 + .../@aws-cdk/aws-iot-actions/lib/s3-action.ts | 77 +++++++ .../@aws-cdk/aws-iot-actions/package.json | 6 + .../test/s3/integ.s3-action.expected.json | 85 +++++++ .../test/s3/integ.s3-action.ts | 25 ++ .../aws-iot-actions/test/s3/s3-action.test.ts | 213 ++++++++++++++++++ 9 files changed, 458 insertions(+) create mode 100644 packages/@aws-cdk/aws-iot-actions/lib/s3-action.ts create mode 100644 packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.expected.json create mode 100644 packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.ts create mode 100644 packages/@aws-cdk/aws-iot-actions/test/s3/s3-action.test.ts diff --git a/package.json b/package.json index e0f41dbbeabf1..024f771f1666b 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,8 @@ "@aws-cdk/aws-eks/yaml/**", "@aws-cdk/aws-events-targets/aws-sdk", "@aws-cdk/aws-events-targets/aws-sdk/**", + "@aws-cdk/aws-iot-actions/case", + "@aws-cdk/aws-iot-actions/case/**", "@aws-cdk/aws-s3-deployment/case", "@aws-cdk/aws-s3-deployment/case/**", "@aws-cdk/cloud-assembly-schema/jsonschema", diff --git a/packages/@aws-cdk/aws-iot-actions/NOTICE b/packages/@aws-cdk/aws-iot-actions/NOTICE index 5fc3826926b5b..39cd25bf899ae 100644 --- a/packages/@aws-cdk/aws-iot-actions/NOTICE +++ b/packages/@aws-cdk/aws-iot-actions/NOTICE @@ -1,2 +1,32 @@ AWS Cloud Development Kit (AWS CDK) Copyright 2018-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +------------------------------------------------------------------------------- + +The AWS CDK includes the following third-party software/licensing: + +** case - https://www.npmjs.com/package/case +Copyright (c) 2013 Nathan Bubna + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +---------------- diff --git a/packages/@aws-cdk/aws-iot-actions/README.md b/packages/@aws-cdk/aws-iot-actions/README.md index b18182a80a9ad..ba6f220ccaa40 100644 --- a/packages/@aws-cdk/aws-iot-actions/README.md +++ b/packages/@aws-cdk/aws-iot-actions/README.md @@ -49,6 +49,25 @@ new iot.TopicRule(this, 'TopicRule', { }); ``` +## Put objects to a S3 bucket + +The code snippet below creates an AWS IoT Rule that put objects to a S3 bucket +when it is triggered. + +```ts +import * as iot from '@aws-cdk/aws-iot'; +import * as actions from '@aws-cdk/aws-iot-actions'; +import * as s3 from '@aws-cdk/aws-s3'; + +const bucket = new s3.Bucket(this, 'MyBucket'); + +new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + actions: [new actions.S3Action(bucket)], +}); +``` + + ## Put logs to CloudWatch Logs The code snippet below creates an AWS IoT Rule that put logs to CloudWatch Logs diff --git a/packages/@aws-cdk/aws-iot-actions/lib/index.ts b/packages/@aws-cdk/aws-iot-actions/lib/index.ts index ef917fd0e2181..41d25a8ded648 100644 --- a/packages/@aws-cdk/aws-iot-actions/lib/index.ts +++ b/packages/@aws-cdk/aws-iot-actions/lib/index.ts @@ -1,2 +1,3 @@ export * from './cloudwatch-logs-action'; export * from './lambda-function-action'; +export * from './s3-action'; diff --git a/packages/@aws-cdk/aws-iot-actions/lib/s3-action.ts b/packages/@aws-cdk/aws-iot-actions/lib/s3-action.ts new file mode 100644 index 0000000000000..14421d26af143 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/lib/s3-action.ts @@ -0,0 +1,77 @@ +import * as iam from '@aws-cdk/aws-iam'; +import * as iot from '@aws-cdk/aws-iot'; +import * as s3 from '@aws-cdk/aws-s3'; +import { kebab as toKebabCase } from 'case'; +import { singletonActionRole } from './private/role'; + +/** + * Configuration properties of an action for s3. + */ +export interface S3ActionProps { + /** + * The Amazon S3 canned ACL that controls access to the object identified by the object key. + * @see https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html#canned-acl + * + * @default None + */ + readonly cannedAcl?: s3.BucketAccessControl; + + /** + * The path to the file where the data is written. + * + * Supports substitution templates. + * @see https://docs.aws.amazon.com/iot/latest/developerguide/iot-substitution-templates.html + * + * @default '${topic()}/${timestamp()}' + */ + readonly key?: string; + + /** + * The IAM role that allows access to the S3. + * + * @default a new role will be created + */ + readonly role?: iam.IRole; +} + +/** + * The action to write the data from an MQTT message to an Amazon S3 bucket. + */ +export class S3Action implements iot.IAction { + private readonly cannedAcl?: string; + private readonly key?: string; + private readonly role?: iam.IRole; + + /** + * @param bucket The Amazon S3 bucket to which to write data. + * @param props Optional properties to not use default + */ + constructor(private readonly bucket: s3.IBucket, props: S3ActionProps = {}) { + this.cannedAcl = props.cannedAcl; + this.key = props.key; + this.role = props.role; + } + + bind(rule: iot.ITopicRule): iot.ActionConfig { + const role = this.role ?? singletonActionRole(rule); + role.addToPrincipalPolicy(this.putEventStatement(this.bucket)); + + return { + configuration: { + s3: { + bucketName: this.bucket.bucketName, + cannedAcl: this.cannedAcl && toKebabCase(this.cannedAcl.toString()), + key: this.key ?? '${topic()}/${timestamp()}', + roleArn: role.roleArn, + }, + }, + }; + } + + private putEventStatement(bucket: s3.IBucket) { + return new iam.PolicyStatement({ + actions: ['s3:PutObject'], + resources: [bucket.arnForObjects('*')], + }); + } +} diff --git a/packages/@aws-cdk/aws-iot-actions/package.json b/packages/@aws-cdk/aws-iot-actions/package.json index d60ae4c5e1376..c3be29c0cdd8c 100644 --- a/packages/@aws-cdk/aws-iot-actions/package.json +++ b/packages/@aws-cdk/aws-iot-actions/package.json @@ -83,7 +83,9 @@ "@aws-cdk/aws-iot": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", + "case": "1.6.3", "constructs": "^3.3.69" }, "homepage": "https://github.com/aws/aws-cdk", @@ -92,9 +94,13 @@ "@aws-cdk/aws-iot": "0.0.0", "@aws-cdk/aws-lambda": "0.0.0", "@aws-cdk/aws-logs": "0.0.0", + "@aws-cdk/aws-s3": "0.0.0", "@aws-cdk/core": "0.0.0", "constructs": "^3.3.69" }, + "bundledDependencies": [ + "case" + ], "engines": { "node": ">= 10.13.0 <13 || >=13.7.0" }, diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.expected.json b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.expected.json new file mode 100644 index 0000000000000..65932d45094c5 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.expected.json @@ -0,0 +1,85 @@ +{ + "Resources": { + "TopicRule40A4EA44": { + "Type": "AWS::IoT::TopicRule", + "Properties": { + "TopicRulePayload": { + "Actions": [ + { + "S3": { + "BucketName": { + "Ref": "MyBucketF68F3FF0" + }, + "Key": "${topic()}/${timestamp()}", + "RoleArn": { + "Fn::GetAtt": [ + "TopicRuleTopicRuleActionRole246C4F77", + "Arn" + ] + } + } + } + ], + "AwsIotSqlVersion": "2016-03-23", + "Sql": "SELECT topic(2) as device_id FROM 'device/+/data'" + } + } + }, + "TopicRuleTopicRuleActionRole246C4F77": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "iot.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TopicRuleTopicRuleActionRoleDefaultPolicy99ADD687": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:PutObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "/*" + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "TopicRuleTopicRuleActionRoleDefaultPolicy99ADD687", + "Roles": [ + { + "Ref": "TopicRuleTopicRuleActionRole246C4F77" + } + ] + } + }, + "MyBucketF68F3FF0": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.ts b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.ts new file mode 100644 index 0000000000000..f6a8957caa3b3 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.ts @@ -0,0 +1,25 @@ +/// !cdk-integ pragma:ignore-assets +import * as iot from '@aws-cdk/aws-iot'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + +const app = new cdk.App(); + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const topicRule = new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + + const bucket = new s3.Bucket(this, 'MyBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + topicRule.addAction(new actions.S3Action(bucket)); + } +} + +new TestStack(app, 'test-stack'); +app.synth(); diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3/s3-action.test.ts b/packages/@aws-cdk/aws-iot-actions/test/s3/s3-action.test.ts new file mode 100644 index 0000000000000..d179fa1547948 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/test/s3/s3-action.test.ts @@ -0,0 +1,213 @@ +import { Template } from '@aws-cdk/assertions'; +import * as iam from '@aws-cdk/aws-iam'; +import * as iot from '@aws-cdk/aws-iot'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as actions from '../../lib'; + +test('Default s3 action', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const bucket = new s3.Bucket(stack, 'MyBucket'); + + // WHEN + topicRule.addAction( + new actions.S3Action(bucket), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + S3: { + BucketName: { Ref: 'MyBucketF68F3FF0' }, + Key: '${topic()}/${timestamp()}', + RoleArn: { + 'Fn::GetAtt': ['MyTopicRuleTopicRuleActionRoleCE2D05DA', 'Arn'], + }, + }, + }, + ], + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'iot.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + { 'Fn::GetAtt': ['MyBucketF68F3FF0', 'Arn'] }, + '/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyTopicRuleTopicRuleActionRoleDefaultPolicy54A701F7', + Roles: [ + { Ref: 'MyTopicRuleTopicRuleActionRoleCE2D05DA' }, + ], + }); +}); + +test('can set key of bucket', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const bucket = new s3.Bucket(stack, 'MyBucket'); + + // WHEN + topicRule.addAction( + new actions.S3Action(bucket, { + key: 'test-key', + }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + S3: { + BucketName: { Ref: 'MyBucketF68F3FF0' }, + Key: 'test-key', + RoleArn: { + 'Fn::GetAtt': ['MyTopicRuleTopicRuleActionRoleCE2D05DA', 'Arn'], + }, + }, + }, + ], + }, + }); +}); + +test('can set canned ACL and it convert to kebab case', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const bucket = new s3.Bucket(stack, 'MyBucket'); + + // WHEN + topicRule.addAction( + new actions.S3Action(bucket, { + cannedAcl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL, + }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + S3: { + BucketName: { Ref: 'MyBucketF68F3FF0' }, + Key: '${topic()}/${timestamp()}', + CannedAcl: 'bucket-owner-full-control', + RoleArn: { + 'Fn::GetAtt': ['MyTopicRuleTopicRuleActionRoleCE2D05DA', 'Arn'], + }, + }, + }, + ], + }, + }); +}); + +test('can set role', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const bucket = new s3.Bucket(stack, 'MyBucket'); + const role = iam.Role.fromRoleArn(stack, 'MyRole', 'arn:aws:iam::123456789012:role/ForTest'); + + // WHEN + topicRule.addAction( + new actions.S3Action(bucket, { role }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { + TopicRulePayload: { + Actions: [ + { + S3: { + BucketName: { Ref: 'MyBucketF68F3FF0' }, + Key: '${topic()}/${timestamp()}', + RoleArn: 'arn:aws:iam::123456789012:role/ForTest', + }, + }, + ], + }, + }); +}); + +test('The specified role is added a policy needed for putting item to a bucket', () => { + // GIVEN + const stack = new cdk.Stack(); + const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + }); + const bucket = new s3.Bucket(stack, 'MyBucket'); + const role = iam.Role.fromRoleArn(stack, 'MyRole', 'arn:aws:iam::123456789012:role/ForTest'); + + // WHEN + topicRule.addAction( + new actions.S3Action(bucket, { role }), + ); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + { 'Fn::GetAtt': ['MyBucketF68F3FF0', 'Arn'] }, + '/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'MyRolePolicy64AB00A5', + Roles: ['ForTest'], + }); +}); From b81a310065db22dc020e8b834da07b5760e480ac Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Thu, 4 Nov 2021 02:19:49 +0900 Subject: [PATCH 2/7] fix(iot-actions): suppress an error in CodeBuild CI In CodeBuild CI, there was following error: ``` @aws-cdk/aws-iot-actions-alpha: - [yarn/nohoist-bundled-dependencies] Repository-level 'workspaces.nohoist' directive is missing: @aws-cdk/aws-iot-actions-alpha/case, @aws-cdk/aws-iot-actions-alpha/case/** (fixable) @aws-cdk/aws-iot-actions-alpha: Error: Some package.json files had errors @aws-cdk/aws-iot-actions-alpha: at main (/codebuild/output/src514100616/src/github.com/aws/aws-cdk/tools/@aws-cdk/pkglint/bin/pkglint.js:30:15) @aws-cdk/aws-iot-actions-alpha: at Object. (/codebuild/output/src514100616/src/github.com/aws/aws-cdk/tools/@aws-cdk/pkglint/bin/pkglint.js:33:1) @aws-cdk/aws-iot-actions-alpha: at Module._compile (internal/modules/cjs/loader.js:999:30) @aws-cdk/aws-iot-actions-alpha: at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10) @aws-cdk/aws-iot-actions-alpha: at Module.load (internal/modules/cjs/loader.js:863:32) @aws-cdk/aws-iot-actions-alpha: at Function.Module._load (internal/modules/cjs/loader.js:708:14) @aws-cdk/aws-iot-actions-alpha: at Module.require (internal/modules/cjs/loader.js:887:19) @aws-cdk/aws-iot-actions-alpha: at require (internal/modules/cjs/helpers.js:74:18) @aws-cdk/aws-iot-actions-alpha: at Object. (/codebuild/output/src514100616/src/github.com/aws/aws-cdk/tools/@aws-cdk/pkglint/bin/pkglint:2:1) @aws-cdk/aws-iot-actions-alpha: at Module._compile (internal/modules/cjs/loader.js:999:30) ``` --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 024f771f1666b..c7e9dd8f57e4a 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,8 @@ "@aws-cdk/assertions-alpha/table/**", "@aws-cdk/aws-amplify-alpha/yaml", "@aws-cdk/aws-amplify-alpha/yaml/**", + "@aws-cdk/aws-iot-actions-alpha/case", + "@aws-cdk/aws-iot-actions-alpha/case/**", "@aws-cdk/assertions/colors", "@aws-cdk/assertions/colors/**", "@aws-cdk/assertions/diff", From 4b66370501791d7a0274afa7d718f89231de7292 Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Sun, 7 Nov 2021 22:34:59 +0900 Subject: [PATCH 3/7] fix(iot-actions): address comments --- packages/@aws-cdk/aws-iot-actions/README.md | 25 +++++++- .../@aws-cdk/aws-iot-actions/lib/s3-action.ts | 6 +- .../test/s3/integ.s3-action.expected.json | 4 +- .../test/s3/integ.s3-action.ts | 10 +++- .../aws-iot-actions/test/s3/s3-action.test.ts | 59 +++++-------------- 5 files changed, 51 insertions(+), 53 deletions(-) diff --git a/packages/@aws-cdk/aws-iot-actions/README.md b/packages/@aws-cdk/aws-iot-actions/README.md index ba6f220ccaa40..904a9d96828f2 100644 --- a/packages/@aws-cdk/aws-iot-actions/README.md +++ b/packages/@aws-cdk/aws-iot-actions/README.md @@ -22,6 +22,8 @@ supported AWS Services. Instances of these classes should be passed to Currently supported are: - Invoke a Lambda function +- Put objects to a S3 bucket +- Put logs to CloudWatch Logs ## Invoke a Lambda function @@ -63,10 +65,31 @@ const bucket = new s3.Bucket(this, 'MyBucket'); new iot.TopicRule(this, 'TopicRule', { sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), - actions: [new actions.S3Action(bucket)], + actions: [new actions.S3PutObjectAction(bucket)], }); ``` +The property `key` of `S3PutObjectAction` is given the value `${topic()}/${timestamp()}` by default. This `${topic()}` +and `${timestamp()}` is called Substitution templates. For more information see +[this documentation](https://docs.aws.amazon.com/iot/latest/developerguide/iot-substitution-templates.html). +In above sample, `${topic()}` is replaced by a given MQTT topic as `device/001/data`. And `${timestamp()}` is replaced +by the number of the current timestamp in milliseconds as `1636289461203`. So if the MQTT broker receives an MQTT topic +`device/001/data` on `2021-11-07T00:00:00.000Z`, the S3 bucket object will be put to `device/001/data/1636243200000`. + +You can also set specific `key` as following: + +```ts +new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323( + "SELECT topic(2) as device_id, year, month, day FROM 'device/+/data'", + ), + actions: [ + new actions.S3PutObjectAction(bucket, { + key: '${year}/${month}/${day}/${topic(2)}', + }), + ], +}); +``` ## Put logs to CloudWatch Logs diff --git a/packages/@aws-cdk/aws-iot-actions/lib/s3-action.ts b/packages/@aws-cdk/aws-iot-actions/lib/s3-action.ts index 14421d26af143..9c912c90b9a42 100644 --- a/packages/@aws-cdk/aws-iot-actions/lib/s3-action.ts +++ b/packages/@aws-cdk/aws-iot-actions/lib/s3-action.ts @@ -7,7 +7,7 @@ import { singletonActionRole } from './private/role'; /** * Configuration properties of an action for s3. */ -export interface S3ActionProps { +export interface S3PutObjectActionProps { /** * The Amazon S3 canned ACL that controls access to the object identified by the object key. * @see https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html#canned-acl @@ -37,7 +37,7 @@ export interface S3ActionProps { /** * The action to write the data from an MQTT message to an Amazon S3 bucket. */ -export class S3Action implements iot.IAction { +export class S3PutObjectAction implements iot.IAction { private readonly cannedAcl?: string; private readonly key?: string; private readonly role?: iam.IRole; @@ -46,7 +46,7 @@ export class S3Action implements iot.IAction { * @param bucket The Amazon S3 bucket to which to write data. * @param props Optional properties to not use default */ - constructor(private readonly bucket: s3.IBucket, props: S3ActionProps = {}) { + constructor(private readonly bucket: s3.IBucket, props: S3PutObjectActionProps = {}) { this.cannedAcl = props.cannedAcl; this.key = props.key; this.role = props.role; diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.expected.json b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.expected.json index 65932d45094c5..698e1082cd8b1 100644 --- a/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.expected.json +++ b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.expected.json @@ -10,7 +10,7 @@ "BucketName": { "Ref": "MyBucketF68F3FF0" }, - "Key": "${topic()}/${timestamp()}", + "Key": "${year}/${month}/${day}/${topic(2)}", "RoleArn": { "Fn::GetAtt": [ "TopicRuleTopicRuleActionRole246C4F77", @@ -21,7 +21,7 @@ } ], "AwsIotSqlVersion": "2016-03-23", - "Sql": "SELECT topic(2) as device_id FROM 'device/+/data'" + "Sql": "SELECT topic(2) as device_id, year, month, day FROM 'device/+/data'" } } }, diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.ts b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.ts index f6a8957caa3b3..108db163eb5fe 100644 --- a/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.ts +++ b/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.ts @@ -11,13 +11,19 @@ class TestStack extends cdk.Stack { super(scope, id, props); const topicRule = new iot.TopicRule(this, 'TopicRule', { - sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), + sql: iot.IotSql.fromStringAsVer20160323( + "SELECT topic(2) as device_id, year, month, day FROM 'device/+/data'", + ), }); const bucket = new s3.Bucket(this, 'MyBucket', { removalPolicy: cdk.RemovalPolicy.DESTROY, }); - topicRule.addAction(new actions.S3Action(bucket)); + topicRule.addAction( + new actions.S3PutObjectAction(bucket, { + key: '${year}/${month}/${day}/${topic(2)}', + }), + ); } } diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3/s3-action.test.ts b/packages/@aws-cdk/aws-iot-actions/test/s3/s3-action.test.ts index d179fa1547948..61de8b24095bf 100644 --- a/packages/@aws-cdk/aws-iot-actions/test/s3/s3-action.test.ts +++ b/packages/@aws-cdk/aws-iot-actions/test/s3/s3-action.test.ts @@ -11,11 +11,11 @@ test('Default s3 action', () => { const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), }); - const bucket = new s3.Bucket(stack, 'MyBucket'); + const bucket = s3.Bucket.fromBucketArn(stack, 'MyBucket', 'arn:aws:s3::123456789012:test-bucket'); // WHEN topicRule.addAction( - new actions.S3Action(bucket), + new actions.S3PutObjectAction(bucket), ); // THEN @@ -24,7 +24,7 @@ test('Default s3 action', () => { Actions: [ { S3: { - BucketName: { Ref: 'MyBucketF68F3FF0' }, + BucketName: 'test-bucket', Key: '${topic()}/${timestamp()}', RoleArn: { 'Fn::GetAtt': ['MyTopicRuleTopicRuleActionRoleCE2D05DA', 'Arn'], @@ -56,15 +56,7 @@ test('Default s3 action', () => { { Action: 's3:PutObject', Effect: 'Allow', - Resource: { - 'Fn::Join': [ - '', - [ - { 'Fn::GetAtt': ['MyBucketF68F3FF0', 'Arn'] }, - '/*', - ], - ], - }, + Resource: 'arn:aws:s3::123456789012:test-bucket/*', }, ], Version: '2012-10-17', @@ -82,11 +74,11 @@ test('can set key of bucket', () => { const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), }); - const bucket = new s3.Bucket(stack, 'MyBucket'); + const bucket = s3.Bucket.fromBucketArn(stack, 'MyBucket', 'arn:aws:s3::123456789012:test-bucket'); // WHEN topicRule.addAction( - new actions.S3Action(bucket, { + new actions.S3PutObjectAction(bucket, { key: 'test-key', }), ); @@ -97,7 +89,7 @@ test('can set key of bucket', () => { Actions: [ { S3: { - BucketName: { Ref: 'MyBucketF68F3FF0' }, + BucketName: 'test-bucket', Key: 'test-key', RoleArn: { 'Fn::GetAtt': ['MyTopicRuleTopicRuleActionRoleCE2D05DA', 'Arn'], @@ -115,11 +107,11 @@ test('can set canned ACL and it convert to kebab case', () => { const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), }); - const bucket = new s3.Bucket(stack, 'MyBucket'); + const bucket = s3.Bucket.fromBucketArn(stack, 'MyBucket', 'arn:aws:s3::123456789012:test-bucket'); // WHEN topicRule.addAction( - new actions.S3Action(bucket, { + new actions.S3PutObjectAction(bucket, { cannedAcl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL, }), ); @@ -130,7 +122,7 @@ test('can set canned ACL and it convert to kebab case', () => { Actions: [ { S3: { - BucketName: { Ref: 'MyBucketF68F3FF0' }, + BucketName: 'test-bucket', Key: '${topic()}/${timestamp()}', CannedAcl: 'bucket-owner-full-control', RoleArn: { @@ -149,12 +141,12 @@ test('can set role', () => { const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), }); - const bucket = new s3.Bucket(stack, 'MyBucket'); + const bucket = s3.Bucket.fromBucketArn(stack, 'MyBucket', 'arn:aws:s3::123456789012:test-bucket'); const role = iam.Role.fromRoleArn(stack, 'MyRole', 'arn:aws:iam::123456789012:role/ForTest'); // WHEN topicRule.addAction( - new actions.S3Action(bucket, { role }), + new actions.S3PutObjectAction(bucket, { role }), ); // THEN @@ -163,7 +155,7 @@ test('can set role', () => { Actions: [ { S3: { - BucketName: { Ref: 'MyBucketF68F3FF0' }, + BucketName: 'test-bucket', Key: '${topic()}/${timestamp()}', RoleArn: 'arn:aws:iam::123456789012:role/ForTest', }, @@ -171,21 +163,6 @@ test('can set role', () => { ], }, }); -}); - -test('The specified role is added a policy needed for putting item to a bucket', () => { - // GIVEN - const stack = new cdk.Stack(); - const topicRule = new iot.TopicRule(stack, 'MyTopicRule', { - sql: iot.IotSql.fromStringAsVer20160323("SELECT topic(2) as device_id FROM 'device/+/data'"), - }); - const bucket = new s3.Bucket(stack, 'MyBucket'); - const role = iam.Role.fromRoleArn(stack, 'MyRole', 'arn:aws:iam::123456789012:role/ForTest'); - - // WHEN - topicRule.addAction( - new actions.S3Action(bucket, { role }), - ); // THEN Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { @@ -194,15 +171,7 @@ test('The specified role is added a policy needed for putting item to a bucket', { Action: 's3:PutObject', Effect: 'Allow', - Resource: { - 'Fn::Join': [ - '', - [ - { 'Fn::GetAtt': ['MyBucketF68F3FF0', 'Arn'] }, - '/*', - ], - ], - }, + Resource: 'arn:aws:s3::123456789012:test-bucket/*', }, ], Version: '2012-10-17', From 0a37bddefb27ab815ba75dc2b4e99c7ed64befdf Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Sun, 7 Nov 2021 22:39:35 +0900 Subject: [PATCH 4/7] fix(iot-actions): rename files --- packages/@aws-cdk/aws-iot-actions/lib/index.ts | 2 +- .../lib/{s3-action.ts => s3-put-object-action.ts} | 0 .../integ.s3-put-object-action.expected.json} | 0 .../integ.s3-put-object-action.ts} | 0 .../s3-put-object-action.test.ts} | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename packages/@aws-cdk/aws-iot-actions/lib/{s3-action.ts => s3-put-object-action.ts} (100%) rename packages/@aws-cdk/aws-iot-actions/test/{s3/integ.s3-action.expected.json => s3-put-object/integ.s3-put-object-action.expected.json} (100%) rename packages/@aws-cdk/aws-iot-actions/test/{s3/integ.s3-action.ts => s3-put-object/integ.s3-put-object-action.ts} (100%) rename packages/@aws-cdk/aws-iot-actions/test/{s3/s3-action.test.ts => s3-put-object/s3-put-object-action.test.ts} (100%) diff --git a/packages/@aws-cdk/aws-iot-actions/lib/index.ts b/packages/@aws-cdk/aws-iot-actions/lib/index.ts index 41d25a8ded648..bd3a908d0aca2 100644 --- a/packages/@aws-cdk/aws-iot-actions/lib/index.ts +++ b/packages/@aws-cdk/aws-iot-actions/lib/index.ts @@ -1,3 +1,3 @@ export * from './cloudwatch-logs-action'; export * from './lambda-function-action'; -export * from './s3-action'; +export * from './s3-put-object-action'; diff --git a/packages/@aws-cdk/aws-iot-actions/lib/s3-action.ts b/packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts similarity index 100% rename from packages/@aws-cdk/aws-iot-actions/lib/s3-action.ts rename to packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.expected.json b/packages/@aws-cdk/aws-iot-actions/test/s3-put-object/integ.s3-put-object-action.expected.json similarity index 100% rename from packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.expected.json rename to packages/@aws-cdk/aws-iot-actions/test/s3-put-object/integ.s3-put-object-action.expected.json diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.ts b/packages/@aws-cdk/aws-iot-actions/test/s3-put-object/integ.s3-put-object-action.ts similarity index 100% rename from packages/@aws-cdk/aws-iot-actions/test/s3/integ.s3-action.ts rename to packages/@aws-cdk/aws-iot-actions/test/s3-put-object/integ.s3-put-object-action.ts diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3/s3-action.test.ts b/packages/@aws-cdk/aws-iot-actions/test/s3-put-object/s3-put-object-action.test.ts similarity index 100% rename from packages/@aws-cdk/aws-iot-actions/test/s3/s3-action.test.ts rename to packages/@aws-cdk/aws-iot-actions/test/s3-put-object/s3-put-object-action.test.ts From 6fc324850a339ea9268979b1362bb824dbf54cd8 Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Tue, 9 Nov 2021 23:25:54 +0900 Subject: [PATCH 5/7] refactor(iot-actions): superinterface for action props --- .../aws-iot-actions/lib/cloudwatch-logs-action.ts | 9 ++------- .../lib/private/common-action-props.ts | 13 +++++++++++++ .../aws-iot-actions/lib/s3-put-object-action.ts | 10 ++-------- 3 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 packages/@aws-cdk/aws-iot-actions/lib/private/common-action-props.ts diff --git a/packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts b/packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts index dda14de887774..5a5fb8338a4b4 100644 --- a/packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts +++ b/packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts @@ -1,18 +1,13 @@ import * as iam from '@aws-cdk/aws-iam'; import * as iot from '@aws-cdk/aws-iot'; import * as logs from '@aws-cdk/aws-logs'; +import { CommonActionProps } from './private/common-action-props'; import { singletonActionRole } from './private/role'; /** * Configuration properties of an action for CloudWatch Logs. */ -export interface CloudWatchLogsActionProps { - /** - * The IAM role that allows access to the CloudWatch log group. - * - * @default a new role will be created - */ - readonly role?: iam.IRole; +export interface CloudWatchLogsActionProps extends CommonActionProps { } /** diff --git a/packages/@aws-cdk/aws-iot-actions/lib/private/common-action-props.ts b/packages/@aws-cdk/aws-iot-actions/lib/private/common-action-props.ts new file mode 100644 index 0000000000000..5a9b52d8b5f27 --- /dev/null +++ b/packages/@aws-cdk/aws-iot-actions/lib/private/common-action-props.ts @@ -0,0 +1,13 @@ +import * as iam from '@aws-cdk/aws-iam'; + +/** + * Common properties shared by Actions it access to AWS service. + */ +export interface CommonActionProps { + /** + * The IAM role that allows access to AWS service. + * + * @default a new role will be created + */ + readonly role?: iam.IRole; +} diff --git a/packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts b/packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts index 9c912c90b9a42..9ec28be95589b 100644 --- a/packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts +++ b/packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts @@ -2,12 +2,13 @@ import * as iam from '@aws-cdk/aws-iam'; import * as iot from '@aws-cdk/aws-iot'; import * as s3 from '@aws-cdk/aws-s3'; import { kebab as toKebabCase } from 'case'; +import { CommonActionProps } from './private/common-action-props'; import { singletonActionRole } from './private/role'; /** * Configuration properties of an action for s3. */ -export interface S3PutObjectActionProps { +export interface S3PutObjectActionProps extends CommonActionProps { /** * The Amazon S3 canned ACL that controls access to the object identified by the object key. * @see https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html#canned-acl @@ -25,13 +26,6 @@ export interface S3PutObjectActionProps { * @default '${topic()}/${timestamp()}' */ readonly key?: string; - - /** - * The IAM role that allows access to the S3. - * - * @default a new role will be created - */ - readonly role?: iam.IRole; } /** From f0a34807ccb0d4ea3b49cd3020457664d5b6e778 Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Wed, 10 Nov 2021 00:08:46 +0900 Subject: [PATCH 6/7] fix(iot-actions): fix jsii lint error as following: error JSII9002: Unable to resolve type "@aws-cdk/aws-iot-actions.CommonActionProps". It may be @internal or not exported from the module's entry point (as configured in "package.json" as "main"). --- .../@aws-cdk/aws-iot-actions/lib/private/common-action-props.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@aws-cdk/aws-iot-actions/lib/private/common-action-props.ts b/packages/@aws-cdk/aws-iot-actions/lib/private/common-action-props.ts index 5a9b52d8b5f27..df8f642439c37 100644 --- a/packages/@aws-cdk/aws-iot-actions/lib/private/common-action-props.ts +++ b/packages/@aws-cdk/aws-iot-actions/lib/private/common-action-props.ts @@ -2,6 +2,8 @@ import * as iam from '@aws-cdk/aws-iam'; /** * Common properties shared by Actions it access to AWS service. + * + * @internal */ export interface CommonActionProps { /** From f4c292c6f79af7b13ea04b09d8979b647aeb2ef6 Mon Sep 17 00:00:00 2001 From: yamatatsu Date: Wed, 10 Nov 2021 20:52:19 +0900 Subject: [PATCH 7/7] address comments --- packages/@aws-cdk/aws-iot-actions/README.md | 13 ++++++ .../lib/cloudwatch-logs-action.ts | 2 +- .../lib/{private => }/common-action-props.ts | 2 - .../@aws-cdk/aws-iot-actions/lib/index.ts | 1 + .../lib/s3-put-object-action.ts | 22 ++++------ .../integ.s3-put-object-action.expected.json | 1 + .../integ.s3-put-object-action.ts | 1 + .../s3-put-object-action.test.ts | 44 +++---------------- 8 files changed, 31 insertions(+), 55 deletions(-) rename packages/@aws-cdk/aws-iot-actions/lib/{private => }/common-action-props.ts (94%) diff --git a/packages/@aws-cdk/aws-iot-actions/README.md b/packages/@aws-cdk/aws-iot-actions/README.md index 904a9d96828f2..f9043d86bd447 100644 --- a/packages/@aws-cdk/aws-iot-actions/README.md +++ b/packages/@aws-cdk/aws-iot-actions/README.md @@ -91,6 +91,19 @@ new iot.TopicRule(this, 'TopicRule', { }); ``` +If you wanna set access control to the S3 bucket object, you can specify `accessControl` as following: + +```ts +new iot.TopicRule(this, 'TopicRule', { + sql: iot.IotSql.fromStringAsVer20160323("SELECT * FROM 'device/+/data'"), + actions: [ + new actions.S3PutObjectAction(bucket, { + accessControl: s3.BucketAccessControl.PUBLIC_READ, + }), + ], +}); +``` + ## Put logs to CloudWatch Logs The code snippet below creates an AWS IoT Rule that put logs to CloudWatch Logs diff --git a/packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts b/packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts index 5a5fb8338a4b4..fb8f2779f32e7 100644 --- a/packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts +++ b/packages/@aws-cdk/aws-iot-actions/lib/cloudwatch-logs-action.ts @@ -1,7 +1,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as iot from '@aws-cdk/aws-iot'; import * as logs from '@aws-cdk/aws-logs'; -import { CommonActionProps } from './private/common-action-props'; +import { CommonActionProps } from './common-action-props'; import { singletonActionRole } from './private/role'; /** diff --git a/packages/@aws-cdk/aws-iot-actions/lib/private/common-action-props.ts b/packages/@aws-cdk/aws-iot-actions/lib/common-action-props.ts similarity index 94% rename from packages/@aws-cdk/aws-iot-actions/lib/private/common-action-props.ts rename to packages/@aws-cdk/aws-iot-actions/lib/common-action-props.ts index df8f642439c37..5a9b52d8b5f27 100644 --- a/packages/@aws-cdk/aws-iot-actions/lib/private/common-action-props.ts +++ b/packages/@aws-cdk/aws-iot-actions/lib/common-action-props.ts @@ -2,8 +2,6 @@ import * as iam from '@aws-cdk/aws-iam'; /** * Common properties shared by Actions it access to AWS service. - * - * @internal */ export interface CommonActionProps { /** diff --git a/packages/@aws-cdk/aws-iot-actions/lib/index.ts b/packages/@aws-cdk/aws-iot-actions/lib/index.ts index bd3a908d0aca2..88521265228d4 100644 --- a/packages/@aws-cdk/aws-iot-actions/lib/index.ts +++ b/packages/@aws-cdk/aws-iot-actions/lib/index.ts @@ -1,3 +1,4 @@ export * from './cloudwatch-logs-action'; +export * from './common-action-props'; export * from './lambda-function-action'; export * from './s3-put-object-action'; diff --git a/packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts b/packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts index 9ec28be95589b..f690bf813a922 100644 --- a/packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts +++ b/packages/@aws-cdk/aws-iot-actions/lib/s3-put-object-action.ts @@ -2,7 +2,7 @@ import * as iam from '@aws-cdk/aws-iam'; import * as iot from '@aws-cdk/aws-iot'; import * as s3 from '@aws-cdk/aws-s3'; import { kebab as toKebabCase } from 'case'; -import { CommonActionProps } from './private/common-action-props'; +import { CommonActionProps } from './common-action-props'; import { singletonActionRole } from './private/role'; /** @@ -15,7 +15,7 @@ export interface S3PutObjectActionProps extends CommonActionProps { * * @default None */ - readonly cannedAcl?: s3.BucketAccessControl; + readonly accessControl?: s3.BucketAccessControl; /** * The path to the file where the data is written. @@ -32,7 +32,7 @@ export interface S3PutObjectActionProps extends CommonActionProps { * The action to write the data from an MQTT message to an Amazon S3 bucket. */ export class S3PutObjectAction implements iot.IAction { - private readonly cannedAcl?: string; + private readonly accessControl?: string; private readonly key?: string; private readonly role?: iam.IRole; @@ -41,31 +41,27 @@ export class S3PutObjectAction implements iot.IAction { * @param props Optional properties to not use default */ constructor(private readonly bucket: s3.IBucket, props: S3PutObjectActionProps = {}) { - this.cannedAcl = props.cannedAcl; + this.accessControl = props.accessControl; this.key = props.key; this.role = props.role; } bind(rule: iot.ITopicRule): iot.ActionConfig { const role = this.role ?? singletonActionRole(rule); - role.addToPrincipalPolicy(this.putEventStatement(this.bucket)); + role.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['s3:PutObject'], + resources: [this.bucket.arnForObjects('*')], + })); return { configuration: { s3: { bucketName: this.bucket.bucketName, - cannedAcl: this.cannedAcl && toKebabCase(this.cannedAcl.toString()), + cannedAcl: this.accessControl && toKebabCase(this.accessControl.toString()), key: this.key ?? '${topic()}/${timestamp()}', roleArn: role.roleArn, }, }, }; } - - private putEventStatement(bucket: s3.IBucket) { - return new iam.PolicyStatement({ - actions: ['s3:PutObject'], - resources: [bucket.arnForObjects('*')], - }); - } } diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3-put-object/integ.s3-put-object-action.expected.json b/packages/@aws-cdk/aws-iot-actions/test/s3-put-object/integ.s3-put-object-action.expected.json index 698e1082cd8b1..4e530f04da2c1 100644 --- a/packages/@aws-cdk/aws-iot-actions/test/s3-put-object/integ.s3-put-object-action.expected.json +++ b/packages/@aws-cdk/aws-iot-actions/test/s3-put-object/integ.s3-put-object-action.expected.json @@ -10,6 +10,7 @@ "BucketName": { "Ref": "MyBucketF68F3FF0" }, + "CannedAcl": "bucket-owner-full-control", "Key": "${year}/${month}/${day}/${topic(2)}", "RoleArn": { "Fn::GetAtt": [ diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3-put-object/integ.s3-put-object-action.ts b/packages/@aws-cdk/aws-iot-actions/test/s3-put-object/integ.s3-put-object-action.ts index 108db163eb5fe..9e100e0254eaf 100644 --- a/packages/@aws-cdk/aws-iot-actions/test/s3-put-object/integ.s3-put-object-action.ts +++ b/packages/@aws-cdk/aws-iot-actions/test/s3-put-object/integ.s3-put-object-action.ts @@ -22,6 +22,7 @@ class TestStack extends cdk.Stack { topicRule.addAction( new actions.S3PutObjectAction(bucket, { key: '${year}/${month}/${day}/${topic(2)}', + accessControl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL, }), ); } diff --git a/packages/@aws-cdk/aws-iot-actions/test/s3-put-object/s3-put-object-action.test.ts b/packages/@aws-cdk/aws-iot-actions/test/s3-put-object/s3-put-object-action.test.ts index 61de8b24095bf..567bd59d05083 100644 --- a/packages/@aws-cdk/aws-iot-actions/test/s3-put-object/s3-put-object-action.test.ts +++ b/packages/@aws-cdk/aws-iot-actions/test/s3-put-object/s3-put-object-action.test.ts @@ -1,4 +1,4 @@ -import { Template } from '@aws-cdk/assertions'; +import { Template, Match } from '@aws-cdk/assertions'; import * as iam from '@aws-cdk/aws-iam'; import * as iot from '@aws-cdk/aws-iot'; import * as s3 from '@aws-cdk/aws-s3'; @@ -87,15 +87,7 @@ test('can set key of bucket', () => { Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { TopicRulePayload: { Actions: [ - { - S3: { - BucketName: 'test-bucket', - Key: 'test-key', - RoleArn: { - 'Fn::GetAtt': ['MyTopicRuleTopicRuleActionRoleCE2D05DA', 'Arn'], - }, - }, - }, + Match.objectLike({ S3: { Key: 'test-key' } }), ], }, }); @@ -112,7 +104,7 @@ test('can set canned ACL and it convert to kebab case', () => { // WHEN topicRule.addAction( new actions.S3PutObjectAction(bucket, { - cannedAcl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL, + accessControl: s3.BucketAccessControl.BUCKET_OWNER_FULL_CONTROL, }), ); @@ -120,16 +112,7 @@ test('can set canned ACL and it convert to kebab case', () => { Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { TopicRulePayload: { Actions: [ - { - S3: { - BucketName: 'test-bucket', - Key: '${topic()}/${timestamp()}', - CannedAcl: 'bucket-owner-full-control', - RoleArn: { - 'Fn::GetAtt': ['MyTopicRuleTopicRuleActionRoleCE2D05DA', 'Arn'], - }, - }, - }, + Match.objectLike({ S3: { CannedAcl: 'bucket-owner-full-control' } }), ], }, }); @@ -153,29 +136,12 @@ test('can set role', () => { Template.fromStack(stack).hasResourceProperties('AWS::IoT::TopicRule', { TopicRulePayload: { Actions: [ - { - S3: { - BucketName: 'test-bucket', - Key: '${topic()}/${timestamp()}', - RoleArn: 'arn:aws:iam::123456789012:role/ForTest', - }, - }, + Match.objectLike({ S3: { RoleArn: 'arn:aws:iam::123456789012:role/ForTest' } }), ], }, }); - // THEN Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { - PolicyDocument: { - Statement: [ - { - Action: 's3:PutObject', - Effect: 'Allow', - Resource: 'arn:aws:s3::123456789012:test-bucket/*', - }, - ], - Version: '2012-10-17', - }, PolicyName: 'MyRolePolicy64AB00A5', Roles: ['ForTest'], });