diff --git a/bin/sam-translate.py b/bin/sam-translate.py index 3ae9c4a98d..5aee7b7fd6 100755 --- a/bin/sam-translate.py +++ b/bin/sam-translate.py @@ -123,8 +123,7 @@ def transform_template(input_file_path: Path, output_file_path: Path, stdout: bo except InvalidDocumentException as e: error_message = reduce(lambda message, error: message + " " + error.message, e.causes, e.message) LOG.error(error_message) - errors = (cause.message for cause in e.causes) - LOG.error(errors) + sys.exit(1) def deploy(template_file: Path) -> None: diff --git a/samtranslator/internal/schema_source/sam-docs.json b/samtranslator/internal/schema_source/sam-docs.json index 5cc070e18c..3e8d28affa 100644 --- a/samtranslator/internal/schema_source/sam-docs.json +++ b/samtranslator/internal/schema_source/sam-docs.json @@ -978,7 +978,7 @@ "EntryPoint": "The file that the runtime runs, relative to the root of the deployment package. AWS SAM passes it to the runtime in the reserved `AWS_LAMBDA_NODEJS_ENTRYPOINT` environment variable. \nYou can't use this property when `EnvironmentVariables` is an intrinsic function. \n*Type*: String \n*Required*: No \n*Default*: `index.js` \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an CloudFormation equivalent.", "EnvironmentVariables": "Key-value pairs to set as environment variables for the revision. \n+ Keys must start with a letter and contain only letters, numbers, and underscores.\n+ Values must be non-empty and at most 4,096 characters.\n+ AWS SAM converts number and Boolean values to strings.\n*Type*: Map \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`ServiceConfig.EnvironmentVariables`](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-lambda-webfunctionrevision-serviceconfig.html#cfn-lambda-webfunctionrevision-serviceconfig-environmentvariables) property of an `AWS::Lambda::WebFunctionRevision` resource.", "ExecutionRoleArn": "The ARN of the IAM role that the function assumes when it runs. If you don't specify a role, AWS SAM creates one with the `AWSLambdaBasicExecutionRole` managed policy and a logical ID of `Role`. \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`ServiceConfig.ExecutionRoleArn`](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-lambda-webfunctionrevision-serviceconfig.html#cfn-lambda-webfunctionrevision-serviceconfig-executionrolearn) property of an `AWS::Lambda::WebFunctionRevision` resource.", - "FunctionName": "The name of the function. Function names must be unique within an account across all Regions. If you don't specify a name, AWS SAM generates one from the logical ID and the stack ID, at most 64 characters. Changing the name replaces the function, its revision, and its endpoint. \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`FunctionName`](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-lambda-webfunction.html#cfn-lambda-webfunction-functionname) property of the `AWS::Lambda::WebFunction`, `AWS::Lambda::WebFunctionRevision`, and `AWS::Lambda::WebFunctionEndpoint` resources.", + "FunctionName": "The name of the function. Function names must be unique within an account across all Regions. If you don't specify a name, AWS SAM generates one from the logical ID and the stack ID, at most 64 characters. Changing the name replaces the function, its revision, and its endpoint. \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`FunctionName`](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-lambda-webfunction.html#cfn-lambda-webfunction-functionname) property of an `AWS::Lambda::WebFunction` resource. The generated `AWS::Lambda::WebFunctionRevision` and `AWS::Lambda::WebFunctionEndpoint` resources reference the function with `Ref`.", "InlineCode": "Not supported for `AWS::Serverless::WebFunction`. Use [CodeUri](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-webfunction.html#sam-webfunction-codeuri). \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an CloudFormation equivalent.", "KmsKeyArn": "The ARN of the AWS KMS key used to encrypt the revision's environment variables. \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`KmsKeyArn`](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-lambda-webfunctionrevision.html#cfn-lambda-webfunctionrevision-kmskeyarn) property of an `AWS::Lambda::WebFunctionRevision` resource.", "LoggingConfig": "The logging configuration for the revision, such as the log group and log levels. \n*Type*: Map \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`ServiceConfig.TelemetryConfig.LoggingConfig`](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-lambda-webfunctionrevision-telemetryconfig.html#cfn-lambda-webfunctionrevision-telemetryconfig-loggingconfig) property of an `AWS::Lambda::WebFunctionRevision` resource.", diff --git a/samtranslator/model/__init__.py b/samtranslator/model/__init__.py index 44a2bdaafb..568a50542d 100644 --- a/samtranslator/model/__init__.py +++ b/samtranslator/model/__init__.py @@ -243,6 +243,8 @@ def from_dict(cls, logical_id: str, resource_dict: dict[str, Any], relative_id: sam_plugins.act(LifeCycleEvents.before_transform_resource, logical_id, cls.resource_type, properties) for name, value in properties.items(): + if not isinstance(name, str): + raise InvalidResourceException(logical_id, f"Property names must be strings, got {name!r}.") setattr(resource, name, value) if "DependsOn" in resource_dict: diff --git a/samtranslator/model/intrinsics.py b/samtranslator/model/intrinsics.py index 4512ec28c4..c47bde01f7 100644 --- a/samtranslator/model/intrinsics.py +++ b/samtranslator/model/intrinsics.py @@ -147,8 +147,8 @@ def is_intrinsic(_input: Any) -> bool: """ if _input is not None and isinstance(_input, dict) and len(_input) == 1: - key: str = next(iter(_input.keys())) - return key in {"Ref", "Condition"} or key.startswith("Fn::") + key = next(iter(_input.keys())) + return isinstance(key, str) and (key in {"Ref", "Condition"} or key.startswith("Fn::")) return False diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index d7e50c6983..b710c1fd93 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -1654,12 +1654,14 @@ class SamNetworkConnector(SamResourceMacro): __validation_rules__: list[Any] = [] _VALID_COMPUTE_TYPES = ("MicroVm", "WebFunction") + _VALID_NETWORK_PROTOCOLS = ("IPv4", "DualStack") @cw_timer def to_cloudformation(self, **kwargs: Any) -> list[Resource]: # Run the ComputeTypes check first so its actionable message wins at transform time; the schema # (validate_before_transform / cfn-lint) enforces the same rules for `sam validate --lint`. self._validate_compute_types() + self._validate_network_protocol() self.validate_before_transform( schema_class=aws_serverless_networkconnector.Properties, collect_all_errors=True, @@ -1693,12 +1695,26 @@ def _validate_compute_types(self) -> None: f"'ComputeTypes' must contain exactly one value, got {len(self.ComputeTypes)}.", ) for compute_type in self.ComputeTypes: - if isinstance(compute_type, str) and compute_type not in self._VALID_COMPUTE_TYPES: + if is_intrinsic(compute_type): + continue + if not isinstance(compute_type, str) or compute_type not in self._VALID_COMPUTE_TYPES: raise InvalidResourceException( self.logical_id, - f"'ComputeTypes' values must be one of {list(self._VALID_COMPUTE_TYPES)}, got '{compute_type}'.", + f"'ComputeTypes' values must be one of {list(self._VALID_COMPUTE_TYPES)}, got {compute_type!r}.", ) + def _validate_network_protocol(self) -> None: + # Name the allowed values; the schema's intrinsic-or-literal union reports "not a valid dict" instead. + network_protocol = self.VpcConfig.get("NetworkProtocol") if isinstance(self.VpcConfig, dict) else None + if network_protocol is None or is_intrinsic(network_protocol): + return + if network_protocol not in self._VALID_NETWORK_PROTOCOLS: + raise InvalidResourceException( + self.logical_id, + f"'VpcConfig.NetworkProtocol' must be one of {list(self._VALID_NETWORK_PROTOCOLS)}, " + f"got {network_protocol!r}.", + ) + class SamMicroVMImage(SamResourceMacro): """SAM MicrovmImage resource transformer""" @@ -1776,6 +1792,24 @@ def to_cloudformation(self, **kwargs: Any) -> list[Resource]: return resources +_NETWORK_CONNECTOR_TYPES = ("AWS::Serverless::NetworkConnector", "AWS::Lambda::NetworkConnector") + + +def _parse_ref_or_get_att(value: Any) -> tuple[str, str | None] | None: + """Return (logical ID, attribute) for a Ref (attribute None) or Fn::GetAtt, otherwise None.""" + if not is_intrinsic(value): + return None + if "Ref" in value: + target = value["Ref"] + return (target, None) if isinstance(target, str) else None + get_att = value.get("Fn::GetAtt") + if isinstance(get_att, str): + get_att = get_att.split(".", 1) + if isinstance(get_att, list) and len(get_att) == 2 and all(isinstance(x, str) for x in get_att): # noqa: PLR2004 + return get_att[0], get_att[1] + return None + + class SamWebFunction(SamResourceMacro): """ SAM resource for AWS::Serverless::WebFunction. @@ -1839,6 +1873,7 @@ def to_cloudformation(self, **kwargs: Any) -> list[Any]: """Transforms this SAM WebFunction into CloudFormation resources.""" intrinsics_resolver: IntrinsicsResolver = kwargs["intrinsics_resolver"] self._validate(intrinsics_resolver) + self._validate_egress_network_connector_reference(kwargs.get("resource_resolver")) resources: list[Any] = [] @@ -1861,7 +1896,7 @@ def to_cloudformation(self, **kwargs: Any) -> list[Any]: return resources - def _validate(self, intrinsics_resolver: IntrinsicsResolver) -> None: # noqa: PLR0912 + def _validate(self, intrinsics_resolver: IntrinsicsResolver) -> None: # noqa: PLR0912, PLR0915 valid_auto_deploy_modes = ("LatestRevision", "Disabled") # AutoDeploymentMode is a SAM-only property enforced entirely at transform time and never passed @@ -1957,6 +1992,8 @@ def _validate(self, intrinsics_resolver: IntrinsicsResolver) -> None: # noqa: P 'revision IDs to weights (e.g., {"rev-id": 100}). Intrinsic functions may be used for ' "individual weight values.", ) + if not all(isinstance(revision_id, str) for revision_id in self.RevisionWeights): + raise InvalidResourceException(self.logical_id, "'RevisionWeights' keys must be revision ID strings.") _MIN_WEIGHT = 1 _MAX_WEIGHT = 100 weights = list(self.RevisionWeights.values()) @@ -1994,7 +2031,7 @@ def _validate(self, intrinsics_resolver: IntrinsicsResolver) -> None: # noqa: P for key, value in self.EnvironmentVariables.items(): # Match the service key pattern exactly (leading letter required, no leading underscore) so # invalid keys fail here with a clear message instead of at stack creation. - if not re.match(r"^[a-zA-Z][a-zA-Z0-9_]*$", key): + if not isinstance(key, str) or not re.match(r"^[a-zA-Z][a-zA-Z0-9_]*$", key): raise InvalidResourceException( self.logical_id, f"Environment variable key '{key}' must match pattern '^[a-zA-Z][a-zA-Z0-9_]*$'.", @@ -2018,25 +2055,44 @@ def _validate_egress_network_connector_arn(self) -> None: # value -- a network-connector ARN with a non-empty trailing segment -- without pinning the connector # name or qualifier grammar (SAM shouldn't guess at the service's naming/versioning charset, or it # would hard-fail otherwise-valid ARNs). Intrinsics (e.g. a Fn::GetAtt on the connector's - # LatestVersionArn) are passed through and validated by the service. + # LatestVersionArn) are passed through; references to connectors in the same template are checked in + # _validate_egress_network_connector_reference. + value = self.EgressNetworkConnectorArn + if value is None or is_intrinsic(value): + return network_connector_arn_pattern = r"^arn:[^:]+:lambda:[^:]*:[^:]*:network-connector:[^:]+:[^:]+$" - if isinstance(self.EgressNetworkConnectorArn, str) and not re.match( - network_connector_arn_pattern, self.EgressNetworkConnectorArn - ): + if not isinstance(value, str) or not re.match(network_connector_arn_pattern, value): raise InvalidResourceException( self.logical_id, "'EgressNetworkConnectorArn' must be a version-qualified network-connector ARN " - "(ending in ':', e.g. the connector's LatestVersionArn).", + "('arn::lambda:::network-connector::', e.g. the " + f"connector's LatestVersionArn), got {value!r}.", ) - def _construct_function_name(self) -> Any: - """Return the function name shared by the WebFunction, its Revision, and its Endpoint. + def _validate_egress_network_connector_reference(self, resource_resolver: ResourceResolver | None) -> None: + """Check a Ref or Fn::GetAtt on a network connector defined in the same template. - Ref on AWS::Lambda::WebFunction resolves to the function ARN, not the name, so the Revision and - Endpoint must reuse this exact value rather than Ref the function. Referencing it with Ref would set - their FunctionName to the ARN, which the service rejects (the endpoint/revision then cannot be - deleted: the stored ARN does not match the name parsed from it). + Ref and the Arn attribute return the unversioned connector ARN, which the service rejects, so only + LatestVersionArn is allowed. """ + reference = _parse_ref_or_get_att(self.EgressNetworkConnectorArn) + if resource_resolver is None or reference is None: + return + target, attribute = reference + connector = resource_resolver.get_resource_by_logical_id(target) + if not isinstance(connector, dict) or connector.get("Type") not in _NETWORK_CONNECTOR_TYPES: + return + + if attribute != "LatestVersionArn": + used = f"!Ref {target}" if attribute is None else f"!GetAtt {target}.{attribute}" + raise InvalidResourceException( + self.logical_id, + f"'EgressNetworkConnectorArn' must be a version-qualified network-connector ARN; {used} " + f"doesn't return one. Use !GetAtt {target}.LatestVersionArn instead.", + ) + + def _construct_function_name(self) -> Any: + """Return the FunctionName for the AWS::Lambda::WebFunction: the user value or a generated name.""" if self.FunctionName: return self.FunctionName # Generate a name bounded by construction to the 64-char AWS::Lambda::WebFunction FunctionName @@ -2081,7 +2137,9 @@ def _construct_revision(self, execution_role_arn: Any) -> LambdaWebFunctionRevis revision = LambdaWebFunctionRevision( f"{self.logical_id}Revision", attributes=self.get_passthrough_resource_attributes() ) - revision.FunctionName = self._construct_function_name() + # Ref the function (its ARN) instead of repeating the name, like AWS::Lambda::Version does, so a + # FunctionName change only has to be made on the AWS::Lambda::WebFunction. + revision.FunctionName = ref(self.logical_id) revision.Description = self.RevisionDescription revision.KmsKeyArn = self.KmsKeyArn revision.BuildConfig = self._construct_build_config() @@ -2092,7 +2150,7 @@ def _construct_endpoint(self, revision: LambdaWebFunctionRevision) -> LambdaWebF endpoint = LambdaWebFunctionEndpoint( f"{self.logical_id}Endpoint", attributes=self.get_passthrough_resource_attributes() ) - endpoint.FunctionName = self._construct_function_name() + endpoint.FunctionName = ref(self.logical_id) endpoint.EndpointName = self.EndpointName or "prod" endpoint.EndpointType = self.EndpointType or "HomeRegion" # AuthType is required and has no default (validated in _validate); pass the customer's value through. @@ -2196,7 +2254,7 @@ def _construct_region_list(self) -> list[Intrinsicable[str]] | None: if unknown_keys: raise InvalidResourceException( self.logical_id, - f"'ReplicaRegions' contains unrecognized keys: {sorted(unknown_keys)}. Valid keys are: IncludeRegions, ExcludeRegions.", + f"'ReplicaRegions' contains unrecognized keys: {sorted(map(str, unknown_keys))}. Valid keys are: IncludeRegions, ExcludeRegions.", ) # ExcludeRegions is only meaningful as a filter over an explicit inclusion set. Without IncludeRegions @@ -2215,6 +2273,10 @@ def _construct_region_list(self) -> list[Intrinsicable[str]] | None: "'IncludeRegions' and 'ExcludeRegions' do not support top-level intrinsic functions.", ) + for key, value in (("IncludeRegions", include_regions), ("ExcludeRegions", exclude_regions)): + if not isinstance(value, list): + raise InvalidResourceException(self.logical_id, f"'ReplicaRegions.{key}' must be a list.") + # Separate string entries from intrinsics in both lists include_strings = [r for r in include_regions if isinstance(r, str)] include_intrinsics = [r for r in include_regions if not isinstance(r, str)] diff --git a/samtranslator/parser/parser.py b/samtranslator/parser/parser.py index a032dd45bb..747c1c112b 100644 --- a/samtranslator/parser/parser.py +++ b/samtranslator/parser/parser.py @@ -26,7 +26,8 @@ def parse(self, sam_template: dict[str, Any], parameter_values: dict[str, Any], def validate_datatypes(sam_template): # type: ignore[no-untyped-def] """Validates the datatype within the template""" if ( - "Resources" not in sam_template + not isinstance(sam_template, dict) + or "Resources" not in sam_template or not isinstance(sam_template["Resources"], dict) or not sam_template["Resources"] ): diff --git a/samtranslator/plugins/globals/globals.py b/samtranslator/plugins/globals/globals.py index 329096489a..47b5ba1f11 100644 --- a/samtranslator/plugins/globals/globals.py +++ b/samtranslator/plugins/globals/globals.py @@ -314,6 +314,12 @@ def _parse(self, globals_dict): # type: ignore[no-untyped-def] raise InvalidGlobalsSectionException(self._KEYWORD, "It must be a non-empty dictionary") for section_name, properties in globals_dict.items(): + if not isinstance(section_name, str): + raise InvalidGlobalsSectionException( + self._KEYWORD, + f"'{section_name}' is not supported. " + f"Must be one of the following values - {self.supported_resource_section_names}", + ) resource_type = self._make_resource_type(section_name) # type: ignore[no-untyped-call] if resource_type not in self.supported_properties: diff --git a/samtranslator/schema/schema.json b/samtranslator/schema/schema.json index aba09809fe..4ceb4bbcb1 100644 --- a/samtranslator/schema/schema.json +++ b/samtranslator/schema/schema.json @@ -437239,7 +437239,7 @@ "type": "string" } ], - "markdownDescription": "The name of the function. Function names must be unique within an account across all Regions. If you don't specify a name, AWS SAM generates one from the logical ID and the stack ID, at most 64 characters. Changing the name replaces the function, its revision, and its endpoint. \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`FunctionName`](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-lambda-webfunction.html#cfn-lambda-webfunction-functionname) property of the `AWS::Lambda::WebFunction`, `AWS::Lambda::WebFunctionRevision`, and `AWS::Lambda::WebFunctionEndpoint` resources.", + "markdownDescription": "The name of the function. Function names must be unique within an account across all Regions. If you don't specify a name, AWS SAM generates one from the logical ID and the stack ID, at most 64 characters. Changing the name replaces the function, its revision, and its endpoint. \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`FunctionName`](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-lambda-webfunction.html#cfn-lambda-webfunction-functionname) property of an `AWS::Lambda::WebFunction` resource. The generated `AWS::Lambda::WebFunctionRevision` and `AWS::Lambda::WebFunctionEndpoint` resources reference the function with `Ref`.", "title": "FunctionName" }, "InlineCode": { diff --git a/schema_source/sam.schema.json b/schema_source/sam.schema.json index 70435ed8f7..63ce759f43 100644 --- a/schema_source/sam.schema.json +++ b/schema_source/sam.schema.json @@ -11340,7 +11340,7 @@ "type": "string" } ], - "markdownDescription": "The name of the function. Function names must be unique within an account across all Regions. If you don't specify a name, AWS SAM generates one from the logical ID and the stack ID, at most 64 characters. Changing the name replaces the function, its revision, and its endpoint. \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`FunctionName`](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-lambda-webfunction.html#cfn-lambda-webfunction-functionname) property of the `AWS::Lambda::WebFunction`, `AWS::Lambda::WebFunctionRevision`, and `AWS::Lambda::WebFunctionEndpoint` resources.", + "markdownDescription": "The name of the function. Function names must be unique within an account across all Regions. If you don't specify a name, AWS SAM generates one from the logical ID and the stack ID, at most 64 characters. Changing the name replaces the function, its revision, and its endpoint. \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`FunctionName`](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-lambda-webfunction.html#cfn-lambda-webfunction-functionname) property of an `AWS::Lambda::WebFunction` resource. The generated `AWS::Lambda::WebFunctionRevision` and `AWS::Lambda::WebFunctionEndpoint` resources reference the function with `Ref`.", "title": "FunctionName" }, "InlineCode": { diff --git a/tests/model/test_sam_resources.py b/tests/model/test_sam_resources.py index 0dd99b676a..90e894dab2 100644 --- a/tests/model/test_sam_resources.py +++ b/tests/model/test_sam_resources.py @@ -609,22 +609,17 @@ def test_generated_function_name_bounds_long_logical_id(self): self.assertLessEqual(55 + 1 + 8, 64) @patch("boto3.session.Session.region_name", "ap-southeast-1") - def test_generated_function_name_shared_by_all_resources(self): - # Ref on AWS::Lambda::WebFunction returns the ARN, so the revision and endpoint must reuse the same - # generated name value as the function rather than Ref it. + def test_generated_function_name_revision_and_endpoint_ref_function(self): + # The revision and endpoint Ref the function (its ARN), which the service accepts. function = SamWebFunction("MyWebFunction") function.CodeUri = "s3://foobar/foo.zip" function.Runtime = "bar" function.AuthType = "IamAuth" cfnResources = function.to_cloudformation(**self.kwargs) - fn = next(x for x in cfnResources if isinstance(x, LambdaWebFunction)) revision = next(x for x in cfnResources if isinstance(x, LambdaWebFunctionRevision)) endpoint = next(x for x in cfnResources if isinstance(x, LambdaWebFunctionEndpoint)) - self.assertEqual(revision.FunctionName, fn.FunctionName) - self.assertEqual(endpoint.FunctionName, fn.FunctionName) - # And specifically NOT a bare Ref (which resolves to the ARN) - self.assertNotEqual(revision.FunctionName, {"Ref": "MyWebFunction"}) - self.assertNotEqual(endpoint.FunctionName, {"Ref": "MyWebFunction"}) + self.assertEqual(revision.FunctionName, {"Ref": "MyWebFunction"}) + self.assertEqual(endpoint.FunctionName, {"Ref": "MyWebFunction"}) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_webfunction_name_runtime_attr_is_not_ref(self): @@ -639,7 +634,7 @@ def test_webfunction_name_runtime_attr_is_not_ref(self): self.assertNotEqual(fn.get_runtime_attr("name"), {"Ref": "MyWebFunction"}) @patch("boto3.session.Session.region_name", "ap-southeast-1") - def test_explicit_function_name_shared_by_all_resources(self): + def test_explicit_function_name_revision_and_endpoint_ref_function(self): function = SamWebFunction("MyWebFunction") function.CodeUri = "s3://foobar/foo.zip" function.Runtime = "bar" @@ -650,8 +645,8 @@ def test_explicit_function_name_shared_by_all_resources(self): revision = next(x for x in cfnResources if isinstance(x, LambdaWebFunctionRevision)) endpoint = next(x for x in cfnResources if isinstance(x, LambdaWebFunctionEndpoint)) self.assertEqual(fn.FunctionName, "my-explicit-name") - self.assertEqual(revision.FunctionName, "my-explicit-name") - self.assertEqual(endpoint.FunctionName, "my-explicit-name") + self.assertEqual(revision.FunctionName, {"Ref": "MyWebFunction"}) + self.assertEqual(endpoint.FunctionName, {"Ref": "MyWebFunction"}) @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_default_service_config(self): @@ -772,6 +767,44 @@ def test_egress_network_connector_arn_intrinsic_passthrough(self): {"Fn::GetAtt": ["MyConnector", "LatestVersionArn"]}, ) + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_egress_network_connector_latest_version_arn_same_template(self): + connector = { + "Type": "AWS::Serverless::NetworkConnector", + "Properties": {"ComputeTypes": ["WebFunction"], "VpcConfig": {}}, + } + function = SamWebFunction("foo") + function.CodeUri = "s3://foobar/foo.zip" + function.Runtime = "bar" + function.AuthType = "IamAuth" + function.EgressNetworkConnectorArn = {"Fn::GetAtt": ["MyConnector", "LatestVersionArn"]} + kwargs = {**self.kwargs, "resource_resolver": ResourceResolver({"MyConnector": connector})} + cfnResources = function.to_cloudformation(**kwargs) + revisions = [x for x in cfnResources if isinstance(x, LambdaWebFunctionRevision)] + self.assertEqual( + revisions[0].ServiceConfig["EgressNetworkConnectorArn"], + {"Fn::GetAtt": ["MyConnector", "LatestVersionArn"]}, + ) + + @parameterized.expand( + [ + ({"Ref": "MyConnector"}, {"ComputeTypes": ["WebFunction"]}), + ({"Fn::GetAtt": "MyConnector.Arn"}, {"ComputeTypes": ["WebFunction"]}), + ({"Fn::GetAtt": ["MyConnector", "Bogus"]}, {"ComputeTypes": ["WebFunction"]}), + ] + ) + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_egress_network_connector_invalid_same_template_reference_raises(self, arn, connector_properties): + connector = {"Type": "AWS::Serverless::NetworkConnector", "Properties": connector_properties} + function = SamWebFunction("foo") + function.CodeUri = "s3://foobar/foo.zip" + function.Runtime = "bar" + function.AuthType = "IamAuth" + function.EgressNetworkConnectorArn = arn + kwargs = {**self.kwargs, "resource_resolver": ResourceResolver({"MyConnector": connector})} + with pytest.raises(InvalidResourceException): + function.to_cloudformation(**kwargs) + @patch("boto3.session.Session.region_name", "ap-southeast-1") def test_endpoint_defaults(self): function = SamWebFunction("foo") diff --git a/tests/parser/test_parser.py b/tests/parser/test_parser.py index fde6180a02..48ad334a7f 100644 --- a/tests/parser/test_parser.py +++ b/tests/parser/test_parser.py @@ -38,3 +38,8 @@ def test_validate_template_with_empty_resources(self): parser = Parser() with self.assertRaises(InvalidDocumentException): parser._validate({"Resources": {}}, {}) + + def test_validate_template_that_is_not_a_dict(self): + parser = Parser() + with self.assertRaises(InvalidDocumentException): + parser._validate(None, {}) diff --git a/tests/test_intrinsics.py b/tests/test_intrinsics.py index a542309527..faae95d8b8 100644 --- a/tests/test_intrinsics.py +++ b/tests/test_intrinsics.py @@ -24,6 +24,9 @@ def test_is_intrinsic_on_empty_input(self): def test_is_intrinsic_on_non_dict_input(self): self.assertFalse(is_intrinsic([1, 2, 3])) + def test_is_intrinsic_on_non_string_key(self): + self.assertFalse(is_intrinsic({1: "a"})) + def test_is_intrinsic_on_intrinsic_like_dict_input(self): self.assertFalse(is_intrinsic({"Ref": "foo", "key": "bar"})) diff --git a/tests/translator/input/error_globals_non_string_section.yaml b/tests/translator/input/error_globals_non_string_section.yaml new file mode 100644 index 0000000000..e288429545 --- /dev/null +++ b/tests/translator/input/error_globals_non_string_section.yaml @@ -0,0 +1,12 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Globals: + 5: {} + +Resources: + Function: + Type: AWS::Serverless::Function + Properties: + Runtime: python3.13 + Handler: index.handler + InlineCode: def handler(event, context) -> None diff --git a/tests/translator/input/error_network_connector_invalid_protocol.yaml b/tests/translator/input/error_network_connector_invalid_protocol.yaml index 48a1269798..d5f19a73a1 100644 --- a/tests/translator/input/error_network_connector_invalid_protocol.yaml +++ b/tests/translator/input/error_network_connector_invalid_protocol.yaml @@ -1,10 +1,6 @@ AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 -Description: > - Test invalid NetworkProtocol enum value. - Note: error message says "not a valid dict" due to SamIntrinsicable union type - matching - Pydantic reports the dict branch failure rather than the Literal mismatch. - This is a known limitation of the SAM validation framework. +Description: Test invalid NetworkProtocol enum value Resources: NetworkConnectorInvalidProtocol: diff --git a/tests/translator/input/error_network_connector_non_string_compute_type.yaml b/tests/translator/input/error_network_connector_non_string_compute_type.yaml new file mode 100644 index 0000000000..36959d6f40 --- /dev/null +++ b/tests/translator/input/error_network_connector_non_string_compute_type.yaml @@ -0,0 +1,16 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: NetworkConnector with a non-string ComputeTypes value + +Resources: + NetworkConnectorNonStringComputeType: + Type: AWS::Serverless::NetworkConnector + Properties: + ComputeTypes: + - 5 + VpcConfig: + SubnetIds: + - subnet-abc + SecurityGroupIds: + - sg-123 + NetworkProtocol: IPv4 diff --git a/tests/translator/input/error_webfunction_egress_network_connector_reference.yaml b/tests/translator/input/error_webfunction_egress_network_connector_reference.yaml new file mode 100644 index 0000000000..69f4220b91 --- /dev/null +++ b/tests/translator/input/error_webfunction_egress_network_connector_reference.yaml @@ -0,0 +1,33 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: EgressNetworkConnectorArn references to connectors in the same template + that don't return a version-qualified ARN + +Resources: + WebFunctionConnector: + Type: AWS::Serverless::NetworkConnector + Properties: + ComputeTypes: + - WebFunction + VpcConfig: + SubnetIds: + - subnet-abc + SecurityGroupIds: + - sg-123 + NetworkProtocol: IPv4 + + WebFunctionRefConnector: + Type: AWS::Serverless::WebFunction + Properties: + Runtime: nodejs24.x + AuthType: IamAuth + CodeUri: s3://bucket/key.zip + EgressNetworkConnectorArn: !Ref WebFunctionConnector + + WebFunctionGetAttArn: + Type: AWS::Serverless::WebFunction + Properties: + Runtime: nodejs24.x + AuthType: IamAuth + CodeUri: s3://bucket/key.zip + EgressNetworkConnectorArn: !GetAtt WebFunctionConnector.Arn diff --git a/tests/translator/input/error_webfunction_invalid_property_types.yaml b/tests/translator/input/error_webfunction_invalid_property_types.yaml new file mode 100644 index 0000000000..ce7ad07220 --- /dev/null +++ b/tests/translator/input/error_webfunction_invalid_property_types.yaml @@ -0,0 +1,65 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: WebFunction properties with the wrong key or value types + +Resources: + WebFunctionIntegerEnvKey: + Type: AWS::Serverless::WebFunction + Properties: + Runtime: nodejs24.x + AuthType: IamAuth + CodeUri: s3://bucket/key.zip + EnvironmentVariables: + 1: a + B: c + + WebFunctionIntegerRevisionId: + Type: AWS::Serverless::WebFunction + Properties: + Runtime: nodejs24.x + AuthType: IamAuth + CodeUri: s3://bucket/key.zip + AutoDeploymentMode: Disabled + RevisionWeights: + 1: 100 + + WebFunctionScalarIncludeRegions: + Type: AWS::Serverless::WebFunction + Properties: + Runtime: nodejs24.x + AuthType: IamAuth + CodeUri: s3://bucket/key.zip + EndpointType: MultiRegion + AutoDeploymentMode: Disabled + ReplicaRegions: + IncludeRegions: us-east-1 + + WebFunctionMixedReplicaRegionsKeys: + Type: AWS::Serverless::WebFunction + Properties: + Runtime: nodejs24.x + AuthType: IamAuth + CodeUri: s3://bucket/key.zip + EndpointType: MultiRegion + AutoDeploymentMode: Disabled + ReplicaRegions: + IncludeRegions: + - US + 1: 2 + Foo: 3 + + WebFunctionIntegerEgressArn: + Type: AWS::Serverless::WebFunction + Properties: + Runtime: nodejs24.x + AuthType: IamAuth + CodeUri: s3://bucket/key.zip + EgressNetworkConnectorArn: 5 + + WebFunctionIntegerPropertyName: + Type: AWS::Serverless::WebFunction + Properties: + Runtime: nodejs24.x + AuthType: IamAuth + CodeUri: s3://bucket/key.zip + 5: value diff --git a/tests/translator/output/aws-cn/webfunction_basic.json b/tests/translator/output/aws-cn/webfunction_basic.json index 29e9c7ebca..7ef41a1724 100644 --- a/tests/translator/output/aws-cn/webfunction_basic.json +++ b/tests/translator/output/aws-cn/webfunction_basic.json @@ -51,34 +51,7 @@ "EndpointName": "prod", "EndpointType": "HomeRegion", "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "RevisionWeights": [ { @@ -111,34 +84,7 @@ } }, "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "ServiceConfig": { "ExecutionRoleArn": { diff --git a/tests/translator/output/aws-cn/webfunction_multiregion.json b/tests/translator/output/aws-cn/webfunction_multiregion.json index a5c780c87e..bc8aa73d0f 100644 --- a/tests/translator/output/aws-cn/webfunction_multiregion.json +++ b/tests/translator/output/aws-cn/webfunction_multiregion.json @@ -54,34 +54,7 @@ "EndpointName": "prod", "EndpointType": "MultiRegion", "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "Regions": [ "us-east-1", @@ -118,34 +91,7 @@ } }, "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "ServiceConfig": { "ExecutionRoleArn": { diff --git a/tests/translator/output/aws-cn/webfunction_with_all_properties.json b/tests/translator/output/aws-cn/webfunction_with_all_properties.json index 5b85c92a3d..97c0448ea1 100644 --- a/tests/translator/output/aws-cn/webfunction_with_all_properties.json +++ b/tests/translator/output/aws-cn/webfunction_with_all_properties.json @@ -56,34 +56,7 @@ "EndpointName": "staging", "EndpointType": "MultiRegion", "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "Regions": [ "us-east-1", @@ -128,34 +101,7 @@ }, "Description": "initial revision", "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "KmsKeyArn": "arn:aws:kms:us-east-1:123456789012:key/abcd1234", "ServiceConfig": { diff --git a/tests/translator/output/aws-us-gov/webfunction_basic.json b/tests/translator/output/aws-us-gov/webfunction_basic.json index 4bf11194de..7f8e0bebc8 100644 --- a/tests/translator/output/aws-us-gov/webfunction_basic.json +++ b/tests/translator/output/aws-us-gov/webfunction_basic.json @@ -51,34 +51,7 @@ "EndpointName": "prod", "EndpointType": "HomeRegion", "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "RevisionWeights": [ { @@ -111,34 +84,7 @@ } }, "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "ServiceConfig": { "ExecutionRoleArn": { diff --git a/tests/translator/output/aws-us-gov/webfunction_multiregion.json b/tests/translator/output/aws-us-gov/webfunction_multiregion.json index a9018e53c7..147866b932 100644 --- a/tests/translator/output/aws-us-gov/webfunction_multiregion.json +++ b/tests/translator/output/aws-us-gov/webfunction_multiregion.json @@ -54,34 +54,7 @@ "EndpointName": "prod", "EndpointType": "MultiRegion", "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "Regions": [ "us-east-1", @@ -118,34 +91,7 @@ } }, "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "ServiceConfig": { "ExecutionRoleArn": { diff --git a/tests/translator/output/aws-us-gov/webfunction_with_all_properties.json b/tests/translator/output/aws-us-gov/webfunction_with_all_properties.json index 2ab7a9909d..29aa41061d 100644 --- a/tests/translator/output/aws-us-gov/webfunction_with_all_properties.json +++ b/tests/translator/output/aws-us-gov/webfunction_with_all_properties.json @@ -56,34 +56,7 @@ "EndpointName": "staging", "EndpointType": "MultiRegion", "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "Regions": [ "us-east-1", @@ -128,34 +101,7 @@ }, "Description": "initial revision", "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "KmsKeyArn": "arn:aws:kms:us-east-1:123456789012:key/abcd1234", "ServiceConfig": { diff --git a/tests/translator/output/error_globals_non_string_section.json b/tests/translator/output/error_globals_non_string_section.json new file mode 100644 index 0000000000..a3a799a69c --- /dev/null +++ b/tests/translator/output/error_globals_non_string_section.json @@ -0,0 +1,15 @@ +{ + "_autoGeneratedBreakdownErrorMessage": [ + "Invalid Serverless Application Specification document. ", + "Number of errors found: 1. ", + "'Globals' section is invalid. ", + "'5' is not supported. ", + "Must be one of the following values - ['Api', 'CapacityProvider', 'Function', 'HttpApi', 'LayerVersion', 'MicrovmImage', 'NetworkConnector', 'SimpleTable', 'StateMachine', 'WebSocketApi']" + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. 'Globals' section is invalid. '5' is not supported. Must be one of the following values - ['Api', 'CapacityProvider', 'Function', 'HttpApi', 'LayerVersion', 'MicrovmImage', 'NetworkConnector', 'SimpleTable', 'StateMachine', 'WebSocketApi']", + "errors": [ + { + "errorMessage": "'Globals' section is invalid. '5' is not supported. Must be one of the following values - ['Api', 'CapacityProvider', 'Function', 'HttpApi', 'LayerVersion', 'MicrovmImage', 'NetworkConnector', 'SimpleTable', 'StateMachine', 'WebSocketApi']" + } + ] +} diff --git a/tests/translator/output/error_network_connector_invalid_protocol.json b/tests/translator/output/error_network_connector_invalid_protocol.json index 709fb392f4..b579fef572 100644 --- a/tests/translator/output/error_network_connector_invalid_protocol.json +++ b/tests/translator/output/error_network_connector_invalid_protocol.json @@ -3,12 +3,12 @@ "Invalid Serverless Application Specification document. ", "Number of errors found: 1. ", "Resource with id [NetworkConnectorInvalidProtocol] is invalid. ", - "Property 'VpcConfig.NetworkProtocol' value is not a valid dict." + "'VpcConfig.NetworkProtocol' must be one of ['IPv4', 'DualStack'], got 'IPv6'." ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [NetworkConnectorInvalidProtocol] is invalid. Property 'VpcConfig.NetworkProtocol' value is not a valid dict.", + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [NetworkConnectorInvalidProtocol] is invalid. 'VpcConfig.NetworkProtocol' must be one of ['IPv4', 'DualStack'], got 'IPv6'.", "errors": [ { - "errorMessage": "Resource with id [NetworkConnectorInvalidProtocol] is invalid. Property 'VpcConfig.NetworkProtocol' value is not a valid dict." + "errorMessage": "Resource with id [NetworkConnectorInvalidProtocol] is invalid. 'VpcConfig.NetworkProtocol' must be one of ['IPv4', 'DualStack'], got 'IPv6'." } ] } diff --git a/tests/translator/output/error_network_connector_non_string_compute_type.json b/tests/translator/output/error_network_connector_non_string_compute_type.json new file mode 100644 index 0000000000..594527b7da --- /dev/null +++ b/tests/translator/output/error_network_connector_non_string_compute_type.json @@ -0,0 +1,14 @@ +{ + "_autoGeneratedBreakdownErrorMessage": [ + "Invalid Serverless Application Specification document. ", + "Number of errors found: 1. ", + "Resource with id [NetworkConnectorNonStringComputeType] is invalid. ", + "'ComputeTypes' values must be one of ['MicroVm', 'WebFunction'], got 5." + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [NetworkConnectorNonStringComputeType] is invalid. 'ComputeTypes' values must be one of ['MicroVm', 'WebFunction'], got 5.", + "errors": [ + { + "errorMessage": "Resource with id [NetworkConnectorNonStringComputeType] is invalid. 'ComputeTypes' values must be one of ['MicroVm', 'WebFunction'], got 5." + } + ] +} diff --git a/tests/translator/output/error_webfunction_egress_network_connector_reference.json b/tests/translator/output/error_webfunction_egress_network_connector_reference.json new file mode 100644 index 0000000000..016df8bc13 --- /dev/null +++ b/tests/translator/output/error_webfunction_egress_network_connector_reference.json @@ -0,0 +1,21 @@ +{ + "_autoGeneratedBreakdownErrorMessage": [ + "Invalid Serverless Application Specification document. ", + "Number of errors found: 2. ", + "Resource with id [WebFunctionGetAttArn] is invalid. ", + "'EgressNetworkConnectorArn' must be a version-qualified network-connector ARN; !GetAtt WebFunctionConnector.Arn doesn't return one. ", + "Use !GetAtt WebFunctionConnector.LatestVersionArn instead. ", + "Resource with id [WebFunctionRefConnector] is invalid. ", + "'EgressNetworkConnectorArn' must be a version-qualified network-connector ARN; !Ref WebFunctionConnector doesn't return one. ", + "Use !GetAtt WebFunctionConnector.LatestVersionArn instead." + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 2. Resource with id [WebFunctionGetAttArn] is invalid. 'EgressNetworkConnectorArn' must be a version-qualified network-connector ARN; !GetAtt WebFunctionConnector.Arn doesn't return one. Use !GetAtt WebFunctionConnector.LatestVersionArn instead. Resource with id [WebFunctionRefConnector] is invalid. 'EgressNetworkConnectorArn' must be a version-qualified network-connector ARN; !Ref WebFunctionConnector doesn't return one. Use !GetAtt WebFunctionConnector.LatestVersionArn instead.", + "errors": [ + { + "errorMessage": "Resource with id [WebFunctionRefConnector] is invalid. 'EgressNetworkConnectorArn' must be a version-qualified network-connector ARN; !Ref WebFunctionConnector doesn't return one. Use !GetAtt WebFunctionConnector.LatestVersionArn instead." + }, + { + "errorMessage": "Resource with id [WebFunctionGetAttArn] is invalid. 'EgressNetworkConnectorArn' must be a version-qualified network-connector ARN; !GetAtt WebFunctionConnector.Arn doesn't return one. Use !GetAtt WebFunctionConnector.LatestVersionArn instead." + } + ] +} diff --git a/tests/translator/output/error_webfunction_invalid_property_types.json b/tests/translator/output/error_webfunction_invalid_property_types.json new file mode 100644 index 0000000000..8374a85afe --- /dev/null +++ b/tests/translator/output/error_webfunction_invalid_property_types.json @@ -0,0 +1,41 @@ +{ + "_autoGeneratedBreakdownErrorMessage": [ + "Invalid Serverless Application Specification document. ", + "Number of errors found: 6. ", + "Resource with id [WebFunctionIntegerEgressArn] is invalid. ", + "'EgressNetworkConnectorArn' must be a version-qualified network-connector ARN ('arn::lambda:::network-connector::', e.g. ", + "the connector's LatestVersionArn), got 5. ", + "Resource with id [WebFunctionIntegerEnvKey] is invalid. ", + "Environment variable key '1' must match pattern '^[a-zA-Z][a-zA-Z0-9_]*$'. ", + "Resource with id [WebFunctionIntegerPropertyName] is invalid. ", + "Property names must be strings, got 5. ", + "Resource with id [WebFunctionIntegerRevisionId] is invalid. ", + "'RevisionWeights' keys must be revision ID strings. ", + "Resource with id [WebFunctionMixedReplicaRegionsKeys] is invalid. ", + "'ReplicaRegions' contains unrecognized keys: ['1', 'Foo']. ", + "Valid keys are: IncludeRegions, ExcludeRegions. ", + "Resource with id [WebFunctionScalarIncludeRegions] is invalid. ", + "'ReplicaRegions.IncludeRegions' must be a list." + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 6. Resource with id [WebFunctionIntegerEgressArn] is invalid. 'EgressNetworkConnectorArn' must be a version-qualified network-connector ARN ('arn::lambda:::network-connector::', e.g. the connector's LatestVersionArn), got 5. Resource with id [WebFunctionIntegerEnvKey] is invalid. Environment variable key '1' must match pattern '^[a-zA-Z][a-zA-Z0-9_]*$'. Resource with id [WebFunctionIntegerPropertyName] is invalid. Property names must be strings, got 5. Resource with id [WebFunctionIntegerRevisionId] is invalid. 'RevisionWeights' keys must be revision ID strings. Resource with id [WebFunctionMixedReplicaRegionsKeys] is invalid. 'ReplicaRegions' contains unrecognized keys: ['1', 'Foo']. Valid keys are: IncludeRegions, ExcludeRegions. Resource with id [WebFunctionScalarIncludeRegions] is invalid. 'ReplicaRegions.IncludeRegions' must be a list.", + "errors": [ + { + "errorMessage": "Resource with id [WebFunctionIntegerEnvKey] is invalid. Environment variable key '1' must match pattern '^[a-zA-Z][a-zA-Z0-9_]*$'." + }, + { + "errorMessage": "Resource with id [WebFunctionIntegerRevisionId] is invalid. 'RevisionWeights' keys must be revision ID strings." + }, + { + "errorMessage": "Resource with id [WebFunctionScalarIncludeRegions] is invalid. 'ReplicaRegions.IncludeRegions' must be a list." + }, + { + "errorMessage": "Resource with id [WebFunctionMixedReplicaRegionsKeys] is invalid. 'ReplicaRegions' contains unrecognized keys: ['1', 'Foo']. Valid keys are: IncludeRegions, ExcludeRegions." + }, + { + "errorMessage": "Resource with id [WebFunctionIntegerEgressArn] is invalid. 'EgressNetworkConnectorArn' must be a version-qualified network-connector ARN ('arn::lambda:::network-connector::', e.g. the connector's LatestVersionArn), got 5." + }, + { + "errorMessage": "Resource with id [WebFunctionIntegerPropertyName] is invalid. Property names must be strings, got 5." + } + ] +} diff --git a/tests/translator/output/webfunction_basic.json b/tests/translator/output/webfunction_basic.json index 80f5f3f897..d836688b94 100644 --- a/tests/translator/output/webfunction_basic.json +++ b/tests/translator/output/webfunction_basic.json @@ -51,34 +51,7 @@ "EndpointName": "prod", "EndpointType": "HomeRegion", "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "RevisionWeights": [ { @@ -111,34 +84,7 @@ } }, "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "ServiceConfig": { "ExecutionRoleArn": { diff --git a/tests/translator/output/webfunction_multiregion.json b/tests/translator/output/webfunction_multiregion.json index 9b9a574643..ecb5349c8d 100644 --- a/tests/translator/output/webfunction_multiregion.json +++ b/tests/translator/output/webfunction_multiregion.json @@ -54,34 +54,7 @@ "EndpointName": "prod", "EndpointType": "MultiRegion", "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "Regions": [ "us-east-1", @@ -118,34 +91,7 @@ } }, "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "ServiceConfig": { "ExecutionRoleArn": { diff --git a/tests/translator/output/webfunction_with_all_properties.json b/tests/translator/output/webfunction_with_all_properties.json index ced092730c..f81521a47d 100644 --- a/tests/translator/output/webfunction_with_all_properties.json +++ b/tests/translator/output/webfunction_with_all_properties.json @@ -56,34 +56,7 @@ "EndpointName": "staging", "EndpointType": "MultiRegion", "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "Regions": [ "us-east-1", @@ -128,34 +101,7 @@ }, "Description": "initial revision", "FunctionName": { - "Fn::Sub": [ - "MyWebFunction-${StackIdSuffix}", - { - "StackIdSuffix": { - "Fn::Select": [ - 0, - { - "Fn::Split": [ - "-", - { - "Fn::Select": [ - 2, - { - "Fn::Split": [ - "/", - { - "Ref": "AWS::StackId" - } - ] - } - ] - } - ] - } - ] - } - } - ] + "Ref": "MyWebFunction" }, "KmsKeyArn": "arn:aws:kms:us-east-1:123456789012:key/abcd1234", "ServiceConfig": {