Repository: ansible/ansible-lint

Issue:
Role linting behavior with trailing slash in input argument
You will probably find this some combination of amusing and annoying. In #193 it was discovered that inputting a role directory as an argument to ansible-lint 3.2.5 worked without a trailing slash but did not work with a trailing slash. That behavior now seems to be reversed.
# Issue Type
- Bug report
# Ansible and Ansible Lint details

```
ansible 2.1.2.0
ansible-lint 3.3.3
```
- ansible installation method: pip
- ansible-lint installation method: pip
# Desired Behaviour

Ansible-lint lints a role directory whether a trailing slash is in the argument or not.
# Actual Behaviour (Bug report only)

Given the following `main.yml`:

```
# role with issue

---
- name: ANSIBLE0014
  command: HOME=/foo /bin/echo 'hello world' creates=world
```

in `roles/foo/tasks/main.,yml`.

```
ansible-lint -p roles/foo/
path/to/roles/foo/tasks/main.yml:3: [EANSIBLE0014] Environment variables don't work as part of command

ansible-lint -p roles/foo
(no output)
```



Discussion:
I think I linked to a PR that should fix this, but this will be my first attempt at a PR through github, so I am not certain I got it right.

The problem seems to be that if a role gets passed as `roles/example` then the [`os.path.dirname(playbook[0])`](https://github.com/willthames/ansible-lint/blob/master/lib/ansiblelint/utils.py#L168) call will return `roles`, but if you provide `roles/examples/` it will return `roles/example`.

If you adjust the [`self.playbooks.add((playbook, 'role'))`](https://github.com/willthames/ansible-lint/blob/master/lib/ansiblelint/__init__.py#L188) to `self.playbooks.add((os.path.join(playbook,''), 'role'))` you will get a path with a trailing slash no matter what.  And so the later call to dirname will be return the correct path to the role directory.

Resolving patch:
diff --git a/lib/ansiblelint/utils.py b/lib/ansiblelint/utils.py
--- a/lib/ansiblelint/utils.py
+++ b/lib/ansiblelint/utils.py
@@ -307,7 +307,8 @@ def _rolepath(basedir, role):
         path_dwim(
             basedir, os.path.join('..', '..', '..', 'roles', role)
         ),
-        path_dwim(basedir, os.path.join('..', '..', role))
+        path_dwim(basedir, os.path.join('..', '..', role)),
+        path_dwim(basedir, ''),
     ]
 
     if constants.DEFAULT_ROLES_PATH:


Test patch:
diff --git a/test/TestCliRolePaths.py b/test/TestCliRolePaths.py
new file mode 100644
--- /dev/null
+++ b/test/TestCliRolePaths.py
@@ -0,0 +1,78 @@
+import unittest
+import subprocess
+import os
+
+
+class TestCliRolePaths(unittest.TestCase):
+    def setUp(self):
+        self.local_test_dir = os.path.dirname(os.path.realpath(__file__))
+
+    def run_ansible_lint(self, cwd, bin, role_path):
+        command = '{} {}'.format(bin, role_path)
+
+        result, err = subprocess.Popen(
+            [command],
+            cwd=cwd,
+            stdin=subprocess.PIPE,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+            shell=True
+        ).communicate()
+
+        self.assertFalse(err, 'Expected no error but was ' + str(err))
+
+        return result
+
+    def test_run_single_role_path_no_trailing_slash(self):
+        cwd = self.local_test_dir
+        bin = '../bin/ansible-lint'
+        role_path = 'test-role'
+
+        result = self.run_ansible_lint(cwd=cwd, bin=bin, role_path=role_path)
+        self.assertIn('Use shell only when shell functionality is required',
+                      str(result))
+
+    def test_run_single_role_path_with_trailing_slash(self):
+        cwd = self.local_test_dir
+        bin = '../bin/ansible-lint'
+        role_path = 'test-role/'
+
+        result = self.run_ansible_lint(cwd=cwd, bin=bin, role_path=role_path)
+        self.assertIn('Use shell only when shell functionality is required',
+                      str(result))
+
+    def test_run_multiple_role_path_no_trailing_slash(self):
+        cwd = self.local_test_dir
+        bin = '../bin/ansible-lint'
+        role_path = 'roles/test-role'
+
+        result = self.run_ansible_lint(cwd=cwd, bin=bin, role_path=role_path)
+        self.assertIn('Use shell only when shell functionality is required',
+                      str(result))
+
+    def test_run_multiple_role_path_with_trailing_slash(self):
+        cwd = self.local_test_dir
+        bin = '../bin/ansible-lint'
+        role_path = 'roles/test-role/'
+
+        result = self.run_ansible_lint(cwd=cwd, bin=bin, role_path=role_path)
+        self.assertIn('Use shell only when shell functionality is required',
+                      str(result))
+
+    def test_run_inside_role_dir(self):
+        cwd = os.path.join(self.local_test_dir, 'test-role/')
+        bin = '../../bin/ansible-lint'
+        role_path = '.'
+
+        result = self.run_ansible_lint(cwd=cwd, bin=bin, role_path=role_path)
+        self.assertIn('Use shell only when shell functionality is required',
+                      str(result))
+
+    def test_run_role_three_dir_deep(self):
+        cwd = self.local_test_dir
+        bin = '../bin/ansible-lint'
+        role_path = 'roles/roles/test-role'
+
+        result = self.run_ansible_lint(cwd=cwd, bin=bin, role_path=role_path)
+        self.assertIn('Use shell only when shell functionality is required',
+                      str(result))
diff --git a/test/roles/roles/test-role/tasks/main.yml b/test/roles/roles/test-role/tasks/main.yml
new file mode 100644
--- /dev/null
+++ b/test/roles/roles/test-role/tasks/main.yml
@@ -0,0 +1,2 @@
+- name: shell instead of command
+  shell: echo hello world
diff --git a/test/roles/test-role/tasks/main.yml b/test/roles/test-role/tasks/main.yml
new file mode 100644
--- /dev/null
+++ b/test/roles/test-role/tasks/main.yml
@@ -0,0 +1,2 @@
+- name: shell instead of command
+  shell: echo hello world