From b1ddd334a9dfef2226877d64cbdf2ef15c027c5a Mon Sep 17 00:00:00 2001 From: Kevin DeJong Date: Tue, 10 Dec 2024 11:10:58 -0800 Subject: [PATCH 1/2] fix returning None from FindInMap (#3866) --- src/cfnlint/template/transforms/_language_extensions.py | 5 ++++- .../template/transforms/test_language_extensions.py | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/cfnlint/template/transforms/_language_extensions.py b/src/cfnlint/template/transforms/_language_extensions.py index be6c442981..5263cf6868 100644 --- a/src/cfnlint/template/transforms/_language_extensions.py +++ b/src/cfnlint/template/transforms/_language_extensions.py @@ -392,9 +392,12 @@ def value( if mapping: try: - return mapping.get(t_map[1].value(cfn, params, only_params), {}).get( + value = mapping.get(t_map[1].value(cfn, params, only_params), {}).get( t_map[2].value(cfn, params, only_params) ) + if value is None: + raise _ResolveError("Can't resolve Fn::FindInMap", self._obj) + return value except _ResolveError as e: if len(self._map) == 4 and default_on_resolver_failure: return self._map[3].value(cfn, params, only_params) diff --git a/test/unit/module/template/transforms/test_language_extensions.py b/test/unit/module/template/transforms/test_language_extensions.py index 921929e7cb..f992f88cd6 100644 --- a/test/unit/module/template/transforms/test_language_extensions.py +++ b/test/unit/module/template/transforms/test_language_extensions.py @@ -299,6 +299,14 @@ def test_find_in_map_values_with_default(self): with self.assertRaises(_ResolveError): map.value(self.cfn, None, False, False) + def test_find_in_map_values_not_found_with_default(self): + map = _ForEachValueFnFindInMap( + "a", ["Bucket", "Production", "DNE", {"DefaultValue": "bar"}] + ) + + self.assertEqual(map.value(self.cfn, None, False, True), "bar") + self.assertEqual(map.value(self.cfn, None, False, False), ["foo", "bar"]) + def test_find_in_map_values_without_default(self): map = _ForEachValueFnFindInMap("a", ["Bucket", {"Ref": "Foo"}, "Key"]) From c6e3878d802efba432d9ac92cba2011b0996c775 Mon Sep 17 00:00:00 2001 From: Kevin DeJong Date: Tue, 10 Dec 2024 11:11:54 -0800 Subject: [PATCH 2/2] Switch back to raising bad path errors (#3862) * Go to standard posix failures --- src/cfnlint/config.py | 8 ++++---- src/cfnlint/data/CfnLintCli/config/schema.json | 4 ++++ src/cfnlint/rules/errors/__init__.py | 1 + src/cfnlint/rules/errors/config.py | 15 +++++++++++++++ src/cfnlint/runner.py | 7 +++++-- test/unit/module/config/test_config_mixin.py | 14 ++++++++++++++ .../maintenance/test_update_documentation.py | 8 ++++++++ test/unit/module/runner/test_runner.py | 16 ++++++++++++++++ 8 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 src/cfnlint/rules/errors/config.py diff --git a/src/cfnlint/config.py b/src/cfnlint/config.py index 7e81b98203..03846af54d 100644 --- a/src/cfnlint/config.py +++ b/src/cfnlint/config.py @@ -796,10 +796,10 @@ def _glob_filenames(self, filenames: Sequence[str]) -> list[str]: for filename in filenames: add_filenames = glob.glob(filename, recursive=True) - if isinstance(add_filenames, list): - all_filenames.extend(add_filenames) - else: - LOGGER.error(f"{filename} could not be processed by glob.glob") + if not add_filenames and not self.ignore_bad_template: + raise ValueError(f"{filename} could not be processed by glob.glob") + + all_filenames.extend(add_filenames) return sorted(list(map(str, map(Path, all_filenames)))) diff --git a/src/cfnlint/data/CfnLintCli/config/schema.json b/src/cfnlint/data/CfnLintCli/config/schema.json index eb8974c8fb..f72fc6189f 100644 --- a/src/cfnlint/data/CfnLintCli/config/schema.json +++ b/src/cfnlint/data/CfnLintCli/config/schema.json @@ -58,6 +58,10 @@ "description": "custom rule file to use", "type": "string" }, + "ignore_bad_template": { + "description": "Ignore bad templates", + "type": "boolean" + }, "ignore_checks": { "description": "List of checks to ignore", "items": { diff --git a/src/cfnlint/rules/errors/__init__.py b/src/cfnlint/rules/errors/__init__.py index 142af37032..52f3f257cc 100644 --- a/src/cfnlint/rules/errors/__init__.py +++ b/src/cfnlint/rules/errors/__init__.py @@ -3,6 +3,7 @@ SPDX-License-Identifier: MIT-0 """ +from cfnlint.rules.errors.config import ConfigError from cfnlint.rules.errors.parse import ParseError from cfnlint.rules.errors.rule import RuleError from cfnlint.rules.errors.transform import TransformError diff --git a/src/cfnlint/rules/errors/config.py b/src/cfnlint/rules/errors/config.py new file mode 100644 index 0000000000..1a7b533152 --- /dev/null +++ b/src/cfnlint/rules/errors/config.py @@ -0,0 +1,15 @@ +""" +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: MIT-0 +""" + +from cfnlint.rules._rule import CloudFormationLintRule + + +class ConfigError(CloudFormationLintRule): + + id = "E0003" + shortdesc = "Error with cfn-lint configuration" + description = "Error as a result of the cfn-lint configuration" + source_url = "https://github.com/aws-cloudformation/cfn-lint" + tags = ["base", "rule"] diff --git a/src/cfnlint/runner.py b/src/cfnlint/runner.py index e1954650c8..6b8acabd5c 100644 --- a/src/cfnlint/runner.py +++ b/src/cfnlint/runner.py @@ -17,7 +17,7 @@ from cfnlint.decode.decode import decode from cfnlint.helpers import REGIONS from cfnlint.rules import Match, Rules -from cfnlint.rules.errors import ParseError, TransformError +from cfnlint.rules.errors import ConfigError, ParseError, TransformError from cfnlint.schema import PROVIDER_SCHEMA_MANAGER from cfnlint.template.template import Template @@ -223,10 +223,13 @@ def __init__(self, config: ConfigMixIn) -> None: settings for the template scan. """ self.config = config - self.config.templates self.formatter = get_formatter(self.config) self.rules: Rules = Rules() self._get_rules() + try: + self.config.templates + except ValueError as e: + self._cli_output([Match(str(e), ConfigError(), None)]) # load registry schemas before patching if self.config.registry_schemas: for path in self.config.registry_schemas: diff --git a/test/unit/module/config/test_config_mixin.py b/test/unit/module/config/test_config_mixin.py index f6b5abc594..9e584ead07 100644 --- a/test/unit/module/config/test_config_mixin.py +++ b/test/unit/module/config/test_config_mixin.py @@ -208,6 +208,20 @@ def test_config_expand_paths_nomatch(self, yaml_mock): ] config = cfnlint.config.ConfigMixIn([]) + with self.assertRaises(ValueError): + self.assertEqual(len(config.templates), 1) + + @patch("cfnlint.config.ConfigFileArgs._read_config", create=True) + def test_config_expand_paths_nomatch_ignore_bad_template(self, yaml_mock): + """Test precedence in""" + + filename = "test/fixtures/templates/nonexistant/*.yaml" + yaml_mock.side_effect = [ + {"templates": [filename], "ignore_bad_template": True}, + {}, + ] + config = cfnlint.config.ConfigMixIn([]) + # test defaults self.assertEqual(config.templates, []) diff --git a/test/unit/module/maintenance/test_update_documentation.py b/test/unit/module/maintenance/test_update_documentation.py index 6613e85a4f..aecd9ef138 100644 --- a/test/unit/module/maintenance/test_update_documentation.py +++ b/test/unit/module/maintenance/test_update_documentation.py @@ -77,6 +77,14 @@ def test_update_docs(self): " [Source](https://github.com/aws-cloudformation/cfn-lint) |" " `base`,`rule` |\n" ), + call( + '| [E0003]' + "(../src/cfnlint/rules/errors/config.py) | " + "Error with cfn-lint configuration | " + "Error as a result of the cfn-lint configuration | | " + "[Source](https://github.com/aws-cloudformation/cfn-lint) " + "| `base`,`rule` |\n" + ), call("\n\\* experimental rules\n"), ] mock_builtin_open.return_value.write.assert_has_calls(expected_calls) diff --git a/test/unit/module/runner/test_runner.py b/test/unit/module/runner/test_runner.py index 60290d37b8..185e9c161c 100644 --- a/test/unit/module/runner/test_runner.py +++ b/test/unit/module/runner/test_runner.py @@ -3,6 +3,7 @@ SPDX-License-Identifier: MIT-0 """ +from io import StringIO from unittest.mock import patch import pytest @@ -93,3 +94,18 @@ def test_init_schemas(name, registry_path, patch_path, expected): PROVIDER_SCHEMA_MANAGER._registry_schemas = {} PROVIDER_SCHEMA_MANAGER.reset() + + +def test_no_templates(): + params = ["--template", "does-not-exist.yaml"] + + config = ConfigMixIn(params) + with patch("sys.exit") as exit: + with patch("sys.stdout", new=StringIO()) as out: + exit.assert_not_called() + Runner(config) + assert out.getvalue().strip() == ( + "E0003 does-not-exist.yaml could not " + "be processed by glob.glob\nNone:1:1" + ) + exit.assert_called_once_with(2)