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

Pass parameters to templates in aws-api-gateway-* #891

Open
Gamecock opened this issue Feb 6, 2023 · 9 comments
Open

Pass parameters to templates in aws-api-gateway-* #891

Gamecock opened this issue Feb 6, 2023 · 9 comments

Comments

@Gamecock
Copy link

Gamecock commented Feb 6, 2023

Based on aws-api_gateway-dynamodb

The constructor has read_request_template etc.

The only way I can make it work in the python SDK is hard code the table name.

I can see the default template index.ts line 268.

 const readRequestTemplate = props.readRequestTemplate ??
    `{ \
      "TableName": "${this.dynamoTable.tableName}", \
      "KeyConditionExpression": "${partitionKeyName} = :v1", \
      "ExpressionAttributeValues": { \
        ":v1": { \
          "S": "$input.params('${partitionKeyName}')" \
        } \
      } \
    }`;

I cannot figure out how to pass parameters into the template.

the create request template looks like it explicitly replaces {$Table}

@biffgaut
Copy link
Contributor

While we recommend allowing the CDK/CloudFormation to create machine generated names for resources, as that allows multiple stacks to be created within 1 account/1 region, we see two ways to accomplish what you seek:

  • Pass a table name in with the dynamoTableProps
  • Submit an entire custom readRequestTemplate

@Gamecock
Copy link
Author

The latter is what I meant by The only way I can make it work in the python SDK is hard code the table name. I just don't like having to have a separate template for dev/stage/prd.

I have not tried the create method yet to see if the variable substitution there works. If it does I'll submit a PR to make the same changes to read.

@biffgaut
Copy link
Contributor

Does this PR provide what you need? It's merged and should be released this week.

@Gamecock
Copy link
Author

Gamecock commented Mar 1, 2023

I don't think it does, but I will try once it's released. I'm not 100% sure if this is a bug or operator error. If the latter once I figure it out I can update documentation. This example is in the test file in V2.31.0

new ApiGatewayToDynamoDB(stack, 'test-api-gateway-dynamodb-additional-request-templates', {
  existingTableObj,
  additionalReadRequestTemplates: {
    'text/plain': `{ \
      "TableName": "${existingTableObj.tableName}", \
      "KeyConditionExpression": "${partitionKeyName} = :v1", \
      "ExpressionAttributeValues": { \
        ":v1": { \
          "S": "$input.params('${partitionKeyName}')" \
        } \
      } \
    }`
  }
});

I can get the Stack.template.json to include \"${existingTableObj.tableName}\" or \"${existing_table_obj.table_name}\" like the expected.json on a cdk synth but if I deploy and test the API Method, I get "" for the "TableName".

