Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions integration/combination/test_function_with_capacity_provider.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from unittest.case import skipIf

import pytest

from integration.config.service_names import LAMBDA_MANAGED_INSTANCES
from integration.helpers.base_test import BaseTest
from integration.helpers.resource import current_region_does_not_support


@skipIf(
current_region_does_not_support([LAMBDA_MANAGED_INSTANCES]),
"LambdaManagedInstance is not supported in this testing region",
)
class TestFunctionWithCapacityProvider(BaseTest):
@pytest.fixture(autouse=True)
def companion_stack_outputs(self, get_companion_stack_outputs):
self.companion_stack_outputs = get_companion_stack_outputs

def test_function_with_capacity_provider_custom_role(self):
"""Test Lambda function with CapacityProviderConfig using custom operator role."""
# Phase 1: Prepare parameters from companion stack
parameters = [
self.generate_parameter("SubnetId", self.companion_stack_outputs["LMISubnetId"]),
self.generate_parameter("SecurityGroup", self.companion_stack_outputs["LMISecurityGroupId"]),
self.generate_parameter("KMSKeyArn", self.companion_stack_outputs["LMIKMSKeyArn"]),
]

# Phase 2: Deploy and verify against expected JSON
self.create_and_verify_stack("combination/function_lmi_custom", parameters)

# Phase 3: Verify resource counts
lambda_resources = self.get_stack_resources("AWS::Lambda::Function")
self.assertEqual(len(lambda_resources), 1, "Should create exactly one Lambda function")

capacity_provider_resources = self.get_stack_resources("AWS::Lambda::CapacityProvider")
self.assertEqual(len(capacity_provider_resources), 1, "Should create exactly one CapacityProvider")

iam_role_resources = self.get_stack_resources("AWS::IAM::Role")
self.assertEqual(len(iam_role_resources), 2, "Should create exactly two IAM roles")

def test_function_with_capacity_provider_default_role(self):
"""Test Lambda function with CapacityProviderConfig using default operator role.

Note: This test is skipped until 12/01/2024 because the managed policy required for
the default operator role is not available until then.
Comment on lines +44 to +45
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this note (also: 2024???)

"""
# Phase 1: Prepare parameters from companion stack
parameters = [
self.generate_parameter("SubnetId", self.companion_stack_outputs["LMISubnetId"]),
self.generate_parameter("SecurityGroup", self.companion_stack_outputs["LMISecurityGroupId"]),
self.generate_parameter("KMSKeyArn", self.companion_stack_outputs["LMIKMSKeyArn"]),
]

# Phase 2: Deploy and verify against expected JSON
self.create_and_verify_stack("combination/function_lmi_default", parameters)

# Phase 3: Verify resource counts
lambda_resources = self.get_stack_resources("AWS::Lambda::Function")
self.assertEqual(len(lambda_resources), 1, "Should create exactly one Lambda function")

capacity_provider_resources = self.get_stack_resources("AWS::Lambda::CapacityProvider")
self.assertEqual(len(capacity_provider_resources), 1, "Should create exactly one CapacityProvider")

