dougmoscrop / serverless-plugin-split-stacks

A plugin to generate nested stacks to get around CloudFormation resource/parameter/output limits
297 stars 68 forks source link

Layer parameter not being passed to nested stacks #100

Open emontgomerydae opened 4 years ago

emontgomerydae commented 4 years ago

I am using both the split stacks and python requirements plugins. The python requirements plugin is creating a lambda layer of required packages. I am using split stacks per function. The Layer arn being created by the python requirements plugin is being passed as a parameter to the nested stack of the first lambda in my .yml but no subsequent nested stacks in the stack set. All nested stacks except for the first, fail with the error: Template format error: Unresolved resource dependencies [PythonRequirementsLambdaLayerParameter] in the Resources block of the template

The layer does show up in my nested stack as a layer, but again it is not resolving because it is not passed as a parameter.

example first function that actually gets the parameter and does not fail:

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Parameters": {
    "PythonRequirementsLambdaLayerParameter": {
      "Type": "String"
    },
    "ServerlessDeploymentBucketParameter": {
      "Type": "String"
    },
    "AuthorizerApiGatewayAuthorizerParameter": {
      "Type": "String"
    },
    "ApiGatewayResourceAccountsAccountidVarProjectsParameter": {
      "Type": "String"
    }
  },
  "Resources": {
    ...
    "FirstLambdaFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Ref": ...
          },
          "S3Key": ...
        },
        "FunctionName": ...,
        "Handler": ...,
        "MemorySize": 1024,
        "Role": {
          ...
        },
        "Runtime": "python3.6",
        "Timeout": 6,
        "Environment": {
          "Variables": {
            ...
          }
        },
        "Layers": [
          {
            "Ref": "PythonRequirementsLambdaLayerParameter"
          },
          ...
        ]
      },
      ...
    },
    ...
  },
  "Outputs": {}
}

example function 2 through n that do not get the parameter and so they fail:

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Parameters": {
    "ServerlessDeploymentBucketParameter": {
      "Type": "String"
    },
    "ApiGatewayResourceEnvironmentsParameter": {
      "Type": "String"
    },
    "AuthorizerApiGatewayAuthorizerParameter": {
      "Type": "String"
    }
  },
  "Resources": {
    ...
    "MyLambdaFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Code": {
          "S3Bucket": {
            "Ref": ...
          },
          "S3Key": ...
        },
        "FunctionName": ...,
        "Handler": ...,
        "MemorySize": 1024,
        "Role": {
          ...
        },
        "Runtime": "python3.6",
        "Timeout": 6,
        "Environment": {
          "Variables": {
            ...
          }
        },
        "Layers": [
          {
            "Ref": "PythonRequirementsLambdaLayerParameter"
          },
          ...
        ]
      },
      ...
    },
    ...
  },
  "Outputs": {}
}
dougmoscrop commented 4 years ago

Oh interesting, so it's only parameterizing the first one..

mwillbanks commented 4 years ago

@emontgomerydae @dougmoscrop any ideas on how to solve this? I am encountering this exact same issue and am looking at where it might be happening but so far have not been able to resolve it.

dougmoscrop commented 4 years ago

when I got the time I was going to try moving the whole splitting step to the deploy phase rather than the package phase, this would kill two birds with one stone if it works as some users don't want to or have AWS credentials during package but only deploy

https://github.com/dougmoscrop/serverless-plugin-split-stacks/blob/master/split-stacks.js#L30

mwillbanks commented 4 years ago

I've debugged this down to having an issue somewhere in the replace references area; overall, it appears that sometimes the outputs are being removed and no longer assigned to resources that need them thus causing the root of the issue; i'm continuing to debug exactly where or how this is being mapped out.

mwillbanks commented 4 years ago

@dougmoscrop this excludes any plugins and using resources alone, if you simply do a sls package -s dev on this, you will notice that the cloudformation template in .serverless/cloudformation-template-Ex2-nested-stack.json is missing the parameters whereas .serverless/cloudformation-template-Ex1-nested-stack.json contains the parameters. See below for the serverless.yml.

service: example-failure
provider:
  name: aws
  runtime: nodejs12.x
  stage: ${opt:stage, 'dev'}
  region: us-west-2
  memorySize: 128
  logRetentionInDays: 7
  timeout: 30
  environment:
    STAGE: ${self:provider.stage}
  vpc:
    subnetIds:
      - Ref: AppSubnet1
    securityGroupIds:
      - Fn::GetAtt: [ VPC, DefaultSecurityGroup ]
      - Ref: LambdaEndpointSecurityGroup
