Skip to content

Commit 6c18ed7

Browse files
committed
First round of unit testing
1 parent d623724 commit 6c18ed7

File tree

18 files changed

+542
-89
lines changed

18 files changed

+542
-89
lines changed

src/cfnlint/config.py

Lines changed: 93 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,64 @@ def __call__(self, parser, namespace, values, option_string=None):
327327
parser.exit()
328328

329329

330+
class ExtendKeyValuePairs(argparse.Action):
331+
def __init__(
332+
self,
333+
option_strings,
334+
dest,
335+
nargs=None,
336+
const=None,
337+
default=None,
338+
type=None,
339+
choices=None,
340+
required=False,
341+
help=None,
342+
metavar=None,
343+
): # pylint: disable=W0622
344+
super().__init__(
345+
option_strings=option_strings,
346+
dest=dest,
347+
nargs=nargs,
348+
const=const,
349+
default=default,
350+
type=type,
351+
choices=choices,
352+
required=required,
353+
help=help,
354+
metavar=metavar,
355+
)
356+
357+
def __call__(self, parser, namespace, values, option_string=None):
358+
try:
359+
items = {}
360+
for value in values:
361+
# split it into key and value
362+
key, value = value.split("=", 1)
363+
items[key.strip()] = value.strip()
364+
365+
result = getattr(namespace, self.dest) + [items]
366+
setattr(namespace, self.dest, result)
367+
except Exception: # pylint: disable=W0703
368+
parser.print_help()
369+
parser.exit()
370+
371+
372+
class ExtendAction(argparse.Action):
373+
"""Support argument types that are lists and can
374+
be specified multiple times.
375+
"""
376+
377+
def __call__(self, parser, namespace, values, option_string=None):
378+
items = getattr(namespace, self.dest)
379+
items = [] if items is None else items
380+
for value in values:
381+
if isinstance(value, list):
382+
items.extend(value)
383+
else:
384+
items.append(value)
385+
setattr(namespace, self.dest, items)
386+
387+
330388
class CliArgs:
331389
"""Base Args class"""
332390

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

