13

我想在 SAM template.yaml 中为 API Gateway 设置阶段名称。但无论我尝试什么,我都没有成功。无需尝试命名我的阶段,一切都按预期工作,但使用默认阶段名称 Prod 和 Stage。

我的 sam-cli 版本是 0.47.0

我确实在 Stackoverflow 上找到了三个类似的问题,但没有一个答案对我有用。

我总是收到类似这样的错误:

Unresolved resource dependencies [ServerlessRestApi] in the Outputs block of the template

那么我如何获得我自己选择的艺名。我不太关心 Prod 和 Stage 是否与我选择的名称共存。

为了完整起见,我的 template.yaml 文件如下:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app
  
Globals:
  Function:
    Timeout: 3
  Api:
    Cors:
      AllowMethods: "'OPTIONS,PUT'"
      AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
      AllowOrigin: "'*'"
  
Resources:

  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs12.x
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello-world
            Method: put

Outputs:
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/dev/hello-world/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

我可能不明白这背后的预期工作流程。当 API Gateway 指向的 lambda 函数相同时,为什么有 2 个阶段名称?

我将拥有“dev”和“prod”环境,但它们将使用不同的堆栈名称,因此我永远不会混淆不同的环境。

我总是使用 deploy-dev.sh 和 deploy-pod.sh 脚本在实际部署之前检查我是否在开发或主(生产)分支上。因此这些脚本将指向不同的 template.yaml 文件,因为它们是从不同的 git 分支调用的。我已经使用这种方式进行部署很长时间了,它对我来说效果很好。

附带说明:为什么现有的艺名以大写字母开头?它看起来如此丑陋和不寻常。

4

3 回答 3

37

因此,我找到了自己的答案,它是我在 StackOverflow 上找到的问题中提到的两个答案的组合。

我仍然不明白为什么这会如此不必要地复杂。

我在 template.yaml 文件的顶层添加了一个参数。并不严格需要使用参数。我添加了这个,所以我可以拥有一个从我的 deploy-dev.sh 和 deploy-prod.sh 脚本调用的模板文件。下面是参数声明:

Parameters:
  Stage:
    Type: String
    Default: dev

然后,在 Resources 组下,我添加了一个新的ApiDeployment资源。您使用的名称完全取决于您,只要您在其他地方使用与 !Ref 完全相同的名称。添加此资源的唯一原因是不允许您在函数事件的 Api 部分的属性中简单地使用StageName 。您也不能将StageName放在Globals Api部分。

ApiDeployment:
  Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Stage <- this is referencing the parameter but it could be a fixed value

然后,在 Lambda 函数的事件部分下,我添加了引用ApiDeployment资源的属性RestApiId 。下面块中的最后一行。

HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs12.x
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello-world
            Method: put
            RestApiId: !Ref ApiDeployment

正如我在问题中提到的,我收到了抱怨 yaml 文件的输出部分的错误。事实证明,输出部分无论如何都是可选的。因此,当我将其注释掉时,一切正常。

但我使用部署脚本中的输出部分向我展示了 API 网关的 URL,因此通过一些尝试我也得到了它的工作。错误是在第 4 行引起的。它最初有${ServerlessRestApi}。只需将其替换为我添加到 yaml 文件中的新资源名称:${ApiDeployment},一切都很好。

Outputs:
  ApiDeployment:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ApiDeployment}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/hello-world/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

因为我在yaml文件中使用了参数,所以需要调用sam deploy,参数名和值。就像许多 AWS 的文档一样,它的确切语法被很好地隐藏了。下面是您开始部署的方式:

sam deploy --parameter-overrides "ParameterKey=Stage,ParameterValue=dev"

您可能仍然在 API Gateway 控制台中的Stages下拥有Stage阶段,但您可以删除它而不会产生任何影响。

为了完整起见,这里是我的完整 template.yaml 文件,顺便说一下,这是您执行sam init时获得的文件

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
      sam-app: Sample SAM Template for sam-app

