Skip to content

Commit f3189f8

Browse files
authored
Merge pull request #3894 from aws/release-v1.108.0
Release 1.108.0 (to main)
2 parents 34118b6 + 3c2a1a7 commit f3189f8

File tree

71 files changed

+587352
-3544
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+587352
-3544
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ fetch-schema-data:
6868
# aws-cdk updated where they store the cfn doc json files. See https://github.com/aws/aws-cdk/blob/main/packages/%40aws-cdk/cfnspec/README.md
6969
bin/git_lfs_download.sh "https://raw.githubusercontent.com/cdklabs/awscdk-service-spec/main/sources/CloudFormationDocumentation/CloudFormationDocumentation.json"
7070

71-
curl -o .tmp/cloudformation.schema.json https://raw.githubusercontent.com/awslabs/goformation/master/schema/cloudformation.schema.json
71+
# Generate fresh CloudFormation schema using Python generator
72+
python3 schema_source/cfn_schema_generator.py
7273

7374
update-schema-data:
7475
# Parse docs

docs/globals.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ Currently, the following resources and properties are being supported:
9898
TracingEnabled:
9999
OpenApiVersion:
100100
Domain:
101+
SecurityPolicy:
101102
102103
HttpApi:
103104
# Properties of AWS::Serverless::HttpApi
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
from unittest.case import skipIf
2+
3+
import pytest
4+
5+
from integration.config.service_names import LAMBDA_MANAGED_INSTANCES
6+
from integration.helpers.base_test import BaseTest
7+
from integration.helpers.resource import current_region_does_not_support
8+
9+
10+
@skipIf(
11+
current_region_does_not_support([LAMBDA_MANAGED_INSTANCES]),
12+
"LambdaManagedInstance is not supported in this testing region",
13+
)
14+
class TestFunctionWithCapacityProvider(BaseTest):
15+
@pytest.fixture(autouse=True)
16+
def companion_stack_outputs(self, get_companion_stack_outputs):
17+
self.companion_stack_outputs = get_companion_stack_outputs
18+
19+
def generate_lmi_parameters(self):
20+
return [
21+
self.generate_parameter("SubnetId", self.companion_stack_outputs["LMISubnetId"]),
22+
self.generate_parameter("SecurityGroup", self.companion_stack_outputs["LMISecurityGroupId"]),
23+
self.generate_parameter("KMSKeyArn", self.companion_stack_outputs["LMIKMSKeyArn"]),
24+
]
25+
26+
def verify_capacity_provider_basic_config(self, cp_config, cp_name):
27+
"""Verify basic capacity provider configuration (state, existence) and return ARN."""
28+
self.assertIsNotNone(cp_config, f"{cp_name} should have configuration")
29+
self.assertEqual(cp_config["State"], "Active", f"{cp_name} should be in Active state")
30+
capacity_provider_arn = cp_config.get("CapacityProviderArn")
31+
self.assertIsNotNone(capacity_provider_arn, f"{cp_name} should have a capacity provider ARN")
32+
return capacity_provider_arn
33+
34+
def verify_capacity_provider_vpc_config(self, cp_config, cp_name):
35+
"""Verify capacity provider VPC configuration matches companion stack outputs."""
36+
vpc_config = cp_config.get("VpcConfig")
37+
self.assertIsNotNone(vpc_config, f"{cp_name} should have VPC configuration")
38+
self.assertIn(
39+
self.companion_stack_outputs["LMISubnetId"],
40+
vpc_config["SubnetIds"],
41+
f"{cp_name} should use the correct subnet",
42+
)
43+
self.assertIn(
44+
self.companion_stack_outputs["LMISecurityGroupId"],
45+
vpc_config["SecurityGroupIds"],
46+
f"{cp_name} should use the correct security group",
47+
)
48+
49+
def verify_capacity_provider_permissions_config(self, cp_config, cp_name):
50+
"""Verify capacity provider has permissions configuration with operator role."""
51+
permissions_config = cp_config.get("PermissionsConfig")
52+
self.assertIsNotNone(permissions_config, f"{cp_name} should have permissions configuration")
53+
operator_role_arn = permissions_config.get("CapacityProviderOperatorRoleArn")
54+
self.assertIsNotNone(operator_role_arn, f"{cp_name} should have operator role ARN")
55+
return operator_role_arn
56+
57+
def verify_function_capacity_provider_config(self, function_capacity_provider_config):
58+
"""Verify and extract capacity provider ARN from function configuration."""
59+
self.assertIsNotNone(function_capacity_provider_config, "Function should have capacity provider configuration")
60+
self.assertIn(
61+
"LambdaManagedInstancesCapacityProviderConfig",
62+
function_capacity_provider_config,
63+
"Function LambdaManagedInstancesCapacityProviderConfig should have LambdaManagedInstancesCapacityProviderConfig",
64+
)
65+
66+
lmi_config = function_capacity_provider_config["LambdaManagedInstancesCapacityProviderConfig"]
67+
function_capacity_provider_arn = lmi_config.get("CapacityProviderArn")
68+
self.assertIsNotNone(
69+
function_capacity_provider_arn, "Function capacity provider config should have a capacity provider ARN"
70+
)
71+
return function_capacity_provider_arn
72+
73+
def verify_capacity_provider_arn_match(self, function_capacity_provider_arn, capacity_provider_arn):
74+
"""Verify that the function references the correct capacity provider ARN."""
75+
self.assertEqual(
76+
function_capacity_provider_arn,
77+
capacity_provider_arn,
78+
"Function should reference the correct capacity provider ARN",
79+
)
80+
81+
def test_function_with_capacity_provider_custom_role(self):
82+
"""Test Lambda function with CapacityProviderConfig using custom operator role."""
83+
parameters = self.generate_lmi_parameters()
84+
self.create_and_verify_stack("combination/function_lmi_custom", parameters)
85+
86+
lambda_resources = self.get_stack_resources("AWS::Lambda::Function")
87+
self.assertEqual(len(lambda_resources), 1, "Should create exactly one Lambda function")
88+
89+
capacity_provider_resources = self.get_stack_resources("AWS::Lambda::CapacityProvider")
90+
self.assertEqual(len(capacity_provider_resources), 1, "Should create exactly one CapacityProvider")
91+
92+
lambda_function = lambda_resources[0]
93+
function_capacity_provider_config = self.get_function_capacity_provider_config(
94+
lambda_function["PhysicalResourceId"]
95+
)
96+
function_capacity_provider_arn = self.verify_function_capacity_provider_config(
97+
function_capacity_provider_config
98+
)
99+
100+
capacity_provider_config = self.get_lambda_capacity_provider_config("MyCapacityProvider")
101+
actual_capacity_provider_arn = self.verify_capacity_provider_basic_config(
102+
capacity_provider_config, "MyCapacityProvider"
103+
)
104+
self.verify_capacity_provider_arn_match(function_capacity_provider_arn, actual_capacity_provider_arn)
105+
106+
capacity_provider_operator_role_arn = self.verify_capacity_provider_permissions_config(
107+
capacity_provider_config, "MyCapacityProvider"
108+
)
109+
110+
custom_operator_role_physical_id = self.get_physical_id_by_logical_id("MyCapacityProviderCustomRole")
111+
self.assertIsNotNone(custom_operator_role_physical_id, "MyCapacityProviderCustomRole should exist in the stack")
112+
113+
self.assertIn(
114+
custom_operator_role_physical_id,
115+
capacity_provider_operator_role_arn,
116+
f"Capacity provider should use the custom operator role with physical ID {custom_operator_role_physical_id}",
117+
)
118+
119+
def test_function_with_capacity_provider_default_role(self):
120+
"""Test Lambda function with CapacityProviderConfig using default operator role."""
121+
parameters = self.generate_lmi_parameters()
122+
self.create_and_verify_stack("combination/function_lmi_default", parameters)
123+
124+
lambda_resources = self.get_stack_resources("AWS::Lambda::Function")
125+
self.assertEqual(len(lambda_resources), 1, "Should create exactly one Lambda function")
126+
127+
capacity_provider_resources = self.get_stack_resources("AWS::Lambda::CapacityProvider")
128+
self.assertEqual(len(capacity_provider_resources), 2, "Should create exactly two CapacityProviders")
129+
130+
my_function = lambda_resources[0]
131+
function_config = self.get_function_capacity_provider_config(my_function["PhysicalResourceId"])
132+
function_capacity_provider_arn = self.verify_function_capacity_provider_config(function_config)
133+
134+
simple_cp_config = self.get_lambda_capacity_provider_config("SimpleCapacityProvider")
135+
simple_cp_arn = self.verify_capacity_provider_basic_config(simple_cp_config, "SimpleCapacityProvider")
136+
self.verify_capacity_provider_arn_match(function_capacity_provider_arn, simple_cp_arn)
137+
138+
self.verify_capacity_provider_vpc_config(simple_cp_config, "SimpleCapacityProvider")
139+
self.verify_capacity_provider_permissions_config(simple_cp_config, "SimpleCapacityProvider")
140+
141+
advanced_cp_config = self.get_lambda_capacity_provider_config("AdvancedCapacityProvider")
142+
self.verify_capacity_provider_basic_config(advanced_cp_config, "AdvancedCapacityProvider")
143+
self.verify_capacity_provider_vpc_config(advanced_cp_config, "AdvancedCapacityProvider")
144+
self.verify_capacity_provider_permissions_config(advanced_cp_config, "AdvancedCapacityProvider")
145+
146+
instance_requirements = advanced_cp_config.get("InstanceRequirements")
147+
self.assertIsNotNone(instance_requirements, "AdvancedCapacityProvider should have instance requirements")
148+
self.assertIn(
149+
"x86_64",
150+
instance_requirements.get("Architectures", []),
151+
"AdvancedCapacityProvider should have x86_64 architecture",
152+
)
153+
allowed_types = instance_requirements.get("AllowedInstanceTypes", [])
154+
self.assertIn("m5.large", allowed_types, "AdvancedCapacityProvider should allow m5.large")
155+
self.assertIn("m5.xlarge", allowed_types, "AdvancedCapacityProvider should allow m5.xlarge")
156+
self.assertIn("m5.2xlarge", allowed_types, "AdvancedCapacityProvider should allow m5.2xlarge")
157+
158+
scaling_config = advanced_cp_config.get("CapacityProviderScalingConfig")
159+
self.assertIsNotNone(scaling_config, "AdvancedCapacityProvider should have scaling configuration")
160+
self.assertEqual(
161+
scaling_config.get("MaxVCpuCount"), 64, "AdvancedCapacityProvider should have MaxVCpuCount of 64"
162+
)
163+
scaling_policies = scaling_config.get("ScalingPolicies", [])
164+
self.assertTrue(len(scaling_policies) > 0, "AdvancedCapacityProvider should have scaling policies")
165+
cpu_policy = next(
166+
(
167+
p
168+
for p in scaling_policies
169+
if p.get("PredefinedMetricType") == "LambdaCapacityProviderAverageCPUUtilization"
170+
),
171+
None,
172+
)
173+
self.assertIsNotNone(cpu_policy, "AdvancedCapacityProvider should have CPU utilization scaling policy")
174+
self.assertEqual(
175+
cpu_policy.get("TargetValue"), 70.0, "AdvancedCapacityProvider should have CPU utilization target of 70"
176+
)
177+
178+
self.assertEqual(
179+
advanced_cp_config.get("KmsKeyArn"),
180+
self.companion_stack_outputs["LMIKMSKeyArn"],
181+
"AdvancedCapacityProvider should use the correct KMS key",
182+
)
183+
184+
def get_function_capacity_provider_config(self, function_name, alias_name=None):
185+
lambda_client = self.client_provider.lambda_client
186+
187+
try:
188+
# Build the function identifier - include alias if provided
189+
function_identifier = f"{function_name}:{alias_name}" if alias_name else function_name
190+
# Get the function configuration
191+
response = lambda_client.get_function_configuration(FunctionName=function_identifier)
192+
# Return the CapacityProviderConfig if it exists
193+
return response.get("CapacityProviderConfig")
194+
except Exception as e:
195+
# Log the error and return None for graceful handling
196+
print(f"Error getting function capacity provider config: {e}")
197+
return None
198+
199+
def get_lambda_capacity_provider_config(self, capacity_provider_logical_id):
200+
lambda_client = self.client_provider.lambda_client
201+
202+
try:
203+
# Get the physical ID from the logical ID
204+
capacity_provider_name = self.get_physical_id_by_logical_id(capacity_provider_logical_id)
205+
# Get the capacity provider configuration
206+
response = lambda_client.get_capacity_provider(CapacityProviderName=capacity_provider_name)
207+
return response.get("CapacityProvider")
208+
except Exception as e:
209+
# Log the error and return None for graceful handling
210+
print(f"Error getting capacity provider config for {capacity_provider_logical_id}: {e}")
211+
return None

