# We have a separate Serverless config file because Lambda@Edge
# functions must be in us-east-1 and Serverless does not support
# multi-region deployments or Lambda@Edge:
# https://github.com/serverless/serverless/issues/4123
# https://github.com/serverless/serverless/issues/3944
# Serverless also does not support customizing the yml filename,
# so we just cp the file before deploying.
# https://github.com/serverless/serverless/issues/4473
service: lambda-edge # Important: this must be unique in AWS Cloudformation!
frameworkVersion: '>=3.24.1 <4.0.0'

provider:
  name: aws
  runtime: nodejs14.x
  region: us-east-1
  stage: ${env:SLS_STAGE, self:custom.stageDefault}
  role: gladlyLambdaEdgeRole
  # Let's be conservative and prevent any unexpected changes
  # to the stack. See:
  # https://github.com/gladly-team/tab/issues/582
  # To update the stack policy when the deploy is failing due to
  # stack policy restrictions, set the policy with the AWS CLI. Docs:
  # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/protect-stack-resources.html#protect-stack-resources-modifying
  # https://docs.aws.amazon.com/cli/latest/reference/cloudformation/set-stack-policy.html
  # Example:
  #   aws cloudformation set-stack-policy --stack-name lambda-edge-test --region us-east-1 --stack-policy-body \
  #   '{"Statement":[
  #     {"Effect":"Allow","Principal":"*","Action":"Update:Modify","Resource":"LogicalResourceId/NewtabAppLambdaEdgeLambdaFunction"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:*","Resource":"LogicalResourceId/NewtabAppLambdaEdgeLambdaVersion*"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:Modify","Resource":"LogicalResourceId/NewtabAppLambdaEdgeLogGroup"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:Modify","Resource":"LogicalResourceId/SearchAppLambdaEdgeLambdaFunction"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:*","Resource":"LogicalResourceId/SearchAppLambdaEdgeLambdaVersion*"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:Modify","Resource":"LogicalResourceId/SearchAppLambdaEdgeLogGroup"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:Modify","Resource":"LogicalResourceId/Homepage404LambdaEdgeLambdaFunction"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:*","Resource":"LogicalResourceId/Homepage404LambdaEdgeLambdaVersion*"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:Modify","Resource":"LogicalResourceId/Homepage404LambdaEdgeLogGroup"},
  #     {"Effect":"Allow","Principal":"*","Action":"Update:Modify","Resource":"LogicalResourceId/gladlyLambdaEdgeRole"}
  #   ]}'
  # We could also set a temporary policy, but it's unclear how we'd
  # do this with the Serverless framework:
  # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/protect-stack-resources.html#protect-stack-resources-updating
  # Opened a Serverless feature request:
  # https://github.com/serverless/serverless/issues/6182
  stackPolicy:
    # If not specified here, Cloudformation denies updates by default.
    - Effect: Allow
      Principal: '*'
      Action: 'Update:Modify'
      Resource: 'LogicalResourceId/NewtabAppLambdaEdgeLambdaFunction'
    - Effect: Allow
      Principal: '*'
      Action: 'Update:*'
      Resource: 'LogicalResourceId/NewtabAppLambdaEdgeLambdaVersion*'
    - Effect: Allow
      Principal: '*'
      Action: 'Update:Modify'
      Resource: 'LogicalResourceId/NewtabAppLambdaEdgeLogGroup'
    - Effect: Allow
      Principal: '*'
      Action: 'Update:Modify'
      Resource: 'LogicalResourceId/SearchAppLambdaEdgeLambdaFunction'
    - Effect: Allow
      Principal: '*'
      Action: 'Update:*'
      Resource: 'LogicalResourceId/SearchAppLambdaEdgeLambdaVersion*'
    - Effect: Allow
      Principal: '*'
      Action: 'Update:Modify'
      Resource: 'LogicalResourceId/SearchAppLambdaEdgeLogGroup'
    - Effect: Allow
      Principal: '*'
      Action: 'Update:Modify'
      Resource: 'LogicalResourceId/Homepage404LambdaEdgeLambdaFunction'
    - Effect: Allow
      Principal: '*'
      Action: 'Update:*'
      Resource: 'LogicalResourceId/Homepage404LambdaEdgeLambdaVersion*'
    - Effect: Allow
      Principal: '*'
      Action: 'Update:Modify'
      Resource: 'LogicalResourceId/Homepage404LambdaEdgeLogGroup'
    - Effect: Allow
      Principal: '*'
      Action: 'Update:Modify'
      Resource: 'LogicalResourceId/gladlyLambdaEdgeRole'

