diff --git a/.planned/rules/ADFINIS0002RegisterPrefixRule.py b/.planned/rules/ADFINIS0002RegisterPrefixRule.py
new file mode 100644
index 0000000..366aa23
--- /dev/null
+++ b/.planned/rules/ADFINIS0002RegisterPrefixRule.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+# Copyright (c) 2017, Adfinis SyGroup AG
+# All rights reserved.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+# src-repo: https://github.com/adfinis-sygroup/ansible-lint-rules/blob/master/RegisterPrefixRule.py
+
+from ansiblelint import AnsibleLintRule
+
+
+class RegisterPrefixRule(AnsibleLintRule):
+ id = 'ADFINIS0002'
+ shortdesc = "Registered variable must always have a prefix " \
+ "\"$ROLENAME_register_\""
+ description = """
+ Each registered variable name must have a prefix "$ROLENAME_register_",
+ because each variable should be unique.
+ """
+ tags = ['formatting']
+
+ def matchtask(self, file, task):
+ if 'register' in task:
+ register = task['register']
+ try:
+ role = self.rolename(file['path'])
+ prefix = '{0}_register_'.format(role)
+ except BaseException:
+ prefix = 'register_'
+ return not register.startswith(prefix)
+ return False
+
+ def rolename(self, file):
+ elements = file.split('/')
+ if 'tasks' in elements:
+ idx = elements.index('tasks') - 1
+ return elements[idx]
+ raise IndexError('tasks not in path')
diff --git a/.planned/rules/TWSH101PlaybookExtension.py b/.planned/rules/TWSH101PlaybookExtension.py
new file mode 100644
index 0000000..a7efe10
--- /dev/null
+++ b/.planned/rules/TWSH101PlaybookExtension.py
@@ -0,0 +1,21 @@
+from ansiblelint import AnsibleLintRule
+
+import os
+
+class PlaybookExtension(AnsibleLintRule):
+ id = 'TWSH101'
+ shortdesc = 'Playbooks should have the ".yml" extension'
+ description = ''
+ tags = ['playbook']
+ done = [] # already noticed path list
+
+ def match(self, file, text):
+ if file['type'] != 'playbook':
+ return False
+
+ path = file['path']
+ ext = os.path.splitext(path)
+ if ext[1] != ".yml" and path not in self.done:
+ self.done.append(path)
+ return True
+ return False
diff --git a/.planned/rules/TWSH201RoleRelativePath.py b/.planned/rules/TWSH201RoleRelativePath.py
new file mode 100644
index 0000000..ff7b0e2
--- /dev/null
+++ b/.planned/rules/TWSH201RoleRelativePath.py
@@ -0,0 +1,42 @@
+from ansiblelint import AnsibleLintRule
+
+
+format = "{}"
+
+class RoleRelativePath(AnsibleLintRule):
+ id = 'TWSH201'
+ shortdesc = "Doesn't need a relative path in role"
+ description = ''
+ tags = ['role']
+
+ def matchplay(self, file, play):
+ # assume if 'roles' in path, inside a role.
+ if 'roles' not in file['path']:
+ return []
+ if 'template' in play:
+ if not isinstance(play['template'], dict):
+ return False
+ if "../templates" in play['template']['src']:
+ return [({'': play['template']},
+ self.shortdesc)]
+ if 'win_template' in play:
+ if not isinstance(play['win_template'], dict):
+ return False
+ if "../win_templates" in play['win_template']['src']:
+ return ({'win_template': play['win_template']},
+ self.shortdesc)
+ if 'copy' in play:
+ if not isinstance(play['copy'], dict):
+ return False
+ if 'src' in play['copy']:
+ if "../files" in play['copy']['src']:
+ return ({'sudo': play['copy']},
+ self.shortdesc)
+ if 'win_copy' in play:
+ if not isinstance(play['win_copy'], dict):
+ return False
+ if "../files" in play['win_copy']['src']:
+ return ({'sudo': play['win_copy']},
+ self.shortdesc)
+ return []
+
diff --git a/.planned/rules/TWSH301TaskShouldHaveName.py b/.planned/rules/TWSH301TaskShouldHaveName.py
new file mode 100644
index 0000000..a25acfe
--- /dev/null
+++ b/.planned/rules/TWSH301TaskShouldHaveName.py
@@ -0,0 +1,32 @@
+# Copyright (c) 2016 Will Thames
+#
+# 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
+
+
+class TaskShouldHaveName(AnsibleLintRule):
+ id = 'TWSH301'
+ shortdesc = 'All tasks should be named'
+ description = 'All tasks should have a distinct name for readability ' + \
+ 'and for --start-at-task to work'
+ tags = ['task']
+
+ def matchtask(self, file, task):
+ return task.get('name', '') == ''
diff --git a/.planned/rules/TWSH302TaskIncludeShouldHaveTags.py b/.planned/rules/TWSH302TaskIncludeShouldHaveTags.py
new file mode 100644
index 0000000..9975f8c
--- /dev/null
+++ b/.planned/rules/TWSH302TaskIncludeShouldHaveTags.py
@@ -0,0 +1,17 @@
+from ansiblelint import AnsibleLintRule
+
+# FIXME: how to get include task?
+class TaskIncludeShouldHaveTags(AnsibleLintRule):
+ id = 'TWSH302'
+ shortdesc = 'Include should have tags'
+ description = ''
+ tags = ['task']
+
+ def matchplay(self, file, play):
+ ret = []
+
+ if isinstance(play, dict) and 'tasks' in play:
+ for item in play['tasks']:
+ if 'include' in item and 'tags' not in item:
+ ret.append((file, self.shortdesc))
+ return ret
diff --git a/.planned/rules/TWSH303TaskManyArgs.py b/.planned/rules/TWSH303TaskManyArgs.py
new file mode 100644
index 0000000..3cb3301
--- /dev/null
+++ b/.planned/rules/TWSH303TaskManyArgs.py
@@ -0,0 +1,18 @@
+from ansiblelint import AnsibleLintRule
+
+class TaskManyArgs(AnsibleLintRule):
+ id = 'TWSH303'
+ shortdesc = 'Use ":" YAML format when arguments are over 4'
+ description = ''
+ tags = ['task']
+
+ def match(self, file, text):
+ count = 0
+ for action in text.split(" "):
+ if "=" in action:
+ count += 1
+
+ if count > 4:
+ return True
+
+ return False
diff --git a/.planned/rules/TWSH304TaskNoLocalAction.py b/.planned/rules/TWSH304TaskNoLocalAction.py
new file mode 100644
index 0000000..bdfada5
--- /dev/null
+++ b/.planned/rules/TWSH304TaskNoLocalAction.py
@@ -0,0 +1,12 @@
+from ansiblelint import AnsibleLintRule
+
+class TaskNoLocalAction(AnsibleLintRule):
+ id = 'TWSH304'
+ shortdesc = 'Do not use local_action. use delegate_to: localhost instead'
+ description = ''
+ tags = ['task']
+
+ def match(self, file, text):
+ if 'local_action' in text:
+ return True
+ return False
diff --git a/.planned/rules/TWSH305TaskVariableHasSpace.py b/.planned/rules/TWSH305TaskVariableHasSpace.py
new file mode 100644
index 0000000..ebdc255
--- /dev/null
+++ b/.planned/rules/TWSH305TaskVariableHasSpace.py
@@ -0,0 +1,19 @@
+from ansiblelint import AnsibleLintRule
+
+import re
+
+class TaskVariableHasSpace(AnsibleLintRule):
+ id = 'TWSH305'
+ shortdesc = 'Variables should be enclosed by spaces "{{ foo }}"'
+ description = ''
+ tags = ['task']
+
+ compiled = re.compile(ur'{{(\w*)}}')
+
+ def match(self, file, text):
+ m = self.compiled.search(text)
+ if m:
+ return True
+ return False
+
+
diff --git a/.planned/rules/TWSH401ModuleOctalPermissions.py b/.planned/rules/TWSH401ModuleOctalPermissions.py
new file mode 100644
index 0000000..b144089
--- /dev/null
+++ b/.planned/rules/TWSH401ModuleOctalPermissions.py
@@ -0,0 +1,59 @@
+# Copyright (c) 2013-2014 Will Thames
+#
+# 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 ModuleOctalPermissions(AnsibleLintRule):
+ id = 'TWSH401'
+ 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 = ['module']
+
+ _modules = {'assemble', 'copy', 'file', 'ini_file', 'lineinfile',
+ 'replace', 'synchronize', 'template', 'unarchive'}
+
+ mode_regex = re.compile(r'^\s*[0-9]+\s*$')
+ valid_mode_regex = re.compile(r'^\s*0[0-7]{3,4}\s*$')
+
+ def matchtask(self, file, task):
+ if task["action"]["__ansible_module__"] in self._modules:
+ mode = task['action'].get('mode', None)
+ if isinstance(mode, basestring) and self.mode_regex.match(mode):
+ 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
+ # 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
+ mode & 8 < (mode << 3) & 8 or
+ mode & 8 < (mode << 6) & 8 or
+ (mode << 3) & 8 < (mode << 6) & 8)
+
+ return result
diff --git a/.planned/rules/TWSH402ModuleTemplateExt.py b/.planned/rules/TWSH402ModuleTemplateExt.py
new file mode 100644
index 0000000..a4ed00a
--- /dev/null
+++ b/.planned/rules/TWSH402ModuleTemplateExt.py
@@ -0,0 +1,16 @@
+from ansiblelint import AnsibleLintRule
+import os
+
+class ModuleTemplateExt(AnsibleLintRule):
+ id = 'TWSH402'
+ shortdesc = "Template files should have the extension '.j2' "
+ description = ''
+ tags = ['module']
+
+ def matchtask(self, file, task):
+ if task['action']['__ansible_module__'] != 'template':
+ return False
+ ext = os.path.splitext(task['action']['src'])
+ if ext[1] != ".j2":
+ return True
+ return False
diff --git a/.planned/rules/TWSH501ShellAltChmod.py b/.planned/rules/TWSH501ShellAltChmod.py
new file mode 100644
index 0000000..c37efde
--- /dev/null
+++ b/.planned/rules/TWSH501ShellAltChmod.py
@@ -0,0 +1,14 @@
+from ansiblelint import AnsibleLintRule
+
+class ShellAltChmod(AnsibleLintRule):
+ id = 'TWSH501'
+ shortdesc = 'Use chmod module'
+ description = ''
+ tags = ['shell']
+
+ def matchtask(self, file, task):
+ if task['action']['__ansible_module__'] not in ['shell', 'command']:
+ return False
+ if 'chmod' in task['action']['__ansible_arguments__']:
+ return True
+ return False
diff --git a/.planned/rules/TWSH502ShellAltChown.py b/.planned/rules/TWSH502ShellAltChown.py
new file mode 100644
index 0000000..c8ceaca
--- /dev/null
+++ b/.planned/rules/TWSH502ShellAltChown.py
@@ -0,0 +1,14 @@
+from ansiblelint import AnsibleLintRule
+
+class ShellAltChown(AnsibleLintRule):
+ id = 'TWSH502'
+ shortdesc = 'Use chown module'
+ description = ''
+ tags = ['shell']
+
+ def matchtask(self, file, task):
+ if task['action']['__ansible_module__'] not in ['shell', 'command']:
+ return False
+ if 'chown' in task['action']['__ansible_arguments__']:
+ return True
+ return False
diff --git a/.planned/rules/TWSH503ShellAltHostname.py b/.planned/rules/TWSH503ShellAltHostname.py
new file mode 100644
index 0000000..33281d6
--- /dev/null
+++ b/.planned/rules/TWSH503ShellAltHostname.py
@@ -0,0 +1,15 @@
+from ansiblelint import AnsibleLintRule
+
+class ShellAltHostname(AnsibleLintRule):
+ id = 'TWSH503'
+ shortdesc = 'Use hostname module'
+ description = ''
+ tags = ['shell']
+
+ def matchtask(self, file, task):
+ if task['action']['__ansible_module__'] not in ['shell', 'command']:
+ return False
+ if ('hostname' in task['action']['__ansible_arguments__'] and
+ 'register' not in task):
+ return True
+ return False
diff --git a/.planned/rules/TWSH504ShellAltMount.py b/.planned/rules/TWSH504ShellAltMount.py
new file mode 100644
index 0000000..e960d32
--- /dev/null
+++ b/.planned/rules/TWSH504ShellAltMount.py
@@ -0,0 +1,15 @@
+from ansiblelint import AnsibleLintRule
+
+class ShellAltMount(AnsibleLintRule):
+ id = 'TWSH504'
+ shortdesc = 'Use mount module'
+ description = ''
+ tags = ['shell']
+
+ def matchtask(self, file, task):
+ if task['action']['__ansible_module__'] not in ['shell', 'command']:
+ return False
+ if ('mount' in task['action']['__ansible_arguments__'] and
+ 'register' not in task):
+ return True
+ return False
diff --git a/.planned/rules/TWSH505ShellAltNmcli.py b/.planned/rules/TWSH505ShellAltNmcli.py
new file mode 100644
index 0000000..ee0e9cf
--- /dev/null
+++ b/.planned/rules/TWSH505ShellAltNmcli.py
@@ -0,0 +1,14 @@
+from ansiblelint import AnsibleLintRule
+
+class ShellAltNmcli(AnsibleLintRule):
+ id = 'TWSH505'
+ shortdesc = 'Use nmcli module'
+ description = ''
+ tags = ['shell']
+
+ def matchtask(self, file, task):
+ if task['action']['__ansible_module__'] not in ['shell', 'command']:
+ return False
+ if 'nmcli' in task['action']['__ansible_arguments__']:
+ return True
+ return False
diff --git a/.planned/rules/TWSH506ShellAltRpm.py b/.planned/rules/TWSH506ShellAltRpm.py
new file mode 100644
index 0000000..bdfdec4
--- /dev/null
+++ b/.planned/rules/TWSH506ShellAltRpm.py
@@ -0,0 +1,14 @@
+from ansiblelint import AnsibleLintRule
+
+class ShellAltRpm(AnsibleLintRule):
+ id = 'TWSH506'
+ shortdesc = 'Use yum module with file path'
+ description = ''
+ tags = ['shell']
+
+ def matchtask(self, file, task):
+ if task['action']['__ansible_module__'] not in ['shell', 'command']:
+ return False
+ if 'rpm' in task['action']['__ansible_arguments__']:
+ return True
+ return False
diff --git a/.planned/rules/TWSH507ShellAltService.py b/.planned/rules/TWSH507ShellAltService.py
new file mode 100644
index 0000000..1bd0533
--- /dev/null
+++ b/.planned/rules/TWSH507ShellAltService.py
@@ -0,0 +1,20 @@
+from ansiblelint import AnsibleLintRule
+
+class ShellAltService(AnsibleLintRule):
+ id = 'TWSH507'
+ shortdesc = 'Use service module'
+ description = ''
+ tags = ['shell']
+
+ def matchtask(self, file, task):
+ if task['action']['__ansible_module__'] not in ['shell', 'command']:
+ return False
+ args = task['action']['__ansible_arguments__']
+
+ if 'service' in args:
+ return True
+ if 'systemctl' in args:
+ return True
+ if '/etc/rc.d/init.d/' in args:
+ return True
+ return False
diff --git a/.planned/rules/TWSH508ShellAltSysctl.py b/.planned/rules/TWSH508ShellAltSysctl.py
new file mode 100644
index 0000000..909830c
--- /dev/null
+++ b/.planned/rules/TWSH508ShellAltSysctl.py
@@ -0,0 +1,15 @@
+from ansiblelint import AnsibleLintRule
+
+class ShellAltSysctl(AnsibleLintRule):
+ id = 'TWSH508'
+ shortdesc = 'Use sysctl module'
+ description = ''
+ tags = ['shell']
+
+ def matchtask(self, file, task):
+ if task['action']['__ansible_module__'] not in ['shell', 'command']:
+ return False
+ if ('sysctl' in task['action']['__ansible_arguments__'] and
+ 'register' not in task):
+ return True
+ return False
diff --git a/.planned/rules/TWSH509ShellAltUfw.py b/.planned/rules/TWSH509ShellAltUfw.py
new file mode 100644
index 0000000..0c63c50
--- /dev/null
+++ b/.planned/rules/TWSH509ShellAltUfw.py
@@ -0,0 +1,14 @@
+from ansiblelint import AnsibleLintRule
+
+class ShellAltUfw(AnsibleLintRule):
+ id = 'TWSH509'
+ shortdesc = 'Use ufw module'
+ description = ''
+ tags = ['shell']
+
+ def matchtask(self, file, task):
+ if task['action']['__ansible_module__'] not in ['shell', 'command']:
+ return False
+ if 'ufw' in task['action']['__ansible_arguments__']:
+ return True
+ return False
diff --git a/.planned/rules/TWSH510ShellAltUnarchive.py b/.planned/rules/TWSH510ShellAltUnarchive.py
new file mode 100644
index 0000000..b6a5f02
--- /dev/null
+++ b/.planned/rules/TWSH510ShellAltUnarchive.py
@@ -0,0 +1,17 @@
+from ansiblelint import AnsibleLintRule
+
+class ShellAltUnarchive(AnsibleLintRule):
+ id = 'TWSH510'
+ shortdesc = 'Use unarchive module'
+ description = ''
+ tags = ['shell']
+
+ def matchtask(self, file, task):
+ if task['action']['__ansible_module__'] not in ['shell', 'command']:
+ return False
+ args = task['action']['__ansible_arguments__']
+ if 'unzip' in args:
+ return True
+ if 'tar' in args and 'xf' in args:
+ return True
+ return False
diff --git a/.planned/rules/TWSH511ShellHasCreates.py b/.planned/rules/TWSH511ShellHasCreates.py
new file mode 100644
index 0000000..dcf6cfb
--- /dev/null
+++ b/.planned/rules/TWSH511ShellHasCreates.py
@@ -0,0 +1,16 @@
+from ansiblelint import AnsibleLintRule
+
+class ShellHasCreates(AnsibleLintRule):
+ id = 'TWSH511'
+ shortdesc = 'Shell/command module must contain creates or removes'
+ description = ''
+ tags = ['shell']
+
+ def matchtask(self, file, task):
+ if task['action']['__ansible_module__'] not in ['shell', 'command']:
+ return False
+ if 'creates' in task['action'] or 'removes' in task['action']:
+ return False
+ if 'register' in task:
+ return False
+ return True
diff --git a/.planned/rules/TWSH512ShellAltFile.py b/.planned/rules/TWSH512ShellAltFile.py
new file mode 100644
index 0000000..48d22df
--- /dev/null
+++ b/.planned/rules/TWSH512ShellAltFile.py
@@ -0,0 +1,17 @@
+from ansiblelint import AnsibleLintRule
+
+class ShellAltFile(AnsibleLintRule):
+ id = 'TWSH512'
+ shortdesc = 'Use file module instead of mkdir, ln -s and so on'
+ description = ''
+ tags = ['shell']
+ alt_commands = ["ln -s", "mkdir"]
+
+ def matchtask(self, file, task):
+ if task['action']['__ansible_module__'] not in ['shell', 'command']:
+ return False
+ for c in self.alt_commands:
+ if c in task['action']['__ansible_arguments__']:
+ self.shortdesc += " ({})".format(c)
+ return True
+ return False
diff --git a/.planned/rules/TWSH513ShellAltPatch.py b/.planned/rules/TWSH513ShellAltPatch.py
new file mode 100644
index 0000000..eaf0431
--- /dev/null
+++ b/.planned/rules/TWSH513ShellAltPatch.py
@@ -0,0 +1,14 @@
+from ansiblelint import AnsibleLintRule
+
+class ShellAltPatch(AnsibleLintRule):
+ id = 'TWSH513'
+ shortdesc = 'Use patch module'
+ description = ''
+ tags = ['shell']
+
+ def matchtask(self, file, task):
+ if task['action']['__ansible_module__'] not in ['shell', 'command']:
+ return False
+ if 'patch' in task['action']['__ansible_arguments__']:
+ return True
+ return False
diff --git a/.planned/rules/TWSH601TrailingWhitespaceRule.py b/.planned/rules/TWSH601TrailingWhitespaceRule.py
new file mode 100644
index 0000000..1f2ab78
--- /dev/null
+++ b/.planned/rules/TWSH601TrailingWhitespaceRule.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2013-2014 Will Thames
+#
+# 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
+
+
+class TrailingWhitespaceRule(AnsibleLintRule):
+ id = 'TWSH601'
+ shortdesc = 'Trailing whitespace'
+ description = 'There should not be any trailing whitespace'
+ tags = ['formatting']
+
+ def match(self, file, line):
+ return line.rstrip() != line
diff --git a/.planned/rules/TWSH602LineTooLong.py b/.planned/rules/TWSH602LineTooLong.py
new file mode 100644
index 0000000..009528c
--- /dev/null
+++ b/.planned/rules/TWSH602LineTooLong.py
@@ -0,0 +1,14 @@
+from ansiblelint import AnsibleLintRule
+
+
+class LineTooLong(AnsibleLintRule):
+ id = 'TWSH602'
+ shortdesc = 'Line too long'
+ description = 'Line too long'
+ tags = ['formatting']
+
+ def match(self, file, line):
+ if len(line) > 80:
+ self.shortdesc += " ({} characters)".format(len(line))
+ return True
+ return False
diff --git a/.planned/tests/formatting.yml b/.planned/tests/formatting.yml
new file mode 100644
index 0000000..c7b5510
--- /dev/null
+++ b/.planned/tests/formatting.yml
@@ -0,0 +1,5 @@
+- hosts: localhost
+ gather_facts: no
+ tasks:
+ - name: line is toooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo long
+ debug: msg="line too long"
diff --git a/.planned/tests/included.yml b/.planned/tests/included.yml
new file mode 100644
index 0000000..b78c2ac
--- /dev/null
+++ b/.planned/tests/included.yml
@@ -0,0 +1,2 @@
+- name: dummy
+ debug: msg="dummy"
diff --git a/.planned/tests/modules.yml b/.planned/tests/modules.yml
new file mode 100644
index 0000000..906f367
--- /dev/null
+++ b/.planned/tests/modules.yml
@@ -0,0 +1,5 @@
+- name: playbook for roles category
+ hosts: localhost
+ gather_facts: no
+ tasks:
+ - template: src="hoge.noj2" dest="/tmp/hoge"
diff --git a/.planned/tests/roles.retry b/.planned/tests/roles.retry
new file mode 100644
index 0000000..2fbb50c
--- /dev/null
+++ b/.planned/tests/roles.retry
@@ -0,0 +1 @@
+localhost
diff --git a/.planned/tests/roles.yml b/.planned/tests/roles.yml
new file mode 100644
index 0000000..435d664
--- /dev/null
+++ b/.planned/tests/roles.yml
@@ -0,0 +1,5 @@
+- name: playbook for roles category
+ hosts: localhost
+ gather_facts: no
+ roles:
+ - relative
diff --git a/.planned/tests/shell.yml b/.planned/tests/shell.yml
new file mode 100644
index 0000000..b777f7b
--- /dev/null
+++ b/.planned/tests/shell.yml
@@ -0,0 +1,23 @@
+- name: playbook for shell category
+ hosts: localhost
+ gather_facts: no
+ tasks:
+ - name: does not have creates
+ shell: echo "hoge"
+ - name: use mount
+ shell: mount
+ args:
+ creates: /tmp
+ - name: use sysctl module
+ shell: sysctl -a
+ args:
+ creates: /tmp
+ - name: use file module to chown
+ shell: chown -R hoge .
+ args:
+ creates: /tmp
+ - name: use file module to chmod
+ shell: chmod ugo+r .
+ args:
+ creates: /tmp
+
diff --git a/.planned/tests/tasks.yml b/.planned/tests/tasks.yml
new file mode 100644
index 0000000..038c0f7
--- /dev/null
+++ b/.planned/tests/tasks.yml
@@ -0,0 +1,14 @@
+- name: playbook for task category
+ hosts: localhost
+ gather_facts: no
+ vars:
+ foo: "tasks"
+ tasks:
+ - name: use local action
+ local_action: file path=/tmp/tasks state=touch
+ - name: many argument with =
+ file: path="/tmp/tasks" state=touch group=wheel mode=0700 force=yes
+ - name: variable with no space
+ file: path="/tmp/{{foo}}" state=touch
+ - name: variable with space # lint does not match
+ file: path="/tmp/{{ foo }}" state=touch