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
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[flake8]
max-line-length = 120
ignore = E126
ignore = E126 F821 W504 W605
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ init:
$(info [*] Install requirements...)
@pip install -r requirements/dev.txt -r requirements/base.txt

flake:
$(info [*] Running flake8...)
@flake8 samtranslator

test:
$(info [*] Run the unit test with minimum code coverage of $(CODE_COVERAGE)%...)
@pytest --cov samtranslator --cov-report term-missing --cov-fail-under $(CODE_COVERAGE) tests
Expand All @@ -49,7 +53,7 @@ build-docs:
@$(MAKE) -C docs/website html

# Command to run everytime you make changes to verify everything works
dev: test
dev: flake test

# Verifications to run before sending a pull request
pr: init dev
Expand All @@ -68,4 +72,4 @@ TARGETS
build-docs Generate the documentation.
pr Perform all checks before submitting a Pull Request.

endef
endef
35 changes: 16 additions & 19 deletions samtranslator/intrinsics/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from six import string_types


class Action(object):
"""
Base class for intrinsic function actions. Each intrinsic function must subclass this,
Expand Down Expand Up @@ -45,14 +46,15 @@ def can_handle(self, input_dict):
"""

return input_dict is not None \
and isinstance(input_dict, dict) \
and len(input_dict) == 1 \
and self.intrinsic_name in input_dict
and isinstance(input_dict, dict) \
and len(input_dict) == 1 \
and self.intrinsic_name in input_dict

@classmethod
def _parse_resource_reference(cls, ref_value):
"""
Splits a resource reference of structure "LogicalId.Property" and returns the "LogicalId" and "Property" separately.
Splits a resource reference of structure "LogicalId.Property" and returns the "LogicalId" and "Property"
separately.

:param string ref_value: Input reference value which *may* contain the structure "LogicalId.Property"
:return string, string: Returns two values - logical_id, property. If the input does not contain the structure,
Expand Down Expand Up @@ -162,6 +164,7 @@ def resolve_resource_id_refs(self, input_dict, supported_resource_id_refs):
self.intrinsic_name: resolved_value
}


class SubAction(Action):
intrinsic_name = "Fn::Sub"

Expand Down Expand Up @@ -189,7 +192,6 @@ def do_replacement(full_ref, prop_name):

return self._handle_sub_action(input_dict, do_replacement)


def resolve_resource_refs(self, input_dict, supported_resource_refs):
"""
Resolves reference to some property of a resource. Inside string to be substituted, there could be either a
Expand Down Expand Up @@ -249,7 +251,6 @@ def do_replacement(full_ref, ref_value):

return self._handle_sub_action(input_dict, do_replacement)


def resolve_resource_id_refs(self, input_dict, supported_resource_id_refs):
"""
Resolves reference to some property of a resource. Inside string to be substituted, there could be either a
Expand Down Expand Up @@ -306,14 +307,13 @@ def do_replacement(full_ref, ref_value):

return self._handle_sub_action(input_dict, do_replacement)


def _handle_sub_action(self, input_dict, handler):
"""
Handles resolving replacements in the Sub action based on the handler that is passed as an input.

:param input_dict: Dictionary to be resolved
:param supported_values: One of several different objects that contain the supported values that need to be changed.
See each method above for specifics on these objects.
:param supported_values: One of several different objects that contain the supported values that
need to be changed. See each method above for specifics on these objects.
:param handler: handler that is specific to each implementation.
:return: Resolved value of the Sub dictionary
"""
Expand All @@ -327,7 +327,6 @@ def _handle_sub_action(self, input_dict, handler):

return input_dict


def _handle_sub_value(self, sub_value, handler_method):
"""
Generic method to handle value to Fn::Sub key. We are interested in parsing the ${} syntaxes inside
Expand Down Expand Up @@ -365,15 +364,15 @@ def handler_method(full_ref, ref_value):

:param string text: Input text
:param handler_method: Method to be called to handle each occurrence of ${blah} reference structure.
First parameter to this method is the full reference structure Ex: ${LogicalId.Property}. Second parameter is just the
value of the reference such as "LogicalId.Property"
First parameter to this method is the full reference structure Ex: ${LogicalId.Property}.
Second parameter is just the value of the reference such as "LogicalId.Property"

