diff --git a/.flake8 b/.flake8 index 4f80f891a1..83d5c03a0e 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,3 @@ [flake8] max-line-length = 120 -ignore = E126 +ignore = E126 F821 W504 W605 diff --git a/Makefile b/Makefile index ffcc26fcb3..ed57f7a1be 100755 --- a/Makefile +++ b/Makefile @@ -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 @@ -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 @@ -68,4 +72,4 @@ TARGETS build-docs Generate the documentation. pr Perform all checks before submitting a Pull Request. -endef \ No newline at end of file +endef diff --git a/samtranslator/intrinsics/actions.py b/samtranslator/intrinsics/actions.py index a151096d56..8803dddbe0 100644 --- a/samtranslator/intrinsics/actions.py +++ b/samtranslator/intrinsics/actions.py @@ -2,6 +2,7 @@ from six import string_types + class Action(object): """ Base class for intrinsic function actions. Each intrinsic function must subclass this, @@ -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, @@ -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" @@ -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 @@ -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 @@ -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 """ @@ -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 @@ -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 @@ -383,6 +382,7 @@ 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" @@ -390,7 +390,6 @@ 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. @@ -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. @@ -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 @@ -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 \ No newline at end of file + return input_dict diff --git a/samtranslator/intrinsics/resolver.py b/samtranslator/intrinsics/resolver.py index bcf6360e71..f7fe275300 100644 --- a/samtranslator/intrinsics/resolver.py +++ b/samtranslator/intrinsics/resolver.py @@ -3,7 +3,8 @@ 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): @@ -11,8 +12,8 @@ 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 """ @@ -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 @@ -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 diff --git a/samtranslator/model/__init__.py b/samtranslator/model/__init__.py index a9696f7176..ccd093320b 100644 --- a/samtranslator/model/__init__.py +++ b/samtranslator/model/__init__.py @@ -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 @@ -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. """ @@ -384,9 +386,10 @@ 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 """ @@ -394,7 +397,7 @@ def get_resource_references(self, generated_cfn_resources, supported_resource_re 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: @@ -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: @@ -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: @@ -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): @@ -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']] diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 530f5beef2..1dd2581381 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -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) @@ -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 @@ -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 diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index e74e345516..701a78a8d0 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -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 diff --git a/samtranslator/model/cloudformation.py b/samtranslator/model/cloudformation.py index 9edbcbb7e3..5d2409c2f7 100644 --- a/samtranslator/model/cloudformation.py +++ b/samtranslator/model/cloudformation.py @@ -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): @@ -16,4 +16,4 @@ class NestedStack(Resource): runtime_attrs = { "stack_id": lambda self: ref(self.logical_id) - } \ No newline at end of file + } diff --git a/samtranslator/model/eventsources/cloudwatchlogs.py b/samtranslator/model/eventsources/cloudwatchlogs.py index 4c4948f1e7..05ae3e6520 100644 --- a/samtranslator/model/eventsources/cloudwatchlogs.py +++ b/samtranslator/model/eventsources/cloudwatchlogs.py @@ -5,6 +5,7 @@ from samtranslator.translator.arn_generator import ArnGenerator from .push import PushEventSource + class CloudWatchLogs(PushEventSource): """CloudWatch Logs event source for SAM Functions.""" resource_type = 'CloudWatchLogs' @@ -27,20 +28,19 @@ def to_cloudformation(self, **kwargs): if not function: raise TypeError("Missing required keyword argument: function") - source_arn = self.get_source_arn() permission = self._construct_permission(function, source_arn=source_arn) subscription_filter = self.get_subscription_filter(function, permission) resources = [permission, subscription_filter] return resources - + def get_source_arn(self): resource = "log-group:${__LogGroupName__}:*" partition = ArnGenerator.get_partition_name() - + return fnSub(ArnGenerator.generate_arn(partition=partition, service='logs', resource=resource), - {'__LogGroupName__': self.LogGroupName}) + {'__LogGroupName__': self.LogGroupName}) def get_subscription_filter(self, function, permission): subscription_filter = SubscriptionFilter(self.logical_id, depends_on=[permission.logical_id]) diff --git a/samtranslator/model/eventsources/pull.py b/samtranslator/model/eventsources/pull.py index 4828f23bec..fd0681dbff 100644 --- a/samtranslator/model/eventsources/pull.py +++ b/samtranslator/model/eventsources/pull.py @@ -9,8 +9,9 @@ class PullEventSource(ResourceMacro): """Base class for pull event sources for SAM Functions. - The pull events are Kinesis Streams, DynamoDB Streams, and SQS Queues. All of these correspond to an EventSourceMapping in - Lambda, and require that the execution role be given to Kinesis Streams, DynamoDB Streams, or SQS Queues, respectively. + The pull events are Kinesis Streams, DynamoDB Streams, and SQS Queues. All of these correspond to an + EventSourceMapping in Lambda, and require that the execution role be given to Kinesis Streams, DynamoDB + Streams, or SQS Queues, respectively. :cvar str policy_arn: The ARN of the AWS managed role policy corresponding to this pull event source """ @@ -53,7 +54,7 @@ def to_cloudformation(self, **kwargs): if not self.Stream and not self.Queue: raise InvalidEventException( self.relative_id, "No Queue (for SQS) or Stream (for Kinesis or DynamoDB) provided.") - + if self.Stream and not self.StartingPosition: raise InvalidEventException( self.relative_id, "StartingPosition is required for Kinesis and DynamoDB.") diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index fa4ef8cbbe..b4eae408c6 100644 --- a/samtranslator/model/eventsources/push.py +++ b/samtranslator/model/eventsources/push.py @@ -9,12 +9,12 @@ from samtranslator.model.sns import SNSSubscription from samtranslator.model.lambda_ import LambdaPermission from samtranslator.model.events import EventsRule -from samtranslator.model.log import SubscriptionFilter from samtranslator.model.iot import IotTopicRule from samtranslator.translator.arn_generator import ArnGenerator from samtranslator.model.exceptions import InvalidEventException from samtranslator.swagger.swagger import SwaggerEditor + class PushEventSource(ResourceMacro): """Base class for push event sources for SAM Functions. @@ -44,7 +44,8 @@ def _construct_permission(self, function, source_arn=None, source_account=None, :returns: the permission resource :rtype: model.lambda_.LambdaPermission """ - lambda_permission = LambdaPermission(self.logical_id + 'Permission' + suffix, attributes=function.get_passthrough_resource_attributes()) + lambda_permission = LambdaPermission(self.logical_id + 'Permission' + suffix, + attributes=function.get_passthrough_resource_attributes()) try: # Name will not be available for Alias resources @@ -105,7 +106,7 @@ def _construct_target(self, function): """ target = { 'Arn': function.get_runtime_attr("arn"), - 'Id': self.logical_id + 'LambdaTarget' + 'Id': self.logical_id + 'LambdaTarget' } if self.Input is not None: target['Input'] = self.Input @@ -158,7 +159,7 @@ def _construct_target(self, function): """ target = { 'Arn': function.get_runtime_attr("arn"), - 'Id': self.logical_id + 'LambdaTarget' + 'Id': self.logical_id + 'LambdaTarget' } if self.Input is not None: target['Input'] = self.Input @@ -167,6 +168,7 @@ def _construct_target(self, function): target['InputPath'] = self.InputPath return target + class S3(PushEventSource): """S3 bucket event source for SAM Functions.""" resource_type = 'S3' @@ -250,7 +252,7 @@ def _depend_on_lambda_permissions(self, bucket, permission): # DependsOn can be either a list of strings or a scalar string if isinstance(depends_on, string_types): - depends_on = [ depends_on ] + depends_on = [depends_on] depends_on_set = set(depends_on) depends_on_set.add(permission.logical_id) @@ -260,13 +262,13 @@ def _depend_on_lambda_permissions(self, bucket, permission): def _depend_on_lambda_permissions_using_tag(self, bucket, permission): """ - Since conditional DependsOn is not supported this undocumented way of + Since conditional DependsOn is not supported this undocumented way of implicitely making dependency through tags is used. See https://stackoverflow.com/questions/34607476/cloudformation-apply-condition-on-dependson - It is done by using Fn:GetAtt wrapped in a conditional Fn:If. Using Fn:GetAtt implies a - dependency, so CloudFormation will automatically wait once it reaches that function, the same + It is done by using Fn:GetAtt wrapped in a conditional Fn:If. Using Fn:GetAtt implies a + dependency, so CloudFormation will automatically wait once it reaches that function, the same as if you were using a DependsOn. """ properties = bucket.get('Properties', None) @@ -502,8 +504,8 @@ def _add_swagger_integration(self, api, function): function_arn = function.get_runtime_attr('arn') partition = ArnGenerator.get_partition_name() - uri = fnSub('arn:'+partition+':apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/' - + make_shorthand(function_arn) + '/invocations') + uri = fnSub('arn:' + partition + ':apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/' + + make_shorthand(function_arn) + '/invocations') editor = SwaggerEditor(swagger_body) diff --git a/samtranslator/model/exceptions.py b/samtranslator/model/exceptions.py index 9db50a38c4..bca9f8fc90 100644 --- a/samtranslator/model/exceptions.py +++ b/samtranslator/model/exceptions.py @@ -57,7 +57,7 @@ class InvalidResourceException(Exception): Attributes: message -- explanation of the error """ - def __init__(self, logical_id, message): + def __init__(self, logical_id, message): self._logical_id = logical_id self._message = message diff --git a/samtranslator/model/function_policies.py b/samtranslator/model/function_policies.py index d62c2dceb4..c62e94d19d 100644 --- a/samtranslator/model/function_policies.py +++ b/samtranslator/model/function_policies.py @@ -7,6 +7,7 @@ PolicyEntry = namedtuple("PolicyEntry", "data type") + class FunctionPolicies(object): """ Class encapsulating the policies property of AWS::Serverless::Function. This class strictly encapsulates the data @@ -87,7 +88,7 @@ def _get_policies(self, resource_properties): result = [] for policy in policies: policy_type = self._get_type(policy) - entry = PolicyEntry(data = policy, type = policy_type) + entry = PolicyEntry(data=policy, type=policy_type) result.append(entry) return result @@ -100,8 +101,8 @@ def _contains_policies(self, resource_properties): :return: True if we can process this resource. False, otherwise """ return resource_properties is not None \ - and isinstance(resource_properties, dict) \ - and self.POLICIES_PROPERTY_NAME in resource_properties + and isinstance(resource_properties, dict) \ + and self.POLICIES_PROPERTY_NAME in resource_properties def _get_type(self, policy): """ @@ -138,10 +139,9 @@ def _is_policy_template(self, policy): """ return self._policy_template_processor is not None and \ - isinstance(policy, dict) and \ - len(policy) == 1 and \ - self._policy_template_processor.has(list(policy.keys())[0]) is True - + isinstance(policy, dict) and \ + len(policy) == 1 and \ + self._policy_template_processor.has(list(policy.keys())[0]) is True class PolicyTypes(Enum): diff --git a/samtranslator/model/iam.py b/samtranslator/model/iam.py index 959f58a806..fc9039c28f 100644 --- a/samtranslator/model/iam.py +++ b/samtranslator/model/iam.py @@ -17,6 +17,7 @@ class IAMRole(Resource): "arn": lambda self: fnGetAtt(self.logical_id, "Arn") } + class IAMRolePolicies(): @classmethod diff --git a/samtranslator/model/intrinsics.py b/samtranslator/model/intrinsics.py index 64e43d9733..aac26617ef 100644 --- a/samtranslator/model/intrinsics.py +++ b/samtranslator/model/intrinsics.py @@ -15,15 +15,17 @@ def fnSub(string, variables=None): return {'Fn::Sub': [string, variables]} return {'Fn::Sub': string} + def make_conditional(condition, data): return { 'Fn::If': [ condition, data, - { 'Ref': 'AWS::NoValue' } + {'Ref': 'AWS::NoValue'} ] } + def make_shorthand(intrinsic_dict): """ Converts a given intrinsics dictionary into a short-hand notation that Fn::Sub can use. Only Ref and Fn::GetAtt @@ -56,8 +58,8 @@ def is_instrinsic(input): """ if input is not None \ - and isinstance(input, dict) \ - and len(input) == 1: + and isinstance(input, dict) \ + and len(input) == 1: key = list(input.keys())[0] return key == "Ref" or key == "Condition" or key.startswith("Fn::") diff --git a/samtranslator/model/iot.py b/samtranslator/model/iot.py index cb713ad07b..8f008f182f 100644 --- a/samtranslator/model/iot.py +++ b/samtranslator/model/iot.py @@ -2,6 +2,7 @@ from samtranslator.model.types import is_type from samtranslator.model.intrinsics import ref, fnGetAtt + class IotTopicRule(Resource): resource_type = 'AWS::IoT::TopicRule' property_types = { diff --git a/samtranslator/model/lambda_.py b/samtranslator/model/lambda_.py index 750acbbb68..e4603adf70 100644 --- a/samtranslator/model/lambda_.py +++ b/samtranslator/model/lambda_.py @@ -29,6 +29,7 @@ class LambdaFunction(Resource): "arn": lambda self: fnGetAtt(self.logical_id, "Arn") } + class LambdaVersion(Resource): resource_type = 'AWS::Lambda::Version' property_types = { @@ -42,6 +43,7 @@ class LambdaVersion(Resource): "version": lambda self: fnGetAtt(self.logical_id, "Version") } + class LambdaAlias(Resource): resource_type = 'AWS::Lambda::Alias' property_types = { @@ -55,6 +57,7 @@ class LambdaAlias(Resource): "arn": lambda self: ref(self.logical_id) } + class LambdaEventSourceMapping(Resource): resource_type = 'AWS::Lambda::EventSourceMapping' property_types = { @@ -69,6 +72,7 @@ class LambdaEventSourceMapping(Resource): "name": lambda self: ref(self.logical_id) } + class LambdaPermission(Resource): resource_type = 'AWS::Lambda::Permission' property_types = { @@ -80,6 +84,7 @@ class LambdaPermission(Resource): 'EventSourceToken': PropertyType(False, is_str()) } + class LambdaLayerVersion(Resource): """ Lambda layer version resource """ @@ -97,4 +102,3 @@ class LambdaLayerVersion(Resource): "name": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn") } - diff --git a/samtranslator/model/preferences/deployment_preference.py b/samtranslator/model/preferences/deployment_preference.py index 1eac71826d..65ff0ae0e1 100644 --- a/samtranslator/model/preferences/deployment_preference.py +++ b/samtranslator/model/preferences/deployment_preference.py @@ -17,7 +17,7 @@ version. :param alarms: A list of Cloudwatch Alarm references that if ever in the alarm state during a deployment (or before a deployment starts) cause the deployment to fail and rollback. -:param role: An IAM role ARN that CodeDeploy will use for traffic shifting, an IAM role will not be created if +:param role: An IAM role ARN that CodeDeploy will use for traffic shifting, an IAM role will not be created if this is supplied :param enabled: Whether this deployment preference is enabled (true by default) """ diff --git a/samtranslator/model/preferences/deployment_preference_collection.py b/samtranslator/model/preferences/deployment_preference_collection.py index 0f590793ac..25ac191581 100644 --- a/samtranslator/model/preferences/deployment_preference_collection.py +++ b/samtranslator/model/preferences/deployment_preference_collection.py @@ -63,7 +63,6 @@ def can_skip_service_role(self): """ return all(preference.role for preference in self._resource_preferences.values()) - def enabled_logical_ids(self): """ :return: only the logical id's for the deployment preferences in this collection which are enabled @@ -111,7 +110,7 @@ def deployment_group(self, function_logical_id): 'DEPLOYMENT_STOP_ON_ALARM', 'DEPLOYMENT_STOP_ON_REQUEST']} deployment_group.DeploymentConfigName = fnSub("CodeDeployDefault.Lambda${ConfigName}", - {"ConfigName": deployment_preference.deployment_type}) + {"ConfigName": deployment_preference.deployment_type}) deployment_group.DeploymentStyle = {'DeploymentType': 'BLUE_GREEN', 'DeploymentOption': 'WITH_TRAFFIC_CONTROL'} diff --git a/samtranslator/model/s3.py b/samtranslator/model/s3.py index 79846e75e2..264d1252b5 100644 --- a/samtranslator/model/s3.py +++ b/samtranslator/model/s3.py @@ -2,6 +2,7 @@ from samtranslator.model.types import is_type, is_str, any_type from samtranslator.model.intrinsics import ref, fnGetAtt + class S3Bucket(Resource): resource_type = 'AWS::S3::Bucket' property_types = { diff --git a/samtranslator/model/s3_utils/uri_parser.py b/samtranslator/model/s3_utils/uri_parser.py index 886e6b60d8..b276ad6b6e 100644 --- a/samtranslator/model/s3_utils/uri_parser.py +++ b/samtranslator/model/s3_utils/uri_parser.py @@ -47,6 +47,7 @@ def to_s3_uri(code_dict): return uri + def construct_s3_location_object(location_uri, logical_id, property_name): """Constructs a Lambda `Code` or `Content` property, from the SAM `CodeUri` or `ContentUri` property. This follows the current scheme for Lambda Functions and LayerVersions. @@ -61,7 +62,8 @@ def construct_s3_location_object(location_uri, logical_id, property_name): if not location_uri.get("Bucket") or not location_uri.get("Key"): # location_uri is a dictionary but does not contain Bucket or Key property raise InvalidResourceException(logical_id, - "'{}' requires Bucket and Key properties to be specified".format(property_name)) + "'{}' requires Bucket and Key properties to be " + "specified".format(property_name)) s3_pointer = location_uri @@ -71,8 +73,9 @@ def construct_s3_location_object(location_uri, logical_id, property_name): if s3_pointer is None: raise InvalidResourceException(logical_id, - '\'{}\' is not a valid S3 Uri of the form ' - '"s3://bucket/key" with optional versionId query parameter.'.format(property_name)) + '\'{}\' is not a valid S3 Uri of the form ' + '"s3://bucket/key" with optional versionId query ' + 'parameter.'.format(property_name)) code = { 'S3Bucket': s3_pointer['Bucket'], diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 1abf4e7486..35207953cc 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -6,7 +6,7 @@ import samtranslator.model.eventsources.push import samtranslator.model.eventsources.cloudwatchlogs from .api.api_generator import ApiGenerator -from .s3_utils.uri_parser import parse_s3_uri, construct_s3_location_object +from .s3_utils.uri_parser import construct_s3_location_object from .tags.resource_tagging import get_tag_list from samtranslator.model import (PropertyType, SamResourceMacro, ResourceTypeResolver) @@ -153,7 +153,8 @@ def _construct_lambda_function(self): :returns: a list containing the Lambda function and execution role resources :rtype: list """ - lambda_function = LambdaFunction(self.logical_id, depends_on=self.depends_on, attributes=self.resource_attributes) + lambda_function = LambdaFunction(self.logical_id, depends_on=self.depends_on, + attributes=self.resource_attributes) if self.FunctionName: lambda_function.FunctionName = self.FunctionName @@ -566,7 +567,8 @@ def to_cloudformation(self, **kwargs): def _construct_nested_stack(self): """Constructs a AWS::CloudFormation::Stack resource """ - nested_stack = NestedStack(self.logical_id, depends_on=self.depends_on, attributes=self.get_passthrough_resource_attributes()) + nested_stack = NestedStack(self.logical_id, depends_on=self.depends_on, + attributes=self.get_passthrough_resource_attributes()) nested_stack.Parameters = self.Parameters nested_stack.NotificationArns = self.NotificationArns application_tags = self._get_application_tags() @@ -581,11 +583,11 @@ def _get_application_tags(self): """ application_tags = {} if isinstance(self.Location, dict): - if (self.APPLICATION_ID_KEY in self.Location.keys() - and self.Location[self.APPLICATION_ID_KEY] is not None): + if (self.APPLICATION_ID_KEY in self.Location.keys() and + self.Location[self.APPLICATION_ID_KEY] is not None): application_tags[self._SAR_APP_KEY] = self.Location[self.APPLICATION_ID_KEY] - if (self.SEMANTIC_VERSION_KEY in self.Location.keys() - and self.Location[self.SEMANTIC_VERSION_KEY] is not None): + if (self.SEMANTIC_VERSION_KEY in self.Location.keys() and + self.Location[self.SEMANTIC_VERSION_KEY] is not None): application_tags[self._SAR_SEMVER_KEY] = self.Location[self.SEMANTIC_VERSION_KEY] return application_tags @@ -605,7 +607,7 @@ class SamLayerVersion(SamResourceMacro): RETAIN = 'Retain' DELETE = 'Delete' - retention_policy_options = [ RETAIN.lower(), DELETE.lower() ] + retention_policy_options = [RETAIN.lower(), DELETE.lower()] def to_cloudformation(self, **kwargs): """Returns the Lambda layer to which this SAM Layer corresponds. @@ -633,7 +635,8 @@ def _construct_lambda_layer(self, intrinsics_resolver): 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') + self.RetentionPolicy = self._resolve_string_parameter(intrinsics_resolver, self.RetentionPolicy, + 'RetentionPolicy') retention_policy_value = self._get_retention_policy_value() @@ -683,5 +686,3 @@ def _get_retention_policy_value(self): raise InvalidResourceException(self.logical_id, "'{}' must be one of the following options: {}." .format('RetentionPolicy', [self.RETAIN, self.DELETE])) - - diff --git a/samtranslator/model/types.py b/samtranslator/model/types.py index 33cc9ed834..d92ec1ab7e 100644 --- a/samtranslator/model/types.py +++ b/samtranslator/model/types.py @@ -114,6 +114,7 @@ def is_str(): """ return is_type(string_types) + def any_type(): def validate(value, should_raise=False): return True diff --git a/samtranslator/parser/parser.py b/samtranslator/parser/parser.py index 4ce3900f8f..52f594dc71 100644 --- a/samtranslator/parser/parser.py +++ b/samtranslator/parser/parser.py @@ -1,8 +1,7 @@ -from samtranslator.model.exceptions import InvalidDocumentException, InvalidTemplateException, InvalidResourceException, InvalidEventException, DuplicateLogicalIdException +from samtranslator.model.exceptions import InvalidDocumentException, InvalidTemplateException from samtranslator.validator.validator import SamTemplateValidator -from samtranslator.model import ResourceTypeResolver, sam_resources from samtranslator.plugins import LifeCycleEvents -import logging + class Parser: def __init__(self): @@ -22,15 +21,9 @@ def _validate(self, sam_template, parameter_values): if parameter_values is None: raise ValueError("`parameter_values` argument is required") - if "Resources" not in sam_template or not isinstance(sam_template["Resources"], dict) or not sam_template["Resources"]: + if ("Resources" not in sam_template or not isinstance(sam_template["Resources"], dict) or not + sam_template["Resources"]): raise InvalidDocumentException( [InvalidTemplateException("'Resources' section is required")]) - validation_errors = SamTemplateValidator.validate(sam_template) - # has_errors = len(validation_errors) - - # if has_errors: - # NOTE: eventually we will throw on invalid schema - # raise InvalidDocumentException([InvalidTemplateException(validation_errors)]) - # logging.warning( - # "JSON_VALIDATION_WARNING: {0}".format(validation_errors)) + SamTemplateValidator.validate(sam_template) diff --git a/samtranslator/plugins/__init__.py b/samtranslator/plugins/__init__.py index 63d4c09c5e..54bdc61c8a 100644 --- a/samtranslator/plugins/__init__.py +++ b/samtranslator/plugins/__init__.py @@ -3,6 +3,7 @@ from samtranslator.model.exceptions import InvalidResourceException from enum import Enum + class SamPlugins(object): """ Class providing support for arbitrary plugins that can extend core SAM translator in interesting ways. @@ -27,7 +28,8 @@ class SamPlugins(object): ## Plugin Implementation ### Defining a plugin - A plugin is a subclass of `BasePlugin` that implements one or more methods capable of processing the life cycle events. + A plugin is a subclass of `BasePlugin` that implements one or more methods capable of processing the life cycle + events. These methods have a prefix `on_` followed by the name of the life cycle event. For example, to handle `before_transform_resource` event, implement a method called `on_before_transform_resource`. We call these methods as "hooks" which are methods capable of handling this event. @@ -37,7 +39,8 @@ class SamPlugins(object): `BasePlugin` class for detailed description of the method signature ### Raising validation errors - Plugins must raise an `samtranslator.model.exception.InvalidResourceException` when the input SAM template does not conform to the expectation + Plugins must raise an `samtranslator.model.exception.InvalidResourceException` when the input SAM template does + not conform to the expectation set by the plugin. SAM translator will convert this into a nice error message and display to the user. """ @@ -63,7 +66,8 @@ def register(self, plugin): Register a plugin. New plugins are added to the end of the plugins list. :param samtranslator.plugins.BasePlugin plugin: Instance/subclass of BasePlugin class that implements hooks - :raises ValueError: If plugin is not an instance of samtranslator.plugins.BasePlugin or if it is already registered + :raises ValueError: If plugin is not an instance of samtranslator.plugins.BasePlugin or if it is already + registered :return: None """ @@ -120,7 +124,7 @@ def act(self, event, *args, **kwargs): if not hasattr(plugin, method_name): raise NameError("'{}' method is not found in the plugin with name '{}'" - .format(method_name, plugin.name)) + .format(method_name, plugin.name)) try: getattr(plugin, method_name)(*args, **kwargs) @@ -139,6 +143,7 @@ def __len__(self): """ return len(self._plugins) + class LifeCycleEvents(Enum): """ Enum of LifeCycleEvents @@ -211,7 +216,7 @@ def on_after_transform_template(self, template): """ Hook method to execute on "after_transform_template" life cycle event. Plugins may further modify the template. Warning: any changes made in this lifecycle action by a plugin will not be - validated and may cause the template to fail deployment with hard-to-understand error messages + validated and may cause the template to fail deployment with hard-to-understand error messages for customers. This method is called after the template passes all other template transform actions, right before @@ -222,4 +227,4 @@ def on_after_transform_template(self, template): :raises InvalidDocumentException: If the hook decides that the SAM template is invalid. :raises InvalidResourceException: If the hook decides that a SAM resource is invalid. """ - pass \ No newline at end of file + pass diff --git a/samtranslator/plugins/api/implicit_api_plugin.py b/samtranslator/plugins/api/implicit_api_plugin.py index c4a1dc528c..b5b61e2917 100644 --- a/samtranslator/plugins/api/implicit_api_plugin.py +++ b/samtranslator/plugins/api/implicit_api_plugin.py @@ -87,8 +87,10 @@ def _get_api_events(self, function): :param SamResource function: Function Resource object :return dict: Dictionary of API events along with any other configuration passed to it. Example: { - FooEvent: {Path: "/foo", Method: "post", RestApiId: blah, MethodSettings: {}, Cors: {}, Auth: {}}, - BarEvent: {Path: "/bar", Method: "any", MethodSettings: {}, Cors: {}, Auth: {}}" + FooEvent: {Path: "/foo", Method: "post", RestApiId: blah, MethodSettings: {}, + Cors: {}, Auth: {}}, + BarEvent: {Path: "/bar", Method: "any", MethodSettings: {}, Cors: {}, + Auth: {}}" } """ @@ -167,8 +169,8 @@ def _add_api_to_swagger(self, event_id, event_properties, template): # Make sure Swagger is valid resource = template.get(api_id) if not (resource and - isinstance(resource.properties, dict) and - SwaggerEditor.is_valid(resource.properties.get("DefinitionBody"))): + isinstance(resource.properties, dict) and + SwaggerEditor.is_valid(resource.properties.get("DefinitionBody"))): # This does not have an inline Swagger. Nothing can be done about it. return @@ -213,6 +215,7 @@ def _maybe_remove_implicit_api(self, template): else: template.delete(self.implicit_api_logical_id) + class ImplicitApiResource(SamResource): """ Returns a AWS::Serverless::Api resource representing the Implicit APIs. The returned resource includes diff --git a/samtranslator/plugins/application/serverless_app_plugin.py b/samtranslator/plugins/application/serverless_app_plugin.py index 70c3b33059..420aa72f75 100644 --- a/samtranslator/plugins/application/serverless_app_plugin.py +++ b/samtranslator/plugins/application/serverless_app_plugin.py @@ -3,7 +3,7 @@ import logging from time import sleep, time -from samtranslator.model.exceptions import InvalidResourceException, InvalidDocumentException +from samtranslator.model.exceptions import InvalidResourceException from samtranslator.plugins import BasePlugin from samtranslator.plugins.exceptions import InvalidPluginException from samtranslator.public.sdk.resource import SamResourceType @@ -35,14 +35,13 @@ class ServerlessAppPlugin(BasePlugin): LOCATION_KEY = 'Location' TEMPLATE_URL_KEY = 'TemplateUrl' - def __init__(self, sar_client=None, wait_for_template_active_status=False, validate_only=False): """ Initialize the plugin. Explain that Validate_only uses a different API call, and does not produce a valid template. :param boto3.client sar_client: The boto3 client to use to access the Serverless Application Repository - :param bool wait_for_template_active_status: Flag to turn on the option to wait for all templates to become active + :param bool wait_for_template_active_status: Flag to wait for all templates to become active :param bool validate_only: Flag to only validate application access (uses get_application API instead) """ super(ServerlessAppPlugin, self).__init__(ServerlessAppPlugin.__name__) @@ -53,11 +52,10 @@ def __init__(self, sar_client=None, wait_for_template_active_status=False, valid self._validate_only = validate_only # make sure the flag combination makes sense - if self._validate_only == True and self._wait_for_template_active_status == True: + if self._validate_only is True and self._wait_for_template_active_status is True: message = "Cannot set both validate_only and wait_for_template_active_status flags to True." raise InvalidPluginException(ServerlessAppPlugin.__name__, message) - def on_before_transform_template(self, template_dict): """ Hook method that gets called before the SAM template is processed. @@ -94,18 +92,16 @@ def on_before_transform_template(self, template_dict): # Catch all InvalidResourceExceptions, raise those in the before_resource_transform target. self._applications[key] = e - def _can_process_application(self, app): """ Determines whether or not the on_before_transform_template event can process this application :param dict app: the application and its properties """ - return (self.LOCATION_KEY in app.properties - and isinstance(app.properties[self.LOCATION_KEY], dict) - and self.APPLICATION_ID_KEY in app.properties[self.LOCATION_KEY] - and self.SEMANTIC_VERSION_KEY in app.properties[self.LOCATION_KEY]) - + return (self.LOCATION_KEY in app.properties and + isinstance(app.properties[self.LOCATION_KEY], dict) and + self.APPLICATION_ID_KEY in app.properties[self.LOCATION_KEY] and + self.SEMANTIC_VERSION_KEY in app.properties[self.LOCATION_KEY]) def _handle_get_application_request(self, app_id, semver, key, logical_id): """ @@ -119,19 +115,17 @@ def _handle_get_application_request(self, app_id, semver, key, logical_id): :param string key: The dictionary key consisting of (ApplicationId, SemanticVersion) :param string logical_id: the logical_id of this application resource """ - get_application = lambda app_id, semver: self._sar_client.get_application( - ApplicationId=self._sanitize_sar_str_param(app_id), - SemanticVersion=self._sanitize_sar_str_param(semver) - ) + get_application = (lambda app_id, semver: self._sar_client.get_application( + ApplicationId=self._sanitize_sar_str_param(app_id), + SemanticVersion=self._sanitize_sar_str_param(semver))) try: - response = self._sar_service_call(get_application, logical_id, app_id, semver) - self._applications[key] = { 'Available' } + self._sar_service_call(get_application, logical_id, app_id, semver) + self._applications[key] = {'Available'} except EndpointConnectionError as e: # No internet connection. Don't break verification, but do show a warning. warning_message = "{}. Unable to verify access to {}/{}.".format(e, app_id, semver) logging.warning(warning_message) - self._applications[key] = { 'Unable to verify' } - + self._applications[key] = {'Unable to verify'} def _handle_create_cfn_template_request(self, app_id, semver, key, logical_id): """ @@ -142,16 +136,15 @@ def _handle_create_cfn_template_request(self, app_id, semver, key, logical_id): :param string key: The dictionary key consisting of (ApplicationId, SemanticVersion) :param string logical_id: the logical_id of this application resource """ - create_cfn_template = lambda app_id, semver: self._sar_client.create_cloud_formation_template( + create_cfn_template = (lambda app_id, semver: self._sar_client.create_cloud_formation_template( ApplicationId=self._sanitize_sar_str_param(app_id), SemanticVersion=self._sanitize_sar_str_param(semver) - ) + )) response = self._sar_service_call(create_cfn_template, logical_id, app_id, semver) self._applications[key] = response[self.TEMPLATE_URL_KEY] if response['Status'] != "ACTIVE": self._in_progress_templates.append((response[self.APPLICATION_ID_KEY], response['TemplateId'])) - def _sanitize_sar_str_param(self, param): """ Sanitize SAR API parameter expected to be a string. @@ -167,7 +160,6 @@ def _sanitize_sar_str_param(self, param): return None return str(param) - def on_before_transform_resource(self, logical_id, resource_type, resource_properties): """ Hook method that gets called before "each" SAM resource gets processed @@ -192,7 +184,8 @@ def on_before_transform_resource(self, logical_id, resource_type, resource_prope return # If it is a dictionary, check for other required parameters - self._check_for_dictionary_key(logical_id, resource_properties[self.LOCATION_KEY], [self.APPLICATION_ID_KEY, self.SEMANTIC_VERSION_KEY]) + self._check_for_dictionary_key(logical_id, resource_properties[self.LOCATION_KEY], + [self.APPLICATION_ID_KEY, self.SEMANTIC_VERSION_KEY]) app_id = resource_properties[self.LOCATION_KEY].get(self.APPLICATION_ID_KEY) if not app_id: @@ -221,8 +214,8 @@ def _check_for_dictionary_key(self, logical_id, dictionary, keys): """ for key in keys: if key not in dictionary: - raise InvalidResourceException(logical_id, 'Resource is missing the required [{}] property.'.format(key)) - + raise InvalidResourceException(logical_id, 'Resource is missing the required [{}] ' + 'property.'.format(key)) def on_after_transform_template(self, template): """ @@ -241,10 +234,10 @@ def on_after_transform_template(self, template): # Check each resource to make sure it's active for application_id, template_id in temp: - get_cfn_template = lambda application_id, template_id: self._sar_client.get_cloud_formation_template( - ApplicationId=self._sanitize_sar_str_param(application_id), - TemplateId=self._sanitize_sar_str_param(template_id) - ) + get_cfn_template = (lambda application_id, template_id: + self._sar_client.get_cloud_formation_template( + ApplicationId=self._sanitize_sar_str_param(application_id), + TemplateId=self._sanitize_sar_str_param(template_id))) response = self._sar_service_call(get_cfn_template, application_id, application_id, template_id) self._handle_get_cfn_template_response(response, application_id, template_id) @@ -258,8 +251,8 @@ def on_after_transform_template(self, template): # Not all templates reached active status if len(self._in_progress_templates) != 0: application_ids = [items[0] for items in self._in_progress_templates] - raise InvalidResourceException(application_ids, "Timed out waiting for nested stack templates to reach ACTIVE status.") - + raise InvalidResourceException(application_ids, "Timed out waiting for nested stack templates " + "to reach ACTIVE status.") def _handle_get_cfn_template_response(self, response, application_id, template_id): """ @@ -273,11 +266,11 @@ def _handle_get_cfn_template_response(self, response, application_id, template_i if status != "ACTIVE": # Other options are PREPARING and EXPIRED. if status == 'EXPIRED': - message = "Template for {} with id {} returned status: {}. Cannot access an expired template.".format(application_id, template_id, status) + message = ("Template for {} with id {} returned status: {}. Cannot access an expired " + "template.".format(application_id, template_id, status)) raise InvalidResourceException(application_id, message) self._in_progress_templates.append((application_id, template_id)) - def _sar_service_call(self, service_call_lambda, logical_id, *args): """ Handles service calls and exception management for service calls @@ -293,14 +286,13 @@ def _sar_service_call(self, service_call_lambda, logical_id, *args): return response except ClientError as e: error_code = e.response['Error']['Code'] - if error_code in ('AccessDeniedException','NotFoundException'): + if error_code in ('AccessDeniedException', 'NotFoundException'): raise InvalidResourceException(logical_id, e.response['Error']['Message']) # 'ForbiddenException'- SAR rejects connection logging.exception(e) raise e - def _resource_is_supported(self, resource_type): """ Is this resource supported by this plugin? diff --git a/samtranslator/plugins/exceptions.py b/samtranslator/plugins/exceptions.py index 4d4dc6c300..0bb65c893e 100644 --- a/samtranslator/plugins/exceptions.py +++ b/samtranslator/plugins/exceptions.py @@ -8,7 +8,7 @@ class InvalidPluginException(Exception): def __init__(self, plugin_name, message): self._plugin_name = plugin_name self._message = message - + @property def message(self): - return 'The {} plugin is invalid. {}'.format(self._plugin_name, self._message) \ No newline at end of file + return 'The {} plugin is invalid. {}'.format(self._plugin_name, self._message) diff --git a/samtranslator/plugins/globals/globals.py b/samtranslator/plugins/globals/globals.py index 277a0dfbec..5132431b74 100644 --- a/samtranslator/plugins/globals/globals.py +++ b/samtranslator/plugins/globals/globals.py @@ -62,7 +62,8 @@ def __init__(self, template): :param dict template: SAM template to be parsed """ - self.supported_resource_section_names = [x.replace(self._RESOURCE_PREFIX, "") for x in self.supported_properties.keys()] + self.supported_resource_section_names = ([x.replace(self._RESOURCE_PREFIX, "") + for x in self.supported_properties.keys()]) # Sort the names for stability in list ordering self.supported_resource_section_names.sort() @@ -113,17 +114,18 @@ def _parse(self, globals_dict): globals = {} if not isinstance(globals_dict, dict): - raise InvalidGlobalsSectionException(self._KEYWORD, "It must be a non-empty dictionary".format(self._KEYWORD)) + raise InvalidGlobalsSectionException(self._KEYWORD, + "It must be a non-empty dictionary".format(self._KEYWORD)) for section_name, properties in globals_dict.items(): resource_type = self._make_resource_type(section_name) if resource_type not in self.supported_properties: raise InvalidGlobalsSectionException(self._KEYWORD, - "'{section}' is not supported. " - "Must be one of the following values - {supported}" - .format(section=section_name, - supported=self.supported_resource_section_names)) + "'{section}' is not supported. " + "Must be one of the following values - {supported}" + .format(section=section_name, + supported=self.supported_resource_section_names)) if not isinstance(properties, dict): raise InvalidGlobalsSectionException(self._KEYWORD, "Value of ${section} must be a dictionary") @@ -132,9 +134,9 @@ def _parse(self, globals_dict): supported = self.supported_properties[resource_type] if key not in supported: raise InvalidGlobalsSectionException(self._KEYWORD, - "'{key}' is not a supported property of '{section}'. " - "Must be one of the following values - {supported}" - .format(key=key, section=section_name, supported=supported)) + "'{key}' is not a supported property of '{section}'. " + "Must be one of the following values - {supported}" + .format(key=key, section=section_name, supported=supported)) # Store all Global properties in a map with key being the AWS::Serverless::* resource type globals[resource_type] = GlobalProperties(properties) diff --git a/samtranslator/plugins/policies/policy_templates_plugin.py b/samtranslator/plugins/policies/policy_templates_plugin.py index 65c5653a9a..9ae515ff2c 100644 --- a/samtranslator/plugins/policies/policy_templates_plugin.py +++ b/samtranslator/plugins/policies/policy_templates_plugin.py @@ -3,6 +3,7 @@ from samtranslator.model.exceptions import InvalidResourceException from samtranslator.policy_template_processor.exceptions import InsufficientParameterValues, InvalidParameterValues + class PolicyTemplatesForFunctionPlugin(BasePlugin): """ Use this plugin to allow the usage of Policy Templates in `Policies` section of AWS::Serverless::Function resource. diff --git a/samtranslator/policy_template_processor/exceptions.py b/samtranslator/policy_template_processor/exceptions.py index 634e8bd0ea..8a3de49b84 100644 --- a/samtranslator/policy_template_processor/exceptions.py +++ b/samtranslator/policy_template_processor/exceptions.py @@ -1,4 +1,5 @@ + class TemplateNotFoundException(Exception): """ Exception raised when a template with given name is not found @@ -6,6 +7,7 @@ class TemplateNotFoundException(Exception): def __init__(self, template_name): super(TemplateNotFoundException, self).__init__("Template with name '{}' is not found".format(template_name)) + class InsufficientParameterValues(Exception): """ Exception raised when not every parameter in the template is given a value. @@ -13,6 +15,7 @@ class InsufficientParameterValues(Exception): def __init__(self, message): super(InsufficientParameterValues, self).__init__(message) + class InvalidParameterValues(Exception): """ Exception raised when parameter values passed to this template is invalid diff --git a/samtranslator/policy_template_processor/processor.py b/samtranslator/policy_template_processor/processor.py index 54168f0e6e..2b51ddad26 100644 --- a/samtranslator/policy_template_processor/processor.py +++ b/samtranslator/policy_template_processor/processor.py @@ -6,6 +6,7 @@ from samtranslator.policy_template_processor.template import Template from samtranslator.policy_template_processor.exceptions import TemplateNotFoundException + class PolicyTemplatesProcessor(object): """ Policy templates are equivalents of managed policies that can be customized with specific resource name or ARNs. diff --git a/samtranslator/policy_template_processor/template.py b/samtranslator/policy_template_processor/template.py index 973c3b0890..f84cf3445f 100644 --- a/samtranslator/policy_template_processor/template.py +++ b/samtranslator/policy_template_processor/template.py @@ -4,6 +4,7 @@ from samtranslator.intrinsics.actions import RefAction from samtranslator.policy_template_processor.exceptions import InsufficientParameterValues, InvalidParameterValues + class Template(object): """ Class representing a single policy template. It includes the name, parameters and template dictionary. @@ -33,7 +34,8 @@ def to_statement(self, parameter_values): :param dict parameter_values: Dict containing values for each parameter defined in the template :return dict: Dictionary containing policy statement - :raises InvalidParameterValues: If parameter values is not a valid dictionary or does not contain values for all parameters + :raises InvalidParameterValues: If parameter values is not a valid dictionary or does not contain values + for all parameters :raises InsufficientParameterValues: If the parameter values don't have values for all required parameters """ diff --git a/samtranslator/sdk/resource.py b/samtranslator/sdk/resource.py index 9b786b6d9f..94e70e2b3c 100644 --- a/samtranslator/sdk/resource.py +++ b/samtranslator/sdk/resource.py @@ -1,5 +1,6 @@ from enum import Enum + class SamResource(object): """ Class representing a SAM resource. It is designed to make minimal assumptions about the resource structure. diff --git a/samtranslator/sdk/template.py b/samtranslator/sdk/template.py index c8136ee638..4db2d5126a 100644 --- a/samtranslator/sdk/template.py +++ b/samtranslator/sdk/template.py @@ -4,6 +4,7 @@ Classes representing SAM template and resources. """ + class SamTemplate(object): """ Class representing the SAM template diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py index 65d4ed9c91..bc7a5e54f4 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -16,7 +16,6 @@ class SwaggerEditor(object): _X_APIGW_INTEGRATION = 'x-amazon-apigateway-integration' _X_ANY_METHOD = 'x-amazon-apigateway-any-method' - def __init__(self, doc): """ Initialize the class with a swagger dictionary. This class creates a copy of the Swagger and performs all @@ -103,7 +102,7 @@ def add_lambda_integration(self, path, method, integration_uri): 'type': 'aws_proxy', 'httpMethod': 'POST', 'uri': integration_uri - } + } # If 'responses' key is *not* present, add it with an empty dict as value self.paths[path][method].setdefault('responses', {}) @@ -196,7 +195,7 @@ def _options_method_response_for_cors(self, allowed_origins, allowed_headers=Non ALLOW_METHODS = "Access-Control-Allow-Methods" MAX_AGE = "Access-Control-Max-Age" ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials" - HEADER_RESPONSE = lambda x: "method.response.header."+x + HEADER_RESPONSE = (lambda x: "method.response.header." + x) response_parameters = { # AllowedOrigin is always required @@ -284,7 +283,7 @@ def _make_cors_allowed_methods_for_path(self, path): allow_methods = all_http_methods else: allow_methods = methods - allow_methods.append("options") # Always add Options to the CORS methods response + allow_methods.append("options") # Always add Options to the CORS methods response # Clean up the result: # @@ -317,11 +316,13 @@ def set_path_default_authorizer(self, path, default_authorizer, authorizers): was defined at the Function/Path/Method level :param string path: Path name - :param string default_authorizer: Name of the authorizer to use as the default. Must be a key in the authorizers param. + :param string default_authorizer: Name of the authorizer to use as the default. Must be a key in the + authorizers param. :param list authorizers: List of Authorizer configurations defined on the related Api. """ for method_name, method in self.paths[path].items(): - self.set_method_authorizer(path, method_name, default_authorizer, authorizers, default_authorizer=default_authorizer, is_default=True) + self.set_method_authorizer(path, method_name, default_authorizer, authorizers, + default_authorizer=default_authorizer, is_default=True) def add_auth_to_method(self, path, method_name, auth, api): """ @@ -346,7 +347,8 @@ def add_auth_to_method(self, path, method_name, auth, api): def set_method_authorizer(self, path, method_name, authorizer_name, authorizers, default_authorizer, is_default=False): normalized_method_name = self._normalize_method_name(method_name) - existing_security = self.paths[path][normalized_method_name].get('security', []) # TEST: [{'sigv4': []}, {'api_key': []}]) + existing_security = self.paths[path][normalized_method_name].get('security', []) + # TEST: [{'sigv4': []}, {'api_key': []}]) authorizer_names = set(authorizers.keys()) existing_non_authorizer_security = [] existing_authorizer_security = [] @@ -427,9 +429,9 @@ def is_valid(data): :return: True, if data is a Swagger """ return bool(data) and \ - isinstance(data, dict) and \ - bool(data.get("swagger")) and \ - isinstance(data.get('paths'), dict) + isinstance(data, dict) and \ + bool(data.get("swagger")) and \ + isinstance(data.get('paths'), dict) @staticmethod def gen_skeleton(): diff --git a/samtranslator/translator/arn_generator.py b/samtranslator/translator/arn_generator.py index db30057868..a64824b25b 100644 --- a/samtranslator/translator/arn_generator.py +++ b/samtranslator/translator/arn_generator.py @@ -1,5 +1,6 @@ import boto3 + class ArnGenerator(object): @classmethod diff --git a/samtranslator/translator/logical_id_generator.py b/samtranslator/translator/logical_id_generator.py index 30022c95ef..40d87c98f6 100644 --- a/samtranslator/translator/logical_id_generator.py +++ b/samtranslator/translator/logical_id_generator.py @@ -25,7 +25,6 @@ def __init__(self, prefix, data_obj=None): self._prefix = prefix self.data_str = data_str - def gen(self): """ Generate stable LogicalIds based on the prefix and given data. This method ensures that the logicalId is diff --git a/samtranslator/translator/translator.py b/samtranslator/translator/translator.py index 0dbb8372ca..f8b9618a4c 100644 --- a/samtranslator/translator/translator.py +++ b/samtranslator/translator/translator.py @@ -3,8 +3,8 @@ from samtranslator.model import ResourceTypeResolver, sam_resources from samtranslator.translator.verify_logical_id import verify_unique_logical_id from samtranslator.model.preferences.deployment_preference_collection import DeploymentPreferenceCollection -from samtranslator.model.exceptions import InvalidDocumentException, InvalidResourceException, DuplicateLogicalIdException, \ - InvalidEventException +from samtranslator.model.exceptions import (InvalidDocumentException, InvalidResourceException, + DuplicateLogicalIdException, InvalidEventException) from samtranslator.intrinsics.resolver import IntrinsicsResolver from samtranslator.intrinsics.resource_refs import SupportedResourceReferences from samtranslator.plugins.api.default_definition_body_plugin import DefaultDefinitionBodyPlugin @@ -15,6 +15,7 @@ from samtranslator.plugins.policies.policy_templates_plugin import PolicyTemplatesForFunctionPlugin from samtranslator.policy_template_processor.processor import PolicyTemplatesProcessor + class Translator: """Translates SAM templates into CloudFormation templates """ @@ -22,8 +23,8 @@ def __init__(self, managed_policy_map, sam_parser, plugins=None): """ :param dict managed_policy_map: Map of managed policy names to the ARNs :param sam_parser: Instance of a SAM Parser - :param list of samtranslator.plugins.BasePlugin plugins: List of plugins to be installed in the translator, in addition - to the default ones. + :param list of samtranslator.plugins.BasePlugin plugins: List of plugins to be installed in the translator, + in addition to the default ones. """ self.managed_policy_map = managed_policy_map self.plugins = plugins @@ -36,10 +37,10 @@ def translate(self, sam_template, parameter_values): :param dict sam_template: the SAM manifest, as loaded by json.load() or yaml.load(), or as provided by \ CloudFormation transforms. :param dict parameter_values: Map of template parameter names to their values. It is a required parameter that - should at least be an empty map. By providing an empty map, the caller explicitly opts-into the idea that - some functionality that relies on resolving parameter references might not work as expected - (ex: auto-creating new Lambda Version when CodeUri contains reference to template parameter). This is why - this parameter is required + should at least be an empty map. By providing an empty map, the caller explicitly opts-into the idea + that some functionality that relies on resolving parameter references might not work as expected + (ex: auto-creating new Lambda Version when CodeUri contains reference to template parameter). This is + why this parameter is required :returns: a copy of the template with SAM resources replaced with the corresponding CloudFormation, which may \ be dumped into a valid CloudFormation JSON or YAML template @@ -154,7 +155,8 @@ def _get_resources_to_iterate(self, sam_template, macro_resolver): return functions + apis + others - # Ideally this should belong to a separate class called "Parameters" or something that knows how to manage parameters. An instance of this class should be passed as input to the Translate class. + # Ideally this should belong to a separate class called "Parameters" or something that knows how to manage + # parameters. An instance of this class should be passed as input to the Translate class. def _add_default_parameter_values(self, sam_template, parameter_values): """ Method to read default values for template parameters and merge with user supplied values. @@ -203,6 +205,7 @@ def _add_default_parameter_values(self, sam_template, parameter_values): return default_values + def prepare_plugins(plugins): """ Creates & returns a plugins object with the given list of plugins installed. In addition to the given plugins, diff --git a/samtranslator/validator/validator.py b/samtranslator/validator/validator.py index 69f021af85..14d36cbd23 100644 --- a/samtranslator/validator/validator.py +++ b/samtranslator/validator/validator.py @@ -31,7 +31,7 @@ def validate(template_dict, schema=None): # Swallowing expected exception here as our caller is expecting validation errors and # not the valiation exception itself pass - + return validation_errors @staticmethod diff --git a/samtranslator/yaml_helper.py b/samtranslator/yaml_helper.py index 9c4fa6d8ab..67c958b4d3 100644 --- a/samtranslator/yaml_helper.py +++ b/samtranslator/yaml_helper.py @@ -4,6 +4,8 @@ # This helper copied almost entirely from # https://github.com/aws/aws-cli/blob/develop/awscli/customizations/cloudformation/yamlhelper.py + + def yaml_parse(yamlstr): """Parse a yaml string""" yaml.SafeLoader.add_multi_constructor(