Repository: ansible/ansible-lint

Issue:
file module, mode check request
As per http://docs.ansible.com/ansible/file_module.html:

"For those used to /usr/bin/chmod remember that modes are actually octal numbers (like 0644). Leaving off the leading zero will likely have unexpected results. "

Can we get a lint check for this?



Discussion:
It sounded like @siddhardthist was offering to create a PR for this in #99. I would certainly consider such a PR very favourably, particularly one that included a test case for the various possibilities (`mode=644`, `mode=0644`, mode=`g+rwx` etc)

@willthames I am currently working on implementing this. 


Resolving patch:
diff --git a/lib/ansiblelint/rules/OctalPermissionsRule.py b/lib/ansiblelint/rules/OctalPermissionsRule.py
new file mode 100644
--- /dev/null
+++ b/lib/ansiblelint/rules/OctalPermissionsRule.py
@@ -0,0 +1,40 @@
+# Copyright (c) 2013-2014 Will Thames <will@thames.id.au>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+from ansiblelint import AnsibleLintRule
+import re
+
+
+class OctalPermissionsRule(AnsibleLintRule):
+    id = 'ANSIBLE0008'
+    shortdesc = 'Octal file permissions must contain leading zero'
+    description = 'Numeric file permissions without leading zero can behave' + \
+        'in unexpected ways. See ' + \
+        'http://docs.ansible.com/ansible/file_module.html'
+    tags = ['formatting']
+
+    # At least an indent, "mode:", optional whitespace, any digits, EOL
+    mode_regex = re.compile(r'^\s+mode:\s*[0-9]+\s*$')
+    # Same as above, but with a leading zero before three digits
+    valid_mode_regex = re.compile(r'^\s+mode:\s*0[0-7]{3}\s*$')
+
+    def match(self, file, line):
+        if re.match(self.mode_regex, line):
+            return not re.match(self.valid_mode_regex, line)


Test patch:
diff --git a/test/TestOctalPermissions.py b/test/TestOctalPermissions.py
new file mode 100644
--- /dev/null
+++ b/test/TestOctalPermissions.py
@@ -0,0 +1,46 @@
+import unittest
+import ansiblelint.utils
+from itertools import permutations, combinations
+from ansiblelint import RulesCollection
+from ansiblelint.rules.OctalPermissionsRule import OctalPermissionsRule
+
+
+class TestOctalPermissionsRule(unittest.TestCase):
+    rule = OctalPermissionsRule()
+    one_to_seven = [str(digit) for digit in range(8)]
+    # All possible modes are any permutation of three digits in 1-7
+    bad_permutations = set(permutations(combinations(one_to_seven, 3), 3))
+    # Join tuples to strings and flatten the list
+    modes = ["".join(tup) for lst in bad_permutations for tup in lst]
+    bad_modes = ["    mode: " + mode for mode in modes]
+    # Valid modes are just the bad ones with a leading zero
+    good_modes = ["    mode: 0" + mode for mode in modes]
+
+    # Ensure that the given regex matches all octal numbers appropriately
+    def test_regex_positives(self):
+        for good in self.good_modes:
+            self.assertRegexpMatches(good, self.rule.mode_regex)
+            self.assertRegexpMatches(good, self.rule.valid_mode_regex)
+
+    def test_regex_negatives(self):
+        for bad in self.bad_modes:
+            self.assertRegexpMatches(bad, self.rule.mode_regex)
+            self.assertNotRegexpMatches(bad, self.rule.valid_mode_regex)
+
+
+class TestOctalPermissionsRuleWithFile(unittest.TestCase):
+    collection = RulesCollection()
+
+    def test_file_positive(self):
+        self.collection.register(OctalPermissionsRule())
+        success = 'test/octalpermissions-success.yml'
+        good_runner = ansiblelint.Runner(self.collection, [success], [], [], [])
+        self.assertEqual([], good_runner.run())
+
+    def test_files(self):
+        self.collection.register(OctalPermissionsRule())
+        failure = 'test/octalpermissions-failure.yml'
+        bad_runner = ansiblelint.Runner(self.collection, [failure], [], [], [])
+        errs = bad_runner.run()
+        # TODO: all errors are counted twice. Why?
+        self.assertEqual(6, len(errs))
diff --git a/test/octalpermissions-failure.yml b/test/octalpermissions-failure.yml
new file mode 100644
--- /dev/null
+++ b/test/octalpermissions-failure.yml
@@ -0,0 +1,19 @@
+---
+- hosts: hosts
+  vars:
+    varset: varset
+  tasks:
+    - name: octal permissions test fail (600)
+      file:
+        path: foo
+        mode: 600
+
+    - name: octal permissions test fail (000)
+      file:
+        path: foo
+        mode: 000
+
+    - name: octal permissions test fail (123)
+      file:
+        path: foo
+        mode: 123
diff --git a/test/octalpermissions-success.yml b/test/octalpermissions-success.yml
new file mode 100644
--- /dev/null
+++ b/test/octalpermissions-success.yml
@@ -0,0 +1,19 @@
+---
+- hosts: hosts
+  vars:
+    varset: varset
+  tasks:
+    - name: octal permissions test success (0600)
+      file:
+        path: foo
+        mode: 0600
+
+    - name: octal permissions test success (0000)
+      file:
+        path: foo
+        mode: 0000
+
+    - name: octal permissions test success (0123)
+      file:
+        path: foo
+        mode: 0123