Parameters:
  Stage:
    Type: String
    Default: dev

Globals:
  Function:
    Timeout: 3
  Api:
    Cors:
      AllowMethods: "'OPTIONS,PUT'"
      AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
      AllowOrigin: "'*'"
  
Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs12.x
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello-world
            Method: put
            RestApiId: !Ref ApiDeployment
  ApiDeployment:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Stage

Outputs:
  ApiDeployment:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ApiDeployment}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/hello-world/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn
于 2020-06-24T04:58:26.013 回答
4

这是针对此问题的任何人的更新。

我不再使用SAM了。我切换到 AWS CDK。CDK 让您可以在代码中定义整个 AWS 基础设施!您可以使用 Javascript、Typescript、Python、C# 和 Java,尽管大多数示例似乎都在 Typescript 中。

这不是一个很容易的转换,但它是值得的。SAM似乎是一条死胡同。

SAM 仅涵盖 AWS 提供的所有服务的一小部分,但 CDK 涵盖了所有内容。

它很新,而且它是一个移动的目标,而且开发人员不会对中断更新表示不满,因为他们仍在模块之间移动东西。

但是几天后,你会开始掌握它,它具有无限的灵活性,因为你使用普通的编程语言来设置 API 网关、Lambda、自定义域、IAM 规则等。它也非常紧凑(与 SAM 模板相比)。

我用它来根据我所在的 got 分支为暂存和生产提供不同的堆栈。因此,当我在我的 repo 位于 dev 分支上进行部署时,我将拥有不同的环境(包括不同的域名等),然后当我将从 master 或 prod 分支进行部署。不同服务的名称也不同,具体取决于 git 分支。

要部署,您只需运行“cdk deploy”

要开始查看这个优秀的研讨会:https ://cdkworkshop.com/

以下是此分支切换的示例。我只展示了 dev 分支的部分方法。对于 prod 分支,我只是在同一个文件中拥有相同方法的副本,但该方法具有不同的名称,并且服务名称的变量也不同。只需查看一段(不完整的)代码,您就应该了解它是如何工作的。

import * as branchName from 'current-git-branch'

const branch = branchName()

/*-------- This is the development stack --------*/
export class StripePaymentsDev extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props)

    if (branch !== 'dev') {
      throw new Error('Not on dev branch')
    }

    // Env vars from Parameter Store
    const STRIPE_PUBLIC = StringParameter.valueForStringParameter(this, `/${branch}/STRIPE_PUBLIC`)
    const STRIPE_SECRET = StringParameter.valueForStringParameter(this, `/${branch}/STRIPE_SECRET`)
    const STRIPE_API_VERSION = StringParameter.valueForStringParameter(this, `/${branch}/STRIPE_API_VERSION_PAYMENTS`)

    // Names for the dev environment
    const domainMapName = 'PaymentsDev'
    const eventBusName = 'WebhooksBusDev'
    const ruleName = 'WebhooksRuleDev'

    const eventBus = new EventBus(stackScope, eventBusName, { eventBusName })
    const cert = Certificate.fromCertificateArn(stackScope, certName, certArn)
    const stackScope = this

    // IAM rules
    const lambdaPolicy = new iam.PolicyStatement({
      actions: ['events:*'],
      resources: ['*']
    })

    const sqsPolicy = new iam.PolicyStatement({
      actions: ['sqs:*'],
      resources: ['*']
    })
    const webhooks = new lambda.Function(stackScope, lambdaWebhooksName, {
      runtime: lambda.Runtime.NODEJS_12_X,
      code: lambda.Code.fromAsset('webhook-handler'),
      handler: 'webhooks.handler',
      timeout: Duration.seconds(600),
      description: 'Processes Stripe Webhooks',
      retryAttempts: 0,
      environment: {
        STRIPE_PUBLIC,
        STRIPE_SECRET,
        STRIPE_API_VERSION,
        MONGO_URL,
        MONGO_DB,
        MONGO_PORT,
        DEBUG
      }
    })

    webhooks.addToRolePolicy(sqsPolicy)

    const rule = new Rule(stackScope, ruleName, {
      description: 'Triggers lambda to process stipe webhooks',
      enabled: true,
      eventBus: eventBus,
      eventPattern: {
        detailType: ['transaction'],
        source: ['custom.payments']
      },
      ruleName: ruleName
    })

    rule.addTarget(new eventTargets.LambdaFunction(webhooks))

    new HttpApi(stackScope, apiName, {
      defaultIntegration: new LambdaProxyIntegration({ handler: payments }),
      defaultDomainMapping: {
        domainName: new DomainName(stackScope, domainMapName, {
          domainName: PAYMENT_DOMAIN,
          certificate: cert
        })
      }
    })
  }
}
于 2020-11-15T05:34:37.683 回答
2

