aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.51k stars 3.85k forks source link

(CLI): hotswap support for API Gateway #19219

Open misterjoshua opened 2 years ago

misterjoshua commented 2 years ago

Description

I'd like the CDK CLI to hotswap the following API Gateway Method constituents:

Use Case

Development of service-to-service integrations with AwsIntegration, especially when using substantial VTL code.

Proposed Solution

We should hotswap these fields so that incremental development doesn't require substantial CloudFormation waits between changes.

Other information

Example of a non-trivial service-to-service integration:

const AMZ_JSON_1_0 = 'application/x-amz-json-1.0';
const APPLICATION_JSON = 'application/json';

const requestModel = new aws_apigateway.Model(this, 'Model', {
  restApi: resource.api,
  schema: {
    type: aws_apigateway.JsonSchemaType.OBJECT,
    properties: {
      test: { type: aws_apigateway.JsonSchemaType.STRING },
      env: { type: aws_apigateway.JsonSchemaType.STRING },
      result: { type: [aws_apigateway.JsonSchemaType.OBJECT, aws_apigateway.JsonSchemaType.STRING] },
    },
    required: [
      'env',
      'test',
      'result',
    ],
  },
});

new aws_apigateway.Method(this, 'PostMethod', {
  httpMethod: 'POST',
  resource: resource,
  options: {
    methodResponses: [{ statusCode: '200' }],
    requestModels: {
      [APPLICATION_JSON]: requestModel,
    },
    requestValidatorOptions: {
      validateRequestBody: true,
    },
  },
  integration: new aws_apigateway.AwsIntegration({
    service: 'dynamodb',
    action: 'PutItem',
    options: {
      credentialsRole,
      requestTemplates: {
        [APPLICATION_JSON]: [
          "#set ($env = $input.path('$.env'))",
          "#set ($test = $input.path('$.test'))",
          "#set ($timeId = $context.requestTimeEpoch + '#' + $context.requestId)",
          JSON.stringify({
            TableName: table.tableName,
            Item: {
              // Test results in an environment by date
              PK: { S: 'ENV#$util.escapeJavaScript($env)' },
              SK: { S: 'TEST#$util.escapeJavaScript($test)#$timeId' },
              // Test results across environments by date
              GSI1PK: { S: 'TEST#$util.escapeJavaScript($test)' },
              GSI1SK: { S: 'ENV#$util.escapeJavaScript($env)#$timeId' },
              // Data
              env: { S: '$util.escapeJavaScript($env)' },
              test: { S: '$util.escapeJavaScript($test)' },
              result: { S: "$util.escapeJavaScript($input.json('$.result'))" },
            },
          }),
        ].join('\n'),
      },
      passthroughBehavior: aws_apigateway.PassthroughBehavior.WHEN_NO_MATCH,
      integrationResponses: [{
        statusCode: '200',
        responseTemplates: {
          [AMZ_JSON_1_0]: JSON.stringify({
            requestId: '$context.requestId',
          }),
        },
      }],
    },
  }),
});

new aws_apigateway.Method(this, 'GetByEnvMethod', {
  httpMethod: 'GET',
  resource: resource,
  options: {
    methodResponses: [{
      statusCode: '200',
    }],
    requestParameters: {
      'method.request.path.env': true,
    },
  },
  integration: new aws_apigateway.AwsIntegration({
    service: 'dynamodb',
    action: 'Query',
    options: {
      credentialsRole,
      requestParameters: {
        'integration.request.path.env': 'method.request.path.env',
      },
      requestTemplates: {
        [APPLICATION_JSON]: [
          "#set ($env = $input.params('env'))",
          '#set ($limit = 2)',
          JSON.stringify({
            TableName: table.tableName,
            Limit: '$limit',
            KeyConditionExpression: 'PK = :PK',
            ExpressionAttributeValues: {
              ':PK': { S: 'ENV#$util.escapeJavaScript($env)' },
            },
          }),
        ].join('\n'),
      },
      passthroughBehavior: aws_apigateway.PassthroughBehavior.WHEN_NO_MATCH,
      integrationResponses: [{
        statusCode: '200',
        responseTemplates: {
          [AMZ_JSON_1_0]: [
            "#set($root = $input.path('$'))",
            '{',
            // eslint-disable-next-line quotes
            `  "Count": $root.Count,`,
            '  "Results": [',
            '#foreach($item in $root.Items)',
            '#set($env = $item.env.S)',
            '#set($test = $item.test.S)',
            '#set($result = $item.result.S)',
            '    {',
            '      "env": "$util.escapeJavaScript($env)",',
            '      "test": "$util.escapeJavaScript($test)",',
            '      "result": $result', // Provide the JSON directly.
            '    }#if($foreach.hasNext),#end',
            '#end',
            '',
            '  ]',
            '}',
          ].join('\n'),
        },
      }],
    },
  }),
});

Acknowledge

skinny85 commented 2 years ago

Thanks for opening the issue @misterjoshua!