You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用SAM部署API Gateway时遇CloudFormation模板Fn::ToJsonString不支持错误

部署API Gateway时Fn::ToJsonString不支持的错误排查与解决

问题现象

使用sam build/sam deploy部署API Gateway时触发如下错误:

Error: Failed to create changeset for the stack: my-api, ex: Waiter ChangeSetCreateComplete failed: Waiter encountered a terminal failure state: For expression "Status" we matched expected path: "FAILED" Status: FAILED. Reason: Template Error: Encountered unsupported function: Fn::ToJsonString Supported functions are: [Fn::Base64, Fn::GetAtt, Fn::GetAZs, Fn::ImportValue, Fn::Join, Fn::Split, Fn::FindInMap, Fn::Select, Ref, Fn::Equals, Fn::If, Fn::Not, Condition, Fn::And, Fn::Or, Fn::Contains, Fn::EachMemberEquals, Fn::EachMemberIn, Fn::ValueOf, Fn::ValueOfAll, Fn::RefAll, Fn::Sub, Fn::Cidr]

此前部署完全正常,仅修改过api.yaml,回滚该修改后问题仍存在。已尝试使用历史版本、调整Transform块中AWS::Serverless-2016-10-31与AWS::LanguageExtensions的顺序,但均未解决问题。

涉及的CloudFormation模板

AWSTemplateFormatVersion: '2010-09-09'
Transform:
    - AWS::Serverless-2016-10-31
    - AWS::LanguageExtensions
Description: >
    Service root

Parameters:
    EnvType:
        Description: 'Environment type'
        Default: test
        Type: String
        AllowedValues: ['prod', 'dev', 'stage', 'test']
    DefaultServicesVersion:
        Default: latest
        Type: String
    ServicesPrefix:
        Type: String
        Default: ''
    DomainName:
        Type: String
        Default: api.link
    EdgeCertificateArn:
        Type: String
        Default: ''
    HostedZoneId:
        Type: String
        Default: ''
    WafRateLimit:
        Type: Number
        Default: '100'
    WafCountryCodes:
        Type: String
        Default: 'DE'
    JwtAudience:
        Type: String
        Default: 'dee'
    JwtIssuer:
        Type: String
    JwksUri:
        Type: String

Conditions:
    IsEnvProd: !Equals [!Ref EnvType, 'prod']
    IsEnvTest: !Equals [!Ref EnvType, 'test']
    IsEnvNotTest: !Not [!Equals [!Ref EnvType, 'test']]

Globals:
    Api:
        TracingEnabled: true 
        OpenApiVersion: 3.0.1 
    Function:
        Timeout: 5 # Default timeout is 5 seconds
        Handler: lambda.handler
        Runtime: nodejs18.x
        Environment:
            Variables:
                AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1