347-
class ExtendAction(argparse.Action):
348-
"""Support argument types that are lists and can
349-
be specified multiple times.
350-
"""
351-
352-
def __call__(self, parser, namespace, values, option_string=None):
353-
items = getattr(namespace, self.dest)
354-
items = [] if items is None else items
355-
for value in values:
356-
if isinstance(value, list):
357-
items.extend(value)
358-
else:
359-
items.append(value)
360-
setattr(namespace, self.dest, items)
361-
362405
usage = (
363406
"\nBasic: cfn-lint test.yaml\n"
364407
"Ignore a rule: cfn-lint -i E3012 -- test.yaml\n"
@@ -368,18 +411,23 @@ def __call__(self, parser, namespace, values, option_string=None):
368411

369412
parser = ArgumentParser(description="CloudFormation Linter", usage=usage)
370413
parser.register("action", "extend", ExtendAction)
414+
parser.register("action", "rule_configuration", RuleConfigurationAction)
415+
parser.register("action", "extend_key_value", ExtendKeyValuePairs)
371416

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

420+
validation_group = standard.add_mutually_exclusive_group()
421+
parameter_group = standard.add_mutually_exclusive_group()
422+
375423
# Allow the template to be passes as an optional or a positional argument
376424
standard.add_argument(
377425
"templates",
378426
metavar="TEMPLATE",
379427
nargs="*",
380428
help="The CloudFormation template to be linted",
381429
)
382-
standard.add_argument(
430+
validation_group.add_argument(
383431
"-t",
384432
"--template",
385433
metavar="TEMPLATE",
@@ -403,22 +451,21 @@ def __call__(self, parser, namespace, values, option_string=None):
403451
default=[],
404452
action="extend",
405453
)
406-
standard.add_argument(
454+
validation_group.add_argument(
407455
"--deployment-files",
408456
dest="deployment_files",
409457
help="Deployment files",
410458
nargs="+",
411459
default=[],
412460
action="extend",
413461
)
414-
standard.add_argument(
462+
parameter_group.add_argument(
415463
"-tp",
416464
"--template-parameters",
417465
dest="template_parameters",
418-
metavar="KEY=VALUE",
419466
nargs="+",
420-
default={},
421-
action=key_value,
467+
default=[],
468+
action="extend_key_value",
422469
help="only check rules whose id do not match these values",
423470
)
424471
advanced.add_argument(
@@ -509,7 +556,7 @@ def __call__(self, parser, namespace, values, option_string=None):
509556
dest="configure_rules",
510557
nargs="+",
511558
default={},
512-
action=RuleConfigurationAction,
559+
action="rule_configuration",
513560
help=(
514561
"Provide configuration for a rule. Format RuleId:key=value. Example:"
515562
" E3012:strict=true"
@@ -614,15 +661,15 @@ def set_template_args(self, template):
614661

615662
if isinstance(configs, dict):
616663
for key, value in {
617-
"ignore_checks": (list),
618-
"regions": (list),
619664
"append_rules": (list),
620-
"override_spec": (str),
665+
"configure_rules": (dict),
621666
"custom_rules": (str),
622667
"ignore_bad_template": (bool),
668+
"ignore_checks": (list),
623669
"include_checks": (list),
624-
"configure_rules": (dict),
625670
"include_experimental": (bool),
671+
"override_spec": (str),
672+
"regions": (list),
626673
}.items():
627674
if key in configs:
628675
if isinstance(configs[key], value):
@@ -635,17 +682,18 @@ def set_template_args(self, template):
635682

636683
class ManualArgs(TypedDict, total=False):
637684
configure_rules: dict[str, dict[str, Any]]
638-
include_checks: list[str]
639-
ignore_checks: list[str]
640-
template_parameters: dict[str, Any]
641-
mandatory_checks: list[str]
642-
include_experimental: bool
685+
deployment_files: list[str]
643686
ignore_bad_template: bool
687+
ignore_checks: list[str]
644688
ignore_templates: list
689+
include_checks: list[str]
690+
include_experimental: bool
691+
mandatory_checks: list[str]
645692
merge_configs: bool
646693
non_zero_exit_code: str
647694
output_file: str
648695
regions: list
696+
template_parameters: list[dict[str, Any]]
649697

650698

651699
# pylint: disable=too-many-public-methods
@@ -665,24 +713,25 @@ def __init__(self, cli_args: list[str] | None = None, **kwargs: Unpack[ManualArg
665713
def __repr__(self):
666714
return format_json_string(
667715
{
716+
"append_rules": self.append_rules,
717+
"config_file": self.config_file,
718+
"configure_rules": self.configure_rules,
719+
"custom_rules": self.custom_rules,
720+
"debug": self.debug,
721+
"deployment_files": self.deployment_files,
722+
"format": self.format,
723+
"ignore_bad_template": self.ignore_bad_template,
668724
"ignore_checks": self.ignore_checks,
669725
"include_checks": self.include_checks,
670-
"mandatory_checks": self.mandatory_checks,
671-
"template_parameters": self.template_parameters,
672726
"include_experimental": self.include_experimental,
673-
"configure_rules": self.configure_rules,
674-
"regions": self.regions,
675-
"ignore_bad_template": self.ignore_bad_template,
676-
"debug": self.debug,
677727
"info": self.info,
678-
"format": self.format,
679-
"templates": self.templates,
680-
"append_rules": self.append_rules,
681-
"override_spec": self.override_spec,
682-
"custom_rules": self.custom_rules,
683-
"config_file": self.config_file,
728+
"mandatory_checks": self.mandatory_checks,
684729
"merge_configs": self.merge_configs,
685730
"non_zero_exit_code": self.non_zero_exit_code,
731+
"override_spec": self.override_spec,
732+
"regions": self.regions,
733+
"template_parameters": self.template_parameters,
734+
"templates": self.templates,
686735
}
687736
)
688737

@@ -845,7 +894,7 @@ def template_parameters(self):
845894
return self._get_argument_value("template_parameters", True, True)
846895

847896
@template_parameters.setter
848-
def template_parameters(self, template_parameters: dict[str, Any]):
897+
def template_parameters(self, template_parameters: list[dict[str, Any]]):
849898
self._manual_args["template_parameters"] = template_parameters
850899

851900
@property

src/cfnlint/data/CfnLintCli/config/schema.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@
5858
"description": "custom rule file to use",
5959
"type": "string"
6060
},
61+
"deployment_files": {
62+
"items": {
63+
"type": "string"
64+
},
65+
"type": "array"
66+
},
6167
"ignore_bad_template": {
6268
"description": "Ignore bad templates",
6369
"type": "boolean"
@@ -116,6 +122,22 @@
116122
},
117123
"type": "array"
118124
},
125+
"template_parameters": {
126+
"items": {
127+
"patternProperties": {
128+
"^.*$": {
129+
"type": [
130+
"string",
131+
"integer",
132+
"boolean",
133+
"number"
134+
]
135+
}
136+
},
137+
"type": "object"
138+
},
139+
"type": "array"
140+
},
119141
"templates": {
120142
"description": "Templates to lint",
121143
"items": {

src/cfnlint/data/schemas/other/deployment_files/__init__.py

Whitespace-only changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"additionalProperties": false,
3+
"properties": {
4+
"parameters": {
5+
"patternProperties": {
6+
"^.+$": {
7+
"type": "string"
8+
}
9+
},
10+
"type": "object"
11+
},
12+
"tags": {
13+
"patternProperties": {
14+
"^.+$": {
15+
"type": "string"
16+
}
17+
},
18+
"type": "object"
19+
},
20+
"template-file-path": {
21+
"type": "string"
22+
}
23+
},
24+
"required": [
25+
"template-file-path"
26+
],
27+
"type": "object"
28+
}

src/cfnlint/rules/_rule.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ def __init__(self, path: Path, message: str, **kwargs):
9494
for k, v in kwargs.items():
9595
setattr(self, k, v)
9696

97+
def __repr__(self):
98+
return cfnlint.helpers.format_json_string(
99+
{
100+
"path": self.path,
101+
"path_string": self.path_string,
102+
"message": self.message,
103+
"context": self.context,
104+
}
105+
)
106+
97107
def __eq__(self, item):
98108
"""
99109
Override the equality comparison operator to compare rule
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
SPDX-License-Identifier: MIT-0
4+
"""
5+
6+
from __future__ import annotations
7+
8+
from cfnlint._typing import Any, RuleMatches
9+
from cfnlint.jsonschema import StandardValidator
10+
from cfnlint.rules.jsonschema.Base import BaseJsonSchema
11+
12+
13+
class Configuration(BaseJsonSchema):
14+
15+
id = "E0100"
16+
shortdesc = "Validate deployment file configuration"
17+
description = (
18+
"Validate if a deployment file has the correct syntax "
19+
"for one of the supported formats"
20+
)
21+
source_url = "https://github.com/aws-cloudformation/cfn-lint"
22+
tags = ["base"]
23+
24+
def validate_deployment_file(
25+
self, data: dict[str, Any], schema: dict[str, Any]
26+
) -> RuleMatches:
27+
matches = []
28+
29+
validator = StandardValidator(schema)
30+
31+
matches.extend(self.json_schema_validate(validator, data, []))
32+
33+
return matches

src/cfnlint/rules/parameters/DeploymentParameters.py renamed to src/cfnlint/rules/deployment_files/Parameters.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
from cfnlint.rules.jsonschema.CfnLintJsonSchema import CfnLintJsonSchema
1212

1313

14-
class DeploymentParameters(CfnLintJsonSchema):
15-
"""Check if Parameters are configured correctly"""
16-
14+
class Parameters(CfnLintJsonSchema):
1715
id = "E2900"
1816
shortdesc = (
1917
"Validate deployment file parameters are valid against template parameters"

src/cfnlint/rules/deployment_files/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)