def test_get_cloudformation_template_raises_exception_on_any_error( self, get_yaml_or_json_file_mock): get_yaml_or_json_file_mock.side_effect = Exception with self.assertRaises(TemplateErrorException): FileLoader.get_cloudformation_template( "s3://my-bucket/template.yml", None)
def test_handle_yaml_constructors_raises_exception_on_unknown_tag(self): loader_mock = Mock() loader_mock.construct_scalar.return_value = "myResource" node_mock = Mock(spec=yaml.ScalarNode) with self.assertRaises(CfnSphereException): FileLoader.handle_yaml_constructors(loader_mock, "!anyTag", node_mock)
def render_template(filename): check_update_available() loader = FileLoader() template = loader.get_file_from_url(filename, None) template = CloudFormationTemplateTransformer.transform_template(template) click.echo(template.get_template_json())
def test_get_yaml_or_json_file_parses_yaml_on_yml_suffix( self, get_file_mock, yaml_mock): get_file_return_value = Mock() get_file_mock.return_value = get_file_return_value FileLoader.get_yaml_or_json_file('foo.yml', 'baa') yaml_mock.load.assert_called_once_with(get_file_return_value)
def test_get_yaml_or_json_file_parses_json_on_json_suffix( self, get_file_mock, json_mock): get_file_return_value = Mock() get_file_mock.return_value = get_file_return_value FileLoader.get_yaml_or_json_file('foo.json', 'baa') json_mock.loads.assert_called_once_with(get_file_return_value, encoding="utf-8")
def render_template(template_file, confirm): if not confirm: check_update_available() loader = FileLoader() template = loader.get_file_from_url(template_file, None) template = CloudFormationTemplateTransformer.transform_template(template) click.echo(template.get_pretty_template_json())
def render_template(template_file, confirm, yes): confirm = confirm or yes if not confirm: check_update_available() loader = FileLoader() template = loader.get_cloudformation_template(template_file, None) template = CloudFormationTemplateTransformer.transform_template(template) click.echo(template.get_pretty_template_json())
def test_get_yaml_or_json_file_parses_yaml_on_yaml_suffix( self, get_file_mock, yaml_mock): get_file_return_value = Mock() get_file_mock.return_value = get_file_return_value FileLoader.get_yaml_or_json_file('foo.yaml', 'baa') if hasattr(yaml_mock, 'FullLoader'): loader = yaml_mock.FullLoader else: loader = yaml_mock.Loader yaml_mock.load.assert_called_once_with(get_file_return_value, Loader=loader)
def test_handle_yaml_constructors_converts_get_azs(self): loader_mock = Mock() loader_mock.construct_scalar.return_value = "region" node_mock = Mock(spec=yaml.ScalarNode) response = FileLoader.handle_yaml_constructors(loader_mock, "!GetAZs", node_mock) self.assertEqual({'Fn::GetAZs': "region"}, response)
def test_handle_yaml_constructors_converts_getatt(self): loader_mock = Mock() loader_mock.construct_scalar.return_value = "logicalNameOfResource.attributeName" node_mock = Mock(spec=yaml.ScalarNode) response = FileLoader.handle_yaml_constructors(loader_mock, "!GetAtt", node_mock) self.assertEqual({'Fn::GetAtt': ["logicalNameOfResource", "attributeName"]}, response)
def test_handle_yaml_constructors_converts_base64(self): loader_mock = Mock() loader_mock.construct_scalar.return_value = "myString" node_mock = Mock(spec=yaml.ScalarNode) response = FileLoader.handle_yaml_constructors(loader_mock, "!base64", node_mock) self.assertEqual({'Fn::Base64': 'myString'}, response)
def test_handle_yaml_constructors_converts_ref(self): loader_mock = Mock() loader_mock.construct_scalar.return_value = "myResource" node_mock = Mock(spec=yaml.ScalarNode) response = FileLoader.handle_yaml_constructors(loader_mock, "!ref", node_mock) self.assertEqual({'Ref': "myResource"}, response)
def test_handle_yaml_constructors_converts_select(self): loader_mock = Mock() loader_mock.construct_scalar.return_value = ["index", "list"] node_mock = Mock(spec=yaml.ScalarNode) response = FileLoader.handle_yaml_constructors(loader_mock, "!select", node_mock) self.assertEqual({'Fn::Select': ["index", "list"]}, response)
def test_handle_yaml_constructors_converts_or(self): loader_mock = Mock() loader_mock.construct_scalar.return_value = ["myCondition", "myOtherCondition"] node_mock = Mock(spec=yaml.ScalarNode) response = FileLoader.handle_yaml_constructors(loader_mock, "!or", node_mock) self.assertEqual({'Fn::Or': ["myCondition", "myOtherCondition"]}, response)
def test_handle_yaml_constructors_converts_sub(self): loader_mock = Mock() loader_mock.construct_scalar.return_value = ["string", {"key": "value"}] node_mock = Mock(spec=yaml.ScalarNode) response = FileLoader.handle_yaml_constructors(loader_mock, "!sub", node_mock) self.assertEqual({'Fn::Sub': ["string", {"key": "value"}]}, response)
def test_handle_yaml_constructors_converts_join(self): loader_mock = Mock() loader_mock.construct_scalar.return_value = ["delimiter", ["a", "b"]] node_mock = Mock(spec=yaml.ScalarNode) response = FileLoader.handle_yaml_constructors(loader_mock, "!join", node_mock) self.assertEqual({'Fn::Join': ["delimiter", ["a", "b"]]}, response)
def test_handle_yaml_constructors_converts_import_value(self): loader_mock = Mock() loader_mock.construct_scalar.return_value = "sharedValue" node_mock = Mock(spec=yaml.ScalarNode) response = FileLoader.handle_yaml_constructors(loader_mock, "!ImportValue", node_mock) self.assertEqual({'Fn::ImportValue': "sharedValue"}, response)
def test_handle_yaml_constructors_converts_equals(self): loader_mock = Mock() loader_mock.construct_scalar.return_value = ["myValue", "myOtherValue"] node_mock = Mock(spec=yaml.ScalarNode) response = FileLoader.handle_yaml_constructors(loader_mock, "!equals", node_mock) self.assertEqual({'Fn::Equals': ["myValue", "myOtherValue"]}, response)
def __init__(self, config_file=None, config_dict=None, cli_params=None, stack_name_suffix=None): self.logger = get_logger() if isinstance(config_dict, dict): self.stack_config_base_dir = None elif config_file: self.stack_config_base_dir = os.path.dirname( os.path.realpath(config_file)) config_dict = FileLoader.get_yaml_or_json_file( config_file, working_dir=os.getcwd()) else: raise InvalidConfigException( "You need to pass either config_file (path to a file) or config_dict (python dict) property" ) self.cli_params = self._parse_cli_parameters(cli_params) self.region = config_dict.get("region") self.stack_name_suffix = stack_name_suffix self.default_service_role = config_dict.get("service-role") self.default_stack_policy_url = config_dict.get("stack-policy-url") self.default_timeout = config_dict.get("timeout", 600) self.default_tags = config_dict.get("tags", {}) self.default_failure_action = config_dict.get("on_failure", "ROLLBACK") self.default_disable_rollback = config_dict.get( "disable_rollback", False) self._validate(config_dict) stacks = self._parse_stack_configs(config_dict) self.stacks = self._apply_stack_name_suffix(stacks, stack_name_suffix)
def test_handle_yaml_constructors_converts_find_in_map(self): loader_mock = Mock() loader_mock.construct_scalar.return_value = ["MapName", "TopLevelKey", "SecondLevelKey"] node_mock = Mock(spec=yaml.ScalarNode) response = FileLoader.handle_yaml_constructors(loader_mock, "!FindInMap", node_mock) self.assertEqual({'Fn::FindInMap': ["MapName", "TopLevelKey", "SecondLevelKey"]}, response)
def handle_file_value(value, working_dir): components = value.split('|', 3) if len(components) == 3: url = components[2] return FileLoader.get_file(url, working_dir) elif len(components) == 4: url = components[2] pattern = components[3] file_content = FileLoader.get_yaml_or_json_file(url, working_dir) try: return jmespath.search(pattern, file_content) except JMESPathError as e: raise CfnSphereException(e) else: raise CfnSphereException("Invalid format for |File| macro, it must be |File|<path>[|<pattern>]")
def create_or_update_stacks(self): existing_stacks = self.cfn.get_stack_names() desired_stacks = self.config.stacks stack_processing_order = DependencyResolver().get_stack_order(desired_stacks) if len(stack_processing_order) > 1: self.logger.info( "Will process stacks in the following order: {0}".format(", ".join(stack_processing_order))) for stack_name in stack_processing_order: stack_config = self.config.stacks.get(stack_name) raw_template = FileLoader.get_file_from_url(stack_config.template_url, stack_config.working_dir) template = CloudFormationTemplateTransformer.transform_template(raw_template) combined_tags = dict(self.config.tags) combined_tags.update(stack_config.tags) parameters = self.parameter_resolver.resolve_parameter_values(stack_config.parameters, stack_name) stack = CloudFormationStack(template=template, parameters=parameters, tags=combined_tags, name=stack_name, region=self.config.region, timeout=stack_config.timeout) if stack_name in existing_stacks: self.cfn.validate_stack_is_ready_for_action(stack) self.cfn.update_stack(stack) else: self.cfn.create_stack(stack) CustomResourceHandler.process_post_resources(stack)
def upload_cfn_to_s3(project, logger): """ This task is separate since they only work with Python2 >2.6 by the time being. Python3 support is underway. This means, when using Python<2.7, this task is not visible (see __init__.py). """ from cfn_sphere.file_loader import FileLoader from cfn_sphere.template.transformer import CloudFormationTemplateTransformer for path, filename in project.get_property('template_files'): template = FileLoader.get_file_from_url(filename, path) transformed = CloudFormationTemplateTransformer.transform_template( template) output = transformed.get_template_json() bucket_name = project.get_property('bucket_name') key_prefix = project.get_property('template_key_prefix') filename = filename.replace('.yml', '.json') filename = filename.replace('.yaml', '.json') version_path = '{0}v{1}/{2}'.format( key_prefix, project.version, filename) # latest_path = '{0}latest/{1}'.format(key_prefix, filename) acl = project.get_property('template_file_access_control') check_acl_parameter_validity('template_file_access_control', acl) upload_helper(logger, bucket_name, version_path, output, acl)
def upload_cfn_to_s3(project, logger): """ This task is separate since they only work with Python2 >2.6 by the time being. Python3 support is underway. This means, when using Python<2.7, this task is not visible (see __init__.py). """ from cfn_sphere.file_loader import FileLoader from cfn_sphere.template.transformer import CloudFormationTemplateTransformer for path, filename in project.get_property('template_files'): template = FileLoader.get_cloudformation_template(filename, path) transformed = CloudFormationTemplateTransformer.transform_template( template) output = transformed.get_template_json() bucket_name = project.get_property('bucket_name') key_prefix = project.get_property('template_key_prefix') filename = filename.replace('.yml', '.json') filename = filename.replace('.yaml', '.json') version_path = '{0}v{1}/{2}'.format(key_prefix, project.version, filename) # latest_path = '{0}latest/{1}'.format(key_prefix, filename) acl = project.get_property('template_file_access_control') check_acl_parameter_validity('template_file_access_control', acl) upload_helper(logger, bucket_name, version_path, output, acl)
def test_get_cloudformation_template_returns_template_with_transform_property( self, get_yaml_or_json_file_mock): expected = { 'Conditions': {}, 'Mappings': {}, 'Metadata': {}, 'Resources': 'Foo', 'Transform': 'transform-section', 'Parameters': {}, 'Outputs': {}, 'AWSTemplateFormatVersion': '2010-09-09', 'Description': '' } get_yaml_or_json_file_mock.return_value = { "Resources": "Foo", "Transform": "transform-section" } result = FileLoader.get_cloudformation_template( "s3://my-bucket/template.yml", None) self.assertEqual(expected, result.get_template_body_dict()) get_yaml_or_json_file_mock.assert_called_once_with( 's3://my-bucket/template.yml', None)
def create_or_update_stacks(self): existing_stacks = self.cfn.get_stack_names() desired_stacks = self.config.stacks stack_processing_order = DependencyResolver().get_stack_order(desired_stacks) if len(stack_processing_order) > 1: self.logger.info( "Will process stacks in the following order: {0}".format(", ".join(stack_processing_order))) for stack_name in stack_processing_order: stack_config = self.config.stacks.get(stack_name) template = FileLoader.get_cloudformation_template(stack_config.template_url,stack_config.working_dir) transformed_template = CloudFormationTemplateTransformer.transform_template(template) if stack_config.stack_policy_url: self.logger.info("Using stack policy from {0}".format(stack_config.stack_policy_url)) stack_policy = FileLoader.get_yaml_or_json_file(stack_config.stack_policy_url, stack_config.working_dir) else: stack_policy = None parameters = self.parameter_resolver.resolve_parameter_values(stack_name, stack_config) merged_parameters = self.parameter_resolver.update_parameters_with_cli_parameters( parameters=parameters, cli_parameters=self.cli_parameters, stack_name=stack_name) self.logger.debug("Parameters after merging with cli options: {0}".format(merged_parameters)) stack = CloudFormationStack(template=transformed_template, parameters=merged_parameters, tags=stack_config.tags, name=stack_name, region=self.config.region, timeout=stack_config.timeout, service_role=stack_config.service_role, stack_policy=stack_policy) if stack_name in existing_stacks: self.cfn.validate_stack_is_ready_for_action(stack) self.cfn.update_stack(stack) else: self.cfn.create_stack(stack) CustomResourceHandler.process_post_resources(stack)
def test_handle_yaml_constructors_converts_split(self): loader_mock = Mock() loader_mock.construct_scalar.return_value = ["delimiter", "string"] node_mock = Mock(spec=yaml.ScalarNode) response = FileLoader.handle_yaml_constructors(loader_mock, "!split", node_mock) self.assertEqual({'Fn::Split': ["delimiter", "string"]}, response)
def test_get_yaml_or_json_file_accepts_yaml_template_with_getatt_constructor_tag( self, get_file_mock): get_file_mock.return_value = "myKey: !GetAtt myResource.attributeName" result = FileLoader.get_yaml_or_json_file("my-template.yaml", None) self.assertEqual( {"myKey": { "Fn::GetAtt": ["myResource", "attributeName"] }}, result)
def validate_template(template_file, confirm): if not confirm: check_update_available() try: loader = FileLoader() template = loader.get_file_from_url(template_file, None) template = CloudFormationTemplateTransformer.transform_template(template) CloudFormation().validate_template(template) click.echo("Template is valid") except CfnSphereException as e: LOGGER.error(e) sys.exit(1) except Exception as e: LOGGER.error("Failed with unexpected error") LOGGER.exception(e) LOGGER.info("Please report at https://github.com/cfn-sphere/cfn-sphere/issues!") sys.exit(1)
def handle_file_value(value, working_dir): components = value.split('|', 3) if len(components) == 3: url = components[2] return FileLoader.get_file(url, working_dir) elif len(components) == 4: url = components[2] pattern = components[3] file_content = FileLoader.get_yaml_or_json_file(url, working_dir) try: return jmespath.search(pattern, file_content) except JMESPathError as e: raise CfnSphereException(e) else: raise CfnSphereException( "Invalid format for |File| macro, it must be |File|<path>[|<pattern>]" )
def validate_template(template_file, confirm, yes): confirm = confirm or yes if not confirm: check_update_available() try: loader = FileLoader() template = loader.get_cloudformation_template(template_file, None) template = CloudFormationTemplateTransformer.transform_template(template) CloudFormation().validate_template(template) click.echo("Template is valid") except CfnSphereException as e: LOGGER.error(e) sys.exit(1) except Exception as e: LOGGER.error("Failed with unexpected error") LOGGER.exception(e) LOGGER.info("Please report at https://github.com/cfn-sphere/cfn-sphere/issues!") sys.exit(1)
def get_template(template_url, working_dir, region, package_bucket): template = FileLoader.get_cloudformation_template( template_url, working_dir) additional_stack_description = "Config repo url: {0}".format( get_git_repository_remote_url(working_dir)) template = CloudFormationTemplateTransformer.transform_template( template, additional_stack_description) template = CloudFormationSamPackager.package(template_url, working_dir, template, region, package_bucket) return template
def test_get_yaml_or_json_file_accepts_yaml_with_nested_constructor_tags( self, get_file_mock): get_file_mock.return_value = "myKey: !Join [ b, [ !ref a, !ref b] ]" result = FileLoader.get_yaml_or_json_file("my-template.yaml", None) self.assertEqual( {"myKey": { "Fn::Join": ["b", [{ "Ref": "a" }, { "Ref": "b" }]] }}, result)
def resolve_parameter_values(self, stack_name, stack_config, cli_parameters=None): resolved_parameters = {} stack_outputs = self.cfn.get_stacks_outputs() for key, value in stack_config.parameters.items(): if isinstance(value, list): self.logger.debug("List parameter found for {0}".format(key)) for i, item in enumerate(value): if DependencyResolver.is_parameter_reference(item): referenced_stack, output_name = DependencyResolver.parse_stack_reference_value(item) value[i] = str(self.get_output_value(stack_outputs, referenced_stack, output_name)) value_string = self.convert_list_to_string(value) resolved_parameters[key] = value_string elif isinstance(value, str): if DependencyResolver.is_parameter_reference(value): referenced_stack, output_name = DependencyResolver.parse_stack_reference_value(value) resolved_parameters[key] = str(self.get_output_value(stack_outputs, referenced_stack, output_name)) elif self.is_keep_value(value): resolved_parameters[key] = str(self.get_latest_value(key, value, stack_name)) elif self.is_taupage_ami_reference(value): resolved_parameters[key] = str(self.ec2.get_latest_taupage_image_id()) elif self.is_kms(value): resolved_parameters[key] = str(self.kms.decrypt(value.split('|', 2)[2])) elif self.is_file(value): url = value.split('|', 2)[2] resolved_parameters[key] = FileLoader.get_file(url, stack_config.working_dir) else: resolved_parameters[key] = value elif isinstance(value, bool): resolved_parameters[key] = str(value).lower() elif isinstance(value, (int, float)): resolved_parameters[key] = str(value) else: raise NotImplementedError("Cannot handle {0} type for key: {1}".format(type(value), key)) if cli_parameters: return self.update_parameters_with_cli_parameters(resolved_parameters, cli_parameters, stack_name) else: return resolved_parameters
def test_get_cloudformation_template_returns_template(self, get_yaml_or_json_file_mock): expected = { 'Conditions': {}, 'Mappings': {}, 'Resources': 'Foo', 'Parameters': {}, 'Outputs': {}, 'AWSTemplateFormatVersion': '2010-09-09', 'Description': '' } get_yaml_or_json_file_mock.return_value = {"Resources": "Foo"} response = FileLoader.get_cloudformation_template("s3://my-bucket/template.yml", None) self.assertEqual(expected, response.get_template_body_dict()) get_yaml_or_json_file_mock.assert_called_once_with('s3://my-bucket/template.yml', None)
def create_or_update_stacks(self): existing_stacks = self.cfn.get_stack_names() desired_stacks = self.config.stacks stack_processing_order = DependencyResolver().get_stack_order( desired_stacks) if len(stack_processing_order) > 1: self.logger.info( "Will process stacks in the following order: {0}".format( ", ".join(stack_processing_order))) for stack_name in stack_processing_order: stack_config = self.config.stacks.get(stack_name) if stack_config.stack_policy_url: self.logger.info("Using stack policy from {0}".format( stack_config.stack_policy_url)) stack_policy = FileLoader.get_yaml_or_json_file( stack_config.stack_policy_url, stack_config.working_dir) else: stack_policy = None template = TemplateHandler.get_template(stack_config.template_url, stack_config.working_dir) parameters = self.parameter_resolver.resolve_parameter_values( stack_name, stack_config, self.cli_parameters) stack = CloudFormationStack( template=template, parameters=parameters, tags=stack_config.tags, name=stack_name, region=self.config.region, timeout=stack_config.timeout, service_role=stack_config.service_role, stack_policy=stack_policy, failure_action=stack_config.failure_action) if stack_name in existing_stacks: self.cfn.validate_stack_is_ready_for_action(stack) self.cfn.update_stack(stack) else: self.cfn.create_stack(stack) CustomResourceHandler.process_post_resources(stack)
def create_or_update_stacks(self): existing_stacks = self.cfn.get_stack_names() desired_stacks = self.config.stacks stack_processing_order = DependencyResolver().get_stack_order(desired_stacks) if len(stack_processing_order) > 1: self.logger.info( "Will process stacks in the following order: {0}".format(", ".join(stack_processing_order)) ) for stack_name in stack_processing_order: stack_config = self.config.stacks.get(stack_name) if stack_config.stack_policy_url: self.logger.info("Using stack policy from {0}".format(stack_config.stack_policy_url)) stack_policy = FileLoader.get_yaml_or_json_file(stack_config.stack_policy_url, stack_config.working_dir) else: stack_policy = None template = TemplateHandler.get_template(stack_config.template_url, stack_config.working_dir) parameters = self.parameter_resolver.resolve_parameter_values(stack_name, stack_config, self.cli_parameters) stack = CloudFormationStack( template=template, parameters=parameters, tags=stack_config.tags, name=stack_name, region=self.config.region, timeout=stack_config.timeout, service_role=stack_config.service_role, stack_policy=stack_policy, failure_action=stack_config.failure_action, ) if stack_name in existing_stacks: self.cfn.validate_stack_is_ready_for_action(stack) self.cfn.update_stack(stack) else: self.cfn.create_stack(stack) CustomResourceHandler.process_post_resources(stack)
def create_or_update_stacks(self): existing_stacks = self.cfn.get_stack_names() desired_stacks = self.config.stacks stack_processing_order = DependencyResolver().get_stack_order( desired_stacks) if len(stack_processing_order) > 1: self.logger.info( "Will process stacks in the following order: {0}".format( ", ".join(stack_processing_order))) for stack_name in stack_processing_order: stack_config = self.config.stacks.get(stack_name) raw_template = FileLoader.get_file_from_url( stack_config.template_url, stack_config.working_dir) template = CloudFormationTemplateTransformer.transform_template( raw_template) parameters = self.parameter_resolver.resolve_parameter_values( stack_config.parameters, stack_name) stack = CloudFormationStack(template=template, parameters=parameters, tags=(stack_config.tags), name=stack_name, region=self.region, timeout=stack_config.timeout) if stack_name in existing_stacks: self.cfn.validate_stack_is_ready_for_action(stack) self.cfn.update_stack(stack) else: self.cfn.create_stack(stack) CustomResourceHandler.process_post_resources(stack)
def test_get_file_calls_correct_handler_for_S3_prefix(self, s3_get_file_mock): FileLoader.get_file("S3://foo/foo.yml", None) s3_get_file_mock.assert_called_with("S3://foo/foo.yml")
def test_fs_get_file_raises_exception_on_json_error(self, _, yaml_mock): yaml_mock.load.side_effect = ValueError() with self.assertRaises(TemplateErrorException): FileLoader._fs_get_file('foo.json', 'baa')
def test_s3_get_file_parses_yaml_for_yml_suffix(self, s3_mock, json_mock, yaml_mock): s3_mock.return_value.get_contents_from_url.return_value = "{}" FileLoader._s3_get_file('s3://foo/baa.yml') json_mock.assert_not_called() yaml_mock.assert_called_once_with("{}")
def test_s3_get_file_raises_exception_on_error(self, s3_mock): s3_mock.return_value.get_contents_from_url.side_effect = CfnSphereBotoError with self.assertRaises(CfnSphereException): FileLoader._s3_get_file("s3://foo/foo.yml")
def test_fs_get_file_raises_exception_on_any_error(self, open_mock): open_mock.side_effect = IOError with self.assertRaises(CfnSphereException): FileLoader._fs_get_file("/foo/foo.yml", None)
def test_s3_get_file__raises_exception_for_unknown_suffix(self, s3_mock, json_mock, yaml_mock): s3_mock.return_value.get_contents_from_url.return_value = "{}" with self.assertRaises(TemplateErrorException): FileLoader._s3_get_file('s3://foo/baa.foo')
def test_get_yaml_or_json_file_raises_exception_on_json_error(self, _, json_mock): json_mock.loads.side_effect = ValueError() with self.assertRaises(CfnSphereException): FileLoader.get_yaml_or_json_file('foo.json', 'baa')
def test_get_yaml_or_json_file_raises_exception_on_yaml_error(self, _, yaml_mock): yaml_mock.load.side_effect = ScannerError() with self.assertRaises(CfnSphereException): FileLoader.get_yaml_or_json_file('foo.yml', 'baa')
def test_get_yaml_or_json_file_raises_exception_invalid_file_extension(self, _): with self.assertRaises(CfnSphereException): FileLoader.get_yaml_or_json_file('foo.foo', 'baa')
def test_get_file_calls_correct_handler_for_fs_path(self, fs_get_file_mock): FileLoader.get_file("foo/foo.yml", "/home/user") fs_get_file_mock.assert_called_with("foo/foo.yml", "/home/user")