Resources:
    RestApi:
        Condition: IsEnvNotTest
        Type: AWS::Serverless::Api
        Properties:
            Name: !Sub '${AWS::StackName}-api'
            StageName: !Sub ${EnvType}
            AccessLogSetting:
                DestinationArn: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:DeeCloudWatchApiLogs:*'
                Format:
                    Fn::ToJsonString:
                        Fn::Transform:
                            Name: AWS::Include
                            Parameters:
                                Location: './routes/access-log-settings.json'
            Tags:
                monitoring: SLO
                budgeting: SLO
            DefinitionBody:
                Fn::Transform:
                    Name: AWS::Include
                    Parameters:
                        Location: './routes/api.yaml'
            Domain:
                CertificateArn: !Sub '${EdgeCertificateArn}'
                DomainName: !Sub '${DomainName}'
                EndpointConfiguration: EDGE
                Route53:
                    HostedZoneId: !Sub '${HostedZoneId}'
                    EvaluateTargetHealth: true

    RestApiAuthorizer:
        Condition: IsEnvNotTest
        Type: 'AWS::ApiGateway::Authorizer'
        Properties:
            AuthorizerCredentials: !GetAtt
                - LambdaAuthorizerInvocationRole
                - Arn
            AuthorizerResultTtlInSeconds: '3600'
            AuthorizerUri: !Join
                - ''
                - - 'arn:aws:apigateway:'
                  - !Ref 'AWS::Region'
                  - ':lambda:path/2015-03-31/functions/'
                  - !GetAtt
                    - RestApiAuthorizerFunction
                    - Arn
                  - /invocations
            Type: TOKEN
            IdentitySource: method.request.header.Authorization
            Name: !Sub ${AWS::StackName}-api-authorizer
            RestApiId: !Ref RestApi
            IdentityValidationExpression: ^Bearer [-0-9a-zA-z\.]*$

    LambdaAuthorizerInvocationRole:
        Condition: IsEnvNotTest
        Type: AWS::IAM::Role
        Properties:
            AssumeRolePolicyDocument:
                Version: '2012-10-17'
                Statement:
                    - Effect: Allow
                      Principal:
                          Service:
                              - lambda.amazonaws.com
                              - apigateway.amazonaws.com
                      Action:
                          - 'sts:AssumeRole'
            Policies:
                - PolicyName: !Sub ${AWS::StackName}-AWSLambdaRole
                  PolicyDocument:
                      Version: '2012-10-17'
                      Statement:
                          - Effect: Allow
                            Action:
                                - 'lambda:InvokeFunction'
                            Resource: '*'

    RestApiAuthorizerFunction:
        Condition: IsEnvNotTest
        Type: AWS::Serverless::Function
        Properties:
            FunctionName: !Sub ${AWS::StackName}-api-authorizer-func
            CodeUri: auth/jwt-rsa-auth/src/
            Environment:
                Variables:
                    JWT_AUDIENCE: !Ref JwtAudience
                    JWT_ISSUER: !Ref JwtIssuer
                    JWKS_URI: !Ref JwksUri
                    NODE_TLS_REJECT_UNAUTHORIZED: 0 #TODO: WARNING - DEV ONLY. Security Major issue. Remove later
            Role: !GetAtt
                - LambdaAuthorizerInvocationRole
                - Arn
            Tags:
                monitoring: SLO
                budgeting: SLO
            Policies:
                - AWSLambdaBasicExecutionRole
                - Version: '2012-10-17'
                  Statement:
                      - Effect: Allow
                        Principal:
                            Service:
                                - apigateway.amazonaws.com
                        Action:
                            - 'lambda:InvokeFunction'
                        Resource: !GetAtt RestApiAuthorizer.Arn

    TestingRestApi:
        Condition: IsEnvTest
        Type: AWS::Serverless::Api
        Properties:
            Name: !Sub '${AWS::StackName}-testing-api'
            StageName: !Sub ${EnvType}
            AccessLogSetting:
                DestinationArn: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:DeeCloudWatchApiLogs:*'
                Format:
                    Fn::ToJsonString:
                        Fn::Transform:
                            Name: AWS::Include
                            Parameters:
                                Location: './routes/access-log-settings.json'
            Tags:
                monitoring: SLO
                budgeting: SLO
            DefinitionBody:
                Fn::Transform:
                    Name: AWS::Include
                    Parameters:
                        Location: './routes/api.yaml'

    WafModule:
        Condition: IsEnvProd
        Type: CG::Security::RestApiWaf::MODULE
        Properties:
            RestApi: !Sub ${RestApi}
            RestApiStage: !Sub ${RestApi.Stage}
            WafRateLimit: !Sub ${WafRateLimit}
            WafCountryCodes: !Sub ${WafCountryCodes}

    ApiGatewayStepFunctionsRole:
        Type: AWS::IAM::Role
        Properties:
            Path: !Join ['', ['/', !Ref 'AWS::StackName', '/']]
            AssumeRolePolicyDocument:
                Version: 2012-10-17
                Statement:
                    - Sid: AllowApiGatewayServiceToAssumeRole
                      Effect: Allow
                      Action:
                          - 'sts:AssumeRole'
                      Principal:
                          Service:
                              - apigateway.amazonaws.com
            Policies:
                - PolicyName: CallStepFunctions
                  PolicyDocument:
                      Version: 2012-10-17
                      Statement:
                          - Effect: Allow
                            Action:
                                - 'states:StartExecution'
                            Resource: '*'
                - PolicyName: DescribeStepFunctionExecutions
                  PolicyDocument:
                      Version: 2012-10-17
                      Statement:
                          - Effect: Allow
                            Action:
                                - 'states:DescribeExecution'
                            Resource: '*'
Outputs:
    ApiEndpoint:
        Description: API Endpoint
        Export:
            Name: !Sub ${AWS::StackName}-ApiEndpoint
        Value:
            Fn::If:
                - IsEnvTest
                - !Sub 'https://${TestingRestApi}.execute-api.${AWS::Region}.amazonaws.com/${TestingRestApi.Stage}'
                - !Sub 'https://${DomainName}'

解决方法

1. 调整Transform执行顺序

AWS::LanguageExtensions需要在AWS::Serverless-2016-10-31之前执行,因为它提供的Fn::ToJsonString函数需要先被CloudFormation识别,才能被后续的Serverless Transform使用。修改Transform块为:

Transform:
    - AWS::LanguageExtensions
    - AWS::Serverless-2016-10-31

2. 升级SAM CLI版本

旧版本的SAM CLI可能对AWS::LanguageExtensions的支持存在缺陷,执行以下命令升级到最新稳定版:

# pip安装的SAM
pip install --upgrade aws-sam-cli

# Homebrew安装的SAM
brew upgrade aws-sam-cli

3. 替换嵌套的函数调用

如果调整顺序和升级版本仍无效,可以将Fn::ToJsonString与Fn::Transform的嵌套调用替换为直接构造JSON字符串的方式。例如将AccessLogSetting.Format部分修改为:

Format: !Sub |
  {
    "requestId": "$context.requestId",
    "ip": "$context.identity.sourceIp",
    "requestTime": "$context.requestTime",
    "httpMethod": "$context.httpMethod",
    "routeKey": "$context.routeKey",
    "status": "$context.status",
    "responseLength": "$context.responseLength"
  }

若需保留外部文件引用,可先通过AWS::Include引入JSON对象,再用Fn::Join或Fn::Sub手动转换为字符串(注意处理转义字符)。

4. 清理SAM缓存

删除本地的.aws-sam构建目录,避免缓存的旧模板干扰:

rm -rf .aws-sam

之后重新执行sam build && sam deploy。

5. 检查构建产物

执行sam build后,查看.aws-sam/build/template.yaml文件,确认Fn::ToJsonString是否被正确转换。若仍存在未解析的函数调用,说明SAM未正确处理LanguageExtensions,需进一步排查版本或配置问题。

内容的提问来源于stack exchange,提问作者HouseInTheForest

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 15:37:02