Skip to content

Commit

Permalink
First round of unit testing
Browse files Browse the repository at this point in the history
  • Loading branch information
kddejong committed Dec 19, 2024
1 parent d623724 commit 9494437
Show file tree
Hide file tree
Showing 17 changed files with 542 additions and 89 deletions.
137 changes: 93 additions & 44 deletions src/cfnlint/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,64 @@ def __call__(self, parser, namespace, values, option_string=None):
parser.exit()


class ExtendKeyValuePairs(argparse.Action):
def __init__(
self,
option_strings,
dest,
nargs=None,
const=None,
default=None,
type=None,
choices=None,
required=False,
help=None,
metavar=None,
): # pylint: disable=W0622
super().__init__(
option_strings=option_strings,
dest=dest,
nargs=nargs,
const=const,
default=default,
type=type,
choices=choices,
required=required,
help=help,
metavar=metavar,
)

def __call__(self, parser, namespace, values, option_string=None):
try:
items = {}
for value in values:
# split it into key and value
key, value = value.split("=", 1)
items[key.strip()] = value.strip()

result = getattr(namespace, self.dest) + [items]
setattr(namespace, self.dest, result)
except Exception: # pylint: disable=W0703
parser.print_help()
parser.exit()


class ExtendAction(argparse.Action):
"""Support argument types that are lists and can
be specified multiple times.
"""

def __call__(self, parser, namespace, values, option_string=None):
items = getattr(namespace, self.dest)
items = [] if items is None else items
for value in values:
if isinstance(value, list):
items.extend(value)
else:
items.append(value)
setattr(namespace, self.dest, items)


class CliArgs:
"""Base Args class"""

Expand All @@ -344,21 +402,6 @@ def error(self, message):
self.print_help(sys.stderr)
self.exit(32, f"{self.prog}: error: {message}\n")

class ExtendAction(argparse.Action):
"""Support argument types that are lists and can
be specified multiple times.
"""

def __call__(self, parser, namespace, values, option_string=None):
items = getattr(namespace, self.dest)
items = [] if items is None else items
for value in values:
if isinstance(value, list):
items.extend(value)
else:
items.append(value)
setattr(namespace, self.dest, items)

