Skip to content

Commit

Permalink
feat(iotevents): add grant method to Input class (#18617)
Browse files Browse the repository at this point in the history
This PR add `grant` method to `Input` class.
Next of this PR, I aim to create PR that add IoT Event Action to IoT Core Rule.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
yamatatsu committed Feb 3, 2022
1 parent ec8495a commit e89688e
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 31 deletions.
13 changes: 13 additions & 0 deletions packages/@aws-cdk/aws-iotevents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,16 @@ new iotevents.DetectorModel(this, 'MyDetectorModel', {
initialState: onlineState,
});
```

To grant permissions to put messages in the input,
you can use the `grantWrite()` method:

```ts
import * as iam from '@aws-cdk/aws-iam';
import * as iotevents from '@aws-cdk/aws-iotevents';

declare const grantable: iam.IGrantable;
const input = iotevents.Input.fromInputName(this, 'MyInput', 'my_input');

input.grantWrite(grantable);
```
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-iotevents/lib/detector-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { CfnDetectorModel } from './iotevents.generated';
import { State } from './state';

/**
* Represents an AWS IoT Events detector model
* Represents an AWS IoT Events detector model.
*/
export interface IDetectorModel extends IResource {
/**
Expand Down Expand Up @@ -33,7 +33,7 @@ export enum EventEvaluation {
}

/**
* Properties for defining an AWS IoT Events detector model
* Properties for defining an AWS IoT Events detector model.
*/
export interface DetectorModelProps {
/**
Expand Down
10 changes: 5 additions & 5 deletions packages/@aws-cdk/aws-iotevents/lib/expression.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { IInput } from './input';

/**
* Expression for events in Detector Model state
* Expression for events in Detector Model state.
* @see https://docs.aws.amazon.com/iotevents/latest/developerguide/iotevents-expressions.html
*/
export abstract class Expression {
/**
* Create a expression from the given string
* Create a expression from the given string.
*/
public static fromString(value: string): Expression {
return new StringExpression(value);
Expand All @@ -28,14 +28,14 @@ export abstract class Expression {
}

/**
* Create a expression for the Equal operator
* Create a expression for the Equal operator.
*/
public static eq(left: Expression, right: Expression): Expression {
return new BinaryOperationExpression(left, '==', right);
}

/**
* Create a expression for the AND operator
* Create a expression for the AND operator.
*/
public static and(left: Expression, right: Expression): Expression {
return new BinaryOperationExpression(left, '&&', right);
Expand All @@ -45,7 +45,7 @@ export abstract class Expression {
}

/**
* this is called to evaluate the expression
* This is called to evaluate the expression.
*/
public abstract evaluate(): string;
}
Expand Down
77 changes: 67 additions & 10 deletions packages/@aws-cdk/aws-iotevents/lib/input.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,66 @@
import { Resource, IResource } from '@aws-cdk/core';
import * as iam from '@aws-cdk/aws-iam';
import { Resource, IResource, Aws } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { CfnInput } from './iotevents.generated';

/**
* Represents an AWS IoT Events input
* Represents an AWS IoT Events input.
*/
export interface IInput extends IResource {
/**
* The name of the input
* The name of the input.
*
* @attribute
*/
readonly inputName: string;

/**
* The ARN of the input.
*
* @attribute
*/
readonly inputArn: string;

/**
* Grant write permissions on this input and its contents to an IAM principal (Role/Group/User).
*
* @param grantee the principal
*/
grantWrite(grantee: iam.IGrantable): iam.Grant

/**
* Grant the indicated permissions on this input to the given IAM principal (Role/Group/User).
*
* @param grantee the principal
* @param actions the set of actions to allow (i.e. "iotevents:BatchPutMessage")
*/
grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant
}

abstract class InputBase extends Resource implements IInput {
public abstract readonly inputName: string;

public abstract readonly inputArn: string;

public grantWrite(grantee: iam.IGrantable) {
return this.grant(grantee, 'iotevents:BatchPutMessage');
}

public grant(grantee: iam.IGrantable, ...actions: string[]) {
return iam.Grant.addToPrincipal({
grantee,
actions,
resourceArns: [this.inputArn],
});
}
}

/**
* Properties for defining an AWS IoT Events input
* Properties for defining an AWS IoT Events input.
*/
export interface InputProps {
/**
* The name of the input
* The name of the input.
*
* @default - CloudFormation will generate a unique name of the input
*/
Expand All @@ -37,19 +79,25 @@ export interface InputProps {
/**
* Defines an AWS IoT Events input in this stack.
*/
export class Input extends Resource implements IInput {
export class Input extends InputBase {
/**
* Import an existing input
* Import an existing input.
*/
public static fromInputName(scope: Construct, id: string, inputName: string): IInput {
class Import extends Resource implements IInput {
return new class Import extends InputBase {
public readonly inputName = inputName;
}
return new Import(scope, id);
public readonly inputArn = this.stack.formatArn({
service: 'iotevents',
resource: 'input',
resourceName: inputName,
});
}(scope, id);
}

public readonly inputName: string;

public readonly inputArn: string;

constructor(scope: Construct, id: string, props: InputProps) {
super(scope, id, {
physicalName: props.inputName,
Expand All @@ -67,5 +115,14 @@ export class Input extends Resource implements IInput {
});

this.inputName = this.getResourceNameAttribute(resource.ref);
this.inputArn = this.getResourceArnAttribute(arnForInput(resource.ref), {
service: 'iotevents',
resource: 'input',
resourceName: this.physicalName,
});
}
}

function arnForInput(inputName: string): string {
return `arn:${Aws.PARTITION}:iotevents:${Aws.REGION}:${Aws.ACCOUNT_ID}:input/${inputName}`;
}
10 changes: 5 additions & 5 deletions packages/@aws-cdk/aws-iotevents/lib/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Event } from './event';
import { CfnDetectorModel } from './iotevents.generated';

/**
* Properties for defining a state of a detector
* Properties for defining a state of a detector.
*/
export interface StateProps {
/**
Expand All @@ -20,11 +20,11 @@ export interface StateProps {
}

/**
* Defines a state of a detector
* Defines a state of a detector.
*/
export class State {
/**
* The name of the state
* The name of the state.
*/
public readonly stateName: string;

Expand All @@ -33,7 +33,7 @@ export class State {
}

/**
* Return the state property JSON
* Return the state property JSON.
*
* @internal
*/
Expand All @@ -46,7 +46,7 @@ export class State {
}

/**
* returns true if this state has at least one condition via events
* Returns true if this state has at least one condition via events.
*
* @internal
*/
Expand Down
82 changes: 73 additions & 9 deletions packages/@aws-cdk/aws-iotevents/test/input.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { Template } from '@aws-cdk/assertions';
import * as iam from '@aws-cdk/aws-iam';
import * as cdk from '@aws-cdk/core';
import * as iotevents from '../lib';

test('Default property', () => {
const stack = new cdk.Stack();
let stack: cdk.Stack;
beforeEach(() => {
stack = new cdk.Stack();
});

test('Default property', () => {
// WHEN
new iotevents.Input(stack, 'MyInput', {
attributeJsonPaths: ['payload.temperature'],
Expand All @@ -19,7 +23,6 @@ test('Default property', () => {
});

test('can get input name', () => {
const stack = new cdk.Stack();
// GIVEN
const input = new iotevents.Input(stack, 'MyInput', {
attributeJsonPaths: ['payload.temperature'],
Expand All @@ -39,9 +42,38 @@ test('can get input name', () => {
});
});

test('can set physical name', () => {
const stack = new cdk.Stack();
test('can get input ARN', () => {
// GIVEN
const input = new iotevents.Input(stack, 'MyInput', {
attributeJsonPaths: ['payload.temperature'],
});

// WHEN
new cdk.CfnResource(stack, 'Res', {
type: 'Test::Resource',
properties: {
InputArn: input.inputArn,
},
});

// THEN
Template.fromStack(stack).hasResourceProperties('Test::Resource', {
InputArn: {
'Fn::Join': ['', [
'arn:',
{ Ref: 'AWS::Partition' },
':iotevents:',
{ Ref: 'AWS::Region' },
':',
{ Ref: 'AWS::AccountId' },
':input/',
{ Ref: 'MyInput08947B23' },
]],
},
});
});

test('can set physical name', () => {
// WHEN
new iotevents.Input(stack, 'MyInput', {
inputName: 'test_input',
Expand All @@ -55,8 +87,6 @@ test('can set physical name', () => {
});

test('can import a Input by inputName', () => {
const stack = new cdk.Stack();

// WHEN
const inputName = 'test-input-name';
const topicRule = iotevents.Input.fromInputName(stack, 'InputFromInputName', inputName);
Expand All @@ -68,11 +98,45 @@ test('can import a Input by inputName', () => {
});

test('cannot be created with an empty array of attributeJsonPaths', () => {
const stack = new cdk.Stack();

expect(() => {
new iotevents.Input(stack, 'MyInput', {
attributeJsonPaths: [],
});
}).toThrow('attributeJsonPaths property cannot be empty');
});

test('can grant the permission to put message', () => {
const role = iam.Role.fromRoleArn(stack, 'MyRole', 'arn:aws:iam::account-id:role/role-name');
const input = new iotevents.Input(stack, 'MyInput', {
attributeJsonPaths: ['payload.temperature'],
});

// WHEN
input.grantWrite(role);

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Action: 'iotevents:BatchPutMessage',
Effect: 'Allow',
Resource: {
'Fn::Join': ['', [
'arn:',
{ Ref: 'AWS::Partition' },
':iotevents:',
{ Ref: 'AWS::Region' },
':',
{ Ref: 'AWS::AccountId' },
':input/',
{ Ref: 'MyInput08947B23' },
]],
},
},
],
},
PolicyName: 'MyRolePolicy64AB00A5',
Roles: ['role-name'],
});
});

0 comments on commit e89688e

Please sign in to comment.