1
votes

I was trying to implement a dynamodb proxy using apigateway. But when invoking it, the api is returning error,

Fri Mar 19 20:30:27 UTC 2021 : Execution failed due to configuration error: Unable to transform request

Fri Mar 19 20:30:27 UTC 2021 : Method completed with status: 500

To me it looks like the issue is not with the requestTemplates transformation(?), but what else, any idea?

Cloudformation template.

AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"

Description: Feedback Store; A hypothetical example to test direct proxy to Dynamodb table from api gateway.
Metadata:
  cfn-lint:
    config:
      ignore_checks:
        # Not useful at all.
        - I1022
Parameters:
  Environment:
    Type: String
    Description: Current environment
    Default: "dev"
Resources:
  EventsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: feedback_events
      AttributeDefinitions:
        - AttributeName: UserID
          AttributeType: S
        - AttributeName: TimeUTC
          AttributeType: S
      KeySchema:
        - AttributeName: UserID
          KeyType: HASH
        - AttributeName: TimeUTC
          KeyType: RANGE
      BillingMode: PAY_PER_REQUEST
      Tags:
        - Key: "Feedbacks"
          Value: "true"
  ApiGatewayDynamoRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "apigateway.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Policies:
        - PolicyName: "ApiGatewayDynamoRolePolicy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "dynamodb:DescribeTable"
                  - "dynamodb:BatchWriteItem"
                  - "dynamodb:PutItem"
                  - "dynamodb:Query"
                  - "dynamodb:UpdateItem"
                Resource:
                  - !GetAtt EventsTable.Arn
              - Effect: "Allow"
                Action:
                  - "dynamodb:ListTables"
                Resource: "*"
  ApiGatewayApi:
    Type: AWS::Serverless::Api
    Properties:
      DefinitionBody:
        swagger: 2.0
        info:
          title: !Sub "${AWS::StackName}-backend-api"
          version: 2021-03-21T22:37:24Z
        basePath: /prod
        schemes:
          - https
        paths:
          /feedback:
            post:
              summary: Creates a new feedback
              description: |
                Creates a new feedback object in the datastore
              consumes:
                - application/json
              produces:
                - application/json
              parameters:
                - name: NewFeedback
                  in: body
                  description: New feedback details.
                  schema:
                    $ref: "#/definitions/Feedbacks"
              tags:
                - Feedback Store
              x-amazon-apigateway-integration:
                type: aws
                uri: !Sub arn:aws:apigateway:${AWS::Region}:dynamodb:action/BatchWriteItem
                credentials: !GetAtt ApiGatewayDynamoRole.Arn
                httpMethod: POST
                requestTemplates:
                  application/json: |
                    #set($inputRoot = $input.path('$'))
                    {
                      "RequestItems": {
                        "${self:resources.Resources.EventsTable.Properties.TableName}": [
                          #foreach($event in $inputRoot.event)
                            {
                              "PutRequest": {
                                "Item": {
                                  "UserID" : { "S": "$event.userid" },
                                  "TimeUTC" : { "S": "$event.time" },
                                  "Lat": { "S": "$event.lat" },
                                  "Lng": { "S": "$event.lng" },
                                  "UUID" : { "S": "$event.uuid"},
                                  "Sensor" : { "S": "$event.sensor_name" },
                                  "Reading" : { "S": "$event.reading_value" },
                                  "Active" : { "S": "$event.is_active" },
                                }
                              }
                            }#if($foreach.hasNext),#end
                          #end
                        ]
                      },
                      "ReturnValues": "UPDATED_NEW",
                      "ReturnConsumedCapacity": "NONE",
                      "ReturnItemCollectionMetrics": "NONE"
                    }
                responses:
                  "default":
                    statusCode: "200"
                    SelectionPattern: "2\\d{2}"
                    responseParameters:
                      method.response.header.Access-Control-Allow-Methods: "'OPTIONS,POST'"
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                  "BAD.*":
                    statusCode: "400"
                    SelectionPattern: "4\\d{2}"
                    responseParameters:
                      method.response.header.Access-Control-Allow-Methods: "'OPTIONS,POST'"
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
                  "INT.*":
                    statusCode: "500"
                    SelectionPattern: "5\\d{2}"
                    responseParameters:
                      method.response.header.Access-Control-Allow-Methods: "'OPTIONS,POST'"
                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
                      method.response.header.Access-Control-Allow-Origin: "'*'"
              responses:
                "200":
                  description: The unique identifier of the new feedback
                  headers:
                    Access-Control-Allow-Origin:
                      type: "string"
                    Access-Control-Allow-Methods:
                      type: string
                    Access-Control-Allow-Headers:
                      type: string
                  schema:
                    $ref: "#/definitions/NewFeedbackResponse"
                "400":
                  description: Bad request
                  headers:
                    Access-Control-Allow-Origin:
                      type: "string"
                    Access-Control-Allow-Methods:
                      type: string
                    Access-Control-Allow-Headers:
                      type: string
                  schema:
                    $ref: "#/definitions/Error"
                "500":
                  description: Internal error
                  headers:
                    Access-Control-Allow-Origin:
                      type: "string"
                    Access-Control-Allow-Methods:
                      type: string
                    Access-Control-Allow-Headers:
                      type: string
                  schema:
                    $ref: "#/definitions/Error"
        definitions:
          Empty:
            type: object
            title: Empty Schema
          Feedbacks:
            type: array
            items:
              $ref: FeedbackItem
          FeedbackItem:
            properties:
              userid:
                type: string
                description: UserID of the author
              time:
                type: string
                description: Feedback time
              lat:
                type: string
                description: Latitude
              lng:
                type: string
                description: Longitude
              uuid:
                type: string
                description: Globaly unique id for the feedback
              sensor_name:
                type: string
                description: Device the feedback coming from
              reading_value:
                type: string
                description: Current reading
              is_active:
                type: string
                description: Device status
          NewFeedbackResponse:
            properties:
              feedbackId:
                type: string
                description: The generated unique identifier for the new feedback
          Error:
            properties:
              code:
                type: integer
                format: int32
              message:
                type: string
              fields:
                type: string

      StageName: prod
      Variables:
        StageVariableName: "TestAPI"