usage = (
"\nBasic: cfn-lint test.yaml\n"
"Ignore a rule: cfn-lint -i E3012 -- test.yaml\n"
Expand All @@ -368,18 +411,23 @@ def __call__(self, parser, namespace, values, option_string=None):

parser = ArgumentParser(description="CloudFormation Linter", usage=usage)
parser.register("action", "extend", ExtendAction)
parser.register("action", "rule_configuration", RuleConfigurationAction)
parser.register("action", "extend_key_value", ExtendKeyValuePairs)

standard = parser.add_argument_group("Standard")
advanced = parser.add_argument_group("Advanced / Debugging")

validation_group = standard.add_mutually_exclusive_group()
parameter_group = standard.add_mutually_exclusive_group()

# Allow the template to be passes as an optional or a positional argument
standard.add_argument(
"templates",
metavar="TEMPLATE",
nargs="*",
help="The CloudFormation template to be linted",
)
standard.add_argument(
validation_group.add_argument(
"-t",
"--template",
metavar="TEMPLATE",
Expand All @@ -403,22 +451,21 @@ def __call__(self, parser, namespace, values, option_string=None):
default=[],
action="extend",
)
standard.add_argument(
validation_group.add_argument(
"--deployment-files",
dest="deployment_files",
help="Deployment files",
nargs="+",
default=[],
action="extend",
)
standard.add_argument(
parameter_group.add_argument(
"-tp",
"--template-parameters",
dest="template_parameters",
metavar="KEY=VALUE",
nargs="+",
default={},
action=key_value,
default=[],
action="extend_key_value",
help="only check rules whose id do not match these values",
)
advanced.add_argument(
Expand Down Expand Up @@ -509,7 +556,7 @@ def __call__(self, parser, namespace, values, option_string=None):
dest="configure_rules",
nargs="+",
default={},
action=RuleConfigurationAction,
action="rule_configuration",
help=(
"Provide configuration for a rule. Format RuleId:key=value. Example:"
" E3012:strict=true"
Expand Down Expand Up @@ -614,15 +661,15 @@ def set_template_args(self, template):

if isinstance(configs, dict):
for key, value in {
"ignore_checks": (list),
"regions": (list),
"append_rules": (list),
"override_spec": (str),
"configure_rules": (dict),
"custom_rules": (str),
"ignore_bad_template": (bool),
"ignore_checks": (list),
"include_checks": (list),
"configure_rules": (dict),
"include_experimental": (bool),
"override_spec": (str),
"regions": (list),
}.items():
if key in configs:
if isinstance(configs[key], value):
Expand All @@ -635,17 +682,18 @@ def set_template_args(self, template):

class ManualArgs(TypedDict, total=False):
configure_rules: dict[str, dict[str, Any]]
include_checks: list[str]
ignore_checks: list[str]
template_parameters: dict[str, Any]
mandatory_checks: list[str]
include_experimental: bool
deployment_files: list[str]
ignore_bad_template: bool
ignore_checks: list[str]
ignore_templates: list
include_checks: list[str]
include_experimental: bool
mandatory_checks: list[str]
merge_configs: bool
non_zero_exit_code: str
output_file: str
regions: list
template_parameters: list[dict[str, Any]]


# pylint: disable=too-many-public-methods
Expand All @@ -665,24 +713,25 @@ def __init__(self, cli_args: list[str] | None = None, **kwargs: Unpack[ManualArg
def __repr__(self):
return format_json_string(
{
"append_rules": self.append_rules,
"config_file": self.config_file,
"configure_rules": self.configure_rules,
"custom_rules": self.custom_rules,
"debug": self.debug,
"deployment_files": self.deployment_files,
"format": self.format,
"ignore_bad_template": self.ignore_bad_template,
"ignore_checks": self.ignore_checks,
"include_checks": self.include_checks,
"mandatory_checks": self.mandatory_checks,
"template_parameters": self.template_parameters,
"include_experimental": self.include_experimental,
"configure_rules": self.configure_rules,
"regions": self.regions,
"ignore_bad_template": self.ignore_bad_template,
"debug": self.debug,
"info": self.info,
"format": self.format,
"templates": self.templates,
"append_rules": self.append_rules,
"override_spec": self.override_spec,
"custom_rules": self.custom_rules,
"config_file": self.config_file,
"mandatory_checks": self.mandatory_checks,
"merge_configs": self.merge_configs,
"non_zero_exit_code": self.non_zero_exit_code,
"override_spec": self.override_spec,
"regions": self.regions,
"template_parameters": self.template_parameters,
"templates": self.templates,
}
)

Expand Down Expand Up @@ -845,7 +894,7 @@ def template_parameters(self):
return self._get_argument_value("template_parameters", True, True)

@template_parameters.setter
def template_parameters(self, template_parameters: dict[str, Any]):
def template_parameters(self, template_parameters: list[dict[str, Any]]):
self._manual_args["template_parameters"] = template_parameters

@property
Expand Down
22 changes: 22 additions & 0 deletions src/cfnlint/data/CfnLintCli/config/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@
"description": "custom rule file to use",
"type": "string"
},
"deployment_files": {
"items": {
"type": "string"
},
"type": "array"
},
"ignore_bad_template": {
"description": "Ignore bad templates",
"type": "boolean"
Expand Down Expand Up @@ -116,6 +122,22 @@
},
"type": "array"
},
"template_parameters": {
"items": {
"patternProperties": {
"^.*$": {
"type": [
"string",
"integer",
"boolean",
"number"
]
}
},
"type": "object"
},
"type": "array"
},
"templates": {
"description": "Templates to lint",
"items": {
Expand Down
28 changes: 28 additions & 0 deletions src/cfnlint/data/schemas/other/deployment_files/git_sync.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"additionalProperties": false,
"properties": {
"parameters": {
"patternProperties": {
"^.+$": {
"type": "string"
}
},
"type": "object"
},
"tags": {
"patternProperties": {
"^.+$": {
"type": "string"
}
},
"type": "object"
},
"template-file-path": {
"type": "string"
}
},
"required": [
"template-file-path"
],
"type": "object"
}
10 changes: 10 additions & 0 deletions src/cfnlint/rules/_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ def __init__(self, path: Path, message: str, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)

def __repr__(self):
return cfnlint.helpers.format_json_string(
{
"path": self.path,
"path_string": self.path_string,
"message": self.message,
"context": self.context,
}
)

def __eq__(self, item):
"""
Override the equality comparison operator to compare rule
Expand Down
33 changes: 33 additions & 0 deletions src/cfnlint/rules/deployment_files/Configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: MIT-0
"""

from __future__ import annotations

from cfnlint._typing import Any, RuleMatches
from cfnlint.jsonschema import StandardValidator
from cfnlint.rules.jsonschema.Base import BaseJsonSchema


class Configuration(BaseJsonSchema):

id = "E0100"
shortdesc = "Validate deployment file configuration"
description = (
"Validate if a deployment file has the correct syntax "
"for one of the supported formats"
)
source_url = "https://github.com/aws-cloudformation/cfn-lint"
tags = ["base"]

def validate_deployment_file(
self, data: dict[str, Any], schema: dict[str, Any]
) -> RuleMatches:
matches = []

validator = StandardValidator(schema)

matches.extend(self.json_schema_validate(validator, data, []))

return matches
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@
from cfnlint.rules.jsonschema.CfnLintJsonSchema import CfnLintJsonSchema


class DeploymentParameters(CfnLintJsonSchema):
"""Check if Parameters are configured correctly"""

class Parameters(CfnLintJsonSchema):
id = "E2900"
shortdesc = (
"Validate deployment file parameters are valid against template parameters"
Expand Down
Empty file.
Loading

0 comments on commit 9494437

Please sign in to comment.