:return string: Text with all reference structures replaced as necessary
"""

# RegExp to find pattern "${logicalId.property}" and return the word inside bracket
logical_id_regex = '[A-Za-z0-9\.]+'
ref_pattern = re.compile(r'\$\{('+logical_id_regex+')\}')
ref_pattern = re.compile(r'\$\{(' + logical_id_regex + ')\}')

# Find all the pattern, and call the handler to decide how to substitute them.
# Do the substitution and return the final text
Expand All @@ -383,14 +382,14 @@ def handler_method(full_ref, ref_value):
lambda match: handler_method(match.group(0), match.group(1)),
text)


class GetAttAction(Action):
intrinsic_name = "Fn::GetAtt"

def resolve_parameter_refs(self, input_dict, parameters):
# Parameters can never be referenced within GetAtt value
return input_dict


def resolve_resource_refs(self, input_dict, supported_resource_refs):
"""
Resolve resource references within a GetAtt dict.
Expand Down Expand Up @@ -441,12 +440,11 @@ def resolve_resource_refs(self, input_dict, supported_resource_refs):
splits = value_str.split(self._resource_ref_separator)
logical_id = splits[0]
property = splits[1]
remaining = splits[2:] # if any
remaining = splits[2:] # if any

resolved_value = supported_resource_refs.get(logical_id, property)
return self._get_resolved_dictionary(input_dict, key, resolved_value, remaining)


def resolve_resource_id_refs(self, input_dict, supported_resource_id_refs):
"""
Resolve resource references within a GetAtt dict.
Expand Down Expand Up @@ -485,12 +483,11 @@ def resolve_resource_id_refs(self, input_dict, supported_resource_id_refs):
value_str = self._resource_ref_separator.join(value)
splits = value_str.split(self._resource_ref_separator)
logical_id = splits[0]
remaining = splits[1:] # if any
remaining = splits[1:] # if any

resolved_value = supported_resource_id_refs.get(logical_id)
return self._get_resolved_dictionary(input_dict, key, resolved_value, remaining)


def _get_resolved_dictionary(self, input_dict, key, resolved_value, remaining):
"""
Resolves the function and returns the updated dictionary
Expand All @@ -505,4 +502,4 @@ def _get_resolved_dictionary(self, input_dict, key, resolved_value, remaining):
# This is the new value of Fn::GetAtt
input_dict[key] = [resolved_value] + remaining

return input_dict
return input_dict
12 changes: 6 additions & 6 deletions samtranslator/intrinsics/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
from samtranslator.intrinsics.actions import Action, SubAction, RefAction, GetAttAction

# All intrinsics are supported by default
DEFAULT_SUPPORTED_INTRINSICS = {action.intrinsic_name:action() for action in [RefAction, SubAction, GetAttAction]}
DEFAULT_SUPPORTED_INTRINSICS = {action.intrinsic_name: action() for action in [RefAction, SubAction, GetAttAction]}


class IntrinsicsResolver(object):

def __init__(self, parameters, supported_intrinsics=DEFAULT_SUPPORTED_INTRINSICS):
"""
Instantiate the resolver
:param dict parameters: Map of parameter names to their values
:param dict supported_intrinsics: Dictionary of intrinsic functions this class supports along with the Action class that
can process this intrinsic
:param dict supported_intrinsics: Dictionary of intrinsic functions this class supports along with the
Action class that can process this intrinsic
:raises TypeError: If parameters or the supported_intrinsics arguments are invalid
"""

