Repository: aws-cloudformation/cfn-lint

Issue:
Exclude file from recursive search 
*cfn-lint version:  v0.10.2*

I have plenty of CF template files in `iac` folder. I would like them to be recursively linting:
```
templates:
- iac/CloudFormation/**/*.yaml
```

But I would like also to ignore some files from directory. Do you support something like:
```
ignore-templates:
- iac/CloudFormation/notCfTempl.yaml
```


Discussion:
@Sergei-Rudenkov Quick question... The file you want to exclude, are that JSON/YAML file that are no CloudFormation files? Or also (valid) CloudFormation files that you'd like to exclude from the list?
@fatbasstard The first option. I would like to exclude JSON/YAML file that are no CloudFormation files.
A second vote for this. Particularly frustrating that I can't exclude my buildspec.yml (for AWS CodeBuild) so that I can run from the root directory of my repository. It can be avoided by specifying and retaining all CloudFormation files in a particular directory. Would be a nice enhancement to see for an otherwise great little tool! 

Resolving patch:
diff --git a/src/cfnlint/config.py b/src/cfnlint/config.py
--- a/src/cfnlint/config.py
+++ b/src/cfnlint/config.py
@@ -265,6 +265,10 @@ def __call__(self, parser, namespace, values, option_string=None):
             '-b', '--ignore-bad-template', help='Ignore failures with Bad template',
             action='store_true'
         )
+        standard.add_argument(
+            '--ignore-templates', dest='ignore_templates',
+            help='Ignore templates', nargs='+', default=[], action='extend'
+        )
         advanced.add_argument(
             '-d', '--debug', help='Enable debug logging', action='store_true'
         )
@@ -432,6 +436,40 @@ def templates(self):
         if isinstance(filenames, six.string_types):
             filenames = [filenames]
 
+        # handle different shells and Config files
+        # some shells don't expand * and configparser won't expand wildcards
+        all_filenames = []
+        ignore_templates = self._ignore_templates()
+        for filename in filenames:
+            if sys.version_info >= (3, 5):
+                # pylint: disable=E1123
+                add_filenames = glob.glob(filename, recursive=True)
+            else:
+                add_filenames = glob.glob(filename)
+            # only way to know of the glob failed is to test it
+            # then add the filename as requested
+            if not add_filenames:
+                if filename not in ignore_templates:
+                    all_filenames.append(filename)
+            else:
+                for add_filename in add_filenames:
+                    if add_filename not in ignore_templates:
+                        all_filenames.append(add_filename)
+
+        return all_filenames
+
+    def _ignore_templates(self):
+        """ templates """
+        ignore_template_args = self._get_argument_value('ignore_templates', False, True)
+        if ignore_template_args:
+            filenames = ignore_template_args
+        else:
+            return []
+
+        # if only one is specified convert it to array
+        if isinstance(filenames, six.string_types):
+            filenames = [filenames]
+
         # handle different shells and Config files
         # some shells don't expand * and configparser won't expand wildcards
         all_filenames = []


Test patch:
diff --git a/test/module/config/test_config_mixin.py b/test/module/config/test_config_mixin.py
--- a/test/module/config/test_config_mixin.py
+++ b/test/module/config/test_config_mixin.py
@@ -101,10 +101,26 @@ def test_config_expand_paths_failure(self, yaml_mock):
         """ Test precedence in  """
 
         yaml_mock.side_effect = [
-            {'templates': ['*.yaml']},
+            {'templates': ['test/fixtures/templates/badpath/*.yaml']},
             {}
         ]
         config = cfnlint.config.ConfigMixIn([])
 
         # test defaults
-        self.assertEqual(config.templates, ['*.yaml'])
+        self.assertEqual(config.templates, ['test/fixtures/templates/badpath/*.yaml'])
+
+    @patch('cfnlint.config.ConfigFileArgs._read_config', create=True)
+    def test_config_expand_ignore_templates(self, yaml_mock):
+        """ Test ignore templates """
+
+        yaml_mock.side_effect = [
+            {
+                'templates': ['test/fixtures/templates/bad/resources/iam/*.yaml'],
+                'ignore_templates': ['test/fixtures/templates/bad/resources/iam/resource_*.yaml']},
+            {}
+        ]
+        config = cfnlint.config.ConfigMixIn([])
+
+        # test defaults
+        self.assertNotIn('test/fixtures/templates/bad/resources/iam/resource_policy.yaml', config.templates)
+        self.assertEqual(len(config.templates), 2)
diff --git a/test/module/core/test_run_cli.py b/test/module/core/test_run_cli.py
--- a/test/module/core/test_run_cli.py
+++ b/test/module/core/test_run_cli.py
@@ -81,8 +81,14 @@ def test_template_invalid_json_ignore(self):
 
         self.assertEqual(len(matches), 1)
 
-    def test_template_config(self):
+    @patch('cfnlint.config.ConfigFileArgs._read_config', create=True)
+    def test_template_config(self, yaml_mock):
         """Test template config"""
+        yaml_mock.side_effect = [
+            {},
+            {}
+        ]
+
         filename = 'test/fixtures/templates/good/core/config_parameters.yaml'
         (args, _, _,) = cfnlint.core.get_args_filenames([
             '--template', filename, '--ignore-bad-template'])
@@ -101,8 +107,14 @@ def test_template_config(self):
         self.assertEqual(args.update_documentation, False)
         self.assertEqual(args.update_specs, False)
 
-    def test_positional_template_parameters(self):
+    @patch('cfnlint.config.ConfigFileArgs._read_config', create=True)
+    def test_positional_template_parameters(self, yaml_mock):
         """Test overriding parameters"""
+        yaml_mock.side_effect = [
+            {},
+            {}
+        ]
+
         filename = 'test/fixtures/templates/good/core/config_parameters.yaml'
         (args, _, _) = cfnlint.core.get_args_filenames([
             filename, '--ignore-bad-template',
@@ -121,8 +133,14 @@ def test_positional_template_parameters(self):
         self.assertEqual(args.update_documentation, False)
         self.assertEqual(args.update_specs, False)
 
-    def test_override_parameters(self):
+    @patch('cfnlint.config.ConfigFileArgs._read_config', create=True)
+    def test_override_parameters(self, yaml_mock):
         """Test overriding parameters"""
+        yaml_mock.side_effect = [
+            {},
+            {}
+        ]
+
         filename = 'test/fixtures/templates/good/core/config_parameters.yaml'
         (args, _, _) = cfnlint.core.get_args_filenames([
             '--template', filename, '--ignore-bad-template',
@@ -141,8 +159,13 @@ def test_override_parameters(self):
         self.assertEqual(args.update_documentation, False)
         self.assertEqual(args.update_specs, False)
 
-    def test_bad_config(self):
+    @patch('cfnlint.config.ConfigFileArgs._read_config', create=True)
+    def test_bad_config(self, yaml_mock):
         """ Test bad formatting in config"""
+        yaml_mock.side_effect = [
+            {},
+            {}
+        ]
 
         filename = 'test/fixtures/templates/bad/core/config_parameters.yaml'
         (args, _, _) = cfnlint.core.get_args_filenames([