def _construct_route53_recordsetgroup(self): record_set_group = None if self.domain.get("Route53") is not None: route53 = self.domain.get("Route53") if route53.get("HostedZoneId") is None and route53.get( "HostedZoneName") is None: raise InvalidResourceException( self.logical_id, "HostedZoneId or HostedZoneName is required to enable Route53 support on Custom Domains.", ) logical_id = logical_id_generator.LogicalIdGenerator( "", route53.get("HostedZoneId") or route53.get("HostedZoneName")).gen() record_set_group = Route53RecordSetGroup( "RecordSetGroup" + logical_id, attributes=self.passthrough_resource_attributes) if "HostedZoneId" in route53: record_set_group.HostedZoneId = route53.get("HostedZoneId") elif "HostedZoneName" in route53: record_set_group.HostedZoneName = route53.get("HostedZoneName") record_set_group.RecordSets = self._construct_record_sets_for_domain( self.domain) return record_set_group
def _construct_permission( self, function, source_arn=None, source_account=None, suffix="", event_source_token=None, prefix=None): """Constructs the Lambda Permission resource allowing the source service to invoke the function this event source triggers. :returns: the permission resource :rtype: model.lambda_.LambdaPermission """ if prefix is None: prefix = self.logical_id if suffix.isalnum(): permission_logical_id = prefix + 'Permission' + suffix else: generator = logical_id_generator.LogicalIdGenerator(prefix + 'Permission', suffix) permission_logical_id = generator.gen() lambda_permission = LambdaPermission(permission_logical_id, attributes=function.get_passthrough_resource_attributes()) try: # Name will not be available for Alias resources function_name_or_arn = function.get_runtime_attr("name") except NotImplementedError: function_name_or_arn = function.get_runtime_attr("arn") lambda_permission.Action = 'lambda:invokeFunction' lambda_permission.FunctionName = function_name_or_arn lambda_permission.Principal = self.principal lambda_permission.SourceArn = source_arn lambda_permission.SourceAccount = source_account lambda_permission.EventSourceToken = event_source_token return lambda_permission
def _construct_stage(self): """Constructs and returns the ApiGatewayV2 Stage. :returns: the Stage to which this SAM Api corresponds :rtype: model.apigatewayv2.ApiGatewayV2Stage """ # If there are no special configurations, don't create a stage and use the default if not self.stage_name and not self.stage_variables and not self.access_log_settings: return # If StageName is some intrinsic function, then don't prefix the Stage's logical ID # This will NOT create duplicates because we allow only ONE stage per API resource stage_name_prefix = self.stage_name if isinstance(self.stage_name, string_types) else "" if stage_name_prefix.isalnum(): stage_logical_id = self.logical_id + stage_name_prefix + "Stage" elif stage_name_prefix == DefaultStageName: stage_logical_id = self.logical_id + "ApiGatewayDefaultStage" else: generator = logical_id_generator.LogicalIdGenerator(self.logical_id + "Stage", stage_name_prefix) stage_logical_id = generator.gen() stage = ApiGatewayV2Stage(stage_logical_id, attributes=self.passthrough_resource_attributes) stage.ApiId = ref(self.logical_id) stage.StageName = self.stage_name stage.StageVariables = self.stage_variables stage.AccessLogSettings = self.access_log_settings stage.AutoDeploy = True if self.tags is not None: stage.Tags = get_tag_list(self.tags) return stage
def make_auto_deployable(self, stage, openapi_version=None, swagger=None): """ Sets up the resource such that it will trigger a re-deployment when Swagger changes or the openapi version changes. :param swagger: Dictionary containing the Swagger definition of the API """ if not swagger: return # CloudFormation does NOT redeploy the API unless it has a new deployment resource # that points to latest RestApi resource. Append a hash of Swagger Body location to # redeploy only when the API data changes. First 10 characters of hash is good enough # to prevent redeployment when API has not changed # NOTE: `str(swagger)` is for backwards compatibility. Changing it to a JSON or something will break compat hash_input = [str(swagger)] if openapi_version: hash_input.append(str(openapi_version)) data = self._X_HASH_DELIMITER.join(hash_input) generator = logical_id_generator.LogicalIdGenerator( self.logical_id, data) self.logical_id = generator.gen() digest = generator.get_hash(length=40) # Get the full hash self.Description = "RestApi deployment id: {}".format(digest) stage.update_deployment_ref(self.logical_id)
def _construct_api_domain(self, http_api): """ Constructs and returns the ApiGateway Domain and BasepathMapping """ if self.domain is None: return None, None, None if self.domain.get("DomainName") is None or self.domain.get( "CertificateArn") is None: raise InvalidResourceException( self.logical_id, "Custom Domains only works if both DomainName and CertificateArn" " are provided.") self.domain["ApiDomainName"] = "{}{}".format( "ApiGatewayDomainNameV2", logical_id_generator.LogicalIdGenerator( "", self.domain.get("DomainName")).gen()) domain = ApiGatewayV2DomainName( self.domain.get("ApiDomainName"), attributes=self.passthrough_resource_attributes) domain_config = dict() domain.DomainName = self.domain.get("DomainName") domain.Tags = self.tags endpoint = self.domain.get("EndpointConfiguration") if endpoint is None: endpoint = "REGIONAL" # to make sure that default is always REGIONAL self.domain["EndpointConfiguration"] = "REGIONAL" elif endpoint not in ["REGIONAL"]: raise InvalidResourceException( self.logical_id, "EndpointConfiguration for Custom Domains must be one of {}.". format(["REGIONAL"]), ) domain_config["EndpointType"] = endpoint domain_config["CertificateArn"] = self.domain.get("CertificateArn") domain.DomainNameConfigurations = [domain_config] # Create BasepathMappings if self.domain.get("BasePath") and isinstance( self.domain.get("BasePath"), string_types): basepaths = [self.domain.get("BasePath")] elif self.domain.get("BasePath") and isinstance( self.domain.get("BasePath"), list): basepaths = self.domain.get("BasePath") else: basepaths = None basepath_resource_list = self._construct_basepath_mappings( basepaths, http_api) # Create the Route53 RecordSetGroup resource record_set_group = self._construct_route53_recordsetgroup() return domain, basepath_resource_list, record_set_group
def _construct_version(self, function, intrinsics_resolver): """Constructs a Lambda Version resource that will be auto-published when CodeUri of the function changes. Old versions will not be deleted without a direct reference from the CloudFormation template. :param model.lambda_.LambdaFunction function: Lambda function object that is being connected to a version :param model.intrinsics.resolver.IntrinsicsResolver intrinsics_resolver: Class that can help resolve references to parameters present in CodeUri. It is a common usecase to set S3Key of Code to be a template parameter. Need to resolve the values otherwise we will never detect a change in Code dict :return: Lambda function Version resource """ code_dict = function.Code if not code_dict: raise ValueError( "Lambda function code must be a valid non-empty dictionary") if not intrinsics_resolver: raise ValueError( "intrinsics_resolver is required for versions creation") # Resolve references to template parameters before creating hash. This will *not* resolve all intrinsics # because we cannot resolve runtime values like Arn of a resource. For purposes of detecting changes, this # is good enough. Here is why: # # When using intrinsic functions there are two cases when has must change: # - Value of the template parameter changes # - (or) LogicalId of a referenced resource changes ie. !GetAtt NewResource.Arn # # Later case will already change the hash because some value in the Code dictionary changes. We handle the # first case by resolving references to template parameters. It is okay even if these references are # present inside another intrinsic such as !Join. The resolver will replace the reference with the parameter's # value and keep all other parts of !Join identical. This will still trigger a change in the hash. code_dict = intrinsics_resolver.resolve_parameter_refs(code_dict) # Construct the LogicalID of Lambda version by appending 10 characters of SHA of CodeUri. This is necessary # to trigger creation of a new version every time code location changes. Since logicalId changes, CloudFormation # will drop the old version and create a new one for us. We set a DeletionPolicy on the version resource to # prevent CloudFormation from actually deleting the underlying version resource # # SHA Collisions: For purposes of triggering a new update, we are concerned about just the difference previous # and next hashes. The chances that two subsequent hashes collide is fairly low. prefix = "{id}Version".format(id=self.logical_id) logical_id = logical_id_generator.LogicalIdGenerator( prefix, code_dict).gen() attributes = self.get_passthrough_resource_attributes() if attributes is None: attributes = {} attributes["DeletionPolicy"] = "Retain" lambda_version = LambdaVersion(logical_id=logical_id, attributes=attributes) lambda_version.FunctionName = function.get_runtime_attr('name') lambda_version.Description = self.VersionDescription return lambda_version
def _construct_lambda_layer(self, intrinsics_resolver): """Constructs and returns the Lambda function. :returns: a list containing the Lambda function and execution role resources :rtype: list """ # Resolve intrinsics if applicable: self.LayerName = self._resolve_string_parameter( intrinsics_resolver, self.LayerName, 'LayerName') self.LicenseInfo = self._resolve_string_parameter( intrinsics_resolver, self.LicenseInfo, 'LicenseInfo') self.Description = self._resolve_string_parameter( intrinsics_resolver, self.Description, 'Description') self.RetentionPolicy = self._resolve_string_parameter( intrinsics_resolver, self.RetentionPolicy, 'RetentionPolicy') retention_policy_value = self._get_retention_policy_value() attributes = self.get_passthrough_resource_attributes() if attributes is None: attributes = {} attributes['DeletionPolicy'] = retention_policy_value old_logical_id = self.logical_id new_logical_id = logical_id_generator.LogicalIdGenerator( old_logical_id, self.to_dict()).gen() self.logical_id = new_logical_id lambda_layer = LambdaLayerVersion(self.logical_id, depends_on=self.depends_on, attributes=attributes) # Changing the LayerName property: when a layer is published, it is given an Arn # example: arn:aws:lambda:us-west-2:123456789012:layer:MyLayer:1 # where MyLayer is the LayerName property if it exists; otherwise, it is the # LogicalId of this resource. Since a LayerVersion is an immutable resource, when # CloudFormation updates this resource, it will ALWAYS create a new version then # delete the old version if the logical ids match. What this does is change the # logical id of every layer (so a `DeletionPolicy: Retain` can work) and set the # LayerName property of the layer so that the Arn will still always be the same # with the exception of an incrementing version number. if not self.LayerName: self.LayerName = old_logical_id lambda_layer.LayerName = self.LayerName lambda_layer.Description = self.Description lambda_layer.Content = construct_s3_location_object( self.ContentUri, self.logical_id, 'ContentUri') lambda_layer.CompatibleRuntimes = self.CompatibleRuntimes lambda_layer.LicenseInfo = self.LicenseInfo return lambda_layer
def make_auto_deployable(self, stage, openapi_version=None, swagger=None, domain=None, redeploy_restapi_parameters=None): """ Sets up the resource such that it will trigger a re-deployment when Swagger changes or the openapi version changes or a domain resource changes. :param swagger: Dictionary containing the Swagger definition of the API :param openapi_version: string containing value of OpenApiVersion flag in the template :param domain: Dictionary containing the custom domain configuration for the API :param redeploy_restapi_parameters: Dictionary containing the properties for which rest api will be redeployed """ if not swagger: return # CloudFormation does NOT redeploy the API unless it has a new deployment resource # that points to latest RestApi resource. Append a hash of Swagger Body location to # redeploy only when the API data changes. First 10 characters of hash is good enough # to prevent redeployment when API has not changed # NOTE: `str(swagger)` is for backwards compatibility. Changing it to a JSON or something will break compat hash_input = [str(swagger)] if openapi_version: hash_input.append(str(openapi_version)) if domain: hash_input.append(json.dumps(domain)) if redeploy_restapi_parameters: function_names = redeploy_restapi_parameters.get("function_names") else: function_names = None # The deployment logical id is <api logicalId> + "Deployment" # The keyword "Deployment" is removed and all the function names associated with api is obtained if function_names and function_names.get(self.logical_id[:-10], None): hash_input.append(function_names.get(self.logical_id[:-10], "")) data = self._X_HASH_DELIMITER.join(hash_input) generator = logical_id_generator.LogicalIdGenerator( self.logical_id, data) self.logical_id = generator.gen() digest = generator.get_hash(length=40) # Get the full hash self.Description = "RestApi deployment id: {}".format(digest) stage.update_deployment_ref(self.logical_id)
def _generate_logical_id(self, prefix, suffix, resource_type): """Helper utility to generate a logicial ID for a new resource :param string prefix: Prefix to use for the logical ID of the resource :param string suffix: Suffix to add for the logical ID of the resource :param string resource_type: Type of the resource :returns: the logical ID for the new resource :rtype: string """ if prefix is None: prefix = self.logical_id if suffix.isalnum(): logical_id = prefix + resource_type + suffix else: generator = logical_id_generator.LogicalIdGenerator( prefix + resource_type, suffix) logical_id = generator.gen() return logical_id
def _construct_stage(self, deployment, swagger, redeploy_restapi_parameters): """Constructs and returns the ApiGateway Stage. :param model.apigateway.ApiGatewayDeployment deployment: the Deployment for this Stage :returns: the Stage to which this SAM Api corresponds :rtype: model.apigateway.ApiGatewayStage """ # If StageName is some intrinsic function, then don't prefix the Stage's logical ID # This will NOT create duplicates because we allow only ONE stage per API resource stage_name_prefix = self.stage_name if isinstance( self.stage_name, string_types) else "" if stage_name_prefix.isalnum(): stage_logical_id = self.logical_id + stage_name_prefix + "Stage" else: generator = logical_id_generator.LogicalIdGenerator( self.logical_id + "Stage", stage_name_prefix) stage_logical_id = generator.gen() stage = ApiGatewayStage( stage_logical_id, attributes=self.passthrough_resource_attributes) stage.RestApiId = ref(self.logical_id) stage.update_deployment_ref(deployment.logical_id) stage.StageName = self.stage_name stage.CacheClusterEnabled = self.cache_cluster_enabled stage.CacheClusterSize = self.cache_cluster_size stage.Variables = self.variables stage.MethodSettings = self.method_settings stage.AccessLogSetting = self.access_log_setting stage.CanarySetting = self.canary_setting stage.TracingEnabled = self.tracing_enabled if swagger is not None: deployment.make_auto_deployable(stage, self.remove_extra_stage, swagger, self.domain, redeploy_restapi_parameters) if self.tags is not None: stage.Tags = get_tag_list(self.tags) return stage
def _construct_api_domain(self, rest_api): """ Constructs and returns the ApiGateway Domain and BasepathMapping """ if self.domain is None: return None, None, None if self.domain.get("DomainName") is None or self.domain.get("CertificateArn") is None: raise InvalidResourceException( self.logical_id, "Custom Domains only works if both DomainName and CertificateArn" " are provided." ) self.domain["ApiDomainName"] = "{}{}".format( "ApiGatewayDomainName", logical_id_generator.LogicalIdGenerator("", self.domain.get("DomainName")).gen() ) domain = ApiGatewayDomainName(self.domain.get("ApiDomainName"), attributes=self.passthrough_resource_attributes) domain.DomainName = self.domain.get("DomainName") endpoint = self.domain.get("EndpointConfiguration") if endpoint is None: endpoint = "REGIONAL" self.domain["EndpointConfiguration"] = "REGIONAL" elif endpoint not in ["EDGE", "REGIONAL", "PRIVATE"]: raise InvalidResourceException( self.logical_id, "EndpointConfiguration for Custom Domains must be" " one of {}.".format(["EDGE", "REGIONAL", "PRIVATE"]), ) if endpoint == "REGIONAL": domain.RegionalCertificateArn = self.domain.get("CertificateArn") else: domain.CertificateArn = self.domain.get("CertificateArn") domain.EndpointConfiguration = {"Types": [endpoint]} mutual_tls_auth = self.domain.get("MutualTlsAuthentication", None) if mutual_tls_auth: if isinstance(mutual_tls_auth, dict): if not set(mutual_tls_auth.keys()).issubset({"TruststoreUri", "TruststoreVersion"}): invalid_keys = list() for key in mutual_tls_auth.keys(): if not key in {"TruststoreUri", "TruststoreVersion"}: invalid_keys.append(key) invalid_keys.sort() raise InvalidResourceException( ",".join(invalid_keys), "Available MutualTlsAuthentication fields are {}.".format( ["TruststoreUri", "TruststoreVersion"] ), ) domain.MutualTlsAuthentication = {} if mutual_tls_auth.get("TruststoreUri", None): domain.MutualTlsAuthentication["TruststoreUri"] = mutual_tls_auth["TruststoreUri"] if mutual_tls_auth.get("TruststoreVersion", None): domain.MutualTlsAuthentication["TruststoreVersion"] = mutual_tls_auth["TruststoreVersion"] else: raise InvalidResourceException( mutual_tls_auth, "MutualTlsAuthentication must be a map with at least one of the following fields {}.".format( ["TruststoreUri", "TruststoreVersion"] ), ) if self.domain.get("SecurityPolicy", None): domain.SecurityPolicy = self.domain["SecurityPolicy"] # Create BasepathMappings if self.domain.get("BasePath") and isinstance(self.domain.get("BasePath"), string_types): basepaths = [self.domain.get("BasePath")] elif self.domain.get("BasePath") and isinstance(self.domain.get("BasePath"), list): basepaths = self.domain.get("BasePath") else: basepaths = None basepath_resource_list = [] if basepaths is None: basepath_mapping = ApiGatewayBasePathMapping( self.logical_id + "BasePathMapping", attributes=self.passthrough_resource_attributes ) basepath_mapping.DomainName = ref(self.domain.get("ApiDomainName")) basepath_mapping.RestApiId = ref(rest_api.logical_id) basepath_mapping.Stage = ref(rest_api.logical_id + ".Stage") basepath_resource_list.extend([basepath_mapping]) else: for path in basepaths: path = "".join(e for e in path if e.isalnum()) logical_id = "{}{}{}".format(self.logical_id, path, "BasePathMapping") basepath_mapping = ApiGatewayBasePathMapping( logical_id, attributes=self.passthrough_resource_attributes ) basepath_mapping.DomainName = ref(self.domain.get("ApiDomainName")) basepath_mapping.RestApiId = ref(rest_api.logical_id) basepath_mapping.Stage = ref(rest_api.logical_id + ".Stage") basepath_mapping.BasePath = path basepath_resource_list.extend([basepath_mapping]) # Create the Route53 RecordSetGroup resource record_set_group = None if self.domain.get("Route53") is not None: route53 = self.domain.get("Route53") if route53.get("HostedZoneId") is None and route53.get("HostedZoneName") is None: raise InvalidResourceException( self.logical_id, "HostedZoneId or HostedZoneName is required to enable Route53 support on Custom Domains.", ) logical_id = logical_id_generator.LogicalIdGenerator( "", route53.get("HostedZoneId") or route53.get("HostedZoneName") ).gen() record_set_group = Route53RecordSetGroup( "RecordSetGroup" + logical_id, attributes=self.passthrough_resource_attributes ) if "HostedZoneId" in route53: record_set_group.HostedZoneId = route53.get("HostedZoneId") if "HostedZoneName" in route53: record_set_group.HostedZoneName = route53.get("HostedZoneName") record_set_group.RecordSets = self._construct_record_sets_for_domain(self.domain) return domain, basepath_resource_list, record_set_group
def _construct_api_domain(self, rest_api): """ Constructs and returns the ApiGateway Domain and BasepathMapping """ if self.domain is None: return None, None, None if self.domain.get("DomainName") is None or self.domain.get( "CertificateArn") is None: raise InvalidResourceException( self.logical_id, "Custom Domains only works if both DomainName and CertificateArn" " are provided") self.domain["ApiDomainName"] = "{}{}".format( "ApiGatewayDomainName", logical_id_generator.LogicalIdGenerator( "", self.domain.get("DomainName")).gen()) domain = ApiGatewayDomainName( self.domain.get("ApiDomainName"), attributes=self.passthrough_resource_attributes) domain.DomainName = self.domain.get("DomainName") endpoint = self.domain.get("EndpointConfiguration") if endpoint is None: endpoint = "REGIONAL" self.domain["EndpointConfiguration"] = "REGIONAL" elif endpoint not in ["EDGE", "REGIONAL"]: raise InvalidResourceException( self.logical_id, "EndpointConfiguration for Custom Domains must be" " one of {}".format(["EDGE", "REGIONAL"]), ) if endpoint == "REGIONAL": domain.RegionalCertificateArn = self.domain.get("CertificateArn") else: domain.CertificateArn = self.domain.get("CertificateArn") domain.EndpointConfiguration = {"Types": [endpoint]} # Create BasepathMappings if self.domain.get("BasePath") and isinstance( self.domain.get("BasePath"), string_types): basepaths = [self.domain.get("BasePath")] elif self.domain.get("BasePath") and isinstance( self.domain.get("BasePath"), list): basepaths = self.domain.get("BasePath") else: basepaths = None basepath_resource_list = [] if basepaths is None: basepath_mapping = ApiGatewayBasePathMapping( self.logical_id + "BasePathMapping", attributes=self.passthrough_resource_attributes) basepath_mapping.DomainName = ref(self.domain.get("ApiDomainName")) basepath_mapping.RestApiId = ref(rest_api.logical_id) basepath_mapping.Stage = ref(rest_api.logical_id + ".Stage") basepath_resource_list.extend([basepath_mapping]) else: for path in basepaths: path = "".join(e for e in path if e.isalnum()) logical_id = "{}{}{}".format(self.logical_id, path, "BasePathMapping") basepath_mapping = ApiGatewayBasePathMapping( logical_id, attributes=self.passthrough_resource_attributes) basepath_mapping.DomainName = ref( self.domain.get("ApiDomainName")) basepath_mapping.RestApiId = ref(rest_api.logical_id) basepath_mapping.Stage = ref(rest_api.logical_id + ".Stage") basepath_mapping.BasePath = path basepath_resource_list.extend([basepath_mapping]) # Create the Route53 RecordSetGroup resource record_set_group = None if self.domain.get("Route53") is not None: route53 = self.domain.get("Route53") if route53.get("HostedZoneId") is None: raise InvalidResourceException( self.logical_id, "HostedZoneId is required to enable Route53 support on Custom Domains." ) logical_id = logical_id_generator.LogicalIdGenerator( "", route53.get("HostedZoneId")).gen() record_set_group = Route53RecordSetGroup( "RecordSetGroup" + logical_id, attributes=self.passthrough_resource_attributes) record_set_group.HostedZoneId = route53.get("HostedZoneId") record_set_group.RecordSets = self._construct_record_sets_for_domain( self.domain) return domain, basepath_resource_list, record_set_group
def _construct_api_domain(self, http_api): """ Constructs and returns the ApiGateway Domain and BasepathMapping """ if self.domain is None: return None, None, None if self.domain.get("DomainName") is None or self.domain.get( "CertificateArn") is None: raise InvalidResourceException( self.logical_id, "Custom Domains only works if both DomainName and CertificateArn" " are provided.") self.domain["ApiDomainName"] = "{}{}".format( "ApiGatewayDomainNameV2", logical_id_generator.LogicalIdGenerator( "", self.domain.get("DomainName")).gen()) domain = ApiGatewayV2DomainName( self.domain.get("ApiDomainName"), attributes=self.passthrough_resource_attributes) domain_config = dict() domain.DomainName = self.domain.get("DomainName") domain.Tags = self.tags endpoint = self.domain.get("EndpointConfiguration") if endpoint is None: endpoint = "REGIONAL" # to make sure that default is always REGIONAL self.domain["EndpointConfiguration"] = "REGIONAL" elif endpoint not in ["REGIONAL"]: raise InvalidResourceException( self.logical_id, "EndpointConfiguration for Custom Domains must be one of {}.". format(["REGIONAL"]), ) domain_config["EndpointType"] = endpoint domain_config["CertificateArn"] = self.domain.get("CertificateArn") if self.domain.get("SecurityPolicy", None): domain_config["SecurityPolicy"] = self.domain.get("SecurityPolicy") domain.DomainNameConfigurations = [domain_config] mutual_tls_auth = self.domain.get("MutualTlsAuthentication", None) if mutual_tls_auth: if isinstance(mutual_tls_auth, dict): if not set(mutual_tls_auth.keys()).issubset( {"TruststoreUri", "TruststoreVersion"}): invalid_keys = [] for key in mutual_tls_auth.keys(): if key not in {"TruststoreUri", "TruststoreVersion"}: invalid_keys.append(key) invalid_keys.sort() raise InvalidResourceException( ",".join(invalid_keys), "Available MutualTlsAuthentication fields are {}.". format(["TruststoreUri", "TruststoreVersion"]), ) domain.MutualTlsAuthentication = {} if mutual_tls_auth.get("TruststoreUri", None): domain.MutualTlsAuthentication[ "TruststoreUri"] = mutual_tls_auth["TruststoreUri"] if mutual_tls_auth.get("TruststoreVersion", None): domain.MutualTlsAuthentication[ "TruststoreVersion"] = mutual_tls_auth[ "TruststoreVersion"] else: raise InvalidResourceException( mutual_tls_auth, "MutualTlsAuthentication must be a map with at least one of the following fields {}." .format(["TruststoreUri", "TruststoreVersion"]), ) # Create BasepathMappings if self.domain.get("BasePath") and isinstance( self.domain.get("BasePath"), string_types): basepaths = [self.domain.get("BasePath")] elif self.domain.get("BasePath") and isinstance( self.domain.get("BasePath"), list): basepaths = self.domain.get("BasePath") else: basepaths = None basepath_resource_list = self._construct_basepath_mappings( basepaths, http_api) # Create the Route53 RecordSetGroup resource record_set_group = self._construct_route53_recordsetgroup() return domain, basepath_resource_list, record_set_group