Repository: ansible/ansible-lint

Issue:
Octal Permissions Rule, false positive ?
Hello !

# Issue Type
- Bug report

# Ansible and Ansible Lint details

```
ansible 2.2.0.0
ansible-lint 3.4.9
```

- ansible installation method: pip
- ansible-lint installation method: pip

# Desired Behaviour
- name: Ensure destination directories exist and have proper permissions

```yml
  file:
    name: /etc/ssl/private
    state: directory
    owner: root
    group: ssl-cert
    mode: 0710
```

No warning should be triggered

# Actual Behaviour

This task trigger the rule "ANSIBLE0009" because "file permission modes don't have write or execute bit set when read bit is not set", which isn't true for a folder.

On another subject I don't understand why string and int are treated differently:

```python
if isinstance(mode, six.string_types) and self.mode_regex.match(mode):
if isinstance(mode, int):
```
IMHO, we should convert string mode into int and use the same check for both.

I can make a PR if you agree with this bug report.

Regards,



Discussion:

Resolving patch:
diff --git a/lib/ansiblelint/rules/OctalPermissionsRule.py b/lib/ansiblelint/rules/OctalPermissionsRule.py
--- a/lib/ansiblelint/rules/OctalPermissionsRule.py
+++ b/lib/ansiblelint/rules/OctalPermissionsRule.py
@@ -44,15 +44,16 @@ def matchtask(self, file, task):
                 return not self.valid_mode_regex.match(mode)
             if isinstance(mode, int):
                 # sensible file permission modes don't
-                # have write or execute bit set when read bit is
-                # not set
+                # have write bit set when read bit is
+                # not set and don't have execute bit set
+                # when user execute bit is not set.
                 # also, user permissions are more generous than
                 # group permissions and user and group permissions
                 # are more generous than world permissions
 
-                result = (mode % 8 and mode % 8 < 4 or
-                          (mode >> 3) % 8 and (mode >> 3) % 8 < 4 or
-                          (mode >> 6) % 8 and (mode >> 6) % 8 < 4 or
+                result = (mode % 8 and mode % 8 < 4 and not (mode % 8 == 1 and (mode >> 6) % 8 == 1) or
+                          (mode >> 3) % 8 and (mode >> 3) % 8 < 4 and not ((mode >> 3) % 8 == 1 and (mode >> 6) % 8 == 1) or
+                          (mode >> 6) % 8 and (mode >> 6) % 8 < 4 and not (mode >> 6) % 8 == 1 or
                           mode & 8 < (mode << 3) & 8 or
                           mode & 8 < (mode << 6) & 8 or
                           (mode << 3) & 8 < (mode << 6) & 8)


Test patch:
diff --git a/test/TestOctalPermissions.py b/test/TestOctalPermissions.py
--- a/test/TestOctalPermissions.py
+++ b/test/TestOctalPermissions.py
@@ -18,4 +18,4 @@ def test_file_negative(self):
         failure = 'test/octalpermissions-failure.yml'
         bad_runner = Runner(self.collection, failure, [], [], [])
         errs = bad_runner.run()
-        self.assertEqual(4, len(errs))
+        self.assertEqual(5, len(errs))
diff --git a/test/octalpermissions-failure.yml b/test/octalpermissions-failure.yml
--- a/test/octalpermissions-failure.yml
+++ b/test/octalpermissions-failure.yml
@@ -8,6 +8,11 @@
         path: foo
         mode: 600
 
+    - name: octal permissions test fail (710)
+      file:
+        path: foo
+        mode: 710
+
     - name: octal permissions test fail (123)
       file:
         path: foo
diff --git a/test/octalpermissions-success.yml b/test/octalpermissions-success.yml
--- a/test/octalpermissions-success.yml
+++ b/test/octalpermissions-success.yml
@@ -21,5 +21,11 @@
     - name: octal permissions test success (0777)
       file: path=baz mode=0777
 
+    - name: octal permissions test success (0711)
+      file: path=baz mode=0711
+
+    - name: octal permissions test success (0710)
+      file: path=baz mode=0710
+
     - name:  permissions test success (0777)
       file: path=baz mode=u+rwx