Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(iotevents): add grant method to Input class #18617

Merged
merged 18 commits into from
Feb 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
yamatatsu marked this conversation as resolved.
Show resolved Hide resolved

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', () => {
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
// 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', {
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
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'],
});
});