iam_role_resources = self.get_stack_resources("AWS::IAM::Role")
self.assertEqual(len(iam_role_resources), 2, "Should create exactly two IAM roles")
1 change: 1 addition & 0 deletions integration/config/service_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
STATE_MACHINE_CWE_CWS = "StateMachineCweCws"
STATE_MACHINE_WITH_APIS = "StateMachineWithApis"
LAMBDA_URL = "LambdaUrl"
LAMBDA_MANAGED_INSTANCES = "LambdaManagedInstances"
LAMBDA_ENV_VARS = "LambdaEnvVars"
EVENT_INVOKE_CONFIG = "EventInvokeConfig"
API_KEY = "ApiKey"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"LogicalResourceId": "MyCapacityProvider",
"ResourceType": "AWS::Lambda::CapacityProvider"
},
{
"LogicalResourceId": "MyCapacityProviderCustomRole",
"ResourceType": "AWS::IAM::Role"
},
{
"LogicalResourceId": "MyFunction",
"ResourceType": "AWS::Lambda::Function"
},
{
"LogicalResourceId": "MyFunctionRole",
"ResourceType": "AWS::IAM::Role"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"LogicalResourceId": "MyCapacityProvider",
"ResourceType": "AWS::Lambda::CapacityProvider"
},
{
"LogicalResourceId": "MyCapacityProviderOperatorRole",
"ResourceType": "AWS::IAM::Role"
},
{
"LogicalResourceId": "MyFunction",
"ResourceType": "AWS::Lambda::Function"
},
{
"LogicalResourceId": "MyFunctionRole",
"ResourceType": "AWS::IAM::Role"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
Parameters:
SubnetId:
Type: String
SecurityGroup:
Type: String
KMSKeyArn:
Type: String

Resources:
MyCapacityProviderCustomRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: CapacityProviderOperatorRolePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- ec2:TerminateInstances
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Operator role doesn't need ec2:TerminateInstances anymore, since the instances will be terminated by the service-linked role.,

- ec2:AttachNetworkInterface
- ec2:RunInstances
- ec2:CreateTags
Resource:
- !Sub arn:${AWS::Partition}:ec2:*:*:instance/*
- !Sub arn:${AWS::Partition}:ec2:*:*:network-interface/*
- !Sub arn:${AWS::Partition}:ec2:*:*:volume/*
Condition:
StringEquals:
ec2:ManagedResourceOperator: scaler.lambda.amazonaws.com
- Effect: Allow
Action:
- ec2:DescribeAvailabilityZones
- ec2:DescribeCapacityReservations
- ec2:DescribeInstanceTypes
- ec2:DescribeInstanceTypeOfferings
- ec2:DescribeSecurityGroups
- ec2:DescribeSubnets
- ec2:DescribeInstances
- ec2:DescribeInstanceStatus
Comment on lines +40 to +47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to not have these in alphabetical order? (DescribeInstances before DescribeInstanceTypes for example) (or at least to have all the DescribeInstance<Something> together).

Resource: '*'
- Effect: Allow
Action:
- ec2:RunInstances
- ec2:CreateNetworkInterface
Resource:
- !Sub arn:${AWS::Partition}:ec2:*:*:subnet/*
- !Sub arn:${AWS::Partition}:ec2:*:*:security-group/*
- Effect: Allow
Action:
- ec2:RunInstances
Resource:
- !Sub arn:${AWS::Partition}:ec2:*:*:image/*
Condition:
Bool:
ec2:Public: 'true'

MyCapacityProvider:
Type: AWS::Serverless::CapacityProvider
Properties:
CapacityProviderName: !Sub "${AWS::StackName}-cp"
VpcConfig:
SubnetIds:
- !Ref SubnetId
SecurityGroupIds:
- !Ref SecurityGroup
OperatorRole: !GetAtt MyCapacityProviderCustomRole.Arn
KmsKeyArn: !Ref KMSKeyArn

MyFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs22.x
Handler: index.handler
CodeUri: ${codeuri}
CapacityProviderConfig:
Arn: !GetAtt MyCapacityProvider.Arn

Metadata:
SamTransformTest: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Parameters:
SubnetId:
Type: String
SecurityGroup:
Type: String
KMSKeyArn:
Type: String

Resources:
MyCapacityProvider:
Type: AWS::Serverless::CapacityProvider
Properties:
CapacityProviderName: !Sub "${AWS::StackName}-cp"
VpcConfig:
SubnetIds:
- !Ref SubnetId
SecurityGroupIds:
- !Ref SecurityGroup
KmsKeyArn: !Ref KMSKeyArn
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a Capacity provider without KmsKeyArn too, since it's not needed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also without CapacityProviderName too, considering that the only thing really required is the VpcConfig?


MyFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs22.x
Handler: index.handler
CodeUri: ${codeuri}
CapacityProviderConfig:
Arn: !GetAtt MyCapacityProvider.Arn

Metadata:
SamTransformTest: true
109 changes: 109 additions & 0 deletions integration/setup/companion-stack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,106 @@ Resources:
Type: AWS::S3::Bucket
DeletionPolicy: Delete

LMIKMSKey:
Type: AWS::KMS::Key
Properties:
Description: KMS Key for Lambda Capacity Provider Resource
KeyPolicy:
Version: '2012-10-17'
Statement:
- Sid: Enable IAM User Permissions
Effect: Allow
Principal:
AWS: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:root"
Action: kms:*
Resource: '*'
- Sid: Allow Lambda service
Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action:
- kms:Decrypt
- kms:DescribeKey
Resource: '*' # Lambda Managed Instances (LMI) VPC Resources

LMIVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-VPC"

LMIPrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref LMIVpc
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-PrivateSubnet"

LMIPrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref LMIVpc
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-PrivateRT"

LMIPrivateSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref LMIPrivateSubnet
RouteTableId: !Ref LMIPrivateRouteTable

LMISecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for capacity provider Lambda functions
VpcId: !Ref LMIVpc
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 10.0.0.0/16
Description: Allow HTTPS within VPC
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-LambdaSG"

VPCEndpointSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for VPC endpoints
VpcId: !Ref LMIVpc
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 10.0.0.0/16
Description: Allow HTTPS from within VPC
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-VPCEndpointSG"

CloudWatchLogsEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref LMIVpc
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.logs'
VpcEndpointType: Interface
PrivateDnsEnabled: true
SubnetIds:
- !Ref LMIPrivateSubnet
SecurityGroupIds:
- !Ref VPCEndpointSecurityGroup
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-CloudWatchLogsEndpoint"
Outputs:
PreCreatedVpc:
Description: Pre-created VPC that can be used inside other tests
Expand All @@ -66,5 +166,14 @@ Outputs:
Description: Pre-created S3 Bucket that can be used inside other tests
Value:
Ref: PreCreatedS3Bucket
LMISubnetId:
Description: Private subnet ID for Lambda functions
Value: !Ref LMIPrivateSubnet
LMISecurityGroupId:
Description: Security group ID for Lambda functions
Value: !Ref LMISecurityGroup
LMIKMSKeyArn:
Description: ARN of the KMS key for Capacity Provider
Value: !GetAtt LMIKMSKey.Arn
Metadata:
SamTransformTest: true
Loading