plugins:
  # https://github.com/claygregory/serverless-prune-plugin
  - serverless-prune-plugin

custom:
  stageDefault: dev
  stage: ${env:SLS_STAGE, self:custom.stageDefault}
  # Important: create an alias for any version we will rely on in
  # other stacks, such as the one used for a Lambda@Edge association
  # in Cloudfront. That way, this plugin will not prune it, because
  # it will not delete any aliased functions.
  # Alternatively, disable automatic pruning and handle it manually.
  # https://github.com/claygregory/serverless-prune-plugin
  prune:
    automatic: true
    number: 5
  searchRequestSNSTopicName:
    test: dev-SearchRequest
    dev: dev-SearchRequest
    prod: SearchRequest

# We are using a custom role (building on the default Serverless role)
# so that we can add edgelambda.amazonaws.com as a trust provider.
# Custom IAM roles:
# https://serverless.com/framework/docs/providers/aws/guide/iam/
# AWS permissions for Lambda@Edge:
# https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-edge-permissions.html
# Serverless does not yet support Lambda@Edge:
# https://github.com/serverless/serverless/issues/3944
resources:
  Resources:
    gladlyLambdaEdgeRole:
      Type: AWS::IAM::Role
      Properties:
        Path: /gladly/lambda-edge/
        # Required if you want to use 'serverless deploy --function' later on.
        RoleName: gladly-${self:custom.stage}-lambda-edge-role
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
                  - edgelambda.amazonaws.com
              Action: sts:AssumeRole
        # Note that these rights are needed if you want your function to be
        # able to communicate with resources within your VPC.
        ManagedPolicyArns:
          - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
        Policies:
          - PolicyName: gladly-${self:custom.stage}-lambda-execution-role
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                # Note that these rights are given in the default policy and are
                # required if you want logs out of your lambdas.
                - Effect: Allow
                  Action:
                    - logs:CreateLogGroup
                    - logs:CreateLogStream
                    - logs:PutLogEvents
                    - logs:DescribeLogStreams
                  Resource:
                    - 'Fn::Join':
                        - ':'
                        - - 'arn:aws:logs'
                          - Ref: 'AWS::Region'
                          - Ref: 'AWS::AccountId'
                          - 'log-group:/aws/lambda/*:*:*'
                - Effect: 'Allow'
                  Action:
                    - 's3:PutObject'
                  Resource:
                    Fn::Join:
                      - ''
                      - - 'arn:aws:s3:::'
                        - 'Ref': 'ServerlessDeploymentBucket'
          # Give permission to publish to the SNS SearchRequest topic in any
          # region.
          - PolicyName: gladly-${self:custom.stage}-search-request-log-role
            PolicyDocument:
              Version: '2012-10-17'
              Statement:
                - Effect: Allow
                  Action:
                    - sqs:*
                  Resource: arn:aws:sqs:*
                - Effect: Allow
                  Action:
                    - sns:Publish
                  Resource:
                    - 'Fn::Join':
                        - ':'
                        - - 'arn:aws:sns:*' # allow in any region
                          - Ref: 'AWS::AccountId'
                          # SNS topic: SearchRequest or dev-SearchRequest
                          - ${self:custom.searchRequestSNSTopicName.${self:custom.stage}}

functions:
  # We manually reference the ARN of these Lambda@Edge functions in our
  # CloudFront stack. Unfortunately, serverless-plugin-cloudfront-lambda-edge
  # does not support cross-service references:
  # https://github.com/silvermine/serverless-plugin-cloudfront-lambda-edge/issues/1
  # Note that Serverless now supports this, so we can likely update our
  # stack and remove the plugin:
  # https://github.com/serverless/serverless/issues/3944
  # Until then, new releases of these function require us to change the
  # function version environment variables (see the references in the web
  # serverless.yml).
  newtabAppLambdaEdge:
    handler: build/newtab-app-lambda-edge/newtab-app-lambda-edge.handler
  searchAppLambdaEdge:
    handler: build/search-app-lambda-edge/search-app-lambda-edge.handler
    memorySize: 128 # 128 is max allowed for CloudFront-triggered Lambda
    timeout: 3 # 5s is max allowed for CloudFront-triggered Lambda
  homepage404LambdaEdge:
    handler: build/homepage-404-lambda-edge/homepage-404-lambda-edge.handler