plugins:
  - serverless-plugin-split-stacks
resources:
  Resources:
    VPC:
      Type: AWS::EC2::VPC
      Properties:
        CidrBlock: 10.10.0.0/16
        EnableDnsSupport: true
        EnableDnsHostnames: true
        InstanceTenancy: default
        Tags:
        - Key: Name
          Value:
            Ref: AWS::StackName
    InternetGateway:
      Type: AWS::EC2::InternetGateway
      Properties:
        Tags:
        - Key: Name
          Value:
            Ref: AWS::StackName
    InternetGatewayAttachment:
      Type: AWS::EC2::VPCGatewayAttachment
      Properties:
        InternetGatewayId:
          Ref: InternetGateway
        VpcId:
          Ref: VPC
    EIP1:
      Type: AWS::EC2::EIP
      Properties:
        Domain: vpc
    NatGateway1:
      Type: AWS::EC2::NatGateway
      Properties:
        AllocationId:
          Fn::GetAtt:
          - EIP1
          - AllocationId
        SubnetId:
          Ref: PublicSubnet1
        Tags:
        - Key: Name
          Value:
            Fn::Join:
            - "-"
            - - Ref: AWS::StackName
              - us-west-2a
    AppSubnet1:
      Type: AWS::EC2::Subnet
      Properties:
        AvailabilityZone: us-west-2a
        CidrBlock: 10.10.0.0/21
        Tags:
        - Key: Name
          Value:
            Fn::Join:
            - "-"
            - - Ref: AWS::StackName
              - app
              - us-west-2a
        VpcId:
          Ref: VPC
    AppRouteTable1:
      Type: AWS::EC2::RouteTable
      Properties:
        VpcId:
          Ref: VPC
        Tags:
        - Key: Name
          Value:
            Fn::Join:
            - "-"
            - - Ref: AWS::StackName
              - app
              - us-west-2a
    AppRouteTableAssociation1:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId:
          Ref: AppRouteTable1
        SubnetId:
          Ref: AppSubnet1
    PublicSubnet1:
      Type: AWS::EC2::Subnet
      Properties:
        AvailabilityZone: us-west-2a
        CidrBlock: 10.10.8.0/22
        Tags:
        - Key: Name
          Value:
            Fn::Join:
            - "-"
            - - Ref: AWS::StackName
              - public
              - us-west-2a
        VpcId:
          Ref: VPC
    PublicRouteTable1:
      Type: AWS::EC2::RouteTable
      Properties:
        VpcId:
          Ref: VPC
        Tags:
        - Key: Name
          Value:
            Fn::Join:
            - "-"
            - - Ref: AWS::StackName
              - public
              - us-west-2a
    PublicRouteTableAssociation1:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId:
          Ref: PublicRouteTable1
        SubnetId:
          Ref: PublicSubnet1
    PublicRoute1:
      Type: AWS::EC2::Route
      Properties:
        DestinationCidrBlock: 0.0.0.0/0
        RouteTableId:
          Ref: PublicRouteTable1
        GatewayId:
          Ref: InternetGateway
    AppRoute1:
      Type: AWS::EC2::Route
      Properties:
        DestinationCidrBlock: 0.0.0.0/0
        RouteTableId:
          Ref: AppRouteTable1
        NatGatewayId:
          Ref: NatGateway1
    LambdaExecutionSecurityGroup:
      Type: AWS::EC2::SecurityGroup
      Properties:
        GroupDescription: Lambda Execution Group
        VpcId:
          Ref: VPC
        Tags:
        - Key: Name
          Value:
            Fn::Join:
            - "-"
            - - Ref: AWS::StackName
              - lambda-exec
    PublicNetworkAcl:
      Type: AWS::EC2::NetworkAcl
      Properties:
        Tags:
        - Key: Name
          Value:
            Fn::Join:
            - "-"
            - - Ref: AWS::StackName
              - public
        VpcId:
          Ref: VPC
    PublicNetworkAclIngress100:
      Type: AWS::EC2::NetworkAclEntry
      Properties:
        CidrBlock: 0.0.0.0/0
        NetworkAclId:
          Ref: PublicNetworkAcl
        Egress: false
        Protocol: -1
        RuleAction: allow
        RuleNumber: 100
    PublicNetworkAclEgress100:
      Type: AWS::EC2::NetworkAclEntry
      Properties:
        CidrBlock: 0.0.0.0/0
        NetworkAclId:
          Ref: PublicNetworkAcl
        Egress: true
        Protocol: -1
        RuleAction: allow
        RuleNumber: 100
    PublicSubnetNetworkAclAssociation1:
      Type: AWS::EC2::SubnetNetworkAclAssociation
      Properties:
        SubnetId:
          Ref: PublicSubnet1
        NetworkAclId:
          Ref: PublicNetworkAcl
    AppNetworkAcl:
      Type: AWS::EC2::NetworkAcl
      Properties:
        Tags:
        - Key: Name
          Value:
            Fn::Join:
            - "-"
            - - Ref: AWS::StackName
              - app
        VpcId:
          Ref: VPC
    AppNetworkAclIngress100:
      Type: AWS::EC2::NetworkAclEntry
      Properties:
        CidrBlock: 0.0.0.0/0
        NetworkAclId:
          Ref: AppNetworkAcl
        Egress: false
        Protocol: -1
        RuleAction: allow
        RuleNumber: 100
    AppNetworkAclEgress100:
      Type: AWS::EC2::NetworkAclEntry
      Properties:
        CidrBlock: 0.0.0.0/0
        NetworkAclId:
          Ref: AppNetworkAcl
        Egress: true
        Protocol: -1
        RuleAction: allow
        RuleNumber: 100
    AppSubnetNetworkAclAssociation1:
      Type: AWS::EC2::SubnetNetworkAclAssociation
      Properties:
        SubnetId:
          Ref: AppSubnet1
        NetworkAclId:
          Ref: AppNetworkAcl
    LambdaEndpointSecurityGroup:
      Type: AWS::EC2::SecurityGroup
      Properties:
        GroupDescription: Lambda access to VPC endpoints
        VpcId:
          Ref: VPC
        SecurityGroupIngress:
        - SourceSecurityGroupId:
            Ref: LambdaExecutionSecurityGroup
          Description: Allow inbound HTTPS traffic from LambdaExecutionSecurityGroup
          IpProtocol: tcp
          FromPort: 443
          ToPort: 443
        Tags:
        - Key: Name
          Value:
            Fn::Join:
            - "-"
            - - Ref: AWS::StackName
              - lambda-endpoint
  Outputs:
    AppSubnet1:
      Value:
        Ref: AppSubnet1
    LambdaEndpointSecurityGroup:
      Value:
        Ref: LambdaEndpointSecurityGroup
