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

Parameterized swagger support for API gateway #1461

Closed
shimzim opened this issue Jan 1, 2019 · 33 comments · Fixed by #25876
Closed

Parameterized swagger support for API gateway #1461

shimzim opened this issue Jan 1, 2019 · 33 comments · Fixed by #25876
Assignees
Labels
@aws-cdk/aws-apigateway Related to Amazon API Gateway effort/large Large work item – several weeks of effort feature/enhancement A new API to make things easier or more intuitive. A catch-all for general feature requests. feature-request A feature should be added or improved. p1

Comments

@shimzim
Copy link

shimzim commented Jan 1, 2019

I know that Swagger integration for the API-gateway resources is on the CDK roadmap.

I think it can bring a lot of value, since at the moment, if you want to use (using CFN) a swagger file you can either inline it and reference CFN resources, or import it and not reference anything.

Typically, you'd want both - you want to separate the swagger file from your CFN/CDK code so that you can use all the fancy tools (UI editor / client generation / etc), but also usually you'd need to reference CFN resources (e.g. lambda integrations).

With CDK it can be possible to have a templated external swagger file, and use string replacements for the referenced resources.

Took this offline with @eladb who suggested something in the lines of:

new apigateway.RestApi(this, 'MyAPI', {
  swagger: Swagger.load('/path/to/swagger', {
    xxx: myLambdaFunction.functionName,
    yyy: myAuthenticator.functionArn
  }
});

I think it could bring huge value to CDK users as you can use the "API first" methodology and leverage all the existing swagger tools.

@eladb eladb added feature-request A feature should be added or improved. @aws-cdk/aws-apigateway Related to Amazon API Gateway labels Jan 1, 2019
@rix0rrr rix0rrr added the gap label Jan 4, 2019
@eladb
Copy link
Contributor

eladb commented Jan 10, 2019

Before we implement built in support for swagger parameterization, users can consider something like https://ejs.co

@abbottdev
Copy link

abbottdev commented Jun 11, 2019

At the moment I'm working around this by using the swagger-parser npm library to parse my swagger file on start up, and then dynamically add lambda integrations. Disclaimer I don't know how well cdk will continue to handle running promises but as of 0.34.0 it works.

Hope this helps people workaround this until it's implemented:
https://gist.github.com/abbottdev/17379763ebc14a5ecbf2a111ffbcdd86

It may be a bit dynamic for people to want to use, it could always be refactored to use more explicitly defined lambda functions quite easily.

@eladb eladb self-assigned this Aug 12, 2019
@eladb eladb assigned nija-at and unassigned eladb Sep 3, 2019
@nija-at nija-at added effort/large Large work item – several weeks of effort and removed gap labels Feb 13, 2020
@ryan-mars
Copy link

Before we implement built in support for swagger parameterization, users can consider something like https://ejs.co

@eladb How would you resolve a Lambda ARN (for x-amazon-apigateway-integration) at the time you process the EJS template?

@eladb
Copy link
Contributor

eladb commented Feb 20, 2020

@ryan-mars If you plug in stringified tokens into the template, the CDK will resolve them during synthesis and render the swagger definition as a CFN expression that resolves these values in deployment.

@ywauto83
Copy link

ywauto83 commented Mar 13, 2020

@ryan-mars If you plug in stringified tokens into the template, the CDK will resolve them during synthesis and render the swagger definition as a CFN expression that resolves these values in deployment.

Tried ejs.renderfile and it's resolving to the arn to ${Token[TOKEN.77]} on the CFN template and if Fn:Sub is still used, it can cause an error. So, in the URI, I replaced ${AWS::Region} in order to get rid of Fn:Sub. And it solves the problem. I am wondering if there is a better way

@ryan-mars
Copy link

ryan-mars commented Mar 13, 2020

@ywauto83 Using the yaml npm module I read in the OAS file. I manilpulate the values in the OAS file as needed using CDK constructs (which use tokens obviously) and pass the javascript object to the body parameter of CfnApi (which expects a JSON object according to the CloudFormation documentation). Apparently the CDK construct takes a JS object in that case. It works fine and I didn't need to use ejs after all.

@ywauto83
Copy link

ywauto83 commented Mar 13, 2020

I manilpulate the values in the OAS file as needed using CDK constructs (which use tokens obviously)
@ryan-mars
This is the key part I was trying to understand better. In my way, I used ejs to replace<%=tag%> in the OAS file. How did you manipulate the values only with CDK constructs? Cheers!

@mmuller88
Copy link
Contributor

mmuller88 commented Mar 24, 2020

I solved. Basically how @ryan-mars describes it but I think my solution is a bit more nice.

const api = new apigateway.RestApi(this, 'itemsApi', {
  restApiName: 'Items Service'
});

const cfnApi = api.node.defaultChild as apigateway.CfnRestApi;
// Upload Swagger to S3
const fileAsset = new assets.Asset(this, 'SwaggerAsset', {
  path: join(__dirname, 'templates/swagger.yaml')
});

cfnApi.bodyS3Location = {bucket: fileAsset.bucket.bucketName, key: fileAsset.s3ObjectKey };

Export from existing API Gatway the swagger yaml with API Gateway extension
In the code it first uploads the swagger.yaml file to s3. Than it sets the bodyS3Location property to RestApi in CFN.

Don't forget to set the validatior!

const validator = api.addRequestValidator('DefaultValidator', {
  validateRequestBody: true,
  validateRequestParameters: true
});

const createOneIntegration = new apigateway.LambdaIntegration(createOneApi;

items.addMethod('POST', createOneIntegration, { requestValidator: validator});

Oh boy that was something :)

@ryan-mars
Copy link

ryan-mars commented Mar 24, 2020

This is the key part I was trying to understand better. In my way, I used ejs to replace<%=tag%> in the OAS file. How did you manipulate the values only with CDK constructs? Cheers!

@ywauto83 Apologies for not seeing your reply. Here's what I'm doing...

this is ugly buuuuuuuut

const file = readFileSync("customer.v1.openapi.yaml", "utf8");

let spec = YAML.parse(file);

const api = new HttpApi(this, "CustomerAPI", {
  name: "API for customers",
  openApiSpec: spec,
  integrations: [
    { path: "/users", method: "post" },
    { path: "/users", method: "get" },
    { path: "/users/{id}", method: "get" }
  ]
});
interface HttpApiIntegrationProps {
  path: string;
  method: string;
}

interface HttpApiProps {
  name: string;
  openApiSpec: any;
  integrations: Array<HttpApiIntegrationProps>;
}

export class HttpApi extends cdk.Construct {
  readonly cfnApi: apigatewayv2.CfnApi;
  readonly logGroup: LogGroup;
  readonly stage: apigatewayv2.CfnStage;
  readonly functions: Array<lambda.Function>;

  constructor(scope: cdk.Construct, id: string, props: HttpApiProps) {
    super(scope, id);
    this.functions = [];

    const stack = Stack.of(this);
    let spec = props.openApiSpec;

    props.integrations.map(e => {
      if (spec?.paths?.[e.path]?.[e.method] == undefined) {
        const error = `There is no path in the Open API Spec matching ${e.method} ${e.path}`;
        console.error(error);
        throw error;
      } else {
        const cleanPath = e.path.replace(/[{}]/gi, "");
        const func = new NodejsFunction(this, `${e.method}${cleanPath}`, {
          entry: join("src", "api", ...cleanPath.split("/"), `${e.method}.ts`)
        });
        this.functions.push(func);
        spec.paths[e.path][e.method]["x-amazon-apigateway-integration"] = {
          type: "AWS_PROXY",
          httpMethod: "POST",
          uri: func.functionArn,
          payloadFormatVersion: "1.0"
        };
      }
    });

    this.cfnApi = new apigatewayv2.CfnApi(this, "HttpApi", {
      body: spec
    });

    this.functions.map((f, i) => {
      new lambda.CfnPermission(this, `LambdaPermission_${i}`, {
        action: "lambda:InvokeFunction",
        principal: "apigateway.amazonaws.com",
        functionName: f.functionName,
        sourceArn: `arn:${stack.partition}:execute-api:${stack.region}:${stack.account}:${this.cfnApi.ref}/*/*`
      });
    });

    this.logGroup = new LogGroup(this, "DefaultStageAccessLogs");

    this.stage = new apigatewayv2.CfnStage(this, "DefaultStage", {
      apiId: this.cfnApi.ref,
      stageName: "$default",
      autoDeploy: true,
      accessLogSettings: {
        destinationArn: this.logGroup.logGroupArn,
        format:
          '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","routeKey":"$context.routeKey", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength" }'
      }
    });
  }
}

Then I have one file (exporting a single function 'handler') for each path/method. Like

src/api/users/get.ts
src/api/users/post.ts
src/api/users/id/get.ts
etc..

@ywauto83
Copy link

Thanks for sharing!

@mmuller88
Copy link
Contributor

one update more. There is a little caveat. If you kind of change an api lambda the uri of it changes as well and is not correct anymore in the api gateway if you used swagger. Its basically the problem the creator of this issue describes.

I solved it with cdk deploy two times

export WITH_SWAGGER='false' && cdk deploy
merge extracted Swagger and Swagger validation file ...
export WITH_SWAGGER='true' && cdk deploy

The first one without using the swagger file. Than I cli extract the swagger file. This one now I merge with a swagger which only contains the parameter validations kind of:
swagger_validation.yaml MERGE swagger_new.yaml

that generates swagger_full.yaml .

swagger_full.yaml is now clean with updated Lambda uris and will be used for the second deploy.

I will write a blog post about that workaround and post it here.

@strottos
Copy link

strottos commented Aug 7, 2020

Martin's blog post FYI: https://martinmueller.dev/cdk-swagger-eng/#:~:text=In%20the%20last%20post%20I,for%20describing%20your%20cloud%20infrastructure.&text=When%20using%20AWS%20API%20Gateway,Query%2C%20Path%20and%20Body%20Parameter.

As this was something I was just searching for and came up. Haven't tried it yet but thanks to Martin for putting this together 👍

@nija-at nija-at added the p1 label Aug 25, 2020
@drissamri
Copy link

That seems like a lot of hassle for something so simple if you look how it is handled in AWS SAM etc?

@ryan-mars
Copy link

@drissamri My comment is now long out of date and should not be used. CDK moves quickly.

@zacyang
Copy link

zacyang commented Oct 22, 2020

@ryan-mars does it mean there's a native support in CDK now? as of 1.61.X?

@suud
Copy link

suud commented Dec 28, 2020

I've shared my solution in this stackoverflow answer. It works without deploying twice.
Maybe @ryan-mars knows an even cleaner way?

I have also published this openapigateway construct for Python to abstract away some complexity.

@ryan-mars
Copy link

@suud Your StackOverflow answer is exactly what I'm doing with JS now.

@zacyang There's currently no support for OpenAPI in the @aws-cdk/aws-apigatewayv2.HttpApi construct like there is in @aws-cdk_aws-apigateway.SpecRestApi.

@heyitsmepatg
Copy link

Does using SpecRestApi solve the original issue?

@ericzbeard ericzbeard added the feature/enhancement A new API to make things easier or more intuitive. A catch-all for general feature requests. label Apr 7, 2021
@chialunwu
Copy link

Bumping this. AWS team, this issue desperately needs your attention.

@OGoodness
Copy link

Bumping this as well

1 similar comment
@mfaisalpasha
Copy link

Bumping this as well

@othe1492
Copy link

othe1492 commented Jun 7, 2022

Bumping this as we have started looking at using OpenApi specifications to set up API Gateway rest api but we need to be able to set up and map integrations in code along side the endpoint configuration in the specification.

@flochaz
Copy link
Contributor

flochaz commented Jun 7, 2022

Might help : https://www.youtube.com/watch?v=Ey7bNVT4W1g&t=12363s / https://github.com/alma-cdk/openapix

@andyRokit
Copy link

I think that the solution to this lies with CloudFormation rather than CDK.

Unlike API Gateway, the Step Functions resource supports the concept of DefinitionSubstitutions.

This would benefit CDK and CF (as it also requires a transformation to use variables) as well as remain consistent with CF's current approach.

Assuming that property was added, the CDK code would be:

const myLambdaFunction = ...
const myApiGatewayRole = ...

const api = new apigateway.SpecRestApi(this, 'books-api', {
  apiDefinition: apigateway.ApiDefinition.fromAsset('path-to-file.json'),
  definitionSubstitutions: {
    'myLambdaFunctionArn': myLambdaFunction.functionArn,
    'myApiGatewayRoleArn': myApiGatewayRole.roleArn
  }
});

I've raised a feature request to the CloudFormation team asking for this property to be added. If it makes sense to anybody reading this issue, feel free to thumbs-up the issue.

@rix0rrr
Copy link
Contributor

rix0rrr commented Oct 19, 2022

If we don't want to wait for CloudFormation to add DefinitionSubstitutions support for these resources, I would be amenable to someone adding something similar to the aws-s3-deployments module, we could do this for any JSON definition for any construct (or even arbitrary text).

const deployment = new s3deploy.BucketDeployment(this, 'DeployWebsite', {
  sources: [s3deploy.Source.asset('path-to-definition.json')],
  destinationBucket: websiteBucket,
  prune: false,
  extract: false,
  
  /* THE BELOW WOULD BE NEW */
  substitutions: {
    'myLambdaFunctionArn': myLambdaFunction.functionArn,
    'myApiGatewayRoleArn': myApiGatewayRole.roleArn
  }
});

const api = new apigateway.SpecRestApi(this, 'books-api', {
  apiDefinition: apigateway.ApiDefinition.fromBucket(deployment.deployedBucket, Fn.select(0, myBucketDeployment.objectKeys)),
});

The new s3deploy.BucketDeployment could do with some sugar (prune and extract and objectKeys are tricky), perhaps:

const deployment = new s3deploy.DeployTimeSubstitutedFile(this, 'DeployWebsite', {
  sources: s3deploy.Source.asset('path-to-definition.json'),
  destinationBucket: websiteBucket,
  substitutions: {
    'myLambdaFunctionArn': myLambdaFunction.functionArn,
    'myApiGatewayRoleArn': myApiGatewayRole.roleArn
  }
});

const api = new apigateway.SpecRestApi(this, 'books-api', {
  apiDefinition: apigateway.ApiDefinition.fromBucket(deployment.bucket, deployment.objectKey),
});

@zxkane
Copy link
Contributor

zxkane commented Oct 28, 2022

For REST API I used the mustache replacing the parameters as inline api definition, which supports the intrinsic functions of CloudFormation.

   const variables = {
      ...
    };
    const restOpenAPISpec = this.resolve(Mustache.render(
      fs.readFileSync(path.join(__dirname, './rest-sqs.yaml'), 'utf-8'),
      variables));
    new SpecRestApi(this, 'rest-to-sqs', {
      apiDefinition: ApiDefinition.fromInline(restOpenAPISpec),
      endpointExportName: 'APIEndpoint',
      deployOptions,
    });

But the CloudFormation of HTTP API does not support intrinsic functions as inline API definition, you have to put the OpenAPI definition to S3, then import it from bucket. You can use built-in custom resource to create the object in S3 like below,

    const variables = {
     ...
    };
    const openAPISpec = this.resolve(Mustache.render(
      fs.readFileSync(path.join(__dirname, './http-sqs.yaml'), 'utf-8'), variables));

    const contentHash = strHash(JSON.stringify(openAPISpec));

    const openAPIFile = `install/openapi-${contentHash}.yaml`;
    const sdkPutCall = {
      service: 'S3',
      action: 'putObject',
      parameters: {
        Body: openAPISpec,
        Bucket: bucket.bucketName,
        Key: openAPIFile,
      },
      physicalResourceId: PhysicalResourceId.of(`openapi-upsert-${contentHash}`),
    };
    const createOpenAPIFile = new AwsCustomResource(
      this,
      'CreateOpenAPIDefinition',
      {
        onCreate: sdkPutCall,
        onUpdate: sdkPutCall,
        installLatestAwsSdk: false,
        policy: AwsCustomResourcePolicy.fromSdkCalls({
          resources: [bucket.arnForObjects('install/openapi-*.yaml')],
        }),
      },
    );

    const httpApi = new CfnApi(this, 'http-api-to-sqs', {
      bodyS3Location: {
        bucket: bucket.bucketName,
        key: openAPIFile,
      },
      failOnWarnings: false,
    });
    httpApi.node.addDependency(createOpenAPIFile);

You can refer to the complete example.

Hope those can be done in apigateway L2 construct.

@zxkane
Copy link
Contributor

zxkane commented Nov 9, 2022

Just turned out the Body of AWS::ApiGatewayV2::Api only supports Json not Yaml string.

It works after converting the yaml OpenAPI to Json.

const yaml = require('js-yaml');

...

    // import openapi as http api
    const variables = {
      integrationRoleArn: apiRole.roleArn,
      queueName: bufferQueue.queueName,
      queueUrl: bufferQueue.queueUrl,
    };
    const openAPISpec = this.resolve(yaml.load(Mustache.render(
      fs.readFileSync(path.join(__dirname, './http-sqs.yaml'), 'utf-8'), variables)));

    const httpApi = new CfnApi(this, 'http-api-to-sqs', {
      body: openAPISpec,
      failOnWarnings: false,
    });

@ddvirt
Copy link

ddvirt commented Jan 27, 2023

I recently implemented the same by using just Python and CDK as following

from aws_cdk import (
    aws_apigateway as apig,
    aws_lambda,
    Stack,
    BundlingOptions,
    aws_s3_assets as s3_assets,
    Fn
)
from constructs import Construct

from os import path

import json

class ApigLambdaStack(Stack):

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

        my_lambda_fn = aws_lambda.Function(self, 'MyFunction',
            runtime=aws_lambda.Runtime.PYTHON_3_9,
            handler='main.handler',            
            code=aws_lambda.Code.from_asset(path.join(path.dirname('.'), 'my_lambda_fn'),
                bundling=BundlingOptions(
                    image=aws_lambda.Runtime.PYTHON_3_9.bundling_image,
                        command=['bash', '-c', 'pip install -r requirements.txt -t /asset-output && cp -au . /asset-output'
                    ]
                )
            )
        )

        swagger_file = None
        with open('./swagger.json') as f:
            swagger_file = f.read()

        swagger_file = swagger_file.replace("${LAMBDA_ARN}", my_lambda_fn.function_arn)                                    

        my_apig = apig.SpecRestApi(self, 'my-awesome-apis', api_definition=apig.ApiDefinition.from_inline(json.loads(swagger_file)))

and in my swagger.json file I've

{
    "openapi" : "3.0.1",
    "info" : {
      "title" : "demo",
      "version" : "2023-01-26T11:14:54Z"
    },
    "servers" : [ {
      "url" : "https://12345abc.execute-api.eu-west-1.amazonaws.com/{basePath}",
      "variables" : {
        "basePath" : {
          "default" : "/v1"
        }
      }
    } ],
    "paths" : {
      "/" : {
        "post" : {
          "responses" : {
            "200" : {
              "description" : "200 response",
              "content" : {
                "application/json" : {
                  "schema" : {
                    "$ref" : "#/components/schemas/Empty"
                  }
                }
              }
            }
          },
          "x-amazon-apigateway-integration" : {
            "type" : "aws_proxy",
            "uri" : "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LAMBDA_ARN}/invocations",
            "httpMethod" : "POST",
            "responses" : {
              "default" : {
                "statusCode" : "200"
              }
            },
            "passthroughBehavior" : "when_no_match",
            "contentHandling" : "CONVERT_TO_TEXT"
          }
        }
      }
    },
    "components" : {
      "schemas" : {
        "Empty" : {
          "title" : "Empty Schema",
          "type" : "object"
        }
      }
    }
  }

NB To be clear LAMBDA_ARN is a placeholder that will be replaced by CloudFormation and not by Python! Python doesn't know anything about the Lambda ARN...

Hope this helps!

@andyRokit
Copy link

@ddvirt Are you sure about that? It looks like it's Python doing the replacement. Could you provide a snippet from the generated template file when you deploy (or a cdk synth)

@ddvirt
Copy link

ddvirt commented Jan 27, 2023

@ddvirt Are you sure about that? It looks like it's Python doing the replacement. Could you provide a snippet from the generated template file when you deploy (or a cdk synth)

Sure here it's my synthesised template (changed API G server URL to mask confidential data)

{
 "Resources": {
  "MyFunctionServiceRole3C357FF2": {
   "Type": "AWS::IAM::Role",
   "Properties": {
    "AssumeRolePolicyDocument": {
     "Statement": [
      {
       "Action": "sts:AssumeRole",
       "Effect": "Allow",
       "Principal": {
        "Service": "lambda.amazonaws.com"
       }
      }
     ],
     "Version": "2012-10-17"
    },
    "ManagedPolicyArns": [
     {
      "Fn::Join": [
       "",
       [
        "arn:",
        {
         "Ref": "AWS::Partition"
        },
        ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
       ]
      ]
     }
    ]
   },
   "Metadata": {
    "aws:cdk:path": "ApigLambdaStack/MyFunction/ServiceRole/Resource"
   }
  },
  "MyFunction3BAA72D1": {
   "Type": "AWS::Lambda::Function",
   "Properties": {
    "Code": {
     "S3Bucket": {
      "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}"
     },
     "S3Key": "369bffe60cabc36675bfc3cf31710d799c7346955d049348da1c9fbfb2bf32ca.zip"
    },
    "Role": {
     "Fn::GetAtt": [
      "MyFunctionServiceRole3C357FF2",
      "Arn"
     ]
    },
    "Handler": "main.handler",
    "Runtime": "python3.9"
   },
   "DependsOn": [
    "MyFunctionServiceRole3C357FF2"
   ],
   "Metadata": {
    "aws:cdk:path": "ApigLambdaStack/MyFunction/Resource",
    "aws:asset:path": "asset.369bffe60cabc36675bfc3cf31710d799c7346955d049348da1c9fbfb2bf32ca",
    "aws:asset:is-bundled": true,
    "aws:asset:property": "Code"
   }
  },
  "myawesomeapisF4FA350C": {
   "Type": "AWS::ApiGateway::RestApi",
   "Properties": {
    "Body": {
     "openapi": "3.0.1",
     "info": {
      "title": "demo",
      "version": "2023-01-26T11:14:54Z"
     },
     "servers": [
      {
       "url": "https://123435Abc.execute-api.eu-west-1.amazonaws.com/{basePath}",
       "variables": {
        "basePath": {
         "default": "/v1"
        }
       }
      }
     ],
     "paths": {
      "/": {
       "post": {
        "responses": {
         "200": {
          "description": "200 response",
          "content": {
           "application/json": {
            "schema": {
             "$ref": "#/components/schemas/Empty"
            }
           }
          }
         }
        },
        "x-amazon-apigateway-integration": {
         "type": "aws_proxy",
         "uri": {
          "Fn::Join": [
           "",
           [
            "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/",
            {
             "Fn::GetAtt": [
              "MyFunction3BAA72D1",
              "Arn"
             ]
            },
            "/invocations"
           ]
          ]
         },
         "httpMethod": "POST",
         "responses": {
          "default": {
           "statusCode": "200"
          }
         },
         "passthroughBehavior": "when_no_match",
         "contentHandling": "CONVERT_TO_TEXT"
        }
       }
      }
     },
     "components": {
      "schemas": {
       "Empty": {
        "title": "Empty Schema",
        "type": "object"
       }
      }
     }
    },
    "Name": "my-awesome-apis"
   },
   "Metadata": {
    "aws:cdk:path": "ApigLambdaStack/my-awesome-apis/Resource"
   }
  },
  "myawesomeapisDeployment3DCDFC8F60566bd1d73a506841ab591a21bcead1": {
   "Type": "AWS::ApiGateway::Deployment",
   "Properties": {
    "RestApiId": {
     "Ref": "myawesomeapisF4FA350C"
    },
    "Description": "Automatically created by the RestApi construct"
   },
   "Metadata": {
    "aws:cdk:path": "ApigLambdaStack/my-awesome-apis/Deployment/Resource"
   }
  },
  "myawesomeapisDeploymentStageprod048352FC": {
   "Type": "AWS::ApiGateway::Stage",
   "Properties": {
    "RestApiId": {
     "Ref": "myawesomeapisF4FA350C"
    },
    "DeploymentId": {
     "Ref": "myawesomeapisDeployment3DCDFC8F60566bd1d73a506841ab591a21bcead1"
    },
    "StageName": "prod"
   },
   "Metadata": {
    "aws:cdk:path": "ApigLambdaStack/my-awesome-apis/DeploymentStage.prod/Resource"
   }
  },
  "CDKMetadata": {
   "Type": "AWS::CDK::Metadata",
   "Properties": {
    "Analytics": "v2:deflate64:H4sIAAAAAAAA/01Py47CMAz8Fu6pWUBCXHmIK6h8ADKpt5i2SUQcoarqv5OksNrTPDSjsZewXsLPDF++0FVTtHyD4SKoGxWt69Bid6sQhmMwWtgatf81Xz4qxg6G0raU7ISj8qsrek/iYZsgatgF3ZDs0JNCxzUKvbCPK450SV62jnP9QyPa8NSU8wdyre07MpIi/1Q8sc6rmYzjX03l2eSyqVPgFMQF+SxMkcj31lQ8PXHu5W7NfAUbWKxnD89cPIMR7gjKCd9JPa9aJAEAAA=="
   },
   "Metadata": {
    "aws:cdk:path": "ApigLambdaStack/CDKMetadata/Default"
   },
   "Condition": "CDKMetadataAvailable"
  }
 },
 "Outputs": {
  "myawesomeapisEndpoint0D61F7DD": {
   "Value": {
    "Fn::Join": [
     "",
     [
      "https://",
      {
       "Ref": "myawesomeapisF4FA350C"
      },
      ".execute-api.",
      {
       "Ref": "AWS::Region"
      },
      ".",
      {
       "Ref": "AWS::URLSuffix"
      },
      "/",
      {
       "Ref": "myawesomeapisDeploymentStageprod048352FC"
      },
      "/"
     ]
    ]
   }
  }
 },
 "Conditions": {
  "CDKMetadataAvailable": {
   "Fn::Or": [
    {
     "Fn::Or": [
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "af-south-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "ap-east-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "ap-northeast-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "ap-northeast-2"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "ap-south-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "ap-southeast-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "ap-southeast-2"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "ca-central-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "cn-north-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "cn-northwest-1"
       ]
      }
     ]
    },
    {
     "Fn::Or": [
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "eu-central-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "eu-north-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "eu-south-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "eu-west-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "eu-west-2"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "eu-west-3"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "me-south-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "sa-east-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "us-east-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "us-east-2"
       ]
      }
     ]
    },
    {
     "Fn::Or": [
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "us-west-1"
       ]
      },
      {
       "Fn::Equals": [
        {
         "Ref": "AWS::Region"
        },
        "us-west-2"
       ]
      }
     ]
    }
   ]
  }
 },
 "Parameters": {
  "BootstrapVersion": {
   "Type": "AWS::SSM::Parameter::Value<String>",
   "Default": "/cdk-bootstrap/hnb659fds/version",
   "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
  }
 },
 "Rules": {
  "CheckBootstrapVersion": {
   "Assertions": [
    {
     "Assert": {
      "Fn::Not": [
       {
        "Fn::Contains": [
         [
          "1",
          "2",
          "3",
          "4",
          "5"
         ],
         {
          "Ref": "BootstrapVersion"
         }
        ]
       }
      ]
     },
     "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
    }
   ]
  }
 }
}

