Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .cfnlintrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ ignore_templates:
- tests/translator/output/**/managed_policies_everything.json # intentionally contains wrong arns
- tests/translator/output/**/function_with_metrics_config.json
- tests/translator/output/**/function_with_self_managed_kafka_and_schema_registry.json # cfnlint is not updated to recognize the SchemaRegistryConfig property
- tests/translator/output/**/function_with_self_managed_kafka_oauth.json # cfnlint is not updated to recognize OAuth SourceAccessConfigurations types
- tests/translator/output/**/function_with_self_managed_kafka_iam_auth.json # cfnlint is not updated to recognize IAM_AUTH SourceAccessConfigurations type
- tests/translator/output/**/function_with_self_managed_kafka_iam_oauth.json # cfnlint is not updated to recognize IAM_OAUTHBEARER_AUTH SourceAccessConfigurations type
- tests/translator/output/**/function_with_msk_with_schema_registry_config.json # cfnlint is not updated to recognize the SchemaRegistryConfig property
- tests/translator/output/**/function_with_logging_config.json # cfnlint is not updated to recognize the LoggingConfig property
- tests/translator/output/aws-*/*capacity_provider*.json # Ignore Capacity Provider test format in non-aws partitions
Expand Down
3 changes: 2 additions & 1 deletion bin/run_cfn_lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ if [ ! -d "${VENV}" ]; then
python3 -m venv "${VENV}"
fi

"${VENV}/bin/python" -m pip install cfn-lint --upgrade --quiet
# pinning 1.55.0 for now because there are some issues with 1.56.0
"${VENV}/bin/python" -m pip install cfn-lint==1.55.0 --upgrade --quiet
# update cfn schema with retry logic (can fail due to network issues)
# --regions us-east-1 avoids a cfn-lint bug where updating all regions causes a
# multiprocessing pickle error. See https://github.com/aws-cloudformation/cfn-lint/issues/4379
Expand Down
33 changes: 33 additions & 0 deletions integration/combination/test_function_with_capacity_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,22 @@ def test_function_with_capacity_provider_default_role(self):
self.verify_capacity_provider_vpc_config(simple_cp_config, "SimpleCapacityProvider")
self.verify_capacity_provider_permissions_config(simple_cp_config, "SimpleCapacityProvider")

# Verify Globals.CapacityProvider.ManagedResourceTags inheritance on SimpleCapacityProvider
simple_propagate_tags = simple_cp_config.get("PropagateTags")
self.assertIsNotNone(simple_propagate_tags, "SimpleCapacityProvider should inherit PropagateTags from Globals")
self.assertEqual(
simple_propagate_tags.get("Mode"),
"Explicit",
"SimpleCapacityProvider should have PropagateTags Mode: Explicit from Globals",
)
simple_explicit_tags = simple_propagate_tags.get("ExplicitTags", {})
self.assertIn("GlobalTag", simple_explicit_tags, "SimpleCapacityProvider should inherit GlobalTag from Globals")
self.assertEqual(
simple_explicit_tags["GlobalTag"],
"shared-value",
"GlobalTag should be 'shared-value' from Globals",
)

advanced_cp_config = self.get_lambda_capacity_provider_config("AdvancedCapacityProvider")
self.verify_capacity_provider_basic_config(advanced_cp_config, "AdvancedCapacityProvider")
self.verify_capacity_provider_vpc_config(advanced_cp_config, "AdvancedCapacityProvider")
Expand Down Expand Up @@ -180,6 +196,23 @@ def test_function_with_capacity_provider_default_role(self):
"AdvancedCapacityProvider should use the correct KMS key",
)

# Verify ManagedResourceTags -> PropagateTags configuration
propagate_tags = advanced_cp_config.get("PropagateTags")
self.assertIsNotNone(propagate_tags, "AdvancedCapacityProvider should have PropagateTags configuration")
self.assertEqual(
propagate_tags.get("Mode"), "Explicit", "AdvancedCapacityProvider should have PropagateTags Mode: Explicit"
)

explicit_tags = propagate_tags.get("ExplicitTags", {})
self.assertIn("Environment", explicit_tags, "AdvancedCapacityProvider should propagate Environment tag")
self.assertIn("Team", explicit_tags, "AdvancedCapacityProvider should propagate Team tag")
self.assertIn("GlobalTag", explicit_tags, "AdvancedCapacityProvider should override GlobalTag from Globals")
self.assertEqual(explicit_tags["Environment"], "Production", "Environment tag should be 'Production'")
self.assertEqual(explicit_tags["Team"], "Lambda-Tooling", "Team tag should be 'Lambda-Tooling'")
self.assertEqual(
explicit_tags["GlobalTag"], "overridden-value", "GlobalTag should be overridden by resource-level value"
)

telemetry_config = advanced_cp_config.get("TelemetryConfig")
self.assertIsNotNone(telemetry_config, "AdvancedCapacityProvider should have TelemetryConfig")
logging_config = telemetry_config.get("LoggingConfig")
Expand Down
28 changes: 27 additions & 1 deletion integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,32 @@ def _companion_stack_parameters():
return params


@retry_with_exponential_backoff_and_jitter(
ThrottlingError,
5,
2,
exc_raise_msg="SAR ListApplications still throttled after 5 attempts",
)
def _sar_list_applications(sar_client):
"""
Wrap SAR ListApplications with the same throttle-retry treatment
the rest of this file applies to CloudFormation and S3 calls, so
a transient SAR throttle at fixture setup does not fail the whole test.

Unlike the CloudFormation and S3 helpers below, this uses a small base
delay: the fixture is function-scoped and autoused by TestBasicApplication,
so the backoff is paid per test. A read-only ListApplications throttle
clears in seconds, so ~1 minute of total backoff is enough.
"""
try:
return sar_client.list_applications().get("Applications", [])
except botocore.exceptions.ClientError as ex:
error_code = ex.response.get("Error", {}).get("Code", "")
if error_code in ("TooManyRequestsException", "Throttling", "ThrottlingException"):
raise ThrottlingError(stack_name="SAR:list_applications", msg=str(ex))
raise


@pytest.fixture()
def get_serverless_application_repository_app():
"""Create or re-use a simple SAR app"""
Expand All @@ -97,7 +123,7 @@ def get_serverless_application_repository_app():
return None

sar_client = ClientProvider().sar_client
sar_apps = sar_client.list_applications().get("Applications", [])
sar_apps = _sar_list_applications(sar_client)
for sar_app in sar_apps:
if sar_app.get("Name") == SAR_APP_NAME:
LOG.info("SAR Application was already created, skipping SAR application publish")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Resources:
Properties:
Handler: index.handler
Runtime: nodejs18.x
InlineCode: |
InlineCode: !Sub |
exports.handler = async (event, context, callback) => {
console.log(event);
const token = event.type === 'TOKEN' ? event.authorizationToken : event.queryStringParameters.authorization
Expand All @@ -65,7 +65,7 @@ Resources:

return {
principalId: 'user',
usageIdentifierKey: 'needatleast20characters',
usageIdentifierKey: 'needatleast20chars-${AWS::StackName}',
context: { },
policyDocument
}
Expand All @@ -77,7 +77,7 @@ Resources:
- MyUsagePlan
Properties:
Enabled: true
Value: needatleast20characters
Value: !Sub "needatleast20chars-${AWS::StackName}"
StageKeys:
- RestApiId:
Ref: MyApi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ Parameters:
KMSKeyArn:
Type: String

Globals:
CapacityProvider:
ManagedResourceTags:
Tags:
GlobalTag: shared-value

Resources:
SimpleCapacityProvider:
Type: AWS::Serverless::CapacityProvider
Expand Down Expand Up @@ -45,6 +51,11 @@ Resources:
KmsKeyArn: !Ref KMSKeyArn
LoggingConfig:
SystemLogLevel: DEBUG
ManagedResourceTags:
Tags:
Environment: Production
Team: Lambda-Tooling
GlobalTag: overridden-value
Tags:
Environment: Test

Expand Down
2 changes: 1 addition & 1 deletion samtranslator/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.113.0"
__version__ = "1.114.0"
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@

class ManagedResourceTags(BaseModel):
Tags: DictStrAny | None = managedresourcetags("Tags")
Propagate: bool | None = managedresourcetags("Propagate")


class VpcConfig(BaseModel):
Expand Down
1 change: 0 additions & 1 deletion samtranslator/internal/schema_source/sam-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@
"ExcludedTypes": "A list of EC2 instance types to exclude from the capacity provider. \n*Type*: List \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`ExcludedInstanceTypes`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-capacityprovider-instancerequirements.html#cfn-lambda-capacityprovider-instancerequirements-excludedinstancetypes) property of [`InstanceRequirements`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-capacityprovider.html#cfn-lambda-capacityprovider-instancerequirements) of an `AWS::Lambda::CapacityProvider` resource."
},
"sam-property-capacityprovider-managedresourcetags": {
"Propagate": "TODO",
"Tags": "TODO"
},
"sam-property-capacityprovider-scalingconfig": {
Expand Down
5 changes: 3 additions & 2 deletions samtranslator/model/api/websocket_api_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,10 @@ def _validate_auth(self, auth_config: dict[str, Any]) -> None:
# Use parameter `auth_config` that we know is not None, instead of `self.auth_config`
auth_type = auth_config.get("AuthType")
if auth_type:
auth_type = auth_type.upper()
if auth_type not in AuthType.TYPES:
raise InvalidResourceException(self.logical_id, "AuthType is not one of AWS_IAM, CUSTOM or NONE.")
raise InvalidResourceException(
self.logical_id, f"AuthType '{auth_type}' is not one of AWS_IAM, CUSTOM or NONE."
)
if auth_type == AuthType.CUSTOM and not auth_config.get("AuthArn"):
raise InvalidResourceException(self.logical_id, "AuthArn must be specified if AuthType is CUSTOM.")
if auth_type == AuthType.AWS_IAM and len(auth_config) > 1:
Expand Down
3 changes: 0 additions & 3 deletions samtranslator/model/capacity_provider/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,4 @@ def _transform_managed_resource_tags(self) -> dict[str, Any]:
if "Tags" in tags:
return {"Mode": "Explicit", "ExplicitTags": get_tag_list(tags["Tags"])}

if "Propagate" in tags:
return {"Mode": "CapacityProvider" if tags["Propagate"] else "None"}

return {}
29 changes: 25 additions & 4 deletions samtranslator/model/eventsources/pull.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,19 @@ class SelfManagedKafka(PullEventSource):
"SASL_SCRAM_512_AUTH",
"BASIC_AUTH",
"CLIENT_CERTIFICATE_TLS_AUTH",
"OAUTHBEARER_AUTH",
"IAM_AUTH",
"IAM_OAUTHBEARER_AUTH",
]
NON_URI_TYPES = [
"IAM_AUTH",
"IAM_OAUTHBEARER_AUTH",
]
OAUTH_METADATA_TYPES = [
"OAUTHBEARER_SCOPE",
"OAUTHBEARER_AUDIENCE",
"OAUTHBEARER_LOGICAL_CLUSTER",
"OAUTHBEARER_IDENTITY_POOL",
]

def get_event_source_arn(self) -> PassThrough | None:
Expand Down Expand Up @@ -699,6 +712,8 @@ def get_policy_statements(
"No SourceAccessConfigurations for self managed kafka event provided.",
)
document = self.generate_policy_document(self.SourceAccessConfigurations, intrinsic_resolver)
if not document["PolicyDocument"]["Statement"]:
return None
return [document]

def generate_policy_document( # type: ignore[no-untyped-def]
Expand All @@ -711,7 +726,7 @@ def generate_policy_document( # type: ignore[no-untyped-def]
statements.append(secret_manager)

if authentication_uri_2:
secret_manager = self.get_secret_manager_secret(authentication_uri) # type: ignore[no-untyped-call]
secret_manager = self.get_secret_manager_secret(authentication_uri_2) # type: ignore[no-untyped-call]
statements.append(secret_manager)

if has_vpc_config:
Expand Down Expand Up @@ -741,6 +756,7 @@ def get_secret_key(self, source_access_configurations: list[Any]) -> tuple[str |
authentication_uri = None
has_vpc_subnet = False
has_vpc_security_group = False
has_auth_mechanism = False
authentication_uri_2 = None

if not isinstance(source_access_configurations, list):
Expand All @@ -759,18 +775,23 @@ def get_secret_key(self, source_access_configurations: list[Any]) -> tuple[str |
has_vpc_security_group = True

elif config.get("Type") in self.AUTH_MECHANISM:
if authentication_uri:
if has_auth_mechanism:
raise InvalidEventException(
self.relative_id,
"Multiple auth mechanism properties specified in SourceAccessConfigurations for self managed kafka event.",
)
self.validate_uri(config.get("URI"), "auth mechanism")
authentication_uri = config.get("URI")
has_auth_mechanism = True
if config.get("Type") not in self.NON_URI_TYPES:
self.validate_uri(config.get("URI"), "auth mechanism")
authentication_uri = config.get("URI")

elif config.get("Type") == "SERVER_ROOT_CA_CERTIFICATE":
self.validate_uri(config.get("URI"), "SERVER_ROOT_CA_CERTIFICATE")
authentication_uri_2 = config.get("URI")

elif config.get("Type") in self.OAUTH_METADATA_TYPES:
self.validate_uri(config.get("URI"), config.get("Type"))

else:
raise InvalidEventException(
self.relative_id,
Expand Down
4 changes: 0 additions & 4 deletions samtranslator/model/sam_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -1577,10 +1577,6 @@ class SamCapacityProvider(SamResourceMacro):
ValidationRule.MUTUALLY_EXCLUSIVE,
["InstanceRequirements.AllowedTypes", "InstanceRequirements.ExcludedTypes"],
),
(
ValidationRule.MUTUALLY_EXCLUSIVE,
["ManagedResourceTags.Propagate=True", "ManagedResourceTags.Tags"],
),
]

def to_cloudformation(self, **kwargs: Any) -> list[Resource]:
Expand Down
Loading
Loading