From e2ac81556c0717663ba40290faeffb6c81b13d89 Mon Sep 17 00:00:00 2001 From: David Hutchison Date: Thu, 3 Oct 2024 23:14:37 +0100 Subject: [PATCH] fix(cfn-lint): backwards compatible support for cfn-lint 1.x --- taskcat/_cfn_lint.py | 42 ++++++++++++++++++++++++++++++++---------- tests/test_cfn_lint.py | 24 ++++++++++++++++++------ 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/taskcat/_cfn_lint.py b/taskcat/_cfn_lint.py index 2d655a61..a895293a 100644 --- a/taskcat/_cfn_lint.py +++ b/taskcat/_cfn_lint.py @@ -2,14 +2,26 @@ import re import textwrap +import cfnlint.config import cfnlint.core import cfnlint.helpers +import cfnlint.version from cfnlint.config import ConfigMixIn as CfnLintConfig from jsonschema.exceptions import ValidationError from taskcat._common_utils import neglect_submodule_templates from taskcat._config import Config from taskcat._dataclasses import Templates +# Ignoring linting errors here as pylint doesn't seem to handle the conditional nature +if cfnlint.version.__version__.startswith("0."): + from cfnlint.core import ( # pylint:disable=no-name-in-module, ungrouped-imports + CfnLintExitException, + ) +else: + from cfnlint.runner import ( # pylint:disable=no-name-in-module, ungrouped-imports + CfnLintExitException, + ) + LOG = logging.getLogger(__name__) @@ -32,8 +44,24 @@ def __init__(self, config: Config, templates: Templates, strict: bool = False): except ValidationError as e: LOG.error("Error parsing cfn-lint config file: %s", str(e)) raise + + # There is a change in the way that the cfn lint config class functions between the 0.x and 1.x versions. + # In 1.x, the append_rules property getter includes the default rule set along with the loaded configuration + # https://github.com/aws-cloudformation/cfn-lint/blob/23ee527fadb43e4fd54238eeea5bc3a169175c91/src/cfnlint/config.py#L773 + # In 0.x, it only returned the loaded configuration. + # https://github.com/aws-cloudformation/cfn-lint/blob/f006cb5d8c7056923f3f21b31c14edfeed3804b5/src/cfnlint/config.py#L730 + # + # This causes issues for us as the get_rules method combines the supplied value with the default rule list, + # resulting in a duplicate rule error. get_rules has always behaved this way though, so not sure if we have just missed something + # in the intended approach to calling this. + if cfnlint.version.__version__.startswith("0."): + append_rules = self._cfnlint_config.append_rules + else: + append_rules = self._cfnlint_config.append_rules + append_rules.remove(cfnlint.config._DEFAULT_RULESDIR) + self._rules = cfnlint.core.get_rules( - self._cfnlint_config.append_rules, + append_rules, self._cfnlint_config.ignore_checks, self._cfnlint_config.include_checks, self._cfnlint_config.configure_rules, @@ -91,17 +119,11 @@ def _run_checks(self, template, name, lint_errors, lints): tpath = str(template.template_path) results = [] try: - (_, rules, template_matches) = cfnlint.core.get_template_rules( - tpath, self._cfnlint_config + results = cfnlint.core.run_checks( + tpath, template.template, self._rules, lints[name]["regions"] ) - if template_matches: - results = template_matches - else: - results = cfnlint.core.run_checks( - tpath, template.template, rules, lints[name]["regions"] - ) lints[name]["results"][tpath] = results - except cfnlint.core.CfnLintExitException as e: + except CfnLintExitException as e: lint_errors.add(str(e)) lints[name]["results"][tpath] = results diff --git a/tests/test_cfn_lint.py b/tests/test_cfn_lint.py index b38286d5..75cd1e47 100644 --- a/tests/test_cfn_lint.py +++ b/tests/test_cfn_lint.py @@ -6,6 +6,7 @@ import yaml +import cfnlint.version from taskcat._cfn_lint import Lint from taskcat._config import Config @@ -30,6 +31,22 @@ def __init__(self): "(This code may only work with `package` cli command as the property" "(Resources/Name/Properties/TemplateURL) is a string) matched /private/tmp/lint_test/test-config-three/templates/taskcat_test_template_test1:1" ) +if cfnlint.version.__version__.startswith("0."): + invalid_type_error = [ + f"[E3001: Basic CloudFormation Resource Check] (Invalid or " + f"unsupported Type AWS::Not::Exist for resource Name in " + f"eu-west-1) matched " + f"{test_two_path}:1" + ] +else: + # The format of this message seems to change in 1.3.0 onwards + # (prior 1.x versions were pre-releases so not supporting them) + invalid_type_error = [ + f"[E3006: Validate the CloudFormation resource type] (Resource " + f"type 'AWS::Not::Exist' does not exist in " + f"'eu-west-1') matched " + f"{test_two_path}:1" + ] test_cases = [ { "config": { @@ -71,12 +88,7 @@ def __init__(self): "/tmp/lint_test/test-config-two/templates/taskcat_test_" "template_test1" ).resolve() - ): [ - f"[E3001: Basic CloudFormation Resource Check] (Invalid or " - f"unsupported Type AWS::Not::Exist for resource Name in " - f"eu-west-1) matched " - f"{test_two_path}:1" - ] + ): invalid_type_error }, "template": Path( "/tmp/lint_test/test-config-two/templates/taskcat_test_template"