From 661bd877344c4a7f2f5b634a5ebdde6a22720981 Mon Sep 17 00:00:00 2001 From: Thomas O'Brien Date: Thu, 21 May 2020 15:40:37 +0100 Subject: [PATCH] Output File CLI Option (#1511) * --output-file CLI argument added Co-authored-by: Kevin DeJong --- src/cfnlint/__main__.py | 8 ++++- src/cfnlint/config.py | 10 +++++++ .../data/CfnLintCli/config/schema.json | 4 +++ test/unit/module/config/test_cli_args.py | 12 ++++++++ test/unit/module/config/test_config_mixin.py | 30 +++++++++++++++++++ test/unit/module/core/test_run_cli.py | 8 +++++ 6 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/cfnlint/__main__.py b/src/cfnlint/__main__.py index 5c6cdce9b8..dd6c95fe00 100644 --- a/src/cfnlint/__main__.py +++ b/src/cfnlint/__main__.py @@ -37,8 +37,14 @@ def main(): LOGGER.debug('Completed linting of file: %s', str(filename)) matches_output = formatter.print_matches(matches, rules) + if matches_output: - print(matches_output) + if args.output_file: + with open(args.output_file, 'w') as output_file: + output_file.write(matches_output) + else: + print(matches_output) + return cfnlint.core.get_exit_code(matches) except cfnlint.core.CfnLintExitException as e: LOGGER.error(str(e)) diff --git a/src/cfnlint/config.py b/src/cfnlint/config.py index 33298230a5..df93b1a8a1 100644 --- a/src/cfnlint/config.py +++ b/src/cfnlint/config.py @@ -426,6 +426,11 @@ def __call__(self, parser, namespace, values, option_string=None): action='store_true' ) + standard.add_argument( + '--output-file', type=str, default=None, + help='Writes the output to the specified file, ideal for producing reports' + ) + return parser @@ -655,3 +660,8 @@ def config_file(self): def build_graph(self): """ build_graph """ return self._get_argument_value('build_graph', False, False) + + @property + def output_file(self): + """ output_file """ + return self._get_argument_value('output_file', False, True) diff --git a/src/cfnlint/data/CfnLintCli/config/schema.json b/src/cfnlint/data/CfnLintCli/config/schema.json index 84571ab7ea..8aefc29a0f 100644 --- a/src/cfnlint/data/CfnLintCli/config/schema.json +++ b/src/cfnlint/data/CfnLintCli/config/schema.json @@ -38,6 +38,10 @@ "description": "Path to spec file to override with", "type": "string" }, + "output_file": { + "description": "Path to the file to write the main output to", + "type": "string" + }, "regions": { "description": "Regions to test against", "items": { diff --git a/test/unit/module/config/test_cli_args.py b/test/unit/module/config/test_cli_args.py index d6ffb1305b..d3d35062c3 100644 --- a/test/unit/module/config/test_cli_args.py +++ b/test/unit/module/config/test_cli_args.py @@ -38,6 +38,18 @@ def test_create_parser_default_param(self): self.assertEqual(config.cli_args.template_alt, []) self.assertEqual(config.cli_args.regions, ['us-east-1', 'us-west-2']) + def test_stdout(self): + """Test success run""" + + config = cfnlint.config.CliArgs(['-t', 'template1.yaml']) + self.assertIsNone(config.cli_args.output_file) + + def test_output_file(self): + """Test success run""" + + config = cfnlint.config.CliArgs(['-t', 'template1.yaml', '--output-file', 'test_output.txt']) + self.assertEqual(config.cli_args.output_file, 'test_output.txt') + def test_create_parser_exend(self): """Test success run""" diff --git a/test/unit/module/config/test_config_mixin.py b/test/unit/module/config/test_config_mixin.py index 3099c41fef..62aa925e1a 100644 --- a/test/unit/module/config/test_config_mixin.py +++ b/test/unit/module/config/test_config_mixin.py @@ -59,6 +59,36 @@ def test_config_precedence(self, yaml_mock): # template file wins over config file self.assertEqual(config.ignore_checks, ['W3001']) + @patch('cfnlint.config.ConfigFileArgs._read_config', create=True) + def test_config_file_output(self, yaml_mock): + """ Test precedence in """ + + yaml_mock.side_effect = [ + { + "output_file": "test_output.txt" + }, + {} + ] + config = cfnlint.config.ConfigMixIn([]) + + # Config file wins + self.assertEqual(config.output_file, 'test_output.txt') + + @patch('cfnlint.config.ConfigFileArgs._read_config', create=True) + def test_config_file_output_mixin(self, yaml_mock): + """ Test precedence in """ + + yaml_mock.side_effect = [ + { + "output_file": "test_output.txt" + }, + {} + ] + config = cfnlint.config.ConfigMixIn(['--output-file', 'test_output_2.txt']) + + # CLI args win + self.assertEqual(config.output_file, 'test_output_2.txt') + @patch('cfnlint.config.ConfigFileArgs._read_config', create=True) def test_config_default_region(self, yaml_mock): """ Test precedence in """ diff --git a/test/unit/module/core/test_run_cli.py b/test/unit/module/core/test_run_cli.py index 9c39263df7..2a4938c556 100644 --- a/test/unit/module/core/test_run_cli.py +++ b/test/unit/module/core/test_run_cli.py @@ -130,6 +130,14 @@ def test_template_config(self, yaml_mock): 'test/fixtures/templates/good/core/config_parameters.yaml']) self.assertEqual(args.update_documentation, False) self.assertEqual(args.update_specs, False) + self.assertEqual(args.output_file, None) + + def test_output_file(self): + filename = 'test/fixtures/templates/good/core/config_parameters.yaml' + (args, _, _,) = cfnlint.core.get_args_filenames([ + '--template', filename, '--output-file', 'test_output.txt']) + + self.assertEqual(args.output_file, 'test_output.txt') @patch('cfnlint.config.ConfigFileArgs._read_config', create=True) def test_positional_template_parameters(self, yaml_mock):