Wed Mar 01 15:50:50 UTC 2023 : Endpoint request body after transformations: { "TableName": "", "KeyConditionExpression": "device = :v1"

@georgebearden
Copy link
Contributor

Hi - Can you please provide a little more information. When I deploy this test, I can see the TableName is set correct in the Integration Request and when I hit the endpoint I get a good response. Can you please provide more context here, like the specific CDK code you are using. Thank you for any additional info you can provide!

@Gamecock
Copy link
Author

Sorry for the delay, but I had not worked with typescript before. I was able to deploy, but could not confirm the additional request was working, as I could not tell it apart from the default template.

My Goal is to be able to use a sort key and not use 'id' as the primary key.

This works in Typescript
import { App, Stack, StackProps, Duration } from 'aws-cdk-lib';
import { ApiGatewayToDynamoDBProps, ApiGatewayToDynamoDB } from "@aws-solutions-constructs/aws-apigateway-dynamodb";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as api_gateway from "aws-cdk-lib/aws-apigateway"

export class ApiGatewayTsStack extends Stack {
constructor(scope: App, id: string, props?: StackProps) {
super(scope, id, props);
const partitionKeyName = 'ident';
const sortKeyName = 'timelike'

const existingTableObj = new dynamodb.Table(this, 'existing-table', {
  partitionKey: {
    name: partitionKeyName,
    type: dynamodb.AttributeType.STRING,
  },
  sortKey: {
    name: sortKeyName,
    type: dynamodb.AttributeType.NUMBER
  },
  pointInTimeRecovery: true,
  encryption: dynamodb.TableEncryption.AWS_MANAGED,
  billingMode: dynamodb.BillingMode.PAY_PER_REQUEST
});

new ApiGatewayToDynamoDB(this, 'test-api-gateway-dynamodb-additional-request-templates', {  existingTableObj,
  readRequestTemplate:  `{ \
      "TableName": "${existingTableObj.tableName}", \
      "KeyConditionExpression": "${partitionKeyName} = :v1  AND ${sortKeyName} < :v2", \
      "ExpressionAttributeValues": { \
        ":v1": { \
          "S": "$input.params('${partitionKeyName}')" \
        }, \
        ":v2": { "N": "$input.params('${sortKeyName}')"}
      } \
    }`
  }
);

}
}

This does not work in Python:

`from aws_solutions_constructs.aws_apigateway_dynamodb import ApiGatewayToDynamoDB, ApiGatewayToDynamoDBProps
from aws_cdk import Stack, aws_dynamodb as ddb, aws_apigateway
from constructs import Construct

class ApiGatewayBugStack(Stack):

def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
    super().__init__(scope, construct_id, **kwargs)

    partitionKeyName = 'ident'
    sortKeyName = "timelike"

    existingTableObj = ddb.Table(self, 'existing-table',   
        partition_key = ddb.Attribute(
            name= partitionKeyName,
            type= ddb.AttributeType.STRING,
        ),
        sort_key= ddb.Attribute(
            name=sortKeyName,
            type= ddb.AttributeType.NUMBER
        ),
        point_in_time_recovery= True,
        encryption= ddb.TableEncryption.AWS_MANAGED,
        billing_mode= ddb.BillingMode.PAY_PER_REQUEST
        )

    ApiGatewayToDynamoDB(self, 'test-api-gateway-dynamodb-additional-request-templates', existing_table_obj=existingTableObj,
            api_gateway_props= aws_apigateway.RestApiProps(rest_api_name="Python_API"),
            read_request_template= repr({ \
            "TableName": "${existingTableObj.tableName}", \
            "KeyConditionExpression": "${partitionKeyName} = :v1 AND ${sortKeyName} < :v2", \
            "ExpressionAttributeValues": { \
                ":v1": { \
                "S": "$input.params('${partitionKeyName}')" \
                }, \
                "v2:" : { "N": "$input.params('${partitionKeyName}')" }, \
            } \
            })
        
    )
    `

The cloud formation template.json seems to be OK:

"RequestTemplates": { "application/json": "{'TableName': '${existingTableObj.tableName}', 'KeyConditionExpression': '${partitionKeyName} = :v1 AND ${sortKeyName} < :v2', 'ExpressionAttributeValues': {':v1': {'S': \"$input.params('${partitionKeyName}')\"}, 'v2:': {'N': \"$input.params('${partitionKeyName}')\"}}}" },

But when it's executed in API Gateway test page, the name is not resolved:

Response:
{"__type":"com.amazon.coral.service#SerializationException"}

Logs Snippet:
Tue Mar 21 18:49:53 UTC 2023 : Endpoint request body after transformations: {'TableName': '', 'KeyConditionExpression': ' = :v1 AND < :v2', 'ExpressionAttributeValues': {':v1': {'S': ""}, 'v2:': {'N': ""}}}

I have not tested any other languages.

@Gamecock
Copy link
Author

Gamecock commented Mar 21, 2023

I'd like for it to work with table props vs existing table, but that is just a nice to have. This method will work if we can get the Table Name in python. Could very easily be operator error.

@georgebearden
Copy link
Contributor

hi @Gamecock - Thank you for the additional info. Looking at your example above, I'm guessing things in the repr call are not evaluating correctly. I just deployed the following example in python, and can successfully call the API and see the expected response. With this, I hope it will be easy for you to take it and extend it to include the sort key requirement you have as well.

After thinking for a moment on the new/existing table point, I think currently this would only be possible using an existing table, as we can't reference a solutions-constructs created table from outside the construct until its created, and at that point, it would be too late as we need to pass these properties in during creation time.

from aws_cdk import (
    Stack
)

import aws_cdk.aws_dynamodb as dynamodb

from constructs import Construct
from aws_solutions_constructs.aws_apigateway_dynamodb import ApiGatewayToDynamoDB

import json

class PythonStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        partition_key_name = 'id'

        existing_table_obj = dynamodb.Table(self, 'existing-table',   
          partition_key = dynamodb.Attribute(
              name= partition_key_name,
              type= dynamodb.AttributeType.STRING,
          ),
          point_in_time_recovery= True,
          encryption= dynamodb.TableEncryption.AWS_MANAGED,
          billing_mode= dynamodb.BillingMode.PAY_PER_REQUEST
        )

        read_request_template = json.dumps({
          "TableName": existing_table_obj.table_name,
          "KeyConditionExpression": f"{partition_key_name} = :v1",
          "ExpressionAttributeValues": {
            ":v1": {
              "S": f"input.params('{partition_key_name}')"
            },
          }
        })

        ApiGatewayToDynamoDB(self, 'ApiGatewayToDynamoDB', 
          read_request_template=read_request_template,
          existing_table_obj=existing_table_obj
        )

@Gamecock
Copy link
Author

Gamecock commented Apr 10, 2023

Thanks, I was able to validate this works for me. Do you want a documentation update to say the request templates require the use of an existing template, or should it actually throw an error if you provide a template without an existingTableObject?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants