def test_global_parameter_overrides_can_be_passed_to_child_stacks( self, resource_type, location_property_name, child_location, child_location_path ): template_file = "somedir/template.yaml" template = { "Resources": { "ChildStack": { "Type": resource_type, "Properties": {location_property_name: child_location}, } } } self.get_template_data_mock.side_effect = lambda t: { template_file: template, child_location_path: LEAF_TEMPLATE, }.get(t) global_parameter_overrides = {"AWS::Region": "custom_region"} stacks = SamLocalStackProvider.get_stacks( template_file, "", "", parameter_overrides=None, global_parameter_overrides=global_parameter_overrides ) self.assertListEqual( stacks, [ Stack("", "", template_file, global_parameter_overrides, template), Stack("", "ChildStack", child_location_path, global_parameter_overrides, LEAF_TEMPLATE), ], )
def test_sam_nested_stack_should_be_extracted(self, resource_type, location_property_name, child_location, child_location_path): template = { "Resources": { "ChildStack": { "Type": resource_type, "Properties": { location_property_name: child_location }, } } } self.get_template_data_mock.side_effect = lambda t: { self.template_file: template, child_location_path: LEAF_TEMPLATE, }.get(t) with patch.dict( os.environ, {SamLocalStackProvider.ENV_SAM_CLI_ENABLE_NESTED_STACK: "1"}): stacks = SamLocalStackProvider.get_stacks( self.template_file, "", "", parameter_overrides=None, ) self.assertListEqual( stacks, [ Stack("", "", self.template_file, None, template), Stack("", "ChildStack", child_location_path, None, LEAF_TEMPLATE), ], )
def test_sam_nested_stack_should_be_extracted(self, resource_type, location_property_name, child_location, child_location_path): template = { "Resources": { "ChildStack": { "Type": resource_type, "Properties": { location_property_name: child_location }, } } } self.get_template_data_mock.side_effect = lambda t: { self.template_file: template, child_location_path: LEAF_TEMPLATE, }.get(t) stacks, remote_stack_full_paths = SamLocalStackProvider.get_stacks( self.template_file, "", "", parameter_overrides=None, ) self.assertListEqual( stacks, [ Stack("", "", self.template_file, {}, template), Stack("", "ChildStack", child_location_path, {}, LEAF_TEMPLATE), ], ) self.assertFalse(remote_stack_full_paths)
def test_sam_nested_stack_template_path_can_be_resolved_if_root_template_is_not_in_working_dir( self, resource_type, location_property_name, child_location, child_location_path): template_file = "somedir/template.yaml" template = { "Resources": { "ChildStack": { "Type": resource_type, "Properties": { location_property_name: child_location }, } } } self.get_template_data_mock.side_effect = lambda t: { template_file: template, child_location_path: LEAF_TEMPLATE, }.get(t) with patch.dict( os.environ, {SamLocalStackProvider.ENV_SAM_CLI_ENABLE_NESTED_STACK: "1"}): stacks = SamLocalStackProvider.get_stacks( template_file, "", "", parameter_overrides=None, ) self.assertListEqual( stacks, [ Stack("", "", template_file, None, template), Stack("", "ChildStack", child_location_path, None, LEAF_TEMPLATE), ], )
def test_sam_nested_stack_template_path_can_be_resolved_if_root_template_is_not_in_working_dir( self, resource_type, location_property_name, child_location, child_location_path): template_file = "somedir/template.yaml" template = { "Resources": { "ChildStack": { "Type": resource_type, "Properties": { location_property_name: child_location }, } } } self.get_template_data_mock.side_effect = lambda t: { template_file: template, child_location_path: LEAF_TEMPLATE, }.get(t) stacks, remote_stack_full_paths = SamLocalStackProvider.get_stacks( template_file, "", "", parameter_overrides=None, ) self.assertListEqual( stacks, [ Stack("", "", template_file, {}, template), Stack("", "ChildStack", child_location_path, {}, LEAF_TEMPLATE), ], ) self.assertFalse(remote_stack_full_paths)
def setUp(self): self.parameter_overrides = {} root_stack = Stack("", "", "template.yaml", self.parameter_overrides, self.TEMPLATE) child_stack = Stack("", "ChildStack", "./child/template.yaml", None, self.CHILD_TEMPLATE) with patch("samcli.lib.providers.sam_stack_provider.get_template_data") as get_template_data_mock: get_template_data_mock.side_effect = lambda t: { "template.yaml": self.TEMPLATE, "./child/template.yaml": self.CHILD_TEMPLATE, } self.provider = SamFunctionProvider([root_stack, child_stack])
def _convert_sam_application_resource( template_directory: str, stack_path: str, name: str, resource_properties: Dict ) -> Optional[Stack]: location = resource_properties.get("Location") if isinstance(location, dict): LOG.warning( "Nested application '%s' has specified an application published to the " "AWS Serverless Application Repository which is unsupported. " "Skipping resources inside this nested application.", name, ) return None location = cast(str, location) if SamLocalStackProvider.is_remote_url(location): LOG.warning( "Nested application '%s' has specified S3 location for Location which is unsupported. " "Skipping resources inside this nested application.", name, ) return None if location.startswith("file://"): location = unquote(urlparse(location).path) elif not os.path.isabs(location): location = os.path.join(template_directory, os.path.relpath(location)) return Stack( parent_stack_path=stack_path, name=name, location=location, parameters=resource_properties.get("Parameters"), template_dict=get_template_data(location), )
def _convert_cfn_stack_resource( template_file: str, stack_path: str, name: str, resource_properties: Dict, global_parameter_overrides: Optional[Dict] = None, ) -> Optional[Stack]: template_url = resource_properties.get("TemplateURL", "") if SamLocalStackProvider.is_remote_url(template_url): raise RemoteStackLocationNotSupported() if template_url.startswith("file://"): template_url = unquote(urlparse(template_url).path) else: template_url = SamLocalStackProvider.normalize_resource_path( template_file, template_url) return Stack( parent_stack_path=stack_path, name=name, location=template_url, parameters=SamLocalStackProvider.merge_parameter_overrides( resource_properties.get("Parameters", {}), global_parameter_overrides), template_dict=get_template_data(template_url), )
def _convert_sam_application_resource( template_file: str, stack_path: str, name: str, resource_properties: Dict, global_parameter_overrides: Optional[Dict] = None, ) -> Optional[Stack]: location = resource_properties.get("Location") if isinstance(location, dict): raise RemoteStackLocationNotSupported() location = cast(str, location) if SamLocalStackProvider.is_remote_url(location): raise RemoteStackLocationNotSupported() if location.startswith("file://"): location = unquote(urlparse(location).path) else: location = SamLocalStackProvider.normalize_resource_path( template_file, location) return Stack( parent_stack_path=stack_path, name=name, location=location, parameters=SamLocalStackProvider.merge_parameter_overrides( resource_properties.get("Parameters", {}), global_parameter_overrides), template_dict=get_template_data(location), )
def get_stacks( template_file: str, stack_path: str = "", name: str = "", parameter_overrides: Optional[Dict] = None, ) -> List[Stack]: template_dict = get_template_data(template_file) stacks = [Stack(stack_path, name, template_file, parameter_overrides, template_dict)] # Note(xinhol): recursive get_stacks is only enabled in tests by env var SAM_CLI_ENABLE_NESTED_STACK. # We will remove this env var and make this method recursive by default # for nested stack support in the future. if not os.environ.get(SamLocalStackProvider.ENV_SAM_CLI_ENABLE_NESTED_STACK, False): return stacks current = SamLocalStackProvider(template_file, stack_path, template_dict, parameter_overrides) for child_stack in current.get_all(): stacks.extend( SamLocalStackProvider.get_stacks( child_stack.location, os.path.join(stack_path, name), child_stack.name, child_stack.parameters, ) ) return stacks
def test_remote_stack_is_skipped(self, resource_type, location_property_name): template = { "Resources": { "ChildStack": { "Type": resource_type, "Properties": { location_property_name: "s3://bucket/key" }, } } } self.get_template_data_mock.side_effect = lambda t: { self.template_file: template, }.get(t) with patch.dict( os.environ, {SamLocalStackProvider.ENV_SAM_CLI_ENABLE_NESTED_STACK: "1"}): stacks = SamLocalStackProvider.get_stacks( self.template_file, "", "", parameter_overrides=None, ) self.assertListEqual( stacks, [ Stack("", "", self.template_file, None, template), ], )
def test_remote_stack_is_skipped(self, resource_type, location_property_name): template = { "Resources": { "ChildStack": { "Type": resource_type, "Properties": { location_property_name: "s3://bucket/key" }, } } } self.get_template_data_mock.side_effect = lambda t: { self.template_file: template, }.get(t) stacks, remote_stack_full_paths = SamLocalStackProvider.get_stacks( self.template_file, "", "", parameter_overrides=None, ) self.assertListEqual( stacks, [ Stack("", "", self.template_file, {}, template), ], ) self.assertEqual(remote_stack_full_paths, ["ChildStack"])
def _convert_cfn_stack_resource( template_directory: str, stack_path: str, name: str, resource_properties: Dict, global_parameter_overrides: Optional[Dict] = None, ) -> Optional[Stack]: template_url = resource_properties.get("TemplateURL", "") if SamLocalStackProvider.is_remote_url(template_url): LOG.warning( "Nested stack '%s' has specified S3 location for Location which is unsupported. " "Skipping resources inside this nested stack.", name, ) return None if template_url.startswith("file://"): template_url = unquote(urlparse(template_url).path) elif not os.path.isabs(template_url): template_url = os.path.join(template_directory, os.path.relpath(template_url)) return Stack( parent_stack_path=stack_path, name=name, location=template_url, parameters=SamLocalStackProvider.merge_parameter_overrides( resource_properties.get("Parameters", {}), global_parameter_overrides), template_dict=get_template_data(template_url), )
def test_sam_deep_nested_stack(self): child_template_file = "./child.yaml" grand_child_template_file = "./grand-child.yaml" template = { "Resources": { "ChildStack": { "Type": AWS_SERVERLESS_APPLICATION, "Properties": { "Location": child_template_file }, } } } child_template = { "Resources": { "GrandChildStack": { "Type": AWS_SERVERLESS_APPLICATION, "Properties": { "Location": grand_child_template_file }, } } } self.get_template_data_mock.side_effect = lambda t: { self.template_file: template, child_template_file: child_template, grand_child_template_file: LEAF_TEMPLATE, }.get(t) with patch.dict( os.environ, {SamLocalStackProvider.ENV_SAM_CLI_ENABLE_NESTED_STACK: "1"}): stacks = SamLocalStackProvider.get_stacks( self.template_file, "", "", parameter_overrides=None, ) self.assertListEqual( stacks, [ Stack("", "", self.template_file, None, template), Stack("", "ChildStack", child_template_file, None, child_template), Stack("ChildStack", "GrandChildStack", grand_child_template_file, None, LEAF_TEMPLATE), ], )
def test_sam_deep_nested_stack(self): child_template_file = "child.yaml" grand_child_template_file = "grand-child.yaml" template = { "Resources": { "ChildStack": { "Type": AWS_SERVERLESS_APPLICATION, "Properties": { "Location": child_template_file }, } } } child_template = { "Resources": { "GrandChildStack": { "Type": AWS_SERVERLESS_APPLICATION, "Properties": { "Location": grand_child_template_file }, } } } self.get_template_data_mock.side_effect = lambda t: { self.template_file: template, child_template_file: child_template, grand_child_template_file: LEAF_TEMPLATE, }.get(t) stacks, remote_stack_full_paths = SamLocalStackProvider.get_stacks( self.template_file, "", "", parameter_overrides=None, ) self.assertListEqual( stacks, [ Stack("", "", self.template_file, {}, template), Stack("", "ChildStack", child_template_file, {}, child_template), Stack("ChildStack", "GrandChildStack", grand_child_template_file, {}, LEAF_TEMPLATE), ], ) self.assertFalse(remote_stack_full_paths)
def get_stacks( template_file: str, stack_path: str = "", name: str = "", parameter_overrides: Optional[Dict] = None, global_parameter_overrides: Optional[Dict] = None, ) -> List[Stack]: """ Recursively extract stacks from a template file. Parameters ---------- template_file: str the file path of the template to extract stacks from stack_path: str the stack path of the parent stack, for root stack, it is "" name: str the name of the stack associated with the template_file, for root stack, it is "" parameter_overrides: Optional[Dict] Optional dictionary of values for SAM template parameters that might want to get substituted within the template global_parameter_overrides: Optional[Dict] Optional dictionary of values for SAM template global parameters that might want to get substituted within the template and its child templates Returns ------- stacks: List[Stack] The list of stacks extracted from template_file """ template_dict = get_template_data(template_file) stacks = [ Stack( stack_path, name, template_file, SamLocalStackProvider.merge_parameter_overrides( parameter_overrides, global_parameter_overrides), template_dict, ) ] current = SamLocalStackProvider(template_file, stack_path, template_dict, parameter_overrides, global_parameter_overrides) for child_stack in current.get_all(): stacks.extend( SamLocalStackProvider.get_stacks( child_stack.location, os.path.join(stack_path, name), child_stack.name, child_stack.parameters, global_parameter_overrides, )) return stacks
def test_auth_per_resource_auth_on_event_properties(self): event_properties = self.template_dict["Resources"][ "HelloWorldFunction"]["Properties"]["Events"]["HelloWorld"][ "Properties"] # setup authorizer and auth explicitly on the event properties. event_properties["Auth"] = {"ApiKeyRequired": True, "Authorizer": None} self.template_dict["Resources"]["HelloWorldFunction"]["Properties"][ "Events"]["HelloWorld"]["Properties"] = event_properties _auth_per_resource = auth_per_resource( [Stack("", "", "", {}, self.template_dict)]) self.assertEqual(_auth_per_resource, [("HelloWorldFunction", True)])
def test_auth_per_resource_defined_on_api_resource(self): self.template_dict["Resources"]["HelloWorldApi"] = OrderedDict([ ("Type", "AWS::Serverless::Api"), ("Properties", OrderedDict([("StageName", "Prod"), ("Auth", OrderedDict([("ApiKeyRequired", True)]))])), ]) # setup the lambda function with a restapiId which has Auth defined. self.template_dict["Resources"]["HelloWorldFunction"]["Properties"][ "Events"]["HelloWorld"]["Properties"]["RestApiId"] = { "Ref": "HelloWorldApi" } _auth_per_resource = auth_per_resource( [Stack("", "", "", {}, self.template_dict)]) self.assertEqual(_auth_per_resource, [("HelloWorldFunction", True)])
def test_auth_supplied_via_definition_body_uri_instrinsics_involved_unable_to_determine( self): self.template_dict["Resources"]["HelloWorldApi"] = OrderedDict([ ("Type", "AWS::Serverless::Api"), ( "Properties", OrderedDict([ ("StageName", "Prod"), ( "DefinitionBody", { "swagger": "2.0", "info": { "version": "1.0", "title": "local" }, "paths": { "/hello": { "Fn::If": [ "Condition", { "get": {} }, { "Ref": "AWS::NoValue" } ] } }, }, ), ]), ), ]) # setup the lambda function with a restapiId which has definitionBody defined with auth on the route. self.template_dict["Resources"]["HelloWorldFunction"]["Properties"][ "Events"]["HelloWorld"]["Properties"]["RestApiId"] = { "Ref": "HelloWorldApi" } _auth_per_resource = auth_per_resource( [Stack("", "", "", {}, self.template_dict)]) self.assertEqual(_auth_per_resource, [("HelloWorldFunction", False)])
def _convert_cfn_stack_resource( stack_path: str, name: str, resource_properties: Dict) -> Optional[Stack]: template_url = resource_properties.get("TemplateURL", "") if SamLocalStackProvider.is_remote_url(template_url): LOG.warning( "Nested stack '%s' has specified S3 location for Location which is unsupported. " "Skipping resources inside this nested stack.", name, ) return None if template_url.startswith("file://"): template_url = unquote(urlparse(template_url).path) return Stack( parent_stack_path=stack_path, name=name, location=template_url, parameters=resource_properties.get("Parameters"), template_dict=get_template_data(template_url), )
def test_signer_config_per_function(self): function_name_1 = "HelloWorldFunction1" function_name_2 = "HelloWorldFunction2" layer_name = "HelloWorldFunctionLayer" template_dict = { "AWSTemplateFormatVersion": "2010-09-09", "Transform": "AWS::Serverless-2016-10-31", "Description": "\nSample SAM Template for Tests\n", "Globals": OrderedDict([("Function", OrderedDict([("Timeout", 3)]))]), "Resources": OrderedDict([ ( function_name_1, OrderedDict([ ("Type", "AWS::Serverless::Function"), ( "Properties", OrderedDict([ ("CodeUri", "HelloWorldFunction"), ("Handler", "app.lambda_handler"), ("Runtime", "python3.7"), ("CodeSigningConfigArn", "MyCodeSigningConfigArn"), ( "Layers", [ OrderedDict([("Ref", layer_name)]), ], ), ]), ), ]), ), ( function_name_2, OrderedDict([ ("Type", "AWS::Serverless::Function"), ( "Properties", OrderedDict([ ("CodeUri", "HelloWorldFunction2"), ("Handler", "app.lambda_handler2"), ("Runtime", "python3.7"), ("CodeSigningConfigArn", "MyCodeSigningConfigArn"), ( "Layers", [ OrderedDict([("Ref", layer_name)]), ], ), ]), ), ]), ), ( layer_name, OrderedDict([ ("Type", "AWS::Serverless::LayerVersion"), ( "Properties", OrderedDict([ ("LayerName", "dependencies"), ("ContentUri", "dependencies/"), ("CompatibleRuntimes", ["python3.7"]), ]), ), ]), ), ]), } (functions_with_code_sign, layers_with_code_sign) = signer_config_per_function( [Stack("", "", "", {}, template_dict)]) self.assertEqual(functions_with_code_sign, {function_name_1, function_name_2}) self.assertEqual(layers_with_code_sign, {layer_name: {function_name_1, function_name_2}})
def test_stack_get_output_template_path(self, parent_stack_path, name, output_template_path): root_stack = Stack(parent_stack_path, name, None, None, None) self.assertEqual(root_stack.get_output_template_path("builddir"), output_template_path)
def test_auth_per_resource_no_auth(self): _auth_per_resource = auth_per_resource( [Stack("", "", "", {}, self.template_dict)]) self.assertEqual(_auth_per_resource, [("HelloWorldFunction", False)])
def test_must_set_debug_function_if_warm_containers_enabled_no_debug_function_provided_and_template_contains_one_function( self, SamFunctionProviderMock, ContainerManagerMock): function_provider = Mock() function_provider.functions = {"function_name": ANY} SamFunctionProviderMock.return_value = function_provider template_file = "template_file" env_vars_file = "env_vars_file" container_env_vars_file = "container_env_vars_file" log_file = "log_file" invoke_context = InvokeContext( template_file=template_file, function_identifier="id", env_vars_file=env_vars_file, docker_volume_basedir="volumedir", docker_network="network", log_file=log_file, skip_pull_image=True, debug_ports=[1111], debugger_path="path-to-debugger", container_env_vars_file=container_env_vars_file, debug_args="args", parameter_overrides={}, aws_region="region", aws_profile="profile", warm_container_initialization_mode=ContainersInitializationMode. EAGER.value, debug_function="", shutdown=True, ) _initialize_all_functions_containers_mock = Mock() invoke_context._initialize_all_functions_containers = _initialize_all_functions_containers_mock template_dict = "template_dict" stacks = [ Stack("", "", template_file, invoke_context.parameter_overrides, template_dict) ] invoke_context._get_stacks = Mock() invoke_context._get_stacks.return_value = stacks invoke_context._get_env_vars_value = Mock( side_effect=["Env var value", "Debug env var value"]) log_file_handle = "handle" invoke_context._setup_log_file = Mock() invoke_context._setup_log_file.return_value = log_file_handle debug_context_mock = Mock() invoke_context._get_debug_context = Mock() invoke_context._get_debug_context.return_value = debug_context_mock container_manager_mock = Mock() container_manager_mock.is_docker_reachable = True ContainerManagerMock.return_value = container_manager_mock # Call Enter method manually for testing purposes result = invoke_context.__enter__() self.assertTrue(result is invoke_context, "__enter__() must return self") self.assertEqual(invoke_context._template_dict, template_dict) self.assertEqual(invoke_context._function_provider, function_provider) self.assertEqual(invoke_context._env_vars_value, "Env var value") self.assertEqual(invoke_context._container_env_vars_value, "Debug env var value") self.assertEqual(invoke_context._log_file_handle, log_file_handle) self.assertEqual(invoke_context._debug_context, debug_context_mock) self.assertEqual(invoke_context._container_manager, container_manager_mock) self.assertEqual(invoke_context._containers_mode, ContainersMode.WARM) self.assertEqual(invoke_context._containers_initializing_mode, ContainersInitializationMode.EAGER) invoke_context._get_stacks.assert_called_once() SamFunctionProviderMock.assert_called_with(stacks) self.assertEqual(invoke_context.parameter_overrides, {"AWS::Region": "region"}) self.assertEqual(invoke_context._get_env_vars_value.call_count, 2) self.assertEqual( invoke_context._get_env_vars_value.call_args_list, [call("env_vars_file"), call("container_env_vars_file")]) invoke_context._setup_log_file.assert_called_with(log_file) invoke_context._get_debug_context.assert_called_once_with( [1111], "args", "path-to-debugger", "Debug env var value", "function_name") ContainerManagerMock.assert_called_once_with( docker_network_id="network", skip_pull_image=True, do_shutdown_event=True) _initialize_all_functions_containers_mock.assert_called_once_with()
def make_root_stack(template, parameter_overrides=None): return Stack("", "", "template.yaml", parameter_overrides, template)
def test_no_container_will_be_initialized_if_lazy_containers_is_enabled( self, SamFunctionProviderMock, ContainerManagerMock): function_provider = Mock() SamFunctionProviderMock.return_value = function_provider template_file = "template_file" env_vars_file = "env_vars_file" log_file = "log_file" invoke_context = InvokeContext( template_file=template_file, function_identifier="id", env_vars_file=env_vars_file, docker_volume_basedir="volumedir", docker_network="network", log_file=log_file, skip_pull_image=True, debug_ports=[1111], debugger_path="path-to-debugger", debug_args="args", parameter_overrides={}, aws_region="region", aws_profile="profile", warm_container_initialization_mode=ContainersInitializationMode. LAZY.value, debug_function="debug_function", shutdown=True, ) template_dict = "template_dict" stacks = [Stack("", "", template_file, Mock(), template_dict)] invoke_context._get_stacks = Mock() invoke_context._get_stacks.return_value = stacks env_vars_value = "env_vars_value" invoke_context._get_env_vars_value = Mock() invoke_context._get_env_vars_value.return_value = env_vars_value log_file_handle = "handle" invoke_context._setup_log_file = Mock() invoke_context._setup_log_file.return_value = log_file_handle debug_context_mock = Mock() invoke_context._get_debug_context = Mock() invoke_context._get_debug_context.return_value = debug_context_mock container_manager_mock = Mock() container_manager_mock.is_docker_reachable = True ContainerManagerMock.return_value = container_manager_mock # Call Enter method manually for testing purposes result = invoke_context.__enter__() self.assertTrue(result is invoke_context, "__enter__() must return self") self.assertEqual(invoke_context._function_provider, function_provider) self.assertEqual(invoke_context._env_vars_value, env_vars_value) self.assertEqual(invoke_context._log_file_handle, log_file_handle) self.assertEqual(invoke_context._debug_context, debug_context_mock) self.assertEqual(invoke_context._container_manager, container_manager_mock) self.assertEqual(invoke_context._containers_mode, ContainersMode.WARM) self.assertEqual(invoke_context._containers_initializing_mode, ContainersInitializationMode.LAZY) invoke_context._get_stacks.assert_called_once() SamFunctionProviderMock.assert_called_with(stacks) self.assertEqual(invoke_context._global_parameter_overrides, {"AWS::Region": "region"}) self.assertEqual(invoke_context._get_env_vars_value.call_count, 2) self.assertEqual(invoke_context._get_env_vars_value.call_args_list, [call(env_vars_file), call(None)]) invoke_context._setup_log_file.assert_called_with(log_file) invoke_context._get_debug_context.assert_called_once_with( [1111], "args", "path-to-debugger", "env_vars_value", "debug_function") ContainerManagerMock.assert_called_once_with( docker_network_id="network", skip_pull_image=True, do_shutdown_event=True)