integration/config/service_names.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
STATE_MACHINE_CWE_CWS = "StateMachineCweCws"
3131
STATE_MACHINE_WITH_APIS = "StateMachineWithApis"
3232
LAMBDA_URL = "LambdaUrl"
33+
LAMBDA_MANAGED_INSTANCES = "LambdaManagedInstances"
3334
LAMBDA_ENV_VARS = "LambdaEnvVars"
3435
EVENT_INVOKE_CONFIG = "EventInvokeConfig"
3536
API_KEY = "ApiKey"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[
2+
{
3+
"LogicalResourceId": "MyCapacityProvider",
4+
"ResourceType": "AWS::Lambda::CapacityProvider"
5+
},
6+
{
7+
"LogicalResourceId": "MyCapacityProviderCustomRole",
8+
"ResourceType": "AWS::IAM::Role"
9+
},
10+
{
11+
"LogicalResourceId": "MyFunction",
12+
"ResourceType": "AWS::Lambda::Function"
13+
},
14+
{
15+
"LogicalResourceId": "MyFunctionRole",
16+
"ResourceType": "AWS::IAM::Role"
17+
}
18+
]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[
2+
{
3+
"LogicalResourceId": "SimpleCapacityProvider",
4+
"ResourceType": "AWS::Lambda::CapacityProvider"
5+
},
6+
{
7+
"LogicalResourceId": "SimpleCapacityProviderOperatorRole",
8+
"ResourceType": "AWS::IAM::Role"
9+
},
10+
{
11+
"LogicalResourceId": "AdvancedCapacityProvider",
12+
"ResourceType": "AWS::Lambda::CapacityProvider"
13+
},
14+
{
15+
"LogicalResourceId": "AdvancedCapacityProviderOperatorRole",
16+
"ResourceType": "AWS::IAM::Role"
17+
},
18+
{
19+
"LogicalResourceId": "MyFunction",
20+
"ResourceType": "AWS::Lambda::Function"
21+
},
22+
{
23+
"LogicalResourceId": "MyFunctionRole",
24+
"ResourceType": "AWS::IAM::Role"
25+
}
26+
]
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
Parameters:
2+
SubnetId:
3+
Type: String
4+
SecurityGroup:
5+
Type: String
6+
KMSKeyArn:
7+
Type: String
8+
9+
Resources:
10+
MyCapacityProviderCustomRole:
11+
Type: AWS::IAM::Role
12+
Properties:
13+
AssumeRolePolicyDocument:
14+
Version: '2012-10-17'
15+
Statement:
16+
- Effect: Allow
17+
Principal:
18+
Service: lambda.amazonaws.com
19+
Action: sts:AssumeRole
20+
Policies:
21+
- PolicyName: CapacityProviderOperatorRolePolicy
22+
PolicyDocument:
23+
Version: '2012-10-17'
24+
Statement:
25+
- Effect: Allow
26+
Action:
27+
- ec2:AttachNetworkInterface
28+
- ec2:CreateTags
29+
- ec2:RunInstances
30+
Resource:
31+
- !Sub arn:${AWS::Partition}:ec2:*:*:instance/*
32+
- !Sub arn:${AWS::Partition}:ec2:*:*:network-interface/*
33+
- !Sub arn:${AWS::Partition}:ec2:*:*:volume/*
34+
Condition:
35+
StringEquals:
36+
ec2:ManagedResourceOperator: scaler.lambda.amazonaws.com
37+
- Effect: Allow
38+
Action:
39+
- ec2:DescribeAvailabilityZones
40+
- ec2:DescribeCapacityReservations
41+
- ec2:DescribeInstances
42+
- ec2:DescribeInstanceStatus
43+
- ec2:DescribeInstanceTypeOfferings
44+
- ec2:DescribeInstanceTypes
45+
- ec2:DescribeSecurityGroups
46+
- ec2:DescribeSubnets
47+
Resource: '*'
48+
- Effect: Allow
49+
Action:
50+
- ec2:RunInstances
51+
- ec2:CreateNetworkInterface
52+
Resource:
53+
- !Sub arn:${AWS::Partition}:ec2:*:*:subnet/*
54+
- !Sub arn:${AWS::Partition}:ec2:*:*:security-group/*
55+
- Effect: Allow
56+
Action:
57+
- ec2:RunInstances
58+
Resource:
59+
- !Sub arn:${AWS::Partition}:ec2:*:*:image/*
60+
Condition:
61+
Bool:
62+
ec2:Public: 'true'
63+
64+
MyCapacityProvider:
65+
Type: AWS::Serverless::CapacityProvider
66+
Properties:
67+
VpcConfig:
68+
SubnetIds:
69+
- !Ref SubnetId
70+
SecurityGroupIds:
71+
- !Ref SecurityGroup
72+
OperatorRole: !GetAtt MyCapacityProviderCustomRole.Arn
73+
74+
MyFunction:
75+
Type: AWS::Serverless::Function
76+
Properties:
77+
Runtime: nodejs22.x
78+
Handler: index.handler
79+
CodeUri: ${codeuri}
80+
CapacityProviderConfig:
81+
Arn: !GetAtt MyCapacityProvider.Arn
82+
83+
Metadata:
84+
SamTransformTest: true

0 commit comments

Comments
 (0)