Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add a new property SeparateRecordSetGroup to disable merging into record set group #2993

Merged
merged 17 commits into from
Mar 16, 2023
Merged
Show file tree
Hide file tree
Changes from 8 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
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ class Route53(BaseModel):
IpV6: Optional[bool] = route53("IpV6")
SetIdentifier: Optional[PassThroughProp] # TODO: add docs
Region: Optional[PassThroughProp] # TODO: add docs
SeparateRecordSets: Optional[bool] # TODO: add docs


class Domain(BaseModel):
Expand Down
90 changes: 82 additions & 8 deletions samtranslator/model/api/api_generator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from collections import namedtuple
from typing import Any, Dict, List, Optional, Set, Tuple, Union, cast
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Set, Union, cast

from samtranslator.metrics.method_decorator import cw_timer
from samtranslator.model.apigateway import (
Expand All @@ -23,7 +24,7 @@
)
from samtranslator.model.intrinsics import fnGetAtt, fnSub, is_intrinsic, make_or_condition, ref
from samtranslator.model.lambda_ import LambdaPermission
from samtranslator.model.route53 import Route53RecordSetGroup
from samtranslator.model.route53 import Route53RecordSet, Route53RecordSetGroup
from samtranslator.model.s3_utils.uri_parser import parse_s3_uri
from samtranslator.model.tags.resource_tagging import get_tag_list
from samtranslator.model.types import PassThrough
Expand Down Expand Up @@ -67,6 +68,14 @@
GatewayResponseProperties = ["ResponseParameters", "ResponseTemplates", "StatusCode"]


@dataclass
class ApiDomainResponse:
Domain: Optional[ApiGatewayDomainName]
ApiGWBasePathMappingList: Optional[List[ApiGatewayBasePathMapping]]
RecordSetsGroup: Any
RecordSet: Optional[List[Route53RecordSet]]


class SharedApiUsagePlan:
"""
Collects API information from different API resources in the same template,
Expand Down Expand Up @@ -443,12 +452,12 @@ def _construct_stage(

def _construct_api_domain( # noqa: too-many-branches
self, rest_api: ApiGatewayRestApi, route53_record_set_groups: Any
) -> Tuple[Optional[ApiGatewayDomainName], Optional[List[ApiGatewayBasePathMapping]], Any]:
) -> ApiDomainResponse:
"""
Constructs and returns the ApiGateway Domain and BasepathMapping
"""
if self.domain is None:
return None, None, None
return ApiDomainResponse(None, None, None, None)

sam_expect(self.domain, self.logical_id, "Domain").to_be_a_map()
domain_name: PassThrough = sam_expect(
Expand Down Expand Up @@ -565,6 +574,18 @@ def _construct_api_domain( # noqa: too-many-branches
logical_id = "RecordSetGroup" + logical_id_suffix

record_set_group = route53_record_set_groups.get(logical_id)

if route53.get("SeparateRecordSets") and not is_intrinsic(route53.get("SeparateRecordSets")):
xazhao marked this conversation as resolved.
Show resolved Hide resolved
sam_expect(
route53.get("SeparateRecordSets"), self.logical_id, "Domain.Route53.SeparateRecordSets"
).to_be_a_string
xazhao marked this conversation as resolved.
Show resolved Hide resolved
return ApiDomainResponse(
domain,
basepath_resource_list,
None,
self._construct_individual_record_set(self.domain, api_domain_name, route53),
)

if not record_set_group:
record_set_group = Route53RecordSetGroup(logical_id, attributes=self.passthrough_resource_attributes)
if "HostedZoneId" in route53:
Expand All @@ -576,7 +597,39 @@ def _construct_api_domain( # noqa: too-many-branches

record_set_group.RecordSets += self._construct_record_sets_for_domain(self.domain, api_domain_name, route53)

return domain, basepath_resource_list, record_set_group
return ApiDomainResponse(domain, basepath_resource_list, record_set_group, None)

def _construct_individual_record_set(
self, domain: Dict[str, Any], api_domain_name: str, route53: Any
) -> List[Route53RecordSet]:
logical_id_suffix = LogicalIdGenerator(
"", [route53.get("HostedZoneId") or route53.get("HostedZoneName"), domain.get("DomainName")]
).gen()
logical_id = "RecordSet" + logical_id_suffix
logical_id_ipv6 = "RecordSetIpv6" + logical_id_suffix

recordset_list = []

recordset = Route53RecordSet(logical_id, attributes=self.passthrough_resource_attributes)
recordset.Name = domain.get("DomainName")
recordset.Type = "A"
recordset.AliasTarget = self._construct_alias_target(domain, api_domain_name, route53)
xazhao marked this conversation as resolved.
Show resolved Hide resolved

if route53.get("HostedZoneId"):
xazhao marked this conversation as resolved.
Show resolved Hide resolved
recordset.HostedZoneId = route53.get("HostedZoneId")
else:
recordset.HostedZoneName = route53.get("HostedZoneName")

recordset_list.extend([recordset])

if route53.get("IpV6"):
recordset_ipv6 = Route53RecordSet(logical_id_ipv6, attributes=self.passthrough_resource_attributes)
xazhao marked this conversation as resolved.
Show resolved Hide resolved
recordset_ipv6.Name = domain.get("DomainName")
recordset_ipv6.Type = "AAAA"
recordset_ipv6.AliasTarget = self._construct_alias_target(domain, api_domain_name, route53)
recordset_list.extend([recordset_ipv6])

return recordset_list

def _construct_record_sets_for_domain(
self, custom_domain_config: Dict[str, Any], api_domain_name: str, route53_config: Dict[str, Any]
Expand Down Expand Up @@ -626,14 +679,21 @@ def _construct_alias_target(self, domain: Dict[str, Any], api_domain_name: str,
return alias_target

@cw_timer(prefix="Generator", name="Api")
def to_cloudformation(self, redeploy_restapi_parameters, route53_record_set_groups): # type: ignore[no-untyped-def]
def to_cloudformation(
self, redeploy_restapi_parameters: Optional[Any], route53_record_set_groups: Dict[str, Route53RecordSetGroup]
) -> List[Any]:
xazhao marked this conversation as resolved.
Show resolved Hide resolved
"""Generates CloudFormation resources from a SAM API resource

