Repository: aws-cloudformation/cfn-lint

Issue:
Parameter to pick the location of .cfnlintrc
Requesting to add a parameter that allows a user to specify a path to a `.cfnlintrc` file.  Right now those are only a few locations we look for a `.cfnlintrc` file.  We would like to allow a user to specify that location.


Discussion:
Hello! What's the status of this task? Is someone working on this? 
Sorry @Jhonatangiraldo.  I missed this.  I was going to pick this up this week unless you would like to take it.
So digging this into today.  The question I have is if a path is specified do we not look into any other .cfnlintrc files?  Right now we look at a users config file a "projects" config file and we merge them together.  

we have 2 options here.
1.  Merge the custom config file with the other 2
2. Not load the other 2 at all and only use the one specified.  

thoughts?
> 2. Not load the other 2 at all and only use the one specified.




Resolving patch:
diff --git a/src/cfnlint/config.py b/src/cfnlint/config.py
--- a/src/cfnlint/config.py
+++ b/src/cfnlint/config.py
@@ -48,17 +48,25 @@ class ConfigFileArgs(object):
         Parses .cfnlintrc in the Home and Project folder.
     """
     file_args = {}
+    __user_config_file = None
+    __project_config_file = None
+    __custom_config_file = None
 
-    def __init__(self, schema=None):
+    def __init__(self, schema=None, config_file=None):
         # self.file_args = self.get_config_file_defaults()
-        self.__user_config_file = None
-        self.__project_config_file = None
         self.file_args = {}
         self.default_schema_file = Path(__file__).parent.joinpath(
             'data/CfnLintCli/config/schema.json')
         with self.default_schema_file.open() as f:
             self.default_schema = json.load(f)
         self.schema = self.default_schema if not schema else schema
+
+        if config_file:
+            self.__custom_config_file = config_file
+        else:
+            LOGGER.debug('Looking for CFLINTRC before attempting to load')
+            self.__user_config_file, self.__project_config_file = self._find_config()
+
         self.load()
 
     def _find_config(self):
@@ -113,24 +121,30 @@ def load(self):
             CFLINTRC configuration
         """
 
-        LOGGER.debug('Looking for CFLINTRC before attempting to load')
-        user_config, project_config = self._find_config()
+        if self.__custom_config_file:
+            custom_config = self._read_config(self.__custom_config_file)
+            LOGGER.debug('Validating Custom CFNLINTRC')
+            self.validate_config(custom_config, self.schema)
+            LOGGER.debug('Custom configuration loaded as')
+            LOGGER.debug('%s', custom_config)
 
-        user_config = self._read_config(user_config)
-        LOGGER.debug('Validating User CFNLINTRC')
-        self.validate_config(user_config, self.schema)
+            self.file_args = custom_config
+        else:
+            user_config = self._read_config(self.__user_config_file)
+            LOGGER.debug('Validating User CFNLINTRC')
+            self.validate_config(user_config, self.schema)
 
-        project_config = self._read_config(project_config)
-        LOGGER.debug('Validating Project CFNLINTRC')
-        self.validate_config(project_config, self.schema)
+            project_config = self._read_config(self.__project_config_file)
+            LOGGER.debug('Validating Project CFNLINTRC')
+            self.validate_config(project_config, self.schema)
 
-        LOGGER.debug('User configuration loaded as')
-        LOGGER.debug('%s', user_config)
-        LOGGER.debug('Project configuration loaded as')
-        LOGGER.debug('%s', project_config)
+            LOGGER.debug('User configuration loaded as')
+            LOGGER.debug('%s', user_config)
+            LOGGER.debug('Project configuration loaded as')
+            LOGGER.debug('%s', project_config)
 
-        LOGGER.debug('Merging configurations...')
-        self.file_args = self.merge_config(user_config, project_config)
+            LOGGER.debug('Merging configurations...')
+            self.file_args = self.merge_config(user_config, project_config)
 
     def validate_config(self, config, schema):
         """Validate configuration against schema
@@ -378,6 +392,9 @@ def __call__(self, parser, namespace, values, option_string=None):
             help='Provide configuration for a rule. Format RuleId:key=value. Example: E3012:strict=false'
         )
 
+        standard.add_argument('--config-file', dest='config_file',
+                              help='Specify the cfnlintrc file to use')
+
         advanced.add_argument(
             '-o', '--override-spec', dest='override_spec',
             help='A CloudFormation Spec override file that allows customization'
@@ -459,8 +476,9 @@ def __init__(self, cli_args):
         CliArgs.__init__(self, cli_args)
         # configure debug as soon as we can
         configure_logging(self.cli_args.debug, self.cli_args.info)
-        ConfigFileArgs.__init__(self)
         TemplateArgs.__init__(self, {})
+        ConfigFileArgs.__init__(
+            self, config_file=self._get_argument_value('config_file', False, False))
 
     def _get_argument_value(self, arg_name, is_template, is_config_file):
         """ Get Argument value """
@@ -623,6 +641,11 @@ def configure_rules(self):
         """ Configure rules """
         return self._get_argument_value('configure_rules', True, True)
 
+    @property
+    def config_file(self):
+        """ Config file """
+        return self._get_argument_value('config_file', False, False)
+
     @property
     def build_graph(self):
         """ build_graph """


Test patch:
diff --git a/test/unit/module/config/test_config_file_args.py b/test/unit/module/config/test_config_file_args.py
--- a/test/unit/module/config/test_config_file_args.py
+++ b/test/unit/module/config/test_config_file_args.py
@@ -26,22 +26,34 @@ def tearDown(self):
 
     def test_config_parser_read_config(self):
         """ Testing one file successful """
-        # cfnlintrc_mock.return_value.side_effect = [Mock(return_value=True), Mock(return_value=False)]
-        config = cfnlint.config.ConfigFileArgs()
-        config_file = Path('test/fixtures/configs/cfnlintrc_read.yaml')
-        config_template = config._read_config(config_file)
+        config = cfnlint.config.ConfigFileArgs(
+            config_file=Path('test/fixtures/configs/cfnlintrc_read.yaml')
+        )
         self.assertEqual(
-            config_template,
+            config.file_args,
             {
                 'templates': ['test/fixtures/templates/good/**/*.yaml'],
                 'include_checks': ['I']
             }
         )
 
+    @patch('cfnlint.config.ConfigFileArgs._read_config', create=True)
+    def test_config_parser_read_config_only_one_read(self, yaml_mock):
+        """ Testing one file successful """
+        # Should only have one read here
+        # We aren't going to search and find the possible locations of
+        # cfnlintrc
+        yaml_mock.side_effect = [
+            {"regions": ["us-west-1"]},
+        ]
+        config = cfnlint.config.ConfigFileArgs(
+            config_file=Path('test/fixtures/configs/cfnlintrc_read.yaml')
+        )
+        self.assertEqual(config.file_args, {'regions': ['us-west-1']})
+
     @patch('cfnlint.config.ConfigFileArgs._read_config', create=True)
     def test_config_parser_read(self, yaml_mock):
         """ Testing one file successful """
-        # cfnlintrc_mock.return_value.side_effect = [Mock(return_value=True), Mock(return_value=False)]
         yaml_mock.side_effect = [
             {"regions": ["us-west-1"]},
             {}