Repository: localstack/localstack

Issue:
Support KinesisStreamSpecification parameter for AWS::DynamoDB::Table resource in CloudFormation
<!-- Love localstack? Please consider supporting our collective:
👉  https://opencollective.com/localstack/donate -->

# Type of request: This is a ...

[ ] bug report
[x] feature request

# Detailed description

Currently, the KinesisStreamSpecification parameter of AWS::DynamoDB::Table resource in CloudFormation is not supported and will be ignored even if this parameter is set.
We would like to be able to use this parameter.

## Expected behavior

When the following template is applied...

```yaml
---
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  EventStream:
    Type: AWS::Kinesis::Stream
    Properties:
      Name: EventStream
      ShardCount: 1
  EventTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: EventTable
      AttributeDefinitions:
      - AttributeName: pkey
        AttributeType: S
      KeySchema:
      - AttributeName: pkey
        KeyType: HASH
      BillingMode: PAY_PER_REQUEST
      StreamSpecification:
        StreamViewType: NEW_IMAGE
      KinesisStreamSpecification:
        StreamArn: !GetAtt EventStream.Arn

```

We will get the following results as when we run enable-kinesis-streaming-destination.

```sh
$ awslocal dynamodb describe-kinesis-streaming-destination --table-name EventTable
{
    "TableName": "EventTable",
    "KinesisDataStreamDestinations": [
        {
            "StreamArn": "arn:aws:kinesis:us-east-1:000000000000:stream/EventStream",
            "DestinationStatus": "ACTIVE"
        }
    ]
}
```

## Actual behavior

The KinesisStreamSpecification parameter will be ignored even if this parameter is set.

```sh
$ awslocal dynamodb describe-kinesis-streaming-destination --table-name EventTable
{
    "TableName": "EventTable",
    "KinesisDataStreamDestinations": []
}
```

# Steps to reproduce

## Command used to start LocalStack

```
$ docker run --rm -p 4566:4566 localstack/localstack:0.12.11
```

## Client code (AWS SDK code snippet, or sequence of "awslocal" commands)

CloudFormation templates to apply.

```yaml
---
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  EventStream:
    Type: AWS::Kinesis::Stream
    Properties:
      Name: EventStream
      ShardCount: 1
  EventTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: EventTable
      AttributeDefinitions:
      - AttributeName: pkey
        AttributeType: S
      KeySchema:
      - AttributeName: pkey
        KeyType: HASH
      BillingMode: PAY_PER_REQUEST
      StreamSpecification:
        StreamViewType: NEW_IMAGE
      KinesisStreamSpecification:
        StreamArn: !GetAtt EventStream.Arn

```

Check the results.

```sh
awslocal dynamodb describe-kinesis-streaming-destination --table-name EventTable
```

Manually enable kinesis streaming.

```sh
awslocal dynamodb enable-kinesis-streaming-destination --table-name EventTable --stream-arn arn:aws:kinesis:us-east-1:000000000000:stream/EventStream
```
Fix kinesis streaming destinations
Fix kinesis streaming destinations


Discussion:

Resolving patch:
diff --git a/localstack/utils/cloudformation/template_deployer.py b/localstack/utils/cloudformation/template_deployer.py
--- a/localstack/utils/cloudformation/template_deployer.py
+++ b/localstack/utils/cloudformation/template_deployer.py
@@ -91,6 +91,13 @@ def get_ddb_global_sec_indexes(params, **kwargs):
     return args
 
 
+def get_ddb_kinesis_stream_specification(params, **kwargs):
+    args = params.get('KinesisStreamSpecification')
+    if args:
+        args['TableName'] = params['TableName']
+    return args
+
+
 def get_apigw_resource_params(params, **kwargs):
     result = {
         'restApiId': params.get('RestApiId'),
@@ -161,7 +168,7 @@ def get_apigw_resource_params(params, **kwargs):
         }
     },
     'DynamoDB::Table': {
-        'create': {
+        'create': [{
             'function': 'create_table',
             'parameters': {
                 'TableName': 'TableName',
@@ -179,7 +186,10 @@ def get_apigw_resource_params(params, **kwargs):
                     'WriteCapacityUnits': 5
                 }
             }
-        },
+        }, {
+            'function': 'enable_kinesis_streaming_destination',
+            'parameters': get_ddb_kinesis_stream_specification
+        }],
         'delete': {
             'function': 'delete_table',
             'parameters': {


Test patch:
diff --git a/tests/integration/test_cloudformation.py b/tests/integration/test_cloudformation.py
--- a/tests/integration/test_cloudformation.py
+++ b/tests/integration/test_cloudformation.py
@@ -489,6 +489,29 @@ def handler(event, context):
     Value: !Ref MessageQueue
 """
 
+TEST_TEMPLATE_28 = """
+AWSTemplateFormatVersion: '2010-09-09'
+Resources:
+  EventStream:
+    Type: AWS::Kinesis::Stream
+    Properties:
+      Name: EventStream
+      ShardCount: 1
+  EventTable:
+    Type: AWS::DynamoDB::Table
+    Properties:
+      TableName: %s
+      AttributeDefinitions:
+      - AttributeName: pkey
+        AttributeType: S
+      KeySchema:
+      - AttributeName: pkey
+        KeyType: HASH
+      BillingMode: PAY_PER_REQUEST
+      KinesisStreamSpecification:
+        StreamArn: !GetAtt EventStream.Arn
+"""
+
 
 def bucket_exists(name):
     s3_client = aws_stack.connect_to_service('s3')
@@ -2185,3 +2208,16 @@ def test_cfn_with_apigateway_resources(self):
 
         apis = [api for api in apigw_client.get_rest_apis()['items'] if api['name'] == 'celeste-Gateway-local']
         self.assertEqual(len(apis), 0)
+
+    def test_dynamodb_stream_response_with_cf(self):
+
+        dynamodb = aws_stack.connect_to_service('dynamodb')
+        template = TEST_TEMPLATE_28 % 'EventTable'
+        stack_name = 'stack-%s' % short_uid()
+        create_and_await_stack(StackName=stack_name, TemplateBody=template)
+
+        response = dynamodb.describe_kinesis_streaming_destination(TableName='EventTable')
+
+        self.assertEqual(response.get('TableName'), 'EventTable')
+        self.assertEqual(len(response.get('KinesisDataStreamDestinations')), 1)
+        self.assertIn('StreamArn', response.get('KinesisDataStreamDestinations')[0])