Payload:

{
  "event": [
    {
      "userid": "21d6523137f6",
      "time": "2020-06-16T15:22:33Z",
      "lng": "-122.03053391",
      "lat": "37.33180957",
      "uuid": "96a6f48c-fe67-4cad-be24-21d6523137f6",
      "sensor_name": "CYT523",
      "reading_value": "72.9",
      "is_active": "true"
    },
    {
      "userid": "4354069ba6e5",
      "time": "2020-06-16T15:22:33Z",
      "lng": "-122.03053391",
      "lat": "37.33180957",
      "uuid": "512f2543-a458-424c-a141-4354069ba6e5",
      "sensor_name": "JQR928",
      "reading_value": "41.3",
      "is_active": "true"
    }
  ]
}
2

2 Answers

2
votes

Three things I noticed which needs corrections.

  • Dynamo table name is not replaced correctly. We need to use !Sub with tableName: !Ref EventsTable
  • There is a comma after "Active" : { "S": "$event.is_active" } which shouldn't be there.
  • The three additional parameters are not all valid at root level in batch-write-item api, I removed them temporarily, we can add as needed. "ReturnValues": "UPDATED_NEW", "ReturnConsumedCapacity": "NONE", and "ReturnItemCollectionMetrics": "NONE"

Updated request template:

requestTemplates:
  application/json: !Sub
    - |
      #set($inputRoot = $input.path('$'))
      {
        "RequestItems": {
          "${tableName}": [
            #foreach($event in $inputRoot.event)
              {
                "PutRequest": {
                  "Item": {
                    "UserID" : { "S": "$event.userid" },
                    "TimeUTC" : { "S": "$event.time" },
                    "Lat": { "S": "$event.lat" },
                    "Lng": { "S": "$event.lng" },
                    "UUID" : { "S": "$event.uuid"},
                    "Sensor" : { "S": "$event.sensor_name" },
                    "Reading" : { "S": "$event.reading_value" },
                    "Active" : { "S": "$event.is_active" }
                  }
                }
              }#if($foreach.hasNext),#end
            #end
          ]
        },
        "ReturnValues": "UPDATED_NEW",
        "ReturnConsumedCapacity": "NONE",
        "ReturnItemCollectionMetrics": "NONE"
      }
    - {
    tableName: !Ref EventsTable
    }
1
votes

I think this line is the issue:

"${self:resources.Resources.EventsTable.Properties.TableName}"

I am not sure that works in API Gateway VTL. My recommendation is to get it working with static code, then add the refs back in.