Skip to content

Commit

Permalink
feat(dynamodb): expose stream features on ITable (#6635)
Browse files Browse the repository at this point in the history
In order to make it possible to use the `DynamoEventSource` feature from
`@aws-cdk/aws-lambda-event-sources` with imported tables (`ITable`s
obtained from `Table.fromTableAttributes`), the `tableStreamArn`
property must be visible on the `ITable` interface, and accepted as part
of the `TableAttributes` struct.

The necessary `grant` methods that target the table stream were also
modified so that they can be used on any `ITable` that was built with a
`tableStreamArn`.

As a bonus, added documentation text for a couple of previously
undocumented enum constants.

Fixes #6344
  • Loading branch information
RomainMuller authored Mar 9, 2020
1 parent 6961fca commit 52e8b95
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 83 deletions.
8 changes: 6 additions & 2 deletions packages/@aws-cdk/aws-dynamodb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,20 @@ const table = new dynamodb.Table(this, 'Table', {

### Importing existing tables

To import an existing table into your CDK application, use the `Table.fromTableName` or `Table.fromTableArn`
To import an existing table into your CDK application, use the `Table.fromTableName`, `Table.fromTableArn` or `Table.fromTableAttributes`
factory method. This method accepts table name or table ARN which describes the properties of an already
existing table:

```ts
const table = Table.fromTableArn(this, 'ImportedTable', 'arn:aws:dynamodb:us-east-1:111111111:table/my-table');
const table = Table.fromTableArn(this, 'ImportedTable', 'arn:aws:dynamodb:us-east-1:111111111:table/my-table');
// now you can just call methods on the table
table.grantReadWriteData(user);
```

If you intend to use the `tableStreamArn` (including indirectly, for example by creating an
`@aws-cdk/aws-lambda-event-source.DynamoEventSource` on the imported table), you *must* use the
`Table.fromTableAttributes` method and the `tableStreamArn` property *must* be populated.

### Keys

When a table is defined, you must define it's schema using the `partitionKey`
Expand Down
146 changes: 75 additions & 71 deletions packages/@aws-cdk/aws-dynamodb/lib/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,13 @@ export interface ITable extends IResource {
*/
readonly tableName: string;

/**
* ARN of the table's stream, if there is one.
*
* @attribute
*/
readonly tableStreamArn?: string;

/**
* Permits an IAM principal all data read operations from this table:
* BatchGetItem, GetRecords, GetShardIterator, Query, GetItem, Scan.
Expand Down Expand Up @@ -305,17 +312,24 @@ export interface TableAttributes {
* The ARN of the dynamodb table.
* One of this, or {@link tabeName}, is required.
*
* @default no table arn
* @default - no table arn
*/
readonly tableArn?: string;

/**
* The table name of the dynamodb table.
* One of this, or {@link tabeArn}, is required.
*
* @default no table name
* @default - no table name
*/
readonly tableName?: string;

/**
* The ARN of the table's stream.
*
* @default - no table stream
*/
readonly tableStreamArn?: string;
}

abstract class TableBase extends Resource implements ITable {
Expand All @@ -329,6 +343,11 @@ abstract class TableBase extends Resource implements ITable {
*/
public abstract readonly tableName: string;

/**
* @attribute
*/
public abstract readonly tableStreamArn?: string;

/**
* Adds an IAM policy statement associated with this table to an IAM
* principal's policy.
Expand All @@ -347,6 +366,25 @@ abstract class TableBase extends Resource implements ITable {
});
}

/**
* Adds an IAM policy statement associated with this table's stream to an
* IAM principal's policy.
* @param grantee The principal (no-op if undefined)
* @param actions The set of actions to allow (i.e. "dynamodb:DescribeStream", "dynamodb:GetRecords", ...)
*/
public grantStream(grantee: iam.IGrantable, ...actions: string[]): iam.Grant {
if (!this.tableStreamArn) {
throw new Error(`DynamoDB Streams must be enabled on the table ${this.node.path}`);
}

return iam.Grant.addToPrincipal({
grantee,
actions,
resourceArns: [this.tableStreamArn],
scope: this,
});
}

/**
* Permits an IAM principal all data read operations from this table:
* BatchGetItem, GetRecords, GetShardIterator, Query, GetItem, Scan.
Expand All @@ -359,17 +397,31 @@ abstract class TableBase extends Resource implements ITable {
/**
* Permits an IAM Principal to list streams attached to current dynamodb table.
*
* @param _grantee The principal (no-op if undefined)
* @param grantee The principal (no-op if undefined)
*/
public abstract grantTableListStreams(_grantee: iam.IGrantable): iam.Grant;
public grantTableListStreams(grantee: iam.IGrantable): iam.Grant {
if (!this.tableStreamArn) {
throw new Error(`DynamoDB Streams must be enabled on the table ${this.node.path}`);
}
return iam.Grant.addToPrincipal({
grantee,
actions: ['dynamodb:ListStreams'],
resourceArns: [
Lazy.stringValue({ produce: () => `${this.tableArn}/stream/*` })
],
});
}

/**
* Permits an IAM principal all stream data read operations for this
* table's stream:
* DescribeStream, GetRecords, GetShardIterator, ListStreams.
* @param grantee The principal to grant access to
*/
public abstract grantStreamRead(grantee: iam.IGrantable): iam.Grant;
public grantStreamRead(grantee: iam.IGrantable): iam.Grant {
this.grantTableListStreams(grantee);
return this.grantStream(grantee, ...READ_STREAM_DATA_ACTIONS);
}

/**
* Permits an IAM principal all data write operations to this table:
Expand Down Expand Up @@ -521,47 +573,41 @@ export class Table extends TableBase {

public readonly tableName: string;
public readonly tableArn: string;
public readonly tableStreamArn?: string;

constructor(_scope: Construct, _id: string, _tableArn: string, _tableName: string) {
super(_scope, _id);
constructor(_tableArn: string, tableName: string, tableStreamArn?: string) {
super(scope, id);
this.tableArn = _tableArn;
this.tableName = _tableName;
this.tableName = tableName;
this.tableStreamArn = tableStreamArn;
}

protected get hasIndex(): boolean {
return false;
}

public grantTableListStreams(_grantee: iam.IGrantable): iam.Grant {
throw new Error("Method not implemented.");
}

public grantStreamRead(_grantee: iam.IGrantable): iam.Grant {
throw new Error("Method not implemented.");
}
}

let tableName: string;
let tableArn: string;
let name: string;
let arn: string;
const stack = Stack.of(scope);
if (!attrs.tableName) {
if (!attrs.tableArn) { throw new Error('One of tableName or tableArn is required!'); }

tableArn = attrs.tableArn;
arn = attrs.tableArn;
const maybeTableName = stack.parseArn(attrs.tableArn).resourceName;
if (!maybeTableName) { throw new Error('ARN for DynamoDB table must be in the form: ...'); }
tableName = maybeTableName;
name = maybeTableName;
} else {
if (attrs.tableArn) { throw new Error("Only one of tableArn or tableName can be provided"); }
tableName = attrs.tableName;
tableArn = stack.formatArn({
name = attrs.tableName;
arn = stack.formatArn({
service: 'dynamodb',
resource: 'table',
resourceName: attrs.tableName,
});
}

return new Import(scope, id, tableArn, tableName);
return new Import(arn, name, attrs.tableStreamArn);
}

/**
Expand Down Expand Up @@ -664,54 +710,6 @@ export class Table extends TableBase {
}
}

/**
* Adds an IAM policy statement associated with this table's stream to an
* IAM principal's policy.
* @param grantee The principal (no-op if undefined)
* @param actions The set of actions to allow (i.e. "dynamodb:DescribeStream", "dynamodb:GetRecords", ...)
*/
public grantStream(grantee: iam.IGrantable, ...actions: string[]): iam.Grant {
if (!this.tableStreamArn) {
throw new Error(`DynamoDB Streams must be enabled on the table ${this.node.path}`);
}

return iam.Grant.addToPrincipal({
grantee,
actions,
resourceArns: [this.tableStreamArn],
scope: this,
});
}

/**
* Permits an IAM Principal to list streams attached to current dynamodb table.
*
* @param grantee The principal (no-op if undefined)
*/
public grantTableListStreams(grantee: iam.IGrantable): iam.Grant {
if (!this.tableStreamArn) {
throw new Error(`DynamoDB Streams must be enabled on the table ${this.node.path}`);
}
return iam.Grant.addToPrincipal({
grantee,
actions: ['dynamodb:ListStreams'],
resourceArns: [
Lazy.stringValue({ produce: () => `${this.tableArn}/stream/*` })
],
});
}

/**
* Permits an IAM principal all stream data read operations for this
* table's stream:
* DescribeStream, GetRecords, GetShardIterator, ListStreams.
* @param grantee The principal to grant access to
*/
public grantStreamRead(grantee: iam.IGrantable): iam.Grant {
this.grantTableListStreams(grantee);
return this.grantStream(grantee, ...READ_STREAM_DATA_ACTIONS);
}

/**
* Add a global secondary index of table.
*
Expand Down Expand Up @@ -1088,8 +1086,11 @@ export class Table extends TableBase {
}

export enum AttributeType {
/** Up to 400KiB of binary data (which must be encoded as base64 before sending to DynamoDB) */
BINARY = 'B',
/** Numeric values made of up to 38 digits (positive, negative or zero) */
NUMBER = 'N',
/** Up to 400KiB of UTF-8 encoded text */
STRING = 'S',
}

Expand All @@ -1108,8 +1109,11 @@ export enum BillingMode {
}

export enum ProjectionType {
/** Only the index and primary keys are projected into the index. */
KEYS_ONLY = 'KEYS_ONLY',
/** Only the specified table attributes are projected into the index. The list of projected attributes is in `nonKeyAttributes`. */
INCLUDE = 'INCLUDE',
/** All of the table attributes are projected into the index. */
ALL = 'ALL'
}

Expand Down
8 changes: 1 addition & 7 deletions packages/@aws-cdk/aws-dynamodb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@
"awslint": {
"exclude": [
"docs-public-apis:@aws-cdk/aws-dynamodb.TableProps",
"docs-public-apis:@aws-cdk/aws-dynamodb.ProjectionType.ALL",
"docs-public-apis:@aws-cdk/aws-dynamodb.Table.tableName",
"docs-public-apis:@aws-cdk/aws-dynamodb.Table.tableStreamArn",
"docs-public-apis:@aws-cdk/aws-dynamodb.Attribute",
Expand All @@ -109,12 +108,7 @@
"docs-public-apis:@aws-cdk/aws-dynamodb.TableOptions",
"docs-public-apis:@aws-cdk/aws-dynamodb.Table.tableArn",
"docs-public-apis:@aws-cdk/aws-dynamodb.AttributeType",
"docs-public-apis:@aws-cdk/aws-dynamodb.AttributeType.BINARY",
"docs-public-apis:@aws-cdk/aws-dynamodb.AttributeType.NUMBER",
"docs-public-apis:@aws-cdk/aws-dynamodb.AttributeType.STRING",
"docs-public-apis:@aws-cdk/aws-dynamodb.ProjectionType",
"docs-public-apis:@aws-cdk/aws-dynamodb.ProjectionType.KEYS_ONLY",
"docs-public-apis:@aws-cdk/aws-dynamodb.ProjectionType.INCLUDE"
"docs-public-apis:@aws-cdk/aws-dynamodb.ProjectionType"
]
}
}
Loading

0 comments on commit 52e8b95

Please sign in to comment.