Expand All @@ -26,7 +27,6 @@ def __init__(self, parameters, supported_intrinsics=DEFAULT_SUPPORTED_INTRINSICS
self.supported_intrinsics = supported_intrinsics
self.parameters = parameters


def resolve_parameter_refs(self, input):
"""
Resolves references to parameters within the given dictionary recursively. Other intrinsic functions such as
Expand Down Expand Up @@ -218,5 +218,5 @@ def _is_intrinsic_dict(self, input):
"""
# All intrinsic functions are dictionaries with just one key
return isinstance(input, dict) \
and len(input) == 1 \
and list(input.keys())[0] in self.supported_intrinsics
and len(input) == 1 \
and list(input.keys())[0] in self.supported_intrinsics
34 changes: 19 additions & 15 deletions samtranslator/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,16 +307,17 @@ def get_runtime_attr(self, attr_name):

def get_passthrough_resource_attributes(self):
"""
Returns a dictionary of resource attributes of the ResourceMacro that should be passed through from the main
Returns a dictionary of resource attributes of the ResourceMacro that should be passed through from the main
vanilla CloudFormation resource to its children. Currently only Condition is copied.

:return: Dictionary of resource attributes.
"""
"""
attributes = None
if 'Condition' in self.resource_attributes:
attributes = { 'Condition': self.resource_attributes['Condition'] }
attributes = {'Condition': self.resource_attributes['Condition']}
return attributes



class ResourceMacro(Resource):
"""A ResourceMacro object represents a CloudFormation macro. A macro appears in the CloudFormation template in the
"Resources" mapping, but must be expanded into one or more vanilla CloudFormation resources before a stack can be
Expand Down Expand Up @@ -346,6 +347,7 @@ def to_cloudformation(self, **kwargs):
"""
raise NotImplementedError("Method to_cloudformation() must be implemented in a subclass of ResourceMacro")


class SamResourceMacro(ResourceMacro):
"""ResourceMacro that specifically refers to SAM (AWS::Serverless::*) resources.
"""
Expand Down Expand Up @@ -384,17 +386,18 @@ def get_resource_references(self, generated_cfn_resources, supported_resource_re
by to_cloudformation() on this SAM resource. Each SAM resource must provide a map of properties that it
supports and the type of CFN resource this property resolves to.

:param list of Resource object generated_cfn_resources: List of CloudFormation resources generated by this SAM resource
:param samtranslator.intrinsics.resource_refs.SupportedResourceReferences supported_resource_refs: Object holding
the mapping between property names and LogicalId of the generated CFN resource it maps to
:param list of Resource object generated_cfn_resources: List of CloudFormation resources generated by this
SAM resource
:param samtranslator.intrinsics.resource_refs.SupportedResourceReferences supported_resource_refs: Object
holding the mapping between property names and LogicalId of the generated CFN resource it maps to
:return: Updated supported_resource_refs
"""

if supported_resource_refs is None:
raise ValueError("`supported_resource_refs` object is required")

# Create a map of {ResourceType: LogicalId} for quick access
resource_id_by_type = {resource.resource_type:resource.logical_id for resource in generated_cfn_resources}
resource_id_by_type = {resource.resource_type: resource.logical_id for resource in generated_cfn_resources}

for property, cfn_type in self.referable_properties.items():
if cfn_type in resource_id_by_type:
Expand All @@ -406,7 +409,7 @@ def _construct_tag_list(self, tags, additional_tags=None):
if not bool(tags):
tags = {}

if additional_tags == None:
if additional_tags is None:
additional_tags = {}

for tag in self._RESERVED_TAGS:
Expand All @@ -424,7 +427,8 @@ def _check_tag(self, reserved_tag_name, tags):
if reserved_tag_name in tags:
raise InvalidResourceException(self.logical_id, reserved_tag_name + " is a reserved Tag key name and "
"cannot be set on your resource. "
"Please change the tag key in the input.")
"Please change the tag key in the "
"input.")

def _resolve_string_parameter(self, intrinsics_resolver, parameter_value, parameter_name):
if not parameter_value:
Expand All @@ -450,9 +454,9 @@ def __init__(self, *modules):
for module in modules:
# Get all classes in the specified module which have a class variable resource_type.
for _, resource_class in inspect.getmembers(module,
lambda cls: inspect.isclass(cls)
and cls.__module__ == module.__name__
and hasattr(cls, 'resource_type')):
lambda cls: inspect.isclass(cls) and
cls.__module__ == module.__name__ and
hasattr(cls, 'resource_type')):
self.resource_types[resource_class.resource_type] = resource_class