这种方式只创建您指定的阶段,而不是创建一个名为Stage.

这个设置确实成功了。

全局
变量:API:OpenApiVersion:3.0.1

我还创建了新的AWS::Serverless::Api命名RestApi来覆盖隐式ServerlessRestApi。请记住设置RestApi每个RestApiIdAPI 事件。

模板.yaml

    AWSTemplateFormatVersion: 2010-09-09
    Description: >-
      app-sam
    
    
    Transform:
    - AWS::Serverless-2016-10-31
    
    # ====================================
    # PARAMETERS SETUP
    # ====================================
    Parameters:
      StageName:
        Type: String
        Default: dev
        Description: (Required) Enter dev, prod. Default is dev.
        AllowedValues:
          - dev
          - prod
      ProjectName:
        Type: String
        Default: sam-api
        Description: (Required) The name of the project
        MinLength: 3
        MaxLength: 50
        AllowedPattern: ^[A-Za-z_-]+$
        ConstraintDescription: "Required. Can be characters, hyphen, and underscore only. No numbers or special characters allowed."
      ExistingTable:
        Type: String
        Default: example-table
        Description: (Required) The name of existing DynamoDB
        MinLength: 3
        MaxLength: 50
        AllowedPattern: ^[A-Za-z_-]+$
        ConstraintDescription: "Required. Can be characters, hyphen, and underscore only. No numbers or special characters allowed."
    
    
    # ====================================
    # GLOBAL SETUP
    # ====================================
    Globals:
      Api:
        OpenApiVersion: 3.0.1
      Function:
        Runtime: nodejs14.x
        Timeout: 180
        MemorySize: 256
        Environment:
          Variables:
            TABLE_NAME: !Ref ExistingTable
    
    Resources:
      # Reference this one to overwrite implicit stage
      # https://github.com/aws/serverless-application-model/issues/191#issuecomment-580412747 
      RestApi:
        Type: AWS::Serverless::Api
        Properties:
          Name: !Ref ProjectName
          StageName: !Ref StageName
          
      # This is a Lambda function config associated with the source code: get-all-items.js
      getAllItemsFunction:
        Type: AWS::Serverless::Function
        Properties:
          Handler: src/handlers/get-all-items.getAllItemsHandler
          Description: A simple example includes a HTTP get method to get all items from a DynamoDB table.
          Policies:
            # Give Create/Read/Update/Delete Permissions to the ExistingTable
            - DynamoDBCrudPolicy:
                TableName: !Ref ExistingTable
          Events:
            Api:
              Type: Api
              Properties:
                Path: /
                Method: GET
                RestApiId: !Ref RestApi
      
    
    Outputs:
      WebEndpoint:
        Description: "API Gateway endpoint URL for Prod stage"
        Value: !Sub "https://${RestApi}.execute-api.${AWS::Region}.amazonaws.com/${StageName}/"

于 2021-04-08T12:06:47.397 回答