Repository: ansible/ansible-lint

Issue:
Feature Request - Add exclude-paths option
I work on a project that is slowly integrating ansible-lint into the standards. We're working on doing this on a per-role basis and what we'd like to do is have a list of paths that we don't check and slowly remove them from that list until we have no more left.

Would the project be open to a command-line option that would allow us to do something like

```
$ ansible-lint --exclude-paths=roles/roleA,roles/roleB,roles/roleC *.yml
```

Perhaps instead something like

```
$ ansible-lint --exclude-roles=roleA,roleB,roleC *.yml
```

I'd be happy to work on implementing this.



Discussion:
Hi, that sounds reasonable to me - if you want to work on it, I'd be happy to include it.

Which version do you prefer, `--exclude-paths`, `--exclude-roles`, something else?

`--exclude-paths` seems more flexible (e.g. you can exclude `role/roleA/tasks/non-compliant-include.yml`)

:+1: I'll try to tackle this tomorrow


Resolving patch:
diff --git a/lib/ansiblelint/__init__.py b/lib/ansiblelint/__init__.py
--- a/lib/ansiblelint/__init__.py
+++ b/lib/ansiblelint/__init__.py
@@ -154,22 +154,44 @@ def __repr__(self):
 
 class Runner:
 
-    def __init__(self, rules, playbooks, tags, skip_list):
+    def __init__(self, rules, playbooks, tags, skip_list, exclude_paths):
         self.rules = rules
         self.playbooks = set()
         for pb in playbooks:
             self.playbooks.add((pb, 'playbook'))
         self.tags = tags
         self.skip_list = skip_list
+        self._update_exclude_paths(exclude_paths)
+
+    def _update_exclude_paths(self, exclude_paths):
+        if exclude_paths:
+            # These will be (potentially) relative paths
+            paths = [s.strip() for s in exclude_paths]
+            # Since ansiblelint.utils.find_children returns absolute paths,
+            # and the list of files we create in `Runner.run` can contain both
+            # relative and absolute paths, we need to cover both bases.
+            self.exclude_paths = paths + [os.path.abspath(p) for p in paths]
+        else:
+            self.exclude_paths = []
+
+    def is_excluded(self, file_path):
+        # Any will short-circuit as soon as something returns True, but will
+        # be poor performance for the case where the path under question is
+        # not excluded.
+        return any(file_path.startswith(path) for path in self.exclude_paths)
 
     def run(self):
         files = list()
         for playbook in self.playbooks:
+            if self.is_excluded(playbook[0]):
+                continue
             files.append({'path': playbook[0], 'type': playbook[1]})
         visited = set()
         while (visited != self.playbooks):
             for arg in self.playbooks - visited:
                 for file in ansiblelint.utils.find_children(arg):
+                    if self.is_excluded(file['path']):
+                        continue
                     self.playbooks.add((file['path'], file['type']))
                     files.append(file)
                 visited.add(arg)


Test patch:
diff --git a/test/TestRunner.py b/test/TestRunner.py
--- a/test/TestRunner.py
+++ b/test/TestRunner.py
@@ -33,10 +33,16 @@ def setUp(self):
 
     def test_runner_count(self):
         filename = 'test/nomatchestest.txt'
-        runner = ansiblelint.Runner(self.rules, {filename}, [], [])
+        runner = ansiblelint.Runner(self.rules, {filename}, [], [], [])
         assert (len(runner.run()) == 0)
 
     def test_unicode_runner_count(self):
         filename = 'test/unicode.txt'
-        runner = ansiblelint.Runner(self.rules, {filename}, [], [])
+        runner = ansiblelint.Runner(self.rules, {filename}, [], [], [])
+        assert (len(runner.run()) == 0)
+
+    def test_runner_excludes_paths(self):
+        files = {'test/unicode.txt', 'examples/lots_of_warnings.yml'}
+        excludes = ['examples/lots_of_warnings.yml']
+        runner = ansiblelint.Runner(self.rules, files, [], [], excludes)
         assert (len(runner.run()) == 0)
diff --git a/test/TestSkippedTasks.py b/test/TestSkippedTasks.py
--- a/test/TestSkippedTasks.py
+++ b/test/TestSkippedTasks.py
@@ -34,5 +34,5 @@ def setUp(self):
 
     def test_runner_count(self):
         filename = 'test/skiptasks.txt'
-        runner = ansiblelint.Runner(self.rules, {filename}, [], [])
+        runner = ansiblelint.Runner(self.rules, {filename}, [], [], [])
         self.assertEqual(len(runner.run()), 4)
diff --git a/test/TestTaskIncludes.py b/test/TestTaskIncludes.py
--- a/test/TestTaskIncludes.py
+++ b/test/TestTaskIncludes.py
@@ -12,6 +12,6 @@ def setUp(self):
 
     def test_included_tasks(self):
         filename = 'test/taskincludes.txt'
-        runner = ansiblelint.Runner(self.rules, {filename}, [], [])
+        runner = ansiblelint.Runner(self.rules, {filename}, [], [], [])
         runner.run()
         self.assertEqual(len(runner.playbooks), 4)