def can_resolve(self, resource_dict):
Expand All @@ -469,8 +473,8 @@ def resolve_resource_type(self, resource_dict):
:rtype: class
"""
if not self.can_resolve(resource_dict):
raise TypeError("Resource dict has missing or invalid value for key Type. Resource Dict is: "
+ str(resource_dict))
raise TypeError("Resource dict has missing or invalid value for key Type. Resource Dict is: " +
str(resource_dict))
if resource_dict['Type'] not in self.resource_types:
raise TypeError("Invalid resource type {resource_type}".format(resource_type=resource_dict['Type']))
return self.resource_types[resource_dict['Type']]
10 changes: 7 additions & 3 deletions samtranslator/model/api/api_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from samtranslator.translator.arn_generator import ArnGenerator

_CORS_WILDCARD = "'*'"
CorsProperties = namedtuple("_CorsProperties", ["AllowMethods", "AllowHeaders", "AllowOrigin", "MaxAge", "AllowCredentials"])
CorsProperties = namedtuple("_CorsProperties", ["AllowMethods", "AllowHeaders", "AllowOrigin", "MaxAge",
"AllowCredentials"])
# Default the Cors Properties to '*' wildcard and False AllowCredentials. Other properties are actually Optional
CorsProperties.__new__.__defaults__ = (None, None, _CORS_WILDCARD, None, False)

Expand All @@ -23,7 +24,10 @@

class ApiGenerator(object):

def __init__(self, logical_id, cache_cluster_enabled, cache_cluster_size, variables, depends_on, definition_body, definition_uri, name, stage_name, endpoint_configuration=None, method_settings=None, binary_media=None, cors=None, auth=None, access_log_setting=None, canary_setting=None, tracing_enabled=None):
def __init__(self, logical_id, cache_cluster_enabled, cache_cluster_size, variables, depends_on,
definition_body, definition_uri, name, stage_name, endpoint_configuration=None,
method_settings=None, binary_media=None, cors=None, auth=None, access_log_setting=None,
canary_setting=None, tracing_enabled=None):
"""Constructs an API Generator class that generates API Gateway resources

:param logical_id: Logical id of the SAM API Resource
Expand Down Expand Up @@ -224,7 +228,7 @@ def _add_cors(self):

editor = SwaggerEditor(self.definition_body)
for path in editor.iter_on_path():
editor.add_cors(path, properties.AllowOrigin, properties.AllowHeaders, properties.AllowMethods,
editor.add_cors(path, properties.AllowOrigin, properties.AllowHeaders, properties.AllowMethods,
max_age=properties.MaxAge, allow_credentials=properties.AllowCredentials)

# Assign the Swagger back to template
Expand Down
9 changes: 6 additions & 3 deletions samtranslator/model/apigateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,15 +187,18 @@ def _get_identity_source(self):
identity_source_headers = list(map(lambda h: 'method.request.header.' + h, self.identity.get('Headers')))

if self.identity.get('QueryStrings'):
identity_source_query_strings = list(map(lambda qs: 'method.request.querystring.' + qs, self.identity.get('QueryStrings')))
identity_source_query_strings = list(map(lambda qs: 'method.request.querystring.' + qs,
self.identity.get('QueryStrings')))

if self.identity.get('StageVariables'):
identity_source_stage_variables = list(map(lambda sv: 'stageVariables.' + sv, self.identity.get('StageVariables')))
identity_source_stage_variables = list(map(lambda sv: 'stageVariables.' + sv,
self.identity.get('StageVariables')))

if self.identity.get('Context'):
identity_source_context = list(map(lambda c: 'context.' + c, self.identity.get('Context')))

identity_source_array = identity_source_headers + identity_source_query_strings + identity_source_stage_variables + identity_source_context
identity_source_array = (identity_source_headers + identity_source_query_strings +
identity_source_stage_variables + identity_source_context)
identity_source = ', '.join(identity_source_array)

return identity_source
Expand Down
6 changes: 3 additions & 3 deletions samtranslator/model/cloudformation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from samtranslator.model import PropertyType, Resource
from samtranslator.model.types import is_type, one_of, is_str, list_of, any_type
from samtranslator.model.intrinsics import fnGetAtt, ref
from samtranslator.model.types import is_type, is_str, list_of
from samtranslator.model.intrinsics import ref


class NestedStack(Resource):
Expand All @@ -16,4 +16,4 @@ class NestedStack(Resource):

runtime_attrs = {
"stack_id": lambda self: ref(self.logical_id)
}
}
Loading