custom:
  splitStacks:
    perFunction: true
    perType: false
    perGroupFunction: false
functions:
  ex1:
    handler: handler.ex1
    events:
      - http:
          method: get
          path: ex1
  ex2:
    handler: handler.ex2
    events:
      - http:
          method: get
          path: ex2
mwillbanks commented 4 years ago

Workaround is to define on each function, rather than using the global configuration.

KasparLee commented 4 years ago

Any update on this? Am having the same issue and can't seem to figure out a workaround.

Angel-Mu commented 3 years ago

I found a different workaround pretty elegant so far. seems like somehow, there's no parent from layers, and I noticed that, for environment values that are making a Ref: logicalId is creating the CF Parameter

Basically, is passed to all of the nested stacks as another parameter which wasn't injected as there's no parent from layers other than provider, and so, adding it to environment variables, will force to put this created and create a CF Parameter passed over every single nested stack, which is perfect, because was the only piece missing, I don't like so much having that arn at the env vars of every lambda, but is better than adding it to every single function, which is a pain on a very huge stack.

:white_check_mark:

provider:
  name: aws
  runtime: nodejs10.x
  layers:
    - { Ref: MyCustomLambdaLayer }
  environment:
    a: 1
    layerArn: { Ref: MyCustomLambdaLayer }

:x:

provider:
  name: aws
  runtime: nodejs10.x
  layers:
    - { Ref: MyCustomLambdaLayer }
  environment:
    a: 1
abhishek-mudgal commented 1 year ago

Have we found a fix for it yet? @Angel-Mu I tried your fix, ARN is getting referenced in the in the env variable, but still not getting passed into function parameter.

provider:
    name: aws
    runtime: nodejs14.x
    region: ap-south-1
    layers: ${self:custom.CONFIG.LAYER_ARN}
    stage: prod
    environment:
        COMMON: ${self:custom.CONFIG.LAYER_ARN}