├── .gitignore ├── LICENSE ├── Makefile ├── README.rst ├── rules ├── LineTooLong.py ├── ModuleOctalPermissions.py ├── ModuleTemplateExt.py ├── PlaybookExtension.py ├── RoleRelativePath.py ├── ShellAltChmod.py ├── ShellAltChown.py ├── ShellAltFile.py ├── ShellAltHostname.py ├── ShellAltMount.py ├── ShellAltNmcli.py ├── ShellAltPatch.py ├── ShellAltRpm.py ├── ShellAltService.py ├── ShellAltSysctl.py ├── ShellAltUfw.py ├── ShellAltUnarchive.py ├── ShellHasCreates.py ├── TaskIncludeShouldHaveTags.py ├── TaskManyArgs.py ├── TaskNoLocalAction.py ├── TaskShouldHaveName.py ├── TaskVariableHasSpace.py ├── TrailingWhitespaceRule.py └── __init__.py └── tests ├── formatting.yml ├── included.yml ├── modules.yml ├── roles.retry ├── roles.yml ├── roles └── relative │ ├── tasks │ ├── included.yml │ └── main.yml │ └── templates │ └── hoge.yml ├── shell.yml └── tasks.yml /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | #* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Tsukinowa Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | 3 | test: 4 | ansible-lint -r rules tests/*.yml 5 | 6 | list_tags: 7 | ansible-lint -r rules -T 8 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. note:: 2 | 3 | This repository has no longer maintained. 4 | 5 | Please use some forks, e.g. `lean-delivery/ansible-lint-rules `_ 6 | 7 | 8 | ==================================== 9 | Rules for ansible-lint 10 | ==================================== 11 | 12 | This is a rule set for `ansible-lint `_ . 13 | 14 | These rules are used in the `Tsukinowa Inc. `_ , but anyone can use with the license (MIT). 15 | 16 | How to use 17 | ---------------- 18 | 19 | 1. Install `ansible-lint` (ex: `pip install ansible-lint`) 20 | 2. Copy or `git clone` on your ansible playbook repository with `rules` name 21 | 3. Run ansible lint with `-r rules` flag (ex: `ansible-lint -r rules `) 22 | 23 | 24 | Rules 25 | ========= 26 | 27 | +------------+----------------------------------------------------------------------+ 28 | |code |sample message | 29 | +============+======================================================================+ 30 | |**E1** |*playbook* | 31 | +------------+----------------------------------------------------------------------+ 32 | |E101 |Playbook should has ".yml" extension | 33 | +------------+----------------------------------------------------------------------+ 34 | +------------+----------------------------------------------------------------------+ 35 | |**E2** |*Role* | 36 | +------------+----------------------------------------------------------------------+ 37 | |E201 |Doesn't need a relative path in role | 38 | +------------+----------------------------------------------------------------------+ 39 | +------------+----------------------------------------------------------------------+ 40 | |**E3** |*Task* | 41 | +------------+----------------------------------------------------------------------+ 42 | |E301 |All tasks should be named | 43 | +------------+----------------------------------------------------------------------+ 44 | |E302 |Include should has tags | 45 | +------------+----------------------------------------------------------------------+ 46 | |E303 |Use ":" YAML syntax when arguments are over 4 | 47 | +------------+----------------------------------------------------------------------+ 48 | |E304 |Do not use local_action. use delegate_to: localhost instead | 49 | +------------+----------------------------------------------------------------------+ 50 | |E305 |Variable should has space "{{ foo }}" | 51 | +------------+----------------------------------------------------------------------+ 52 | +------------+----------------------------------------------------------------------+ 53 | |**E4** |*Module* | 54 | +------------+----------------------------------------------------------------------+ 55 | |E401 |Octal file permissions must contain leading zero | 56 | +------------+----------------------------------------------------------------------+ 57 | |E402 |Template file should has '.j2' extension | 58 | +------------+----------------------------------------------------------------------+ 59 | +------------+----------------------------------------------------------------------+ 60 | |**E5** |*Shell/Command alternative module* | 61 | +------------+----------------------------------------------------------------------+ 62 | |E501 |Use chmod module | 63 | +------------+----------------------------------------------------------------------+ 64 | |E502 |Use chown module | 65 | +------------+----------------------------------------------------------------------+ 66 | |E503 |Use hostname module | 67 | +------------+----------------------------------------------------------------------+ 68 | |E504 |Use mount module | 69 | +------------+----------------------------------------------------------------------+ 70 | |E505 |Use nmcli module | 71 | +------------+----------------------------------------------------------------------+ 72 | |E506 |Use yum module with file path | 73 | +------------+----------------------------------------------------------------------+ 74 | |E507 |Use service module | 75 | +------------+----------------------------------------------------------------------+ 76 | |E508 |Use sysctl module | 77 | +------------+----------------------------------------------------------------------+ 78 | |E509 |Use ufw module | 79 | +------------+----------------------------------------------------------------------+ 80 | |E510 |Use unarchive module | 81 | +------------+----------------------------------------------------------------------+ 82 | |E511 |Shell/command module must contain creates or removes | 83 | +------------+----------------------------------------------------------------------+ 84 | |E512 |Use file module instead of mkdir, ln -s and so on | 85 | +------------+----------------------------------------------------------------------+ 86 | +------------+----------------------------------------------------------------------+ 87 | |**E6** |*Formatting* | 88 | +------------+----------------------------------------------------------------------+ 89 | |E601 |Trailing whitespace | 90 | +------------+----------------------------------------------------------------------+ 91 | |E602 |Line too long | 92 | +------------+----------------------------------------------------------------------+ 93 | +------------+----------------------------------------------------------------------+ 94 | 95 | 96 | Why so many shell module lint? 97 | --------------------------------------------------------- 98 | 99 | Because user may want to use a command to correct use. Since we separete these rule, user can disable specific rule easily. 100 | 101 | If you can manage playbook your self, consider set `skip_ansible_lint` tag. 102 | 103 | 104 | 105 | 106 | License 107 | ============== 108 | 109 | MIT License (same as ansible-lint) 110 | -------------------------------------------------------------------------------- /rules/LineTooLong.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | 4 | class LineTooLong(AnsibleLintRule): 5 | id = 'E602' 6 | shortdesc = 'Line too long' 7 | description = 'Line too long' 8 | tags = ['formatting'] 9 | 10 | def match(self, file, line): 11 | if len(line) > 80: 12 | self.shortdesc += " ({} characters)".format(len(line)) 13 | return True 14 | return False 15 | -------------------------------------------------------------------------------- /rules/ModuleOctalPermissions.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2014 Will Thames 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from ansiblelint import AnsibleLintRule 22 | import re 23 | 24 | 25 | class ModuleOctalPermissions(AnsibleLintRule): 26 | id = 'E401' 27 | shortdesc = 'Octal file permissions must contain leading zero' 28 | description = 'Numeric file permissions without leading zero can behave' + \ 29 | 'in unexpected ways. See ' + \ 30 | 'http://docs.ansible.com/ansible/file_module.html' 31 | tags = ['module'] 32 | 33 | _modules = {'assemble', 'copy', 'file', 'ini_file', 'lineinfile', 34 | 'replace', 'synchronize', 'template', 'unarchive'} 35 | 36 | mode_regex = re.compile(r'^\s*[0-9]+\s*$') 37 | valid_mode_regex = re.compile(r'^\s*0[0-7]{3,4}\s*$') 38 | 39 | def matchtask(self, file, task): 40 | if task["action"]["__ansible_module__"] in self._modules: 41 | mode = task['action'].get('mode', None) 42 | if isinstance(mode, str) and self.mode_regex.match(mode): 43 | return not self.valid_mode_regex.match(mode) 44 | if isinstance(mode, int): 45 | # sensible file permission modes don't 46 | # have write or execute bit set when read bit is 47 | # not set 48 | # also, user permissions are more generous than 49 | # group permissions and user and group permissions 50 | # are more generous than world permissions 51 | 52 | result = (mode % 8 and mode % 8 < 4 or 53 | (mode >> 3) % 8 and (mode >> 3) % 8 < 4 or 54 | (mode >> 6) % 8 and (mode >> 6) % 8 < 4 or 55 | mode & 8 < (mode << 3) & 8 or 56 | mode & 8 < (mode << 6) & 8 or 57 | (mode << 3) & 8 < (mode << 6) & 8) 58 | 59 | return result 60 | -------------------------------------------------------------------------------- /rules/ModuleTemplateExt.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | import os 3 | 4 | class ModuleTemplateExt(AnsibleLintRule): 5 | id = 'E402' 6 | shortdesc = "Template files should have the extension '.j2' " 7 | description = '' 8 | tags = ['module'] 9 | 10 | def matchtask(self, file, task): 11 | if task['action']['__ansible_module__'] != 'template': 12 | return False 13 | ext = os.path.splitext(task['action']['src']) 14 | if ext[1] != ".j2": 15 | return True 16 | return False 17 | -------------------------------------------------------------------------------- /rules/PlaybookExtension.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | import os 4 | 5 | class PlaybookExtension(AnsibleLintRule): 6 | id = 'E101' 7 | shortdesc = 'Playbooks should have the ".yml" extension' 8 | description = '' 9 | tags = ['playbook'] 10 | done = [] # already noticed path list 11 | 12 | def match(self, file, text): 13 | if file['type'] != 'playbook': 14 | return False 15 | 16 | path = file['path'] 17 | ext = os.path.splitext(path) 18 | if ext[1] != ".yml" and path not in self.done: 19 | self.done.append(path) 20 | return True 21 | return False 22 | -------------------------------------------------------------------------------- /rules/RoleRelativePath.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | 4 | format = "{}" 5 | 6 | class RoleRelativePath(AnsibleLintRule): 7 | id = 'E201' 8 | shortdesc = "Doesn't need a relative path in role" 9 | description = '' 10 | tags = ['role'] 11 | 12 | def matchplay(self, file, play): 13 | # assume if 'roles' in path, inside a role. 14 | if 'roles' not in file['path']: 15 | return [] 16 | if 'template' in play: 17 | if not isinstance(play['template'], dict): 18 | return False 19 | if "../templates" in play['template']['src']: 20 | return [({'': play['template']}, 21 | self.shortdesc)] 22 | if 'win_template' in play: 23 | if not isinstance(play['win_template'], dict): 24 | return False 25 | if "../win_templates" in play['win_template']['src']: 26 | return ({'win_template': play['win_template']}, 27 | self.shortdesc) 28 | if 'copy' in play: 29 | if not isinstance(play['copy'], dict): 30 | return False 31 | if 'src' in play['copy']: 32 | if "../files" in play['copy']['src']: 33 | return ({'sudo': play['copy']}, 34 | self.shortdesc) 35 | if 'win_copy' in play: 36 | if not isinstance(play['win_copy'], dict): 37 | return False 38 | if "../files" in play['win_copy']['src']: 39 | return ({'sudo': play['win_copy']}, 40 | self.shortdesc) 41 | return [] 42 | 43 | -------------------------------------------------------------------------------- /rules/ShellAltChmod.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | class ShellAltChmod(AnsibleLintRule): 4 | id = 'E501' 5 | shortdesc = 'Use chmod module' 6 | description = '' 7 | tags = ['shell'] 8 | 9 | def matchtask(self, file, task): 10 | if task['action']['__ansible_module__'] not in ['shell', 'command']: 11 | return False 12 | if 'chmod' in task['action']['__ansible_arguments__']: 13 | return True 14 | return False 15 | -------------------------------------------------------------------------------- /rules/ShellAltChown.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | class ShellAltChown(AnsibleLintRule): 4 | id = 'E502' 5 | shortdesc = 'Use chown module' 6 | description = '' 7 | tags = ['shell'] 8 | 9 | def matchtask(self, file, task): 10 | if task['action']['__ansible_module__'] not in ['shell', 'command']: 11 | return False 12 | if 'chown' in task['action']['__ansible_arguments__']: 13 | return True 14 | return False 15 | -------------------------------------------------------------------------------- /rules/ShellAltFile.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | class ShellAltFile(AnsibleLintRule): 4 | id = 'E512' 5 | shortdesc = 'Use file module instead of mkdir, ln -s and so on' 6 | description = '' 7 | tags = ['shell'] 8 | alt_commands = ["ln -s", "mkdir"] 9 | 10 | def matchtask(self, file, task): 11 | if task['action']['__ansible_module__'] not in ['shell', 'command']: 12 | return False 13 | for c in self.alt_commands: 14 | if c in task['action']['__ansible_arguments__']: 15 | self.shortdesc += " ({})".format(c) 16 | return True 17 | return False 18 | -------------------------------------------------------------------------------- /rules/ShellAltHostname.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | class ShellAltHostname(AnsibleLintRule): 4 | id = 'E503' 5 | shortdesc = 'Use hostname module' 6 | description = '' 7 | tags = ['shell'] 8 | 9 | def matchtask(self, file, task): 10 | if task['action']['__ansible_module__'] not in ['shell', 'command']: 11 | return False 12 | if ('hostname' in task['action']['__ansible_arguments__'] and 13 | 'register' not in task): 14 | return True 15 | return False 16 | -------------------------------------------------------------------------------- /rules/ShellAltMount.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | class ShellAltMount(AnsibleLintRule): 4 | id = 'E504' 5 | shortdesc = 'Use mount module' 6 | description = '' 7 | tags = ['shell'] 8 | 9 | def matchtask(self, file, task): 10 | if task['action']['__ansible_module__'] not in ['shell', 'command']: 11 | return False 12 | if ('mount' in task['action']['__ansible_arguments__'] and 13 | 'register' not in task): 14 | return True 15 | return False 16 | -------------------------------------------------------------------------------- /rules/ShellAltNmcli.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | class ShellAltNmcli(AnsibleLintRule): 4 | id = 'E505' 5 | shortdesc = 'Use nmcli module' 6 | description = '' 7 | tags = ['shell'] 8 | 9 | def matchtask(self, file, task): 10 | if task['action']['__ansible_module__'] not in ['shell', 'command']: 11 | return False 12 | if 'nmcli' in task['action']['__ansible_arguments__']: 13 | return True 14 | return False 15 | -------------------------------------------------------------------------------- /rules/ShellAltPatch.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | class ShellAltPatch(AnsibleLintRule): 4 | id = 'TWSH008' 5 | shortdesc = 'Use patch module' 6 | description = '' 7 | tags = ['shell'] 8 | 9 | def matchtask(self, file, task): 10 | if task['action']['__ansible_module__'] not in ['shell', 'command']: 11 | return False 12 | if 'patch' in task['action']['__ansible_arguments__']: 13 | return True 14 | return False 15 | -------------------------------------------------------------------------------- /rules/ShellAltRpm.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | class ShellAltRpm(AnsibleLintRule): 4 | id = 'E506' 5 | shortdesc = 'Use yum module with file path' 6 | description = '' 7 | tags = ['shell'] 8 | 9 | def matchtask(self, file, task): 10 | if task['action']['__ansible_module__'] not in ['shell', 'command']: 11 | return False 12 | if 'rpm' in task['action']['__ansible_arguments__']: 13 | return True 14 | return False 15 | -------------------------------------------------------------------------------- /rules/ShellAltService.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | class ShellAltService(AnsibleLintRule): 4 | id = 'E507' 5 | shortdesc = 'Use service module' 6 | description = '' 7 | tags = ['shell'] 8 | 9 | def matchtask(self, file, task): 10 | if task['action']['__ansible_module__'] not in ['shell', 'command']: 11 | return False 12 | args = task['action']['__ansible_arguments__'] 13 | 14 | if 'service' in args: 15 | return True 16 | if 'systemctl' in args: 17 | return True 18 | if '/etc/rc.d/init.d/' in args: 19 | return True 20 | return False 21 | -------------------------------------------------------------------------------- /rules/ShellAltSysctl.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | class ShellAltSysctl(AnsibleLintRule): 4 | id = 'E508' 5 | shortdesc = 'Use sysctl module' 6 | description = '' 7 | tags = ['shell'] 8 | 9 | def matchtask(self, file, task): 10 | if task['action']['__ansible_module__'] not in ['shell', 'command']: 11 | return False 12 | if ('sysctl' in task['action']['__ansible_arguments__'] and 13 | 'register' not in task): 14 | return True 15 | return False 16 | -------------------------------------------------------------------------------- /rules/ShellAltUfw.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | class ShellAltUfw(AnsibleLintRule): 4 | id = 'E509' 5 | shortdesc = 'Use ufw module' 6 | description = '' 7 | tags = ['shell'] 8 | 9 | def matchtask(self, file, task): 10 | if task['action']['__ansible_module__'] not in ['shell', 'command']: 11 | return False 12 | if 'ufw' in task['action']['__ansible_arguments__']: 13 | return True 14 | return False 15 | -------------------------------------------------------------------------------- /rules/ShellAltUnarchive.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | class ShellAltUnarchive(AnsibleLintRule): 4 | id = 'E510' 5 | shortdesc = 'Use unarchive module' 6 | description = '' 7 | tags = ['shell'] 8 | 9 | def matchtask(self, file, task): 10 | if task['action']['__ansible_module__'] not in ['shell', 'command']: 11 | return False 12 | args = task['action']['__ansible_arguments__'] 13 | if 'unzip' in args: 14 | return True 15 | if 'tar' in args and 'xf' in args: 16 | return True 17 | return False 18 | -------------------------------------------------------------------------------- /rules/ShellHasCreates.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | class ShellHasCreates(AnsibleLintRule): 4 | id = 'E511' 5 | shortdesc = 'Shell/command module must contain creates or removes' 6 | description = '' 7 | tags = ['shell'] 8 | 9 | def matchtask(self, file, task): 10 | if task['action']['__ansible_module__'] not in ['shell', 'command']: 11 | return False 12 | if 'creates' in task['action'] or 'removes' in task['action']: 13 | return False 14 | if 'register' in task: 15 | return False 16 | return True 17 | -------------------------------------------------------------------------------- /rules/TaskIncludeShouldHaveTags.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | # FIXME: how to get include task? 4 | class TaskIncludeShouldHaveTags(AnsibleLintRule): 5 | id = 'E302' 6 | shortdesc = 'Include should have tags' 7 | description = '' 8 | tags = ['task'] 9 | 10 | def matchplay(self, file, play): 11 | ret = [] 12 | 13 | if isinstance(play, dict) and 'tasks' in play: 14 | for item in play['tasks']: 15 | if 'include' in item and 'tags' not in item: 16 | ret.append((file, self.shortdesc)) 17 | return ret 18 | -------------------------------------------------------------------------------- /rules/TaskManyArgs.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | class TaskManyArgs(AnsibleLintRule): 4 | id = 'E303' 5 | shortdesc = 'Use ":" YAML format when arguments are over 4' 6 | description = '' 7 | tags = ['task'] 8 | 9 | def match(self, file, text): 10 | count = 0 11 | for action in text.split(" "): 12 | if "=" in action: 13 | count += 1 14 | 15 | if count > 4: 16 | return True 17 | 18 | return False 19 | -------------------------------------------------------------------------------- /rules/TaskNoLocalAction.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | class TaskNoLocalAction(AnsibleLintRule): 4 | id = 'E304' 5 | shortdesc = 'Do not use local_action. use delegate_to: localhost instead' 6 | description = '' 7 | tags = ['task'] 8 | 9 | def match(self, file, text): 10 | if 'local_action' in text: 11 | return True 12 | return False 13 | -------------------------------------------------------------------------------- /rules/TaskShouldHaveName.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Will Thames 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from ansiblelint import AnsibleLintRule 22 | 23 | 24 | class TaskShouldHaveName(AnsibleLintRule): 25 | id = 'E301' 26 | shortdesc = 'All tasks should be named' 27 | description = 'All tasks should have a distinct name for readability ' + \ 28 | 'and for --start-at-task to work' 29 | tags = ['task'] 30 | 31 | def matchtask(self, file, task): 32 | return task.get('name', '') == '' 33 | -------------------------------------------------------------------------------- /rules/TaskVariableHasSpace.py: -------------------------------------------------------------------------------- 1 | from ansiblelint import AnsibleLintRule 2 | 3 | import re 4 | 5 | class TaskVariableHasSpace(AnsibleLintRule): 6 | id = 'E305' 7 | shortdesc = 'Variables should be enclosed by spaces "{{ foo }}"' 8 | description = '' 9 | tags = ['task'] 10 | 11 | compiled = re.compile(r'{{(\w*)}}') 12 | 13 | def match(self, file, text): 14 | m = self.compiled.search(text) 15 | if m: 16 | return True 17 | return False 18 | -------------------------------------------------------------------------------- /rules/TrailingWhitespaceRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013-2014 Will Thames 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | from ansiblelint import AnsibleLintRule 22 | 23 | 24 | class TrailingWhitespaceRule(AnsibleLintRule): 25 | id = 'E601' 26 | shortdesc = 'Trailing whitespace' 27 | description = 'There should not be any trailing whitespace' 28 | tags = ['formatting'] 29 | 30 | def match(self, file, line): 31 | return line.rstrip() != line 32 | -------------------------------------------------------------------------------- /rules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tsukinowasha/ansible-lint-rules/3ad6fe92aee901c51e51de61ebd451d5e1cd18c4/rules/__init__.py -------------------------------------------------------------------------------- /tests/formatting.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | gather_facts: no 3 | tasks: 4 | - name: line is toooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo long 5 | debug: msg="line too long" 6 | -------------------------------------------------------------------------------- /tests/included.yml: -------------------------------------------------------------------------------- 1 | - name: dummy 2 | debug: msg="dummy" 3 | -------------------------------------------------------------------------------- /tests/modules.yml: -------------------------------------------------------------------------------- 1 | - name: playbook for roles category 2 | hosts: localhost 3 | gather_facts: no 4 | tasks: 5 | - template: src="hoge.noj2" dest="/tmp/hoge" 6 | -------------------------------------------------------------------------------- /tests/roles.retry: -------------------------------------------------------------------------------- 1 | localhost 2 | -------------------------------------------------------------------------------- /tests/roles.yml: -------------------------------------------------------------------------------- 1 | - name: playbook for roles category 2 | hosts: localhost 3 | gather_facts: no 4 | roles: 5 | - relative 6 | -------------------------------------------------------------------------------- /tests/roles/relative/tasks/included.yml: -------------------------------------------------------------------------------- 1 | - name: included by no tag include 2 | debug: msg="no" 3 | -------------------------------------------------------------------------------- /tests/roles/relative/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - template: 2 | src: ../templates/hoge.yml 3 | dest: /tmp/hoge.yml 4 | - include: included.yml 5 | -------------------------------------------------------------------------------- /tests/roles/relative/templates/hoge.yml: -------------------------------------------------------------------------------- 1 | nothing 2 | -------------------------------------------------------------------------------- /tests/shell.yml: -------------------------------------------------------------------------------- 1 | - name: playbook for shell category 2 | hosts: localhost 3 | gather_facts: no 4 | tasks: 5 | - name: does not have creates 6 | shell: echo "hoge" 7 | - name: use mount 8 | shell: mount 9 | args: 10 | creates: /tmp 11 | - name: use sysctl module 12 | shell: sysctl -a 13 | args: 14 | creates: /tmp 15 | - name: use file module to chown 16 | shell: chown -R hoge . 17 | args: 18 | creates: /tmp 19 | - name: use file module to chmod 20 | shell: chmod ugo+r . 21 | args: 22 | creates: /tmp 23 | 24 | -------------------------------------------------------------------------------- /tests/tasks.yml: -------------------------------------------------------------------------------- 1 | - name: playbook for task category 2 | hosts: localhost 3 | gather_facts: no 4 | vars: 5 | foo: "tasks" 6 | tasks: 7 | - name: use local action 8 | local_action: file path=/tmp/tasks state=touch 9 | - name: many argument with = 10 | file: path="/tmp/tasks" state=touch group=wheel mode=0700 force=yes 11 | - name: variable with no space 12 | file: path="/tmp/{{foo}}" state=touch 13 | - name: variable with space # lint does not match 14 | file: path="/tmp/{{ foo }}" state=touch 15 | --------------------------------------------------------------------------------