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.71k stars 3.93k forks source link

Parameterized swagger support for API gateway #1461

Closed shimzim closed 1 year ago

shimzim commented 5 years ago

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 commented 5 years ago

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

abbottdev commented 5 years ago

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.

ryan-mars commented 4 years ago

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 commented 4 years ago

@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 commented 4 years ago

@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 commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

Thanks for sharing!

mmuller88 commented 4 years ago

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 commented 4 years ago

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 👍

drissamri commented 4 years ago

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

ryan-mars commented 4 years ago

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

zacyang commented 4 years ago

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

suud commented 3 years ago

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 commented 3 years ago

@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 commented 3 years ago

Does using SpecRestApi solve the original issue?

chialunwu commented 2 years ago

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

OGoodness commented 2 years ago

Bumping this as well

mfaisalpasha commented 2 years ago

Bumping this as well

othe1492 commented 2 years ago

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 commented 2 years ago

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

andyRokit commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 1 year ago

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 commented 1 year ago

@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 commented 1 year ago

@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 commented 1 year ago

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

github-actions[bot] commented 1 year ago

⚠️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 commented 1 year ago

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