:returns: a tuple containing the RestApi, Deployment, and Stage for an empty Api.
:rtype: tuple
"""
rest_api = self._construct_rest_api()
domain, basepath_mapping, route53 = self._construct_api_domain(rest_api, route53_record_set_groups)
apiDomainResponse = self._construct_api_domain(rest_api, route53_record_set_groups)
domain = apiDomainResponse.Domain
basepath_mapping = apiDomainResponse.ApiGWBasePathMappingList
route53_recordsetGroup = apiDomainResponse.RecordSetsGroup
xazhao marked this conversation as resolved.
Show resolved Hide resolved
route53_recordsets = apiDomainResponse.RecordSet

deployment = self._construct_deployment(rest_api)

swagger = None
Expand All @@ -646,7 +706,21 @@ def to_cloudformation(self, redeploy_restapi_parameters, route53_record_set_grou
permissions = self._construct_authorizer_lambda_permission()
usage_plan = self._construct_usage_plan(rest_api_stage=stage)

return rest_api, deployment, stage, permissions, domain, basepath_mapping, route53, usage_plan
generated_resources: List[Any] = []
hoffa marked this conversation as resolved.
Show resolved Hide resolved
generated_resources.extend(
[
rest_api,
deployment,
stage,
permissions,
domain,
basepath_mapping,
route53_recordsetGroup,
usage_plan,
route53_recordsets,
]
)
return generated_resources

def _add_cors(self) -> None:
"""
Expand Down
18 changes: 17 additions & 1 deletion samtranslator/model/route53.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, List, Optional
from typing import Any, Dict, List, Optional

from samtranslator.model import GeneratedProperty, Resource
from samtranslator.utils.types import Intrinsicable
Expand All @@ -15,3 +15,19 @@ class Route53RecordSetGroup(Resource):
HostedZoneId: Optional[Intrinsicable[str]]
HostedZoneName: Optional[Intrinsicable[str]]
RecordSets: Optional[List[Any]]


