├── .github └── workflows │ ├── tests-in-centos.yml │ └── tests.yml ├── .gitlab-ci.yml ├── LICENSE.MIT ├── MANIFEST.in ├── README.rst ├── azure-pipelines.yml ├── mypy.ini ├── package.spec.in ├── requirements.txt ├── rules ├── BlockedModules.py ├── DebugRule.py ├── FileHasValidNameRule.py ├── FileIsSmallEnoughRule.py ├── LoopIsRecommendedRule.py ├── NoEmptyDataFilesRule.py ├── TaskHasValidNameRule.py ├── TasksFileHasValidNameRule.py ├── VarsInVarsFilesHaveValidNamesRule.py ├── VarsShouldNotBeUsedRule.py └── __init__.py ├── setup.cfg ├── setup.py ├── tests ├── TestBlockedModules.py ├── TestDebugRule.py ├── TestFileHasValidNameRule.py ├── TestFileIsSmallEnoughRule.py ├── TestLoopIsRecommendedRule.py ├── TestNoEmptyDataFilesRule.py ├── TestTaskHasValidNameRule.py ├── TestTasksFileHasValidNameRule.py ├── TestVarsInVarsFilesHaveValidNamesRule.py ├── TestVarsShouldNotBeUsedRule.py ├── __init__.py ├── common │ ├── __init__.py │ ├── base.py │ ├── constants.py │ ├── datatypes.py │ ├── runner.py │ ├── test_base.py │ ├── test_runner.py │ ├── test_utils.py │ ├── testcases.py │ └── utils.py ├── requirements.txt └── res │ ├── BlockedModules │ ├── ng │ │ ├── 0 │ │ │ └── playbook.yml │ │ └── 1 │ │ │ ├── conf.json │ │ │ └── playbook.yml │ └── ok │ │ ├── 0 │ │ └── playbook.yml │ │ └── 1 │ │ ├── conf.json │ │ ├── playbook.yml │ │ └── roles │ │ └── ping │ ├── DebugRule │ ├── ng │ │ ├── 0 │ │ │ ├── conf.json │ │ │ └── playbook.yml │ │ ├── 1 │ │ │ ├── conf.json │ │ │ └── playbook.yml │ │ └── 2 │ │ │ ├── env.json │ │ │ └── playbook.yml │ ├── ok │ │ ├── 0 │ │ │ └── playbook.yml │ │ ├── 1 │ │ │ ├── playbook.yml │ │ │ └── roles │ │ │ │ └── ping │ │ └── 2 │ │ │ ├── conf.json │ │ │ └── playbook.yml │ └── roles │ ├── FileHasValidNameRule │ ├── ng │ │ ├── 0 │ │ │ └── ping_ng-0.yml │ │ ├── 1 │ │ │ └── ng-1.yml │ │ ├── 2 │ │ │ └── ng-2.yml │ │ ├── 3 │ │ │ └── ng 3.yml │ │ └── 4 │ │ │ ├── conf.json │ │ │ └── playbook_4.yml │ └── ok │ │ ├── 0 │ │ ├── ping_ok_1.yml │ │ └── roles │ │ │ └── ping │ │ ├── 1 │ │ ├── conf.json │ │ ├── ping-ok-1.yml │ │ └── roles │ │ │ └── ping │ │ └── 2 │ │ ├── conf.json │ │ ├── ping_ok_2.yml │ │ └── roles │ │ └── ping │ ├── FileIsSmallEnoughRule │ ├── ng │ │ └── 0 │ │ │ ├── conf.json │ │ │ └── playbook.yml │ └── ok │ │ ├── 0 │ │ └── playbook.yml │ │ └── 1 │ │ ├── playbook.yml │ │ └── roles │ │ └── ping │ ├── LoopIsRecommendedRule │ ├── ng │ │ └── 0 │ │ │ └── playbook.yml │ └── ok │ │ └── 0 │ │ └── playbook.yml │ ├── NoEmptyDataFilesRule │ ├── ng │ │ └── 0 │ │ │ ├── playbook.yml │ │ │ └── roles │ │ │ └── no_empty_data_files_rule_ng_1 │ └── ok │ │ └── 0 │ │ ├── playbook.yml │ │ └── roles │ │ └── no_empty_data_files_rule_ok_1 │ ├── TaskHasValidNameRule │ ├── ng │ │ ├── 0 │ │ │ └── playbook.yml │ │ └── 1 │ │ │ └── playbook.yml │ └── ok │ │ ├── 0 │ │ └── plays_ok_0.yml │ │ ├── 1 │ │ └── playbook.yml │ │ └── 2 │ │ ├── conf.json │ │ └── playbook.yml │ ├── TasksFileHasValidNameRule │ ├── ng │ │ ├── 0 │ │ │ ├── playbook.yml │ │ │ └── tasks │ │ ├── 1 │ │ │ ├── playbook.yml │ │ │ └── tasks │ │ └── 2 │ │ │ ├── playbook.yml │ │ │ └── tasks │ ├── ok │ │ ├── 0 │ │ │ ├── playbook.yml │ │ │ └── tasks │ │ ├── 1 │ │ │ ├── playbook.yml │ │ │ └── tasks │ │ └── 2 │ │ │ ├── conf.json │ │ │ ├── playbook.yml │ │ │ └── tasks │ └── tasks │ │ ├── ng _2.yml │ │ ├── ng-1.yml │ │ ├── ng_三.yml │ │ ├── ok.yml │ │ └── ok_1.yml │ ├── VariableHasValidNameRule │ ├── ng │ │ ├── 0.yml │ │ ├── 1.yml │ │ ├── c │ │ │ └── 1.json │ │ └── roles │ │ │ ├── ping │ │ │ └── ping_0 │ │ │ ├── defaults │ │ │ └── main.yml │ │ │ └── tasks │ │ │ ├── assertions.yml │ │ │ ├── debug.yml │ │ │ ├── do_ping.yml │ │ │ └── main.yml │ └── ok │ │ ├── .coverage │ │ ├── 0.yml │ │ ├── 1.yml │ │ ├── inventory │ │ └── group_vars │ │ │ └── localhost.yml │ │ └── roles │ │ └── ping │ ├── VarsInVarsFilesHaveValidNamesRule │ ├── ng │ │ ├── 0 │ │ │ ├── group_vars │ │ │ │ └── localhost.yml │ │ │ └── playbook.yml │ │ ├── 1 │ │ │ ├── playbook.yml │ │ │ └── roles │ │ │ │ └── ping │ │ │ │ ├── defaults │ │ │ │ └── main.yml │ │ │ │ └── tasks │ │ │ │ ├── assertions.yml │ │ │ │ ├── debug.yml │ │ │ │ ├── do_ping.yml │ │ │ │ └── main.yml │ │ └── 2 │ │ │ ├── conf.json │ │ │ ├── group_vars │ │ │ └── localhost.yml │ │ │ └── playbook.yml │ └── ok │ │ ├── 0 │ │ ├── group_vars │ │ │ └── localhost.yml │ │ └── playbook.yml │ │ └── 1 │ │ ├── playbook.yml │ │ └── roles │ │ └── ping │ ├── VarsShouldNotBeUsedRule │ ├── ng │ │ ├── 0 │ │ │ └── playbook.yml │ │ └── 1 │ │ │ ├── playbook.yml │ │ │ └── vars │ │ │ └── VarsShouldNotBeUsedRule_1.yml │ └── ok │ │ └── 0 │ │ └── plays_ok_0.yml │ ├── common │ └── ok_1.yml │ ├── inventories │ └── VariablesNamingRule │ │ ├── ng │ │ └── 1 │ │ │ ├── group_vars │ │ │ └── all.yml │ │ │ ├── host_vars │ │ │ └── localhost_0.yml │ │ │ └── hosts │ │ └── ok │ │ └── 1 │ │ ├── group_vars │ │ └── all.yml │ │ ├── host_vars │ │ └── localhost_0.yml │ │ └── hosts │ ├── playbooks │ ├── ping_ok_1.yml │ ├── play_using_role_0.yml │ └── plays_ok_0.yml │ ├── roles │ ├── no_empty_data_files_rule_ng_1 │ │ ├── defaults │ │ │ └── main.yml │ │ ├── handlers │ │ │ └── main.yml │ │ ├── meta │ │ │ └── main.yml │ │ ├── tasks │ │ │ └── main.yml │ │ └── vars │ │ │ └── main.yml │ ├── no_empty_data_files_rule_ok_1 │ ├── ping │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ ├── assertions.yml │ │ │ ├── debug.yml │ │ │ ├── do_ping.yml │ │ │ └── main.yml │ ├── ping_with_vars │ │ ├── defaults │ │ │ └── main.yml │ │ └── tasks │ │ │ ├── assertions.yml │ │ │ ├── debug.yml │ │ │ ├── do_ping.yml │ │ │ └── main.yml │ ├── variable_naming_rule_test_ng_1 │ │ ├── defaults │ │ │ └── main.yml │ │ ├── tasks │ │ └── vars │ │ │ └── main.yml │ └── variable_naming_rule_test_ok_1 │ │ ├── defaults │ │ └── main.yml │ │ ├── tasks │ │ └── main.yml │ │ └── vars │ │ └── main.yml │ └── tasks │ └── simple_ping_1.yml └── tox.ini /.github/workflows/tests-in-centos.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # .. seealso:: https://github.com/ymyzk/tox-gh-actions 3 | # 4 | name: Tests inside CentOS container 5 | # yamllint disable-line rule:truthy 6 | on: 7 | - push 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | container: pycontribs/centos:8 12 | env: 13 | TOXENV: py36 14 | 15 | steps: 16 | - uses: actions/checkout@v1 17 | - name: Install dependencies 18 | run: | 19 | dnf install -y graphviz 20 | pip3 install tox tox-gh-actions 21 | - name: Test with tox 22 | run: tox 23 | 24 | # vim:sw=2:ts=2:et: 25 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # .. seealso:: https://github.com/ymyzk/tox-gh-actions 3 | # 4 | name: Tests 5 | # yamllint disable-line rule:truthy 6 | on: 7 | - push 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.platform }} 12 | strategy: 13 | max-parallel: 10 14 | matrix: 15 | platform: 16 | - ubuntu-latest 17 | # - macos-latest 18 | python-version: [3.6, 3.7, 3.8, 3.9] 19 | 20 | steps: 21 | - uses: actions/checkout@v1 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | - name: Install dependencies 27 | run: | 28 | sudo apt-get update 29 | sudo apt-get install -y graphviz 30 | python -m pip install --upgrade pip 31 | pip install tox tox-gh-actions 32 | - name: Test with tox 33 | run: tox 34 | 35 | # vim:sw=2:ts=2:et: 36 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | services: 3 | - docker:dind 4 | 5 | test py37: &default_def 6 | image: python:3.7-buster 7 | variables: 8 | TOXENV: py37 9 | before_script: 10 | - apt-get -qq update 11 | - apt-get install -y tox python3-pip 12 | script: 13 | - tox 14 | 15 | # test py38: 16 | # <<: *default_def 17 | # image: python:3.8-buster 18 | # variables: 19 | # TOXENV: py38 20 | 21 | # vim:sw=2:ts=2:et: 22 | -------------------------------------------------------------------------------- /LICENSE.MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Satoru SATOH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.MIT 2 | include MANIFEST.in 3 | include README.* 4 | include setup.* 5 | include *.ini 6 | include *.txt 7 | recursive-include rules *.py 8 | # for f in tests/**/*.* ; do echo ${f/*\./*.}; done | sort | uniq 9 | recursive-include tests *.py *.txt *.yml 10 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | About 2 | ======= 3 | 4 | .. image:: https://github.com/ssato/ansible-lint-custom-rules/workflows/Tests/badge.svg 5 | :target: https://github.com/ssato/ansible-lint-custom-rules/actions?query=workflow%3ATests 6 | :alt: [GHA Tests] 7 | 8 | .. image:: https://dev.azure.com/satorusatoh0471/ansible-lint-custom-rules/_apis/build/status/ssato.ansible-lint-custom-rules?branchName=master 9 | :target: https://dev.azure.com/satorusatoh0471/ansible-lint-custom-rules/_build/latest?definitionId=1 10 | :alt: [Azure Pipelines Status] 11 | 12 | .. .. image:: https://img.shields.io/coveralls/ssato/ansible-lint-custom-rules.svg 13 | :target: https://coveralls.io/r/ssato/ansible-lint-custom-rules 14 | :alt: [Coverage Status] 15 | 16 | Some ansible-lint [#]_ custom rule class implementations as examples really works 17 | 18 | - Author: Satoru SATOH 19 | - License: MIT 20 | 21 | .. [#] https://github.com/ansible/ansible-lint 22 | 23 | Notes 24 | ======= 25 | 26 | - How to test?: run 'tox [-e py37]' 27 | 28 | .. vim:sw=2:ts=2:et: 29 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # yamllint disable-line rule:line-length 3 | # Based on https://github.com/Azure-Samples/azure-pipelines-python/blob/master/.azure-pipelines/simple_package.1-multi-target.yml 4 | # another ref. https://github.com/tox-dev/azure-pipelines-template 5 | variables: 6 | package: pre_commit_hooks_for_ansible 7 | srcDirectory: src/ 8 | testsDirectory: tests/$(package) 9 | 10 | trigger: 11 | branches: 12 | include: 13 | - '*' 14 | 15 | jobs: 16 | - job: Build 17 | strategy: 18 | matrix: 19 | python36: 20 | pythonVersion: 3.6 21 | python37: 22 | pythonVersion: 3.7 23 | python38: 24 | pythonVersion: 3.8 25 | python39: 26 | pythonVersion: 3.9 27 | 28 | pool: 29 | vmImage: 'ubuntu-latest' 30 | 31 | variables: 32 | TOXENV: py${{ replace(variables['pythonVersion'], '.', '') }} 33 | 34 | steps: 35 | - task: UsePythonVersion@0 36 | displayName: Use Python $(pythonVersion) 37 | inputs: 38 | versionSpec: $(pythonVersion) 39 | 40 | - script: | 41 | sudo apt-get update 42 | sudo apt-get install -y graphviz 43 | python -m pip install --upgrade pip 44 | pip install tox tox-gh-actions 45 | displayName: Install some more test time dependencies 46 | 47 | - bash: tox 48 | displayName: Run tests 49 | 50 | # vim:sw=2:ts=2:et: 51 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = True 3 | warn_unused_ignores=False 4 | 5 | -------------------------------------------------------------------------------- /package.spec.in: -------------------------------------------------------------------------------- 1 | %global pkgname ansiblelint_custom_rules_ex 2 | 3 | %global desc \ 4 | Some ansible-lint custom rule impolementations as examples. 5 | 6 | %bcond_with tests 7 | 8 | Name: python-%{pkgname} 9 | Version: @VERSION@ 10 | Release: 1%{?dist} 11 | Summary: Some ansible-lint custom rule examples 12 | License: MIT 13 | URL: https://github.com/ssato/ansible-lint-custom-rules 14 | Source0: %{url}/archive/RELEASE_%{version}.tar.gz 15 | BuildArch: noarch 16 | BuildRequires: python3-devel 17 | BuildRequires: python3-setuptools 18 | %if %{with tests} 19 | BuildRequires: python3-coveralls 20 | BuildRequires: python3-flake8 21 | BuildRequires: python3-mock 22 | BuildRequires: python3-pytest 23 | BuildRequires: python3-pycodestyle 24 | BuildRequires: python3-pylint 25 | BuildRequires: python3-tox 26 | %endif 27 | 28 | %description %{desc} 29 | 30 | %package -n python3-%{pkgname} 31 | Summary: %{summary} 32 | Requires: ansible-lint 33 | %{?python_provide:%python_provide python3-%{pkgname}} 34 | 35 | %description -n python3-%{pkgname} %{desc} 36 | 37 | %prep 38 | %autosetup -n %{pkgname}-%{version} 39 | 40 | %build 41 | %py3_build 42 | 43 | %install 44 | %py3_install 45 | 46 | %if %{with tests} 47 | %check 48 | tox -e py$(python -c "import sys; sys.stdout.write(sys.version[:3].replace('.', ''))") 49 | %endif 50 | 51 | %files -n python3-%{pkgname} 52 | %doc README.rst 53 | %license LICENSE.MIT 54 | %{python3_sitelib}/* 55 | 56 | %changelog 57 | * Tue Sep 1 2020 Satoru SATOH - 0.1.0-1 58 | - Initial release 59 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ansible 2 | ansible-lint 3 | -------------------------------------------------------------------------------- /rules/BlockedModules.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # 3 | # SPDX-License-Identifier: MIT 4 | # 5 | # pylint: disable=invalid-name 6 | r""" 7 | Lint rule class to test if some blocked modules were used. 8 | """ 9 | import functools 10 | import typing 11 | 12 | import ansiblelint.rules 13 | 14 | if typing.TYPE_CHECKING: 15 | from typing import Optional 16 | from ansiblelint.file_utils import Lintable 17 | 18 | 19 | ID: str = 'blocked_modules' 20 | C_BLOCKED_MODULES: str = 'blocked' 21 | 22 | DESC: str = """Rule to check if some blocked modules were used in tasks. 23 | 24 | - Options 25 | 26 | - ``blocked`` lists the modules blocked to use 27 | 28 | - Configuration 29 | 30 | .. code-block:: yaml 31 | 32 | rules: 33 | blocked_modules: 34 | blocked: 35 | - shell 36 | - include 37 | 38 | .. seealso:: :class:`~ansiblielint.rules.DeprecatedModuleRule` 39 | """ 40 | 41 | BLOCKED_MODULES: typing.FrozenSet[str] = frozenset(""" 42 | shell 43 | include 44 | """.split()) 45 | 46 | 47 | class BlockedModules(ansiblelint.rules.AnsibleLintRule): 48 | """ 49 | Lint rule class to test if variables defined by users follow the namging 50 | conventions and guildelines. 51 | """ 52 | id: str = ID 53 | shortdesc: str = 'Blocked modules' 54 | description: str = DESC 55 | severity: str = 'HIGH' 56 | tags: typing.List[str] = [ID, 'module'] 57 | 58 | @functools.lru_cache() 59 | def blocked_modules(self): 60 | """ 61 | .. seealso:: rules.DebugRule.DebugRule.enabled 62 | """ 63 | blocked = self.get_config(C_BLOCKED_MODULES) 64 | if blocked: 65 | return frozenset(blocked) 66 | 67 | return BLOCKED_MODULES 68 | 69 | def matchtask(self, task: typing.Dict[str, typing.Any], 70 | file: 'Optional[Lintable]' = None 71 | ) -> typing.Union[bool, str]: 72 | """ 73 | .. seealso:: ansiblelint.rules.AnsibleLintRule.matchtasks 74 | """ 75 | try: 76 | mod = task['action']['__ansible_module__'] 77 | if mod in self.blocked_modules(): 78 | return f'{self.shortdesc}: {mod}' 79 | except KeyError: 80 | pass 81 | 82 | return False 83 | 84 | # vim:sw=4:ts=4:et: 85 | -------------------------------------------------------------------------------- /rules/DebugRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | """ Lint rule class to Debug 6 | """ 7 | import functools 8 | import os 9 | import typing 10 | 11 | import ansiblelint.errors 12 | import ansiblelint.file_utils 13 | import ansiblelint.rules 14 | 15 | if typing.TYPE_CHECKING: 16 | from typing import Optional 17 | from ansiblelint.constants import odict 18 | from ansiblelint.file_utils import Lintable 19 | 20 | 21 | ID: str = 'debug' 22 | E_ENABLED_VAR: str = '_ANSIBLE_LINT_RULE_DEBUG' 23 | C_ENABLED: str = 'enabled' 24 | 25 | DESC: str = """Rule to debug and monitor ansible-lint behavior. 26 | 27 | - Options 28 | 29 | - ``enabled`` enables this rule disabled by default. 30 | 31 | - Configuration 32 | 33 | .. code-block:: yaml 34 | 35 | rules: 36 | debug: 37 | enabled: true 38 | 39 | - Environment variables 40 | 41 | - Set ``_ANSIBLE_LINT_RULE_DEBUG`` to any value evaluated to true like 1, 42 | '0', 'foo', if you want to enable this rule. The value to enable this rule 43 | will be given higher priority than the above configuration value. 44 | """ 45 | 46 | 47 | def is_enabled(default: bool = False) -> bool: 48 | """ 49 | Is this rule enabled with the environment variable? 50 | """ 51 | return bool(os.environ.get(E_ENABLED_VAR, default)) 52 | 53 | 54 | class DebugRule(ansiblelint.rules.AnsibleLintRule): 55 | """ 56 | Lint rule class for debug. 57 | """ 58 | id = ID 59 | shortdesc = 'Debug ansible-lint' 60 | description = DESC 61 | severity = 'LOW' 62 | tags = ['debug'] 63 | 64 | @functools.lru_cache(None) 65 | def enabled(self): 66 | """ 67 | .. seealso:: ansiblelint.config.options 68 | .. seealso:: ansiblelint.cli.load_config 69 | """ 70 | if is_enabled(): 71 | return True # Gives higher prio. to the environment variable. 72 | 73 | return bool(self.get_config(C_ENABLED)) 74 | 75 | def match(self, line: str) -> typing.Union[bool, str]: 76 | """ 77 | .. seealso:: ansiblelint.rules.AnsibleLintRule.matchlines 78 | """ 79 | return f'match() at: {line}' if self.enabled() else False 80 | 81 | def matchtask(self, task: typing.Dict[str, typing.Any], 82 | file: 'Optional[Lintable]' = None 83 | ) -> typing.Union[bool, str]: 84 | """ 85 | .. seealso:: ansiblelint.rules.AnsibleLintRule.matchtasks 86 | """ 87 | return f'matchtask(): {task!r}, {file!r}' if self.enabled() else False 88 | 89 | def matchplay(self, file: ansiblelint.file_utils.Lintable, 90 | data: 'odict[str, typing.Any]' 91 | ) -> typing.List[ansiblelint.errors.MatchError]: 92 | """ 93 | .. seealso:: ansiblelint.rules.AnsibleLintRule.matchtasks 94 | """ 95 | if self.enabled(): 96 | msg = f'matchplay(): {file!r}, {data!r}' 97 | return [self.create_matcherror(message=msg, filename=file.name)] 98 | 99 | return [] 100 | -------------------------------------------------------------------------------- /rules/FileHasValidNameRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | """Lint rule class to test if playbook files have valid filenames. 6 | """ 7 | import functools 8 | import re 9 | import typing 10 | import warnings 11 | 12 | import ansiblelint.errors 13 | import ansiblelint.file_utils 14 | import ansiblelint.rules 15 | 16 | 17 | ID: str = 'file_has_valid_name' 18 | DESC = r"""Rule to check if file has a valid name. 19 | 20 | - Options 21 | 22 | - ``name`` lists the modules blocked to use 23 | - ``unicode`` allows unicode characters are used in filenames 24 | 25 | - Configuration 26 | 27 | .. code-block:: yaml 28 | 29 | rules: 30 | file_has_valid_name: 31 | name: ^\w+\.ya?ml$ 32 | unicode: false 33 | """ 34 | 35 | C_NAME_RE: str = 'name' 36 | C_UNICODE: str = 'unicode' 37 | 38 | DEFAULT_NAME_RE: typing.Pattern = re.compile(r'^\w+\.ya?ml$', re.ASCII) 39 | 40 | FILE_KINDS: typing.FrozenSet[str] = frozenset(( 41 | 'playbook', 42 | 'meta', 43 | 'tasks', 44 | 'handlers', 45 | 'role', 46 | 'yaml' 47 | )) 48 | 49 | 50 | class FileHasValidNameRule(ansiblelint.rules.AnsibleLintRule): 51 | """ 52 | Rule class to test if playbook file has a valid filename satisfies the file 53 | naming rules in the organization. 54 | """ 55 | id = ID 56 | shortdesc = 'Playbook and related files should have valid filenames' 57 | description = DESC 58 | severity = 'MEDIUM' 59 | tags = [ID, 'playbook', 'readability', 'formatting'] 60 | 61 | @functools.lru_cache(None) 62 | def valid_name_re(self) -> typing.Pattern: 63 | """A valid file name regex pattern. 64 | """ 65 | pattern = self.get_config(C_NAME_RE) 66 | if pattern is not None and pattern: 67 | pattern = str(pattern).strip() 68 | if pattern: 69 | try: 70 | if self.get_config(C_UNICODE): 71 | return re.compile(pattern) 72 | 73 | return re.compile(pattern, re.ASCII) 74 | except BaseException: # pylint: disable=broad-except 75 | warnings.warn(f'Invalid pattern? "{pattern}"') 76 | 77 | return DEFAULT_NAME_RE 78 | 79 | def is_invalid_filename(self, filename: str) -> bool: 80 | """ 81 | Test if given `filename` is NOT valid and does NOT satisfy the rule. 82 | """ 83 | return self.valid_name_re().match(filename) is None 84 | 85 | def matchyaml(self, file: ansiblelint.file_utils.Lintable 86 | ) -> typing.List[ansiblelint.errors.MatchError]: 87 | """ 88 | .. seealso:: ansiblelint.rules.AnsibleLintRule.matchyaml 89 | """ 90 | if file.kind in FILE_KINDS: 91 | if self.is_invalid_filename(file.path.name): 92 | return [ 93 | self.create_matcherror( 94 | filename=file, 95 | message=f'{self.shortdesc}: {file.path.name}' 96 | ) 97 | ] 98 | 99 | return [] 100 | -------------------------------------------------------------------------------- /rules/FileIsSmallEnoughRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | """Lint rule class to test if playbook files are small enough. 5 | 6 | Users can change the behavior of this class by specifying an environment 7 | variable, _ANSIBLE_LINT_RULE_CUSTOM_2020_30_MAX_LINES. 8 | 9 | :: 10 | 11 | _ANSIBLE_LINT_RULE_CUSTOM_2020_30_MAX_LINES=500 12 | 13 | """ 14 | import functools 15 | import typing 16 | import warnings 17 | 18 | import ansiblelint.constants 19 | import ansiblelint.rules 20 | 21 | if typing.TYPE_CHECKING: 22 | from ansiblelint.errors import MatchError 23 | from ansiblelint.file_utils import Lintable 24 | 25 | 26 | ID: str = 'file_is_small_enough' 27 | 28 | C_MAX_LINES: str = 'max_lines' 29 | DEFAULT_MAX_LINES: int = 500 30 | DESC: str = """Rule to test if files are smalll enough. 31 | 32 | - Options 33 | 34 | - ``max_lines`` limits the number of maximum lines files can have. 35 | 36 | - Configuration 37 | 38 | .. code-block:: yaml 39 | 40 | rules: 41 | file_is_small_enough: 42 | max_lines: 500 43 | """ 44 | 45 | 46 | def exceeds_max_lines(filepath: str, max_lines: int) -> bool: 47 | """ 48 | Test if given file in ``filepath`` has a content exceeds max lines 49 | ``max_lines``. 50 | 51 | >>> exceeds_max_lines(__file__, 1000000) 52 | False 53 | >>> exceeds_max_lines(__file__, 10) 54 | True 55 | """ 56 | with open(filepath) as fobj: 57 | # Which is better? 58 | # return len(fobj.readliens()) > max_lines 59 | for idx, _line in enumerate(fobj): 60 | if idx + 1 > max_lines: 61 | return True 62 | 63 | return False 64 | 65 | 66 | # .. seealso:: ansiblelint.constants.FileType 67 | FTYPES: typing.FrozenSet = frozenset( 68 | 'playbook meta tasks handlers role yaml'.split() 69 | ) 70 | 71 | 72 | class FileIsSmallEnoughRule(ansiblelint.rules.AnsibleLintRule): 73 | """ 74 | Rule class to test if playbook and tasks files are small enough. 75 | """ 76 | id = ID 77 | shortdesc = 'Playbook and tasks files should be small enough' 78 | description = shortdesc 79 | severity = 'MEDIUM' 80 | tags = [ID, 'playbook', 'tasks', 'readability'] 81 | 82 | @functools.lru_cache() 83 | def max_lines(self): 84 | """The limit number of lines files can have. 85 | """ 86 | try: 87 | max_lines = int(self.get_config(C_MAX_LINES)) 88 | assert max_lines > 0 89 | return max_lines 90 | 91 | except (ValueError, AssertionError) as exc: 92 | warnings.warn(f'Invalid max_lines value: {max_lines:!r}' 93 | f'exc={exc!s}') 94 | 95 | return DEFAULT_MAX_LINES 96 | 97 | @functools.lru_cache() 98 | def exceeds_max_lines(self, path: str): 99 | """Test if given file is small enough. 100 | """ 101 | return exceeds_max_lines(path, self.max_lines()) 102 | 103 | def matchyaml(self, file: 'Lintable') -> typing.List['MatchError']: 104 | """Test playbook files. 105 | """ 106 | if file.kind in FTYPES: 107 | path = str(file.path) 108 | if self.exceeds_max_lines(path): 109 | return [ 110 | self.create_matcherror( 111 | message='File {path} may be too large', 112 | filename=path 113 | ) 114 | ] 115 | 116 | return [] 117 | 118 | # vim:sw=4:ts=4:et: 119 | -------------------------------------------------------------------------------- /rules/LoopIsRecommendedRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | """Lint rule class to test if tasks use with_* directives. 6 | """ 7 | import typing 8 | 9 | import ansiblelint.rules 10 | 11 | if typing.TYPE_CHECKING: 12 | from typing import Optional 13 | from ansiblelint.file_utils import Lintable 14 | 15 | 16 | ID: str = 'loop_is_recommended' 17 | DESC: str = """loop is recommended and use of with_* may be repalced with it. 18 | See also: 19 | https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html 20 | """ 21 | 22 | 23 | class LoopIsRecommendedRule(ansiblelint.rules.AnsibleLintRule): 24 | """ 25 | Rule class to test if any tasks use with_* loop directive. 26 | """ 27 | id: str = ID 28 | shortdesc: str = 'loop is recommended and with_* may be repalced with it' 29 | description: str = DESC 30 | severity: str = 'LOW' 31 | tags: typing.List[str] = [ID, 'readability', 'formatting'] 32 | 33 | def matchtask(self, task: typing.Dict[str, typing.Any], 34 | file: 'Optional[Lintable]' = None 35 | ) -> typing.Union[bool, str]: 36 | """ 37 | .. seealso:: ansiblelint.rules.AnsibleLintRule.matchtasks 38 | """ 39 | with_st = [key for key in task if key.startswith('with_')] 40 | if with_st: 41 | return f'Use of with_* was found: { ", ".join(with_st) }' 42 | 43 | return False 44 | -------------------------------------------------------------------------------- /rules/NoEmptyDataFilesRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | r""" 6 | Lint rule class to test if there are YAML files have no data. 7 | """ 8 | import functools 9 | import typing 10 | import yaml 11 | import yaml.parser 12 | 13 | import ansiblelint.rules 14 | import ansiblelint.errors 15 | 16 | from ansiblelint.file_utils import Lintable 17 | 18 | 19 | ID: str = 'no-empty-data-files' 20 | 21 | 22 | @functools.lru_cache() 23 | def yml_file_has_some_data(filepath: str) -> bool: 24 | """ 25 | Is given YAML file has some data? 26 | """ 27 | try: 28 | with open(filepath) as fio: 29 | return bool(yaml.safe_load(fio)) 30 | except yaml.parser.ParserError: 31 | pass 32 | 33 | return True # Innocent until proven guilty. 34 | 35 | 36 | # .. seealso:: ansiblelint.constants.FileType 37 | FTYPES: typing.FrozenSet = frozenset( 38 | 'playbook meta tasks handlers role yaml'.split() 39 | ) 40 | 41 | 42 | class NoEmptyDataFilesRule(ansiblelint.rules.AnsibleLintRule): 43 | """ 44 | Lint rule class to test if roles' YAML files have some data. 45 | """ 46 | id = ID 47 | shortdesc = description = 'All YAML files should have some data' 48 | severity = 'MEDIUM' 49 | tags = [ID, 'format', 'yaml'] 50 | 51 | def matchyaml(self, file: Lintable 52 | ) -> typing.List[ansiblelint.errors.MatchError]: 53 | """Test playbook files. 54 | """ 55 | if file.kind in FTYPES: 56 | path = str(file.path) 57 | if not yml_file_has_some_data(path): 58 | return [ 59 | self.create_matcherror( 60 | message=f'Empty data file: {path!s}', 61 | filename=path 62 | ) 63 | ] 64 | 65 | return [] 66 | 67 | # vim:sw=4:ts=4:et: 68 | -------------------------------------------------------------------------------- /rules/TaskHasValidNameRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | r"""Lint rule class to test if tasks have valid names. 5 | """ 6 | import functools 7 | import re 8 | import typing 9 | import warnings 10 | 11 | import ansiblelint.utils 12 | import ansiblelint.rules 13 | 14 | if typing.TYPE_CHECKING: 15 | from typing import Optional 16 | from ansiblelint.file_utils import Lintable 17 | 18 | 19 | ID: str = 'task_has_valid_name' 20 | C_NAME_RE: str = 'name' 21 | DESC: str = r"""Rule to test if files are smalll enough. 22 | 23 | - Options 24 | 25 | - ``name`` gives a valid task name pattern (regexp) 26 | 27 | - Configuration 28 | 29 | .. code-block:: yaml 30 | 31 | rules: 32 | task_has_valid_name: 33 | name: ^\S+$ 34 | """ 35 | 36 | VERBS: typing.List[str] = """\ 37 | add ask assemble become begin call check collect configure copy create debug 38 | delete deploy determine disable do download drop enable ensure execute exit 39 | extract fail find flag generate get go install help keep leave let link load 40 | look make move notify own parse perform play put refresh reload remove reown 41 | retrieve restart run set show start stop take talk tell test try turn update 42 | use validate verify wait work\ 43 | """.split() 44 | 45 | VERBS_ALL: typing.List[str] = VERBS + [v.capitalize() for v in VERBS] 46 | DEFAULT_NAME_RE: typing.Pattern = re.compile( 47 | r'(' + '|'.join(VERBS_ALL) + r')(\s+(\S+))+$', 48 | re.ASCII 49 | ) 50 | 51 | _NAMELESS_TASKS: typing.FrozenSet[str] = frozenset(""" 52 | meta 53 | debug 54 | import_role 55 | import_tasks 56 | include_role 57 | include_tasks 58 | """.split()) 59 | 60 | 61 | def is_named_task(task: typing.Dict[str, typing.Any], 62 | nameless_tasks: typing.FrozenSet[str] = _NAMELESS_TASKS 63 | ) -> bool: 64 | """Test if given task should be named? 65 | """ 66 | return task['action']['__ansible_module__'] not in nameless_tasks 67 | 68 | 69 | class TaskHasValidNameRule(ansiblelint.rules.AnsibleLintRule): 70 | """ 71 | Rule class to test if given task has a valid name satisfies the naming rule 72 | in the organization. 73 | """ 74 | id = ID 75 | shortdesc = 'All tasks should be named correctly' 76 | description = DESC 77 | severity = 'MEDIUM' 78 | tags = [ID, 'task', 'readability', 'formatting'] 79 | 80 | @functools.lru_cache() 81 | def valid_name_re(self) -> typing.Pattern: 82 | """A valid task name pattern. 83 | """ 84 | pattern_s = self.get_config(C_NAME_RE) 85 | if pattern_s: 86 | try: 87 | return re.compile(pattern_s) 88 | except BaseException: # pylint: disable=broad-except 89 | warnings.warn(f'Invalid pattern "{pattern_s}"') 90 | 91 | return DEFAULT_NAME_RE 92 | 93 | @functools.lru_cache() 94 | def is_invalid_task_name(self, name: str) -> bool: 95 | """ 96 | Test if given task's name is invalid. 97 | """ 98 | return self.valid_name_re().match(name) is None 99 | 100 | def matchtask(self, task: typing.Dict[str, typing.Any], 101 | file: 'Optional[Lintable]' = None 102 | ) -> typing.Union[bool, str]: 103 | """ 104 | .. seealso:: ansiblelint.rules.AnsibleLintRule.matchtask 105 | """ 106 | if is_named_task(task): 107 | name = task.get('name', False) 108 | if not name: 109 | return 'Task has no name: {}'.format( 110 | ansiblelint.utils.task_to_str(task) 111 | ) 112 | if self.is_invalid_task_name(name): 113 | return f"Invalid task name '{name}'" 114 | 115 | return False 116 | 117 | # vim:sw=4:ts=4:et: 118 | -------------------------------------------------------------------------------- /rules/TasksFileHasValidNameRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | """Lint rule class to test if tasks files have valid filenames. 6 | """ 7 | import functools 8 | import pathlib 9 | import re 10 | import typing 11 | import warnings 12 | 13 | import ansiblelint.errors 14 | import ansiblelint.file_utils 15 | import ansiblelint.rules 16 | 17 | if typing.TYPE_CHECKING: 18 | from ansiblelint.constants import odict 19 | 20 | 21 | ID: str = 'tasks_file_has_valid_name' 22 | DESC: str = r"""Rule to test if file defines tasks has valid filename. 23 | 24 | - Options 25 | 26 | - ``name`` gives a valid task filename pattern (regexp) 27 | - ``unicode`` allows unicode characters are used in filenames 28 | 29 | - Configuration 30 | 31 | .. code-block:: yaml 32 | 33 | rules: 34 | tasks_file_has_valid_name: 35 | name: ^\w+\.ya?ml$ 36 | unicode: false 37 | """ 38 | C_NAME_RE: str = 'name' 39 | C_UNICODE: str = 'unicode' 40 | 41 | DEFAULT_NAME_RE: typing.Pattern = re.compile(r'^\w+\.ya?ml$', re.ASCII) 42 | 43 | 44 | class TasksFileHasValidNameRule(ansiblelint.rules.AnsibleLintRule): 45 | """ 46 | Rule class to test if tasks file has a valid filename satisfies the file 47 | naming rules in the organization. 48 | """ 49 | id = ID 50 | shortdesc: str = 'Tasks file must have valid filename' 51 | description = DESC 52 | severity = 'HIGH' 53 | tags = [ID, 'task'] 54 | 55 | @functools.lru_cache() 56 | def valid_name_re(self) -> typing.Pattern: 57 | """A valid task name pattern. 58 | """ 59 | pattern_s = self.get_config(C_NAME_RE) 60 | if pattern_s: 61 | try: 62 | if self.get_config(C_UNICODE): 63 | return re.compile(pattern_s) 64 | 65 | return re.compile(pattern_s, re.ASCII) 66 | except BaseException: # pylint: disable=broad-except 67 | warnings.warn(f'Invalid pattern "{pattern_s}"') 68 | 69 | return DEFAULT_NAME_RE 70 | 71 | @functools.lru_cache() 72 | def is_valid_filename(self, path: str) -> bool: 73 | """ 74 | Test if given task's filename is valid. 75 | """ 76 | return self.valid_name_re().match(pathlib.Path(path).name) is not None 77 | 78 | def matchplay(self, file: ansiblelint.file_utils.Lintable, 79 | _data: 'odict[str, typing.Any]' 80 | ) -> typing.List[ansiblelint.errors.MatchError]: 81 | """ 82 | .. seealso:; ansiblelint.rules.AnsibleLintRule.matchplay 83 | """ 84 | if file.kind == 'tasks': 85 | path = str(file.path) 86 | if not self.is_valid_filename(path): 87 | return [ 88 | self.create_matcherror(message=f'{self.shortdesc}: {path}', 89 | filename=file.name) 90 | ] 91 | 92 | return [] 93 | -------------------------------------------------------------------------------- /rules/VarsInVarsFilesHaveValidNamesRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | """Lint rule class to test if variables in vars files have valid names. 6 | """ 7 | import functools 8 | import re 9 | import typing 10 | import warnings 11 | 12 | import ansiblelint.rules 13 | import ansiblelint.utils 14 | 15 | if typing.TYPE_CHECKING: 16 | from ansiblelint.constants import odict 17 | from ansiblelint.errors import MatchError 18 | from ansiblelint.file_utils import Lintable 19 | 20 | 21 | ID: str = 'vars_in_vars_files_have_valid_names' 22 | DESC: str = r"""Rule to test if variables in vars files have valid names. 23 | 24 | - Notes 25 | 26 | - Variables files = {host_vars,group_vars,vars,defaults}/**/*.ya?ml 27 | - .. seealso:: ansiblelint.config.DEFAULT_KINDS 28 | 29 | - Options 30 | 31 | - ``name`` gives a valid variable name pattern (regexp) 32 | - ``unicode`` allows unicode characters are used in variable names 33 | 34 | - Configuration 35 | 36 | .. code-block:: yaml 37 | 38 | rules: 39 | vars_in_vars_files_have_valid_names: 40 | name: ^\w+$ 41 | unicode: false 42 | """ 43 | C_NAME_RE: str = 'name' 44 | C_UNICODE: str = 'unicode' 45 | 46 | DEFAULT_NAME_RE: typing.Pattern = re.compile(r'^\w+$', re.ASCII) 47 | 48 | 49 | def each_keys(data: 'odict[str, typing.Any]') -> typing.Iterator[str]: 50 | """ 51 | Traverse nested dict and yield keys. 52 | """ 53 | for key, _val, _parent in ansiblelint.utils.nested_items(data): 54 | # Special case. 55 | # .. seealso:: ansiblelint.utils.nested_items 56 | if key == 'list-item': 57 | continue 58 | 59 | yield key 60 | 61 | 62 | class VarsInVarsFilesHaveValidNamesRule(ansiblelint.rules.AnsibleLintRule): 63 | """ 64 | Rule class to test if variables defined in vars files (host_vars, 65 | group_vars, defaults, vars) have valid names follow the naming rules. 66 | """ 67 | id = ID 68 | shortdesc: str = 'Variable in vars files must have valid name' 69 | description = DESC 70 | severity = 'HIGH' 71 | tags = ['idiom'] 72 | 73 | @functools.lru_cache(None) 74 | def valid_name_re(self) -> typing.Pattern: 75 | """A valid variable name pattern. 76 | """ 77 | pattern_s = self.get_config(C_NAME_RE) 78 | if pattern_s: 79 | try: 80 | if self.get_config(C_UNICODE): 81 | return re.compile(pattern_s) 82 | 83 | return re.compile(pattern_s, re.ASCII) 84 | except BaseException: # pylint: disable=broad-except 85 | warnings.warn(f'Invalid pattern "{pattern_s}"') 86 | 87 | return DEFAULT_NAME_RE 88 | 89 | @functools.lru_cache() 90 | def is_invalid_name(self, var_name: str) -> bool: 91 | """ 92 | True if given variable name is NOT valid. 93 | """ 94 | return self.valid_name_re().match(var_name) is None 95 | 96 | def matchplay(self, file: 'Lintable', 97 | data: 'odict[str, typing.Any]' 98 | ) -> typing.List['MatchError']: 99 | """ 100 | .. seealso:; ansiblelint.rules.AnsibleLintRule.matchplay 101 | """ 102 | if file.kind == 'vars': 103 | return [ 104 | self.create_matcherror( 105 | details=f'{self.shortdesc}: {var_name}', filename=file 106 | ) 107 | for var_name in each_keys(data) 108 | if self.is_invalid_name(var_name) 109 | ] 110 | 111 | return [] 112 | -------------------------------------------------------------------------------- /rules/VarsShouldNotBeUsedRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | """Lint rule class to test if vars and include_vars are used. 6 | """ 7 | import functools 8 | import re 9 | import typing 10 | import warnings 11 | 12 | import ansiblelint.errors 13 | import ansiblelint.file_utils 14 | import ansiblelint.rules 15 | 16 | 17 | ID: str = "vars_should_not_be_used" 18 | 19 | VARS_DIRECTIVES: typing.FrozenSet = frozenset(""" 20 | include_vars 21 | vars 22 | vars_files 23 | """.split()) 24 | 25 | VARS_RE: typing.Pattern = re.compile( 26 | r'^(\s+)?(-\s+)?(' 27 | f"{'|'.join(VARS_DIRECTIVES)}" 28 | r'):', re.ASCII 29 | ) 30 | 31 | 32 | KINDS: typing.FrozenSet[str] = frozenset( 33 | 'playbook tasks role'.split() 34 | ) 35 | 36 | 37 | @functools.lru_cache() 38 | def contains_vars_directive(path: str) -> bool: 39 | """ 40 | Test if file of given path contains vars directives in it. 41 | """ 42 | try: 43 | with open(path) as fobj: 44 | for line in fobj: 45 | if VARS_RE.match(line): 46 | return True 47 | except (IOError, OSError) as exc: 48 | warnings.warn(f'Failed to load {path}, exc={exc!r}') 49 | 50 | return False 51 | 52 | 53 | class VarsShouldNotBeUsedRule(ansiblelint.rules.AnsibleLintRule): 54 | """ 55 | Rule class to test if vars directives are used. 56 | """ 57 | id: str = ID 58 | shortdesc: str = 'vars should not be used' 59 | description: str = ('vars and include_vars should not be used and ' 60 | 'replaced with variables defined in inventory ' 61 | 'and related data instead.') 62 | severity = 'LOW' 63 | tags = [ID, 'readability', 'formatting'] 64 | 65 | def matchyaml(self, file: ansiblelint.file_utils.Lintable 66 | ) -> typing.List[ansiblelint.errors.MatchError]: 67 | """Test playbook files. 68 | """ 69 | if file.kind in KINDS: 70 | path = str(file.path) 71 | if contains_vars_directive(path): 72 | return [ 73 | self.create_matcherror(message=f'{self.shortdesc}: {path}') 74 | ] 75 | 76 | return [] 77 | 78 | # vim:sw=4:ts=4:et: 79 | -------------------------------------------------------------------------------- /rules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssato/ansible-lint-custom-rules/1021976e9db1f7d28634f42f2af8c5d30465994b/rules/__init__.py -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # .. seealso:: https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files 2 | 3 | # .. seealso:: https://wheel.readthedocs.io/en/stable/ 4 | [bdist_wheel] 5 | universal = 1 6 | 7 | [aliases] 8 | dists = clean --all sdist bdist_wheel 9 | 10 | [metadata] 11 | name = ansiblelint_custom_rules_ex 12 | description = Ansible-lint custom rule examples 13 | project_urls = 14 | CI: Travis = https://travis-ci.org/ssato/ansible-lint-custom-rules 15 | Bug Tracker = https://github.com/ssato/ansible-lint-custom-rules/issues 16 | Source = https://github.com/ssato/ansible-lint-custom-rules 17 | long_description = 18 | Some code examples to extend ansible-lint by adding custom rules 19 | implementations. 20 | 21 | author = Satoru SATOH 22 | author_email = ssato@redhat.com 23 | maintainer = Satoru SATOH 24 | maintainer_email = ssato@redhat.com 25 | license = MIT 26 | url = https://github.com/ssato/ansible-lint-custom-rules 27 | classifiers = 28 | Development Status :: 4 - Beta 29 | Intended Audience :: Developers 30 | Programming Language :: Python 31 | Programming Language :: Python :: 3 32 | Programming Language :: Python :: 3.6 33 | Programming Language :: Python :: 3.7 34 | Programming Language :: Python :: 3.8 35 | Programming Language :: Python :: 3.9 36 | Environment :: Console 37 | Operating System :: OS Independent 38 | Topic :: Software Development :: Libraries :: Python Modules 39 | Topic :: Utilities 40 | License :: OSI Approved :: MIT License 41 | 42 | [options] 43 | include_package_data = True 44 | packages = 45 | ansiblelint.rules.custom.ssato 46 | package_dir = 47 | ansiblelint.rules.custom.ssato = rules 48 | 49 | # minimum dependencies. 50 | install_requires = 51 | setuptools 52 | ansible-lint 53 | 54 | [options.extras_require] 55 | devel = 56 | coveralls 57 | flake8 58 | mock 59 | pytest 60 | pycodestyle 61 | pylint 62 | 63 | [options.packages.find] 64 | where = . 65 | exclude = 66 | tests 67 | tests.* 68 | 69 | [tool:pytest] 70 | testpaths = 71 | tests 72 | 73 | python_files = 74 | test_*.py 75 | Test*.py 76 | 77 | addopts = --doctest-modules -n auto --cov=rules -vv 78 | 79 | [flake8] 80 | per-file-ignores = 81 | tests/common/__init__.py:F401 82 | 83 | # vim:sw=4:ts=4:et: 84 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import os.path 4 | import os 5 | import setuptools 6 | import setuptools.command.bdist_rpm 7 | 8 | 9 | NAME = "ansiblelint_custom_rules_ex" 10 | VERSION = "0.3.1" 11 | 12 | # For daily snapshot versioning mode: 13 | if os.environ.get("_SNAPSHOT_BUILD", None) is not None: 14 | import datetime 15 | VERSION = VERSION + datetime.datetime.now().strftime(".%Y%m%d") 16 | 17 | 18 | class bdist_rpm(setuptools.command.bdist_rpm.bdist_rpm): 19 | """Override the default content of the RPM SPEC. 20 | """ 21 | spec_tmpl = os.path.join(os.path.abspath(os.curdir), "package.spec.in") 22 | 23 | def _replace(self, line): 24 | """Replace some strings in the RPM SPEC template""" 25 | if "@VERSION@" in line: 26 | return line.replace("@VERSION@", VERSION) 27 | 28 | if "Source0:" in line: # Dirty hack 29 | return "Source0: %{pkgname}-%{version}.tar.gz" 30 | 31 | return line 32 | 33 | def _make_spec_file(self): 34 | return [self._replace(l.rstrip()) for l 35 | in open(self.spec_tmpl).readlines()] 36 | 37 | 38 | setuptools.setup(name=NAME, version=VERSION, 39 | cmdclass=dict(bdist_rpm=bdist_rpm)) 40 | 41 | # vim:sw=4:ts=4:et: 42 | -------------------------------------------------------------------------------- /tests/TestBlockedModules.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | # pylint: disable=too-few-public-methods,missing-class-docstring 6 | # pylint: disable=missing-function-docstring 7 | """Test cases for the rule, BlockedModules. 8 | """ 9 | from rules import BlockedModules as TT 10 | from tests import common 11 | 12 | 13 | class Base(common.Base): 14 | this_mod: common.MaybeModT = TT 15 | 16 | 17 | class RuleTestCase(common.RuleTestCase): 18 | base_cls = Base 19 | 20 | 21 | class CliTestCase(common.CliTestCase): 22 | base_cls = Base 23 | -------------------------------------------------------------------------------- /tests/TestDebugRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020, 2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | # pylint: disable=too-few-public-methods,missing-class-docstring 6 | # pylint: disable=missing-function-docstring 7 | """Test cases for the rule, DebugRule. 8 | """ 9 | import os 10 | import unittest.mock 11 | 12 | import pytest 13 | 14 | from rules import DebugRule as TT 15 | from tests import common 16 | 17 | 18 | @pytest.mark.parametrize( 19 | ('env', 'exp'), 20 | (({}, False), 21 | ({TT.E_ENABLED_VAR: ''}, False), 22 | ({TT.E_ENABLED_VAR: '1'}, True), 23 | ) 24 | ) 25 | def test_is_enabled(env, exp): 26 | with unittest.mock.patch.dict(os.environ, env, clear=True): 27 | assert TT.is_enabled() == exp 28 | 29 | 30 | class Base(common.Base): 31 | this_mod: common.MaybeModT = TT 32 | 33 | 34 | class RuleTestCase(common.RuleTestCase): 35 | base_cls = Base 36 | 37 | def test_base_get_filename(self): 38 | self.assertEqual(self.base.get_filename(), 'TestDebugRule.py') 39 | 40 | def test_base_get_rule_name(self): 41 | self.assertEqual(self.base.get_rule_name(), 'DebugRule') 42 | 43 | def test_base_get_rule_class_by_name(self): 44 | rule_class = self.base.get_rule_class_by_name(self.base.name) 45 | self.assertTrue(bool(rule_class)) 46 | self.assertTrue(isinstance(rule_class(), type(self.base.rule))) 47 | 48 | def test_base_is_runnable(self): 49 | self.assertTrue(self.base.is_runnable()) 50 | 51 | def test_list_test_data_dirs(self): 52 | self.assertTrue(self.list_test_data_dirs(True)) 53 | self.assertTrue(self.list_test_data_dirs(False)) 54 | 55 | def test_clear_fns(self): 56 | fns = self.base.clear_fns 57 | self.assertTrue(fns) 58 | self.assertTrue(len(fns) > 1) 59 | 60 | 61 | class CliTestCase(common.CliTestCase): 62 | base_cls = Base 63 | -------------------------------------------------------------------------------- /tests/TestFileHasValidNameRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | # pylint: disable=too-few-public-methods,missing-class-docstring 6 | # pylint: disable=missing-function-docstring 7 | """Test cases for the rule, FileHasValidNameRule. 8 | """ 9 | import pytest 10 | 11 | import ansiblelint.config 12 | 13 | from rules import FileHasValidNameRule as TT 14 | from tests import common 15 | 16 | 17 | NG_VALID_NAME_RE = r'\S+NEVER_MATCH' 18 | 19 | 20 | @pytest.mark.parametrize( 21 | ('path', 'name', 'unicode', 'expected'), 22 | (('main.yml', '', False, False), 23 | ('main-0.yml', '', False, True), 24 | ('main .yml', '', False, True), 25 | ('ng_1.yml', '', False, True), 26 | ('ng_1.yml', r'^\w+\.ya?ml$', True, False), 27 | ('ng-2.yml', '', False, True), 28 | ('main-0.yml', r'\S+', False, False), 29 | ) 30 | ) 31 | def test_is_invalid_filename(path, name, unicode, expected, monkeypatch): 32 | base = Base() 33 | rule = base.rule 34 | ansiblelint.config.options.rules = { 35 | rule.id: dict(name=TT.DEFAULT_NAME_RE.pattern, unicode=False) 36 | } 37 | 38 | if name: 39 | monkeypatch.setitem( 40 | ansiblelint.config.options.rules, TT.ID, 41 | dict(name=name, unicode=unicode) 42 | ) 43 | assert rule.is_invalid_filename(path) == expected 44 | base.clear() 45 | 46 | 47 | class Base(common.Base): 48 | this_mod: common.MaybeModT = TT 49 | 50 | 51 | class RuleTestCase(common.RuleTestCase): 52 | base_cls = Base 53 | 54 | 55 | class CliTestCase(common.CliTestCase): 56 | base_cls = Base 57 | -------------------------------------------------------------------------------- /tests/TestFileIsSmallEnoughRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | # pylint: disable=too-few-public-methods,missing-class-docstring 6 | # pylint: disable=missing-function-docstring 7 | """Test cases for the rule. 8 | """ 9 | import pytest 10 | 11 | from rules import FileIsSmallEnoughRule as TT 12 | from tests import common 13 | 14 | 15 | @pytest.mark.parametrize( 16 | 'max_lines,expected', 17 | [(100000, False), 18 | (1, True), 19 | ] 20 | ) 21 | def test_exceeds_max_lines(max_lines, expected): 22 | assert TT.exceeds_max_lines(__file__, max_lines) == expected 23 | 24 | 25 | class Base(common.Base): 26 | this_mod: common.MaybeModT = TT 27 | 28 | 29 | class RuleTestCase(common.RuleTestCase): 30 | base_cls = Base 31 | 32 | 33 | class CliTestCase(common.CliTestCase): 34 | base_cls = Base 35 | -------------------------------------------------------------------------------- /tests/TestLoopIsRecommendedRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | # pylint: disable=too-few-public-methods,missing-class-docstring 6 | # pylint: disable=missing-function-docstring 7 | """Test cases for the rule, LoopIsRecommendedRule. 8 | """ 9 | from rules import LoopIsRecommendedRule as TT 10 | from tests import common 11 | 12 | 13 | class Base(common.Base): 14 | this_mod: common.MaybeModT = TT 15 | 16 | 17 | class RuleTestCase(common.RuleTestCase): 18 | base_cls = Base 19 | 20 | 21 | class CliTestCase(common.CliTestCase): 22 | base_cls = Base 23 | -------------------------------------------------------------------------------- /tests/TestNoEmptyDataFilesRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | # pylint: disable=too-few-public-methods,missing-class-docstring 6 | # pylint: disable=missing-function-docstring 7 | """Test cases for the rule. 8 | """ 9 | import pytest 10 | 11 | from rules import NoEmptyDataFilesRule as TT 12 | from tests import common 13 | 14 | 15 | @pytest.mark.parametrize( 16 | ('content', 'expected'), 17 | (('', False), 18 | ('---\n', False), 19 | ('---\n{}\n', False), 20 | ('---\na: 1\n', True), 21 | ) 22 | ) 23 | def test_yml_file_has_some_data(content, expected, tmp_path): 24 | path = tmp_path / 'test.yml' 25 | path.write_text(content) 26 | 27 | assert TT.yml_file_has_some_data(str(path)) == expected 28 | TT.yml_file_has_some_data.cache_clear() 29 | 30 | 31 | class Base(common.Base): 32 | this_mod: common.MaybeModT = TT 33 | 34 | 35 | class RuleTestCase(common.RuleTestCase): 36 | base_cls = Base 37 | 38 | 39 | class CliTestCase(common.CliTestCase): 40 | base_cls = Base 41 | -------------------------------------------------------------------------------- /tests/TestTaskHasValidNameRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | # pylint: disable=missing-function-docstring,missing-class-docstring 6 | # pylint: disable=too-few-public-methods 7 | """Test cases for the rule, TaskHasValidNamePatternRule. 8 | """ 9 | import ansiblelint.config 10 | import pytest 11 | 12 | from rules import TaskHasValidNameRule as TT 13 | from tests import common 14 | 15 | 16 | VALID_NAME_0 = 'Ensure foo is installed' 17 | INVALID_NAME_0 = 'foo' 18 | NAME_RE_0 = r'^\S+$' 19 | 20 | CNF_0 = dict(name=NAME_RE_0) 21 | 22 | 23 | @pytest.mark.parametrize( 24 | ('name', 'evalue', 'expected'), 25 | ((VALID_NAME_0, '', False), # default. 26 | (INVALID_NAME_0, '', True), 27 | (VALID_NAME_0, r'NEVER_MATCH', True), 28 | (VALID_NAME_0, NAME_RE_0, True), 29 | (INVALID_NAME_0, NAME_RE_0, False), 30 | ) 31 | ) 32 | def test_is_invalid_task_name(name, evalue, expected, monkeypatch): 33 | monkeypatch.setitem( 34 | ansiblelint.config.options.rules, TT.ID, 35 | dict(name=evalue) 36 | ) 37 | base = Base() 38 | rule = base.rule 39 | assert rule.is_invalid_task_name(name) == expected 40 | 41 | 42 | class Base(common.Base): 43 | this_mod: common.MaybeModT = TT 44 | 45 | 46 | class RuleTestCase(common.RuleTestCase): 47 | base_cls = Base 48 | 49 | 50 | class CliTestCase(common.CliTestCase): 51 | base_cls = Base 52 | -------------------------------------------------------------------------------- /tests/TestTasksFileHasValidNameRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | # pylint: disable=too-few-public-methods,missing-class-docstring 6 | # pylint: disable=missing-function-docstring 7 | """Test cases for the rule, TasksFileHasValidName. 8 | """ 9 | import ansiblelint.config 10 | import pytest 11 | 12 | from rules import TasksFileHasValidNameRule as TT 13 | from tests import common 14 | 15 | 16 | class Base(common.Base): 17 | this_mod: common.MaybeModT = TT 18 | default_skip_list = ['file_has_valid_name'] 19 | 20 | 21 | class RuleTestCase(common.RuleTestCase): 22 | base_cls = Base 23 | 24 | 25 | class CliTestCase(common.CliTestCase): 26 | base_cls = Base 27 | 28 | 29 | @pytest.mark.parametrize( 30 | 'path,name,unicode,expected', 31 | [('tasks/main.yml', '', False, True), 32 | ('tasks/incl/main.yml', '', False, True), 33 | ('tasks/main-0.yml', '', False, False), 34 | ('tasks/main .yml', '', False, False), 35 | ('tasks/ng_1.yml', '', False, False), 36 | ('tasks/ng_1.yml', r'^\w+\.ya?ml$', True, True), 37 | ('tasks/ng-2.yml', '', False, False), 38 | ('tasks/include/main-0.yml', r'\S+', False, True), 39 | ] 40 | ) 41 | def test_is_valid_filename(path, name, unicode, expected, monkeypatch): 42 | if name: 43 | patch = dict(name=name, unicode=unicode) 44 | else: 45 | patch = dict(unicode=unicode) 46 | 47 | # pylint: disable=no-member 48 | monkeypatch.setitem(ansiblelint.config.options.rules, TT.ID, patch) 49 | base = Base() 50 | assert base.rule.is_valid_filename(path) == expected 51 | base.clear() 52 | -------------------------------------------------------------------------------- /tests/TestVarsInVarsFilesHaveValidNamesRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | # pylint: disable=missing-function-docstring,missing-class-docstring 6 | # pylint: disable=too-few-public-methods 7 | """Test cases for the rule, VarsInVarsFilesHaveValidNamesRule. 8 | """ 9 | from rules import VarsInVarsFilesHaveValidNamesRule as TT 10 | from tests import common 11 | 12 | 13 | class Base(common.Base): 14 | this_mod: common.MaybeModT = TT 15 | default_skip_list = ['vars_should_not_be_used'] 16 | 17 | 18 | class RuleTestCase(common.RuleTestCase): 19 | base_cls = Base 20 | 21 | 22 | class CliTestCase(common.CliTestCase): 23 | base_cls = Base 24 | -------------------------------------------------------------------------------- /tests/TestVarsShouldNotBeUsedRule.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020,2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | # pylint: disable=too-few-public-methods,missing-class-docstring 6 | # pylint: disable=missing-function-docstring 7 | """Test cases for the rule, VarsShouldNotBeUsedRule. 8 | """ 9 | import pytest 10 | 11 | from rules import VarsShouldNotBeUsedRule as TT 12 | from tests import common 13 | 14 | 15 | RES_DIR = common.TESTS_RES_DIR / 'VarsShouldNotBeUsedRule' 16 | 17 | 18 | class Base(common.Base): 19 | this_mod: common.MaybeModT = TT 20 | 21 | 22 | @pytest.mark.parametrize( 23 | ('path', 'expected'), 24 | ((str(RES_DIR / 'ok/0/plays_ok_0.yml'), False), 25 | (str(RES_DIR / 'ng/0/playbook.yml'), True), 26 | (str(RES_DIR / 'ng/1/playbook.yml'), True), 27 | ) 28 | ) 29 | def test_contains_vars_directive(path, expected): 30 | assert TT.contains_vars_directive(path) == expected 31 | TT.contains_vars_directive.cache_clear() 32 | 33 | 34 | class RuleTestCase(common.RuleTestCase): 35 | base_cls = Base 36 | 37 | 38 | class CliTestCase(common.CliTestCase): 39 | base_cls = Base 40 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssato/ansible-lint-custom-rules/1021976e9db1f7d28634f42f2af8c5d30465994b/tests/__init__.py -------------------------------------------------------------------------------- /tests/common/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | """Entry point of tests.common.*. 5 | """ 6 | from .base import ( 7 | MaybeModT, Base 8 | ) 9 | from .constants import ( 10 | TESTS_DIR, TESTS_RES_DIR, RULES_DIR, 11 | ) 12 | from .testcases import ( 13 | RuleTestCase, CliTestCase 14 | ) 15 | 16 | __all__ = [ 17 | 'TESTS_DIR', 'TESTS_RES_DIR', 'RULES_DIR', 18 | 'MaybeModT', 'Base', 'RuleTestCase', 'CliTestCase', 19 | ] 20 | -------------------------------------------------------------------------------- /tests/common/base.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020, 2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | """An abstract class to help to collect test data and tests for target rule. 6 | """ 7 | import functools 8 | import inspect 9 | import pathlib 10 | import re 11 | import types 12 | import typing 13 | 14 | from . import constants, runner, utils 15 | 16 | 17 | MaybeModT = typing.Optional[types.ModuleType] 18 | 19 | # Try to resolve the name of the rule class from the name of the test code. For 20 | # example, name will be resolved to 'DebugRule' if the test code for the rule 21 | # DebugRule is TestDebugRule.py. 22 | RULE_NAME_RE: typing.Pattern = re.compile( 23 | r'^test_?(\w+).py$', 24 | re.IGNORECASE | re.ASCII 25 | ) 26 | 27 | 28 | # pylint: disable=protected-access 29 | def each_lru_cache_clear_fns(*objs): 30 | """Get the callable object which wrapped with functools.lru_cache. 31 | """ 32 | for obj in objs: 33 | funcs = [ 34 | (fun, name) for name, fun in inspect.getmembers(obj) 35 | if isinstance(fun, functools._lru_cache_wrapper) 36 | ] 37 | for fun, _name in funcs: 38 | yield getattr(fun, 'cache_clear') # It should have this attr. 39 | 40 | 41 | class Base: 42 | """Base class for test rule cases. 43 | 44 | .. note:: 45 | 46 | The test case of the following methods are implemented in 47 | tests.TestDebugRule because it needs a concrete rule class to run. 48 | 49 | - get_rule_name 50 | - get_rule_instance_by_name 51 | """ 52 | # .. todo:: 53 | # I don't know how to compute and set them in test case classes in 54 | # modules import this module. 55 | this_mod: MaybeModT = None 56 | 57 | use_default_rules: bool = False 58 | 59 | # List other rules' IDs conflict with this during tests. 60 | default_skip_list: typing.List[str] = [] 61 | 62 | @classmethod 63 | def is_runnable(cls): 64 | """ 65 | This class is not runnable but chidlren classes have the appropriate 66 | member this_mod should be runnable. 67 | """ 68 | return bool(cls.this_mod) 69 | 70 | @classmethod 71 | def get_filename(cls) -> str: 72 | """Resolve and get the filename of self like __file___ dynamically. 73 | 74 | .. note:: 75 | 76 | This method must be a class method because inspect.getfile(self) 77 | should fail. 78 | """ 79 | return pathlib.Path(inspect.getfile(cls)).name 80 | 81 | @classmethod 82 | def get_rule_name(cls) -> str: 83 | """Resolve the name of the target rule by filename (__file__). 84 | """ 85 | match = RULE_NAME_RE.match(cls.get_filename()) 86 | if match: 87 | return match.groups()[0] 88 | 89 | return '' 90 | 91 | @classmethod 92 | def get_rule_class_by_name(cls, rule_name): 93 | """Get the rule instance to test.""" 94 | rule_cls = getattr(cls.this_mod, rule_name) 95 | if not rule_cls: 96 | raise ValueError(f'No such rule class {rule_name} ' 97 | f'in {cls.this_mod!r}.') 98 | return rule_cls 99 | 100 | @classmethod 101 | def get_test_data_dir(cls, root: pathlib.Path) -> pathlib.Path: 102 | """Get the top dir to keep test data for this rule.""" 103 | return root / cls.get_rule_name() 104 | 105 | def __init__(self): 106 | """Initialize.""" 107 | if not self.is_runnable(): 108 | return 109 | 110 | # .. note:: 111 | # The followings only happen in children classes inherits this and 112 | # have appropriate self.this_mod. 113 | self.name = self.get_rule_name() 114 | self.rule_class = self.get_rule_class_by_name(self.name) 115 | self.rule = self.rule_class() 116 | 117 | self.clear_fns = list( 118 | each_lru_cache_clear_fns(self.this_mod, self.rule_class) 119 | ) 120 | 121 | args = (self.rule, constants.RULES_DIR) 122 | kwargs = dict( 123 | skip_list=self.default_skip_list, 124 | enable_default=self.use_default_rules 125 | ) 126 | self.rule_runner = runner.RuleRunner(*args, **kwargs) 127 | self.cli_runner = runner.CliRunner(*args, **kwargs) 128 | 129 | def clear(self): 130 | """Call clear function if it's callable. 131 | 132 | .. note:: 133 | 134 | It depends on each_lru_cache_clear_fns entirely. It might need to 135 | call utis.clear_all_lru_cache instead. 136 | """ 137 | for clear_fn in self.clear_fns: 138 | clear_fn() # pylint: disable=not-callable 139 | 140 | # Ensure all cache were cleared just in case. 141 | utils.clear_all_lru_cache() # May be too powerful. 142 | 143 | def list_test_data_dirs(self, subdir: str, 144 | root: typing.Optional[pathlib.Path] = None 145 | ) -> typing.Iterator[pathlib.Path]: 146 | """List test data dirs contain playbook and related data. 147 | """ 148 | if root is None or not root: 149 | root = constants.TESTS_RES_DIR 150 | 151 | datadir = self.get_test_data_dir(root) 152 | dirs = sorted( 153 | d for d in datadir.glob(f'{subdir}/*') if d.is_dir() 154 | ) 155 | if not dirs: 156 | raise OSError(f'{self.name}: No test data dirs found [{subdir}]') 157 | 158 | return dirs 159 | 160 | def run(self, workdir: pathlib.Path, isolated: bool = True, 161 | cli: bool = False): 162 | """Run Ansible Lint Runner at dir ``workdir``.""" 163 | if not self.is_runnable(): 164 | raise ValueError(f'Not initialized! rule: {self.rule}') 165 | 166 | if cli: 167 | return self.cli_runner.run(workdir, isolated=isolated) 168 | 169 | return self.rule_runner.run(workdir, isolated=isolated) 170 | 171 | # vim:sw=4:ts=4:et: 172 | -------------------------------------------------------------------------------- /tests/common/constants.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020, 2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | """Common utility test routines and classes - Some constants. 5 | """ 6 | import pathlib 7 | import typing 8 | 9 | 10 | TESTS_DIR: pathlib.Path = pathlib.Path(__file__).parent.parent.resolve() 11 | TESTS_RES_DIR = TESTS_DIR / 'res' 12 | 13 | RULES_DIR = TESTS_DIR.parent / 'rules' 14 | 15 | SUB_CTX_NAMES: typing.Tuple[str, str] = ('conf.json', 'env.json') 16 | 17 | # .. seealso:: ansiblelint.testsing.run_ansible_lint 18 | SAFE_ENV_VARS: typing.Iterable[str] = ( 19 | 'LANG', 20 | 'LC_ALL', 21 | 'LC_CTYPE', 22 | 'NO_COLOR', 23 | 'PATH', 24 | 'PYTHONIOENCODING', 25 | 'PYTHONPATH', 26 | 'TERM', 27 | ) 28 | 29 | # vim:sw=4:ts=4:et: 30 | -------------------------------------------------------------------------------- /tests/common/datatypes.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020, 2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=inherit-non-class 5 | """Common utility functios and classes - datatypes. 6 | """ 7 | import pathlib 8 | import typing 9 | 10 | 11 | class SubCtx(typing.NamedTuple): 12 | """A namedtuple object to keep sub context info, conf and env. 13 | """ 14 | conf: typing.Dict[str, typing.Any] 15 | env: typing.Dict[str, str] 16 | os_env: typing.Dict[str, str] 17 | 18 | 19 | class Context(typing.NamedTuple): 20 | """A namedtuple object to keep context info. 21 | """ 22 | workdir: pathlib.Path 23 | lintables: typing.List[typing.Any] # TBD 24 | conf: typing.Dict[str, typing.Any] 25 | env: typing.Dict[str, str] 26 | os_env: typing.Dict[str, str] 27 | 28 | 29 | class Result(typing.NamedTuple): 30 | """A namedtuple object to keep lint result and context info. 31 | """ 32 | result: typing.Any 33 | ctx: Context 34 | 35 | # vim:sw=4:ts=4:et: 36 | -------------------------------------------------------------------------------- /tests/common/runner.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020, 2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name,too-few-public-methods 5 | """Common utility test routines and classes. 6 | """ 7 | import os 8 | import pathlib 9 | import subprocess 10 | import tempfile 11 | import typing 12 | import unittest.mock 13 | 14 | import ansiblelint.config 15 | import ansiblelint.constants 16 | import ansiblelint.errors 17 | import ansiblelint.rules 18 | import ansiblelint.runner 19 | import ansiblelint.utils 20 | import yaml 21 | 22 | from . import datatypes, utils 23 | 24 | if typing.TYPE_CHECKING: 25 | from ansiblelint.file_utils import Lintable 26 | 27 | 28 | def clear_internal_caches(): 29 | """ 30 | Clear the internal caches of ansible-lint by functools.lru_cache. 31 | 32 | Because runner will run many times for different test data, all of the 33 | caches must be cleared for each run. 34 | 35 | .. todo:: Any others? 36 | """ 37 | ansiblelint.utils.parse_yaml_linenumbers.cache_clear() 38 | 39 | 40 | def get_lintables(fail_if_no_data: bool = True) -> typing.List['Lintable']: 41 | """Get a list of lintables in workdir. 42 | 43 | note: It must change dir to the workdir to collect lintables. 44 | 45 | .. seealso:: ansiblelint.utils.get_lintables 46 | """ 47 | lintables = ansiblelint.utils.get_lintables( 48 | options=ansiblelint.config.options 49 | ) 50 | if not lintables: 51 | if fail_if_no_data: 52 | raise FileNotFoundError( 53 | f'No lintables were found: {pathlib.Path().cwd()!s}' 54 | ) 55 | 56 | return [] 57 | 58 | return lintables 59 | 60 | 61 | def make_context(workdir: pathlib.Path, 62 | fail_if_no_data: bool = True) -> datatypes.Context: 63 | """Make a context object from args and loaded data. 64 | """ 65 | return datatypes.Context( 66 | workdir, 67 | get_lintables(fail_if_no_data=fail_if_no_data), 68 | *utils.load_sub_ctx_data_in_dir(workdir) 69 | ) 70 | 71 | 72 | class RuleRunner: 73 | """Base Class to run ansiblelint without a lintable in a dir. 74 | 75 | .. seealso:: ansiblelint.testing.RunFromText 76 | """ 77 | def __init__(self, rule: ansiblelint.rules.AnsibleLintRule, 78 | rules_dir: pathlib.Path, 79 | skip_list: typing.Optional[typing.List[str]] = None, 80 | enable_default: bool = False): 81 | """Initialize an instance with given rules collection. 82 | 83 | :param rule: An AnsibleLintRule instance to test 84 | :param rules_dir: The path to a dir contains custom rules 85 | :param skip_list: A list of rule IDs to disable (skip) 86 | :param enable_default: 87 | True if default rules will be enabled also 88 | """ 89 | self.rule = rule 90 | 91 | self.rulesdirs = [str(rules_dir.resolve())] 92 | if enable_default: 93 | self.rulesdirs.append(ansiblelint.constants.DEFAULT_RULESDIR) 94 | 95 | self.skip_list = skip_list.copy() if skip_list else [] 96 | 97 | options = ansiblelint.config.options 98 | # .. seealso:: ansiblelint.testing.fixtures.default_rules_collection 99 | options.enable_list = ['no-same-owner'] 100 | self.rules = ansiblelint.rules.RulesCollection( 101 | rulesdirs=self.rulesdirs, options=options 102 | ) 103 | 104 | def get_skip_list(self, isolated: bool = True) -> typing.List[str]: 105 | """Get a list of rule IDs to skip. 106 | 107 | :param isolated: True if to disable other rules 108 | """ 109 | skip_list = self.skip_list.copy() if self.skip_list else [] 110 | if isolated: 111 | skip_list.extend([ 112 | r.id for r in self.rules if r.id != self.rule.id 113 | ]) 114 | 115 | return skip_list 116 | 117 | def run_with_env(self, ctx: datatypes.Context, 118 | isolated: bool = True) -> datatypes.Result: 119 | """ 120 | Run runner with (os) environment variables are set as needed. 121 | """ 122 | runner = ansiblelint.runner.Runner( 123 | *ctx.lintables, rules=self.rules, 124 | skip_list=self.get_skip_list(isolated) 125 | ) 126 | if ctx.os_env: 127 | with unittest.mock.patch.dict(os.environ, ctx.os_env, 128 | clear=True): 129 | res = runner.run() 130 | else: 131 | res = runner.run() 132 | 133 | clear_internal_caches() 134 | return datatypes.Result(res, ctx) 135 | 136 | def run(self, workdir: pathlib.Path, isolated: bool = True 137 | ) -> typing.List[ansiblelint.errors.MatchError]: 138 | """Lint in the workdir. 139 | 140 | :param workdir: Working dir to run runner later 141 | :param isolated: True if to disable other rules 142 | """ 143 | with utils.chdir(workdir): 144 | ctx = make_context(workdir.resolve()) 145 | rule_config = ctx.conf.get('rules', {}) 146 | 147 | if rule_config: 148 | # pylint: disable=no-member 149 | with unittest.mock.patch.dict(ansiblelint.config.options.rules, 150 | rule_config): 151 | return self.run_with_env(ctx, isolated) 152 | 153 | return self.run_with_env(ctx, isolated) 154 | 155 | 156 | class CliRunner(RuleRunner): 157 | """Base Class to run ansiblelint without a lintable in a dir. 158 | 159 | .. seealso:: RuleRunner 160 | """ 161 | def __init__(self, rule: ansiblelint.rules.AnsibleLintRule, 162 | rules_dir: pathlib.Path, 163 | skip_list: typing.Optional[typing.List[str]] = None, 164 | enable_default: bool = False): 165 | """Initialize an instance with given rules collection. 166 | 167 | :param rule: An AnsibleLintRule instance to test 168 | :param rules_dir: The path to a dir contains custom rules 169 | :param skip_list: A list of rule IDs to disable (skip) 170 | :param enable_default: 171 | True if default rules will be enabled also 172 | """ 173 | super().__init__(rule, rules_dir, skip_list, enable_default) 174 | 175 | self.cmd = ['ansible-lint', '-r', f'{rules_dir.resolve()!s}'] 176 | if enable_default: 177 | self.cmd.append('-R') 178 | 179 | def run(self, workdir: pathlib.Path, isolated: bool = True 180 | ) -> typing.Tuple[int, str, str]: 181 | """Run Ansible Lint in the workdir. 182 | 183 | :param workdir: Working dir to run runner later 184 | :param isolated: True if to disable other rules 185 | 186 | .. seealso:: ansiblelint.testing.run_ansible_lint 187 | """ 188 | workdir = workdir.resolve() 189 | with utils.chdir(workdir): 190 | ctx = make_context(workdir, fail_if_no_data=False) 191 | 192 | conf = ctx.conf if ctx.conf else dict() 193 | env = utils.get_env(ctx.env or {}) 194 | 195 | conf['skip_list'] = self.get_skip_list(isolated) 196 | 197 | with tempfile.NamedTemporaryFile(mode='w') as cio: 198 | yaml.safe_dump(conf, cio) 199 | 200 | opts = dict(stdout=subprocess.PIPE, stderr=subprocess.PIPE, 201 | check=False, shell=False, env=env, cwd=str(workdir), 202 | universal_newlines=True) 203 | 204 | # pylint: disable=subprocess-run-check 205 | res = subprocess.run(self.cmd + ['-c', cio.name], **opts) 206 | return datatypes.Result(res, ctx) 207 | 208 | # vim:sw=4:ts=4:et: 209 | -------------------------------------------------------------------------------- /tests/common/test_base.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=missing-function-docstring 5 | """Test cases of tests.common.base. 6 | """ 7 | from rules import DebugRule # This depends on it. 8 | from tests.common import base as TT 9 | 10 | 11 | class FakeBase(TT.Base): 12 | """Fake Base class.""" 13 | this_mod = TT 14 | 15 | 16 | def test_get_filename(): 17 | assert FakeBase.get_filename() == 'test_base.py' 18 | 19 | 20 | def test_get_rule_name(): 21 | assert FakeBase.get_rule_name() == 'base' 22 | 23 | 24 | def test_each_lru_cache_clear_fns(): 25 | clear_fns = list(TT.each_lru_cache_clear_fns(DebugRule)) 26 | assert not clear_fns # No lru_cache-ed in module level. 27 | 28 | clear_fns = list(TT.each_lru_cache_clear_fns(DebugRule.DebugRule)) 29 | assert clear_fns # lru_cache-ed are in class level. 30 | 31 | # vim:sw=4:ts=4:et: 32 | -------------------------------------------------------------------------------- /tests/common/test_runner.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name,missing-function-docstring 5 | """Test cases of tests.common.runner. 6 | """ 7 | import sys 8 | 9 | import pytest 10 | 11 | from ansiblelint.rules.DeprecatedModuleRule import DeprecatedModuleRule 12 | from rules.BlockedModules import ID as OTHER_CUSTOM_RULE_ID_EX 13 | from rules.DebugRule import DebugRule 14 | from tests.common import constants, datatypes, runner as TT, utils 15 | 16 | 17 | # ansiblelint.rules.DeprecatedModuleRule: 18 | DEFAULT_RULE_ID_EX: str = DeprecatedModuleRule.id 19 | 20 | 21 | @pytest.mark.parametrize( 22 | ('workdir', ), 23 | ((constants.TESTS_RES_DIR / 'DebugRule/ok/0', ), 24 | ) 25 | ) 26 | def test_get_lintables_success(workdir): 27 | with utils.chdir(workdir): 28 | assert TT.get_lintables(False) 29 | assert TT.get_lintables(True) 30 | 31 | 32 | def test_get_lintables_failure(tmp_path): 33 | with utils.chdir(tmp_path): 34 | res = TT.get_lintables(False) 35 | assert not res, res 36 | 37 | with pytest.raises(FileNotFoundError) as exc: 38 | TT.get_lintables(True) 39 | 40 | assert 'No lintables' in str(exc) 41 | 42 | 43 | @pytest.mark.parametrize( 44 | ('workdir', 'conf', 'env'), 45 | ((constants.TESTS_RES_DIR / 'DebugRule/ok/0', False, False), 46 | (constants.TESTS_RES_DIR / 'DebugRule/ng/1', True, False), 47 | (constants.TESTS_RES_DIR / 'DebugRule/ng/2', False, True), 48 | ) 49 | ) 50 | def test_make_context(workdir, conf, env): 51 | ctx = TT.make_context(workdir) 52 | assert bool(ctx) 53 | assert isinstance(ctx, datatypes.Context) 54 | 55 | assert ctx.workdir == workdir.resolve() 56 | assert ctx.lintables != [] 57 | assert bool(ctx.conf) == conf, ctx.conf # TBD 58 | assert bool(ctx.env) == env, ctx.env # TBD 59 | assert bool(ctx.os_env) == env, ctx.os_env # TBD 60 | 61 | 62 | @pytest.mark.parametrize( 63 | ('rules_dir', 'skip_list', 'enable_default'), 64 | ((constants.RULES_DIR, None, False), 65 | (constants.RULES_DIR, [OTHER_CUSTOM_RULE_ID_EX], False), 66 | (constants.RULES_DIR, None, True), 67 | ) 68 | ) 69 | def test_RuleRunner__init__(rules_dir, skip_list, enable_default): 70 | runner = TT.RuleRunner( 71 | DebugRule(), rules_dir, skip_list=skip_list, 72 | enable_default=enable_default 73 | ) 74 | assert runner.rule.id == DebugRule.id, runner.rule.id 75 | assert runner.rules 76 | 77 | rule_ids = [r.id for r in runner.rules] 78 | assert DebugRule.id in rule_ids, rule_ids 79 | 80 | if skip_list: 81 | assert runner.skip_list == skip_list, runner.skip_list 82 | 83 | if enable_default: 84 | assert DEFAULT_RULE_ID_EX in rule_ids, rule_ids 85 | 86 | 87 | @pytest.mark.parametrize( 88 | ('rules_dir', 'skip_list', 'isolated'), 89 | ((constants.RULES_DIR, None, True), 90 | (constants.RULES_DIR, [OTHER_CUSTOM_RULE_ID_EX], True), 91 | (constants.RULES_DIR, None, True), 92 | (constants.RULES_DIR, None, False), 93 | (constants.RULES_DIR, [OTHER_CUSTOM_RULE_ID_EX], False), 94 | (constants.RULES_DIR, None, False), 95 | ) 96 | ) 97 | def test_RuleRunner_get_skip_list(rules_dir, skip_list, isolated): 98 | runner = TT.RuleRunner( 99 | DebugRule(), rules_dir, skip_list=skip_list, enable_default=True 100 | ) 101 | skips = runner.get_skip_list(isolated=isolated) 102 | 103 | assert DebugRule.id not in skips, skips 104 | 105 | if skip_list: 106 | assert all(s in skips for s in skip_list), skips 107 | 108 | if isolated: 109 | assert DebugRule.id not in skips, skips 110 | assert DEFAULT_RULE_ID_EX in skips, skips 111 | 112 | 113 | DEBUG_RES_DIR = constants.TESTS_RES_DIR / 'DebugRule' 114 | 115 | 116 | # see tests/res/DebugRule/{ok,ng}/... 117 | @pytest.mark.parametrize( 118 | ('workdir', 'isolated', 'success'), 119 | ((DEBUG_RES_DIR / 'ok/0', True, True), 120 | (DEBUG_RES_DIR / 'ok/0', False, True), 121 | (DEBUG_RES_DIR / 'ng/0', True, False), 122 | (DEBUG_RES_DIR / 'ng/0', False, False), 123 | ) 124 | ) 125 | def test_RuleRunner_run(workdir, isolated, success): 126 | runner = TT.RuleRunner(DebugRule(), constants.RULES_DIR) 127 | res = runner.run(workdir, isolated=isolated) 128 | if success: 129 | assert not res.result, res.result 130 | else: 131 | assert res.result 132 | 133 | 134 | @pytest.mark.parametrize( 135 | ('enable_default', ), 136 | ((False, ), 137 | (True, ), 138 | ) 139 | ) 140 | def test_CliRunner__init__(enable_default): 141 | runner = TT.CliRunner( 142 | DebugRule(), constants.RULES_DIR, enable_default=enable_default 143 | ) 144 | assert str(constants.RULES_DIR.resolve()) in runner.cmd 145 | 146 | if enable_default: 147 | assert '-R' in runner.cmd 148 | 149 | 150 | @pytest.mark.parametrize( 151 | ('workdir', 'success'), 152 | ((DEBUG_RES_DIR / 'ok/0', True), 153 | (DEBUG_RES_DIR / 'ng/0', False), 154 | ) 155 | ) 156 | def test_CliRunner_run(workdir, success): 157 | runner = TT.CliRunner(DebugRule(), constants.RULES_DIR) 158 | res = runner.run(workdir) 159 | if success: 160 | assert res.result.returncode == 0 161 | assert res.result.stdout == '' 162 | 163 | if sys.version_info.minor >= 8: 164 | assert res.result.stderr == '' 165 | else: 166 | assert res.result.returncode != 0 167 | assert bool(res.result.stdout) 168 | assert bool(res.result.stderr) 169 | 170 | # vim:sw=4:ts=4:et: 171 | -------------------------------------------------------------------------------- /tests/common/test_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=missing-function-docstring 5 | """Test cases of tests.common.runner. 6 | """ 7 | import functools 8 | import os 9 | import pathlib 10 | import random 11 | import warnings 12 | 13 | import pytest 14 | 15 | from tests.common import constants, datatypes, utils as TT 16 | 17 | 18 | def test_chdir(tmp_path): 19 | # todo: 20 | # a_file_path = tmp_path / 'a_file.txt' 21 | # a_file_path.touch() 22 | # assert a_file_path.exists() and a_file_path.is_file(), a_file_path 23 | 24 | # with pytest.raises(NotADirectoryError): 25 | # TT.chdir(a_file_path) 26 | 27 | # with pytest.raises(FileNotFoundError): 28 | # TT.chdir(tmp_path / 'this_dir_should_not_exist') 29 | 30 | pwd = pathlib.Path().cwd() 31 | with TT.chdir(tmp_path): 32 | assert pathlib.Path().cwd() != pwd 33 | assert pathlib.Path().cwd() == tmp_path 34 | 35 | assert pathlib.Path().cwd() != tmp_path 36 | assert pathlib.Path().cwd() == pwd 37 | 38 | 39 | @functools.lru_cache(None) 40 | def randome_int(imax: int = 100000000): 41 | return random.randint(0, imax) 42 | 43 | 44 | def test_clear_all_lru_cache(): 45 | first = randome_int() 46 | second = randome_int() # It should be cached one, first. 47 | assert first == second 48 | 49 | TT.clear_all_lru_cache() 50 | # There is an 1 / imax chance that it fails. 51 | assert first != randome_int() 52 | 53 | 54 | @pytest.mark.parametrize( 55 | ('updates', 'safe_list'), 56 | ((dict(), constants.SAFE_ENV_VARS), 57 | (dict(FOO='FOO'), constants.SAFE_ENV_VARS), 58 | ) 59 | ) 60 | def test_get_env(updates, safe_list): 61 | env = TT.get_env(updates, safe_list) 62 | assert env 63 | assert all(v in env for v in safe_list if v in os.environ), env 64 | assert all(v not in env for v in os.environ 65 | if v not in safe_list and v not in updates), env 66 | assert all(env[v] == updates[v] for v in updates.keys()), env 67 | assert all(env[v] == os.environ[v] for v in safe_list 68 | if v in os.environ and v not in updates), env 69 | 70 | 71 | # see: tests/res/DebugRule/ng/**/*.* 72 | @pytest.mark.parametrize( 73 | ('path', 'warn', 'exp'), 74 | ((constants.TESTS_RES_DIR / 'DebugRule/ng/2/env.json', False, True), 75 | (constants.TESTS_RES_DIR / 'not_exist.json', False, False), 76 | (constants.TESTS_RES_DIR / 'not_exist.json', True, False), 77 | ) 78 | ) 79 | def test_load_data(path, warn, exp): 80 | with warnings.catch_warnings(record=True) as warns: 81 | warnings.simplefilter("always") 82 | 83 | result = TT.load_data(path, warn=warn) 84 | assert bool(result) == exp 85 | 86 | if not exp and warn: 87 | assert len(warns) > 0 88 | assert issubclass(warns[-1].category, UserWarning) 89 | assert 'Not exist' in str(warns[-1].message) 90 | 91 | 92 | @pytest.mark.parametrize( 93 | ('workdir', 'conf', 'env'), 94 | ((constants.TESTS_RES_DIR / 'DebugRule/ok/0', False, False), 95 | (constants.TESTS_RES_DIR / 'DebugRule/ng/1', True, False), 96 | (constants.TESTS_RES_DIR / 'DebugRule/ng/2', False, True), 97 | ) 98 | ) 99 | def test_load_sub_ctx_data_in_dir(workdir, conf, env): 100 | res = TT.load_sub_ctx_data_in_dir(workdir) 101 | assert bool(res) 102 | assert isinstance(res, datatypes.SubCtx) 103 | assert bool(res.conf) == conf, res.conf # TBD 104 | assert bool(res.env) == env, res.env # TBD 105 | assert bool(res.os_env) == env, res.os_env # TBD 106 | 107 | # vim:sw=4:ts=4:et: 108 | -------------------------------------------------------------------------------- /tests/common/testcases.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020, 2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | # pylint: disable=invalid-name 5 | """Common utility classes for test cases. 6 | """ 7 | import pathlib 8 | import typing 9 | import unittest 10 | 11 | from . import base 12 | 13 | 14 | class RuleTestCase(unittest.TestCase): 15 | """Base class to test rules. 16 | """ 17 | base_cls = base.Base 18 | 19 | def setUp(self): 20 | """Setup base.""" 21 | self.base = self.base_cls() 22 | 23 | def tearDown(self): 24 | """De-initialize.""" 25 | self.base.clear() 26 | 27 | def list_test_data_dirs(self, success: bool = True 28 | ) -> typing.Iterator[pathlib.Path]: 29 | """Yield the test data dirs for the rule.""" 30 | subdir = 'ok' if success else 'ng' 31 | for datadir in self.base.list_test_data_dirs(subdir): 32 | yield datadir 33 | 34 | def lint(self, success: bool = True, isolated: bool = True) -> None: 35 | """Lint the lintables found under test data dirs with the rule. 36 | """ 37 | if not self.base.is_runnable(): 38 | return 39 | 40 | for datadir in self.list_test_data_dirs(success): 41 | res = self.base.run(datadir, isolated=isolated) 42 | 43 | msg = f'{res!r}' 44 | if success: 45 | self.assertEqual(0, len(res.result), msg) # No errors. 46 | else: 47 | self.assertTrue(len(res.result) > 0, msg) # It should fail. 48 | 49 | self.base.clear() 50 | 51 | def test_success_cases_only_with_the_rule(self): 52 | """Run test cases only with the rule, should succeed.""" 53 | self.lint() 54 | 55 | def test_success_cases_with_other_rules(self): 56 | """Run test cases together with other rules, should succeed.""" 57 | self.lint(isolated=False) 58 | 59 | def test_failure_cases_only_with_the_rule(self): 60 | """Run test cases only with the rule, should fail.""" 61 | self.lint(success=False) 62 | 63 | def test_failure_cases_with_other_rules(self): 64 | """Run test cases together with other rules, should fail.""" 65 | self.lint(success=False, isolated=False) 66 | 67 | 68 | class CliTestCase(RuleTestCase): 69 | """Base class to test rules with CLI. 70 | """ 71 | def lint(self, success: bool = True, isolated: bool = True) -> None: 72 | """Run ansible-lint in test data dirs with the rule.""" 73 | if not self.base.is_runnable(): 74 | return 75 | 76 | for datadir in self.list_test_data_dirs(success): 77 | res = self.base.run(datadir, isolated=isolated, cli=True) 78 | 79 | msg = f'{res!r}' 80 | args = (res.result.returncode, 0, msg) 81 | if success: 82 | self.assertEqual(*args) 83 | else: 84 | self.assertNotEqual(*args) 85 | 86 | # vim:sw=4:ts=4:et: 87 | -------------------------------------------------------------------------------- /tests/common/utils.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020, 2021 Satoru SATOH 2 | # SPDX-License-Identifier: MIT 3 | # 4 | """Common utility test routines and classes - utilities. 5 | """ 6 | import contextlib 7 | import functools 8 | import gc 9 | import json 10 | import os 11 | import pathlib 12 | import typing 13 | import warnings 14 | 15 | from . import constants, datatypes 16 | 17 | 18 | @contextlib.contextmanager 19 | def chdir(destdir: pathlib.Path): 20 | """Chnage dir temporary. 21 | """ 22 | saved = pathlib.Path().cwd() 23 | try: 24 | os.chdir(str(destdir)) 25 | yield 26 | finally: 27 | os.chdir(str(saved)) 28 | 29 | 30 | # pylint: disable=protected-access 31 | def clear_all_lru_cache(): 32 | """Clear the cache of all lru_cache-ed functions. 33 | """ 34 | gc.collect() 35 | wrappers = ( 36 | obj for obj in gc.get_objects() 37 | if callable(obj) and isinstance(obj, functools._lru_cache_wrapper) 38 | ) 39 | for wrapper in wrappers: 40 | wrapper.cache_clear() 41 | 42 | 43 | def get_env(env_updates: typing.Dict[str, str], 44 | safe_list: typing.Iterable[str] = constants.SAFE_ENV_VARS 45 | ) -> typing.Dict[str, str]: 46 | """Get os.environ subset updated with ``env_updates``. 47 | 48 | .. seealso:: ansiblelint.testing.run_ansible_lint 49 | """ 50 | env = env_updates.copy() if env_updates else dict() 51 | 52 | for val in safe_list: 53 | if val in os.environ and val not in env: 54 | env[val] = os.environ[val] 55 | 56 | return env 57 | 58 | 59 | def load_data(path: pathlib.Path, warn: bool = False): 60 | """An wrapper for json.load. 61 | """ 62 | if not path.exists(): 63 | if warn: 64 | warnings.warn(f'Not exist: {path!s}') 65 | return {} 66 | 67 | try: 68 | with path.open(encoding='utf-8') as fio: 69 | return json.load(fio) 70 | 71 | except (IOError, OSError) as exc: 72 | warnings.warn(f'Failed to open {path!s}, exc={exc!r}') 73 | 74 | return {} 75 | 76 | 77 | def load_sub_ctx_data_in_dir( 78 | workdir: typing.Optional[pathlib.Path], 79 | sub_ctx_names: typing.Tuple[str, str] = constants.SUB_CTX_NAMES 80 | ) -> datatypes.SubCtx: 81 | """Load sub context data (env and/or config) from given or current dir. 82 | """ 83 | conf = load_data(workdir / sub_ctx_names[0]) 84 | env = load_data(workdir / sub_ctx_names[1]) 85 | os_env = get_env(env) if env else {} 86 | 87 | return datatypes.SubCtx(conf, env, os_env) 88 | 89 | # vim:sw=4:ts=4:et: 90 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | coveralls 2 | flake8 3 | pytest 4 | pytest-cov 5 | pytest-randomly 6 | pytest-xdist[psutil] 7 | pycodestyle 8 | pylint 9 | -------------------------------------------------------------------------------- /tests/res/BlockedModules/ng/0/playbook.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | connection: local 3 | gather_facts: false 4 | tasks: 5 | - name: Run shell (danger!) 6 | shell: ls 7 | -------------------------------------------------------------------------------- /tests/res/BlockedModules/ng/1/conf.json: -------------------------------------------------------------------------------- 1 | {"rules": {"blocked_modules": {"blocked": ["ping"]}}} -------------------------------------------------------------------------------- /tests/res/BlockedModules/ng/1/playbook.yml: -------------------------------------------------------------------------------- 1 | ../../ok/0/playbook.yml -------------------------------------------------------------------------------- /tests/res/BlockedModules/ok/0/playbook.yml: -------------------------------------------------------------------------------- 1 | ../../../playbooks/plays_ok_0.yml -------------------------------------------------------------------------------- /tests/res/BlockedModules/ok/1/conf.json: -------------------------------------------------------------------------------- 1 | {"rules": {"blocked_modules": {"blocked": ["shell"]}}} -------------------------------------------------------------------------------- /tests/res/BlockedModules/ok/1/playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | connection: local 4 | gather_facts: false 5 | tasks: 6 | - name: Ping 7 | include: roles/ping/tasks/do_ping.yml 8 | -------------------------------------------------------------------------------- /tests/res/BlockedModules/ok/1/roles/ping: -------------------------------------------------------------------------------- 1 | ../../../../roles/ping -------------------------------------------------------------------------------- /tests/res/DebugRule/ng/0/conf.json: -------------------------------------------------------------------------------- 1 | {"rules": {"debug": {"enabled": true}}} -------------------------------------------------------------------------------- /tests/res/DebugRule/ng/0/playbook.yml: -------------------------------------------------------------------------------- 1 | ../../ok/0/playbook.yml -------------------------------------------------------------------------------- /tests/res/DebugRule/ng/1/conf.json: -------------------------------------------------------------------------------- 1 | ../0/conf.json -------------------------------------------------------------------------------- /tests/res/DebugRule/ng/1/playbook.yml: -------------------------------------------------------------------------------- 1 | ../../ok/1/playbook.yml -------------------------------------------------------------------------------- /tests/res/DebugRule/ng/2/env.json: -------------------------------------------------------------------------------- 1 | {"_ANSIBLE_LINT_RULE_DEBUG": "1"} -------------------------------------------------------------------------------- /tests/res/DebugRule/ng/2/playbook.yml: -------------------------------------------------------------------------------- 1 | ../0/playbook.yml -------------------------------------------------------------------------------- /tests/res/DebugRule/ok/0/playbook.yml: -------------------------------------------------------------------------------- 1 | ../../../playbooks/plays_ok_0.yml -------------------------------------------------------------------------------- /tests/res/DebugRule/ok/1/playbook.yml: -------------------------------------------------------------------------------- 1 | ../../../playbooks/ping_ok_1.yml -------------------------------------------------------------------------------- /tests/res/DebugRule/ok/1/roles/ping: -------------------------------------------------------------------------------- 1 | ../../../roles/ping -------------------------------------------------------------------------------- /tests/res/DebugRule/ok/2/conf.json: -------------------------------------------------------------------------------- 1 | {"rules": {"debug": {"enabled": false}}} -------------------------------------------------------------------------------- /tests/res/DebugRule/ok/2/playbook.yml: -------------------------------------------------------------------------------- 1 | ../0/playbook.yml -------------------------------------------------------------------------------- /tests/res/DebugRule/roles: -------------------------------------------------------------------------------- 1 | ../roles/ -------------------------------------------------------------------------------- /tests/res/FileHasValidNameRule/ng/0/ping_ng-0.yml: -------------------------------------------------------------------------------- 1 | ../../../playbooks/plays_ok_0.yml -------------------------------------------------------------------------------- /tests/res/FileHasValidNameRule/ng/1/ng-1.yml: -------------------------------------------------------------------------------- 1 | ../0/ping_ng-0.yml -------------------------------------------------------------------------------- /tests/res/FileHasValidNameRule/ng/2/ng-2.yml: -------------------------------------------------------------------------------- 1 | ../0/ping_ng-0.yml -------------------------------------------------------------------------------- /tests/res/FileHasValidNameRule/ng/3/ng 3.yml: -------------------------------------------------------------------------------- 1 | ../0/ping_ng-0.yml -------------------------------------------------------------------------------- /tests/res/FileHasValidNameRule/ng/4/conf.json: -------------------------------------------------------------------------------- 1 | {"rules": {"file_has_valid_name": {"name": "^NEVER_MATCH.ya?ml$", "unicode": false}}} 2 | -------------------------------------------------------------------------------- /tests/res/FileHasValidNameRule/ng/4/playbook_4.yml: -------------------------------------------------------------------------------- 1 | ../0/ping_ng-0.yml -------------------------------------------------------------------------------- /tests/res/FileHasValidNameRule/ok/0/ping_ok_1.yml: -------------------------------------------------------------------------------- 1 | ../../../playbooks/ping_ok_1.yml -------------------------------------------------------------------------------- /tests/res/FileHasValidNameRule/ok/0/roles/ping: -------------------------------------------------------------------------------- 1 | ../../../../roles/ping -------------------------------------------------------------------------------- /tests/res/FileHasValidNameRule/ok/1/conf.json: -------------------------------------------------------------------------------- 1 | {"rules": {"file_has_valid_name": {"name": "^[\\w-]+\\.ya?ml$", "unicode": false}}} -------------------------------------------------------------------------------- /tests/res/FileHasValidNameRule/ok/1/ping-ok-1.yml: -------------------------------------------------------------------------------- 1 | ../0/ping_ok_1.yml -------------------------------------------------------------------------------- /tests/res/FileHasValidNameRule/ok/1/roles/ping: -------------------------------------------------------------------------------- 1 | ../../../../roles/ping -------------------------------------------------------------------------------- /tests/res/FileHasValidNameRule/ok/2/conf.json: -------------------------------------------------------------------------------- 1 | {"rules": {"file_has_valid_name": {"name": "^.+.ya?ml$", "unicode": true}}} -------------------------------------------------------------------------------- /tests/res/FileHasValidNameRule/ok/2/ping_ok_2.yml: -------------------------------------------------------------------------------- 1 | ../0/ping_ok_1.yml -------------------------------------------------------------------------------- /tests/res/FileHasValidNameRule/ok/2/roles/ping: -------------------------------------------------------------------------------- 1 | ../../../../roles/ping -------------------------------------------------------------------------------- /tests/res/FileIsSmallEnoughRule/ng/0/conf.json: -------------------------------------------------------------------------------- 1 | {"rules": {"file_is_small_enough": {"max_lines": 1}}} -------------------------------------------------------------------------------- /tests/res/FileIsSmallEnoughRule/ng/0/playbook.yml: -------------------------------------------------------------------------------- 1 | ../../ok/0/playbook.yml -------------------------------------------------------------------------------- /tests/res/FileIsSmallEnoughRule/ok/0/playbook.yml: -------------------------------------------------------------------------------- 1 | ../../../playbooks/plays_ok_0.yml -------------------------------------------------------------------------------- /tests/res/FileIsSmallEnoughRule/ok/1/playbook.yml: -------------------------------------------------------------------------------- 1 | ../../../playbooks/ping_ok_1.yml -------------------------------------------------------------------------------- /tests/res/FileIsSmallEnoughRule/ok/1/roles/ping: -------------------------------------------------------------------------------- 1 | ../../../../roles/ping -------------------------------------------------------------------------------- /tests/res/LoopIsRecommendedRule/ng/0/playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | connection: local 4 | gather_facts: false 5 | vars: 6 | tasks: 7 | - name: Try ping 8 | ping: 9 | with_items: 10 | - pong 11 | -------------------------------------------------------------------------------- /tests/res/LoopIsRecommendedRule/ok/0/playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | connection: local 4 | gather_facts: false 5 | tasks: 6 | - name: Try ping 7 | ping: 8 | loop: 9 | - pong 10 | -------------------------------------------------------------------------------- /tests/res/NoEmptyDataFilesRule/ng/0/playbook.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | connection: local 3 | gather_facts: false 4 | tasks: 5 | - name: Try ping 6 | ping: 7 | 8 | - hosts: localhost 9 | connection: local 10 | gather_facts: false 11 | roles: 12 | - no_empty_data_files_rule_ng_1 13 | -------------------------------------------------------------------------------- /tests/res/NoEmptyDataFilesRule/ng/0/roles/no_empty_data_files_rule_ng_1: -------------------------------------------------------------------------------- 1 | ../../../../roles/no_empty_data_files_rule_ng_1 -------------------------------------------------------------------------------- /tests/res/NoEmptyDataFilesRule/ok/0/playbook.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | connection: local 3 | gather_facts: false 4 | tasks: 5 | - name: Try ping 6 | ping: 7 | 8 | - hosts: localhost 9 | connection: local 10 | gather_facts: false 11 | roles: 12 | - no_empty_data_files_rule_ok_1 13 | -------------------------------------------------------------------------------- /tests/res/NoEmptyDataFilesRule/ok/0/roles/no_empty_data_files_rule_ok_1: -------------------------------------------------------------------------------- 1 | ../../../../roles/no_empty_data_files_rule_ok_1 -------------------------------------------------------------------------------- /tests/res/TaskHasValidNameRule/ng/0/playbook.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | connection: local 3 | gather_facts: false 4 | tasks: 5 | - name: 6 | ping: 7 | -------------------------------------------------------------------------------- /tests/res/TaskHasValidNameRule/ng/1/playbook.yml: -------------------------------------------------------------------------------- 1 | ../../ok/2/playbook.yml -------------------------------------------------------------------------------- /tests/res/TaskHasValidNameRule/ok/0/plays_ok_0.yml: -------------------------------------------------------------------------------- 1 | ../../../playbooks/plays_ok_0.yml -------------------------------------------------------------------------------- /tests/res/TaskHasValidNameRule/ok/1/playbook.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | connection: local 3 | gather_facts: false 4 | tasks: 5 | - name: try ping 6 | ping: 7 | -------------------------------------------------------------------------------- /tests/res/TaskHasValidNameRule/ok/2/conf.json: -------------------------------------------------------------------------------- 1 | {"rules": {"task_has_valid_name": {"name": "^\\w+$"}}} -------------------------------------------------------------------------------- /tests/res/TaskHasValidNameRule/ok/2/playbook.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | connection: local 3 | gather_facts: false 4 | tasks: 5 | - name: ZZZZZ 6 | ping: 7 | -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/ng/0/playbook.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | connection: local 3 | gather_facts: false 4 | tasks: 5 | - name: include task 6 | include_tasks: tasks/ng-1.yml 7 | -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/ng/0/tasks: -------------------------------------------------------------------------------- 1 | ../../tasks -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/ng/1/playbook.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | connection: local 3 | gather_facts: false 4 | tasks: 5 | - name: include task 6 | include_tasks: "tasks/ng _2.yml" 7 | -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/ng/1/tasks: -------------------------------------------------------------------------------- 1 | ../../tasks -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/ng/2/playbook.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | connection: local 3 | gather_facts: false 4 | tasks: 5 | - name: include task 6 | include_tasks: "tasks/ng_三.yml" 7 | -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/ng/2/tasks: -------------------------------------------------------------------------------- 1 | ../../tasks -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/ok/0/playbook.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | connection: local 3 | gather_facts: false 4 | tasks: 5 | - name: include task 6 | include_tasks: tasks/ok_1.yml 7 | -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/ok/0/tasks: -------------------------------------------------------------------------------- 1 | ../../tasks -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/ok/1/playbook.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | connection: local 3 | gather_facts: false 4 | tasks: 5 | - name: include task 6 | include_tasks: tasks/ok.yml 7 | -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/ok/1/tasks: -------------------------------------------------------------------------------- 1 | ../../tasks -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/ok/2/conf.json: -------------------------------------------------------------------------------- 1 | {"rules": {"tasks_file_has_valid_name": {"name": "^[\\w-]+\\.ya?ml$", "unicode": false}}} -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/ok/2/playbook.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | connection: local 3 | gather_facts: false 4 | tasks: 5 | - name: include task 6 | include_tasks: tasks/ng-1.yml 7 | -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/ok/2/tasks: -------------------------------------------------------------------------------- 1 | ../../tasks -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/tasks/ng _2.yml: -------------------------------------------------------------------------------- 1 | ok_1.yml -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/tasks/ng-1.yml: -------------------------------------------------------------------------------- 1 | ok_1.yml -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/tasks/ng_三.yml: -------------------------------------------------------------------------------- 1 | ok_1.yml -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/tasks/ok.yml: -------------------------------------------------------------------------------- 1 | ok_1.yml -------------------------------------------------------------------------------- /tests/res/TasksFileHasValidNameRule/tasks/ok_1.yml: -------------------------------------------------------------------------------- 1 | - name: Run Ping 2 | ping: 3 | -------------------------------------------------------------------------------- /tests/res/VariableHasValidNameRule/ng/0.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | connection: local 4 | gather_facts: false 5 | roles: 6 | - ping_0 7 | -------------------------------------------------------------------------------- /tests/res/VariableHasValidNameRule/ng/1.yml: -------------------------------------------------------------------------------- 1 | ../ok/1.yml -------------------------------------------------------------------------------- /tests/res/VariableHasValidNameRule/ng/c/1.json: -------------------------------------------------------------------------------- 1 | {"rules": {"variable_has_valid_name": {"name": "NEVER_MATCH_STR", "unicode": false}}} -------------------------------------------------------------------------------- /tests/res/VariableHasValidNameRule/ng/roles/ping: -------------------------------------------------------------------------------- 1 | ../../../roles/ping -------------------------------------------------------------------------------- /tests/res/VariableHasValidNameRule/ng/roles/ping_0/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | foo: 1 3 | bar: 4 | barz: 5 | foo_bar_baz: aaa 6 | invalid_var_name_ex-0: true 7 | 'invalid var_name_ex_1': true 8 | -------------------------------------------------------------------------------- /tests/res/VariableHasValidNameRule/ng/roles/ping_0/tasks/assertions.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test variables are set 3 | assert: 4 | that: 5 | - foo is defined 6 | - foo is number 7 | - foo > 0 8 | - bar is defined 9 | - bar is mapping 10 | - bar | bool 11 | - bar.baz is defined 12 | - bar.baz is mapping 13 | - bar.baz | bool 14 | - bar.baz.foo_bar_baz is defined 15 | - bar.baz.foo_bar_baz is string 16 | - bar.baz.foo_bar_baz | bool 17 | -------------------------------------------------------------------------------- /tests/res/VariableHasValidNameRule/ng/roles/ping_0/tasks/debug.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: debug 1 3 | debug: 4 | var: foo 5 | 6 | - name: debug 2 7 | debug: 8 | var: bar.baz.foo_bar_baz 9 | -------------------------------------------------------------------------------- /tests/res/VariableHasValidNameRule/ng/roles/ping_0/tasks/do_ping.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Try ping 3 | ping: 4 | -------------------------------------------------------------------------------- /tests/res/VariableHasValidNameRule/ng/roles/ping_0/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: include task 1 3 | include_tasks: "assertions.yml" 4 | - name: include task 2 5 | include_tasks: "do_ping.yml" 6 | -------------------------------------------------------------------------------- /tests/res/VariableHasValidNameRule/ok/.coverage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssato/ansible-lint-custom-rules/1021976e9db1f7d28634f42f2af8c5d30465994b/tests/res/VariableHasValidNameRule/ok/.coverage -------------------------------------------------------------------------------- /tests/res/VariableHasValidNameRule/ok/0.yml: -------------------------------------------------------------------------------- 1 | ../../playbooks/plays_ok_0.yml -------------------------------------------------------------------------------- /tests/res/VariableHasValidNameRule/ok/1.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | connection: local 4 | gather_facts: false 5 | roles: 6 | - ping 7 | -------------------------------------------------------------------------------- /tests/res/VariableHasValidNameRule/ok/inventory/group_vars/localhost.yml: -------------------------------------------------------------------------------- 1 | --- 2 | foo: 1 3 | foo_bar: 4 | baz: BAZ 5 | -------------------------------------------------------------------------------- /tests/res/VariableHasValidNameRule/ok/roles/ping: -------------------------------------------------------------------------------- 1 | ../../../roles/ping -------------------------------------------------------------------------------- /tests/res/VarsInVarsFilesHaveValidNamesRule/ng/0/group_vars/localhost.yml: -------------------------------------------------------------------------------- 1 | --- 2 | foo.bar: 1 3 | bar.bar: 4 | baz: 5 | - foo 6 | - bar 7 | - baz 8 | -------------------------------------------------------------------------------- /tests/res/VarsInVarsFilesHaveValidNamesRule/ng/0/playbook.yml: -------------------------------------------------------------------------------- 1 | ../../ok/0/playbook.yml -------------------------------------------------------------------------------- /tests/res/VarsInVarsFilesHaveValidNamesRule/ng/1/playbook.yml: -------------------------------------------------------------------------------- 1 | ../../ok/1/playbook.yml -------------------------------------------------------------------------------- /tests/res/VarsInVarsFilesHaveValidNamesRule/ng/1/roles/ping/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | foo.bar: 1 3 | bar.baz: 4 | foo_bar_baz: aaa 5 | -------------------------------------------------------------------------------- /tests/res/VarsInVarsFilesHaveValidNamesRule/ng/1/roles/ping/tasks/assertions.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test variables are set 3 | assert: 4 | that: 5 | - foo is defined 6 | - foo is number 7 | - foo > 0 8 | - bar is defined 9 | - bar is mapping 10 | - bar | bool 11 | - bar.baz is defined 12 | - bar.baz is mapping 13 | - bar.baz | bool 14 | - bar.baz.foo_bar_baz is defined 15 | - bar.baz.foo_bar_baz is string 16 | - bar.baz.foo_bar_baz | bool 17 | -------------------------------------------------------------------------------- /tests/res/VarsInVarsFilesHaveValidNamesRule/ng/1/roles/ping/tasks/debug.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: debug 1 3 | debug: 4 | var: foo 5 | 6 | - name: debug 2 7 | debug: 8 | var: bar.baz.foo_bar_baz 9 | -------------------------------------------------------------------------------- /tests/res/VarsInVarsFilesHaveValidNamesRule/ng/1/roles/ping/tasks/do_ping.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Try ping 3 | ping: 4 | -------------------------------------------------------------------------------- /tests/res/VarsInVarsFilesHaveValidNamesRule/ng/1/roles/ping/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: include task 1 3 | include_tasks: "assertions.yml" 4 | vars: 5 | foo: true 6 | bar: 7 | baz: 8 | foo_bar_baz: BAZ 9 | 10 | - name: include task 2 11 | include_tasks: "do_ping.yml" 12 | -------------------------------------------------------------------------------- /tests/res/VarsInVarsFilesHaveValidNamesRule/ng/2/conf.json: -------------------------------------------------------------------------------- 1 | {"rules": {"vars_in_vars_files_have_valid_names": {"name": "^NEVER_MATCH_PATTERN$"}}} -------------------------------------------------------------------------------- /tests/res/VarsInVarsFilesHaveValidNamesRule/ng/2/group_vars/localhost.yml: -------------------------------------------------------------------------------- 1 | ../../../ok/0/group_vars/localhost.yml -------------------------------------------------------------------------------- /tests/res/VarsInVarsFilesHaveValidNamesRule/ng/2/playbook.yml: -------------------------------------------------------------------------------- 1 | ../../ok/0/playbook.yml -------------------------------------------------------------------------------- /tests/res/VarsInVarsFilesHaveValidNamesRule/ok/0/group_vars/localhost.yml: -------------------------------------------------------------------------------- 1 | --- 2 | foo: 1 3 | bar: 4 | bar: 5 | baz: 6 | - foo 7 | - bar 8 | - baz 9 | -------------------------------------------------------------------------------- /tests/res/VarsInVarsFilesHaveValidNamesRule/ok/0/playbook.yml: -------------------------------------------------------------------------------- 1 | ../../../playbooks/plays_ok_0.yml -------------------------------------------------------------------------------- /tests/res/VarsInVarsFilesHaveValidNamesRule/ok/1/playbook.yml: -------------------------------------------------------------------------------- 1 | ../../../playbooks/ping_ok_1.yml -------------------------------------------------------------------------------- /tests/res/VarsInVarsFilesHaveValidNamesRule/ok/1/roles/ping: -------------------------------------------------------------------------------- 1 | ../../../../roles/ping_with_vars/ -------------------------------------------------------------------------------- /tests/res/VarsShouldNotBeUsedRule/ng/0/playbook.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | connection: local 3 | gather_facts: false 4 | vars: 5 | items: 6 | - pong 7 | tasks: 8 | - name: Try ping 9 | ping: 10 | loop: "{{ items }}" 11 | -------------------------------------------------------------------------------- /tests/res/VarsShouldNotBeUsedRule/ng/1/playbook.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | connection: local 3 | gather_facts: false 4 | tasks: 5 | - name: include vars 6 | include_vars: 7 | file: vars/VarsShouldNotBeUsedRule_1.yml 8 | - name: Try ping 9 | ping: 10 | loop: "{{ items }}" 11 | -------------------------------------------------------------------------------- /tests/res/VarsShouldNotBeUsedRule/ng/1/vars/VarsShouldNotBeUsedRule_1.yml: -------------------------------------------------------------------------------- 1 | items: 2 | - pong 3 | -------------------------------------------------------------------------------- /tests/res/VarsShouldNotBeUsedRule/ok/0/plays_ok_0.yml: -------------------------------------------------------------------------------- 1 | ../../../playbooks/plays_ok_0.yml -------------------------------------------------------------------------------- /tests/res/common/ok_1.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ssato/ansible-lint-custom-rules/1021976e9db1f7d28634f42f2af8c5d30465994b/tests/res/common/ok_1.yml -------------------------------------------------------------------------------- /tests/res/inventories/VariablesNamingRule/ng/1/group_vars/all.yml: -------------------------------------------------------------------------------- 1 | ../host_vars/localhost_0.yml -------------------------------------------------------------------------------- /tests/res/inventories/VariablesNamingRule/ng/1/host_vars/localhost_0.yml: -------------------------------------------------------------------------------- 1 | --- 2 | フーバーバズ: 1 3 | -------------------------------------------------------------------------------- /tests/res/inventories/VariablesNamingRule/ng/1/hosts: -------------------------------------------------------------------------------- 1 | [localhost] 2 | localhost_0 ansible_host=127.0.0.1 foo_1=1 3 | 4 | [localhost:vars] 5 | BAR_baz=bar_baz 6 | invalid_var_1=1 7 | -------------------------------------------------------------------------------- /tests/res/inventories/VariablesNamingRule/ok/1/group_vars/all.yml: -------------------------------------------------------------------------------- 1 | ../host_vars/localhost_0.yml -------------------------------------------------------------------------------- /tests/res/inventories/VariablesNamingRule/ok/1/host_vars/localhost_0.yml: -------------------------------------------------------------------------------- 1 | --- 2 | foo_1: 1 3 | BAR_baz: 4 | bar_BAR: baz 5 | __xyz: 6 | - 1 7 | - 2 8 | - 3 9 | -------------------------------------------------------------------------------- /tests/res/inventories/VariablesNamingRule/ok/1/hosts: -------------------------------------------------------------------------------- 1 | [localhost] 2 | localhost_0 ansible_host=127.0.0.1 foo_1=1 3 | 4 | [localhost:vars] 5 | BAZ_2=bar_baz 6 | -------------------------------------------------------------------------------- /tests/res/playbooks/ping_ok_1.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | connection: local 4 | gather_facts: false 5 | roles: 6 | - ping 7 | -------------------------------------------------------------------------------- /tests/res/playbooks/play_using_role_0.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | connection: local 4 | gather_facts: false 5 | roles: 6 | - ok_0 7 | -------------------------------------------------------------------------------- /tests/res/playbooks/plays_ok_0.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | connection: local 4 | gather_facts: false 5 | tasks: 6 | - name: Try ping 7 | ping: 8 | 9 | - name: debug 10 | debug: 11 | var: ansible_hostname 12 | -------------------------------------------------------------------------------- /tests/res/roles/no_empty_data_files_rule_ng_1/defaults/main.yml: -------------------------------------------------------------------------------- 1 | # test 2 | --- 3 | -------------------------------------------------------------------------------- /tests/res/roles/no_empty_data_files_rule_ng_1/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /tests/res/roles/no_empty_data_files_rule_ng_1/meta/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /tests/res/roles/no_empty_data_files_rule_ng_1/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Run Ping 2 | ping: 3 | -------------------------------------------------------------------------------- /tests/res/roles/no_empty_data_files_rule_ng_1/vars/main.yml: -------------------------------------------------------------------------------- 1 | ../defaults/main.yml -------------------------------------------------------------------------------- /tests/res/roles/no_empty_data_files_rule_ok_1: -------------------------------------------------------------------------------- 1 | variable_naming_rule_test_ok_1 -------------------------------------------------------------------------------- /tests/res/roles/ping/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | foo: 1 3 | bar: 4 | barz: 5 | foo_bar_baz: aaa 6 | -------------------------------------------------------------------------------- /tests/res/roles/ping/tasks/assertions.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test variables are set 3 | assert: 4 | that: 5 | - foo is defined 6 | - foo is number 7 | - foo > 0 8 | - bar is defined 9 | - bar is mapping 10 | - bar | bool 11 | - bar.baz is defined 12 | - bar.baz is mapping 13 | - bar.baz | bool 14 | - bar.baz.foo_bar_baz is defined 15 | - bar.baz.foo_bar_baz is string 16 | - bar.baz.foo_bar_baz | bool 17 | -------------------------------------------------------------------------------- /tests/res/roles/ping/tasks/debug.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: debug 1 3 | debug: 4 | var: foo 5 | 6 | - name: debug 2 7 | debug: 8 | var: bar.baz.foo_bar_baz 9 | -------------------------------------------------------------------------------- /tests/res/roles/ping/tasks/do_ping.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Try ping 3 | ping: 4 | -------------------------------------------------------------------------------- /tests/res/roles/ping/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: include task 1 3 | include_tasks: "assertions.yml" 4 | - name: include task 2 5 | include_tasks: "do_ping.yml" 6 | -------------------------------------------------------------------------------- /tests/res/roles/ping_with_vars/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | foo: 1 3 | bar: 4 | barz: 5 | foo_bar_baz: aaa 6 | -------------------------------------------------------------------------------- /tests/res/roles/ping_with_vars/tasks/assertions.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Test variables are set 3 | assert: 4 | that: 5 | - foo is defined 6 | - foo is number 7 | - foo > 0 8 | - bar is defined 9 | - bar is mapping 10 | - bar | bool 11 | - bar.baz is defined 12 | - bar.baz is mapping 13 | - bar.baz | bool 14 | - bar.baz.foo_bar_baz is defined 15 | - bar.baz.foo_bar_baz is string 16 | - bar.baz.foo_bar_baz | bool 17 | -------------------------------------------------------------------------------- /tests/res/roles/ping_with_vars/tasks/debug.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: debug 1 3 | debug: 4 | var: foo 5 | 6 | - name: debug 2 7 | debug: 8 | var: bar.baz.foo_bar_baz 9 | -------------------------------------------------------------------------------- /tests/res/roles/ping_with_vars/tasks/do_ping.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Try ping 3 | ping: 4 | -------------------------------------------------------------------------------- /tests/res/roles/ping_with_vars/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: include task 1 3 | include_tasks: "assertions.yml" 4 | vars: 5 | foo: true 6 | bar: 7 | baz: 8 | foo_bar_baz: BAZ 9 | 10 | - name: include task 2 11 | include_tasks: "do_ping.yml" 12 | -------------------------------------------------------------------------------- /tests/res/roles/variable_naming_rule_test_ng_1/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # .. note:: 3 | # The followings cause 4 | # ansible.errors.AnsibleParserError: Invalid variable name ... 5 | # foo-: 1 6 | # 0_bar_1: aaa 7 | # a b c: abc 8 | フー_バー_バズ: 9 | - 1 10 | - 2 11 | -------------------------------------------------------------------------------- /tests/res/roles/variable_naming_rule_test_ng_1/tasks: -------------------------------------------------------------------------------- 1 | ../variable_naming_rule_test_ok_1/tasks -------------------------------------------------------------------------------- /tests/res/roles/variable_naming_rule_test_ng_1/vars/main.yml: -------------------------------------------------------------------------------- 1 | ../defaults/main.yml -------------------------------------------------------------------------------- /tests/res/roles/variable_naming_rule_test_ok_1/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | foo: 1 3 | BAR_1: aaa 4 | _foo_bar_baz: 5 | - 1 6 | - 2 7 | -------------------------------------------------------------------------------- /tests/res/roles/variable_naming_rule_test_ok_1/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Run Ping 2 | ping: 3 | -------------------------------------------------------------------------------- /tests/res/roles/variable_naming_rule_test_ok_1/vars/main.yml: -------------------------------------------------------------------------------- 1 | ../defaults/main.yml -------------------------------------------------------------------------------- /tests/res/tasks/simple_ping_1.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | connection: local 4 | tasks: 5 | - name: Try ping 6 | ansible.builtin.ping: 7 | 8 | - name: Debug var 9 | debug: 10 | var: inventory_hostname 11 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36, py37, py38, py39, lint, type-check 3 | 4 | [gh-actions] 5 | python = 6 | 3.6: py36 7 | 3.7: py37 8 | 3.8: py38 9 | 3.9: py39, lint, type-check 10 | 11 | [testenv] 12 | deps = 13 | -r {toxinidir}/requirements.txt 14 | -r {toxinidir}/tests/requirements.txt 15 | 16 | setenv = 17 | PATH = {toxworkdir}/bin{:}{env:PATH} 18 | 19 | passenv = 20 | _ANSIBLE_LINT_RULE_* 21 | 22 | commands = 23 | pytest 24 | 25 | [testenv:type-check] 26 | deps = 27 | {[testenv]deps} 28 | types-PyYAML 29 | mypy 30 | 31 | commands = 32 | mypy rules 33 | 34 | [testenv:lint] 35 | commands = 36 | flake8 --doctests rules tests 37 | - pylint --disable=invalid-name,locally-disabled rules 38 | 39 | [testenv:profile] 40 | # It also requires graphviz (dot). 41 | deps = 42 | {[testenv]deps} 43 | gprof2dot 44 | pytest-profiling 45 | 46 | commands = 47 | pytest --profile --profile-svg 48 | 49 | [testenv:dists] 50 | deps = 51 | wheel 52 | 53 | commands = 54 | python setup.py sdist 55 | python setup.py bdist_wheel 56 | - python setup.py bdist_rpm --source-only 57 | 58 | # vim:sw=4:ts=4:et: 59 | --------------------------------------------------------------------------------