@andyRokit
Copy link

Wow. Fair enough. CDK seems to be freakishly clever! Even the way it wraps the whole value in Fn::Join. Very good - thanks!

@mergify mergify bot closed this as completed in #25876 Jun 16, 2023
mergify bot pushed a commit that referenced this issue Jun 16, 2023
…titutions in file (#25876)

Closes #1461 

The `DeployTimeSubstitutedFile` construct allows you to upload a file and specify substitutions to be made in it, which will be resolved during deployment.

For example, if you wanted to create a REST API from a Swagger file spec but want to reference other CDK resources in your API spec, you can now do so in-line:

```ts
const bucket: Bucket;
const myLambdaFunction: lambda.Function;

const deployment = new s3deploy.DeployTimeSubstitutedFile(this, 'MyApiFile', {
  source: 'my-swagger-spec.yaml',
  destinationBucket: bucket,
  substitutions: {
    xxxx: myLambdaFunction.functionArn,
    yyyy: 'mySubstitution',
  },
});

const api = new apigateway.SpecRestApi(this, 'books-api', {
  apiDefinition: apigateway.ApiDefinition.fromBucket(deployment.bucket, deployment.objectKey),
});
```

Where 'xxxx' and 'yyyy' are the examples of placeholder text you can add in your local file spec to be substituted by surrounding the placeholder with double curly braces, for example writing: `{{ xxxx }}` in your file where you want a substitution.

----
*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
@github-actions
Copy link

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

@pawKer
Copy link

pawKer commented Aug 18, 2023

Just commenting to say this isn't actually fixed in #25876 and would still need #1233 raised by @andyRokit to be implemented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/aws-apigateway Related to Amazon API Gateway effort/large Large work item – several weeks of effort feature/enhancement A new API to make things easier or more intuitive. A catch-all for general feature requests. feature-request A feature should be added or improved. p1
Projects
None yet
Development

Successfully merging a pull request may close this issue.