class Route53RecordSet(Resource):
xazhao marked this conversation as resolved.
Show resolved Hide resolved
resource_type = "AWS::Route53::RecordSet"
property_types = {
"HostedZoneId": GeneratedProperty(),
"HostedZoneName": GeneratedProperty(),
"AliasTarget": GeneratedProperty(),
"Name": GeneratedProperty(),
"Type": GeneratedProperty(),
}
HostedZoneId: Optional[Intrinsicable[str]]
HostedZoneName: Optional[Intrinsicable[str]]
AliasTarget: Optional[Dict[str, Any]]
Name: Optional[Intrinsicable[str]]
Type: Optional[Intrinsicable[str]]
33 changes: 11 additions & 22 deletions samtranslator/model/sam_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -1228,7 +1228,6 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def]
:returns: a list of vanilla CloudFormation Resources, to which this Function expands
:rtype: list
"""
resources = []

intrinsics_resolver = kwargs["intrinsics_resolver"]
self.BinaryMediaTypes = intrinsics_resolver.resolve_parameter_refs(self.BinaryMediaTypes)
Expand Down Expand Up @@ -1276,28 +1275,18 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def]
always_deploy=self.AlwaysDeploy,
)

(
rest_api,
deployment,
stage,
permissions,
domain,
basepath_mapping,
route53,
usage_plan_resources,
) = api_generator.to_cloudformation(redeploy_restapi_parameters, route53_record_set_groups)
generated_api_resources = api_generator.to_cloudformation(
redeploy_restapi_parameters, route53_record_set_groups
)

resources.extend([rest_api, deployment, stage])
resources.extend(permissions)
if domain:
resources.extend([domain])
if basepath_mapping:
resources.extend(basepath_mapping)
if route53:
resources.extend([route53])
# contains usage plan, api key and usageplan key resources
if usage_plan_resources:
resources.extend(usage_plan_resources)
resources: List[Any] = []
xazhao marked this conversation as resolved.
Show resolved Hide resolved

for resource in generated_api_resources:
if resource:
if isinstance(resource, (list, tuple)):
hoffa marked this conversation as resolved.
Show resolved Hide resolved
resources.extend(resource)
else:
resources.extend([resource])
return resources


Expand Down
4 changes: 4 additions & 0 deletions samtranslator/schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -198371,6 +198371,10 @@
"Region": {
"$ref": "#/definitions/PassThroughProp"
},
"SeparateRecordSets": {
"title": "Separaterecordsets",
"type": "boolean"
},
"SetIdentifier": {
"$ref": "#/definitions/PassThroughProp"
}
Expand Down
4 changes: 4 additions & 0 deletions schema_source/sam.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -4094,6 +4094,10 @@
"Region": {
"$ref": "#/definitions/PassThroughProp"
},
"SeparateRecordSets": {
"title": "Separaterecordsets",
"type": "boolean"
},
"SetIdentifier": {
"$ref": "#/definitions/PassThroughProp"
}
Expand Down
82 changes: 82 additions & 0 deletions tests/translator/input/separate_route53_recordset.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
apigateway-2402

Sample SAM Template for apigateway-2402

Parameters:
EnvType:
Description: Environment type.
Default: test
Type: String
AllowedValues:
- prod
- test
ConstraintDescription: must specify prod or test.
Conditions:
CreateProdResources: !Equals
- !Ref EnvType
- prod
Resources:
ApiGatewayAdminOne:
Type: AWS::Serverless::Api
Properties:
Name: App-Prod-Web
StageName: Prod
TracingEnabled: true
MethodSettings:
- LoggingLevel: Info
ResourcePath: /*
HttpMethod: '*'
Domain:
DomainName: admin.one.amazon.com
CertificateArn: arn::cert::abc
EndpointConfiguration: REGIONAL
Route53:
HostedZoneId: abc123456
EndpointConfiguration:
Type: REGIONAL


ApiGatewayAdminTwo:
Type: AWS::Serverless::Api
Condition: CreateProdResources
Properties:
Name: App-Prod-Web
StageName: Prod
TracingEnabled: true
MethodSettings:
- LoggingLevel: Info
ResourcePath: /*
HttpMethod: '*'
Domain:
DomainName: admin.two.amazon.com
CertificateArn: arn::cert::abc
EndpointConfiguration: REGIONAL
Route53:
HostedZoneId: abc123456
SeparateRecordSets: true
EndpointConfiguration:
Type: REGIONAL


ApiGatewayAdminThree:
Type: AWS::Serverless::Api
Properties:
Name: App-Prod-Web
StageName: Prod
TracingEnabled: true
MethodSettings:
- LoggingLevel: Info
ResourcePath: /*
HttpMethod: '*'
Domain:
DomainName: admin.three.amazon.com
CertificateArn: arn::cert::abc
EndpointConfiguration: REGIONAL
Route53:
HostedZoneId: abc123456
SeparateRecordSets: true
EndpointConfiguration:
Type: REGIONAL
Loading