pax_global_header00006660000000000000000000000064142017126070014512gustar00rootroot0000000000000052 comment=19efc81b9b6ecec5e044497b4756d9508c4e3a51 ansible-lint-5.4.0/000077500000000000000000000000001420171260700141015ustar00rootroot00000000000000ansible-lint-5.4.0/.ansible-lint000066400000000000000000000052051420171260700164650ustar00rootroot00000000000000# .ansible-lint # exclude_paths included in this file are parsed relative to this file's location # and not relative to the CWD of execution. CLI arguments passed to the --exclude # option will be parsed relative to the CWD of execution. exclude_paths: - .cache/ # implicit unless exclude_paths is defined in config - .github/ # parseable: true # quiet: true # verbosity: 1 # Mock modules or roles in order to pass ansible-playbook --syntax-check mock_modules: - zuul_return # note the foo.bar is invalid as being neither a module or a collection - fake_namespace.fake_collection.fake_module - fake_namespace.fake_collection.fake_module.fake_submodule mock_roles: - mocked_role - author.role_name # old standalone galaxy role - fake_namespace.fake_collection.fake_role # role within a collection # Enable checking of loop variable prefixes in roles loop_var_prefix: "{role}_" # Enforce variable names to follow pattern below, in addition to Ansible own # requirements, like avoiding python identifiers. To disable add `var-naming` # to skip_list. # var_naming_pattern: "^[a-z_][a-z0-9_]*$" use_default_rules: true # Load custom rules from this specific folder # rulesdir: # - ./rule/directory/ # This makes linter to fully ignore rules/tags listed below skip_list: - skip_this_tag - git-latest # Any rule that has the 'opt-in' tag will not be loaded unless its 'id' is # mentioned in the enable_list: enable_list: - fqcn-builtins # opt-in - no-log-password # opt-in - no-same-owner # opt-in # add yaml here if you want to avoid ignoring yaml checks when yamllint # library is missing. Normally its absence just skips using that rule. - yaml # Report only a subset of tags and fully ignore any others # tags: # - var-spacing # This makes the linter display but not fail for rules/tags listed below: warn_list: - skip_this_tag - git-latest - experimental # experimental is included in the implicit list # - role-name # Offline mode disables installation of requirements.yml offline: false # Define required Ansible's variables to satisfy syntax check extra_vars: foo: bar multiline_string_variable: | line1 line2 complex_variable: ":{;\t$()" # Uncomment to enforce action validation with tasks, usually is not # needed as Ansible syntax check also covers it. # skip_action_validation: false # List of additional kind:pattern to be added at the top of the default # match list, first match determines the file kind. kinds: # - playbook: "**/examples/*.{yml,yaml}" # - galaxy: "**/folder/galaxy.yml" # - tasks: "**/tasks/*.yml" # - vars: "**/vars/*.yml" # - meta: "**/meta/main.yml" - yaml: "**/*.yaml-too" ansible-lint-5.4.0/.coveragerc000066400000000000000000000006761420171260700162330ustar00rootroot00000000000000[run] branch = true parallel = true [report] skip_covered = True show_missing = True exclude_lines = \#\s*pragma: no cover ^\s*raise AssertionError\b ^\s*raise NotImplementedError\b ^\s*return NotImplemented\b ^\s*raise$ ^if __name__ == ['"]__main__['"]:$ [paths] source = lib/ansiblelint */.tox/*/lib/python*/site-packages/ansiblelint */.tox/pypy*/site-packages/ansiblelint */lib/ansiblelint ansible-lint-5.4.0/.darglint000066400000000000000000000001001420171260700156750ustar00rootroot00000000000000[darglint] docstring_style=sphinx enable=DAR104 strictness=long ansible-lint-5.4.0/.flake8000066400000000000000000000073531420171260700152640ustar00rootroot00000000000000[flake8] # Don't even try to analyze these: extend-exclude = # No need to traverse egg info dir *.egg-info, # GitHub configs .github, # Cache files of MyPy .mypy_cache, # Cache files of pytest .pytest_cache, # Temp dir of pytest-testmon .tmontmp, # Occasional virtualenv dir .venv # VS Code .vscode, # Temporary build dir build, # This contains sdists and wheels of ansible-lint that we don't want to check dist, # Occasional virtualenv dir env, # Metadata of `pip wheel` cmd is autogenerated pip-wheel-metadata, # Let's not overcomplicate the code: max-complexity = 10 # Accessibility/large fonts and PEP8 friendly: #max-line-length = 79 # Accessibility/large fonts and PEP8 unfriendly: max-line-length = 100 # The only allowed ignores are related to black and isort # https://black.readthedocs.io/en/stable/the_black_code_style.html#line-length # "H" are generated by hacking plugin, which is not black compatible extend-ignore = E203, E501, F401, # duplicate of pylint W0611 (unused-import) F821, # duplicate of pylint E0602 (undefined-variable) F841, # duplicate of pylint W0612 (unused-variable) H, # Allow certain violations in certain files: per-file-ignores = # FIXME: D100 Missing docstring in public module # FIXME: D101 Missing docstring in public class # FIXME: D102 Missing docstring in public method # FIXME: drop these once they're made simpler # Ref: https://github.com/ansible-community/ansible-lint/issues/744 src/ansiblelint/cli.py: D101 D102 src/ansiblelint/formatters/__init__.py: D101 D102 src/ansiblelint/rules/*.py: D100 D101 D102 src/ansiblelint/rules/__init__.py: D100 D101 D102 # FIXME: drop these once they're fixed # Ref: https://github.com/ansible-community/ansible-lint/issues/725 test/__init__.py: D102 test/conftest.py: D100 test/rules/EMatcherRule.py: D100 D101 D102 test/rules/UnsetVariableMatcherRule.py: D100 D101 D102 test/TestAnsibleLintRule.py: D100 test/TestBaseFormatter.py: D100 test/TestBecomeUserWithoutBecome.py: D100 D101 D102 test/TestCliRolePaths.py: D100 D101 D102 test/TestCommandLineInvocationSameAsConfig.py: D100 test/TestCommandHasChangesCheck.py: D100 D101 D102 test/TestComparisonToLiteralBool.py: D100 D101 D102 test/TestDependenciesInMeta.py: D100 test/TestDeprecatedModule.py: D100 D101 D102 test/TestEnvVarsInCommand.py: D100 D101 D102 test/TestFormatter.py: D100 D101 D102 test/TestImportIncludeRole.py: D100 test/TestImportWithMalformed.py: D100 test/TestIncludeMissFileWithRole.py: D100 test/TestLineTooLong.py: D100 D101 D102 test/TestLintRule.py: D100 D101 D102 test/TestNestedJinjaRule.py: D100 test/TestMatchError.py: D101 test/TestMetaChangeFromDefault.py: D100 D101 D102 test/TestMetaMainHasInfo.py: D100 D101 D102 test/TestMetaVideoLinks.py: D100 D101 D102 test/TestNoFormattingInWhenRule.py: D100 D101 D102 test/TestOctalPermissions.py: D100 D101 D102 test/TestPackageIsNotLatest.py: D100 D101 D102 test/TestRoleRelativePath.py: D100 D101 D102 test/TestRuleProperties.py: D100 test/TestRulesCollection.py: D100 test/TestRunner.py: D100 test/TestShellWithoutPipefail.py: D100 D101 D102 test/TestSkipImportPlaybook.py: D100 test/TestSkipInsideYaml.py: D100 test/TestSkipPlaybookItems.py: D100 test/TestTaskHasName.py: D100 D101 D102 test/TestTaskIncludes.py: D100 test/TestTaskNoLocalAction.py: D100 D101 D102 test/TestUseHandlerRatherThanWhenChanged.py: D100 D101 D102 test/TestUsingBareVariablesIsDeprecated.py: D100 D101 D102 test/TestWithSkipTagId.py: D100 D101 D102 # flake8-pytest-style # PT001: pytest-fixture-no-parentheses = true # PT006: pytest-parametrize-names-type = tuple # PT007: pytest-parametrize-values-type = tuple pytest-parametrize-values-row-type = tuple ansible-lint-5.4.0/.git_archival.txt000066400000000000000000000000271420171260700173530ustar00rootroot00000000000000ref-names: tag: v5.4.0 ansible-lint-5.4.0/.gitattributes000066400000000000000000000002211420171260700167670ustar00rootroot00000000000000# Force LF line endings for text files * text=auto eol=lf *.png binary # Needed for setuptools-scm-git-archive .git_archival.txt export-subst ansible-lint-5.4.0/.github/000077500000000000000000000000001420171260700154415ustar00rootroot00000000000000ansible-lint-5.4.0/.github/CODEOWNERS000066400000000000000000000000441420171260700170320ustar00rootroot00000000000000* @ansible-community/devtools ansible-lint-5.4.0/.github/CODE_OF_CONDUCT.md000066400000000000000000000002421420171260700202360ustar00rootroot00000000000000# Community Code of Conduct Please see the official [Ansible Community Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html). ansible-lint-5.4.0/.github/CONTRIBUTING.rst000066400000000000000000000044531420171260700201100ustar00rootroot00000000000000Contributing to Ansible-lint ============================ To contribute to ansible-lint, please use pull requests on a branch of your own fork. After `creating your fork on GitHub`_, you can do: .. code-block:: shell-session $ git clone git@github.com:yourname/ansible-lint $ cd ansible-lint $ git checkout -b your-branch-name # DO SOME CODING HERE $ git add your new files $ git commit -v $ git push origin your-branch-name You will then be able to create a pull request from your commit. All fixes to core functionality (i.e. anything except docs or examples) should be accompanied by tests that fail prior to your change and succeed afterwards. Feel free to raise issues in the repo if you feel unable to contribute a code fix. .. _creating your fork on GitHub: https://guides.github.com/activities/forking/ Standards --------- ansible-lint is flake8 compliant with ``max-line-length`` set to 100 (see `.flake8`_). ansible-lint works only with `supported Ansible versions`_ at the time it was released. Automated tests will be run against all PRs for flake8 compliance and Ansible compatibility — to check before pushing commits, just use `tox`_. .. _.flake8: https://github.com/ansible-community/ansible-lint/blob/main/.flake8 .. _supported Ansible versions: https://docs.ansible.com/ansible-core/devel/reference_appendices /release_and_maintenance.html#ansible-core-release-cycle .. _tox: https://tox.readthedocs.io .. DO-NOT-REMOVE-deps-snippet-PLACEHOLDER Talk to us ---------- Use Github `discussions`_ forum or for a live chat experience try ``#ansible-lint`` IRC channel on libera.chat. For the full list of Ansible IRC and Mailing list, please see the `Ansible Communication`_ page. Release announcements will be made to the `Ansible Announce`_ list. Possible security bugs should be reported via email to security@ansible.com. .. _Ansible Announce: https://groups.google.com/forum/#!forum/ansible-announce .. _discussions: https://github.com/ansible-community/ansible-lint/discussions .. _Ansible Communication: https://docs.ansible.com/ansible/latest/community/communication.html Code of Conduct --------------- As with all Ansible projects, we have a `Code of Conduct`_. .. _Code of Conduct: https://docs.ansible.com/ansible/latest/community /code_of_conduct.html ansible-lint-5.4.0/.github/ISSUE_TEMPLATE.md000066400000000000000000000014701420171260700201500ustar00rootroot00000000000000# Issue Type - Bug report - Feature request # Ansible and Ansible Lint details ``` ansible --version ansible-lint --version ``` - ansible installation method: one of source, pip, OS package - ansible-lint installation method: one of source, pip, OS package # Desired Behaviour Please give some details of the feature being requested or what should happen if providing a bug report Possible security bugs should be reported via email to `security@ansible.com` # Actual Behaviour (Bug report only) Please give some details of what is actually happening. Include a [minimum complete verifiable example] with: - playbook - output of running ansible-lint - if you're getting a stack trace, output of `ansible-playbook --syntax-check playbook` [minimum complete verifiable example]: http://stackoverflow.com/help/mcve ansible-lint-5.4.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001420171260700176245ustar00rootroot00000000000000ansible-lint-5.4.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000034661420171260700223270ustar00rootroot00000000000000--- name: Bug report about: > Create a bug report. Please test against the main branch before submitting it. For anything else, please use discussions link below. labels: bug, new --- ##### Summary ##### Issue Type - Bug Report ##### Ansible and Ansible Lint details ```console (paste below) ansible --version ansible-lint --version ``` - ansible installation method: one of source, pip, OS package - ansible-lint installation method: one of source, pip, OS package ##### OS / ENVIRONMENT ##### STEPS TO REPRODUCE ```console (paste below) ``` ##### Desired Behaviour Possible security bugs should be reported via email to `security@ansible.com` ##### Actual Behaviour Please give some details of what is actually happening. Include a [minimum complete verifiable example] with: - playbook - output of running ansible-lint - if you're getting a stack trace, output of `ansible-playbook --syntax-check playbook` ```paste below ``` [minimum complete verifiable example]: http://stackoverflow.com/help/mcve ansible-lint-5.4.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000021541420171260700216160ustar00rootroot00000000000000# Ref: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser blank_issues_enabled: false # default is true contact_links: - name: Feature requests url: https://github.com/ansible-community/ansible-lint/discussions/categories/ideas about: Suggest an idea for this project - name: Discussions url: https://github.com/ansible-community/ansible-lint/discussions/ about: Any kind of questions should go on the forum. - name: Security bug report url: https://docs.ansible.com/ansible/latest/community/reporting_bugs_and_features.html about: | Please learn how to report security vulnerabilities here. For all security related bugs, email security@ansible.com instead of using this issue tracker and you will receive a prompt response. For more information, see https://docs.ansible.com/ansible/latest/community/reporting_bugs_and_features.html - name: Ansible Code of Conduct url: https://docs.ansible.com/ansible/latest/community/code_of_conduct.html about: Be nice to other members of the community. Behave. ansible-lint-5.4.0/.github/SECURITY.rst000066400000000000000000000012541420171260700174440ustar00rootroot00000000000000Security Policy --------------- Supported Versions ================== Ansible applies security fixes according to the 3-versions-back support policy. Please find more information in `our docs`_. .. _our docs: https://docs.ansible.com/ansible-core/devel/reference_appendices /release_and_maintenance.html#ansible-core-release-cycle Reporting a Vulnerability ========================= We encourage responsible disclosure practices for security vulnerabilities. Please read our `policies for reporting bugs `_ if you want to report a security issue that might affect Ansible. ansible-lint-5.4.0/.github/dependabot.yml000066400000000000000000000011371420171260700202730ustar00rootroot00000000000000# Until bug below is sorted we will not allow dependabot to run by itself # https://github.com/dependabot/dependabot-core/issues/369 version: 2 updates: - package-ecosystem: pip directory: /docs schedule: day: sunday interval: weekly labels: - dependabot-deps-updates - skip-changelog versioning-strategy: lockfile-only open-pull-requests-limit: 0 # neutered - package-ecosystem: pip directory: / schedule: day: sunday interval: weekly labels: - dependabot-deps-updates - skip-changelog versioning-strategy: lockfile-only open-pull-requests-limit: 0 # neutered ansible-lint-5.4.0/.github/release-drafter.yml000066400000000000000000000001311420171260700212240ustar00rootroot00000000000000# see https://github.com/ansible-community/devtools _extends: ansible-community/devtools ansible-lint-5.4.0/.github/workflows/000077500000000000000000000000001420171260700174765ustar00rootroot00000000000000ansible-lint-5.4.0/.github/workflows/ack.yml000066400000000000000000000004041420171260700207550ustar00rootroot00000000000000# See https://github.com/ansible-community/devtools/blob/main/.github/workflows/ack.yml name: ack on: pull_request_target: types: [opened, labeled, unlabeled, synchronize] jobs: ack: uses: ansible-community/devtools/.github/workflows/ack.yml@main ansible-lint-5.4.0/.github/workflows/push.yml000066400000000000000000000004101420171260700211730ustar00rootroot00000000000000# See https://github.com/ansible-community/devtools/blob/main/.github/workflows/push.yml name: push on: push: branches: - main - 'releases/**' - 'stable/**' jobs: ack: uses: ansible-community/devtools/.github/workflows/push.yml@main ansible-lint-5.4.0/.github/workflows/release.yml000066400000000000000000000023511420171260700216420ustar00rootroot00000000000000name: release on: release: types: [published] jobs: pypi: name: Publish to PyPI registry environment: release runs-on: ubuntu-20.04 env: FORCE_COLOR: 1 PY_COLORS: 1 TOXENV: packaging TOX_PARALLEL_NO_SPINNER: 1 steps: - name: Switch to using Python 3.8 by default uses: actions/setup-python@v2 with: python-version: 3.8 - name: Install tox run: >- python3 -m pip install --user tox - name: Check out src from Git uses: actions/checkout@v2 with: fetch-depth: 0 # needed by setuptools-scm - name: Build dists run: python -m tox - name: Publish to test.pypi.org if: >- # "create" workflows run separately from "push" & "pull_request" github.event_name == 'release' uses: pypa/gh-action-pypi-publish@master with: password: ${{ secrets.testpypi_password }} repository_url: https://test.pypi.org/legacy/ - name: Publish to pypi.org if: >- # "create" workflows run separately from "push" & "pull_request" github.event_name == 'release' uses: pypa/gh-action-pypi-publish@master with: password: ${{ secrets.pypi_password }} ansible-lint-5.4.0/.github/workflows/tox.yml000066400000000000000000000201171420171260700210340ustar00rootroot00000000000000name: tox on: create: # is used for publishing to PyPI and TestPyPI tags: # any tag regardless of its name, no branches - "**" push: # only publishes pushes to the main branch to TestPyPI branches: # any integration branch but not tag - "main" pull_request: release: types: - published # It seems that you can publish directly without creating schedule: - cron: 1 0 * * * # Run daily at 0:01 UTC # Run every Friday at 18:02 UTC # https://crontab.guru/#2_18_*_*_5 # - cron: 2 18 * * 5 jobs: linters: name: >- ${{ matrix.env.TOXENV }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: python-version: - 3.9 os: - ubuntu-20.04 env: - TOXENV: lint - TOXENV: docs - TOXENV: linkcheck-docs - TOXENV: eco - TOXENV: packaging env: TOX_PARALLEL_NO_SPINNER: 1 FORCE_COLOR: 1 steps: - name: Check out src from Git uses: actions/checkout@v2 with: fetch-depth: 0 # needed by setuptools-scm - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} # - name: set PY_SHA256 # run: echo "::set-env name=PY_SHA256::$(python -VV | sha256sum | cut -d' ' -f1)" # - name: Pre-commit cache # uses: actions/cache@v1 # with: # path: ~/.cache/pre-commit # key: ${{ runner.os }}-pre-commit-${{ env.PY_SHA256 }}-${{ hashFiles('setup.cfg') }}-${{ hashFiles('tox.ini') }}-${{ hashFiles('pyproject.toml') }}-${{ hashFiles('.pre-commit-config.yaml') }}-${{ hashFiles('pytest.ini') }} # - name: Pip cache # uses: actions/cache@v1 # with: # path: ~/.cache/pip # key: ${{ runner.os }}-pip-${{ env.PY_SHA256 }}-${{ hashFiles('setup.cfg') }}-${{ hashFiles('tox.ini') }}-${{ hashFiles('pyproject.toml') }}-${{ hashFiles('.pre-commit-config.yaml') }}-${{ hashFiles('pytest.ini') }} # restore-keys: | # ${{ runner.os }}-pip- # ${{ runner.os }}- - name: Install tox run: | python3 -m pip install --upgrade pip python3 -m pip install --upgrade tox - name: Log installed dists run: >- python -m pip freeze --all - name: >- Initialize tox envs run: >- python -m tox --parallel auto --parallel-live --notest --skip-missing-interpreters false -vv env: ${{ matrix.env }} - name: Test with tox run: | python -m tox --parallel auto --parallel-live env: ${{ matrix.env }} - name: Archive logs uses: actions/upload-artifact@v2 with: name: logs.zip path: .tox/**/log/ unit: name: ${{ matrix.name || matrix.tox_env }} runs-on: ${{ matrix.os }} defaults: run: shell: ${{ matrix.shell || 'bash'}} strategy: fail-fast: false # max-parallel: 5 # The matrix testing goal is to cover the *most likely* environments # which are expected to be used by users in production. Avoid adding a # combination unless there are good reasons to test it, like having # proof that we failed to catch a bug by not running it. Using # distribution should be prefferred instead of custom builds. matrix: python-version: # keep list sorted as it determines UI order too - 3.6 - 3.7 - 3.8 - 3.9 - "3.10" os: # https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners - ubuntu-20.04 # - windows-latest # - windows-2016 include: - tox_env: py36 os: ubuntu-20.04 python-version: 3.6 # Disabled due to gettings stuck and timeouts not working # https://github.com/Vampire/setup-wsl/discussions/25 # - name: py39 (wsl) # tox_env: py39 # os: windows-latest # shell: "wsl-bash {0}" - tox_env: py37 os: ubuntu-20.04 python-version: 3.7 - tox_env: py38 os: ubuntu-20.04 python-version: 3.8 devel: true - tox_env: py39 os: ubuntu-20.04 python-version: 3.9 devel: true - tox_env: py310 os: ubuntu-20.04 python-version: "3.10" devel: true skip_ansible29: true - name: py36 (macos) tox_env: py36 os: macOS-latest python-version: 3.6 - name: py310 (macos) tox_env: py310 os: macOS-latest python-version: "3.10" skip_ansible29: true env: TOX_PARALLEL_NO_SPINNER: 1 FORCE_COLOR: 1 # vars safe to be passed to wsl: WSLENV: FORCE_COLOR:TOXENV:TOX_PARALLEL_NO_SPINNER steps: - name: Activate WSL1 if: "contains(matrix.shell, 'wsl')" uses: Vampire/setup-wsl@v1 - name: MacOS workaround for https://github.com/actions/virtual-environments/issues/1187 if: ${{ matrix.os == 'macOS-latest' }} run: | sudo sysctl -w net.link.generic.system.hwcksum_tx=0 sudo sysctl -w net.link.generic.system.hwcksum_rx=0 - uses: actions/checkout@v2 with: fetch-depth: 0 # needed by setuptools-scm - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Run ./tools/test-setup.sh run: | ./tools/test-setup.sh # - name: Pip cache # uses: actions/cache@v1 # with: # path: ~/.cache/pip # key: ${{ runner.os }}-pip-${{ env.PY_SHA256 }}-${{ hashFiles('setup.cfg') }}-${{ hashFiles('tox.ini') }}-${{ hashFiles('pyproject.toml') }}-${{ hashFiles('.pre-commit-config.yaml') }}-${{ hashFiles('pytest.ini') }} # restore-keys: | # ${{ runner.os }}-pip- # ${{ runner.os }}- - name: Install tox run: | python3 -m pip install --upgrade tox 'coverage[toml]' - name: Log installed dists run: >- python3 -m pip freeze --all - name: >- Initialize tox envs run: >- python3 -m tox --parallel auto --parallel-live --notest --skip-missing-interpreters false -vv env: TOXENV: ${{ matrix.tox_env }}-core,${{ matrix.tox_env }}-ansible29 # sequential run improves browsing experience (almost no speed impact) - name: "Test with tox: ${{ matrix.tox_env }}-core" run: | python3 -m tox env: TOXENV: ${{ matrix.tox_env }}-core - name: "Test with tox: ${{ matrix.tox_env }}-ansible29" if: ${{ !matrix.skip_ansible29 }} run: | python3 -m tox env: TOXENV: ${{ matrix.tox_env }}-ansible29 - name: "Test with tox: ${{ matrix.tox_env }}-devel" if: ${{ matrix.devel }} run: | python3 -m tox env: TOXENV: ${{ matrix.tox_env }}-devel - name: Combine coverage data # produce a single .coverage file at repo root run: coverage combine .tox/.coverage.* - name: Upload coverage data if: "runner.os == 'Linux'" uses: codecov/codecov-action@v1 with: name: ${{ matrix.tox_env }} fail_ci_if_error: true # optional (default = false) verbose: true # optional (default = false) - name: Archive logs uses: actions/upload-artifact@v2 with: name: logs.zip path: .tox/**/log/ # https://github.com/actions/upload-artifact/issues/123 continue-on-error: true - name: Report junit failures uses: shyim/junit-report-annotations-action@3d2e5374f2b13e70f6f3209a21adfdbc42c466ae with: path: .tox/junit.*.xml if: always() check: # This job does nothing and is only used for the branch protection if: always() needs: - linters - unit runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: jobs: ${{ toJSON(needs) }} ansible-lint-5.4.0/.gitignore000066400000000000000000000010121420171260700160630ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__ *.py[co] *$py.class # Packages .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib64/ parts/ pip-wheel-metadata sdist/ var/ *.egg-info/ .installed.cfg *.egg # Installer logs pip-log.txt # Unit test / coverage reports .tox # Needed for CLI tests .sandbox # pyenv .python-version # Coverage artifacts .coverage coverage.xml pip-wheel-metadata .test-results/ # mypy .mypy_cache # .cache is used by progressive mode .cache # other .DS_Store .vscode ansible-lint-5.4.0/.isort.cfg000066400000000000000000000005221420171260700157770ustar00rootroot00000000000000[settings] known_first_party = ansiblelint known_third_party = ansible,pytest,ruamel,setuptools,sphinx,yaml # https://black.readthedocs.io/en/stable/the_black_code_style.html#line-length multi_line_output = 3 include_trailing_comma = True force_grid_wrap = 0 use_parentheses = True ensure_newline_before_comments = True line_length = 88 ansible-lint-5.4.0/.pre-commit-config.yaml000066400000000000000000000102761420171260700203700ustar00rootroot00000000000000--- ci: # format compatible with commitlint autoupdate_commit_msg: "chore: pre-commit autoupdate" autoupdate_schedule: monthly autofix_commit_msg: | chore: auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci skip: # https://github.com/pre-commit-ci/issues/issues/55 - pip-compile - pip-compile-docs - pip-compile-docs-upgrade - pip-compile-upgrade default_language_version: python: python3.9 repos: - repo: https://github.com/sirosen/check-jsonschema rev: 0.10.2 hooks: - id: check-github-workflows - repo: https://github.com/pre-commit/pre-commit-hooks.git rev: v4.1.0 hooks: - id: end-of-file-fixer exclude: > (?x)^( test/eco/.*.result )$ - id: trailing-whitespace exclude: > (?x)^( examples/playbooks/(with-skip-tag-id|unicode).yml| examples/playbooks/example.yml )$ - id: mixed-line-ending - id: fix-byte-order-marker - id: check-executables-have-shebangs - id: check-merge-conflict - id: debug-statements language_version: python3 - repo: https://github.com/codespell-project/codespell rev: v2.1.0 hooks: - id: codespell - repo: https://github.com/PyCQA/doc8 rev: 0.10.1 hooks: - id: doc8 - repo: https://github.com/adrienverge/yamllint.git rev: v1.26.3 hooks: - id: yamllint exclude: > (?x)^( examples/playbooks/templates/.*| examples/other/some.j2.yaml )$ files: \.(yaml|yml)$ types: [file, yaml] entry: yamllint --strict - repo: https://github.com/PyCQA/isort rev: 5.10.1 hooks: - id: isort args: # https://github.com/pre-commit/mirrors-isort/issues/9#issuecomment-624404082 - --filter-files - repo: https://github.com/psf/black rev: 22.1.0 hooks: - id: black language_version: python3 - repo: https://github.com/pycqa/flake8.git rev: 4.0.1 hooks: - id: flake8 language_version: python3 additional_dependencies: - flake8-2020>=1.6.0 # - flake8-black>=0.1.1 - flake8-docstrings>=1.5.0 - flake8-pytest-style>=1.2.2 - repo: https://github.com/pre-commit/mirrors-mypy rev: v0.931 hooks: - id: mypy # empty args needed in order to match mypy cli behavior args: ["--strict"] additional_dependencies: - Sphinx>=3.1.2 - ansible-core - enrich - flaky - pytest - rich>=11.0.0 - ruamel.yaml - tenacity - types-PyYAML - types-dataclasses - types-docutils - types-pkg_resources - wcmatch - yamllint exclude: > (?x)^( test/local-content/.*| plugins/.* )$ - repo: https://github.com/pre-commit/mirrors-pylint rev: v3.0.0a4 hooks: - id: pylint additional_dependencies: - ansible-core - docutils - enrich - flaky - pytest - pyyaml - rich>=11.0.0 - ruamel.yaml - sphinx - tenacity - typing_extensions - wcmatch - # keep at bottom as these are slower repo: local hooks: - id: pip-compile name: pip-compile entry: python3.9 -m piptools compile -q --no-annotate --output-file=constraints.txt setup.cfg --extra test --extra yamllint --strip-extras language: system files: ^(setup\.cfg|=constraints\.txt)$ pass_filenames: false stages: [manual] - id: pip-compile-docs name: pip-compile-docs entry: python3.9 -m piptools compile -q --no-annotate --output-file=docs/requirements.txt --extra yamllint --strip-extras docs/requirements.in setup.cfg language: system files: ^(docs\/requirements\.(txt|in)|)$ pass_filenames: false stages: [manual] - id: pip-compile-upgrade name: pip-compile-upgrade entry: python3.9 -m piptools compile -q --upgrade --no-annotate --output-file=constraints.txt setup.cfg --extra test --extra yamllint --strip-extras language: system files: ^(setup\.cfg|=constraints\.txt)$ pass_filenames: false stages: [manual] - id: pip-compile-docs-upgrade name: pip-compile-docs-upgrade entry: python3.9 -m piptools compile -q --upgrade --no-annotate --extra yamllint --output-file=docs/requirements.txt --strip-extras docs/requirements.in setup.cfg language: system files: ^(docs\/requirements\.(txt|in)|)$ pass_filenames: false stages: [manual] ansible-lint-5.4.0/.pre-commit-hooks.yaml000066400000000000000000000013301420171260700202350ustar00rootroot00000000000000--- # For use with pre-commit. # See usage instructions at http://pre-commit.com - id: ansible-lint name: Ansible-lint description: This hook runs ansible-lint. entry: ansible-lint --force-color language: python # do not pass files to ansible-lint, see: # https://github.com/ansible-community/ansible-lint/issues/611 pass_filenames: false always_run: true additional_dependencies: # https://github.com/pre-commit/pre-commit/issues/1526 # if you want to use only the base ansible version for linting, # replace 'community' extra with 'core' or just mention the exact # version of Ansible you want to install as a dependency. - .[community,yamllint] ansible-lint-5.4.0/.pylintrc000066400000000000000000000013311420171260700157440ustar00rootroot00000000000000[IMPORTS] preferred-modules = unittest:pytest, [MESSAGES CONTROL] disable = # On purpose disabled as we rely on black line-too-long, # TODO(ssbarnea): remove temporary skips adding during initial adoption: bad-continuation, broad-except, dangerous-default-value, duplicate-code, fixme, inconsistent-return-statements, invalid-name, missing-class-docstring, missing-function-docstring, missing-module-docstring, no-member, no-self-use, not-callable, protected-access, raise-missing-from, redefined-outer-name, too-few-public-methods, too-many-branches, too-many-instance-attributes, too-many-return-statements, unused-argument, ansible-lint-5.4.0/.readthedocs.yml000066400000000000000000000013711420171260700171710ustar00rootroot00000000000000# Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html # for details --- # Required version: 2 # Build documentation in the docs/ directory with Sphinx sphinx: builder: html configuration: docs/conf.py fail_on_warning: true # Build documentation with MkDocs #mkdocs: # configuration: mkdocs.yml # fail_on_warning: true # Optionally build your docs in additional formats # such as PDF and ePub formats: [] submodules: include: all # [] exclude: [] recursive: true build: image: latest # Optionally set the version of Python and requirements required # to build docs python: version: 3.8 install: - method: pip path: . - requirements: docs/requirements.txt system_packages: false ansible-lint-5.4.0/.vscode/000077500000000000000000000000001420171260700154425ustar00rootroot00000000000000ansible-lint-5.4.0/.vscode/extensions.json000066400000000000000000000001671420171260700205400ustar00rootroot00000000000000{ "recommendations": [ "ms-python.python", "redhat.vscode-yaml", "redhat.ansible", ] } ansible-lint-5.4.0/.yamllint000066400000000000000000000003201420171260700157260ustar00rootroot00000000000000rules: document-start: disable indentation: level: error indent-sequences: consistent ignore: | .tox examples/playbooks/example.yml # ignore added because this file includes on-purpose errors ansible-lint-5.4.0/DCO_1_1.md000066400000000000000000000031241420171260700154700ustar00rootroot00000000000000DCO === All contributors must use `git commit --signoff` for any commit to be merged, and agree that usage of --signoff constitutes agreement with the terms of DCO 1.1, which appears below: ``` Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 1 Letterman Drive Suite D4700 San Francisco, CA, 94129 Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ``` ansible-lint-5.4.0/LICENSE000066400000000000000000000021371420171260700151110ustar00rootroot00000000000000Copyright (c) 2013-2018 Will Thames Copyright (c) 2018 Ansible by Red Hat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ansible-lint-5.4.0/README.rst000066400000000000000000000054371420171260700156010ustar00rootroot00000000000000.. image:: https://img.shields.io/pypi/v/ansible-lint.svg :target: https://pypi.org/project/ansible-lint :alt: PyPI version .. image:: https://img.shields.io/badge/Ansible--lint-rules%20table-blue.svg :target: https://ansible-lint.readthedocs.io/en/latest/default_rules.html :alt: Ansible-lint rules explanation .. image:: https://img.shields.io/badge/Code%20of%20Conduct-black.svg :target: https://docs.ansible.com/ansible/latest/community/code_of_conduct.html :alt: Ansible Code of Conduct .. image:: https://img.shields.io/badge/Discussions-gray.svg :target: https://github.com/ansible-community/ansible-lint/discussions :alt: Discussions .. image:: https://github.com/ansible-community/ansible-lint/workflows/gh/badge.svg :target: https://github.com/ansible-community/ansible-lint/actions?query=workflow%3Agh+branch%3Amain+event%3Apush :alt: GitHub Actions CI/CD .. image:: https://img.shields.io/lgtm/grade/python/g/ansible-community/ansible-lint.svg?logo=lgtm&logoWidth=18 :target: https://lgtm.com/projects/g/ansible-community/ansible-lint/context:python :alt: Language grade: Python .. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white :target: https://github.com/pre-commit/pre-commit :alt: pre-commit Ansible-lint ============ ``ansible-lint`` checks playbooks for practices and behaviour that could potentially be improved. As a community backed project ansible-lint supports only the last two major versions of Ansible. `Visit the Ansible Lint docs site `_ Contributing ============ Please read `Contribution guidelines`_ if you wish to contribute. Licensing ========= The code in the ansible-lint repository is licensed under the MIT_ license. If you contribute to this repository, that license applies to your contributions. The ansible-lint project also imports the Ansible python module, which is licensed under the GPLv3_ license. Because of this import, the GPLv3_ rules apply to the full distribution of ansible-lint. We maintain the MIT_ license on the repository so we can fully use an MIT_ license in the future if we ever remove the runtime dependency on Ansible code. Installing the `ansible-lint` python package does not install any GPL dependencies, all of them are listed as extras. Authors ======= ansible-lint was created by `Will Thames`_ and is now maintained as part of the `Ansible`_ by `Red Hat`_ project. .. _Contribution guidelines: https://ansible-lint.readthedocs.io/en/latest/contributing.html .. _Will Thames: https://github.com/willthames .. _Ansible: https://ansible.com .. _Red Hat: https://redhat.com .. _MIT: https://github.com/ansible-community/ansible-lint/blob/main/LICENSE .. _GPLv3: https://github.com/ansible/ansible/blob/devel/COPYING ansible-lint-5.4.0/bindep.txt000066400000000000000000000007751420171260700161140ustar00rootroot00000000000000# This is a cross-platform list tracking distribution packages needed by tests; # see https://pypi.org/project/bindep/ for additional information. bzip2 [platform:rpm] gcc [test platform:rpm] gcc-c++ [test platform:rpm] libselinux-python [platform:centos-7] python3 [test platform:rpm !platform:centos-7] python3-devel [test platform:rpm !platform:centos-7] python3-libselinux [test platform:rpm !platform:centos-7] python3-netifaces [test !platform:centos-7 platform:rpm] openssl-devel [test platform:rpm] ansible-lint-5.4.0/codecov.yml000066400000000000000000000002141420171260700162430ustar00rootroot00000000000000codecov: require_ci_to_pass: true comment: false coverage: status: patch: false project: default: threshold: 0.5% ansible-lint-5.4.0/conftest.py000066400000000000000000000014331420171260700163010ustar00rootroot00000000000000"""PyTest Fixtures.""" import os import re import sys from typing import List os.environ["NO_COLOR"] = "1" pytest_plugins = ["ansiblelint.testing.fixtures"] def pytest_cmdline_preparse(args: List[str]) -> None: """Pytest hook.""" # disable xdist when called with -k args (filtering) # https://stackoverflow.com/questions/66407583/how-to-disable-pytest-xdist-only-when-pytest-is-called-with-filters if "xdist" in sys.modules and "-k" in args: for i, arg in enumerate(args): # remove -n # option if arg == "-n": del args[i] del args[i] break # remove -n# option if re.match(r"-n\d+", arg): del args[i] break args[:] = ["-n0"] + args ansible-lint-5.4.0/constraints.txt000066400000000000000000000014041420171260700172100ustar00rootroot00000000000000# # This file is autogenerated by pip-compile with python 3.9 # To update, run: # # pip-compile --extra=test --extra=yamllint --no-annotate --output-file=constraints.txt --strip-extras setup.cfg # attrs==21.4.0 bracex==2.2.1 colorama==0.4.4 commonmark==0.9.1 coverage==6.2 enrich==1.2.7 execnet==1.9.0 flaky==3.7.0 iniconfig==1.1.1 packaging==21.3 pathspec==0.9.0 pluggy==1.0.0 psutil==5.9.0 py==1.11.0 pygments==2.11.2 pyparsing==3.0.7 pytest==6.2.5 pytest-cov==3.0.0 pytest-forked==1.4.0 pytest-xdist==2.5.0 pyyaml==6.0 rich==11.1.0 ruamel-yaml==0.17.20 ; python_version >= "3.7" ruamel-yaml-clib==0.2.6 tenacity==8.0.1 toml==0.10.2 tomli==1.2.3 wcmatch==8.3 yamllint==1.26.3 # The following packages are considered to be unsafe in a requirements file: # setuptools ansible-lint-5.4.0/docs/000077500000000000000000000000001420171260700150315ustar00rootroot00000000000000ansible-lint-5.4.0/docs/.gitignore000066400000000000000000000003211420171260700170150ustar00rootroot00000000000000# Old compiled python stuff *.py[co] # package building stuff build # Emacs backup files... *~ .\#* .doctrees # Generated docs stuff ansible*.xml .buildinfo objects.inv .doctrees *.min.css _build rst_warnings ansible-lint-5.4.0/docs/.nojekyll000066400000000000000000000000001420171260700166470ustar00rootroot00000000000000ansible-lint-5.4.0/docs/README.md000066400000000000000000000007561420171260700163200ustar00rootroot00000000000000# Documentation source for Ansible Lint To build the docs, run `tox -e docs`. At the end of the build, you will see the local location of your built docs. Building docs locally may not be identical to CI/CD builds. We recommend you to create a draft PR and check the RTD PR preview page too. If you do not want to learn the reStructuredText format, you can also [file an issue](https://github.com/ansible-community/ansible-lint/issues), and let us know how we can improve our documentation. ansible-lint-5.4.0/docs/_static/000077500000000000000000000000001420171260700164575ustar00rootroot00000000000000ansible-lint-5.4.0/docs/_static/ansible-lint.svg000066400000000000000000000033461420171260700215670ustar00rootroot00000000000000 ansible-lint-5.4.0/docs/_static/images/000077500000000000000000000000001420171260700177245ustar00rootroot00000000000000ansible-lint-5.4.0/docs/_static/images/logo_invert.png000066400000000000000000000111441420171260700227620ustar00rootroot00000000000000PNG  IHDR>aiCCPsRGB IEC61966-2.1(u+DQ?3C1XXXLB3~(3 %McfFFlƯ[e5AyFd{sϪUz.oFLF|ө1Snp9**_,\UƒaGǗÛ jF>rAkGOt?6_jԅ%>KZnjRbx1F`a"̀}CVΟ`QrU ,&CKR]]o_ToO?  \}<=J{*zIkمU89/i-8];C1o#NjP b~9|l@Vg pHYs%%IR$IDATx{pTyzAAH%!$^t]$ v&H33n֍f225-c 1/H1Rqwjٽ{w]~;|;!C 2d]P\("CJwgGX`\NT>Km AD'J`0)gh@D[| 0v+"ͱb*X NOhDc'e> 2NwRˀo9 gc  2'pס}+JUu>{\h_2 GWD{)7+؎ E䖅:l#"yW;\U`2ׯԡ57 &o=`K/M~-"}ԙ^gEqM"R<:Ddnh;M4d{ٮ%"OEvI¿/"]NT*"@xXr"8Bm֮)> p hfxOvhA{߅Ren/5zX :,"`sI"tx@:-׀,꿩8'/Ď$Y ޱUDS6k*'÷ q3BFF*@Ho2`Q} 3" ++:f͚Eyy9:u?kђ'`Ѣϗihh`466z(Yt?e˖QVVD2 t҄d%rfbܸqe3fKȞx dee` _3rJJJP*`b4i-ZD 0{$K #Srssihh0uOB32|sQd?>Iy} DT5G0X,1e&NhzrsScq ;_q^^s5.ǻ.=x)@x#NضmneeeL:iѼ@@@pFf.][Ne1hw10sLt:tꖫfю#ׯ_G̟?Q<-ŴTVhlldhHێϱct/X@y*هEKqii8|k ƍc֬YN; c8ׇ]x"~#G ;qHC 0g &ڍ JJJ,!m~)=dܾ8&&ĐIEɓ'3i$2_,wȑ#744@[`usb13'Q&6b [[[sbeRhKCXy摓h 3ees]ƙ3gt˨LByn: "x8s#X~3XPP Ɨ@~~>uuu=+V`(iO>mCg\4*d{Qܚ^xJKKMݧ24i$åhJ!# c0a/fpPY*,:k^PYYi;,+//+W裏*'qPT@%K*73?HJAD8Q^6l`EdڵJ[Ϝ9իWudN`jAx׆ 2HGG֭3PTTĚ5k7>u,..6\r@D:HIPٞqF0 4|r-[!N:[$ߪ ݢ0+'ѲÇٸq^SK/1mZsA~e|@ͅn0qD ^t.\`ݺutt{3f ?</ﷶ6˷4y&Hݘ!1BC`p ɖtRu߿gȑ#7NTUUQZZJkk밉,]g/77n3a3P7 ~x7ٹs^:֬Y3lݿwC6oެ# %\xVLReܹs3fiiia>1c] ͦ@Dni*dee)eÈn^{5 !f~cz.OIB555Upm6o޽{-+fٌ;Ɇq ,)\B Ԥu8 ^~DE@D/P__oq#ΟCCC޽[.cڴiNOON|`6'ψG+Fc3x3NV9n=ӧO7uz./^4|fU"XGlfUqڸr劝8N$(L,v#Q PV?#yٱcRigoepױ>rXs__'ORk:tH)Ԣ"*"mrLsh'T x\#G| >M4_dv&G+[<q&"P|AANs)usCQZZJWW'Nb EhDLdm$ :]{"J*w 40?@ˇP!);Bе`0 t2!&"I:n&)eK"b/Q)@8ċi7?[$DR}?e*4&M2MEϭ&D&`%tf|I# n=3>P(? mM?HȋwhZDJSXwp7>4 Hم4XhB`e/p4>xP@0<|o|77s:o<K;U9 FDdTiXκ+|er!_kY~l|K( %=|)I^8~U| -@?K ^ b. ԒZ]BO[hD+h{j Zjr2 "V/»SR>?*r=k2@?prh-"JU"R{!Ѳs8KR^"$3-.;"bHraC_N'VOSCYhD6~z; ynYs iD$ZtFK"^ ADrUzцu n5#B" -YxYDA#J"H-a !eȐ!C 2xl^lfIENDB`ansible-lint-5.4.0/docs/_static/theme_overrides.css000066400000000000000000000007161420171260700223610ustar00rootroot00000000000000/* table width fix via: https://rackerlabs.github.io/docs-rackspace/tools/rtd-tables.html */ /* override table width restrictions */ @media screen and (min-width: 767px) { .wy-table-responsive table td { /* !important prevents the common CSS stylesheets from overriding * this as on RTD they are loaded after this stylesheet */ white-space: normal !important; } .wy-table-responsive { overflow: visible !important; } } ansible-lint-5.4.0/docs/conf.py000066400000000000000000000255701420171260700163410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # documentation build configuration file, created by # sphinx-quickstart on Sat Sep 27 13:23:22 2008-2009. # # This file is execfile()d with the current directory set to its # containing dir. # # The contents of this file are pickled, so don't put values in the namespace # that aren't pickleable (module imports are okay, they're removed # automatically). # # All configuration values have a default value; values that are commented out # serve to show the default value. """Documentation Configuration.""" import os import sys from pathlib import Path from typing import List import pkg_resources # Make in-tree extension importable in non-tox setups/envs, like RTD. # Refs: # https://github.com/readthedocs/readthedocs.org/issues/6311 # https://github.com/readthedocs/readthedocs.org/issues/7182 sys.path.insert(0, str(Path(__file__).parent.resolve())) # pip3 install sphinx_rtd_theme # import sphinx_rtd_theme # html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # If your extensions are in another directory, add it here. If the directory # is relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. # sys.path.append(os.path.abspath('some/directory')) # sys.path.insert(0, os.path.join("ansible", "lib")) sys.path.append(os.path.abspath("_themes")) VERSION = "2.6" AUTHOR = "Ansible, Inc" # General configuration # --------------------- # Add any Sphinx extension module names here, as strings. # They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # TEST: 'sphinxcontrib.fulltoc' extensions = [ "myst_parser", "sphinx.ext.autodoc", "sphinx.ext.intersphinx", # Third-party extensions: "sphinxcontrib.apidoc", "sphinxcontrib.programoutput", # Tree-local extensions: "rules_table_generator_ext", # in-tree extension ] # Fail safe protection to detect conflicting packages try: pkg_resources.get_distribution("sphinxcontrib-programoutput") print( "FATAL: We detected presence of sphinxcontrib-programoutput package instead of sphinxcontrib-programoutput2 one. You must be sure the first is not installed.", file=sys.stderr, ) sys.exit(2) except pkg_resources.DistributionNotFound: pass # Later on, add 'sphinx.ext.viewcode' to the list if you want to have # colorized code generated too for references. # Add any paths that contain templates here, relative to this directory. templates_path = [".templates"] # The suffix of source filenames. source_suffix = ".rst" # The master toctree document. master_doc = "index" apidoc_excluded_paths: List[str] = [] apidoc_extra_args = [ "--implicit-namespaces", "--private", # include “_private” modules ] apidoc_module_dir = "../src/ansiblelint" apidoc_module_first = False apidoc_output_dir = "pkg" apidoc_separate_modules = True apidoc_toc_file = None # General substitutions. project = "Ansible Lint Documentation" copyright = "2013-2021 Ansible, Inc" # pylint: disable=redefined-builtin github_url = "https://github.com" github_repo_org = "ansible" github_repo_name = "ansible-lint" github_repo_slug = f"{github_repo_org}/{github_repo_name}" github_repo_url = f"{github_url}/{github_repo_slug}" extlinks = { "issue": (f"{github_repo_url}/issues/%s", "#"), "pr": (f"{github_repo_url}/pull/%s", "PR #"), "commit": (f"{github_repo_url}/commit/%s", ""), "gh": (f"{github_url}/%s", "GitHub: "), } intersphinx_mapping = { "ansible": ("https://docs.ansible.com/ansible/devel/", None), "ansible-core": ("https://docs.ansible.com/ansible-core/devel/", None), "packaging": ("https://packaging.rtfd.io/en/latest", None), "pytest": ("https://docs.pytest.org/en/latest", None), "python": ("https://docs.python.org/3", None), "python2": ("https://docs.python.org/2", None), "rich": ("https://rich.rtfd.io/en/latest", None), } # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. # # The short X.Y version. version = VERSION # The full version, including alpha/beta/rc tags. release = VERSION # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. today_fmt = "%B %d, %Y" # List of documents that shouldn't be included in the build. # unused_docs = [] # List of directories, relative to source directories, that shouldn't be # searched for source files. # exclude_dirs = [] # A list of glob-style patterns that should be excluded when looking # for source files. # OBSOLETE - removing this - dharmabumstead 2018-02-06 exclude_patterns = ["README.md"] # The reST default role (used for this markup: `text`) to use for all # documents. default_role = "any" # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" highlight_language = "YAML+Jinja" # Substitutions, variables, entities, & shortcuts for text which do not need to link to anything. # For titles which should be a link, use the intersphinx anchors set at the index, chapter, and # section levels, such as qi_start_: rst_epilog = """ .. |acapi| replace:: *Ansible Core API Guide* .. |acrn| replace:: *Ansible Core Release Notes* .. |ac| replace:: Ansible Core .. |acversion| replace:: Ansible Core Version 2.1 .. |acversionshort| replace:: Ansible Core 2.1 .. |versionshortest| replace:: 2.2 .. |versiondev| replace:: 2.3 .. |pubdate| replace:: July 19, 2016 .. |rhel| replace:: Red Hat Enterprise Linux """ # Options for HTML output # ----------------------- html_theme_path = ["../_themes"] html_theme = "sphinx_ansible_theme" html_theme_options = { "collapse_navigation": False, "analytics_id": "UA-128382387-1", "style_nav_header_background": "#5bbdbf", "style_external_links": True, # 'canonical_url': "https://docs.ansible.com/ansible/latest/", "vcs_pageview_mode": "edit", "navigation_depth": 3, } html_context = { "display_github": "True", "github_user": "ansible-community", "github_repo": "ansible-lint", "github_version": "main/docs/", "current_version": version, "latest_version": "latest", # list specifically out of order to make latest work "available_versions": ("latest", "stable"), "css_files": (), # overrides to the standard theme } html_short_title = "Ansible Lint Documentation" # The style sheet to use for HTML and HTML Help pages. A file of that name # must exist either in Sphinx' static/ path, or in one of the custom paths # given in html_static_path. # html_style = 'solar.css' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = "Ansible Lint Documentation" # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (within the static path) to place at the top of # the sidebar. html_logo = "_static/ansible-lint.svg" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = html_logo # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['.static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = "%b %d, %Y" # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_use_modindex = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, the reST sources are included in the HTML build as _sources/. html_copy_source = False # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. html_use_opensearch = "https://ansible-lint.readthedocs.io/en/latest/" # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = "Poseidodoc" # Options for LaTeX output # ------------------------ # The paper size ('letter' or 'a4'). # latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). # latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class # [howto/manual]). latex_documents = [ ("index", "ansible.tex", "Ansible 2.2 Documentation", AUTHOR, "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # Additional stuff for the LaTeX preamble. # latex_preamble = '' # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_use_modindex = True autoclass_content = "both" # table width fix via: https://rackerlabs.github.io/docs-rackspace/tools/rtd-tables.html html_static_path = ["_static"] html_css_files = [ "theme_overrides.css", # override wide tables in RTD theme "ansi.css", ] linkcheck_workers = 25 nitpicky = True nitpick_ignore = [ ("py:class", "ansible.parsing.yaml.objects.AnsibleBaseYAMLObject"), ("py:class", "ansible.template.Templar"), ("py:class", "Templar"), ("py:class", "Lintable"), ("py:class", "yaml"), ("py:class", "role"), ("py:class", "requirements"), ("py:class", "handlers"), ("py:class", "tasks"), ("py:class", "meta"), ("py:class", "playbook"), ("py:class", "AnsibleBaseYAMLObject"), ("py:class", "Namespace"), ("py:class", "RulesCollection"), ("py:class", "_pytest.fixtures.SubRequest"), ("py:class", "MatchError"), ("py:class", "Pattern"), ("py:class", "odict"), ("py:class", "LintResult"), ("py:obj", "Any"), ("py:obj", "ansiblelint.formatters.T"), ] ansible-lint-5.4.0/docs/configuring.rst000066400000000000000000000027261420171260700201040ustar00rootroot00000000000000 .. _configuring_lint: *********** Configuring *********** .. contents:: Topics Configuration File ------------------ Ansible-lint supports local configuration via a ``.ansible-lint`` configuration file. Ansible-lint checks the working directory for the presence of this file and applies any configuration found there. The configuration file location can also be overridden via the ``-c path/to/file`` CLI flag. When configuration file is not found in current directory, the tool will try to look for one in parent directories but it will not go outside current git repository. If a value is provided on both the command line and via a config file, the values will be merged (if a list like **exclude_paths**), or the **True** value will be preferred, in the case of something like **quiet**. The following values are supported, and function identically to their CLI counterparts: .. literalinclude:: ../.ansible-lint :language: yaml Pre-commit Setup ---------------- To use ansible-lint with `pre-commit`_, just add the following to your local repo's ``.pre-commit-config.yaml`` file. Make sure to change **rev:** to be either a git commit sha or tag of ansible-lint containing ``.pre-commit-hooks.yaml``. .. code-block:: yaml - repo: https://github.com/ansible-community/ansible-lint.git rev: ... # put latest release tag from https://github.com/ansible-community/ansible-lint/releases/ hooks: - id: ansible-lint .. _pre-commit: https://pre-commit.com ansible-lint-5.4.0/docs/contributing.rst000066400000000000000000000043141420171260700202740ustar00rootroot00000000000000.. include:: ../.github/CONTRIBUTING.rst :end-before: DO-NOT-REMOVE-deps-snippet-PLACEHOLDER Module dependency graph ----------------------- Extra care should be taken when considering adding any dependency. Removing most dependencies on Ansible internals is desired as these can change without any warning. .. command-output:: pipdeptree -p ansible-lint .. include:: ../.github/CONTRIBUTING.rst :start-after: DO-NOT-REMOVE-deps-snippet-PLACEHOLDER Adding a new rule ----------------- Writing a new rule is as easy as adding a single new rule, one that combines **implementation, testing and documentation**. One good example is MetaTagValidRule_ which can easily be copied in order to create a new rule by following the steps below: * Use a short but clear class name, which must match the filename * Pick an unused ``id``, the first number is used to determine rule section. Look at rules_ page and pick one that matches the best your new rule. see which one fits best. * Include ``experimental`` tag. Any new rule must stay as experimental for at least two weeks until this tag is removed in next major release. * Update all class level variables. * Implement linting methods needed by your rule, these are those starting with **match** prefix. Implement only those you need. For the moment you will need to look at how similar rules were implemented to figure out what to do. * Update the tests. It must have at least one test and likely also a negative match one. * If the rule is task specific, it may be best to include a test to verify its use inside blocks as well. * Optionally run only the rule specific tests with a command like: :command:`tox -e py38-ansible29 -- -k NewRule` * Run :command:`tox` in order to run all ansible-lint tests. Adding a new rule can break some other tests. Update them if needed. * Run :command:`ansible-lint -L` and check that the rule description renders correctly. * Build the docs using :command:`tox -e docs` and check that the new rule is displayed correctly in them. .. _MetaTagValidRule: https://github.com/ansible-community/ansible-lint/blob/main/src/ansiblelint/rules/MetaTagValidRule.py .. _rules: https://ansible-lint.readthedocs.io/en/latest/default_rules.html ansible-lint-5.4.0/docs/custom-rules.rst000066400000000000000000000101731420171260700202270ustar00rootroot00000000000000************ Custom Rules ************ Creating Custom Rules --------------------- Rules are described using a class file per rule. Default rules are named *DeprecatedVariableRule.py*, etc. Each rule definition should have the following: * ID: A unique identifier * Short description: Brief description of the rule * Description: Behaviour the rule is looking for * Tags: One or more tags that may be used to include or exclude the rule * At least one of the following methods: * ``match`` that takes a line and returns None or False, if the line doesn't match the test, and True or a custom message, when it does. (This allows one rule to test multiple behaviours - see e.g. the *CommandsInsteadOfModulesRule*.) * ``matchtask`` that operates on a single task or handler, such that tasks get standardized to always contain a *module* key and *module_arguments* key. Other common task modifiers, such as *when*, *with_items*, etc., are also available as keys, if present in the task. An example rule using ``match`` is: .. code-block:: python from ansiblelint.rules import AnsibleLintRule class DeprecatedVariableRule(AnsibleLintRule): id = 'EXAMPLE002' shortdesc = 'Deprecated variable declarations' description = 'Check for lines that have old style ${var} ' + \ 'declarations' tags = { 'deprecations' } def match(self, line: str) -> Union[bool, str]: return '${' in line An example rule using ``matchtask`` is: .. code-block:: python from typing import TYPE_CHECKING, Any, Dict, Union import ansiblelint.utils from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class TaskHasTag(AnsibleLintRule): id = 'EXAMPLE001' shortdesc = 'Tasks must have tag' description = 'Tasks must have tag' tags = ['productivity'] def matchtask(self, task: Dict[str, Any], file: 'Optional[Lintable]' = None) -> Union[bool,str]: # If the task include another task or make the playbook fail # Don't force to have a tag if not set(task.keys()).isdisjoint(['include','fail']): return False # Task should have tags if not task.has_key('tags'): return True return False The task argument to ``matchtask`` contains a number of keys - the critical one is *action*. The value of *task['action']* contains the module being used, and the arguments passed, both as key-value pairs and a list of other arguments (e.g. the command used with shell). In ansible-lint 2.0.0, *task['action']['args']* was renamed *task['action']['module_arguments']* to avoid a clash when a module actually takes args as a parameter key (e.g. ec2_tag) In ansible-lint 3.0.0 *task['action']['module']* was renamed *task['action']['__ansible_module__']* to avoid a clash when a module take module as an argument. As a precaution, *task['action']['module_arguments']* was renamed *task['action']['__ansible_arguments__']*. Packaging Custom Rules ---------------------- Ansible-lint provides a sub directory named *custom* in its built-in rules, ``/usr/lib/python3.8/site-packages/ansiblelint/rules/custom/`` for example, to install custom rules since v4.3.1. The custom rules which are packaged as a python package installed into this directory will be loaded and enabled automatically by ansible-lint. To make custom rules loaded automatically, you need the following: - Packaging your custom rules as a python package named some descriptive ones like ``ansible_lint_custom_rules_foo``. - Make it installed into ``/custom//``. You may accomplish the second by adding some configurations into the [options] section of the ``setup.cfg`` of your custom rules python package like the following. .. code-block:: [options] packages = ansiblelint.rules.custom. package_dir = ansiblelint.rules.custom. = ansible-lint-5.4.0/docs/default_rules.rst000066400000000000000000000000451420171260700204200ustar00rootroot00000000000000.. ansible-lint-default-rules-list:: ansible-lint-5.4.0/docs/index.rst000066400000000000000000000035661420171260700167040ustar00rootroot00000000000000.. _lint_documentation: Ansible Lint Documentation ========================== About Ansible Lint `````````````````` Ansible Lint is a command-line tool for linting **playbooks, roles and collections** aimed towards any Ansible users. Its main goal is to promote proven practices, patterns and behaviors while avoiding common pitfalls that can easily lead to bugs or make code harder to maintain. Ansible lint is also supposed to help users upgrade their code to work with newer versions of Ansible. Due to this reason we recommend using it with the newest version of Ansible, even if the version used in production may be older. As any other linter, it is opinionated. Still, its rules are the result of community contributions and they can always be disabled based individually or by category by each user. `Ansible Galaxy project `_ makes use of this linter in order to compute quality scores for `Galaxy Hub `_ contributed content. This does not mean this tool is aimed only to those that want to share their code. Files like ``galaxy.yml``, or sections like ``galaxy_info`` inside ``meta.yml`` help with documentation and maintenance, even for unpublished roles or collections. The project was originally started by `@willthames `_, and has since been adopted by the Ansible Community team. Its development is purely community driven, while keeping permanent communications with other Ansible teams. .. toctree:: :maxdepth: 3 :caption: Installing installing .. toctree:: :maxdepth: 3 :caption: Usage usage .. toctree:: :maxdepth: 3 :caption: Configuring configuring .. toctree:: :maxdepth: 4 :caption: Rules rules custom-rules default_rules .. toctree:: :caption: Contributing contributing Private unsupported (dev) API autodoc ansible-lint-5.4.0/docs/installing.rst000066400000000000000000000046361420171260700177400ustar00rootroot00000000000000 .. _installing_lint: ********** Installing ********** .. contents:: Topics Installing on Windows is not supported because we use symlinks inside Python packages. While our project does not directly ship a container, the tool is part of the toolset_ container. Please avoid raising any bugs related to containers and use the discussions_ forum instead. .. code-block:: bash # replace docker with podman docker run -h toolset -it quay.io/ansible/toolset ansible-lint --version .. _toolset: https://github.com/ansible-community/toolset .. _discussions: https://github.com/ansible-community/ansible-lint/discussions .. note:: The default installation of the ansible-lint package no longer installs any specific version of Ansible. You need to either install the desired version of Ansible yourself or mention one of the helper extras: * ``core`` - will install the latest version of ansible-core 2.11+ * ``community`` - will install the latest version of ansible community edition Using pip or pipx ----------------- You can use either pip3_ or pipx_ to install it; the latter one automatically isolates the linter from your current python environment. That approach may avoid having to deal with particularities of installing python packages, like creating a virtual environment, activating it, installing using ``--user`` or fixing potential conflicts if not using virtualenvs. .. code-block:: bash # Assuming you already installed Ansible and you also want the optional # yamllint support: pip3 install "ansible-lint[yamllint]" # If you want to install and use the latest Ansible (w/o community collections) pip3 install "ansible-lint[core,yamllint]" # If you want to install and use the latest Ansible with community collections pip3 install "ansible-lint[community,yamllint]" # If you want to install an older version of Ansible 2.9 pip3 install ansible-lint "ansible>=2.9,<2.10" .. _installing_from_source: .. _pip3: https://pypi.org/project/pip/ .. _pipx: https://pypa.github.io/pipx/ From Source ----------- **Note**: pip 19.0+ is required for installation. Please consult with the `PyPA User Guide`_ to learn more about managing Pip versions. .. code-block:: bash pip3 install git+https://github.com/ansible-community/ansible-lint.git .. _PyPA User Guide: https://packaging.python.org/tutorials/installing-packages/#ensure-pip-setuptools-and-wheel-are-up-to-date ansible-lint-5.4.0/docs/jinja2-2.9.7.inv000066400000000000000000000062121420171260700174000ustar00rootroot00000000000000# Sphinx inventory version 2 # Project: Jinja2 # Version: 2.10-dev # The remainder of this file is compressed using zlib. xڭ\KsWjs&Sjo?6Ng9 `-)> |I$.hFw0YT >zNo0'ZVY@l]: X# jv2)".Rqbˌ>f0B2~0|baj0T1>>^)Re[%7[O#L0>F~g28\n?9XiFŬz.G%*#%iI݅XE~P 6PJ2Fa.(*&p>^z9<AUO/.#WΎ[\]6`D/}KQi VqVhs ~ R\!sҴOZd{̻ #TD6/> 26DrvN> *S#ܩbdOSâ(A*!u1'NH/}a!#4R>њ*agcFKWrcyUcT°"ד0x-%x 0 FC@J7.bC0kyZ&;O'n$;uV/4c䪭Trn;/U/kA1XPMufo\@WdHlXFg γV96}NfC$*k%C<.@քT m~]mzu?޵0̍vЗY:J0t"@љateE mR|g^{{*f/͜JLTIaCLoVN{uS7@׵5sR"N˥YQ5 JW:=uһzA gK;΃fs:6nP'9dDSA:$ȶ90<̱n@eƯna+Z43Ra[X߃anEFL<J 6AD&*<B?M侮{\dޅmBЂWDjnU2"Rp<&@Ae=tyIN{:C v;7*gSu Bآ2p9ɚFȚ 9]'a1RQj[2'< 4oT?Uds$TCa}дwphV)fNKDhlhm$d᳴[왷=>EU͸13&{0L~'+x|TKIDے?'6~T ~Y[6pjORE[*8aO0[WXm҇_S}fFuϢECM=&2!%$uSJ>K+ 4y:bx *CKЁeEْ[K8h^Jkg# CgH7aH7 s7C'9rB35FɼR{%g&?3tPy;U4* dPb[rv:MJv'Jr *E1!v-xҩq.bC!*I_*uXDvr-L=})n9G!A$v8wf]/$g("Zg6xrq&~.8zا?8l]!2j sm+@@TiyB4f]k!`D,!Sqkq/^xIx ySGc4i=OH_,h$~c6[ Dn; Sַ=_ؙm ^ .>?1ansible-lint-5.4.0/docs/pkg/000077500000000000000000000000001420171260700156125ustar00rootroot00000000000000ansible-lint-5.4.0/docs/pkg/.gitignore000066400000000000000000000000161420171260700175770ustar00rootroot00000000000000* !.gitignore ansible-lint-5.4.0/docs/python2-2.7.13.inv000066400000000000000000002456201420171260700177110ustar00rootroot00000000000000# Sphinx inventory version 2 # Project: Python # Version: 2.7 # The remainder of this file is compressed using zlib. xڼ]w8y?ך=ꮨ:uNuʒK#2 "!e`m$%J@ FIӔgxIL($Q%E{}Q@a DWHlhBYm'}bEJzBj]K:- r6 *nnX}oV%P}`s5s"0βЁ*)&ZWY5Ղߟ)6n -r-?;%OS^j 15CgA-eP9AVcxf)9NyMaQb44IIwqj".l1[n_7>bYoOf(àC̹H /I#xHy}Ç-Jυ+=d(`€Z{) S6iRp4TQܾB0"DKq[91WSgZX,0Di׏nPt$ρފ~gK>Ϧ4{s3@ %=s8 $?b(i '̟b (@쓄 I=C?Hշ ߕa| ΍-27Lq 5W<2uFz;I*bx88;ḟV+F^7j,SBQv2L,a56)2 o"j*O0.1o`N.f91,wgt {4SSAI?3Gz ϒ0T6죘]R@Ojs.kKěfD7h>@cksolE k͟ 12$!%6Z@n(1j1s1[xn 'i䜁RUj\NPě[B1uuV`EkӇ}Y>Z:Z#ԠAo4rc Zܛ[_=d_@o2-kNZ)TG8$Ԟg"nnI89G"* ͎ #`i@OYvp"k[oFH 2%B˶WDVȼހhI mBZx -Dz] _Ŏ:c(@Ӂ=ѺWMv6LI7\`f DWH3c3*dyM"au,OO 3=0(}%`v{=c2rce<-lǰ0[N[jCPt;_>1ce5[fuOLCAaʡ~&aहٹƇ16ס8ߴrGlG NJO;/dtCL}2Z7IM=gI_}Fl("Y8gіi?9[=(J4:8 Tً4/-!AzGwOıURyrǽD74%qqV0* ~x ߖ,iuІ`rYWHSxS}Ua5e *m^*zL%-Xߵ}=6PЙ <〔젷&+me%!Bb&[2(3A$n(iď5q8uKЄTg(!wTMIAVqw0J@=Y zI'RW[rxDj˵]jU;80&4m^1'y%}LҜO^X(ZZ 'Դ, _2qo6*̔$ǐWy3p7b<"_3,K3*D"a U^ Da}hmQRhͧjiV݆  ȂBe`OdDYuЮJ$B3C$oI@Cj 2@u EȠi5 #C8Ztu}Pvn=1HQ2ω(wڙ%o,fgX.t  TA£:bXCC$kKr/uF}Xu땃n 0R$c϶'rBW3 b qLO}<. Ϸ qnISМzx'h+GfvawƋQaβv6L OHzӕ7t[lY& Lf_v4B p敄!0FHt°Tx36q=dva֭6d@jǘpvB\ 4]LZȂ*B %kkF7 #h{]iܪ!;U^6 {L-[˭t})6QiK6l!Heтo@;w-J$$]A\9fLo[@I]s}[1I9qoitĆrM!ΠpQlt}94N ^@tD7XVk/i4zc^xktU1Ur_i&1[,ܑBi]&Hfg)9Fl׍sr;:u lå|qm.vX"]#i[BA2,㚂240F/I[Lm7 f s+\JvRAkeZCrʃ}) N 8c8l8鐄',V#f /|lDImFG?D(;&1=nK#/pF>R/SY#olKRg;FlGgR/.~w:謺#cv8y?"NUd<Գ [_]RKasNi;R:mNjGS7NwSj+{]-f9l0hh旤 oG-{}~gHτGT@0j Sƹd>8F!̸Gr4MRMjlx!A²@'C %[.j|Xhχ{ݫ7':`*_*_*_è*_ k`Q{ ;dk Tܲ w1r紇wP;v,z)w)q ŤN mhnڦlN6OG%qF#Qel $| кN E ->>_8E`},K>8tC`PVg;8j[ȳ+ɂܿډ<@B3@od;LgK=x]rmD9ʘƋz$RkiNw'r>ޕ# yt%ZV'n9q>̟`).n3̓$ẏO߉aԹٱ%aUiQ7p/O$ 󞱤$Zev]YG(Hb+hH\MmwmQy'uG*υ(ӥ;2(;c:cp-pi@@ScAҧ*IAA7,? #X iMhb Őw\)-x{}g9:1j+ݷxtJ}Xٜ_ GiZj;G9m^ԛcطE/`5Bxu9žu,yt|b@1Zk̕͝ 7}]aam=1_*7ePl jxW zձ2z}a6RWI^MWYKvHQ7"8Yޥ˞$Q)ztDMԷDҜ#EBL1KgҐ (UH6?_gQ kG%]ʛv/[Z(h)yT9WN&F(k)buD,xYowڲhjӹ> 뷽ko(q%[Ϟ0W ~{%M1KE ,ܱOʈ$?!2AP1UC%CtǕD`@>a*K"ZAT8.:/_<Ѝbrf1$bTsBVH bTƻg"3b(ZBbX&%ЭSmW>DRePBQM<Xvk>6((Vz ŸMf勂fl 0Ӊ,Q)3 j`V)5lʮMkfͯxo5[#3^y`_5yz'roI)O]7 &*瘼iN R2TV{l=÷2MÄqDV,ѫ=RW4p (؇'+EBC+|].B}=/-{Ds+9j]2h2aKq8PyJꃼT$8鉪)pC߂FcijԔf%&(,*q,-1Aݞ9<}'qB ȧ$DfKwZZ@ċ>/b rrE{*èTV[.è+9ka]r]Td3L.%RI?yQ2wڳ΢Bh;Ue>1L= v?>}+#)!q+.2?ūmd^deAGJNځg^DF o {Yٖao:2jbІt7JtŰrXA`jYƟ #I rq[%اىo%a,3gz翓Ǹppg;\]A< $"}ήAߜ}!o\g[{ na"+Vޜgjصs =?U^7ek'vŤiۆݹV/`v@ \*LIiʐ@h2:*8UAQG Ke*X:NM ljK׭Vߛ=j|l"j ݰ]Hw>,f}crcAC#DMʷI(RYl"aq׫5>ơ÷< oG cH"rl{|ҞM_-:p6ƭi%Iw!1 nMR&U-:m$VAצ|vw +!GT_*w5grN٘Ӥ ~ñ,#Vq՜cm'}T-? {;1}CI]y6D"1T矹{\Ln*yz|l띦5_7j֫W4? )\ge<}[`eyZor0{-t | &gb7$}}g|Pl-|G}|{iB| "GjàbF ꒑$Iox ie(='ʹhZ3.?+Z%'DVWP]啄f5xXPԣCPy7 b'_ MF(N\"ssj?Ntߕ"K[y.)b5Lʹ=Xd\TzK28;Κ;Yѕ%;M fibibb3[Dq ?_XsIو0R~r=;$y-ϝXkx-10F/|xGT_pG&HP4,FK\©SiCLIaLL,XH؁1i2SG'f-&# ($l Ђ HI gM2tH2 w}+ݷ)TBtdތ\Y~tsS.ABAOӴ rS}Wt_-];d6nVpH%ʚ4>cl=RDGRlbsz8FvB1 1#k1_;894c+9FI6Qi|c+,%Qo4|:)uś~L'TiNa᠌=ʅ2KFHYPTnwpIK wfU/b&I?\Sge 3pA1^leMpJ:c%aVpdb%WZۑ!gߔgjU卦t7 1M"r-$`7brX{pBV"Uwɸ<z=q*Eo}p$zY4wu{?4r6yjBnރ,,N$ 2] H$mnsa.e$yss{:]T%hE X&hF5mWu?rUbU9Z >N43۟tTR1d ?Xrb'Q4 )%(Bye0qy Bא{ERHDSdq!8 jnkK}[#_˛Lo7˥ Ը/n|}GAH h:&mTLL3h &9FgUIFN wʹ h%wܼ[ )ojh5@^@[qU^V{zoM::yT<'qhE0L{?"0.Vȁ}2Lnm 1ns FO ;'0EF$ܻ-dCDoƹzիKq=cw}w)l1 _KtU֤% 1l_cؾu~Kk1>eYyq4-y$/^2{(79!%4YtDV#vVy]'ΙŤ##[L4<ϋ &[nd?(y F~h: )jW@Bw jף)5maxHcP9I ?&C$jUq5mc"4bȤ )9<:L~Vr~a]Kg%yؙMph-Y9[ٰvn9c:P^#ǰn\;#98,ms݀5YbHW:o >;qCbK,凒 7;կ;yc_8/GwQ\v$O)oKVa2Fi\(PE)g1]ێ9QϤUR91~d0`?9} ( AQ\Py h} Z%rGm.DIk*{Sq.-^PM Lo]@=Tos\[W-4jtkڳ,4FPE` c\1oqeI694鬌23܂BRwz@Z0 h[6ښ~C[sUL1=?+|ӯ) bO <(IuBҎ?sn,2XJ6#mO$p%mduPE* 3=T8W?g%4s{ә-w-be(MIf*hnv"Bn!VEA.ans%/G "xx ;! ;g۵ { Z.ZK$yT5tN0"ya:%h`DhDؖ%2q ,96@{(x$dt"SsHj ӆN48dELOynU 800VH蚯 f8.X;POeFRݱW g 9Lqη*0WCӲ07@aSDi"Wf z@`~dzz0' VkJy*!g&0-BȖtkl]Du6#xуhDGR *+ MTiUU#m*)u91g!^KꋣJG8J&yt781҅1\0&J:$WXwzҟQ4zCo^Z;{+E~'IEaˍ M .bAm!l\cYQdi \Ǫ orRM|&_ "Ѱ\`$UQlUnYvPL.*^~X.%oV?tU1Q:Gn߼H08yՑe;dO dz͞pV@ɞ)wꗚi^ib]{*\qwΟ@kN>k] yu:nu w'' [ SԋJ>'Rti-& LޗjK4U*͠6 >` AD67r!/;%L*/(+"nA)˭yL%%#6a&7˥\]iPfךRsy%tkk GʵU#m<F3DuϸM)I޼դxf~WZ6-mGAU$>2^,?IDA4Ÿ+d3 }$e Rf ICm;U"D{8 7}qJJqaUP?ڻ\K \h!%`ÉΞ^LRH1ۊ@)i҄K~CJgS[o5 >W+gI*/Q kjC1jNqfըebShMG4dQ„汞=y&QegVJyȢb莦1ueLq4W >NK`MXu˯ȱfWV4nmT3e\Is6xxإL z\ݴ2|ӑ!%ZޢB #H T"XUPTK_N|-E7Xgg,*lX Ϛe0E}-tqQ{1r=c _jc^r]Tۋo5nb ٙ\_;ewR eż8)4?iJ駝zztQr {_\<{}5cj;5v 2ϊ6^h0*OnHqt*(%|.qOOXXZQ[ilLwnL֫9yY\mGSq!)r\tC)Itm. ֕T{丅#`Yƨ 5ށcC=X~tw>>weeHJrdZeR M79zI4DKl j]7}A2,i?vZ})w՝޺~<- ioöI d4fP!>3rUI ==Ϳlܔkz1If${jUw3?)s6=(<]!me:4NMҚ'$R ŗuBaeXX+{/g`IPVņ.8IL_I ?v?@.Y51?+\1gr DfrM LWh]҈ 8Zp Hsm\dIARgAd2jDz)!-}LHq (yV,/<-xa0.ړ 6-չ v 4`Ics} f\R. !9k " wV Dm B7gC DH ZrDL'|UVKX/bUPťwG}<CGX+TY.kH21htklJ,jln3uT`bL\Og2k,Ys#d,cx`oCEAr]s) @W7-W%|0tswKxǶ)`/IbO5W4X["s4_yx %a |3}3!q#p,H~ ^@]% 9w@~iY$+{REEcc(JsLγ<'HH Hof$S,8=Ch#Q~Ib"ƂV ]S4<1 EZ LAe}38))ПQ E$2΀ML` ̀sD*P׭sq`8F0mmxx 7D0˜,1ܗj$W~kx#dTSw Дp`<֕~$3̀)1 cn'cU3 Cd}Ǎi)f|{h&r`i/_Կdo(S2Hib,ʒ~8Lz'Λ\qS; |4Mt%-1gpvjSMHL_zPp+TK n Y[ cm.`H}RE#b DޜƬ5Hf0cW=ĐL D1)a2CƋ!1ˑ' OG 0g2U3@%۳R`P9 F4AIqVТYrq]/4I1dAPhxD*}y\ S򉍥DZ(Y Ó2G+G!ǥ)pd*8qƱhAP͇@iQbQ@Bh@'`UxN@E{ (:w*C+&ݣ8.c`SʪVcHfIZIyUⒸQA><m@K4jr*,Z=@VǥYEqW{\{$@\Qcj;Ƿ*@oK*- $?ծd/{kOu[o[hCde*`$^JFxq*ϼ"3rF,21g:2Ld:oa 06RAefsC0YR+C %^vNcwj )dgj6I l/~b#n/%BJHh%.B=EU>ui5=N _=ObiȲO&+K'Pџ6g~JoWW?g فv *U 5J*4`_|}H1 _!{t~Ou{^]_+e&zV.Goxt)yS˔$IPNKCPAV&e*2I#K[ Wqd}.!(hڎ}Y[ ˩HWH jr*\viᡰ߹XS_wj`- @5l?b_m]pQoS3ygQ0j8u3H=GQCi6MSA\])r#./&3t  Ձ 䈕x2@Ew4N!G㢬E$^EÍFL^,Ttёm]xXl)1<!p)2?dxVNQ㷬s4^~W{ϨI dv=#08s3}y?D;4Р7O93w^R Kzݠ6IDPOP 98"YtAY *obHbЍ9IiJ 7YlOl3.:ݲ !.Y@cc-!- O0F:Mp^h1wKBkF/8f}fV޽^eWLohdF>Ynq 3 K<~QL:8nϔ,b!vq/q3Ď| 2؈bݣI ^3C׀NOx xb'j7<|rc&jD킣'jn҈9\4Oz. IυOz.Ol1mHo̼G's ,3|9M W=}jzdgtYRQ]"FksZpEyH:[+] dA̶;4r^e4qM|ȓH#|Uctj[c5ntKQK#-\Z,`{9zgaHsޮ2(6;]Q%7l1; 4 WV1g,`V~<Kz h#8M<<AM<:*dXlٷf:7~C,9ҟBL;֌eGŤ h9r,WrA.u1F%d?8| HOh *ݣHsY]`)M`2uK<4T?IѠi/Ι1Fcֹz͢乮+Qj(bͣqp9 jD1\:@4X,E +r')' +5VC?qw_ UQCSxotQ5hB\Ob^t5Glӷq;qC>h^м`!i1˸\иL>fg44h1[H#d^4u#&DZfc$ɲsu"$j("F_Mf+@Ӿ+*ojKV7`Ũ#ď'' /WֶmgNZ09c/"DUG)hBx#MLqV8BXg#`'("Axmjx'y$#²C|UYA&P#io v+5RY*QTW6_h}e KX%t@mM?2pni}H114&O=Hgґsk?.73#lLׂmzsXou=fw(^Enw*7huLp$ -ݻ͹a3_&e3ZW OFg7 WTBJygV8%<$9: m^l?]F.j[^ ovϩK5[6],,H~Ol2,U~R+e׫]0/a9]&Oavy)MeWF+V5 O2ժ!IҼ٢4QDŽA@иOȟHTRmxbro֣4.v&M}7 sM֛::m.J[:5\WRp-0J:US2S@~pX!AV^8ҔF;@"f\.^ P_uFȋz!W8֊D:GLLqjwiq0)1y1XDК0O@TdqŽ%\G※H`7qԆ@b|$5i7;F ,GO5N2t Zm":'Dɭr_vH(AvGP3^1^R JZ}AAt.ښ1[s>zkǫ|:)ƋxreH$ohv:&4cWJZeiR&_JnV,S7pKB63_6 J[[ȼV{#!0V+.,2fS,mM$ 9 C_rؖ]5ŚU)|gHI 1^4dj58 )3H^[DTG^x|iD$[e`'qmV5+7dǦba𺫡_8\ΛR0]sXy%R;1Pf;wOGQڗg`fOwf p;+P VD 0}sft_,]^6믢dnnp},NJ?.ޮ) P_V?V ncx#sх@K^!η3\A:zitk.);0!{^'5gk|^A 7E{x}vqRwKH@=p5`y]-܇yC ̻盧vxF/ŋ{j 5"Df p=} q.j9m<5~{(dUl *F `<zZъhA-aׯatfrl9tȪ|h)wץ[j% $,g @/W".O)-~N^sZD]\>"rŠMƄGFJΣTojAh|1m4+d!y)+"W+bBݑF>JNC>|4^h&?֒nhcJ'P$"-ܤے u߰oϢgjyISP(|#{wܫPfj$Qg#ͳF piEc\/U*(u]UX c_*f9~ ߟr{_Y>OE 7ZȂv\w9frzӛCO/.8W$YAzpp@iy_Vi-`l Pj.Ft: Q:$?A!Dڬa3ioBnYB"s1p߻ǑSr%IMf86dJW J-=ͻ~U8_(~ =>l^B`YN±F"$}Gy/&RNK{F\t~őW{pV6;zm17Ek:͌nmiК.Y'{\Zh,bK>A~elVp\qA,ǚi-ִyQɘ(iJBkM"Lr/qۘhBsBf$**#aiY^3o/WZ^`9DX:]l/w:*=/7X?12UY.R2!ņ{XHhN*+qbkJ$rhHfe&A!I*MF N^1 "rg" JlՁ1rI # H󿎄(43CQ e4\3{n Y1GZjڬ1P%X))q˳_e 0R;eIʍo!,x&2P&R-.tF?|rݎ6g; jȒ\6p~TW =uU|:Vǧc*yᛂzՏ/uu:eUdp&qTka3(zkZĶybt%Ϯ |?4B9qm߿ <(Y{sˣfj5$ x "…QRL$I < 0(uFAJ 0:ynh{ظ҉ _vTi>H %XXJCq y {(=^r9Zo-y,Vt3B ,\ݮ[Yz;>jMگ+P+1?79^gGR}?ױVcymEm` 婠Ē y Qguv9:tD{&!*ȇSMW5F^HECȓ0 4sw]PVILf~+î[SiuS]$c$o IQ啼(.i2w_n_"K[QH@(l9NRwd`UUoQihYO;pK,/ "_iC=~Xu:$E0`jSdUr977$|K \$Nj9`OFwԈU88ac&D5TϿ^[C2M$G%Efj\`nnLEB${)۝*$$[`]%'>8n2 ʼ.&Gǣ VDY-b)@7\Ut :ːR^Dkّeo3 |(%O}9o~*C"ͮ>R땕Ax%?ěJ-Gis=S@$D/}M_Toә_[V @M5P:Ge2R.;\F?I\r`CGxjb\{5QqA24J^LzaP~h#)<+9 )r0qypܠM_]_WG#p ,r qg|m|G֯&/6!Y(Ϯ폆.q"qs_ED(A6P!d]K|;JO%HM6杦H^"lHr+Bl€v/zǿመ#j\,'\5DRTZr/1.(Ű/JyU.YR\-37W+W"d/WM)h9 -} !򣹓p0[r؛oz4pűC4/XNkHe +6c&߁;Iu+V6pl"2gk_/9kύ;[>y"d 5s杫o@:m]P_%k$bqؕ[U3S nQuhzR;:C$=]wnoG3msI'w!f3캟6L1WΪ,b/irk)"eǟčJ ʭbѭ_=A)3Z7iT(UX) 7I*7Cb&dqD!3ιwQwӗi.#&/n=[/v^F\2je4"D2+c[0Ck\"p"I`ࡶ~rOn7k|vW>?ǩivNU tQHHBs]I@+!4'iAUv˰jhY9o<59 r?HZK.)ð⛁޺L5uv:hN?.ZvÂӺ`/c6*' 52HnXF2^Iܐb3V 2Yg>ikW$qe%tG:|$ \(B WbDaMO'8A [5Sg H &J61y<$+.G"&06mUmXr~u+r9x&(ޓ:s'X6*~8ցH8,{VeA;Ķ`NmH:uIW#V?Be_RVfr}=\gbo8/{L݄dʹ0Mk:* 9,yWA1O,tV͌ޣP{?sl~UWWVCM4h"<){TڊT!(%/09A# H̱8 p1 r^LS*#/|nd9*j^"%*W.`gl5ō^)/ W%Bgy_?Cj7-.ze\A!@wN堔O/Y5$s"J&DW4}p1Mh;kzѫ%=+E=t#%w 7) ,L#Okgy4gے ́[.O{qutE;t=O|7jn'=$̷SP`aFLY<@}x pØ_<_;ĻT5\^ʾ5_<g v)=~7Vdpoyg kD#FU{08ؤAjumA4j'aw^7^'ö2(8+`",ѯU0qXT8DS D37iT9h*H?Ү!L$Q)Et;]۳6TBY72#Ly['Oy8>M UJ[-K1-3qytx`aQT.l;~ ǜpB߈KM+RXj-j%=my_-$oAo\6Յ2~VšlJQ^,F7|Me(}U1f|p9y1/zk誾&Eӓ$[|6BzG)%籚)o~WYt'jIʹ8}(/Fz*,=(xc v6Zu5R),*-</jX l8~G7F1ƻr6N]ik@T" $ iku*An%OKYLz@N^v(vw\F{Bq3\RQ[Tdݜ\-Y(>ML򞎙-?aXg+S$|^X&\ZҠ(Lk E,:^xR}8u~"6Q̄a~q.zl TUF_$x&V#uM~`ˆfWM m@\Hxg:f n~9jh6gSbzoS~*+sOyuם=?>rj/PDpqUP\~ sW\Ji? = z^@R[l N14av^pJY9ܠIxLUn "<ԭZ?3acd/%{˰ً)Ix̱VtWĭƮh1O₣74_X.#HB fW$k ]taO[5]uւL!S~)Dl~b]Pz]˘=HX@z7=àʞGƇ ގ/-5M3 8%ք~ D1 4.,:SN@(Kb?X~h#N~~ :(h*xBC@7GV&O~ϗ!#$x | ɘ,8 鼼1jsFEs?P@`r@!u).B +V>FŒjtoർ*͊&UJ}3"E)yzVy-Bϵ;eQv^ ;Q40QǦ.T cd z>l1wҴ wOW=y]Mѯ}pY ް9=qs<[Pq#!؍c S _5l,3!rO]q/ ZR5;XA,&1}р^mMz=far_M`bqlfFYyX8xcwoXf6IP?)SBoq%'Jkt?"ྭoMA;UE9pRF-ϵ| %XzH1zb6q}woFAYq}&ɼz_5:xx3ER8R [V]\U,!}g^NNkl+c/SNj*`-uyӺc$4zybb{U u'AqUg!T SoKEeC|[w T+0:^t$eENu|e^f`gzgZC&9|3L)$}OT_uG&ik`~ o#xbLiIfaIZ[ " Ȩz۫#ҽ9 Íʦ^PU\OY&Wav1*[P2XGZn[9y,.[]]~h7:l =uUWOqiH6a&o5S3ȫn]OѠXRX>#ʪ2E# WENz Cs6woOBo!Vc:\FM(mT1OMҋbLԺf]?KR&(u{[g^HSJyE?2GHw&oָ:EM1\ zOE|DErHlŊ\b^s(U1hHO(?E#UϝG mѪ 4 ' Wcu0"LBH:Z`/~4o/+dF|_FB[^y;^_g]Xt}N/8Gqkw[7(iUn2c>l ,8?k97&ح*uC^sU#t/*YUA1VpEF,JۨY\'j;]G.ra$QZcN?cectISdrfoUzI,X#6y}sSCPUƭP*W>A7ӊ0Z:)]?cKOnt2^rI$̊O x_+K[3Ay'e~@ykKt3FxQ̛=NKqZ'ٛ7v23[nQF e% $J1>g ˏfʖE+K(CZ!42^R{N^=/{bm2&+qX# I:՜TrV'%|A+"zH{hmLi'$Xܿu?1sD|jc8B~v;@ϋB`Bl뻂l]Q":ʂovf}!j9i xweQ\m~Bw ?L5mkf4dLR))@kN/ת+F4~H\6ׇ68-j3ev%~S{]\5գ콰mQ* h@Ke6T5V? {+i~ko&(1֊9eB* Ɠݴ()NEA^?~]9cf@5s/35>$nYϚf\!q@LYqe1Eg tZ{Xx#X$6gUFLz#Ԟ ĸLz?NETsc09i-29abAV9AwykCxTN#!8'~ ӮOȀ4S*b"zV_ 9NX&MA#*0Uv7ɑ`g4aXŅ!Kq QJdӂq/_rLbYR,nͲ8B =t7do ]( }D gAƊFggl#l'a{["*qo%6QRzCaBSdXO&(ŵaQFq`PXy3*2scRGĿ%DNwP**$ɏ4#[.lÕ$vBr ?$(⃬eH59vDcG9v~ eTSN/3|HV6mrd5a"08$ǎCrr&a\ 9dEPa(!AEާfJ{ڭ~ H0i,!"2YE N(wBr>o4HE@R"[ bnG`pz}Cm#0$ tI] D;Z702Tɡk˝ E k&pY;]*Kd.8sѭSl!C ŧ}(((N1d#(PU! 4C68y"d3vG2#(lEPo-¿z|ɘ: Ęaچs:"9&#)wU 0 džF׻R7X,=ay*+7؎N!Lyǔl#j1Xl&)E)0''ZpPFBz6Ni!a،fݜpP܀"G5Y,uVNVG)1k=g~ˌ!{o-^ozx̀'6 (t p10^wp9s@z܊Ii#w@T?Tn+FL;nbځd)Z *=v?Uӟo?g>#\-wJg]Ϗlj59 (V'i04ͦl;o鍮 ^=-Z}*bmr-.V۰2iz =`;۬Z˹Uv_Ktk?o ;;;}Ln"Uޮf3y<&b5eҲv>\hH󟳥]8w^|9Jj=/vAgz\ X}o{}mzkYV/u}=m~:fr1){MȵM֪m<طVn>Ûl-CZ2nn{yՋMaǗn.bO/;K`t+BD[|]M`mx>~,?jZ>jάިUQISv)zwDAK|B0$1O6C}MМRjc[}4="TfBuf۬+E\15-}fD~ Iҷ(&渍0~ _%$,@ "AZ ۵3G@Cs4G@qIl 7!KWc֊2xc2ۑنϵ"/~-s"EB–ց)W&} IUv?Eu g!9a'Xpțdֺ² G/l(Cf) c@c8JUJº!rMs۠%QjWK^ёچMm W԰6}/BZwo7To*>{o;` N͕b& !\@YNOk 0*Ӱd$Ǒo~SO(F8H.Ar, [ tbD̍J Xe ĸ[$pA^q(6'b,k5\G8(̣7"H\iiM:k{5`UZ 'Mhۅ5jEY "B; ,Öa~8^m灄c~X[zVe`!cew=\/'Z[-9un\K%Wr<Islec?, Ÿ7kFtY} n0W_Gq]ۋσY~"F> RJ詂j]̠Ojt3 =qkf=X}&V|ij?L9ޯڦ  7KqW]@ ;$!8Kh_!uXm22x-6o2e4[d_w{){ڛH3Pυ&j$sQzLX5C~A*~r"Յ/E-Bu;nɫ,"<ϰuH#mTJeN@Y\j&Z fnWy>T/vɲG衢;j/ꭌt6x;SF106e?rRWm/.QڒocۼyLZU^y_tM. 3+pqIklv|F >IEXϮ[z`P\ o{TbWE$௫ 5mBma IyeV"n^l:K*#yeQ J5׭ D&.ic] \:pU3(x8<ԍ<9>r1!3pUefy"MX^py4*pe;f~M{7liSW/^wd'\fowZw"dm_kBMAUR<<MU;\Wu(EU{hUuNAVf$OdpˢQY҈ck;yZW}4;B߬(?-:_%坔RЖC?vL 4$IU (^nTzQ_ɯuu# ΉjS{ءrhdjʍ!EEvGR!pP {)JtIXSBef~N K?h=am`LF5|SVBMox.ø2 b4M*1Da/A[,4++?zE ']z?].-,|]t7kr=.--,bey}ӆĚjDgFhtz'CP dPwѦ+ҰY{ -2j)ܒ.r_nr ͒k>F*у1&}@1㑐m% :-exǰLg^ʪRc*QɌ$͙5(6Wـc($-=FzZ[W]&"E 3x0s?pN~85dJ-a&LQi=Tü7=`d&Te96G6 ׵mGKLFH^r) ᵸ>!=ڣkCyR,6sɾl*XB~$h):+~q$e!U4"]$T_}}^ʑW1Sݷi&6g?];ټ(f4*..i:fsRڭ|(1qFưi'' Į$ yZyh&^XLnB%1Bؾ.)Mhv32}M{\sq-1Ȝ}u7s] &]6IL8Nf2xJt&zAZ?UZß)3$~#l9//?s@96%QM zPk}"&w-T[. L p zPibbCNH%ʵyLAPA_5* `AVOhu3+E-ms((tc|^cX棒{<T_^tn)Wy0 4I %կ$,.!Ykjկ~|'gPj_<Ϸ+\ĉfgr[|0`ue)ֈU=Ʈ%RIE(3Q̳xv^fdI/WӇ{b[ʒQ8&is -ւt⇡FwcdY+Ȁ?5(G|CA8dv" <ws"l?"/([b"weF^}L^O;i{8MN?.Ц =7]\} ǖ2'%YeO)јv2! sLGsL襄Tcz0i=1F0+1Ci?biYw ?V&YUK&>- ,ȶX֧*,}q{/$wIG^? "etPCV.sxG{ ^fOXuy-VJK]7xY-~v dPnf:eNPdӟj@}m,ɎGhU7꩙^)xN~>?G !9e\n_a Δ0jkͿiTBT|6HQ1 7@yƔsA20bK eD1 skZ==(Lall17|3gڐu3~ZSa!y iMQeÝ #1uy5`F[R (-(m"QlԌZ ,JȔrZmm N s6s]H11 .Ćs|v×a:iL#NXn] ;ؔ:Qս~볛WoK1S/kC$-=?rrn[ hMn/ Sh@ /b %1VL鐻5Uy 7PU64xZp 8ζx~ >,<<eH)^LS Zز ; Bm`JKdrtAׄܰG5%Kcxͬb^Zt{r5}V0rn;WramHG$1++ ԅeV{EDƠJmz-BB5V%ߒ^k#bG4{b#6]η{^wb_W޷qm{./Jǭ^fϷқo뭍LH֫uൌJ5Q l,G+gjꫧmz /Q|8B|>η^ MPo3=m&"|6^vZ]on}ow "ayV/]Bob|]nΝ?ͷlnּz>?ğ ,oM%0Fv[y65Ҷn<-;<á -a*22-ޛAzb[ _''Xx1+-95 zM},¯J5x HN(/o&R~NV3Oi+v.f`j}bsQd뚄PIؔ9Sd;ܺ6664I ckdUwM--M{CH嗢sϸ{|\'kٷ=x~~ً×jZ.VXt[|6);[B?ο[xX>kfK:unxӳuেg߶} *T:v^|-ݔSlS2~niϯkH\[.@]큦-&\ {Mi'^ }:Hi)?#А蟐DN l ă7pȷӦ:8p"M/@pDA=eʩ#^=U\ߧ&i-z^mD1 4iO#i9 $[y?lH!V~?ɻo?x F MsX?;$|2-W:A) Z4]ݙҰ2#R{\1UXm^N $b\(g`NºHw{d,xܵDp6%ZȌ7)VI6asnc 1B/W)q A^\5Ncd; n7]y6&F{vɄNG~INLkA"NAj^}5 #yʁHP BTߢe &~.Y=yõfc50E&&ܡgi͊aՋ |}y]t.l"T[0+?V`9ă7_q0^re(\·FmSTh¶,>$2>>">9t{UaE8p@Z9rh]QoQCћ>U<~G d/Qf9m!>(#q"d@ct a}G._'}Jg4]=Лileio,{apG>H!Y9!lKUB7HHm)T[-UUGJt)`GW >k֎ ﳢˌJO 754`MxyQhXx^]TyTw~{\Ϯot ui}Vn^Ӓ-!:ԯD2Rp1n?1ג;>s4 13Sol<o5DIU`'X[bI8Cv /5cd8G 9%3;G{ cXjE]t}bQlj &E]V iD)x)9"Imt6{:+YME+[X'&Y ԍ.4vVqL^mQ+ O^C;dXur~`TDiJ("s_kŎqe>J xa48( x AqBX~~Q<9ӷ(iB⻟-";#"pZ4^^&Q1D|P#v@qj ,5 &z[oZ6PnI|گ >I̭W:s6|di9$=K Im`i Oqv1c",= (LIY'yFf` ~xijHZ^A. MM̬LᖮGF/.(_K=2hJ,I@`*CkL.(B,b0$yg1! xy&ʀWU>=eX$ ڪ/;v.(Lͨ{ hZrasC.*k"kmqDIĝےWϛVUt]/ӥ5 0z?fi9 '@0[/+S:׀†n9}C ܩt '@Wu'LXiد[G RI׶zXUi1a;Y~m,v W۰ 밐>B]po5Uu`nn *|ܠ6ꑳ!{z۲7j rh^C0LlpNEȫ3ƟD*|r2 u#ʔd~M̾yhD\$uo#1hg]U{y>NC]YQ  ]nV.t]Gh Hjn֏0%T*"F\28t,[…zʯhrl[6pD݈h̖XтjcFcaSXx6m&/P, a$>#*`v&'aVE׌ CQKj6|rRU+s(NxAN43i0Wc;mؿԇM 4ϾX M0d焼Eݪ6''6. 1>bc@H ѣΦ\{ 0ZnxNNEGw(j߳v $" }V.-9}el-)2BKQyOkk[)G ݠ;<1(=F"Ş'MuP`@)v5cMhl 302o<5_\nĥv*ti)g?#bf.\TgipFT8;# <daIO T˨~ISÝP( ͽ QPʄB[FxQHc ɩq bXC>8BZ@w`"iho^qm@@#፱<ᝬAXmT.D[$0SxJ0ZPY@|Iޙ&PX#{Xt>LLĉϘcJ=WA5Y4 se^trB̸_ e&deuqtJJ Q"t US`j,sNLAjMDWW/y<'pU2[1b+p|~Rj(Gh3& <Js~A9&VV#=xi5^KB>_;ADf7+BE*NZ DzTS _H! 뾌E /h&(/+ t^; "Ikd&>+H]@3?qZIE14HKA6K^F%)8-sђk6$\7Xkmɉ|d~kyIJԈ϶~>Q掌RyZ--m>cJZMW2JG\d޵AmdIϼ{O%.:"6,dn8Ӽ]LoPz:C׳n@-`6ϳҞ8'{iCMk5cv'H=Ze/^'Cb-xMզuPv?R^b%5㎄tN§7qED8*A\E^RRgukTØvTcmbM=-,pW q/VDyl|ōjZ9RI=SibZXFA$7H!1d`xiaV8jQ8MN.<঳uI 'v784PkAya FZqK ;6M=ym,> Gthgԑx]Ҍ誕 8,&+˸fD-I5niX^BNĞ`A!X٩yf}fN/ԇ~,_%n5!^D)"D_|6BxtTNu 5Ѐ}Ѵ`_&{|Ӳ8w: uCV,2O=_r&ct⍶k {*c$?6$Éqg0eĔqKU8)5i uLHe̙:dDs6N}P +V|)LN!bJ<:F!Ff0n-Vl/0eM;(0-q(8KڵK[XC6+XLM՚iuc#5|EБZJNAE-~ۇ_Br[3(E/,S|e k29+:l917_e' <(r&l(T_~wl=*] _I^HdA<"o~>1s? /X[RE!u?)Ҡ~yzGZJt^W侦j(.HޅOjO8.|cqH2N_pG>xGh< ߈XEkx>>1a&Y;bkgCQunn_ M$Fq, e~R`蜼\L\W8QҸ"TC+x-(-+|_va=Dم'kNꞥ\3?ŚFvio6 q\T*Gtִp6 )xRh%qȅ;^Û{{9 _!]mmW!P]-2pnIz[,*ڭд Wa^t^ND_@&'c.P}x/uKK,J0ُ@oo1[|Kr0|彑j%0q43)PQH N,:U[41z&6$-L#'?#%Pnh $J^>X%w |L)䑮,ֻMo(9:H@ļ3ͼE@)ghЏPH 'u' xQzP7?Gw/Ժ(~Jug}yFzhm\(th )uQ7ݹ5f8c8? n7a )G US^y7Y:uK8:ݭSf[%͠A&λK7x_XK ۃ{xppt~pWK@ۉ{! ϐzBh{:f23IL?nC7*=^ _:Yb=OZ8)lz5?BRN%J ~|u~B5 J,B7vaIP*IAp+=ڃcz*c!<*5YlR¦wpoMEe1!w"#_ps%`#T6J%`#UZAڷRcڵElHKv-8X9fg_%3 c +|\^2rgbkt+M7xLSuT>%ť>q8+p AR^08fl{$xoX>)i1Y>8Jr9#}?.b)+&O] 4mՀipwMw{exe :V-%szo $yC!/jPloE'E2h^ nP-U 1]d"SD|aⰔp`ʊU%wN9x8GV4ܠMy*Z8SL&̞>UHR*QOoIFY/v*Xz-x]90 '_P>~Sr}n׵0!OsDrKNOf |_'jtfz]eQ "?'ENūugT?}JQfb6p36×{唎R[ZOi0ٕ4|+ƿUs5)>pfgOlOZwOΐKA^IrѸLUf\0K^FT=zse]nG-4j#X Nn cJBGNkq%Es7nE3n6r#WGuzKkޒh+)QiV:.v+4%=>5j-h2O:ko0)պhϻppp3C0Y)T~H I^*'I/МDq.3GM^ek9kFd$E!I ih*FX :F2CS.O+kRejS`CnI@O)/Slw#^ܩ7ھUޤ&kJaIe7b ul7"7ɳeZ1HqW\qzh6&F,i`ގ-\/3$4KMU5!dcmRFn+gC%6q!Ch'$<7KoJJa o@9r^O-UWI ":ez }j]BomU\i}bv1QI8ShvM>\4)YI2YTs- N?}HMXU@Ȯ*owm풓(@@8  f%`M xUȑT'6}kch‡Vw3ܓ&973tGz%|PmT[ \nR|T6Mo䦞$i>UcVا+iŊ}Rb+.)ZxTElAͳ@9wմ]Z I`3S?E}=8 &I{[Rgzf6Pi{嶑l]O㏪J}"vnRTo h %W?~?הH*ȵL$rXr 0o0 d\{.xZƓ\^+-uGqˣ{~7DG[n4ZNgIY@rE2vYK As=7bjIǙI|R%d5@Ʌ}y_A乭@)wF>64$bO㐌E8jB&g8p*=Icŧ,Byi3ջtiazLwŪ/yUg3eM\0\(oE&;;y@" &f'6/X]d^ {xk?vOh80_~a;ββΒ gYg)YvY}7@y0WG? !XYQ 7JR.JY**M"G&{IŒrN~l'2[Gz뻽6IPf9em=G.}}!?~1%ea:0Oᅧ/&U!`L>vwWO>==?Glg\$|C%ep߮/]^|=pu9vs{p};zruwYn?] ??gMOφ78>3/N:U#nt<t}5 9ܶZWgC&wN&Wdӡ:ۛauvqrQabr1=L3]`6\8 G埾?N{3ozp7 {K^.~8'7!,U}K% \Ml|/&9>vz8nn'ۛL&3'Cwקgä=& PO|o'|R)8P6@PۏC 93LnAR 2]*1p_(9!CR#!JRC'+%vzn=hxl:wp?J&c.wjg; |'a*tų/C:bhhhhU@)3T'A:pb&acpalo T5>elWq}x:b0n1ӳN{PFX ÀAtXӮKvC"ʢC4?osΞ P2kC#-62e;)YdsHbY8XpXBqp܌h21;?jus1}d{KH,~&U((cV&'4ڢb*(bŒGqb'8 H EN˚)}dn`9TrQD.y{ ,Uj r:6JyyLPQ*gH6 4/Xdq +DBEᯗg>Se O~*j5 >a@ Lt 5F`,p #k0f) @U7vߦ Me,NȨg{:4 |wF~>KJkRi*{)i^ZA{!w@PFhG qc׃Qs*TNMhM|-[ dhp9|s_N2G$`0AF@[!V/WRح'Z9 oA/| OL5؆݅@َY6N)`.$ (X$:q"PCi-iʫc||52 @1 hTfޥ"l@z`n4p Eukj3 fǴ"7RB,:hK'I2&JEM*DCKB іq~|Z{1Rd_.pjz|Q(܁:n]; u40ڟ86O!\X7n[R )MS<ڪjxנ$SK0*‚Ϲ٥wam+n[46BNLTed8  P"XٗZ̽ n.JGahxL wS=H響7tJoݵ^s=|f-Z+CуR%y-v|pfRⳇC=eX E[<𘙐 K5ָvAaL  L37Ls%(-a5c 2^*@$*N6TmdN+A%tIHXU.# 5`>f:BA!!*VJ ko'[Ue ah+ o@SfLkغ"iT5vv=qaEcf?QȒMYeku.asٗs5~]c~Wy>g Du`LW)h^ ((AXUoO6'F$R+ /ߩ~@kZc4Jv!=2K EuRRw۪/iTD1 lp)@gvnZnjzy~b]lƴz09O4jtEd[y1 /T^93˸66xJLVp9m;u>|N5UX[a֙]My&nG:'(PBV? b;kVv!!(J^(\Aui#UJxiJoqc=N"McIf-l殐 tuvVx!LKEk&0\3\|P\krM1|̓)c`:zL%R%8 )bJ)oJ:U7$:4K=% fjx|&1'$I|`T4JfO1; ,2 Je 0f EbNߙGUXn;]o w`fiS̷X(\w)M(?DTꭧ:Z[pCtmOaiAN ~#` \4ۆ*V@QǏ^2vT-j-1ca34 $`3k2mח6-"D UlX?2:Yr!zKX~LMcvKB$G#ѭڣF-E* awu%./%ё\?h5\FGkx`_'HX_yɢ*Mb+pqpI.=.q}vIQ ʩAfG7-TZLfD*KSMh|`mtc`n":MOo(X~(T.m)P)1U-~Ug%!fX'w`De@Yхk'6Elsݾ0}Vʈ<^FbE>TKT;:Y#DۣLsfQ.벺J;1;`h˕@ 4b`U3$8p+ ǐ\ ֩ƽg2Xǒ䡐bTq( .3.UdO5V?> /Ly5lZQC'0iJjaVf-@gXlvPcCO &ljXeVk{m ҷ @?=)[ nb:6OzIP{C`\1F (`=efUhU[LZm$n7b'#s3\؁Vr <4Zfi2;d*j48cwr'?x;holWSS+Vn]>|e V"yߺ/vK`Fxm.Å[j"#1ԸEF2VߥBPB0^T^Hӊ TƽȆZbʈ[i, vus6Nhwp^T$LuA5``(R|j4eM գZXHm,ؓ7zͫ_A2gC}#6ۧ3沕'}AшX8;-.0{لstAojꨓС@ 0 -c!OG72e$ ?G+Ұ?d}cC %.7 @j,fX$Xlz64l"u:/ gqP A_ R=oa7>Ƃ%&Z's} !`p }&54mD;Аx|o<{k0ۛqHM#*fSܣب t>FkT 97ɣ{Bo[G_P.<#tܶjD|b@-|xj]͓ p ohV\l?l}%}}}}V3 퐊o|ם:wmDꚭCLT ͝lmpOHi  sۮ#n#'e}6Zz9J3}bd/QC+TˋCj~'tͷRC Ə}",KJmBCQ`X~ɷ1 oWñ:&̣ iنnv~12'ܲbk*nd7O)^0uFi;Piaq jX[F~շeH_;0D[hԮ"{dU{MP`ۆ֤}B"Ǘ1  eٚ`O|0rǼT\k5N49v4*^sCTCogQ&s3>ҵSb-|W@7p%(puȶfY $`Q{sX[#kԨ=i[!)=CYcjkY"ב0>f,ڦQbQ< Fj"{k@m'RnJ$ӵib32 sL:*q(I>_o;Z  J.Lͼ6Mln{[@(ci 5<8E!;yγY4IvDAڍw0ZQ.i,b*pdVdkolll_bŰM &3fj[ _H}x):Hl0:P௃Xcbvk~qjxC\eb/ @@qunWRJd*}Tg\-L%ǴZٳ' n? 3&{gxMX&sb͖+5p)KldoR{3Ț Þ5-ͷ)L(n,Wn,gY5ix9)(c3-6y.@;GSFkQņAGb+YorXA $)QHS9~lY%]"t"H uZWPf%S+FD=!o_ˢ&7o?l>0Y 0j.#%x\FAyn74}6$ ;8wno*A WtAH;Ա ֊ɉ HC<%)l<Ċ j@NO'vbyJug]# a[Z'Usu}{حgYG0~*Q-uIuKj DW\F{&9n (w߻|'z0,}ǩ tZfr@bmZz=5`_-F伾p>SL#o Vx{<1~hBo> BcxhCut%S6*W! V#x8mNַ(I;.@ӷFcKTۧeRBA[ PxіGXN%Ln2j'H7!i0&ؒ?2@l%ng>oꋪ `C:alNtK#s\⠣x&4>T/(qffi6hŒQI]Fj CcmgElF+2ۢdjs~tAJEk:ǻ5\R,͉1rAv xW` `2ČIFWgSO7Iat풏- d 1fUCko>i7  i0#$Bk2#m%:j&Is [ZB9fc T,2Ȏi"i 4SEzEәa6_32䀓n0uPڦ Ҷzmvb'̏ڿb7{h_cR󋩩.8^>ܪlv9Zrh8Cs}01*q2lc"eB+mlݼ&|_Z x29d,}pZ<]\ЊH]Z&dMaZpȂCjP N/N𶬱AJ'Ni6Y0 0"c;SYwIm0 _{LtRopE O= *=&#bTDz:9,礏^CMÿ*1鯚_! Ժu3 jѼ~l%i&Ak _ ~cKFhAl3cx)όyh@j4BB6^;m F+bHE!x{363i.hl mB}mVI P' #:aұThƀܷ)Qt ya84X+8EqN 0,^/&zxq[nmrgX,hF[ c<͆M|y͇q }0.4rz|DKHm*-N6!$mnO+;i9\.C_XÆM51eʤZ &nXztv~c'x,X`/ _'e8pzhf*p >o8G-OIG˸,vG(YQdGhnZXrb+|dÝ8|#V;ذ %s#:JBE#}i.Abn4?utd -B<[ռN;%5iX_`ai?Ga /"dL_z@**Ss5JE$}{De~`,O~\0(_şJ!B8!p[ N!u6qHQ V&>_(0Vk4~ߧJ׸ʊroQ34HC$U\DU z: PT8kx zҧ':Qs [q/NG u3j\[=3mف=&yWvb/t*xIߩӻej .M"bI&5LBlܖ^VگjWcxC'f*pk3~p}(E09QH3u>&$(7E3@MoU4f*hY0])+(<8K% HyQɥ[UDu2 E7Z0Rbk6᫲J]jNh>L&wTqքr,]*ysml0R<54jjep˼ |^|nbӌ7 HV%>lXԎ8Z%٬<)u4$BMʡRZ`X<+aMshQԳflѯȍɵek2$ &w/X GqǼF*S/ + 8-jUZӂ긙څᇮ{RL)\efp 1_Y^W}$D|D5oTuW : Eq k4ڟx &ɖF$OO<0MpO']K2"&ub [YAN.bx= Rui$4B)P2 Eڽ4!X<^^ ;6qB(YDvD"K*}I=ux[-8`-e1gq\""*;(92؎"GtmB+?Y2;o+m @j{Nz/;jQDrfv!þS1m"béF23Es 'r +/cNҽ(9af W|cA9Hl X@aXEr *B)j)y5X |R^?CKCUuTЩql|ĵ>oi^sJ 9$quV+H[}4mER)]S \Ծ/03%WUL||tB; R*PNCXG2AgJd[28[yNl׺f/e*9zӢ1܇LqĠ[a c;{ cUco! WLag1'^P#*7RU tP4 n|ڲxѸ*EsL+#.J3P} WWWgJܩ͆Z[$FAI_Y8jgC+S2nF= @&%L=dta Mc3* rTܳd|+z";q3*wf4_[@e(*ood*(p˶*s{V/cOTCwؓUeΫ'<ڢ g>(B+g Ք3%ȞM[ LincqFKl #ݝ}*0v34h, Jص&kB ٢gJjhF´vUJ+  {6#ZZ\Bxtv{?v/[:WpF~a*RevmN6Rf`0ԔO-+aC# iE Y 5f%mųxvrB(=eJOFR- |`& C5H-axB"`9,1j=Z$6U'J5 v]+sXp!L&N[jr&nL8x|Q$jE$rGzt|M.t>ge59z.0 ݼ3%*<1jVbs@!n|]NBMkD)#(J2xpr"BDӭB CaC%A ? qۘN7L/8+¹]#<@/1j{l_k5biђdž탋L[RnhJ]I2yS?2 4S- Վ2e>^5Qwd[Ŝ9C=8gzDs0+V|T3#zuVJ{\0G`IM[K m.XX@#cW,xVaFk "y՞y bOZqAZUGF[&J?t{O,sZ ZƎq 'oԪH%֮4]~VUqQ lX~TJ .V R :&fO|x^lT׬ *[yj^p"07I 8(ivr0HZM.d`M[Ath(d:fORi=Ob## tblt\3[C)UZ3 7W7P`QbA*M`աvv*14y̲$ isDl*c4BFE/N8ѨV)iEyv+lPK HZҏ ^V(>}g]:ۣ>"Y^EG.!& _'Y5, ^ *b4c=(b5R% `}qNٮOÅ6Sa77=PgLLЙ҃ob}Xǔ2E~7d~ yO@oB@,JpJ 'їX *1>[B*Q-Ⱥωn˚N)̲t!L x`~s̮`:߿Ne׍F{lDᙈ Tײu-٨kI&]9]ebpN[CRK}|@_<vA6h7ثWr nȃ:aC\f~36Rᱜ`ۚU`Qr(U s6{Y1Hc"^¾3^zW%KZA9azHHLC˕@nT`v)Z(qggVT ^  E(+mXȸ!sɅG:n)(PG?PHCjhBݬe%x~P͓naUp `/8:Y2Y»n)9 \ 8=gQd >X<-5B~rPA? Nlt ɼ# Ѵ64Sn>wdJp7 Bj5 ReZmI0 b΀74Hׁt٨u]@`ǽD Lܡ@HS_]C|/4v;$~.fI29Ah/AdX={r&׼Ji`hmjY›J[:s )<uw2- &Ro?l]"8."D+ٷy먨UW>'jejGԅfh Ydk˰VT TX¸D2'N$ܢ9,=^k a<©E涅ը)t{]Y̓Ad $YiEw P\كpcŎ#΃΃Udl\pL)aYN4J'a"Z)m-隶x8r`=U>3loX$]E1^<[(+W Gۦ:TF,i%|iXwp~C Fk6؊܃Dg&~d("dVP]8No#e)yZQHm`.e_9F9 d;M^s _&"SP_:ز\#"~bлRxZN^!c_vRFb6ࡰs۳ܬ5[斳h}OEШvJyXzs1]YX/U6bڪnjExt)cj0j E` x RtŞUꗭ@Gl͒U̕Y(X",+^~m,`HyT{Yzl, lU#0mSs\4嬧'1C>,E6X9f} r\ΑϏ(HAghELdQIZ}&ի04ӏ{%]V%F&.F.I[팊W3*GϱJǨdVR 0ejT15mDcPJ={I,7"*aL"+>sڡ0olJã VBi볖hCYrS`fJio(P; 2ku/aPH7CƕV\dP\iQE0QU,zx8 *b m/<7ڌ#p`Y⭏~ju\~kgدܣVZm;6gp*1Qj[d/-ǩHEt!< N^}oA@ ]h, 2RR;x|-vr߷D2^RN9HDTRwihBTEց#'e˨O6JYÍae뼔SYZ\~[۱o&e@]%̝C4>6WvÑoNUw H͍zN ,\E*Kff^qLAup*[n JJ6Z0u*J-f:]+C++ ,͚jU%J2~-[9j]nD (e)E-lijțT\$VV&* (ZuUXƩZ,]hZ2T_UJmckLИQ>ӌf .jJ$K+\$QiJ%3rE±X,:}%`5U)AHCS3%:k{Dc<bA7iz&IMNh yR v9a/JjMQ]me"cX;t@r8X ~M !&Qb]iW_8'UGrPrbD×4^e]"nYG}'da1 3 /q#*JN)Rv +U.Y &$C<<\k3g9wNL{u.4|5%ٕL^"V ̳Wt[! s?zᨈۥ6ylII jjTPmZQW~s-. N#qSaTױp&_-TcyQ*a7Z(H A#?┐X&M W<&-{`N*acϽ]WO Lu+~.2rzV1Ԉӻ*+T訁I4Yn0mcGaҴ:&qYL|zmpQ+'P#4͠ *FgA\DSEy~—ŨIJDTHCKu?Cn}pyEq$T,!pR.5/KkRaZU}I"\DB \Wwq 9 5,Z^n"H*:/&v@s0*#ёr k*aB$"0dDA։z/NLГE-M֒&KEQʁ! :#t9f9,,qcaXh=1Էm71=bti0Hmɳ ::)A%yNFd {TZ0̒v Ps9g9&rR˚۞b%:{>ofzm] oyhsW"6s%Qd0j߲a߹.̪q\m+AdП!FU BmeC^7,5FX@wc6w}B0Ho" ,Rk30ߵBmOsjIT ~pvMT!4g 7N9K V4M#NzV )[tur,Da6p'%qk}'K-^4L伫MR`1:h twhxM4FU%2W]{l@_ a`$c68A B}6µI(ڲDq` bGUW @|e̽ F7`4p @~9j#5qBPT#Z!&̵sd6yR),>. 56}#Hh(^;&O-D&NfC)`$2s'#+!MעiQOj;-u|G^7e&~y}g{9 YOJ:NFAK'mRAӤtq2Ag6bʹaVcCyDHmy;f_0R(hly!I8E,OՠkIhOOTT)IupQ \pށ]C;QxVXqępt Wo=x1uX!~ <؇bj%0>lDu4َĠߒ.B4#qQjkf L25sW|G$أJ0Z,)<U K!l5] @T'%#w(a[AK1ZB2{-()BVĞu-a\7wZbr3g"wMW0L@?P~ݍEҐH؊zqcm9gC`săly~A X˺o!1t5A ߖv*ؘ{jg#FWHUKx 3' !L3D?}'`W*M .M]bsh49{ {4yH0 !U;"8=աjf5}wKecb+}?j1,3p'Դbc{8mm`izy6! в[l__UG,=qP_b> [$K7?`#e@9Q4 P}-bV3}PQ@$VZ;hSaAW$'QIrM٥XFTݡE:cEQS,1"} P# 0Xb=!ԗ+ܘC:ײl^1ƿڙ1ο\J[;C楈J*k_}~.xPOe3P0UdXFټ* J&}Ykh+'S+k 45Cz5_SmiXvIV1AAZ6xL#t&&ĀK!v|+*C=%eJ(&!FىWl l,C;\!<^`Ue7"Ú+0pk(p`G4Fp|a7jsLpE",K.cz PLE?w`@ebu|h1x3,'RɁ X=N-97zo( !mkQj5G@7-t`u? `_ꖜd緘,K'aP׍n1v5h}unpב}Qq ȜҬs``̺q;Úm]{)hC~9˗j 6(Pky5ZD&hP&LNEdz ; \R[!JkZji3/0awa-w?`݀cobۿ!w_;|$f؞b@mV|]7G BMi)mD@xSj;,/챞 浢݁5\7;! p5x h-1/ o6jC닳|cJcKf9di`z>\Jazttxv4; (cw %kC5m!P82`q}ĘJRj70 8@1*WS~akc>h]|Di]snvU>SF]H E뎅DŔo5C5Q17?m;uwq(·5M|^Ɛ䕌xIo֙'/߿l!Egz֖C_RJgpSV3;6"r=D[·xnBakQ [;nO=Y))71e=r~BBD⅙H~ SǦt $KHQ51p SoR!w4*ʷ&Rլ"1Io85tDc{#1~T״Ư10W3uz{|M`Ͻ@8NS3'_"VwA7eY`{7D5bC%%{~|;hlg%db}ë>u >Qa)8GN'}'ɯE8h*Mxo?6~PlL&ky]a"(FIA^?Br'}Nh6y/ -M_7E_/cS6t: Oh̍6'ոܱ %DyL2/}ѯ;"t Lɑ/XN`;+vm>&.%g)>lwKtV~n`J;Ai{ڣN*0na2&w]>s, VtV =퍰rT] 1-پЙm}w{|m`nloNB /$iPZ7/诊nɑ%r?} u@H&jA-Ե]'hG<Igr#4o|- ݀ϘRզjRɊ*/(/holBJ5 %r} 򈅛q`w1+FtIRڒSjkX}tM^4Jeo,oTgV3y;d#F3[D>>>&{䄪IIL7yf"~g>rkLϐ@YCQ)6fӒŵ&v-7C3ڤӤuFՂbv5yA(+ߣ1~OP]Gs+];W$?.DΪ7:BV>̊{].F`?Wk\@K W3XM`m)+ZվiJu0*` n?Mn$/; OfkSKI;pz|dNE/41!JCrllߩkVhd1?x, ycSn4^:_m~¥e?|pwk˫7SD5??s ,@v|' &o~@{'`1Z6U찀* g RDqRX!x"j2!KЪr!M bX:4X(yFN^(ShC\E 3@m`F@/67EfNTB11XS*8z>tu o Wtͷ cH pZ(k -=6N~ LiċLj4J Ռ(zBm0d(3~ D-Q]_6<9㕾MI% >4|ևDf|ۋ:Km}b5m66Sݭo l ?dS駾H?XXe Wp%"Fi(U[B8!1r|NSNFǠ!/vDſ+_lZۦ¨5j؆g Ɵ#E 7jECe'5URhH7SmK% (Yam <T?@z2vh80"4cpu(,<*/ n}{o,!DZ[kAQվSUUTg5+$;mn(* 9ʽN|JŮx 886-_AݖS6+VKZ8:6nKcUX6W_WW{8}MmXci\'b>+ 6h~q"o`H +,U}(_'5Zp_#m,(HSI]uQWUHpDI.7wgkȭg9FR@„P!AS<*D05Fjn4D2:h ͬ BgΛ!Hmv,>cA{0! 3cN~:r1Np d>H 0Z kStDHy0RWt8} CXY##1TJtV=͘o ԫ. uHLըpv-Cao:y%爫 瀸Ȭps~Aw^d'',I~bz+#1e#y)Z q L5AW.*&ib 8kUs?{^6-q*㿁'9%)4yI`Ij#&*9 adK hV%j,гœeXE?znu.Kʥ1gϋZ$4KΏkۼG 2"p՝0:!?pFKs0ʓyթga_!Ŧ:Pj@^-5oQg `RՐqP 'ͮ޺G0ū9i j6ȴFAQ?1T!a)LMk>&_ JFPj,mLLOK+Tt$ޙ*W5ԅ1[63CL+ẇuǨ@Ȣz 3 {vUS7vI;a$xG"P Fi-׊ƨ$`|r*~B+⥩z;ڏ apCpڅS@-o>Zcchټ5& Rt` nӅAbmaHL ]> X0LO>\ Cǜ>Ċi}MVagvp!wpRsLj[6[.Lo`=8S }0ܣ$5*T}qp2|=1'v]ֵieu+(7gk #'y'&&ځK;L\I;~>%D8J{OB~O:e`昗MZh98ZAh0PPJd8'4a #_'Y2Hd=z5WN02z*m6nmG*$'>GÜ;|`zƋ0Y{"4%_EK7I4Ek3{tkD+@5"ȍLx'%жCɞH5OJX$>0N6jB{S Y0V&@HIi1GH @hrNaA0 N8BW:?#o|G͇ c6' }2?R:d&OQ&֜'I91ƄWLD^^(b_4ּM$VC e nsu 0hȄ9$k=ֆ4zA`vކb'x șok_AulMpG찳hY6(ֈa@kI8ktġPFN׌|&Yn wk;a `bM-uB `& i lGNv:` a}Qr.'H)uЗ ', _iMV'͏\6\|"?r&GNyg'TmقL: 37_M8<Zk]T͚ o&psW6)ڮ9n WlZN\RjDh5CFS~D/dI$Ⲇ F_Vf!uSz1Fy ֱΈ11'37K&5VuZUzW/[$ j, \%: ki?$^kU[d\ٖ!k è<*x+59O_q7FJIi5qj:Iݭ9n(S:V'Q))wͩa69:2&'ϟ+L$+@-3uT)Ik^ du0x\;. iTSw`PWbp/B(xde+ et!i16kqFD_ -RͯBY7? 6C(Lur|1=WĤb.*Jyjȳ5MQ3Qe(ةFKP|K{H)_|iH;gFSC8U4fbwG|/Y7Az-HfK Ϩ%Ŋ>jRvAk׭Eg8Ϝ#(ΰ=K] sŀEҍ S;ƜM 2hȁvVuBkڼn" #$jOmmb`t t,lP%kw`@!/MAFY$J;W)HAѼ:DH^Mxz,N#3bE.$T'"18uxeVkwЅoM F-LƖ땜;cy*0wmGDIᾤV FR)0EBٴhHynb*t t -1(|qƼ.l}QN|_7]ٕy((MoևV6%:4"D@mP^w+Nn`bn=`oؤ=6",IQ*(P/ QnPrabQTF14SjS|R*0eyfʳgqtXK ##|)hkC͕.⻺+!:{uEXr+-u5w\ vH`H:uشwq5fmyvkhrB#%0ra\SªT an6+)0(ĭq:1]WX_F/uɉdY`|{olcWNPsi{:qq?r`Cg] rM 4\ɃG9aGyt/ƤVn)Wޱ0ȶar Zv`TG`: [yڪP42̢\H83*b6ioa_mhV'A6hןϖ:_ܺ+}Uɧ#-mTNMV| l3RT*J,:P8=C~XOu؉?Mhۏt5G!e8Q"@1Tb!tmJăK,MGp}3V>"7ļ }(uu08P]Qɕ5 B$_}g >%7-<z7B<$)ܭwxR|XDó{lJ&;ͼ6{ovVfQ} <2x ʍcJz΄ ƹx&9Iv؄j8@ tˌ*%1LKIɑգ' J9fjʂ_=y,4K,fEMR^"{=U){ojmO^R&TC0afsG`oF~׬'b8dVV;Y͉u?*gH<fiVqEFt%Y#{u |ٵF(LU?vVafbD2HIorܬӂvشhx,џ^1a5^GsA}Nn q+8_W/~<2+dn3ΐah=d nW,& p(ѧëzcΒGhĮ+WE3?)e:0Z 7t[+jmiSCN^""R&DOgU*H9݁ ,J@W Hs03C߁OBQPx;TjƕJK"|m9 H(*P^v񈴜׌`'gwy:GC'*kD$ <{y|vKWv'Ր4>v4qM8+a`52۠c _!bu-?ݜpxnyay@&@l /m= _O|m|f[ȷ < &@^ͺ cFgxtMx/_LKn2_tpn՟~I$=Cbc;M6[6yٻz>=_HBkz״_Cm9y :RL^,mBn#cVO&`ŋ1,$(&-7 Q3.5v,}6/[F?Rha10wǿΆ\F'K bDmh[nh\s!5:WFQsF]4(JnE)m(-ȅ#]nptL)%i4а;C/ansible-lint-5.4.0/docs/python3-3.6.2.inv000066400000000000000000002770111420171260700176270ustar00rootroot00000000000000# Sphinx inventory version 2 # Project: Python # Version: 3.6 # The remainder of this file is compressed using zlib. xڴYw8{~Eum:ﻓe9B̺HHb"!AZݕϋy6fyb~IxsyqܯU A# L lXa!\mi޸O?f'Pa8-(o$aZn_4ctl&Tty9D~ܣwv*tכ-=$r|YA!R{lkK̽^|ոyң#}Y uOH*-5ӄNC$3d%yQmӫM؋"=0 覊0R,?(4ɇ~lVWC1eމ4q jSW)&"2Q*_;Hg7cEx'HpsQEXO ]+4{ZZ̈́1k "V4 0٫CsmCT }1TFR"/â̍8u&Mn0=֐nE&)'>-RT!~ֺVVr_i!-@<+ri5υsrx<\MTZ->;^ٱEf4I]v(ݚhI!ipmT-($[PH4͗%!{ ҭ%e5.9ȔFsV|]h] _xBiCt,&B=Hb)؛ y8,$" s*;I^ה 4 ی䒱2Sq =ƼFTh)9o(HaoىzEs ء`!!ޠW/fQK)S- jiۇ(Ԇ :gws08W4E=AڞG>gY`6h!cGu {R]|W!\8cDnfq1۬ZexȬws|TIOz x&FSI`¢*4俑 ^sD}>ѫ*_wdUU "0L٤IA@*REq x(DL㬷Vqx15̜jk#'YV Å(]xnraYܛ˘J(ɡRL| 8 $8eQ30,8Q>'obPARWqH#W22+BZ=2B"sߴdp.\>gIRR! jpGړv:4p-)I>W6> YS[hͷGR' lPL4B"#8V2~o+{%أ:4 Db'2iD3S;Psī( +l¿ ~9FNfk3L [X?246

ټ,]ӣ%.rb9?p<5Ea)hO\P6isiNkc[E evk9MUk42Vny8fdd$-3 5l4Q+ 8*%dx"IrRNgo/̙b~b'1cxGRERnAYղAbV#҅U{~JYAtL@4OUѿE"8+_[3TUp1˾- :f[` .Oz>ROV+˩fr57n ^t\G=*_5ݣv!Oh5 zƽ1|=rC?rLėzT7h@cksou'bAb>g&IBY44Pa 72آ3[xn '餠9ָdPOm'ZW]fZ!ao`塏2+p8. ?Y=917^ٗ-0 82Z0J(s26uuJ ZtYRNwJ`¶d@ij>~=3hA8r[A|>Iko zyODԚ2),@jx=7D7T.Eh#^RC$KSR~c(GzVp6l& <[#)Whk1LMV!TAV3\eA*[m0=4` p}TG,? -S X #Hő~>aMarK9[e$jSw*m虽R():#Z\.V!Il@;+>)&ݺ?9՞I1ebW=*-"pڞ2ut3\6M jf `ȧP\,JlG>}K=`b A`v@AFVf5Jȗ9.V--TWL>X̦Za?:SoS}ݍwWPb5wt[nT6 ;a,flhf$ dx!0ey#>ƫ?`&m1iED44έpe(&" 2. PcapZo3X2萄',K#@ 2"cjV8U/zAj͙88/Ey 9&4˰06WOթI8WG $mS\ [a n]goe*#Y]Y &}\@5plp2e"8u3z9-&LUTpa:"~-Hi)N[|TQ-~]SEFU7:cdvَ'_*Zb'zta[_ =})l(mGJx(~/wt7ѶrX.V@V /H/oH/[{^-f9D-MuI&1BKR/hnE4<$E =϶Cb3}`/w/>?jq!6q#=]QX?o"6t S;:Mlͷ]8aS`!3W$t~ WN}HhʁG$R&/m*,$ B/xTu: RvQ ?=P_߽O5FbbM_M"Gl-g#5yj*ԻdpmM"ډ_]>spl*Vpw')5|6fI\(i-W/v$ND7«چ9NF$;c9x;@԰j&|7feu T濓f-uIіC;AKNѷʁz\k>Q~-9.E2'YfGe2'>T0Yn߫ =vͿ.Vt\vdϱ1wWPI~o4y=-K>a {/ūUn aƽ`s6fKU~d$qc>l]bpD4R#,.DаyJMt!pR$ޞ :\(I+˭CGr%W+fH]s}|Iԃ {k1*C3 OΎ99?I Z4@ʼn^=,VK'!8cY(rdR|{H]CS/&oۆ(?\ݘi̇},Z_Gij;Du[<>-nޟ;b-`4!Y-)UZ@Tf[PŴ[D QL##[mǖ5'@"O tϳ/Na/.r;_9+b;]gWӷ)tB_; (R܉7P|aЧb-x4b DoyYnk9]^8߶[Wp eL~iλW }^aaгX]d!3W?oћ `x+W* GSR?U]eS‚]VIpX hMf &Z 9&rqs*)?Ap0޿72VqJxmM{@L-LdI[ TVψ`[?,,\Y{ضwCj$NYpv=tEVm+rNs,h_M!42ob oD 4o"ɪ^>:!ѥXã}NbM&2P >@ M j Ig5 Nbn$2錥)Ut/J=<<Ѝr/ϕqk0/ Za)?_?}ls!SO]kT6p0M@UꍶR'+X!anňTy(:9"# 1Ds?|*A9/3LXT[`WbQhd~>w߂rvj~|=mݹXkwt>ݸE=j 5BTr+IS,Qn6]}0`/[,2.e[}=/XCʾ%3CI\ =s@+Omp7mp5c@}7V|C,f;oߦb5]C` ̧OX zGVIb힟s\e2fn!갘\. H<Č7rCm2B~ [I~9|]ah~) 8=PuH +s+ui$oӽb36'V>܄6g#$+{Xڜ)h9:w&£q7rhc'5(ns1o:ū.Ia#Dt3/j]]fxw8 @hħ#-X.BYTEJDP5SS&pY _hNN).<ه-~mNSsy#ve{۴39=\^pVOByW;'QY2P̓槬r q^I5"ɮKL߳u[ !NǢ0J|⃡n!lG_[@@?݆Q)fuO,G0/Fbix"iJ8b2"009{e;eN4!YH8pD!ܖ#R$E$Eɛ+*Icuc~GuS0 e芽:GXۆhU BI( H> QR1ge6YLEID&)ѤŨT}3/baeQF>GZŰ,hzȶs8zF!Q] NTri\OJ̪q쪄^R"'p#+1_;ypY8Vr JrI< ϤU9!o=%@(t %CW%h)kK=hiW4F4Sյ f[6 _TYGVi:<-HL']PiVzne&|q T':0iyo$xbI~8平\5yU5'qB Fy\iMQ E7R)g Y2E]3k g3)y59 攗1mV{12-b pMYO8ՌJhكy-pI փ񑕛%`DWJ3" !QY_-'&43N,<+XƳnΗDʊ1D04Hsx#q- gz SH.K$Ő{-wIKkt9.eL'$'rfNemֻ,.;qrc63CN|Vcu5]SૅMWMjيʁ>X e,N[h'wwl&1oJ SONKE6a\\ƥv!^t듬~ldĩ `I*13+51M::ξI}9N=h]}J*)iUQ-\OS*֊.\) l bCRن@K8X+9Gr %eUuD.O:K p/{^yGk D⿝#t=t༠FiqTZ_ 8yL\ gAAC+O`V^Dqyڬ,>IR|xݯw#dHz n;t3)kpzl"M43=jF G{ǔ-k k#be1:cIk7W#m0T*[)yMdc#>r9'i G*[{ܩ$n"t9?8 ݢ.zr/Ck^B~b"&tB0g&#uÄ&:?EGu6LޡX|EB(R_TDICci)x?)ƫ2"QK g;3K3%l J`'o־k?p5>ukpl?0 _|oʷ .|xU1i(h5Jk 1QD0+LzhLDԆ|*[']v:︛Ō% Ǜ2g-Ƅ$eiyUQ޿ wCy g< lj&mFsvڟ,0_<c4Ӝ4- ar]qdT>AAd91(̪?룠'yɗ˺4'!$VTj L+f|\F]@M#.7{FҨ {F8V(g _ױ'#6$I3NE1.\33@Õ͉ga?JOJ\B&r<cgBյOIqa%dF4K09{~9*sTY~9.q)|ʊ7&6׼.lH\tSIOq U8|CpЄ%:$E8xC~pЃ#1G 58tC >'xh=5%8|C>pЁÇ |5\ม b" 9 ބ~/{!~ϗ;g9\x j ym[!8]F 0kKlJiă85ʴ^޶$-[»u.66|١hgշ^I"_ʵPHMf6qi-^g?85@TxɼK\吇xRJb7N*1b(a E3_*}P|Ptd2rP%z$M%R`$'[4:lrd9&,]s99FNb1axH`Y8I5 ?ls!ɪ8| P3)$ ~H>Ty<9<}]٤9v4*K?YCFCƵźG`F,0af#, 7ؘ")Lo'22':ȃ:_-k8BNV9=МQh8 //O@m ݹWL1?+үW2csݟ@73y =` AH; :| qzpUNpvv9$w;AŽ@㒦©sg'K,vfyr)[{`Sm `m7 R|dh#MMfgyD 0]K@L1jA8<9%3/-G4-“ހ|w&D6upf΢ ƤKiSڜ!cmXr`r @LyL%S= Fެ{`s\\BȈ6EߙPo>$2.R벥Ր+(wuautY:GZwE(i|i]lQ!;Xٛj&9 $%/ TF\-H+c62O=ҴF8JDҴCei63C]Z -CS:`ؘ>L3J>e_2jXt:^eAJ`C[7ڍإKvcr\1a0!a0b;)Y>r@ +0q􉟼(u͜0i>igY5!Uscߵ|!ؼj!$ 2H^N$N|hədgg"SxӘU'#iVɫ\wc'4Q-;GFVWD_臧ʙB/2g!^ ˊJEZ#9@eOJ<@bDou =s۽'e} aۚ|PTG4'lZEB.M]X;L/}-2ZHoO1S!ZeM޼SW37_|'ʯ;edeK(Ag)Uxl#$o%JqK;IJW|s,v֘sQ gKAbAtzɬFFMvDUhbU̶]XǛ,8 3%+ߔ4pCS {BS2->aoJTNG@Z]TeWq@31oU&MS ͭA¯[I#՛D#G_VIJSBcɈ-$7ϔŘN |^Nw*?iN U7u\7+6NڭH5AVvI_ ya(z2hVңugf1G.-Ҙ<l HLn|:88~z$)93JF,GA6]chzy%?Ie, Lo[dRC? W/ ҟ6zV8@X+y !4 G{6Aj]Gf0ڻ[9=# i &2,,): ]4˖jBq*GSn6 G8 iZ!&B'aD1/l«W9?(VR$􏳪/V>wlN1o//_\6teNTh>e^8I\}v_Y"~IB 'ƭ| >>[IVd]y{ ʻ8m8 \$>IӝfgL@]`aa9snkv^-_\L%4} t<.wX-`^AWϏblӸK4[ Iцn^ȤbXc:jK3T /:%:q,+~PVWy1!~>q7PVĩ(AANUrySs4& " 7 w7;ieL^_V}ޙBti~,I{kz; iX 6 8-4a(zHt&>zxv_WOsS$9MVUb&a)_zA3T׃%f}^麬>/q(M_MI(/CJZw\{NKXV{Yz-3 Hҏ3+y93! /P95mUXpGʁU?7IWO @Bg9EK0նr- $O^:6Ӥt`4,pi>T@Z؎ 4X^d?h ÎFE-VSx8":wɫd DkwHS9҅a} }}1IQPЬ9 !Ւi99.-LB J$p 2Db`k@zRBW /`%"VX U\JxyyeqlM#=4{j,egV(XCI51hIL؍3=7דAP}ǝ."Qy/;op\s}II/w'8d5́]ܴ>W@盍ntuwKxǶ)`U-=>yAΒvEkB'xcNz/_|}K@z&o&D1.|9N> {3.gZx쫗|ڽ~@e~iY$+{RF9)ߣ(`DHR9MN@lr  #  L2 S=YΎb ڌM1+]LS4<1 Fo LNx{hvh!N -C1S`}!Cx3\:U\"μP`3L8x%1B!ȃ|A50"tl62P$/e~ {zjJz&$;JJ2pHf2x2x~ƪb9y!43=HJL[90ʹOhtdtY;i(gbj UD)ϊS䛷ɲ(maIU>ڙkMEA)!W'T*%#' avVy0vi~ƀpWx(9b!2C 9j-*81ez4\xNwϴ~6w1hC*$ʼn> r#SK}8v%y!2Dlp^KZ3@r(tRgn[xB`(rRtyB٬&}c8!y "AumӭCb R/\ʋG"AQ˙0"Aa +9FH !#wL {Ay@R`P4,3U''FwIBz%fS#C$lV@?ޑ()i;L8W{3yRZ(; Ój+G!ǝ4‘))rB5[#(X9$q\E V'_x^~28j^=ǥEeŖzcHfIZ hZ٬,pI('o zMY Ü% DU5R捹) [4iEq{\{$@\Qcy(._|F-Sޜ k!$Tn|ܶ>N_bS\K$ʮ"0.5H6lJF*/\LIb~EDK4b /sD _e~%2 p19N(K70m|,)EE9K}$Ncwj )dl3nm ?Z0?${^iOKUE*<-NCF~2'^m:)xtꏔH=dV@v_TpȠCŸxkSKxt*?|JSgsG4w5p0k;*.n9EC=Ǡ>Og3:Ux F x >zu~-GhyGҊ.0L UTlDUTEy|Sx5"Yp۪Ip"]Ȥ`pJ ~ 0jq-'a5]:0cZL09eq΍̘|CNG͠{$3Sh HE@!LjZ)0G{H!nNqxNigݶ^,l;b&"N 5\,$ɞ#(ՈA\u#P$ sFr<>$5qLXO§nSHѸ$ sWp(+tۑm xtXlgcxbB -P4.F~)I\G߲{]='NNA{4)+G`,4/4Xǿc*q|-L8K# /`o$GA1N_qe2'f(|ܳ6'Y@G:'QZ9=8nq1x$X 1vܜi<#ydOf\tQ{]H;ZBZE;/>!a e༬P3bG 舩ywQsR bco`|V1_J;LG ^p̤LSSDosQ5gE<}̧[Huk#\@s?@Ji3JXb;Z!!Lw6}?~xUQbX}+8\7ptQs,IT JYAYguittLd"/!YUMSI^O""y> #.H6?uXRjKA`:ap/W*^7߉1GéPg@ e (%TuyȕP\@>n^ ]WVM7A6]秘'ԇ/*oto=~5kUQ,iGR'`N߰,=R.dYO>o[ëP]OiB/&B]`&SN$|n6*44Y9Gx 9rVdbQ3B_nJ)0"ܹ|")AKyT1z'{Wr =!^PiJ!3w"346:兣CN]e 8iT-,8u,;͡|-,'B43+sѷL:ꫮ\ _i u%rjI&z僣F5=1]!-Q]$IF3ΚPkfPα&0:RM%ژ0)E]Gs5qLØbfg44h1[c`#D5`$n}Myo$ ۑ2/kK1dyk2A ʷGտPU~,Ns,-'1?)?O@PKx8&jx9G¯3c2c6jW|uwUhV}e.]X.t@mnfimH~b*BchL?zʤ# e'[٢,ܤ'Z6geJK>h.&8 ۫h6}QZFs}dJ0tv}epay+06ɋmu8 hxa vkAkm9u6VvP|%R[KOQjfL^XR-/RPLn_g+hi#پ:b(cAC킔i˔!`='i3V%% 㽗UиOHՂM7PLUG#zFkZ+R\nDS" 6%ӭMXL%k.|Rahԅ1tʰw T=mXB˖^8/g} rx.@]F9[9jx<Gv 999+7t $_W<&O 1~DB}@MQ:f P#UsKEiH"ZҬ'oHe,Y8P90%tNw_Mj!Wr. H2IHrijމDKG7i2]H_>FqXJIm#>A#騚ꐟ22# #gi_ mSgPZ}2w5 Md`&24uQ!YF "s=HjSpbcfQ,-W=UՒ%jk/bҲV]=bmazrD(jcJF4x`.]L1j| j.z!+pXEAپ# i4"<4b)Ef.Di&j1Ri'XBxc%<'GMvģU#J'j莢y9rtY?rҝEGml,5')/#I|\hcIEiH*5O` h|.U]40PɞWJp![SҼ6N,!!w@RYqo&-VH򂗒gOuˊZT\PnBN pM \e:PW6@E!98kɈNn6'I8Fѡ:\«eg>n3if>3jOv/bfٱ۵V.00r:.ezn< tDZ M*U?! NShGE;+0*b6 g\R,4XM'eIe-E.L/,2jeJGPw,A$++Q#k,KL9jHa{XԖS[0,&Unu< ^W){$1R\+իke#iQOtUSvK@Ͳ8Cndb%kSS8iog(*wSiQ:\ǯģZP~!lޫ Mջ!"wҴy$4`i${ 6LIyOvp^r'_=7O& D/%P t֕XmŁרawvHt/U0/ WWo I@~#=(D{A˻%Mv-S0]K8JR&55WɎMuWC ?qc,>C< x|3}|5s=c茈$5ܹ슖gCP˳&yf6-:} ;8]@Xh`N4i{;o<'Oondra:a[wy;n] a>?Wܙr#Bq|rv?w=sf{=gGjz'JlA==Ei_ sAN6 ݙ#N?\[l]?Lp[oE4@( 7gfqqOr i*J6ߧ=bt+j/ke}h/ RP1;18]Ĺ|}qv^+H'\?>,cbLvh%}[k@`F R6uʳzZ\=?lgt78|5~KDԓqq +Ha\? OOTn w ׿Cn !]p>| |,0ϫ0o`h~yw|\pbmi޺sv\g߾Bt(D9$f 1ߞa# Yw<ۂvl7V%`"pPwG{E8]2x<[Χy5y;߸yyZ 2Dy[3#F$9&Ԫ/ WXY.TN4iH"[QU}`Ҵ6x[5sƹJ[fbU=Р̚@>r#44,v-Gܛ 1σ+jv僚(h\b%I=$c7n$F^;:6(wlG^P%Cq)?C/ٽsMIE7˷>x]^|)"`P<)mgw 4WU` bV?Ut:$E0`JIe_7$K H'$OJx+WaE.. M_Wϻ1ŽOHPe4ur}"6W ڴ\a;I,әIrr%c%-DQ?*O bCq[p:4'_翣f^t ;ǥO5j/8. _fLٱ(>Ræ 뉾muM1)}l3 (::bȊ%O}{.[Wm!)AxU? 4?4!y9 ?J2@^]tt\ׂ~YEEq:iu&}+ @e3PFؚ>Hip 9KyX/C iZELk\\ /M:V}#U;Ab^ruoy]ǥ ޞ|;yQc`\1*f{OʕœFcgu8FXZ4 rOM#Bmj^NMK2}I[)e j%<]?g_)JoZ^{qSTHTy c{b*3؊S>І,qJI&:o2@%uH]U>mD=}feqjj\xˍ;<,JOy[ }jSPjcd򀴮_b ֭G;8I )#&=l)n>mo5pIl;B ǝh48g9h~,T.hx"e^IGϛ%ZBv-<ܠƊuP|\6 zpԊp@ =%9ɤI}-#I^ Y⫳a[z͐jc/1@0 h?_FZ(0,[)XP8;#UǦ4Va#VN8b:Rh{甏#&W}$3h^W!+-G#YAJ͙{(]C?qR,H!t9ͻP'߃ @l'HV>4}WD=:"M C^SXzOWd.8Қ(OqdQjuҢ]KSO(:󏿏wu+T2RԨMU!]{i 3fL&csShd&)Ei=hǘ8G {95Ig1~.ҷe?B["Ӥ'IL6/~MMv%,{?ުYտsbt~~Co[o_oڤoӟ77 ][Yޭ_3&0 .򘬶o0WN9+ڂ7jȋ 9E}PajM6i0k(ۍgmtE.&UJR06UaWorx F߯^xꏯ+rMCYPv&o5T}랙`̊0C.x/h0^궉9iN&CtL-tR|aIfP2,RyϹp6W *jx i]&W ^i5}-Wwk3O.P1TN/7V+kJB:q:ϴ с8AYE YTTChT/3 KI VnҪq'%;t{;)Ju(s\b]G17] tR{ցI4DZϽ7[=Pꎞt[=6JnZ&؜qX\'qfq^,ruamlnVvYJ5SKuՍU6eHs;YeAw~NsWV5W1|u-7ʼ]+>%HnGk7#{e|KOo^H[+ }דe OOd['G"oH}")<o*d832TYqUYۑ;|WJ*s\S77%" ,#NɐJGn\-ٔFn,Uz;}WR4RuvG ӯh(RI׸)%BVh[y~;ggeXy@b/xz6]qASخܷvSx?s!u_:^D:f 4ɊO5[!saܾ;u; k!~c}ma \]z0 fN:5V]w$6ev§\nBs o(Uw(>{$ jVΣ+C'P eNaUꦠH)fTqt=7z Dl*PL%U4WSuooa‹Pr,֦XSO"Y>p69el MFi6o*GpVzW&`JF0'զZ-~CrZ7v(&<ҙ7$ P=0(}IA˹2ikU@etdeOXۤ )'@^B~[>;uS{ӗxy|n]kH~̷ʬ*dI-;0^ֽi}?q5 eNMLÇWN &Q i9,# :/R8ŌdL* r*H6[6 rMl' z$<YnMPPu^O.E'H9wmDâSf79K: m bOu0~v$ĺV\ͭ+S~"߯*}Aup-zbӘ[vCfO7}2&yuv-vؼ{zI]ZN&bJя,Ɯ"bv{E(zNIXl˸j%e,Ab ,LVRym RmDh|פ#<7B'NNEial ({zW+$ܓ ڿuH]EBhz"ycʹ%EW71ĥ!g$]Ԑ7kE9ސunajGFȹvg:uǿבr:Zc8_V7vZ&uWcbIX-^D9&GMhX۪q+5J̹8>2KAZk/2Zxk/#Z<*x?eԕۗqVn_F^z<(ؑJBȜe/NK/c,%2R˸K/c/%8,%2R˨K/-%2RR˨K/#.%2RˈKɮ /.L02( /-L02H /.L02tU%_bY uviRQHJ?1*Qm:N4|gny$$i3IzB%DŋH X Z7=LW>Bk"BZ;^O_5z;_~ `,|oo (lpZDfw7(6*{tx_w6L-~1ÜeXd1G᧜F<TbO__\4UHbQ"oDĄ䕢 (1HL+Cm7&`[SƙE@GE݁HZr>':cEr3mɻҚHFV$s"D/hBs+zqIo t-:tO.CqGӔ=D]䃻]&!SJ=͵;iO$dF䆎)d,-h ?xy ^\i@=r_>R]̞[y@jssy v >&Q`4w0˙?].gr`F<{AbO{}˳]@~ xTt!=Z -ܟ>o홟c7)QjFTFOVF(,eaaJIl2jh$&wvװ{^C@`\1IOM:Pf14W)Ta"r }=cv:gL`'qū v }a:4 L1*HAfU8†]8kkg"N2 ^b(Uh 4m$rd$ %ZT[2h۷ytxaaQj.l;u T,ŜӄQ:&o4Kvڊ)Ccݻ]TՌv7j36Hq&ʣЕW]\l5cu"FWԥkEucG,qŒl,ay& Ɏ8h,"8p ET^WFYgjcE[ܑ}U?b;1{ W$ };msY '!{q鬾 NjUx7l!"]:!żcS~LB1_gL;niDWo:سסdvw۠gwm:!Q|`F=1r #}]Y19ИcXw.d+S$9ˤ%Gg]GT5*(7cLt'dԇ$3NZ'Pm~~EM4gMGU /&MTh8DDSN<*Tnj2as;,fy~Œ0-Wbdnf`pMK <a(l(%&DU W19cJ(pGShZӛp77L $J9xny+ M`~ςi[E ρ?qOlSw48=yN ׬.&༬+tOW$1f/6h& V8[a8uRI nPO(((lirX6 !IEb-mEGW(vp-"K%ˉ{-Y/uMuG^>Frl"\_+$+>qt]%k%)1PWqIW]t[p7CυYB4S-W|u,7Iy!煪 z[C!^#B`YHOQ]>sMhxARXai hBGBpP`@Cz)ޚx,?4c'?"(c̴W >$4 όծF][*<"O!XBJ4(h <tТY2Eػ}9 #а 59XؐY.\Y~&N[DL_O|m5c_t*YbPuH0zUMjk _(.$di&1XrdBdG^{6\$ɟaXYm(m\ hIc_/,Nt[HاMy~WjK* {]z=ʢ[C}8^Ymn٢j(2\ uNJlM%u+߃_Zh[TVW.afü63dE8a~;9 G%❊}?ȗ,ƄJ \=rypRF!Ðh>hPpg@($]hO{h1g cNc>;}׶Bz 0DSEks1?> iw.b0[P>QG_gFqᮁLlxYyvjoPJ;83%~R~D`֛@\Pg,L["+"Dބ'nO̳jYB(o>o@W#W׀~t++].)4ښu/ Dzj"  8LQ|[Q̧=m'ݥ`U]YO_7/ʋ_Y徍tu(M{Z6c,e~&&S.\D撥<0lڌiM`p9lECGED 4Xyw}]3w֝bLlљa."N]DZ8:Ґaۍ uw t.# \[Cyƅ@j~^b^bYp'gd&ڑ&21PPT߈ ` Ҽ'$c2^햠HHm_Yu=\&|}d awݿʓv&3GI埣r 9%1Vsv![5i QR6RJzVD@sL8;Uտ ڳ֩9Hyʸ C':;GTg|M9I‘rEq!gxXH׷,~rbYZP,ђ"v#[bؾ3&Y.+,(D6RjVUVI~y Ϣ5fO岉9\ ?;GA4rj1QIb.\$ed}`1 C2yyPSJA,^(uMQq`PE@pyIM1Eb);g~=K$R%q,+æ5HpMY3Ƒ.9vα#viXYPM]=m)=PK:Mir'wBrtcD6\ǑV0;ȩA2#WE0z)V1+!6hUȥA+P8#B[wjw_j[`BL(lAQJ7fp Y:2ETȭpupw6q^vl7Wlӻv&s `j،KKkC76t)r'$Ȑ;D6N]StvjV-&( XDM=e`SN Y*Ùf'vL{"TՔQ_)(D 0yKN9ދ1%]"[KtÕ%LbaY1v(<)BToJgJ1 8=81]3AԺ6`^U\qg~V&~^tG1uzߧ?wƛjzˌ)'oūtm=Tv|ypwG3"/@D7bx%o$ n'OviARY-P}/TʄJB:d)Z@Ts㋌ +yIsMs&|ry*1^v<»,"w"uHl9g~z=%ٕ >y؆~,aJx^ZDi>u7tkI G4! g~׫W~mVakېz Xm^lnee5 l]mVwgt]׳*n>)ta~a~MZ|y ljg]\Ԩ v-*ڗݸM!w?W3t&l~xVڟ/ rmԹ{?fKpvWso9Jj=/=Rq\ۅ]boضNtdξo][pUdiԪl3igՠm+;ֲIV8%n_}kWnοWM~uz<۔wR:ri ۅh_۠,C͚6ti~OgO6!WSN m:*]0˗KeJ-fMPy˥ %mĔ"*~]ϬeWk1z{:r=[IVsɛӽM%?.׶as ?4*y{[Zh5_X ;ßl-CZMUH~!gێGrc l7&[On'>MRwegxJqIUjuf~~,SV-B}~;Y%ê1ϳ 6KozXT}c!$:0=E4}bu81oOp1#!y;Ÿ:tyg! yD!)"8nq@X<`ZڃTˆ m~>dfYc*2B~ *o@OK>0wk,&S<^@L 3 ׌fOȇmdP˒Qn|18 Q)cC6W;[HwD*(jO!)hl5Vhd\8x7;ls:lesȎ38s1r/nrU I daUpHh <#i K!3P`$,H8jݛv Y{@ɺ6C% r=r}⌏62X#tblp9_8pzj2 = V},RWĩZ͒*.4'1%"Yqb~ќol3US۵ty =Hԛ<"98fͥh.,2{pu`-8`:lPhB~wg`p~L;kV}%g>_Ei{T&[V2N%Ki *]Cۇ}]-ɰ}`sZԮ8)ÈՃhy˳n7GonU{l}8?PgIҘ%g؛Ei&P=鉍81bl3ύjf?`5U#*f21cUR|EJ v#iqUsSTK3tk#HXZ:艓ScHK Wrɥ*5l`$A!6r^bŖS߬Fz7j˒7q萻` -m`=&9P$3km(u LOR28#&7TFthDg~S44!ipfyDu: |^nOS-r,(2n 77"OH`YͥaPliD9E9j rZ83+,<$Jqyg1^^( ;`.Ԭž՛mS 5eI^jcn/S 7\;E2wLL':k,6cʔA ^~iգ*S</˽XWC>->y; QwLd#3!er}_φDD7sGw5JGBpQzdK3e&GH\pd'" k*F_34#`FsM_R= cG=єehZ:uJ5|Fu+uOZ/hh{`BziñP~Lje9E-z*(/}i׿/LO";.~-E R*_dUvܦjGpQ'O,.rzYyNMJNXT#˓J[?g]W"Wth"oDzꃓ"H%×UڳL#!xRDz[%ᔣ ˙ Y}.9E482^Oԋ"Q\ŪerqK7~֫4z2xIMg :ߣ_;_i?{sWN~,yh4gdvπѳL[WգZYo[O;MpHb]F 1|q8?fRȎLd.#xrZpSƹvg*Gb97qg#95i DQ%'T }P_L8ogowd)[Ozz%hΔĨG~ވT|nre77:]U#.fsb I ,w\};N4!*@]Wͅ](#ԗ}RiR]U4%rV%ӯ l0%րXR+Lu_rݺ+dk&$ -sKkg,gJcSS>Cs#ۏXw_z$~3i&V]pe*pu^>}oҜûD]O͉]7^}|&GY7WN2m5SOAs%K=y[LvUf=@iBoIȈVM%1VeFs X&[-ʪF䖜c_"O?ѐZ/UKZ Iԝl_'/cXFGgC/N(D m^n3@R340{fOYcB6v!LVA PA܁Q@L ey#у_c!imd*wzGE^ :H Oz(QM^胇QPn^ 5E*΃5ݜsQ/%Ez2?YȜG]%4mӒ0['-.U L Naq8Zv(Uݾy꙼-S3nX=.HHJOmd]kX]7jxhhEXd4> r# |X9ilQTό"E\$h#8Ds?̶Tpy_vŔlrӥV|1Qye ȭM) 1Ġc%9ȣ,snD]2)όD]W 75,3Be^'zкSq}[tn13 yN>s:*t\BǜCD5J=P#53-P`ViZY -8ݴI#MSZ 4TzEbOt6[o=yAq~w=/׳?p/ .b׏%l3$!|b;\fX# _7jX})O7(h5p_'}MzzgO|P[>֛a-s([1bI {MF䄈5U'C( jbN[q Ql!b "@uáøL505O5p?AÃuFC~`~#$$sTc6"5F3ċXGМI2fOc]FsJ7JP%:8:@GObc!&YKd@:m PJ3mS +Ra'~MY@$P4V0b e0zOCIOn_.Nm4٥ -7Eo$zH(;b(e,DgGĶ*0Te"9]QRG EN+s@|f,;ɺ]*;ʯ%& U>Q2DyyE`؜D٦4r3J3P d8~h=Tz:uFM0;1v>ٷѰǩ~O#~#sT=܀j( #>6k89(oԯ,r$QJb݀t>_ߐ yI%Mn#E kWL//\$IjIGee);2)e;DSգy.7y}ω-9R yk+==!%_@\%47fjj?h>,`?OwdBb5:AgY8`<챌Z:8k8R3! b)3QL? 0 . 񇼔7仅, Xhă[警Kىm{ qL6[qمV;tBPY;ͥ;F5x14e`l&t\#NnN4K\CtWRd} S徙hߧ޿~/3ℤ}|ZCʱ2=73I!O^`X!*΃(*yP1}wkO 4ObQV _|6@H8/$"E8yzS~^Cwٲ~#ߗ"fݻ^]j~a )UɍdȂ$Dp3$?SgtSJCLO?RTcv0i1.` Un3.{l:}h@@޽ֹ2ekh74-@}*1M:!;<~2S~fCʖQS[z,|'uˏh4wIG5#^wHS{( aT~oub+\PV<}Mv~6]/V |@_V|:o'QV,)Qlǹ>{l=f_4PʹLm=꺹?_l(r,/>o )lnXxf\|o%]כqi\}7c"rf@jUE Zw PbMST? ^,[:~WjSkfvL|5"K?s`dHIr+ʴ]Lfeƛ(tf8|6g`{r1Өh%*r3 *Q1u\4L DG_YW$8f*Zj`6c h`e,'RDt̹[gȱ}eEe&$6'Ĺ3jڐu C1.j fH(֚` `<+ _AApZ͍fJ,NK IˁFPk+9^4xC(1Q dϋH%iF)?0M*j'GcydvrB֟i4.Tsehccd$.:b|(`W4Cyx+≤<՝qӴ*xV((q3g82\kf>:,T7\&/ y9.ܯr;*mDJ;8*U=OW]u~̴ykC$tI;! X&7\BC9Mg-p ?f$`i]C?hP:n'$tAe tI[:CRz.-U7zi $Ces X\,l~^,K|+MZ/{b\>Ȧ$vcғᶊV L)*s@3Հ;  \z"+ՏV GZ*`hrNz=KbM+ fuuc:G-tʣo۝˕{ʾaz¥z[D+iƋGu.1ر+' X=lƝj:4K J#!)y&bP˖]פߜ1 )QUE9nx Xt[ y$ G(;&AEU_@&8 i$^Ye-.<ԡS[z,$$uQ}]҇&N eFQx WVu+μmЩskoCd!bt+c֏dPɒ K#nq29h> _M]ե"252І dUёʃD9J lPrK}h[2Z?P,Kav{GɏzL^)4V5MɃ~F yCY@ܸ0g^hIDL x趂w6B䢦TIP ar˾؊$МmiQ) O&?yFtacuG.#{ӗ@? bۗޛVfz78T`jHQ1LW 164MD-o5p2,l|%tB]OnǃH?xpULXzt/j^m;{Z|ݙl%tli{3\֛۟([Ͷ?7{dBb`LhPd>@D"V{r}yP6P b+|sj WG06EN®b$6Y 4ݣ?l>xՓ痽oM/+5^`nvOk˂En-=3PhY[]as?^R2{unٶ^oLJgO[35 qmxa}S/ǭ>Ncj?'t4 5d^$?B!_f׻uCuc'gOw"pD՛fgOMh=yom&o5^uCCH@:/]/ zO)_r@L|LIa#zGDAhASR\\mP=<ڿnnT$OX~BIjxe FnYi/iI #xs=3/BdH. vq"fNEk[#CBBh.XiPW k N?4,ȁbHg+ݷ^k8- i%$1)*@DJ1T#., !2Qbe?(͑9|3W??QМQ}L}\L']UCbN$+%v)#qIqO8}۞&YS-+LL89R9jm iE'yƒ(c*s.M .;=)5;י~@PjgW.[3Fv9*h;7b211tU)u-f,N*6* g U] oWq?k} )!Y=+b莬G}yׂ=s:>`n`01yʹsfp`dV$-+;MjNev^}d8-:{ lCm[lM? t" o=No_F#DJ[ӯ=K&&LAܸ\Df:!+Z` VocMPN= uL69}מA3q\,CWPھRfK-848*^pD02,6V8ۡsn/0!ٜ&1%2gr`Y7kr'/An gɳ 5A/3s9舕)fiH[v0 R1',ָT>#WT -V*51>vf=]fN:S1f(.Dm[5 > ˏp}\Dl8ܛZ36*EśCZGh6iWrT0uw+CGmB1fк,]48ԗi-E+Kک3UGH@~UFbW[BISl4f"\9V:ҜL"ҫG}eV౵`ȓϕo3[=(ua;# (gz07P항;"4TZ\G4ұŧM*^XnNGs" yRQ3L{gW@GN,Soz[9>f\̿v`Vy}JZEϥ3Tuja39$)<"/|XB>rgvfq=sP]W'Su~,]N⢑k@6Yi!K8~S8de116ؑ'<.)+㧻JNcs}h)׼d9\{cmꦫJNR&3ci#͏XG5@1 xy&ʠhnظ'`?mGc۷7, >?*z{}~nOHY\~P[Ճ9ͨ缑d%WERudzK"KmʘFYW 16źK8h+K)/AG) Cɨo;j7#Jζ"j@ <93|e:T" Ύ"#/8YPj Ѳ:wUw2(U{ͱrzhٻ~&."M%%2ח,b#YAαk}㣗g=Z9\ N{4ڀQ9ކ8үuPhLx)jVpuTzNPƙ|re~>om se&v?W3{T|.gqTC[Y| v r-E-n?OطOXz@vóH_h=[`pP+H[ O?ZD*M Z>l3m~x܃PzormXt/o>[ijwuX:8(o6K"aA۩[5e_KZv:,dQ<[/rn]=ا[[ __싇}@N% e =Z_pmlcAd[OuKF)mxݗ|r_{99-e\m߭/Y]o ;}VV'HKHP| (YXkWOԙ]$Ds2Zˁc]=7$RH?჉훿ymG͟{䴲{.Ry&~LI^=_[7u=FzZ)Zvʲjӻj+U RxPSk݀_]^5uo(RKvhR+8jW W!0* +OsqNW|̊GKf~W=Lڡxbe[$T0v_ZQ%-ϠުN\}Q3J6*4w+mFzi7kq|]J2~fExD Y e9JUwԒVV:CY5Am*F$*}r&NbГ[*̞e6$ܴ9Ra haVj̤yont˜Jbhp/ҙ:jwptF<]LjNRyZt)iT0^,òe;kqQ|/*oѓUu[.I_U:|}ګ}Cwe;5o#dvANA4Mf$}#IFN9e0v? Έ g7>Ûd/3EOQ 뛸74)`1ˡP7A , ʃ4o(,ޯǡ`m BwXHO" 9 zb* )p΅qT+MCD@;h@?AA;#/u3L43O ځe.I0Lb)L+\@QH1;J{E<:@_&G`'-hq}1 20&Ye^'Q,\;DbUGJ~(5Úm]@&Z6q0j>=IIǹX .f(ZxW7v0|:'r '0CtH,!hxvc;0`,YJ ϡ0k X[+be~$A?nq[ϧq=mwWyg:TJeϒ &IItQ$MR)_Cř'9Xk HA2/J> ~G \rQm@i=&-H`h90ܲ'?y/u_YARYh[Q۹莑!Sm em"ɡ} ۅw~YճN 'V+' (P9#&wyS/)>1~J5/nBTŸl~ k^[Θdg?K$+)v7&mW.M}ڰ7 06[7M@|ȒБ;/$5(w=vB|[pvc`C78}] tbr:ql[:ASl)V!m(wj;b<1 Lbq*Y,@K;1x8&.x<}\be?UT|7!,WSZpQ0SY-p*JVEnm8ۻL2 ] l]5hBh[Yb~֍aTlgqEABB~%uvúϠCAYX{Qɭc?3IJb\GXVsY{AM{rMzx&P"zbQrmF\. u=ήVoIVӧ V>Yk5_EdMk}of泟 H 0n۵1 :]y1bA|嵘ge"ÆE ]9ؾm9דau8?p(]aFk/kpI<vf}[{8sw$ ΂66nq Y2$D!JƋxav 0f< d>oomU3_!AC;( NK qśQeWtK9j,Q@nl^ґGkӵ%R'OCH8 wcvs 18;G 2K-`E\*"I18F W^H1F7ӂQxW=FExajYD3xTXq-!KwA> (CW(g~oďQ/ncp V@_?x:t~KL  AcT7Jp~jDIe۷)DXp  c%#tz&.QLjbѓ/gc~Q4?E ;vB! kOr)oݶ4eu($p,6,x^ pI}ӕV*;۰QpT{@6 k09+g) EX$M~&J<7MwkҟX7Lc$,#]n6a!g{7 =I 0|N<7ﵛz-Hb{*rsp}*L=ZA9(7"͛@F׃llYJՅWMyT|'hU Ck\# \87hg7Zya.R%%)pNHR&/ AF=}%a'-`w hLQ07 `: +*ܱ־9~Y+}`ĤcO/Vt Np7`[~Іxp\o\(W޺lWcRMArůض{++,hC8k!ڲCPmQ#ЁzJ9Mov61(W7bژ%y1G-l$ !20{1ȃH.#TRƨZ1BsP@9IZy6t2m8YgqlCZ^prZ%8:e1_cȲl$.pQꔺ ^&`i^v&QGM"LqxݪѦdtxA2F^ƛmuW/yD|lzvlu9sӗq62qbd<\ G@;6]a񨲢o\:P5oP%/w}ԬokjMo竇l[鴞%R_\B` o0j<\?sKb__-qw m~˻wr?>R6b}K妕@ـc3b[^m!ajrb[ȳ}I&~qXn.vSv֠&V9$4-|8!F)UYv-S-؇~y;8%W1l|e-|spa~aw|ہ׋kˊ8_)d/+ԗDͪvZl$)<{釰JbϹ- )tV׺)* _V8χ=G/kg?KLu(J~I$St*u@6?8$fWY HғmED_B0Ets>5ܱ+5AoIy:? cW ^NQm+Mo[g7 }jXt~fAI2YsUG%Q}0LwV;edNAt`,md;* NRc9n A;&D68r'qЧcO<4nn%3co.Xu9ΰsC~5(Zvㇿ;"{\3?94rv[Ci"!&930.wttc A,v9zMUFJz K~ʋЋ@*g׍􍣹 7CAw9l;z GE&Oa2x?3-b?x;).c^v~ xX9@Z@0ۻ.pܢA:@*~m}~t릗ٮ7UQȗUmI?_(J^ \n1a[6$E=\/>JSbaVG ?%> +>$a_EbX'ۦB&kyRXߺy^7N(ZB;09惾Ih},H#zoBpmb h*)A%QidE%F{POUٮu O%Nݪ7}ơq5v!H6~&- ߊդ)=̻mH$ 7!gB&T %Ӊ75oJ `~~-8Awp޿79mbHM֍ !llb!4 K&Y2Ţ'dr)'}_2]1Lm6Ea!(#ΜGV7靍EMԫ㓲A9C9[|oxE Q7*q^c_oohBHhE9q^[޳Jm ZXE+oܜNtGܤC:fBmX)Xaw`ٚttBq休:c@pH5˛B{B0M{&^An@o5"i혢0ddmQE-^tbt~*{|_nՀWnX:z\|x\7nz7He|^mf1)UMz>{0]wG*f17Gb-r5Ln0wKћE7T@zs[>6wۇ950p.ٚj]}=f}n$|y3f]bsuf-<bp3 #ݯ75ޤh^4j|lBގ?3M1^EM> l-<Eo בӏ0o٧(kH|w7^|tvsbS lVl׻D>TϚ WUwTn5+eh[܍83zh7Áܯ$aۢ {bOxĀㄫõ*9{Jw3p*q5oΒx'>7azE5i gG`vy`(ΘWt0vuy+I)9Gkq)aw"lIamA#+T?S*`;ZfzEULpA{)Tu=ufiMt jNÈKr̘тB!۬EGA.QG77>h4b/ y Vyx6OF+$ݫk'Fk֠(ix0k-/Ŭ3ުb={3aj2΍ tEⶈZT؇̒6=Nɞ#mqԓ:ܺijtړ+IAͿâTO?ا<(D7IV^4+\zf#r3ii(A~nx*9{<z 18uTR_h]nَ)LfFfKsU~g6AA&ehf/O`nOȬ冼2Gbko_6WNSLx=:79lb~X:, ^Cu1+'t)ZZNZta8Bys7.FÈfqLf> . Z @W1R~Ly7dfe(>ӷ,Ə/I.K֌˟܅FelMЇP<״uh_Z^cߊ9?-1-b/#oEg̶zҝj՗VWV6k=%%t =.SA.oA>?+f}P"-ǺR"/Ěgo7lhM9?d9 .𪡐SѥsϢ0IPb"i*ƨ K R8xhK1>^"DЮ]ܺO/.`|h^u|dӶ/Ȇ|"8/ ޴"K@Oo;oX&y/vxcw,3%W\UD +JN?c'40~UQ F^fKkxҽ}I5>|Bogy.o+l4Gh\'*4Պ Ԑ=c(ZP;6P[U+4୰.t\a*t*&tzpRxES;L+j~}3ջjvjY%hIю{2*nr>7:x-*_9{,~?Kmүt@ ghwtE0뇆~H&lP$YO$|ձݪw}-xϲ s9[h yjםh zV:¤ŝYHuֲ˒^15ǰ.2d&m"ϐ%V]7?ݴW=BZCFoL)&  ꬈zdۚ$+L2:tUƿZ֘/6+R:0|nDk2^nYc5X0R?뭩}3!wQ>W{\oMD3ps,:iF|ux} S`~`z 8Ho lLM-9pifa|[/nJЄBpiihohH~eM}cUVO{8y2dW~"`{. ( МMDrXNjb˓/Q!V# B/wnW0n {;L[v 0no~bynu7dŏCȳ=$!Cr{y_///Ä3<8 O'0{Kz} "w<^~xwc-N^ߡLΰ9]Mb%gH6`} Z}a[ſU.+^=FL2j~Td ^w؞/K{|%Ʈ[r/-.t7VX!ǔ93q?/3c'X$`opbfsy82.ݥ8y 끓I_8 vDՄ"7XoL3F0_y /'$)dY JJãL1w{[oVkkt\|>ެ7oa}˻_ZCQ~z-̾q޷|j| j3[7?ϡYoua:ڣ ҿ0yi3r^,fskآ֏7^b|Ƚ~`׾ȟX os^ܱnc$ve C[fࠏ77ְdB\=>lVmNź bٲpc&C`0[.-8ǘԱ}e镥h-VHX%xz\mQs̄[ AvKyvUV MGezfam ~PvH+I,FïOwxx$1LpNi z6],},|v'}; Aظ\#_R^Ozj4]}ZW==j[A{{38e>j$jqoq˶_8yأ?@c?@>LA {r~yoxz3|z4gd#r`m0-$|=Xޯ75m!6!+V3&D>VMe}rC0ӊ}3DNVo"MINE=YII  L.ԧ!±Ooz1@;HWLp];\Ҵ?mR: pa8,pJ:ݨ%`O&vPaYupZGp5llWP("prLSuz $N$| i  z=>GFD;L/) ;rU+?~2G!IR`L(tEƽƚg&/|iQX7#,Zx[)ӑTf!qPdF~~\$4 <^fEd{K_epOq2*@>ƾw @5,9d|[y,J$(Uϕ ݧPU`jLh釅X_bditBPKDuJG(SgOa9G(dK%EEH R28[@\͟QR&ˡf2, ZT*&dgdjd<İ3Y+=" ?C#/J}V4LX7KЍt_]3=+nЃÀ 7-`Æ_]lY~ }93< H$J39pߌz >" N8jR4HUW~ qO"hYMK4bAɷIfO %E[*h2vx0<}g9?n%qr̝s: ]T`an/ a x6Z? h> pz\趦-&~3 Q 7:mzc';Rd/eo֮#fJKn7OS}+J*SYţGIkqF*0L&&k*[:4ϢR ϰDI*%w¶T`w.0H)<@+3?|O/TWip ^h F~}Zqvn`\:T:H{ pE ?2$[gPY-6aar٢nAԾay(в^q=v}r`=N3NN~(!ק>GɅuTDQ2yI۽?('ItJ9Z䑥6/:7 %m{f޳$QpªHv&[vL*c! b`baRVrܑMFL:Lr:#Us"Q'9AaG`c%]G!7pg(eY ^E̪|W>L.ҷs|_>ѐMh0 qY`Nd\ rIXx$ v={pf^LY/COdAx y^}`hRLG/xuKb}Yrqp6ӡ{7xYND/OeXNwΧ\ j.[cI}u2$[0l] FEln8 6N1y#۱mE鳪˩ t>sV\eDJV|%x.2T>&O\" ՘cP=X=҄ʙc(?g#p_; r1\rzwpdjw|ڟ0-fiy ~80l" e\"%$]!_Կ-`yL".6X7^?ȿʣf?ЈeՏO{kqE]93PBcZ1LJ iizQ@1-TK? SΆϙ)jqxu haw]c-A&~7}5tP :O'6d,sO;d0x(S+ĥf8e #|!eYԄP-1܅-brc7:q*`< #]R>xjQ)2ф ܱ"@HhȍM6-H ѰfDbX\3i9jͶ 5 )G.lMY}9Q7AJ0ՎaTLXU".֋q!'ĺo/Jx1 JYh+h+yBʘ-|;IZ[E= -o%$J+} n-0x%Qdt<1'sh.V2 &\2Qaa""fA}Ap"D tVH"8lע(>DuoU2 T~X>*¸Qtun]?sE1n8n*p#(>|NҴcg(VJ8.+<Fd3Y朆5OZ&j"ɝxaB"RTʅ&Z ?E\^[TR\_iQ1,DܚӱlFa ?)%܊x$ϵT@0& ]קYSC*v۰y$ {ښ=)86Q uho2N"U;OUe;ܓ͞ p rdő:!}ժd pɡ(cPe^Y4PuZ"@GbmcH-{5c 5{QtHjS ݄O9D&sʑ#֗v7h$~k$֬TWVQ7MC'窽$i Op)x^EzUjYx$N j@E EZkA=o9sPMmυyiDu PXM(Ҥu z dzt'n?!^Skc>v0G50\oĞrz\^9fԣȏ1@|y乄,Vts$NUk"&Tnra[ 2߀ @E} oB˦B #l--^y:% Iqٻ8>k!0,*0Y}:7n6PVHh̀zP(]JOHLZsq] qԅQ3tI#\:wR BƉ*[F*#B xڝXu2'\LMeiOpEVדo׫`dK|}i]=uTINlF(N'сaEPEcnJVpK.n

Ơn) QLVvʥB^٤r}&9`W45\*JFl. ZF$/&Ü"aU8) y 6A| }Q1.3ˀӥjODm0̞W4iީ]'t_]Cq^^XCR$h{$u"(|IB{ۓziۮY(=9Fw*V ?3.RՍYC* s,%tI=-s߉_sLnm&apy\]'4NgHI zd&pD[wI+sO)-LyV/{I_/T$&Ml2O|%Pl7"w](-@6/^+s$m.v'QЫL/ҷ^&)JÒv H1gV0,L=dNy[NFsYnLGG44U_LMT.JJ?JWF^K7u%m3=S&挑r|ݑŤcj/~keKJ@_5jK &E;Lخ:+,w]_&PIt6Bx S}!"v.59a>:T^ׅpksS:|yv^}bp88G>=L`aL-y.~rRTө%ڔT[9q/. F I3J`*ݶ9xdKe˷<^mxqkdP;fiUdh4ay7\W+Cg눹sD7i >8^I;|nNR73gD9y(7K9qJ)y-9F6DՈ^[kW^Omstcs%TYW(!(:zUS ~V=Uf,a$5׵ઽHB~^@/!GTCQϠVNBV })ExFF 6[jQ,dSf7+L?,SeF^(jCWBzGCIZ"X2=YrJcTl0iN9d"%HZ^,JE5lOn5M'Wγ(L\_ɐP`,@~Ie}S@v?Qh"H cBŇjYȐD`yn6TODkdS} W07\B742J^s,u :xdBI[h M=t!*2/4j{zV_SeuhX^zETWr*S) OQ^HNxqP+O\ \P'd^Uj%S̭x>{J'3Q|ht+/&{8AQ'%-^`ez߻ *]_!>QLΩdnqP#6UѥDu>dB <;{ ]/WPGNff0[QI)'V$;&j7ir# AwuB| "/( ; m6B5 8]V61I ,Z%@8$-;/ med.<{ XdTAÎ.aiK9<]j)N~S(p Sb=E R0Q h_@6w8׹Nw) >*xUORKtތĢ:wZ5/IwV88E&jqpʆ%D`]) ;9ԬN3*|SyJ-u7~6di\B('$Rb\IdFڴrRV?0>@ۀf_-"Xj?;J>4 $?pBR\e> AVVɤ\zsVKb8)͘(CZ[ X%!:#6Q& ULUàYTDOZg}[ &TauޝMK( zT>(Kfv]FHr_t(ba<|~a;dy_JNۍxK_:j~R/h_;O'`] XǛ&ʙGp0sTgr_2|6fW~ցd ol˹Va:al##':7,/SGt(Qg JK2GMk4$"MrBJ4eʁ)vk5z v~|СdBAKXMCs$M~:T~Fn}$I6B4M)f$NsɋSvʎJxӣC|}Tvȗ 6$݆q8qr6r='[NqrXd@6"S,&sz ]UhUINo\Gcf~’n3h*[ƍ/8} U :Q /EP9tldF-g6b~S+Ps-S8[īc8'pM*~)@^`(KS4߫mۢ{"Uz=@) hL|( M7=<5!m@zG&\va] `FRx/90 SüvWU)֍c*"H(͏Ө:\&(7]V{"VQn]GaSoNz.2Ҫ*V`u;x5îdZ ͻa7DCAwnIz9GL Z;*K.Dԧ'WŒLJ%FEw"Ph6jDmq]w'W6.] Kqb#ty~5|x$Ayצ@[&ޤGP6Q<;ra ?ZEL$`zت +n]kl*|G[t NC1p xX;K3xoԗkUfF3g'& l '+&T/*p>_լewT_l' }ڏw)T޾0Bƺ3 .p v`52톉_vB0 OY-1 '_.xm? ZѪN'ɖ#hlJE8A&ŁwiUT ip x¾-a+|lfO؏p|h_?f,]YJDܜn ]^%^ {7#l[$,&lC؞FD_9jq5(_d2EضBP690a@ӆ'M"KYm#諄8zN`{6@5Nn:| T56cJl&,vT* |ܢp8]7~݌]\뗜 5P 3a.Ŀ9K FAI*BCeX wI'4-_N0uߤJ> TDK!iM7Lkp>eܩ$u֗ j>jJU*ldfd W( DyϮ ӌnPA,a$<~>QrT-8 J olqeuSѪ܇8DMκڪ:Y+BsŒZ籙O-pH1RRsh ͤ\BUk%[.5 j(ã+j_J)[Tr/4/"PvJjr/f @M_cxBx[9<4nPl$Rrۚ7u2ԝ%-8HYzu>0 J *5NʁO<ō"4vڋj3ZGMߥeiØP&L2.<>G rHӢQy4 WMrppT"pQfaՄZ)YLU>[?C*t?w% of&?k^6PSbawPd,OWTZºBiEc>!JO?90ιB[Nd٥nDU"P5J3pHU-:P郡vh2g1fdcA_itܵTr@d!v$z ځT>-Cun}7;5A~RxQ 7m$ق+~M=jLS8kx(D]4ji6b!F>Y!\9gv xqLt^gߒ'r"w%% Q9D%`9rprZԉ"^Q^0c3V$82hX V Jaŋ­{,X %F0(g =%GEic륫%$0d{쨹Ba DիERc)/H\q6 bW?2G ,1]x|SFV-he ĕq&2T w!"H;+\^>WLYBa> J&Q- Cqe{"rr&Y| Tbn^(5ZfrՆE-my%IgDF@=dY\ɴ{E'%*9Td&-j++x"_uОj֊- R0$Ir u7#CZ\2$qt4VTQRmBqg5襬š T{cp "d_ÓZ 7Ls#M2y5^<T+yj_p@H-ӌ}'ku#8n!7.;Ԡ9c/S'DAQwMJŰV`8Iӽ,^+'PLG4gr̍s~%6npT c; $%[*J_v%<NfT0PU cMC6@Rd0䅿 Av}t|1qA:Lw<%KӰrjFurkK،n9NP-k(sj͔EaA5yʦ]X= Mu)Gmy\k.շdQ(2\">[-1f2_OW:فr*ˉ-Wω Bw ʷe(ewDFAwH$%}M๥HH&ol%)QL0֏GoQ߁"h<aFy$_f)m[7NꕎZ$<^++Ԇ ϚF cA? }nKS4. S8- K%M#8LnGЍu27PQGJ)s% ˿{;?ҿhiAb'mt#FJRi΂)|֠\X s/ M$#rBpd kjX}L5ErC.Ҩ׈Lz Oa.Ĺ:<K\.‹@Iy #6c[qZ??.[l㝖n @Q-jP)8Ʉf&ޕ^gPc:+yr pJr%^_銡$^GCU2dP ۵,ܵDv1@v`Fypha JAOl3ךz1hI݄;+i)S-X<^KjpŻ2z@H+3>mD_&8 qsNC+9̋t@7M< )"ip<ɏ{<4Em?؈9Llq Ó*($Sk[{%һ.ꨎԩ PHH%w!jBvLഘ%Pb2P$ -wĩy>܀7c 5dMFͱQUh(0#ō Nnx˧ ]VV 4 [A -%j|/W6hԀ.IFҒWbR9e^y]>}IK\Fgrx.mVD,.PHcee^ft/ιE?1NHt]UQQjMrCg5(Q5$5ՈfJt -U=ǒ\Wb~QZ^W)\hde…_P ZsE(%N1ia3=!>E; (ƈ1lC =;dIl .=ŻÆ{?sQQR4O!=Sn2.TL!93A[ B+@!3&TRoja^{x'>R)S{JumCjy+NW):gex uA)W-uI+Fs@)sѬMW7XOCǓQb_:CD+R-R";Wȫ\$=娔"Pja[ra j@T kF\-7{ Pfoy A'GZ}sr|V&2b>%0I@jJ1Q<v<752 AF"T6!y !RPf|?@IO@r A6e+U+d QA&rNSƦ<ɯ**|qu7fq}! } ;A 3dSEZ%^s$#1)\b|kځpUı'q<-kC]-^!sV/ZX!%\&]}`AZYOsޜI0?4A_?O53sre6M(>*I$IK)py *sD@%YPW ?V#:\ܐYRP;jEt?('@!Jfzi>uQ13 C$H yJ̥т r~uvT%(3nᏨU&+ڏӒk80Wrx PvSIV]Zy\YP aPeFXTv&}iy>7*ʯPn߅Q--O"'+2JnBE\GxX L y(,ͭZ+ \bΚ&ŊRx\4W=K\y*G!5jUY?XNM`F (,>UGG %5Hy * {R CdRUI=a9Ӌ/Kޝ5FXd*,sk인GZO9yѓ~Ai)K^pI ȴ4\6ǥ^ƸSe QruP*MKB<|7&0P`"t%@JiIxsGe?W)}rH>fwUc=MhX$U "Yf^+"@&D@ + ? nD| ;ΜP6Ęf]6]Z "Ԋg*DpTW\9VʭFTI".c`VH:֤ 8V }Ƥ'ɥpD0ՖMEA'6zP]!ש|x1a6Z.{:A2{vuN;=^ۅ<2(mvQ-g ]~"p\u+4dPÅJOK5I*ͱKFn良ܮú?2<4M}*UW͓YأU!ﵓ>M 5>:?v^1}Ab3T QP"}NZieTA ʤ>Xpdn@K!zֵDפ| V N@I)"$+zݳSJ<\E3޷T{Q0 P"ɍڤFViv:|m2YQ2cz}k9yT10m);A4KIH3@wkg.ٴ H1Х[oo+طY"{n}Q&_G_?dV3yfi)40.u/ՠdAHt@ԍ% Ʌxu?i'W,CVfů.jZb;6h!x|fwiݩv[Zj uQ)Njվ䢎4qc[JdvaA2$H̪Ke `.`3<.\| TFEf_"Ԭ9iIyV=Ta4C]%L%Fa| 7Qzl${-$[Zr6l_^SȪLuY{v.z"\0¥cN18}|^& М|.|DM_!˷v]Z(oF 8=`oKy~UkrѺ}VT钂WMᥖ\y(g!ptG*.\‡{?%.Q5e4KQ_AWd"[ʻ2L _kv^y֖ Hrk{9Q&*VG@b#:7EwjVnIӽPCϪW՝ΣZifw։cfnl36}vQ~X~|Ѩ23*-^- >de-oZU=cl]GxMyp߾DLYN8~$R6*l}SO. #\=#ywVNܮJX*C =?r)zy4ܢu(vi7$=Ӡ\S}n9VLu? 5<ąg|0\fwEӖ|Rqxn`j\(#s_Jđ qӼAoAj"{.W3[|:)ҬNuhu&mh5"ş^2?œ J|#Skw߯t$\""J^ٌ"Zn,$I"_#py u W˷6O4 Bh\]uF;>Jy\@zUMK*Y9fQְ ܀O/)t/ɬϓs'A4*lgwjg#,obj>4]l~Ͷ&S~0!2JEN>?"=GeX4y 2e0ՀJM ZmetxG*+1'`Hl *MP1jhp$FX:Y3ꡰEKY^BFms^GZqЛи fhqAnX߹))y H&W avl[o_y'Ua.pNؓ&+?dQG"aLfx,У? G0+ը7_ ֋׮8_ٺյrvbblre$vW!'Kډ1B n  8jɖӒOZN,ft-_Etbkr,FOE8B GNRjWb(1_z.N+z,kOc4*: RNgq&ZZtv`VF&M-f; +NܢꚑmyCA \\(fVgA@[1QU6Ћh. />UXZk]6Se#^a4'k.nҙ]EQlk]@339GqI;Dt}GF[97Ӓ^$Su؈m>VkEGCC{)H RǎES<^l(937,Q>:cj\wlvRdS;Ҹ/BJ; 3.ۖ~"7ǂ 1cNwRRS4X= f[c,a5u1ȚگǻZaia ⇜h.` ?Pyc5&;jjrNyz_%;4ޮs-0YNKڄ<# ܃b|C&cRec]Fm)]=xXaL^(hZ%'<(zQžKEYYT^Rsm>[ GAq+c3 9 =y\ם.?m]`oNi{56]v2K͡2Syrg,3/# CsN~sL.k+Sڀ'*IBl@o'8~0D-t|:dCWwD>Qѓ_4d)WK?fy< g64kdQ=AcEj z!^YsWEF6jN'qx 6x//:XL ěn +OޥHcOZ0zv:7uaMl*3S{ Z}<Y>79'rb耥+T`O4iǒL]oAPmeehgk&r~UGoYt[e)<(y)X0]Z^ WGEc.ʂJZɅUl@]$. 3>l5_XGMFG)Y6;̝YWCFi~g*sY6Ӯ\9v5_q1l,jD Il.|l~-?iωdN(b1ُ)SYb\Z$\GB<G<ELa0+[zt-h.7puM@|n1Vu^׋Kd9sU2܋ؔ)FOZ{܀:쎞,}KDi"9dF-R>y&ep J[m}I;\,򺸽uwkSWcJ?EO 5+1"(v̐z7H"+>9'2ijMg{j2[\u&B]Wi{@$CJ/0'}C_Cq NDwYӱ \čEad0%rߴF0h'7WHpcQ.{WK7V*L-E~e>ڈq7_JvnF꽁p#Al^ %Ɇc ]+9Zεs"1C2Ώ" + &M>>(w⸃vR tPۑ2=2D,,z.֥RڝQdtIS&̘eʡm,qhUZK.ZBMyy=fXxv'P}niHJkI{23. 6FyPSXɗgh㓂 n4}D1ayvb)J7tY֏hT Uƻap#\|9O @LerC45yw]@z]jXݲ&^K(6B6uӗoë{TTٓl Ц˥usAs4+#e0trE"3MIWv- t3IFŋXv&7dTM0%3oJm*X\ hz=06FOnn!v,R`?#:bǟSyeT0r0h5KX=uN,hֺ21 JFĖїlV)ͱE CwundhA'Fd},n _؊R~ر)12$"eQ%5V` <k Up\Ri:i$, ꒨D$p3K~n}51[YcG ڈ_w׃I\1(i+xL",ӽO;&q]둪6 3m!}5[yG 8xIӧۘ-(n  8s WƉr!S 5̲;fުcS 2J#eQxYʕ cMgqYA&ڷ#T\$^92~=0Ybt͋kȸќzy9PsEU._G\h:["M.DUG FQc:htSaUUVNz1Oy1QtϹp`8NO;Ng_J^FA}FEc*`a<׉2k 繀 חEwE:V"4,9O%B&ķa_.Mq L 8';f uvl@QEa8q EH=K%dPWQz^gJH1[ ~M.Ծ␋i[ex-)x{:+t(UBFܫd-D$MzT5}8th_Ц[MVj'@ތAlDC#t$yC ߋ⾅g@-)%҄{ \T0 '%qsJ*ڐ#mWt{&#Hj^ :ɥ!5n[2HzDmmNBi)tU$߾3no--BUy&2f0׭kMY:1b)@yՁ>ښPGy^1La' 7$f*qJ ^|y P>%lOoS6. H Jκ!ЈI-rMnDN}$}ɤpѱ2+]lTgm.6gWޖ@z&uxĪ9L,$ V ?cC)0Mqm00$Lrh_+VP/ 2/S)FmC M,mDEH6+izƴcL\Eb m:rI>P1,+hJS_e&Iu+t\* 2 2MĿ^jӀm5\+=PFt^M0_,8tUFn Қ(XJdK~d@\5dbjÈ *7E#Q@3~yeM nm@ztݷvlGkҳ#qq-FדjQVE#չ(Ea%P G(@,,YIV .yKg/"P@gbC,`хQK5|/$W!x 1ųbdY3ѲfN*6?$?EqNP3.!l'@R8G'_@z/×XՊgեJHGJ{;Tx4+V[8Ďk1Ī 1A t8&y1IPr`']6Fu50<BxNj=-?`JUF*=~$,7x;O%f*[-2U)|~Tg9׽56g MS(N1W'C:fsC,6 ;. @reR: -/e##G Ba_6!ͽ,FP,p /Xo89'<MHB2V+]aFL p¤[^ԅ-ԜBޣ9nݜ]dOJ )e/2,QVA[QFdB :MN xs5 0V>s#]:P4 P)e(Ҟ<^71g跤 -쁼h)OTu*~iq? 1~bd?䍔^P}| OJʃMfUF0 #[12mTՄJF|zsZ N\WbL9ĦJ܆g H!( MQm}6t`lܖW7ne~QݻtkjF=G`.3ko`ݏ7i2yE?K0oC?]d.R0zfֹJDm ! :i0=C|=O'%Ha0XQ }*ԾiT=ݮ(#qz8b!zr6Pߡֶb ņ B ъwEB$Tv8 gg zt6 Otkh`fr dszEu{4HT(Y s /!f8Z%=+h~B Wv8.ȸKn,3hx߇-ik#y60KqSFZHeJz3"p*X6m\7Y0r҃4~ηjAWn?ܪGZ([o?Zey,n{5^ "YRTtHYR(G(THVKfmJR8*s^i!boðd)ᾡ!C١e * ZN56o/ժ-R R"hIcB~آSq RO?lqlȌcPᚱ_4lS*X%kzSR@ZЏAr&[bfIw -ʔP)-EG `̲e #l7G_C6,6;T u v|/oIHM`$`/,\0zNnlnm;-׉Ŗ DLVĀQӪ2F.jP-C쀶cUzh&:%&:ߨDuTF,x௷ma20/RWs>xi媾.YfBY9x2 ۢVgVZeC3j.fIZoC}dmB;J=Ѯ7˖7Yw#rLd`t-חNs$E9Ζ|qlЀ[Q)1Y}wB:%w,s±8i iLCoK5'`JsN#؁(V,u!X;PeFέ67@"{JssG''T Sd(JPszH0aT"ǃT&I cM$dU[waQ?)%րgĹ7^HԎafb:Gb"!peJ ȱ7x6{Q ь3R> ͺuS64u0"UiTLޝZ*OJLsbٗAI4#tB=]5\Q >з ϐg4r{!REٔav ע)T8 EZIUTZNEǷ#>cFZ0q# "(ΦJ)m%5IaT͔疛棆~W'dY:n}ɷuݥِgB,_L^Ly*aD3]#ūu9E 0PS Y>&,lz(k9\?)m/ ŝ4w VV Au.LhTaGk+ C1&3ڣ>'%>q;oem6VѼa0o&XU]럏h7r,rI%+۠GJ2uVTn{ʶFW>ATy')`dbxM:|~:1:(X+#J[nng؂MͰL4U Gp.@:z3V?d2#CȀ2gޘyC#|OOEg-BsM<L'Jw_i"N3@=jq9LEŚEVK7D"&[z#JnTIFbtd+O7$˷WU)zNEEqmX 2NEy9̙fˠ1COSqak aR~PaozI*$*A_ `fY(D(tR/j<̔gyRfG@69Äp<| Dl>Ha#÷qGpΠG~3ipb 6ϚBh/gt[a/1S K4wZUo`LP,fqKt)yzfWEY4G;܇I8njE[Z4'1rIKs# keWh"azy} z],SoKٜiq|?XFGzO,F9$ HIy85.( $IiJ>KFSuE:Aa6IQַtZF6'F;!i5@4.SfkT(\v }9foA㽉qۥH8!T4$D^@o",c(Iӥ0u99Fg"kl+lxЍ򕞘g Bw+]׫|\urY}CX%nvņ+7%Gۑgt_kq^3C|#g'mkwccL{74wuG'RX1P͊ǿMT W~RI)+NYik kCL/9=6w[5N$yuL= b|'5):k3bn "gE4M~̪Ng^F6iVL}[<<2MgSYEΟC 8W<KK@PXGNFmK9dDJlÞwFzGt;k2i+h2 /AZof0 bnT5MTU\509*OM:Ek= 1|a?QPuK"[vo8`{n1ʻ*^IV3J̊a,G-mRf2[ zDO,O#>*sf}{K4Yn|:oFh&mRm1 or:bꈼyR}7}nݺ0Z0q#,"Z"O֦naJjf~Y<._9D o !<0*RgZjlg9Cymb?/UBƈp#PJo0|vlZIblϡ*)=+FS\OEWXgD"~qx spZPxi\1^W:5rYwk ah> Uյ=P0CƁqtt~BΎY%wq!ʼnQ/xpr0 C+MBqQ;3= [5#;寬M+DGHd ܢ3R3jnF?AgPl\nа5ra TtwF)4&b!nPie|B2&MҿIdo6:pj21Uw-4IźFOx]3yuNx||@ W4g7A`z^Nۡn{՞z9)h$ǛUEm"Dp02jJ/nUfH:"69` ;^aJZO[Q.0G[: QFne+K Z*=HћZB;a:+lD6HM>iE*Wfm?0> ų8ug:oᛊA| jWZ!F}4_|t Nέ5Nh>NM 4I=0P:xe`'}= =ZyjnB4Kg|}2b(%&pZU{WT)(\n_7ii.UHuз}S"%EEcvQlN\]+ZX+hHDu:Q9#MQzPOzXyV-LhljP_a2SЯTVs,OmiP4%r:/ĽCZrt0C?{#'U:%NŰDdpN`5P0OY`"'t`9SEYŴ㽀 ;ۛG4êaQ]Ԓt)+PդrvǼA[|5JFBkE v.kɛ|0e|W۷۝Qj+E[ҋuZGY>\9X78V;ol omȎ ٵA< { {6Ⱦdd9  < @M!'qrAvq'qbAv< ; ;6Ȯdwd }r0bր ms<;e ;}ˇXJ+};,VϔEt%=NWl6U.!#ezvtr%ـ8|@F--iN!vw:vgxNXi9G*+}J): i)s%nl>XyoQo_ 3tZVC5aЇSO~ nHJyn yMqTCެv zv( )^FPƩN%aτxܘ! =~VA(&O8Md}({}a -߮>;J,ɣhБ-0>_ΎNO7n;շ&w.\)ASW^%+7ѫ rZ> #O|gbw4(q'Meybq]Ďr_sQ\&ߩ>a$8Y-$S df|m'L(x ?W37i 8؟8%Mz ;tv4v/,"@E*%jϸp$JdِJMٌ %5L|K|-#"p?) `WS4Hq-3M#ޒ8`}fnIjI7@Pe)=ROtľM|C'H9Gz5WF:}y0/0ƽD"r[`T޶Xz:h": c_$7&"u'"oAO&x\W6a cD4^ cbwgSpkfAÂA=c=Hɐ'!WC(r\&HbWH B_r}CDlb8pu?7h዆QѸ,2+1!M$ "}~?ئ@+&>Kdb3X iaXPh4߾K} 7ԍ±"޽lH<6/c^/SJj䛢6J)J(;>q9O%i'\)~k6lansible-lint-5.4.0/docs/requirements.in000066400000000000000000000005111420171260700201010ustar00rootroot00000000000000ansible-core>=2.12.2 # needed by rules_table_generator myst-parser>=0.16.1 pipdeptree>=2.2.1 pytest>=6.2.5,<7.0.0 # 7.0.0 dropped support for py36 sphinx>=4.4.0 sphinx-ansible-theme>=0.9.1 sphinx-rtd-theme>=0.5.2,<1.0.0 # 1.0.0 broke rendering sphinxcontrib-apidoc>=0.3.0 sphinxcontrib-programoutput2>=2.0a1 yamllint>=1.26.3 ansible-lint-5.4.0/docs/requirements.txt000066400000000000000000000026601420171260700203210ustar00rootroot00000000000000# # This file is autogenerated by pip-compile with python 3.9 # To update, run: # # pip-compile --extra=yamllint --no-annotate --output-file=docs/requirements.txt --strip-extras docs/requirements.in setup.cfg # alabaster==0.7.12 ansible-core==2.12.2 ansible-pygments==0.1.1 attrs==21.4.0 babel==2.9.1 bracex==2.2.1 certifi==2021.10.8 cffi==1.15.0 charset-normalizer==2.0.11 colorama==0.4.4 commonmark==0.9.1 cryptography==36.0.1 docutils==0.16 enrich==1.2.7 idna==3.3 imagesize==1.3.0 importlib-metadata==4.10.1 iniconfig==1.1.1 jinja2==3.0.3 markdown-it-py==2.0.1 markupsafe==2.0.1 mdit-py-plugins==0.3.0 mdurl==0.1.0 myst-parser==0.16.1 packaging==21.3 pathspec==0.9.0 pbr==5.8.1 pipdeptree==2.2.1 pluggy==1.0.0 py==1.11.0 pycparser==2.21 pygments==2.11.2 pyparsing==3.0.7 pytest==6.2.5 pytz==2021.3 pyyaml==6.0 requests==2.27.1 resolvelib==0.5.4 rich==11.1.0 ruamel-yaml==0.17.20 ; python_version >= "3.7" ruamel-yaml-clib==0.2.6 snowballstemmer==2.2.0 sphinx==4.4.0 sphinx-ansible-theme==0.9.1 sphinx-rtd-theme==0.5.2 sphinxcontrib-apidoc==0.3.0 sphinxcontrib-applehelp==1.0.2 sphinxcontrib-devhelp==1.0.2 sphinxcontrib-htmlhelp==2.0.0 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-programoutput2==2.0a1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 tenacity==8.0.1 toml==0.10.2 urllib3==1.26.8 wcmatch==8.3 yamllint==1.26.3 zipp==3.7.0 # The following packages are considered to be unsafe in a requirements file: # pip # setuptools ansible-lint-5.4.0/docs/rules.rst000066400000000000000000000077721420171260700167320ustar00rootroot00000000000000.. _lint_rules: ***** Rules ***** Specifying Rules at Runtime --------------------------- By default, ``ansible-lint`` uses the rules found in ``ansible-lint/src/ansiblelint/rules``. To override this behavior and use a custom set of rules, use the ``-r /path/to/custom-rules`` option to provide a directory path containing the custom rules. For multiple rule sets, pass multiple ``-r`` options. It's also possible to use the default rules, plus custom rules. This can be done by passing the ``-R`` to indicate that the default rules are to be used, along with one or more ``-r`` options. Using Tags to Include Rules ``````````````````````````` Each rule has an associated set of one or more tags. To view the list of tags for each available rule, use the ``-T`` option. The following shows the available tags in an example set of rules, and the rules associated with each tag: .. command-output:: ansible-lint -v -T :cwd: .. :returncode: 0 :nostderr: To run just the *idempotency* rules, for example, run the following: .. code-block:: bash $ ansible-lint -t idempotency playbook.yml Excluding Rules ``````````````` To exclude rules using their identifiers or tags, use the ``-x SKIP_LIST`` option. For example, the following runs all of the rules except those with the tags *formatting* and *metadata*: .. code-block:: bash $ ansible-lint -x formatting,metadata playbook.yml Ignoring Rules `````````````` To only warn about rules, use the ``-w WARN_LIST`` option. In this example all rules are run, but if rules with the ``experimental`` tag match they only show an error message but don't change the exit code: .. code-block:: console $ ansible-lint -w experimental playbook.yml The default value for ``WARN_LIST`` is ``['experimental']`` if you don't define your own either on the cli or in the config file. If you do define your own ``WARN_LIST`` you will need to add ``'experimental'`` to it if you don't want experimental rules to change your exit code. False Positives: Skipping Rules ------------------------------- Some rules are a bit of a rule of thumb. Advanced *git*, *yum* or *apt* usage, for example, is typically difficult to achieve through the modules. In this case, you should mark the task so that warnings aren't produced. To skip a specific rule for a specific task, inside your ansible yaml add ``# noqa [rule_id]`` at the end of the line. If the rule is task-based (most are), add at the end of any line in the task. You can skip multiple rules via a space-separated list. .. code-block:: yaml - name: this would typically fire git-latest and partial-become become_user: alice # noqa git-latest partial-become git: src=/path/to/git/repo dest=checkout If the rule is line-based, ``# noqa [rule_id]`` must be at the end of the particular line to be skipped .. code-block:: yaml - name: this would typically fire LineTooLongRule 204 and var-spacing get_url: url: http://example.com/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/file.conf # noqa 204 dest: "{{dest_proj_path}}/foo.conf" # noqa var-spacing It's also a good practice to comment the reasons why a task is being skipped. If you want skip running a rule entirely, you can use either use ``-x`` command line argument, or add it to ``skip_list`` inside the configuration file. A less-preferred method of skipping is to skip all task-based rules for a task (this does not skip line-based rules). There are two mechanisms for this: the ``skip_ansible_lint`` tag works with all tasks, and the ``warn`` parameter works with the *command* or *shell* modules only. Examples: .. code-block:: yaml - name: this would typically fire deprecated-command-syntax command: warn=no chmod 644 X - name: this would typically fire command-instead-of-module command: git pull --rebase args: warn: false - name: this would typically fire git-latest git: src=/path/to/git/repo dest=checkout tags: - skip_ansible_lint ansible-lint-5.4.0/docs/rules_table_generator_ext.py000066400000000000000000000036121420171260700226340ustar00rootroot00000000000000#! /usr/bin/env python3 # Requires Python 3.6+ """Sphinx extension for generating the rules table document.""" from typing import Dict, List, Union from sphinx.application import Sphinx from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import nested_parse_with_titles, nodes # isort: split from docutils import statemachine from ansiblelint import __version__ from ansiblelint.constants import DEFAULT_RULESDIR from ansiblelint.generate_docs import rules_as_rst from ansiblelint.rules import RulesCollection def _nodes_from_rst( state: statemachine.State, rst_source: str, ) -> List[nodes.Node]: """Turn an RST string into a list of nodes. These nodes can be used in the document. """ node = nodes.Element() node.document = state.document nested_parse_with_titles( state=state, content=statemachine.ViewList( statemachine.string2lines(rst_source), source="[ansible-lint autogenerated]", ), node=node, ) return node.children # type: ignore class AnsibleLintDefaultRulesDirective(SphinxDirective): """Directive ``ansible-lint-default-rules-list`` definition.""" has_content = False def run(self) -> List[nodes.Node]: """Generate a node tree in place of the directive.""" self.env.note_reread() # rebuild the current RST doc unconditionally default_rules = RulesCollection([DEFAULT_RULESDIR]) rst_rules_table = rules_as_rst(default_rules) return _nodes_from_rst(state=self.state, rst_source=rst_rules_table) def setup(app: Sphinx) -> Dict[str, Union[bool, str]]: """Initialize the Sphinx extension.""" app.add_directive( "ansible-lint-default-rules-list", AnsibleLintDefaultRulesDirective, ) return { "parallel_read_safe": True, "parallel_write_safe": True, "version": __version__, } ansible-lint-5.4.0/docs/usage.rst000066400000000000000000000074131420171260700166740ustar00rootroot00000000000000.. _using_lint: ***** Usage ***** .. contents:: Topics Command Line Options -------------------- The tool produces output on both ``stdout`` and ``stderr``, first one being used to display any matching rule violations while the second one being used for logging and free form messages, like displaying stats. In most of our examples we will be using the pep8 output format (``-p``) which is machine parseable. The default output format is more verbose and likely to contain more information, like long description of the rule and its associated tags. .. command-output:: ansible-lint --help :cwd: .. :returncode: 0 Temporary files --------------- As part of the execution, the linter will likely need to create a cache of installed or mocked roles, collections and modules. This is done inside ``{project_dir}/.cache`` folder. The project directory is either given as a command line argument, determined by location of the configuration file, git project top-level directory or user home directory as fallback. In order to speed-up reruns, the linter does not clean this folder by itself. If you are using git, you will likely want to add this folder to your ``.gitignore`` file. Progressive mode ---------------- In order to ease tool adoption, git users can enable the progressive mode using ``--progressive`` option. This makes the linter return a success even if some failures are found, as long the total number of violations did not increase since the previous commit. As expected, this mode makes the linter run twice if it finds any violations. The second run is performed against a temporary git working copy that contains the previous commit. All the violations that were already present are removed from the list and the final result is displayed. The most notable benefit introduced by this mode it does not prevent merging new code while allowing developer to address historical violation at his own speed. CI/CD ----- If execution under `Github Actions`_ is detected via the presence of ``GITHUB_ACTIONS=true`` and ``GITHUB_WORKFLOW=...`` variables, the linter will also print errors using their `annotation`_ format. .. _GitHub Actions: https://github.com/features/actions .. _annotation: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message Linting Playbooks and Roles --------------------------- We recommend following the :ref:`collection structure layout ` regardless if you are planning to build a collection or not. Following that layout assures the best integration with all ecosystem tools as it helps them better distinguish between random YAML files and files managed by ansible. When you call ansible-lint without arguments the tool will use its internal heuristics to determine file types. ``ansible-lint`` also accepts a list of **roles** or **playbooks** as arguments. The following command lints ``examples/playbooks/play.yml`` and ``examples/roles/bobbins`` role: .. command-output:: ansible-lint -p examples/playbooks/play.yml examples/roles/bobbins :cwd: .. :returncode: 2 :nostderr: Examples -------- Included in ``ansible-lint/examples`` are some example playbooks with undesirable features. Running ansible-lint on them works, as demonstrated in the following: .. command-output:: ansible-lint -p examples/playbooks/example.yml :cwd: .. :returncode: 2 :nostderr: If playbooks include other playbooks, or tasks, or handlers or roles, these are also handled: .. command-output:: ansible-lint --force-color --offline -p examples/playbooks/include.yml :cwd: .. :returncode: 2 :nostderr: A codeclimate report in JSON format can be generated with ansible-lint. .. command-output:: ansible-lint -f codeclimate examples/playbooks/norole.yml :cwd: .. :returncode: 2 :nostderr: ansible-lint-5.4.0/examples/000077500000000000000000000000001420171260700157175ustar00rootroot00000000000000ansible-lint-5.4.0/examples/galaxy.yml000066400000000000000000000004761420171260700177360ustar00rootroot00000000000000name: foo namespace: bar version: 1.2.3 authors: - John readme: ../README.md description: ... dependencies: "other_namespace.collection1": ">=1.0.0" "other_namespace.collection2": ">=2.0.0,<3.0.0" "anderson55.my_collection": "*" # note: "*" selects the highest version available license: - GPL - Apache ansible-lint-5.4.0/examples/group_vars/000077500000000000000000000000001420171260700201065ustar00rootroot00000000000000ansible-lint-5.4.0/examples/group_vars/all.yml000066400000000000000000000000571420171260700214030ustar00rootroot00000000000000some_var: some_value_defined_in_group_vars_all ansible-lint-5.4.0/examples/host_vars/000077500000000000000000000000001420171260700177275ustar00rootroot00000000000000ansible-lint-5.4.0/examples/host_vars/localhost.yml000066400000000000000000000000641420171260700224420ustar00rootroot00000000000000some_var: some_value_defined_in_host_vars_localhost ansible-lint-5.4.0/examples/inventory/000077500000000000000000000000001420171260700177545ustar00rootroot00000000000000ansible-lint-5.4.0/examples/inventory/inventory.yml000066400000000000000000000011031420171260700225270ustar00rootroot00000000000000# https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html ungrouped: {} all: hosts: mail.example.com: children: webservers: hosts: foo.example.com: bar.example.com: dbservers: hosts: one.example.com: two.example.com: three.example.com: east: hosts: foo.example.com: one.example.com: two.example.com: west: hosts: bar.example.com: three.example.com: prod: children: east: {} test: children: west: {} ansible-lint-5.4.0/examples/lineno.yml000066400000000000000000000000351420171260700177240ustar00rootroot00000000000000- tasks: - git: repo=hello ansible-lint-5.4.0/examples/other/000077500000000000000000000000001420171260700170405ustar00rootroot00000000000000ansible-lint-5.4.0/examples/other/some.j2.yaml000066400000000000000000000001421420171260700211760ustar00rootroot00000000000000# Used to validate that a templated YAML file does not confuse the linter {% include 'port.j2' %} ansible-lint-5.4.0/examples/other/some.yaml-too000066400000000000000000000000671420171260700214710ustar00rootroot00000000000000# Used to test custom kinds defined in .ansible-config ansible-lint-5.4.0/examples/playbooks/000077500000000000000000000000001420171260700177225ustar00rootroot00000000000000ansible-lint-5.4.0/examples/playbooks/README.md000066400000000000000000000001331420171260700211760ustar00rootroot00000000000000The `./roles` symlink helps Ansible find local roles used by files from current directory. ansible-lint-5.4.0/examples/playbooks/always-run-success.yml000066400000000000000000000000231420171260700242100ustar00rootroot00000000000000- hosts: localhost ansible-lint-5.4.0/examples/playbooks/become-user-without-become-failure.yml000066400000000000000000000007631420171260700272370ustar00rootroot00000000000000- hosts: localhost name: become_user without become play become_user: root tasks: - debug: msg: hello - hosts: localhost tasks: - name: become_user without become task command: whoami become_user: postgres changed_when: false - hosts: localhost tasks: - name: a block with become and become_user on different tasks block: - name: become become: true command: whoami - name: become_user become_user: postgres command: whoami ansible-lint-5.4.0/examples/playbooks/become-user-without-become-success.yml000066400000000000000000000010401420171260700272450ustar00rootroot00000000000000- hosts: localhost become_user: root become: true tasks: - debug: msg: hello - hosts: localhost tasks: - name: foo command: whoami become_user: postgres become: true changed_when: false - hosts: localhost become: true tasks: - name: Accepts a become from higher scope command: whoami become_user: postgres changed_when: false - hosts: localhost become_user: postgres tasks: - name: Accepts a become from a lower scope command: whoami become: true changed_when: false ansible-lint-5.4.0/examples/playbooks/become.yml000066400000000000000000000004611420171260700217000ustar00rootroot00000000000000- hosts: all tasks: - name: clone content repository git: repo: '{{ archive_services_repo_url }}' dest: '/home/www' accept_hostkey: true version: master update: false become: true become_user: nobody notify: - restart apache2 ansible-lint-5.4.0/examples/playbooks/block.yml000066400000000000000000000013661420171260700215450ustar00rootroot00000000000000--- - hosts: all pre_tasks: - {import_tasks: 'doesnotexist.yml'} tasks: - block: - name: successful debug message debug: msg='i execute normally' - name: failure command command: /bin/false changed_when: false - name: never reached debug message debug: msg='i never execute, cause ERROR!' rescue: - name: exception debug message debug: msg='I caught an error' - name: another failure command command: /bin/false changed_when: false - name: another missed debug message debug: msg='I also never execute :-(' always: - name: always reached debug message debug: msg="this always executes" ansible-lint-5.4.0/examples/playbooks/blockincludes.yml000066400000000000000000000005501420171260700232660ustar00rootroot00000000000000--- - hosts: localhost vars: varset: varset tasks: - block: - include_tasks: tasks/nestedincludes.yml - block: # - include_tasks: "{{ varnotset }}.yml" - block: - include: "{{ varset }}.yml" - block: - include_tasks: "tasks/directory with spaces/main.yml" ansible-lint-5.4.0/examples/playbooks/blockincludes2.yml000066400000000000000000000005131420171260700233470ustar00rootroot00000000000000--- - hosts: webservers vars: varset: varset tasks: - block: - include_tasks: tasks/nestedincludes.yml # - block: # - include_tasks: "{{ varnotset }}.yml" rescue: - include_tasks: "{{ varset }}.yml" always: - include_tasks: "tasks/directory with spaces/main.yml" ansible-lint-5.4.0/examples/playbooks/bracketsmatchtest.yml000066400000000000000000000000611420171260700241550ustar00rootroot00000000000000val1: "{{dest}}" val2: worry val3: "{{victory}}" ansible-lint-5.4.0/examples/playbooks/command-check-failure.yml000066400000000000000000000002731420171260700245650ustar00rootroot00000000000000- hosts: localhost tasks: - name: command without checks command: echo blah args: chdir: X - name: shell without checks shell: echo blah args: chdir: X ansible-lint-5.4.0/examples/playbooks/command-check-success.yml000066400000000000000000000021571420171260700246110ustar00rootroot00000000000000- hosts: localhost tasks: - name: command with creates check command: echo blah args: creates: Z - name: command with removes check command: echo blah args: removes: Z - name: command with changed_when command: echo blah changed_when: False - name: command with inline creates command: creates=Z echo blah - name: command with inline removes command: removes=Z echo blah - name: command with cmd command: cmd: echo blah args: creates: Z - name: shell with creates check shell: echo blah args: creates: Z - name: shell with removes check shell: echo blah args: removes: Z - name: shell with changed_when shell: echo blah changed_when: False - name: shell with inline creates shell: creates=Z echo blah - name: shell with inline removes shell: removes=Z echo blah - name: shell with cmd shell: cmd: echo blah args: creates: Z - hosts: localhost handlers: - name: restart something command: do something - include: handlers/included-handlers.yml ansible-lint-5.4.0/examples/playbooks/common-include-1.yml000066400000000000000000000001731420171260700235150ustar00rootroot00000000000000--- - hosts: localhost gather_facts: false tasks: - name: some include include: tasks/included-with-lint.yml ansible-lint-5.4.0/examples/playbooks/common-include-2.yml000066400000000000000000000002011420171260700235060ustar00rootroot00000000000000--- - hosts: localhost gather_facts: false tasks: - name: some include include_tasks: tasks/included-with-lint.yml ansible-lint-5.4.0/examples/playbooks/conflicting_action.yml000066400000000000000000000005471420171260700243070ustar00rootroot00000000000000- hosts: localhost tasks: - name: foo debug: msg: bar command: echo # On this file ansible-playbook --syntax-check reports: # ERROR! conflicting action statements: debug, command # # The error appears to be in 'test/conflicting_action.yml': line 3, column 7, but may # be elsewhere in the file depending on the exact syntax problem. ansible-lint-5.4.0/examples/playbooks/contains_secrets.yml000066400000000000000000000011521420171260700240120ustar00rootroot00000000000000- hosts: localhost vars: plain: hello123 # just 'hello123' encrypted with 'letmein' for test purposes secret: !vault | $ANSIBLE_VAULT;1.1;AES256 63346434613163653866303630313238626164313961613935373137323639636333393338386232 3735313061316666343839343665383036623237353263310a623639336530383433343833653138 30393032393534316164613834393864616566646164363830316664623636643731383164376163 3736653037356435310a303533383533353739323834343637366438633766666163656330343631 3066 tasks: - name: just a debug task debug: msg="hello world" ansible-lint-5.4.0/examples/playbooks/custom_module.yml000066400000000000000000000002001420171260700233140ustar00rootroot00000000000000- hosts: localhost gather_facts: false tags: - "{{ foo }}" tasks: - name: Run custom module fake_module: {} ansible-lint-5.4.0/examples/playbooks/ematcher-rule.yml000066400000000000000000000003371420171260700232050ustar00rootroot00000000000000- hosts: localhost name: BANNED - hosts: localhost name: Another BANNED line tasks: - name: foo debug: msg: A 3rd BANNED line - name: bar command: echo something changed_when: false ansible-lint-5.4.0/examples/playbooks/empty_playbook.yml000066400000000000000000000002011420171260700234740ustar00rootroot00000000000000# an empty playbook which makes ansible-playbook --syntax-check report # ERROR! Empty playbook, nothing to do # with exit code 4 ansible-lint-5.4.0/examples/playbooks/example.yml000066400000000000000000000021661420171260700221050ustar00rootroot00000000000000--- - hosts: webservers vars: oldskool: "1.2.3" bracket: "and close bracket" tasks: - name: unset variable action: command echo {{thisvariable}} is not set in this playbook - name: trailing whitespace action: command echo do nothing - name: git check action: git a=b c=d - name: git check 2 action: git version=HEAD c=d - name: git check 3 git: version=a1b2c3d4 repo=xyz bobbins=d - name: executing git through command action: command git clone blah - name: executing git through command action: command chdir=bobbins creates=whatever /usr/bin/git clone blah - name: using git module action: git repo=blah - name: passing git as an argument to another task action: debug msg="{{item}}" with_items: - git # yamllint wrong indentation - bobbins - name: yum latest yum: state=latest name=httpd - debug: msg="debug task without a name" - name: apt latest apt: state=latest name=apache2 - meta: flush_handlers # empty task is currently accepted by ansible as valid code: - ansible-lint-5.4.0/examples/playbooks/extra_vars.yml000066400000000000000000000003041420171260700226200ustar00rootroot00000000000000--- - hosts: all tags: - baz - "{{ foo }}" tasks: - name: Show `complex_variable` value loaded from `extra_vars` ansible.builtin.debug: msg: "{{ complex_variable }}" ansible-lint-5.4.0/examples/playbooks/handlers/000077500000000000000000000000001420171260700215225ustar00rootroot00000000000000ansible-lint-5.4.0/examples/playbooks/handlers/included-handlers.yml000066400000000000000000000002071420171260700256310ustar00rootroot00000000000000--- - name: restart xyz service: name=xyz state=restarted # see Issue #165 - name: command handler issue 165 command: do something ansible-lint-5.4.0/examples/playbooks/handlers/y.yml000066400000000000000000000001141420171260700225110ustar00rootroot00000000000000- name: funny handler action: service name=funny state=started force=true ansible-lint-5.4.0/examples/playbooks/include-import-tasks-in-role.yml000066400000000000000000000000661420171260700260700ustar00rootroot00000000000000- hosts: all roles: - role_with_task_inclusions ansible-lint-5.4.0/examples/playbooks/include-in-block.yml000066400000000000000000000001211420171260700235560ustar00rootroot00000000000000--- - hosts: all tasks: - include_tasks: tasks/include-in-block-inner.yml ansible-lint-5.4.0/examples/playbooks/include.yml000066400000000000000000000003731420171260700220730ustar00rootroot00000000000000--- - hosts: bobbins pre_tasks: - include: tasks/x.yml roles: - hello - { role: morecomplex, t: z } tasks: - include: tasks/x.yml - include: tasks/x.yml y=z handlers: - include: handlers/y.yml - include: play.yml ansible-lint-5.4.0/examples/playbooks/jinja2-when-failure.yml000066400000000000000000000002561420171260700242110ustar00rootroot00000000000000- hosts: all tasks: - name: test when with jinja2 debug: msg=text when: "{{ false }}" - hosts: all roles: - role: test when: "{{ '1' = '1' }}" ansible-lint-5.4.0/examples/playbooks/jinja2-when-success.yml000066400000000000000000000002241420171260700242250ustar00rootroot00000000000000- hosts: all tasks: - name: test when debug: msg=text when: true - name: test when 2 debug: msg=text2 when: 1 = 1 ansible-lint-5.4.0/examples/playbooks/lots_of_warnings.yml000066400000000000000000001110441420171260700240230ustar00rootroot00000000000000--- # This playbook causes ansible-lint to output tons of warnings # Enough to exceed typical stdout buffering size and thus to show the need for # catching IOError (EPIEP) errors. - hosts: webservers tasks: - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah - name: executing git through command action: command git clone blah ansible-lint-5.4.0/examples/playbooks/mocked_dependency.yml000066400000000000000000000003611420171260700241050ustar00rootroot00000000000000- hosts: localhost roles: - mocked_role - fake_namespace.fake_collection.fake_role tasks: - name: some task zuul_return: {} - name: mocked module from collection fake_namespace.fake_collection.fake_module: {} ansible-lint-5.4.0/examples/playbooks/multiline-brackets-do-not-match-test.yml000066400000000000000000000014421420171260700275110ustar00rootroot00000000000000--- - hosts: foo roles: - ../../../roles/base_os - ../../../roles/repos - { role: ../../../roles/openshift_master, oo_minion_ips: "{ hostvars['localhost'].oo_minion_ips | default(['']) }}", oo_bind_ip: "{{ hostvars[inventory_hostname].ansible_eth0.ipv4.address | default(['']) }}" } - ../../../roles/pods - name: "Set Origin specific facts on localhost (for later use)" hosts: localhost gather_facts: no tasks: - name: Setting oo_minion_ips fact on localhost set_fact: oo_minion_ips: "{{ hostvars | oo_select_keys(groups['tag_env-host-type-' + oo_env + '-openshift-minion']) | oo_collect(attribute='ansible_eth0.ipv4.address') }" when: groups['tag_env-host-type-' + oo_env + '-openshift-minion'] is defined ansible-lint-5.4.0/examples/playbooks/multiline-bracketsmatchtest.yml000066400000000000000000000014441420171260700261630ustar00rootroot00000000000000--- - hosts: foo roles: - ../../../roles/base_os - ../../../roles/repos - { role: ../../../roles/openshift_master, oo_minion_ips: "{{ hostvars['localhost'].oo_minion_ips | default(['']) }}", oo_bind_ip: "{{ hostvars[inventory_hostname].ansible_eth0.ipv4.address | default(['']) }}" } - ../../../roles/pods - name: "Set Origin specific facts on localhost (for later use)" hosts: localhost gather_facts: no tasks: - name: Setting oo_minion_ips fact on localhost set_fact: oo_minion_ips: "{{ hostvars | oo_select_keys(groups['tag_env-host-type-' + oo_env + '-openshift-minion']) | oo_collect(attribute='ansible_eth0.ipv4.address') }}" when: groups['tag_env-host-type-' + oo_env + '-openshift-minion'] is defined ansible-lint-5.4.0/examples/playbooks/nomatches.yml000066400000000000000000000002671420171260700224330ustar00rootroot00000000000000--- - hosts: whatever tasks: - name: hello world action: debug msg="Hello!" - name: this should be fine too action: file state=touch mode=0644 dest=./wherever ansible-lint-5.4.0/examples/playbooks/nomatchestest.yml000066400000000000000000000002671420171260700233330ustar00rootroot00000000000000--- - hosts: whatever tasks: - name: hello world action: debug msg="Hello!" - name: this should be fine too action: file state=touch dest=./wherever mode=0600 ansible-lint-5.4.0/examples/playbooks/norole.yml000066400000000000000000000000631420171260700217420ustar00rootroot00000000000000--- - hosts: - localhost roles: - name: node ansible-lint-5.4.0/examples/playbooks/norole2.yml000066400000000000000000000000671420171260700220300ustar00rootroot00000000000000--- - hosts: - localhost roles: - name: node ansible-lint-5.4.0/examples/playbooks/package-check-failure.yml000066400000000000000000000006101420171260700245350ustar00rootroot00000000000000- hosts: localhost tasks: - name: install ansible yum: name=ansible state=latest - name: install ansible-lint pip: name=ansible-lint args: state: latest - name: install some-package package: name: some-package state: latest - name: install ansible with update_only to false yum: name: sudo state: latest update_only: false ansible-lint-5.4.0/examples/playbooks/package-check-success.yml000066400000000000000000000006141420171260700245620ustar00rootroot00000000000000- hosts: localhost tasks: - name: install ansible yum: name=ansible-2.1.0.0 state=present - name: install ansible-lint pip: name=ansible-lint args: state: present version: 3.1.2 - name: install some-package package: name: some-package state: present - name: update ansible yum: name: sudo state: latest update_only: true ansible-lint-5.4.0/examples/playbooks/pass-loop-var-prefix.yml000066400000000000000000000005551420171260700244500ustar00rootroot00000000000000- hosts: localhost tasks: # validate that we did not trigger loop-var-prefix on playbooks - name: that should pass debug: var: item loop: - foo - bar - name: a block block: - name: that should also pass debug: var: item loop: - apples - oranges ansible-lint-5.4.0/examples/playbooks/play.yml000066400000000000000000000001351420171260700214110ustar00rootroot00000000000000--- - hosts: bobbins tasks: - name: a bad play action: command service blah restart ansible-lint-5.4.0/examples/playbooks/playbook-imported.yml000066400000000000000000000003011420171260700241000ustar00rootroot00000000000000--- - hosts: localhost connection: local gather_facts: false tasks: - command: echo "no name" # should generate unnamed-task - name: Another task debug: msg: debug message ansible-lint-5.4.0/examples/playbooks/playbook-parent.yml000066400000000000000000000001201420171260700235450ustar00rootroot00000000000000--- - name: Importing another playbook import_playbook: playbook-imported.yml ansible-lint-5.4.0/examples/playbooks/roles000077700000000000000000000000001420171260700222462../rolesustar00rootroot00000000000000ansible-lint-5.4.0/examples/playbooks/rule-no-tabs.yml000066400000000000000000000004071420171260700227560ustar00rootroot00000000000000- hosts: localhost tasks: - name: should not trigger no-tabs rules lineinfile: path: some.txt regexp: '^\t$' line: 'string with \t inside' - name: foo debug: msg: "Presence of \t should trigger no-tabs here." ansible-lint-5.4.0/examples/playbooks/skiptasks.yml000066400000000000000000000025511420171260700224640ustar00rootroot00000000000000--- - hosts: all tasks: - name: test git-latest action: git - name: test hg-latest action: hg - name: test command-instead-of-module command: git log changed_when: False - name: test deprecated-command-syntax command: creates=B chmod 644 A - name: test git-latest (skip) action: git tags: - skip_ansible_lint - name: test hg-latest (skip) action: hg tags: - skip_ansible_lint - name: test command-instead-of-module (skip) command: git log tags: - skip_ansible_lint - name: test deprecated-command-syntax (skip) command: chmod 644 A tags: - skip_ansible_lint - name: test git-latest (don't warn) command: git log args: warn: False changed_when: False - name: test hg-latest (don't warn) command: chmod 644 A args: warn: False creates: B - name: test hg-latest (warn) command: chmod 644 A args: warn: yes creates: B - name: test git-latest (don't warn single line) command: warn=False chdir=/tmp/blah git log changed_when: False - name: test hg-latest (don't warn single line) command: warn=no creates=B chmod 644 A - name: test hg-latest (warn single line) command: warn=yes creates=B chmod 644 A ansible-lint-5.4.0/examples/playbooks/syntax-error-string.yml000066400000000000000000000001571420171260700244310ustar00rootroot00000000000000foo # This file is valid YAML but from our point of view is an error, as is # neither a Sequence or a Mapping. ansible-lint-5.4.0/examples/playbooks/syntax-error.yml000066400000000000000000000002131420171260700231160ustar00rootroot00000000000000--- - name: This should raise syntax-error hosts: localhost tasks: debug: msg: "Note that `tasks` is not entered as a list." ansible-lint-5.4.0/examples/playbooks/task-has-name-failure.yml000066400000000000000000000002621420171260700245230ustar00rootroot00000000000000--- - hosts: all tasks: - command: echo "no name" - name: command: echo "empty name" - debug: msg: "Debug without a name" - meta: flush_handlers ansible-lint-5.4.0/examples/playbooks/task-has-name-success.yml000066400000000000000000000003351420171260700245450ustar00rootroot00000000000000--- - hosts: all tasks: - name: This task has a name command: echo "Hello World" - name: Debug task with name debug: msg="Hello World" - name: Flush handler with name meta: flush_handlers ansible-lint-5.4.0/examples/playbooks/taskimports.yml000066400000000000000000000004001420171260700230170ustar00rootroot00000000000000--- - hosts: localhost vars: varset: tasks/simpletask.yml tasks: - import_tasks: tasks/nestedincludes.yml # - import_tasks: "{{ varnotset }}.yml" - import_tasks: "{{ varset }}" - import_tasks: "tasks/directory with spaces/main.yml" ansible-lint-5.4.0/examples/playbooks/taskincludes.yml000066400000000000000000000003321420171260700231340ustar00rootroot00000000000000--- - hosts: localhost vars: varset: tasks/simpletask.yml tasks: # - include_tasks: "{{ varnotset }}.yml" - include_tasks: "{{ varset }}.yml" - include_tasks: "tasks/directory with spaces/main.yml" ansible-lint-5.4.0/examples/playbooks/tasks/000077500000000000000000000000001420171260700210475ustar00rootroot00000000000000ansible-lint-5.4.0/examples/playbooks/tasks/directory with spaces/000077500000000000000000000000001420171260700252465ustar00rootroot00000000000000ansible-lint-5.4.0/examples/playbooks/tasks/directory with spaces/main.yml000066400000000000000000000002001420171260700267050ustar00rootroot00000000000000# this should generate: unnamed-task: All tasks should be named - assert: fail_msg: tasks in directory with spaces included ansible-lint-5.4.0/examples/playbooks/tasks/include-in-block-inner.yml000066400000000000000000000000621420171260700260200ustar00rootroot00000000000000--- - block: - include_tasks: simpletask.yml ansible-lint-5.4.0/examples/playbooks/tasks/included-with-lint.yml000066400000000000000000000000621420171260700252740ustar00rootroot00000000000000# missing a task name - assert: fail_msg: foo ansible-lint-5.4.0/examples/playbooks/tasks/nestedincludes.yml000066400000000000000000000000521420171260700246000ustar00rootroot00000000000000--- - include_tasks: tasks/simpletask.yml ansible-lint-5.4.0/examples/playbooks/tasks/simpletask.yml000066400000000000000000000001121420171260700237400ustar00rootroot00000000000000--- # unnamed-task: All tasks should be named - assert: fail_msg: foo ansible-lint-5.4.0/examples/playbooks/tasks/varset.yml000066400000000000000000000000621420171260700230740ustar00rootroot00000000000000- debug: msg="var was set" - git: repo=hello.git ansible-lint-5.4.0/examples/playbooks/tasks/varunset.yml000066400000000000000000000000371420171260700234410ustar00rootroot00000000000000- debug: msg="var was not set" ansible-lint-5.4.0/examples/playbooks/tasks/x.yml000066400000000000000000000001451420171260700220410ustar00rootroot00000000000000- # nothing, checks bug #849 - name: test include action: funny value=clown args: key: value ansible-lint-5.4.0/examples/playbooks/templates/000077500000000000000000000000001420171260700217205ustar00rootroot00000000000000ansible-lint-5.4.0/examples/playbooks/templates/not-valid.yaml000066400000000000000000000001421420171260700244760ustar00rootroot00000000000000# Used to validate that a templated YAML file does not confuse the linter {% include 'port.j2' %} ansible-lint-5.4.0/examples/playbooks/unicode.yml000066400000000000000000000001041420171260700220660ustar00rootroot00000000000000--- - hosts: all tasks: - name: тест command: uname ansible-lint-5.4.0/examples/playbooks/using-bare-variables-failure.yml000066400000000000000000000036041420171260700260770ustar00rootroot00000000000000--- - hosts: localhost become: false vars: my_list: - foo - bar my_list2: - 1 - 2 my_list_of_dicts: - foo: 1 bar: 2 - foo: 3 bar: 4 my_list_of_lists: - "{{ my_list }}" - "{{ my_list2 }}" my_filenames: - foo.txt - bar.txt my_dict: foo: bar tasks: - name: with_items loop using bare variable debug: msg: "{{ item }}" with_items: my_list - name: with_dict loop using bare variable debug: msg: "{{ item }}" with_dict: my_dict ### Testing with_dict with a default empty dictionary - name: with_dict loop using variable and default debug: msg: "{{ item.key }} - {{ item.value }}" with_dict: uwsgi_ini | default({}) - name: with_nested loop using bare variable debug: msg: "{{ item.0 }} {{ item.1 }}" with_nested: - my_list - "{{ my_list2 }}" - name: with_file loop using bare variable debug: msg: "{{ item }}" with_file: my_list - name: with_fileglob loop using bare variable debug: msg: "{{ item }}" with_fileglob: my_list - name: with_together loop using bare variable debug: msg: "{{ item.0 }} {{ item.1 }}" with_together: - my_list - "{{ my_list2 }}" - name: with_subelements loop using bare variable debug: msg: "{{ item.0 }}" with_subelements: - my_list_of_dicts - bar - name: with_random_choice loop using bare variable debug: msg: "{{ item }}" with_random_choice: my_list - name: with_first_found loop using bare variable debug: msg: "{{ item }}" with_first_found: my_filenames - name: with_indexed_items loop debug: msg: "{{ item.0 }} {{ item.1 }}" with_indexed_items: my_list ansible-lint-5.4.0/examples/playbooks/using-bare-variables-success.yml000066400000000000000000000074761420171260700261330ustar00rootroot00000000000000--- - hosts: localhost become: false vars: my_list: - foo - bar my_list2: - 1 - 2 my_list_of_dicts: - foo: 1 bar: 2 - foo: 3 bar: 4 my_list_of_lists: - "{{ my_list }}" - "{{ my_list2 }}" my_filenames: - foo.txt - bar.txt my_dict: foo: bar tasks: ### Testing with_items loops - name: with_items loop using static list debug: msg: "{{ item }}" with_items: - foo - bar - name: with_items using a static hash debug: msg: "{{ item.key }} - {{ item.value }}" with_items: - {key: foo, value: 1} - {key: bar, value: 2} - name: with_items loop using variable debug: msg: "{{ item }}" with_items: "{{ my_list }}" ### Testing with_nested loops - name: with_nested loop using static lists debug: msg: "{{ item[0] }} - {{ item[1] }}" with_nested: - ['foo', 'bar'] - ['1', '2', '3'] - name: with_nested loop using variable list and static debug: msg: "{{ item[0] }} - {{ item[1] }}" with_nested: - "{{ my_list }}" - ['1', '2', '3'] ### Testing with_dict - name: with_dict loop using variable debug: msg: "{{ item.key }} - {{ item.value }}" with_dict: "{{ my_dict }}" ### Testing with_dict with a default empty dictionary - name: with_dict loop using variable and default debug: msg: "{{ item.key }} - {{ item.value }}" with_dict: "{{ uwsgi_ini | default({}) }}" ### Testing with_file - name: with_file loop using static files list debug: msg: "{{ item }}" with_file: - foo.txt - bar.txt - name: with_file loop using list of filenames debug: msg: "{{ item }}" with_file: "{{ my_filenames }}" ### Testing with_fileglob - name: with_fileglob loop using list of *.txt debug: msg: "{{ item }}" with_fileglob: - '*.txt' ### Testing non-list form of with_fileglob - name: with_fileglob loop using single value *.txt debug: msg: "{{ item }}" with_fileglob: '*.txt' ### Testing non-list form of with_fileglob with trailing templated pattern - name: with_fileglob loop using templated pattern debug: msg: "{{ item }}" with_fileglob: 'foo{{ glob }}' ### Testing with_together - name: with_together loop using variable lists debug: msg: "{{ item.0 }} - {{ item.1 }}" with_together: - "{{ my_list }}" - "{{ my_list2 }}" - name: with_subelements loop debug: msg: "{{ item }}" with_subelements: - "{{ my_list_of_dicts }}" - bar - name: with_sequence loop debug: msg: "{{ item }}" with_sequence: count=2 - name: with_random_choice loop debug: msg: "{{ item }}" with_random_choice: "{{ my_list }}" - name: with_first_found loop with static files list debug: msg: "{{ item }}" with_first_found: - foo.txt - bar.txt - name: with_first_found loop with list of filenames debug: msg: "{{ item }}" with_first_found: "{{ my_filenames }}" - name: with_indexed_items loop debug: msg: "{{ item.0 }} {{ item.1 }}" with_indexed_items: "{{ my_list }}" - name: with_ini loop debug: msg: "{{ item }}" with_ini: value[1-2] section=section1 file=foo.ini re=true - name: with_inventory_hostnames loop debug: msg: "{{ item }}" with_inventory_hostnames: all - name: test more complex jinja is also allowed debug: msg: "{{ item }}" with_items: >- {%- set ns = [1, 1, 2] -%} {{- ns.keys | unique -}} ansible-lint-5.4.0/examples/playbooks/valid_with_alt_extension.yaml000066400000000000000000000002551420171260700256760ustar00rootroot00000000000000# Used to validate that we also accept .yaml extension on playbooks - hosts: localhost tasks: - debug: # <-- should notify about missing 'name' msg: "hello!" ansible-lint-5.4.0/examples/playbooks/var-spacing.yml000066400000000000000000000041661420171260700226660ustar00rootroot00000000000000# Should raise var-spacing at tasks line 23, 26, 29, 54, 65 - hosts: all tasks: - name: good variable format debug: msg: "{{ good_format }}" - name: good variable format debug: msg: "Value: {{ good_format }}" - name: jinja escaping allowed debug: msg: "{{ '{{' }}" - name: jinja escaping allowed # noqa: 306 shell: docker info --format '{{ '{{' }}json .Swarm.LocalNodeState{{ '}}' }}' | tr -d '"' changed_when: false - name: jinja whitespace control allowed debug: msg: | {{ good_format }}/ {{- good_format }} {{- good_format -}} - name: bad variable format debug: msg: "{{bad_format}}" - name: bad variable format debug: msg: "Value: {{ bad_format}}" - name: bad variable format debug: msg: "{{bad_format }}" - name: not a jinja variable # noqa var-spacing debug: msg: "data = ${lookup{$local_part}lsearch{/etc/aliases}}" - name: JSON inside jinja is valid debug: msg: "{{ {'test': {'subtest': variable}} }}" - name: Avoid false positive on multiline vars: cases: case1: >- http://example.com/{{ case1 }} case2: >- http://example.com/{{ case2 }} debug: var: cases - name: Valid single line nested JSON false positive debug: msg: "{{ {'dummy_2': {'nested_dummy_1': 'value_1', 'nested_dummy_2': value_2}} | combine(dummy_1) }}" - name: Invalid single line nested JSON debug: msg: "{{ {'dummy_2': {'nested_dummy_1': 'value_1', 'nested_dummy_2': value_2}} | combine(dummy_1)}}" - name: Valid multiline nested JSON false positive debug: msg: >- {{ {'dummy_2': {'nested_dummy_1': value_1, 'nested_dummy_2': value_2}} | combine(dummy_1) }} - name: Invalid multiline nested JSON debug: msg: >- {{ {'dummy_2': {'nested_dummy_1': value_1, 'nested_dummy_2': value_2}} | combine(dummy_1)}} ansible-lint-5.4.0/examples/playbooks/vars/000077500000000000000000000000001420171260700206755ustar00rootroot00000000000000ansible-lint-5.4.0/examples/playbooks/vars/not_decryptable.yml000066400000000000000000000013471420171260700246030ustar00rootroot00000000000000$ANSIBLE_VAULT;1.1;AES256 35366433323361393130396530643233373262666636646439303032366431303363316232313738 3738636130636431623936303932306430316635663136610a353737333966353462333532393631 36613030643762636138613734313862333165346464626461313361353732336131633137653865 3862386136386137650a303433643531613337393735633338383163353737656339653134346363 63613436333937313738633437373566333065663662643664643261313366323236356364316663 62336264666464323066336365616634626336616537646336656266343562336533343732613539 61643661303566313664313164623731316236666235656337363632393665353536303730666532 64666639306361653963363462393966623763626566613831613739333666333665343734333630 63623730623033346163393834396639383234393637653733396466316132663131 ansible-lint-5.4.0/examples/playbooks/vars/other.yml000066400000000000000000000000531420171260700225370ustar00rootroot00000000000000some_var: some_value_defined_in_vars_other ansible-lint-5.4.0/examples/playbooks/vars/subfolder/000077500000000000000000000000001420171260700226625ustar00rootroot00000000000000ansible-lint-5.4.0/examples/playbooks/vars/subfolder/settings.yml000066400000000000000000000000701420171260700252420ustar00rootroot00000000000000some_var: some_value_defined_in_vars_subfolder_settings ansible-lint-5.4.0/examples/playbooks/with-skip-tag-id.yml000066400000000000000000000002311420171260700235230ustar00rootroot00000000000000- hosts: all tasks: - name: trailing whitespace on this line git: repo: '{{ archive_services_repo_url }}' dest: '/home/www' ansible-lint-5.4.0/examples/playbooks/with-umlaut-ä.yml000066400000000000000000000000631420171260700235500ustar00rootroot00000000000000--- - hosts: - localhost roles: - name: node ansible-lint-5.4.0/examples/reqs_v1/000077500000000000000000000000001420171260700172775ustar00rootroot00000000000000ansible-lint-5.4.0/examples/reqs_v1/requirements.yml000066400000000000000000000000651420171260700225460ustar00rootroot00000000000000# v1 requirements test file - src: geerlingguy.mysql ansible-lint-5.4.0/examples/reqs_v2/000077500000000000000000000000001420171260700173005ustar00rootroot00000000000000ansible-lint-5.4.0/examples/reqs_v2/requirements.yml000066400000000000000000000001201420171260700225370ustar00rootroot00000000000000--- roles: - name: geerlingguy.mysql collections: - name: ssbarnea.molecule ansible-lint-5.4.0/examples/roles/000077500000000000000000000000001420171260700170435ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/ansible-role-foo/000077500000000000000000000000001420171260700222005ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/ansible-role-foo/tasks/000077500000000000000000000000001420171260700233255ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/ansible-role-foo/tasks/main.yaml000066400000000000000000000000001420171260700251230ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/bobbins/000077500000000000000000000000001420171260700204615ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/bobbins/tasks/000077500000000000000000000000001420171260700216065ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/bobbins/tasks/main.yml000066400000000000000000000000551420171260700232550ustar00rootroot00000000000000--- - name: test tasks action: git a=b c=d ansible-lint-5.4.0/examples/roles/dependency_in_meta/000077500000000000000000000000001420171260700226555ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/dependency_in_meta/meta/000077500000000000000000000000001420171260700236035ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/dependency_in_meta/meta/main.yml000066400000000000000000000021601420171260700252510ustar00rootroot00000000000000--- # meta file, determined by ending in meta/main.yml # https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html#role-dependencies dependencies: # from Bitbucket - src: git+http://bitbucket.org/willthames/git-ansible-galaxy version: v1.4 # from Bitbucket, alternative syntax and caveats - src: http://bitbucket.org/willthames/hg-ansible-galaxy scm: hg # from galaxy - src: yatesr.timezone # from GitHub - src: https://github.com/bennojoy/nginx # from GitHub, overriding the name and specifying a specific tag - src: https://github.com/bennojoy/nginx version: master name: nginx_role # from GitLab or other git-based scm - src: git@gitlab.company.com:mygroup/myrepo.git scm: git version: "0.1" # quoted, so YAML doesn't parse this as a floating-point value # from a webserver, where the role is packaged in a tar.gz - src: https://some.webserver.example.com/files/master.tar.gz name: http-role galaxy_info: author: foo description: 'Testing meta' company: Not applicable license: MIT min_ansible_version: 2.5 platforms: - name: Fedora ansible-lint-5.4.0/examples/roles/fail_loop_var_prefix/000077500000000000000000000000001420171260700232345ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/fail_loop_var_prefix/tasks/000077500000000000000000000000001420171260700243615ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/fail_loop_var_prefix/tasks/main.yml000066400000000000000000000006631420171260700260350ustar00rootroot00000000000000# 3 expected no-loop-var-prefix failures at 2, 8, 18 - name: that should trigger no-loop-var-prefix debug: var: item loop: - foo - bar - name: that should fail due to wrong custom debug: var: zz_item loop: - foo - bar loop_control: loop_var: zz_item - name: Using a block block: - name: that should also not pass debug: var: item loop: - apples - oranges ansible-lint-5.4.0/examples/roles/hello/000077500000000000000000000000001420171260700201465ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/hello/meta/000077500000000000000000000000001420171260700210745ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/hello/meta/main.yml000066400000000000000000000000441420171260700225410ustar00rootroot00000000000000--- dependencies: - role: bobbins ansible-lint-5.4.0/examples/roles/invalid-name/000077500000000000000000000000001420171260700214075ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/invalid-name/tasks/000077500000000000000000000000001420171260700225345ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/invalid-name/tasks/main.yaml000066400000000000000000000000461420171260700243440ustar00rootroot00000000000000--- - name: foo debug: msg: foo ansible-lint-5.4.0/examples/roles/invalid_due_to_meta/000077500000000000000000000000001420171260700230365ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/invalid_due_to_meta/meta/000077500000000000000000000000001420171260700237645ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/invalid_due_to_meta/meta/main.yml000066400000000000000000000002261420171260700254330ustar00rootroot00000000000000galaxy_info: role_name: invalid-due-to-meta author: foo description: foo license: MIT platforms: - name: foo min_ansible_version: 2.7 ansible-lint-5.4.0/examples/roles/invalid_due_to_meta/tasks/000077500000000000000000000000001420171260700241635ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/invalid_due_to_meta/tasks/main.yaml000066400000000000000000000000001420171260700257610ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/morecomplex/000077500000000000000000000000001420171260700213755ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/morecomplex/handlers/000077500000000000000000000000001420171260700231755ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/morecomplex/handlers/main.yml000066400000000000000000000001051420171260700246400ustar00rootroot00000000000000- name: restart service using command command: service bar restart ansible-lint-5.4.0/examples/roles/morecomplex/tasks/000077500000000000000000000000001420171260700225225ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/morecomplex/tasks/main.yml000066400000000000000000000002511420171260700241670ustar00rootroot00000000000000- name: test bad command action: command mkdir blah - name: test bad command v2 command: mkdir blah - name: test bad local command local_action: shell touch foo ansible-lint-5.4.0/examples/roles/role_for_no_same_owner/000077500000000000000000000000001420171260700235655ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/role_for_no_same_owner/tasks/000077500000000000000000000000001420171260700247125ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/role_for_no_same_owner/tasks/fail.yml000066400000000000000000000017531420171260700263560ustar00rootroot00000000000000- name: block block: - name: synchonize-in-block synchronize: src: dummy dest: dummy - name: synchronize synchronize: src: dummy dest: dummy - name: nested-block block: - block: - name: synchonize-in-deep-block synchronize: src: dummy dest: dummy - name: unarchive-bz2 unarchive: src: "{{ file }}.tar.bz2" dest: "dummy" - name: unarchive delegated unarchive: src: "{{ file }}.tar.bz2" dest: "dummy" delegate_to: localhost - name: unarchive-gz unarchive: src: "{{ file }}.tar.gz" dest: "dummy" - name: unarchive-tar unarchive: src: "{{ file }}.tar" dest: "dummy" - name: unarchive-xz unarchive: src: "{{ file }}.tar.xz" dest: "dummy" - name: unarchive-zip unarchive: src: "{{ file }}.zip" dest: dummy extra_opts: - '-X' - name: unarchive-zip-same-owner unarchive: src: "{{ file }}.zip" dest: dummy extra_opts: - '-X' ansible-lint-5.4.0/examples/roles/role_for_no_same_owner/tasks/pass.yml000066400000000000000000000010711420171260700264020ustar00rootroot00000000000000- name: syncronize-delegate synchronize: src: dummy dest: dummy delegate_to: localhost - name: synchronize-no-same-owner synchronize: src: dummy dest: dummy owner: false group: false - name: unarchive-no-same-owner unarchive: src: "{{ file }}.tar.gz" dest: dummy extra_opts: - '--no-same-owner' - name: unarchive-remote-src unarchive: src: "{{ file }}.tar.gz" dest: dummy extra_opts: - '--no-same-owner' - name: unarchive-unknown-file-ending unarchive: src: "{{ file }}" dest: "dummy" ansible-lint-5.4.0/examples/roles/role_with_task_inclusions/000077500000000000000000000000001420171260700243275ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/role_with_task_inclusions/meta/000077500000000000000000000000001420171260700252555ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/role_with_task_inclusions/meta/main.yml000066400000000000000000000000001420171260700267120ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/role_with_task_inclusions/tasks/000077500000000000000000000000001420171260700254545ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/role_with_task_inclusions/tasks/imported_tasks.yml000066400000000000000000000000651420171260700312300ustar00rootroot00000000000000# this task is missing a name (unnamed-task) - ping: ansible-lint-5.4.0/examples/roles/role_with_task_inclusions/tasks/included_tasks.yml000066400000000000000000000000651420171260700311740ustar00rootroot00000000000000# this task is missing a name (unnamed-task) - ping: ansible-lint-5.4.0/examples/roles/role_with_task_inclusions/tasks/main.yml000066400000000000000000000002241420171260700271210ustar00rootroot00000000000000- include_tasks: included_tasks.yml - import_tasks: imported_tasks.yml - include_tasks: file: included_tasks.yml apply: tags: sometag ansible-lint-5.4.0/examples/roles/template_lookup/000077500000000000000000000000001420171260700222475ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/template_lookup/files/000077500000000000000000000000001420171260700233515ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/template_lookup/files/a_file000066400000000000000000000000001420171260700245010ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/template_lookup/tasks/000077500000000000000000000000001420171260700233745ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/template_lookup/tasks/main.yml000066400000000000000000000007361420171260700250510ustar00rootroot00000000000000# Place tasks in a block as templates are not rendered for top-level tasks # in roles. Specifically, in `play_children()` of src/ansiblelint/utils.py, # tasks in blocks go through `delegate_map['block']`, while top-level tasks # in a role is not handled by `delegate_map`. # Ref: https://github.com/ansible-community/ansible-lint/blob/v5.0.12/src/ansiblelint/utils.py#L305 - block: - name: bug demo ansible.builtin.debug: msg: '{{ lookup("file", "a_file") }}' ansible-lint-5.4.0/examples/roles/template_lookup_missing/000077500000000000000000000000001420171260700240005ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/template_lookup_missing/tasks/000077500000000000000000000000001420171260700251255ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/template_lookup_missing/tasks/main.yml000066400000000000000000000003151420171260700265730ustar00rootroot00000000000000# See comments in `examples/roles/template_lookup/tasks/main.yml` # for why the task is in a block. - block: - name: bug demo ansible.builtin.debug: msg: '{{ lookup("file", "a_file") }}' ansible-lint-5.4.0/examples/roles/test-role/000077500000000000000000000000001420171260700207615ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/test-role/meta/000077500000000000000000000000001420171260700217075ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/test-role/meta/requirements.yml000066400000000000000000000000361420171260700251540ustar00rootroot00000000000000--- roles: [] collections: [] ansible-lint-5.4.0/examples/roles/test-role/molecule/000077500000000000000000000000001420171260700225665ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/test-role/molecule/default/000077500000000000000000000000001420171260700242125ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/test-role/molecule/default/include-import-role.yml000066400000000000000000000001201420171260700306200ustar00rootroot00000000000000--- - name: test gather_facts: no hosts: all roles: - role: test-role ansible-lint-5.4.0/examples/roles/test-role/tasks/000077500000000000000000000000001420171260700221065ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/test-role/tasks/main.yml000066400000000000000000000000731420171260700235550ustar00rootroot00000000000000- name: shell instead of command shell: echo hello world ansible-lint-5.4.0/examples/roles/test-role/tasks/world.yml000066400000000000000000000000561420171260700237610ustar00rootroot00000000000000- command: echo this is a task without a name ansible-lint-5.4.0/examples/roles/valid-due-to-meta/000077500000000000000000000000001420171260700222615ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/valid-due-to-meta/meta/000077500000000000000000000000001420171260700232075ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/valid-due-to-meta/meta/main.yml000066400000000000000000000002241420171260700246540ustar00rootroot00000000000000galaxy_info: role_name: valid_due_to_meta author: foo description: foo license: MIT platforms: - name: foo min_ansible_version: 2.7 ansible-lint-5.4.0/examples/roles/valid-due-to-meta/tasks/000077500000000000000000000000001420171260700234065ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/valid-due-to-meta/tasks/debian/000077500000000000000000000000001420171260700246305ustar00rootroot00000000000000ansible-lint-5.4.0/examples/roles/valid-due-to-meta/tasks/debian/main.yml000066400000000000000000000002021420171260700262710ustar00rootroot00000000000000# This empty task file is here to test that roles with tasks organized in subdirectories # are handled correctly by ansible-lint. ansible-lint-5.4.0/examples/roles/valid-due-to-meta/tasks/main.yaml000066400000000000000000000000001420171260700252040ustar00rootroot00000000000000ansible-lint-5.4.0/examples/rules/000077500000000000000000000000001420171260700170515ustar00rootroot00000000000000ansible-lint-5.4.0/examples/rules/TaskHasTag.py000066400000000000000000000022431420171260700214160ustar00rootroot00000000000000"""Example implementation of a rule requiring tasks to have tags set.""" from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class TaskHasTag(AnsibleLintRule): """Tasks must have tag.""" id = "EXAMPLE001" shortdesc = "Tasks must have tag" description = "Tasks must have tag" tags = ["productivity", "tags"] def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: """Task matching method.""" if isinstance(task, str): return False # If the task include another task or make the playbook fail # Don't force to have a tag if not set(task.keys()).isdisjoint(["include", "fail"]): return False if not set(task.keys()).isdisjoint(["include_tasks", "fail"]): return False if not set(task.keys()).isdisjoint(["import_tasks", "fail"]): return False # Task should have tags if "tags" not in task: return True return False ansible-lint-5.4.0/examples/testproject/000077500000000000000000000000001420171260700202655ustar00rootroot00000000000000ansible-lint-5.4.0/examples/testproject/roles/000077500000000000000000000000001420171260700214115ustar00rootroot00000000000000ansible-lint-5.4.0/examples/testproject/roles/test-role/000077500000000000000000000000001420171260700233275ustar00rootroot00000000000000ansible-lint-5.4.0/examples/testproject/roles/test-role/tasks/000077500000000000000000000000001420171260700244545ustar00rootroot00000000000000ansible-lint-5.4.0/examples/testproject/roles/test-role/tasks/main.yml000066400000000000000000000000731420171260700261230ustar00rootroot00000000000000- name: shell instead of command shell: echo hello world ansible-lint-5.4.0/mypy.ini000066400000000000000000000016231420171260700156020ustar00rootroot00000000000000[mypy] python_version = 3.6 color_output = True error_summary = True disallow_untyped_calls = True disallow_untyped_defs = True disallow_any_generics = True ; disallow_any_unimported = True ; warn_redundant_casts = True ; warn_return_any = True ; warn_unused_configs = True exclude = test/local-content # 3rd party ignores [mypy-ansible] ignore_missing_imports = True [mypy-ansible.*] ignore_missing_imports = True [mypy-flaky] # # https://github.com/box/flaky/issues/170 ignore_missing_imports = True [mypy-pytest] ignore_missing_imports = True [mypy-packaging.version] ignore_missing_imports = True [mypy-importlib_metadata] ignore_missing_imports = True [mypy-ruamel.*] ignore_missing_imports = True [mypy-setuptools] ignore_missing_imports = True [mypy-sphinx_ansible_theme] ignore_missing_imports = True [mypy-yamllint.*] ignore_missing_imports = True [mypy-wcmatch.*] ignore_missing_imports = True ansible-lint-5.4.0/playbook.yml000066400000000000000000000001351420171260700164430ustar00rootroot00000000000000- hosts: localhost tasks: - shell: cmd: | if [ $FOO != false ]; then ansible-lint-5.4.0/plugins/000077500000000000000000000000001420171260700155625ustar00rootroot00000000000000ansible-lint-5.4.0/plugins/modules/000077500000000000000000000000001420171260700172325ustar00rootroot00000000000000ansible-lint-5.4.0/plugins/modules/fake_module.py000066400000000000000000000006511420171260700220610ustar00rootroot00000000000000"""Sample custom ansible module named fake_module. This is used to test ability to detect and use custom modules. """ from ansible.module_utils.basic import AnsibleModule def main(): """Return the module instance.""" return AnsibleModule( argument_spec=dict( data=dict(default=None), path=dict(default=None, type=str), file=dict(default=None, type=str), ) ) ansible-lint-5.4.0/pyproject.toml000066400000000000000000000007741420171260700170250ustar00rootroot00000000000000[build-system] requires = [ "setuptools >= 42.0.0", # required by pyproject+setuptools_scm integration "setuptools_scm[toml] >= 3.5.0", # required for "no-local-version" scheme "setuptools_scm_git_archive >= 1.0", "wheel", ] build-backend = "setuptools.build_meta" [tool.coverage.run] source = ["src"] branch = true [tool.coverage.report] exclude_lines = [ "pragma: no cover", "if TYPE_CHECKING:" ] [tool.isort] profile = "black" [tool.setuptools_scm] local_scheme = "no-local-version" ansible-lint-5.4.0/pytest.ini000066400000000000000000000056521420171260700161420ustar00rootroot00000000000000[pytest] addopts = # `pytest-xdist`: -n auto # `pytest-mon`: # useful for live testing with `pytest-watch` during development: # --testmon # flaky: --no-success-flaky-report --durations=10 -ra --showlocals --doctest-modules --junitxml=.test-results/pytest/results.xml # interpret all the target args as importables: --pyargs # importable packages for test lookup: test ansiblelint.rules doctest_optionflags = ALLOW_UNICODE ELLIPSIS filterwarnings = error # Ansible originated ignore:The _yaml extension module is now located at yaml._yaml and its location is subject to change:DeprecationWarning: # Ansible insides on py310: ignore:_SixMetaPathImporter:ImportWarning ignore:_AnsibleCollectionFinder:ImportWarning ignore:_AnsibleCollectionRootPkgLoader:ImportWarning ignore:_AnsibleCollectionNSPkgLoader.exec_module:ImportWarning ignore:_AnsibleCollectionPkgLoader.exec_module:ImportWarning ignore:_AnsiblePathHookFinder.find_spec:ImportWarning ignore:The distutils package is deprecated and slated for removal:DeprecationWarning # TODO: delete the following ignores once Ansible that we support gets rid of `imp` # Ref: https://github.com/ansible-community/ansible-lint/pull/734 ignore:the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses:DeprecationWarning:ansible.plugins.loader # TODO: delete the following ignores once Ansible gets rid of direct # imports from `collections` ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working:DeprecationWarning ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3, and in 3.9 it will stop working:DeprecationWarning ignore:Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated since Python 3.3,and in 3.9 it will stop working:DeprecationWarning junit_duration_report = call # Our github annotation parser from .github/workflows/tox.yml requires xunit1 format. Ref: # https://github.com/shyim/junit-report-annotations-action/issues/3#issuecomment-663241378 junit_family = xunit1 junit_suite_name = ansible_lint_test_suite minversion = 4.6.6 norecursedirs = build dist docs src/ansible_lint.egg-info .cache .eggs .git .github .tox *.egg python_files = test_*.py # Ref: https://docs.pytest.org/en/latest/reference.html#confval-python_files # Needed to discover legacy nose test modules: Test*.py # Needed to discover embedded Rule tests *Rule.py # Using --pyargs instead of testpath as we embed some tests # See: https://github.com/pytest-dev/pytest/issues/6451#issuecomment-687043537 # testpaths = xfail_strict = true markers = eco: Tests effects on a set of 3rd party ansible repositories ansible-lint-5.4.0/setup.cfg000066400000000000000000000075251420171260700157330ustar00rootroot00000000000000[aliases] dists = clean --all sdist bdist_wheel [metadata] name = ansible-lint url = https://github.com/ansible-community/ansible-lint project_urls = Bug Tracker = https://github.com/ansible-community/ansible-lint/issues CI: GitHub = https://github.com/ansible-community/ansible-lint/actions?query=workflow:gh+branch:main+event:push Code of Conduct = https://docs.ansible.com/ansible/latest/community/code_of_conduct.html Documentation = https://ansible-lint.readthedocs.io/en/latest/ Mailing lists = https://docs.ansible.com/ansible-community/latest/community/communication.html#mailing-list-information Source Code = https://github.com/ansible-community/ansible-lint description = Checks playbooks for practices and behaviour that could potentially be improved long_description = file: README.rst long_description_content_type = text/x-rst author = Will Thames author_email = will@thames.id.au maintainer = Ansible by Red Hat maintainer_email = info@ansible.com license = MIT license_file = LICENSE classifiers = Development Status :: 5 - Production/Stable Environment :: Console Intended Audience :: Developers Intended Audience :: Information Technology Intended Audience :: System Administrators Operating System :: OS Independent License :: OSI Approved :: MIT License License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: Implementation Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: Jython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Bug Tracking Topic :: Software Development :: Quality Assurance Topic :: Software Development :: Testing Topic :: Utilities keywords = ansible lint [options] use_scm_version = True python_requires = >=3.6 package_dir = = src packages = find: zip_safe = False # These are required in actual runtime: install_requires = # ansible-lint does not list ansible as direct dependency in order to # allow user to choose his target version. Be sure that you mention one # of the extras or install a specific version of ansible yourself. enrich>=1.2.6 packaging pyyaml rich>=9.5.1 ruamel.yaml >= 0.15.34,<1; python_version < "3.7" ruamel.yaml >= 0.15.37,<1; python_version >= "3.7" # NOTE: per issue #509 0.15.34 included in debian backports tenacity typing-extensions; python_version < "3.8" wcmatch>=7.0 # MIT [options.entry_points] console_scripts = ansible-lint = ansiblelint.__main__:_run_cli_entrypoint [options.extras_require] # Anyone wanting the full ansible (ACD) should mention it as a standalone # dependency as our extras bring the slim versions. # core will point to ansible-core>=2.11 as soon that is released community = ansible>=2.10 # GPLv3+ # this will move to latest stable ansible as soon that is released core = ansible-core>=2.11.4 # GPLv3+ # will install ansible from devel branch, may break at any moment. ; Disabled due to https://github.com/pypa/twine/issues/726 ; devel = ; ansible-core @ git+https://github.com/ansible/ansible.git # GPLv3+ # yamllint must remain optional yamllint = yamllint >= 1.25.0 # GPLv3 test = coverage >= 6.2, < 6.3 # 6.3 dropped py37 support tomli >= 1.2.3, < 2.0.0 # 2.0.0 dropped py37 support (needed by coverage)) flaky >= 3.7.0 pytest >= 6.0.1 pytest-cov >= 2.10.1 pytest-xdist >= 2.1.0 psutil # soft-dep of pytest-xdist [options.packages.find] where = src [options.package_data] ansiblelint = py.typed [codespell] skip = .tox,.mypy_cache,build,.git,.eggs,pip-wheel-metadata ansible-lint-5.4.0/src/000077500000000000000000000000001420171260700146705ustar00rootroot00000000000000ansible-lint-5.4.0/src/ansiblelint/000077500000000000000000000000001420171260700171745ustar00rootroot00000000000000ansible-lint-5.4.0/src/ansiblelint/__init__.py000066400000000000000000000023571420171260700213140ustar00rootroot00000000000000# Copyright (c) 2013-2014 Will Thames # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """Main ansible-lint package.""" # prerun must run before any other imports from ansiblelint.version import __version__ __all__ = ("__version__",) ansible-lint-5.4.0/src/ansiblelint/__main__.py000077500000000000000000000235661420171260700213050ustar00rootroot00000000000000#!/usr/bin/env python # Copyright (c) 2013-2014 Will Thames # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """Command line implementation.""" import errno import hashlib import logging import os import pathlib import shutil import subprocess import sys from contextlib import contextmanager from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional from enrich.console import should_do_markup from ansiblelint import cli from ansiblelint.app import App from ansiblelint.color import console, console_options, reconfigure, render_yaml from ansiblelint.config import options from ansiblelint.constants import EXIT_CONTROL_C_RC from ansiblelint.file_utils import abspath, cwd, normpath from ansiblelint.prerun import check_ansible_presence, prepare_environment from ansiblelint.skip_utils import normalize_tag from ansiblelint.version import __version__ if TYPE_CHECKING: # RulesCollection must be imported lazily or ansible gets imported too early. from ansiblelint.rules import RulesCollection _logger = logging.getLogger(__name__) def initialize_logger(level: int = 0) -> None: """Set up the global logging level based on the verbosity number.""" # We are about to act on the root logger, which defaults to logging.WARNING. # That is where our 0 (default) value comes from. VERBOSITY_MAP = { -2: logging.CRITICAL, -1: logging.ERROR, 0: logging.WARNING, 1: logging.INFO, 2: logging.DEBUG, } handler = logging.StreamHandler() formatter = logging.Formatter("%(levelname)-8s %(message)s") handler.setFormatter(formatter) logger = logging.getLogger() logger.addHandler(handler) # Unknown logging level is treated as DEBUG logging_level = VERBOSITY_MAP.get(level, logging.DEBUG) logger.setLevel(logging_level) logging.captureWarnings(True) # pass all warnings.warn() messages through logging # Use module-level _logger instance to validate it _logger.debug("Logging initialized to level %s", logging_level) def initialize_options(arguments: Optional[List[str]] = None) -> None: """Load config options and store them inside options module.""" new_options = cli.get_config(arguments or []) new_options.cwd = pathlib.Path.cwd() if new_options.version: ansible_version, _ = check_ansible_presence(exit_on_error=True) print( "ansible-lint {ver!s} using ansible {ansible_ver!s}".format( ver=__version__, ansible_ver=ansible_version ) ) sys.exit(0) if new_options.colored is None: new_options.colored = should_do_markup() # persist loaded configuration inside options module for k, v in new_options.__dict__.items(): setattr(options, k, v) # rename deprecated ids/tags to newer names options.tags = [normalize_tag(tag) for tag in options.tags] options.skip_list = [normalize_tag(tag) for tag in options.skip_list] options.warn_list = [normalize_tag(tag) for tag in options.warn_list] options.configured = True # 6 chars of entropy should be enough cache_key = hashlib.sha256( os.path.abspath(options.project_dir).encode() ).hexdigest()[:6] options.cache_dir = "%s/ansible-lint/%s" % ( os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache")), cache_key, ) def _do_list(rules: "RulesCollection") -> int: # On purpose lazy-imports to avoid pre-loading Ansible # pylint: disable=import-outside-toplevel from ansiblelint.generate_docs import rules_as_rich, rules_as_rst, rules_as_str if options.listrules: _rule_format_map: Dict[str, Callable[..., Any]] = { "plain": rules_as_str, "rich": rules_as_rich, "rst": rules_as_rst, } console.print(_rule_format_map[options.format](rules), highlight=False) return 0 if options.listtags: console.print(render_yaml(rules.listtags())) return 0 # we should not get here! return 1 def main(argv: Optional[List[str]] = None) -> int: """Linter CLI entry point.""" # alter PATH if needed (venv support) path_inject() if argv is None: argv = sys.argv initialize_options(argv[1:]) console_options["force_terminal"] = options.colored reconfigure(console_options) initialize_logger(options.verbosity) _logger.debug("Options: %s", options) _logger.debug(os.getcwd()) app = App(options=options) prepare_environment() check_ansible_presence(exit_on_error=True) # On purpose lazy-imports to avoid pre-loading Ansible # pylint: disable=import-outside-toplevel from ansiblelint.rules import RulesCollection rules = RulesCollection(options.rulesdirs) if options.listrules or options.listtags: return _do_list(rules) if isinstance(options.tags, str): options.tags = options.tags.split(",") from ansiblelint.runner import _get_matches result = _get_matches(rules, options) mark_as_success = False if result.matches and options.progressive: _logger.info( "Matches found, running again on previous revision in order to detect regressions" ) with _previous_revision(): _logger.debug("Options: %s", options) _logger.debug(os.getcwd()) old_result = _get_matches(rules, options) # remove old matches from current list matches_delta = list(set(result.matches) - set(old_result.matches)) if len(matches_delta) == 0: _logger.warning( "Total violations not increased since previous " "commit, will mark result as success. (%s -> %s)", len(old_result.matches), len(matches_delta), ) mark_as_success = True ignored = 0 for match in result.matches: # if match is not new, mark is as ignored if match not in matches_delta: match.ignored = True ignored += 1 if ignored: _logger.warning( "Marked %s previously known violation(s) as ignored due to" " progressive mode.", ignored, ) app.render_matches(result.matches) return app.report_outcome(result, mark_as_success=mark_as_success) @contextmanager def _previous_revision() -> Iterator[None]: """Create or update a temporary workdir containing the previous revision.""" worktree_dir = f"{options.cache_dir}/old-rev" # Update options.exclude_paths to include use the temporary workdir. rel_exclude_paths = [normpath(p) for p in options.exclude_paths] options.exclude_paths = [abspath(p, worktree_dir) for p in rel_exclude_paths] revision = subprocess.run( ["git", "rev-parse", "HEAD^1"], check=True, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, ).stdout p = pathlib.Path(worktree_dir) p.mkdir(parents=True, exist_ok=True) os.system(f"git worktree add -f {worktree_dir} 2>/dev/null") try: with cwd(worktree_dir): os.system(f"git checkout {revision}") yield finally: options.exclude_paths = [abspath(p, os.getcwd()) for p in rel_exclude_paths] def _run_cli_entrypoint() -> None: """Invoke the main entrypoint with current CLI args. This function also processes the runtime exceptions. """ try: sys.exit(main(sys.argv)) except IOError as exc: # NOTE: Only "broken pipe" is acceptable to ignore if exc.errno != errno.EPIPE: raise except KeyboardInterrupt: sys.exit(EXIT_CONTROL_C_RC) except RuntimeError as e: raise SystemExit(str(e)) def path_inject() -> None: """Add python interpreter path to top of PATH to fix outside venv calling.""" # This make it possible to call ansible-lint that was installed inside a # virtualenv without having to pre-activate it. Otherwise subprocess will # either fail to find ansible executables or call the wrong ones. # # This must be run before we do run any subprocesses, and loading config # does this as part of the ansible detection. paths = [x for x in os.environ.get("PATH", "").split(os.pathsep) if x] ansible_path = shutil.which("ansible") if ansible_path: ansible_path = os.path.dirname(ansible_path) py_path = os.path.dirname(sys.executable) # Determine if we need to manipulate PATH if ansible_path not in paths or py_path != ansible_path: # pragma: no cover # tested by test_call_from_outside_venv but coverage cannot detect it paths.insert(0, py_path) os.environ["PATH"] = os.pathsep.join(paths) print(f"WARNING: PATH altered to include {py_path}", file=sys.stderr) if __name__ == "__main__": _run_cli_entrypoint() ansible-lint-5.4.0/src/ansiblelint/_internal/000077500000000000000000000000001420171260700211475ustar00rootroot00000000000000ansible-lint-5.4.0/src/ansiblelint/_internal/__init__.py000066400000000000000000000000001420171260700232460ustar00rootroot00000000000000ansible-lint-5.4.0/src/ansiblelint/_internal/rules.py000066400000000000000000000076211420171260700226610ustar00rootroot00000000000000import logging from typing import TYPE_CHECKING, Any, Dict, List, Union if TYPE_CHECKING: from typing import Optional from ansiblelint.constants import odict from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable _logger = logging.getLogger(__name__) class BaseRule: """Root class used by Rules.""" id: str = "" tags: List[str] = [] shortdesc: str = "" description: str = "" version_added: str = "" severity: str = "" link: str = "" has_dynamic_tags: bool = False needs_raw_task: bool = False def getmatches(self, file: "Lintable") -> List["MatchError"]: """Return all matches while ignoring exceptions.""" matches = [] if not file.path.is_dir(): for method in [self.matchlines, self.matchtasks, self.matchyaml]: try: matches.extend(method(file)) except Exception as e: _logger.debug( "Ignored exception from %s.%s: %s", self.__class__.__name__, method, e, ) else: matches.extend(self.matchdir(file)) return matches def matchlines(self, file: "Lintable") -> List["MatchError"]: """Return matches found for a specific line.""" return [] def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: """Confirm if current rule is matching a specific task. If ``needs_raw_task`` (a class level attribute) is ``True``, then the original task (before normalization) will be made available under ``task["__raw_task__"]``. """ return False def matchtasks(self, file: "Lintable") -> List["MatchError"]: """Return matches for a tasks file.""" return [] def matchyaml(self, file: "Lintable") -> List["MatchError"]: """Return matches found for a specific YAML text.""" return [] def matchplay( self, file: "Lintable", data: "odict[str, Any]" ) -> List["MatchError"]: """Return matches found for a specific playbook.""" return [] def matchdir(self, lintable: "Lintable") -> List["MatchError"]: """Return matches for lintable folders.""" return [] def verbose(self) -> str: """Return a verbose representation of the rule.""" return self.id + ": " + self.shortdesc + "\n " + self.description def match(self, line: str) -> Union[bool, str]: """Confirm if current rule matches the given string.""" return False def __lt__(self, other: "BaseRule") -> bool: """Enable us to sort rules by their id.""" return self.id < other.id class RuntimeErrorRule(BaseRule): """Used to identify errors.""" id = "internal-error" shortdesc = "Unexpected internal error" description = ( "This error can be caused by internal bugs but also by " "custom rules. Instead of just stopping linter we generate the errors and " "continue processing. This allows users to add this rule to their warn list until " "the root cause is fixed." ) severity = "VERY_HIGH" tags = ["core"] version_added = "v5.0.0" class AnsibleParserErrorRule(BaseRule): """Used to mark errors received from Ansible.""" id = "parser-error" shortdesc = "AnsibleParserError" description = "Ansible parser fails; this usually indicates an invalid file." severity = "VERY_HIGH" tags = ["core"] version_added = "v5.0.0" class LoadingFailureRule(BaseRule): """File loading failure.""" id = "load-failure" shortdesc = "Failed to load or parse file" description = "Linter failed to process a YAML file, possible not an Ansible file." severity = "VERY_HIGH" tags = ["core"] version_added = "v4.3.0" ansible-lint-5.4.0/src/ansiblelint/app.py000066400000000000000000000156011420171260700203310ustar00rootroot00000000000000"""Application.""" import logging import os from typing import TYPE_CHECKING, Any, List, Tuple, Type from ansiblelint import formatters from ansiblelint.color import console, console_stderr, render_yaml from ansiblelint.errors import MatchError if TYPE_CHECKING: from argparse import Namespace from typing import Dict # pylint: disable=ungrouped-imports from ansiblelint._internal.rules import BaseRule from ansiblelint.runner import LintResult _logger = logging.getLogger(__package__) class App: """App class represents an execution of the linter.""" def __init__(self, options: "Namespace"): """Construct app run based on already loaded configuration.""" options.skip_list = _sanitize_list_options(options.skip_list) options.warn_list = _sanitize_list_options(options.warn_list) self.options = options formatter_factory = choose_formatter_factory(options) self.formatter = formatter_factory(options.cwd, options.display_relative_path) def render_matches(self, matches: List[MatchError]) -> None: """Display given matches.""" if isinstance(self.formatter, formatters.CodeclimateJSONFormatter): # If formatter CodeclimateJSONFormatter is chosen, # then print only the matches in JSON console.print( self.formatter.format_result(matches), markup=False, highlight=False ) return None ignored_matches = [match for match in matches if match.ignored] fatal_matches = [match for match in matches if not match.ignored] # Displayed ignored matches first if ignored_matches: _logger.warning( "Listing %s violation(s) marked as ignored, likely already known", len(ignored_matches), ) for match in ignored_matches: if match.ignored: # highlight must be off or apostrophes may produce unexpected results console.print(self.formatter.format(match), highlight=False) if fatal_matches: _logger.warning( "Listing %s violation(s) that are fatal", len(fatal_matches) ) for match in fatal_matches: if not match.ignored: console.print(self.formatter.format(match), highlight=False) # If run under GitHub Actions we also want to emit output recognized by it. if os.getenv("GITHUB_ACTIONS") == "true" and os.getenv("GITHUB_WORKFLOW"): formatter = formatters.AnnotationsFormatter(self.options.cwd, True) for match in matches: console.print(formatter.format(match), markup=False, highlight=False) def count_results(self, matches: List[MatchError]) -> Tuple[int, int]: """Count failures and warnings in matches.""" failures = 0 warnings = 0 for match in matches: if {match.rule.id, *match.rule.tags}.isdisjoint(self.options.warn_list): failures += 1 else: warnings += 1 return failures, warnings @staticmethod def _get_matched_skippable_rules( matches: List[MatchError], ) -> "Dict[str, BaseRule]": """Extract the list of matched rules, if skippable, from the list of matches.""" matches_unignored = [match for match in matches if not match.ignored] matched_rules = {match.rule.id: match.rule for match in matches_unignored} # remove unskippable rules from the list for rule_id in list(matched_rules.keys()): if "unskippable" in matched_rules[rule_id].tags: matched_rules.pop(rule_id) return matched_rules def report_outcome( self, result: "LintResult", mark_as_success: bool = False ) -> int: """Display information about how to skip found rules. Returns exit code, 2 if errors were found, 0 when only warnings were found. """ msg = "" failures, warnings = self.count_results(result.matches) matched_rules = self._get_matched_skippable_rules(result.matches) entries = [] for key in sorted(matched_rules.keys()): if {key, *matched_rules[key].tags}.isdisjoint(self.options.warn_list): entries.append(f" - {key} # {matched_rules[key].shortdesc}\n") for match in result.matches: if "experimental" in match.rule.tags: entries.append(" - experimental # all rules tagged as experimental\n") break if entries and not self.options.quiet: console_stderr.print( "You can skip specific rules or tags by adding them to your " "configuration file:" ) msg += """\ # .ansible-lint warn_list: # or 'skip_list' to silence them completely """ msg += "".join(sorted(entries)) # Do not deprecate the old tags just yet. Why? Because it is not currently feasible # to migrate old tags to new tags. There are a lot of things out there that still # use ansible-lint 4 (for example, Ansible Galaxy and Automation Hub imports). If we # replace the old tags, those tools will report warnings. If we do not replace them, # ansible-lint 5 will report warnings. # # We can do the deprecation once the ecosystem caught up at least a bit. # for k, v in used_old_tags.items(): # _logger.warning( # "Replaced deprecated tag '%s' with '%s' but it will become an " # "error in the future.", # k, # v, # ) if result.matches and not self.options.quiet: console_stderr.print(render_yaml(msg)) console_stderr.print( f"Finished with {failures} failure(s), {warnings} warning(s) " f"on {len(result.files)} files." ) if mark_as_success or not failures: return 0 return 2 def choose_formatter_factory( options_list: "Namespace", ) -> Type[formatters.BaseFormatter[Any]]: """Select an output formatter based on the incoming command line arguments.""" r: Type[formatters.BaseFormatter[Any]] = formatters.Formatter if options_list.format == "quiet": r = formatters.QuietFormatter elif options_list.parseable_severity: r = formatters.ParseableSeverityFormatter elif options_list.format == "codeclimate": r = formatters.CodeclimateJSONFormatter elif options_list.parseable or options_list.format == "pep8": r = formatters.ParseableFormatter return r def _sanitize_list_options(tag_list: List[str]) -> List[str]: """Normalize list options.""" # expand comma separated entries tags = set() for t in tag_list: tags.update(str(t).split(",")) # remove duplicates, and return as sorted list return sorted(set(tags)) ansible-lint-5.4.0/src/ansiblelint/cli.py000066400000000000000000000302641420171260700203220ustar00rootroot00000000000000# -*- coding: utf-8 -*- """CLI parser setup and helpers.""" import argparse import logging import os import sys from argparse import Namespace from pathlib import Path from typing import Any, Dict, List, Optional, Sequence, Union import yaml from ansiblelint.config import DEFAULT_KINDS from ansiblelint.constants import ( CUSTOM_RULESDIR_ENVVAR, DEFAULT_RULESDIR, INVALID_CONFIG_RC, ) from ansiblelint.file_utils import ( abspath, expand_path_vars, guess_project_dir, normpath, ) _logger = logging.getLogger(__name__) _PATH_VARS = [ "exclude_paths", "rulesdir", ] def expand_to_normalized_paths( config: Dict[str, Any], base_dir: Optional[str] = None ) -> None: """Mutate given config normalizing any path values in it.""" # config can be None (-c /dev/null) if not config: return base_dir = base_dir or os.getcwd() for paths_var in _PATH_VARS: if paths_var not in config: continue # Cause we don't want to add a variable not present normalized_paths = [] for path in config.pop(paths_var): normalized_path = abspath(expand_path_vars(path), base_dir=base_dir) normalized_paths.append(normalized_path) config[paths_var] = normalized_paths def load_config(config_file: str) -> Dict[Any, Any]: """Load configuration from disk.""" config_path = None if config_file: config_path = os.path.abspath(config_file) if not os.path.exists(config_path): _logger.error("Config file not found '%s'", config_path) sys.exit(INVALID_CONFIG_RC) config_path = config_path or get_config_path() if not config_path or not os.path.exists(config_path): # a missing default config file should not trigger an error return {} try: with open(config_path, "r") as stream: config = yaml.safe_load(stream) if not isinstance(config, dict): _logger.error("Invalid configuration file %s", config_path) sys.exit(INVALID_CONFIG_RC) except yaml.YAMLError as e: _logger.error(e) sys.exit(INVALID_CONFIG_RC) config["config_file"] = config_path # TODO(ssbarnea): implement schema validation for config file if isinstance(config, list): _logger.error( "Invalid configuration '%s', expected YAML mapping in the config file.", config_path, ) sys.exit(INVALID_CONFIG_RC) config_dir = os.path.dirname(config_path) expand_to_normalized_paths(config, config_dir) return config def get_config_path(config_file: str = ".ansible-lint") -> Optional[str]: """Return local config file.""" project_filenames = [config_file] parent = tail = os.getcwd() while tail: for project_filename in project_filenames: filename = os.path.abspath(os.path.join(parent, project_filename)) if os.path.exists(filename): return filename if os.path.exists(os.path.abspath(os.path.join(parent, ".git"))): # Avoid looking outside .git folders as we do not want endup # picking config files from upper level projects if current # project has no config. return None (parent, tail) = os.path.split(parent) return None class AbspathArgAction(argparse.Action): def __call__( self, parser: argparse.ArgumentParser, namespace: Namespace, values: Union[str, Sequence[Any], None], option_string: Optional[str] = None, ) -> None: if isinstance(values, (str, Path)): values = [values] if values: normalized_values = [ Path(expand_path_vars(str(path))).resolve() for path in values ] previous_values = getattr(namespace, self.dest, []) setattr(namespace, self.dest, previous_values + normalized_values) def get_cli_parser() -> argparse.ArgumentParser: """Initialize an argument parser.""" parser = argparse.ArgumentParser() parser.add_argument( "-L", dest="listrules", default=False, action="store_true", help="list all the rules", ) parser.add_argument( "-f", dest="format", default="rich", choices=["rich", "plain", "rst", "codeclimate", "quiet", "pep8"], help="Format used rules output, (default: %(default)s)", ) parser.add_argument( "-q", dest="quiet", default=0, action="count", help="quieter, reduce verbosity, can be specified twice.", ) parser.add_argument( "-p", dest="parseable", default=False, action="store_true", help="parseable output, same as '-f pep8'", ) parser.add_argument( "--parseable-severity", dest="parseable_severity", default=False, action="store_true", help="parseable output including severity of rule", ) parser.add_argument( "--progressive", dest="progressive", default=False, action="store_true", help="Return success if it detects a reduction in number" " of violations compared with previous git commit. This " "feature works only in git repositories.", ) parser.add_argument( "--project-dir", dest="project_dir", default=".", help="Location of project/repository, autodetected based on location " " of configuration file.", ) parser.add_argument( "-r", action=AbspathArgAction, dest="rulesdir", default=[], type=Path, help="Specify custom rule directories. Add -R " f"to keep using embedded rules from {DEFAULT_RULESDIR}", ) parser.add_argument( "-R", action="store_true", default=False, dest="use_default_rules", help="Keep default rules when using -r", ) parser.add_argument( "--show-relpath", dest="display_relative_path", action="store_false", default=True, help="Display path relative to CWD", ) parser.add_argument( "-t", dest="tags", action="append", default=[], help="only check rules whose id/tags match these values", ) parser.add_argument( "-T", dest="listtags", action="store_true", help="list all the tags" ) parser.add_argument( "-v", dest="verbosity", action="count", help="Increase verbosity level (-vv for more)", default=0, ) parser.add_argument( "-x", dest="skip_list", default=[], action="append", help="only check rules whose id/tags do not " "match these values", ) parser.add_argument( "-w", dest="warn_list", default=[], action="append", help="only warn about these rules, unless overridden in " "config file defaults to 'experimental'", ) parser.add_argument( "--enable-list", dest="enable_list", default=[], action="append", help="activate optional rules by their tag name", ) # Do not use store_true/store_false because they create opposite defaults. parser.add_argument( "--nocolor", dest="colored", action="store_const", const=False, help="disable colored output, same as NO_COLOR=1", ) parser.add_argument( "--force-color", dest="colored", action="store_const", const=True, help="Force colored output, same as FORCE_COLOR=1", ) parser.add_argument( "--exclude", dest="exclude_paths", action=AbspathArgAction, type=Path, default=[], help="path to directories or files to skip. " "This option is repeatable.", ) parser.add_argument( "-c", dest="config_file", help="Specify configuration file to use. " 'Defaults to ".ansible-lint"', ) parser.add_argument( "--offline", dest="offline", action="store_const", const=True, help="Disable installation of requirements.yml", ) parser.add_argument( "--version", action="store_true", ) parser.add_argument( dest="lintables", nargs="*", help="One or more files or paths. When missing it will " " enable auto-detection mode.", ) return parser def merge_config(file_config: Dict[Any, Any], cli_config: Namespace) -> Namespace: """Combine the file config with the CLI args.""" bools = ( "display_relative_path", "parseable", "parseable_severity", "quiet", "use_default_rules", "progressive", "offline", ) # maps lists to their default config values lists_map = { "exclude_paths": [".cache", ".git", ".hg", ".svn", ".tox"], "rulesdir": [], "skip_list": [], "tags": [], "warn_list": ["experimental", "role-name"], "mock_modules": [], "mock_roles": [], "enable_list": [], } scalar_map = { "loop_var_prefix": None, "project_dir": ".", } if not file_config: # use defaults if we don't have a config file and the commandline # parameter is not set for entry, default in lists_map.items(): if not getattr(cli_config, entry, None): setattr(cli_config, entry, default) return cli_config for entry in bools: x = getattr(cli_config, entry) or file_config.pop(entry, False) setattr(cli_config, entry, x) for entry, default in scalar_map.items(): x = getattr(cli_config, entry, None) or file_config.pop(entry, default) setattr(cli_config, entry, x) # if either commandline parameter or config file option is set merge # with the other, if neither is set use the default for entry, default in lists_map.items(): if getattr(cli_config, entry, None) or entry in file_config.keys(): value = getattr(cli_config, entry, []) value.extend(file_config.pop(entry, [])) else: value = default setattr(cli_config, entry, value) if "verbosity" in file_config: cli_config.verbosity = cli_config.verbosity + file_config.pop("verbosity") # merge options that can be set only via a file config for entry, value in file_config.items(): setattr(cli_config, entry, value) # append default kinds to the custom list kinds = file_config.get("kinds", []) kinds.extend(DEFAULT_KINDS) setattr(cli_config, "kinds", kinds) return cli_config def get_config(arguments: List[str]) -> Namespace: """Extract the config based on given args.""" parser = get_cli_parser() options = parser.parse_args(arguments) file_config = load_config(options.config_file) config = merge_config(file_config, options) options.rulesdirs = get_rules_dirs(options.rulesdir, options.use_default_rules) if options.project_dir == ".": project_dir = guess_project_dir(options.config_file) options.project_dir = normpath(project_dir) if not options.project_dir or not os.path.exists(options.project_dir): raise RuntimeError( f"Failed to determine a valid project_dir: {options.project_dir}" ) # Compute final verbosity level by subtracting -q counter. options.verbosity -= options.quiet return config def print_help(file: Any = sys.stdout) -> None: """Print help test to the given stream.""" get_cli_parser().print_help(file=file) def get_rules_dirs(rulesdir: List[str], use_default: bool = True) -> List[str]: """Return a list of rules dirs.""" default_ruledirs = [DEFAULT_RULESDIR] default_custom_rulesdir = os.environ.get( CUSTOM_RULESDIR_ENVVAR, os.path.join(DEFAULT_RULESDIR, "custom") ) custom_ruledirs = sorted( str(rdir.resolve()) for rdir in Path(default_custom_rulesdir).iterdir() if rdir.is_dir() and (rdir / "__init__.py").exists() ) if use_default: return rulesdir + custom_ruledirs + default_ruledirs return rulesdir or custom_ruledirs + default_ruledirs ansible-lint-5.4.0/src/ansiblelint/color.py000066400000000000000000000025511420171260700206670ustar00rootroot00000000000000"""Console coloring and terminal support.""" from typing import Any, Dict import rich from rich.console import Console from rich.syntax import Syntax from rich.theme import Theme _theme = Theme( { "info": "cyan", "warning": "dim yellow", "danger": "bold red", "title": "yellow", "error_code": "bright_red", "error_title": "red", "filename": "blue", } ) console_options: Dict[str, Any] = {"emoji": False, "theme": _theme, "soft_wrap": True} console_options_stderr = console_options.copy() console_options_stderr["stderr"] = True console = rich.get_console() console_stderr = Console(**console_options_stderr) def reconfigure(new_options: Dict[str, Any]) -> None: """Reconfigure console options.""" global console_options # pylint: disable=global-statement global console_stderr # pylint: disable=global-statement console_options = new_options rich.reconfigure(**new_options) # see https://github.com/willmcgugan/rich/discussions/484#discussioncomment-200182 console_options_stderr = console_options.copy() console_options_stderr["stderr"] = True tmp_console = Console(**console_options_stderr) console_stderr.__dict__ = tmp_console.__dict__ def render_yaml(text: str) -> Syntax: """Colorize YAMl for nice display.""" return Syntax(text, "yaml", theme="ansi_dark") ansible-lint-5.4.0/src/ansiblelint/config.py000066400000000000000000000134431420171260700210200ustar00rootroot00000000000000"""Store configuration options as a singleton.""" import os import re import subprocess import sys from argparse import Namespace from functools import lru_cache from typing import Any, Dict, List, Optional, Tuple from packaging.version import Version from ansiblelint.constants import ANSIBLE_MISSING_RC DEFAULT_KINDS = [ # Do not sort this list, order matters. {"jinja2": "**/*.j2"}, # jinja2 templates are not always parsable as something else {"jinja2": "**/*.j2.*"}, {"inventory": "**/inventory/**.yml"}, {"requirements": "**/meta/requirements.yml"}, # v1 only # https://docs.ansible.com/ansible/latest/dev_guide/collections_galaxy_meta.html {"galaxy": "**/galaxy.yml"}, # Galaxy collection meta {"reno": "**/releasenotes/*/*.{yaml,yml}"}, # reno release notes {"playbook": "**/playbooks/*.{yml,yaml}"}, {"playbook": "**/*playbook*.{yml,yaml}"}, {"role": "**/roles/*/"}, {"tasks": "**/tasks/**/*.{yaml,yml}"}, {"handlers": "**/handlers/*.{yaml,yml}"}, {"vars": "**/{host_vars,group_vars,vars,defaults}/**/*.{yaml,yml}"}, {"meta": "**/meta/main.{yaml,yml}"}, {"yaml": ".config/molecule/config.{yaml,yml}"}, # molecule global config { "requirements": "**/molecule/*/{collections,requirements}.{yaml,yml}" }, # molecule old collection requirements (v1), ansible 2.8 only {"yaml": "**/molecule/*/{base,molecule}.{yaml,yml}"}, # molecule config {"requirements": "**/requirements.yml"}, # v2 and v1 {"playbook": "**/molecule/*/*.{yaml,yml}"}, # molecule playbooks {"yaml": "**/{.ansible-lint,.yamllint}"}, {"yaml": "**/*.{yaml,yml}"}, {"yaml": "**/.*.{yaml,yml}"}, ] BASE_KINDS = [ # These assignations are only for internal use and are only inspired by # MIME/IANA model. Their purpose is to be able to process a file based on # it type, including generic processing of text files using the prefix. { "text/jinja2": "**/*.j2" }, # jinja2 templates are not always parsable as something else {"text/jinja2": "**/*.j2.*"}, {"text": "**/templates/**/*.*"}, # templates are likely not validable {"text/json": "**/*.json"}, # standardized {"text/markdown": "**/*.md"}, # https://tools.ietf.org/html/rfc7763 {"text/rst": "**/*.rst"}, # https://en.wikipedia.org/wiki/ReStructuredText {"text/ini": "**/*.ini"}, # YAML has no official IANA assignation {"text/yaml": "**/{.ansible-lint,.yamllint}"}, {"text/yaml": "**/*.{yaml,yml}"}, {"text/yaml": "**/.*.{yaml,yml}"}, ] options = Namespace( cache_dir=None, colored=True, configured=False, cwd=".", display_relative_path=True, exclude_paths=[], lintables=[], listrules=False, listtags=False, parseable=False, parseable_severity=False, quiet=False, rulesdirs=[], skip_list=[], tags=[], verbosity=False, warn_list=[], kinds=DEFAULT_KINDS, mock_modules=[], mock_roles=[], loop_var_prefix=None, var_naming_pattern=None, offline=False, project_dir=".", # default should be valid folder (do not use None here) extra_vars=None, enable_list=[], skip_action_validation=True, rules=dict(), # Placeholder to set and keep configurations for each rule. ) # Used to store detected tag deprecations used_old_tags: Dict[str, str] = {} # Used to store collection list paths (with mock paths if needed) collection_list: List[str] = [] def get_rule_config(rule_id: str) -> Dict[str, Any]: """Get configurations for the rule ``rule_id``.""" rule_config = options.rules.get(rule_id, dict()) if not isinstance(rule_config, dict): raise RuntimeError("Invalid rule config for %s: %s" % (rule_id, rule_config)) return rule_config @lru_cache() def ansible_collections_path() -> str: """Return collection path variable for current version of Ansible.""" # respect Ansible behavior, which is to load old name if present for env_var in ["ANSIBLE_COLLECTIONS_PATHS", "ANSIBLE_COLLECTIONS_PATH"]: if env_var in os.environ: return env_var # https://github.com/ansible/ansible/pull/70007 if ansible_version() >= ansible_version("2.10.0.dev0"): return "ANSIBLE_COLLECTIONS_PATH" return "ANSIBLE_COLLECTIONS_PATHS" def parse_ansible_version(stdout: str) -> Tuple[str, Optional[str]]: """Parse output of 'ansible --version'.""" # Ansible can produce extra output before displaying version in debug mode. # ansible-core 2.11+: 'ansible [core 2.11.3]' match = re.search(r"^ansible \[(?:core|base) ([^\]]+)\]", stdout, re.MULTILINE) if match: return match.group(1), None # ansible-base 2.10 and Ansible 2.9: 'ansible 2.x.y' match = re.search(r"^ansible ([^\s]+)", stdout, re.MULTILINE) if match: return match.group(1), None return "", "FATAL: Unable parse ansible cli version: %s" % stdout @lru_cache() def ansible_version(version: str = "") -> Version: """Return current Version object for Ansible. If version is not mentioned, it returns current version as detected. When version argument is mentioned, it return converts the version string to Version object in order to make it usable in comparisons. """ if not version: proc = subprocess.run( ["ansible", "--version"], universal_newlines=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) if proc.returncode == 0: version, error = parse_ansible_version(proc.stdout) if error is not None: print(error) sys.exit(ANSIBLE_MISSING_RC) else: print( "Unable to find a working copy of ansible executable.", proc, ) sys.exit(ANSIBLE_MISSING_RC) return Version(version) ansible-lint-5.4.0/src/ansiblelint/constants.py000066400000000000000000000062661420171260700215740ustar00rootroot00000000000000"""Constants used by AnsibleLint.""" import os.path import sys # mypy/pylint idiom for py36-py38 compatibility # https://github.com/python/typeshed/issues/3500#issuecomment-560958608 if sys.version_info >= (3, 8): from typing import Literal # pylint: disable=no-name-in-module else: from typing_extensions import Literal DEFAULT_RULESDIR = os.path.join(os.path.dirname(__file__), "rules") CUSTOM_RULESDIR_ENVVAR = "ANSIBLE_LINT_CUSTOM_RULESDIR" INVALID_CONFIG_RC = 2 ANSIBLE_FAILURE_RC = 3 ANSIBLE_MISSING_RC = 4 INVALID_PREREQUISITES_RC = 10 EXIT_CONTROL_C_RC = 130 # Minimal version of Ansible we support for runtime ANSIBLE_MIN_VERSION = "2.9" # Based on https://docs.ansible.com/ansible/latest/reference_appendices/config.html ANSIBLE_DEFAULT_ROLES_PATH = ( "~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles" ) ANSIBLE_MOCKED_MODULE = """\ # This is a mocked Ansible module generated by ansible-lint from ansible.module_utils.basic import AnsibleModule DOCUMENTATION = ''' module: {name} short_description: Mocked version_added: "1.0.0" description: Mocked author: - ansible-lint (@nobody) ''' EXAMPLES = '''mocked''' RETURN = '''mocked''' def main(): result = dict( changed=False, original_message='', message='') module = AnsibleModule( argument_spec=dict(), supports_check_mode=True, ) module.exit_json(**result) if __name__ == "__main__": main() """ FileType = Literal[ "playbook", "meta", # role meta "tasks", # includes pre_tasks, post_tasks "handlers", # very similar to tasks but with some specificts # https://docs.ansible.com/ansible/latest/galaxy/user_guide.html#installing-roles-and-collections-from-the-same-requirements-yml-file "requirements", "role", # that is a folder! "yaml", # generic yaml file, previously reported as unknown file type "", # unknown file type ] # odict is the base class used to represent data model of Ansible # playbooks and tasks. odict = dict if sys.version_info[:2] < (3, 7): try: # pylint: disable=unused-import from collections import OrderedDict as odict # noqa: 401 except ImportError: pass # Deprecated tags/ids and their newer names RENAMED_TAGS = { "102": "no-jinja-when", "104": "deprecated-bare-vars", "105": "deprecated-module", "106": "role-name", "202": "risky-octal", "203": "no-tabs", "205": "playbook-extension", "206": "var-spacing", "207": "no-jinja-nesting", "208": "risky-file-permissions", "301": "no-changed-when", "302": "deprecated-command-syntax", "303": "command-instead-of-module", "304": "inline-env-var", "305": "command-instead-of-shell", "306": "risky-shell-pipe", "401": "git-latest", "402": "hg-latest", "403": "package-latest", "404": "no-relative-paths", "501": "partial-become", "502": "unnamed-task", "503": "no-handler", "504": "deprecated-local-action", "505": "missing-import", "601": "literal-compare", "602": "empty-string-compare", "701": "meta-no-info", "702": "meta-no-tags", "703": "meta-incorrect", "704": "meta-video-links", "911": "syntax-check", } ansible-lint-5.4.0/src/ansiblelint/errors.py000066400000000000000000000104761420171260700210720ustar00rootroot00000000000000"""Exceptions and error representations.""" import functools from typing import Any, Optional, Union from ansiblelint._internal.rules import BaseRule, RuntimeErrorRule from ansiblelint.file_utils import Lintable, normpath @functools.total_ordering class MatchError(ValueError): """Rule violation detected during linting. It can be raised as Exception but also just added to the list of found rules violations. Note that line argument is not considered when building hash of an instance. """ # IMPORTANT: any additional comparison protocol methods must return # IMPORTANT: `NotImplemented` singleton to allow the check to use the # IMPORTANT: other object's fallbacks. # Ref: https://docs.python.org/3/reference/datamodel.html#object.__lt__ # pylint: disable=too-many-arguments def __init__( self, message: Optional[str] = None, # most linters report use (1,1) base, including yamllint and flake8 # we should never report line 0 or column 0 in output. linenumber: int = 1, column: Optional[int] = None, details: str = "", filename: Optional[Union[str, Lintable]] = None, rule: BaseRule = RuntimeErrorRule(), tag: Optional[str] = None, # optional fine-graded tag ) -> None: """Initialize a MatchError instance.""" super().__init__(message) if rule.__class__ is RuntimeErrorRule and not message: raise TypeError( f"{self.__class__.__name__}() missing a " "required argument: one of 'message' or 'rule'", ) self.message = str(message or getattr(rule, "shortdesc", "")) # Safety measture to ensure we do not endup with incorrect indexes if linenumber == 0: raise RuntimeError( "MatchError called incorrectly as line numbers start with 1" ) if column == 0: raise RuntimeError( "MatchError called incorrectly as column numbers start with 1" ) self.linenumber = linenumber self.column = column self.details = details self.filename = "" if filename: if isinstance(filename, Lintable): self.filename = normpath(str(filename.path)) else: self.filename = normpath(filename) self.rule = rule self.ignored = False # If set it will be displayed but not counted as failure # This can be used by rules that can report multiple errors type, so # we can still filter by them. self.tag = tag def __repr__(self) -> str: """Return a MatchError instance representation.""" formatstr = "[{0}] ({1}) matched {2}:{3} {4}" # note that `rule.id` can be int, str or even missing, as users # can defined their own custom rules. _id = getattr(self.rule, "id", "000") return formatstr.format( _id, self.message, self.filename, self.linenumber, self.details ) @property def position(self) -> str: """Return error positioniong, with column number if available.""" if self.column: return f"{self.linenumber}:{self.column}" return str(self.linenumber) @property def _hash_key(self) -> Any: # line attr is knowingly excluded, as dict is not hashable return ( self.filename, self.linenumber, str(getattr(self.rule, "id", 0)), self.message, self.details, # -1 is used here to force errors with no column to sort before # all other errors. -1 if self.column is None else self.column, ) def __lt__(self, other: object) -> bool: """Return whether the current object is less than the other.""" if not isinstance(other, self.__class__): return NotImplemented return bool(self._hash_key < other._hash_key) def __hash__(self) -> int: """Return a hash value of the MatchError instance.""" return hash(self._hash_key) def __eq__(self, other: object) -> bool: """Identify whether the other object represents the same rule match.""" if not isinstance(other, self.__class__): return NotImplemented return self.__hash__() == other.__hash__() ansible-lint-5.4.0/src/ansiblelint/file_utils.py000066400000000000000000000254331420171260700217140ustar00rootroot00000000000000"""Utility functions related to file operations.""" import copy import logging import os import pathlib import subprocess import sys from argparse import Namespace from collections import OrderedDict from contextlib import contextmanager from pathlib import Path from tempfile import NamedTemporaryFile from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Set, Union # import wcmatch import wcmatch.pathlib from wcmatch.wcmatch import RECURSIVE, WcMatch from ansiblelint.config import BASE_KINDS, options from ansiblelint.constants import FileType if TYPE_CHECKING: # https://github.com/PyCQA/pylint/issues/3979 BasePathLike = os.PathLike[Any] # pylint: disable=unsubscriptable-object else: BasePathLike = os.PathLike _logger = logging.getLogger(__package__) def abspath(path: str, base_dir: str) -> str: """Make relative path absolute relative to given directory. Args: path (str): the path to make absolute base_dir (str): the directory from which make \ relative paths absolute """ if not os.path.isabs(path): # Don't use abspath as it assumes path is relative to cwd. # We want it relative to base_dir. path = os.path.join(base_dir, path) return os.path.normpath(path) def normpath(path: Union[str, BasePathLike]) -> str: """ Normalize a path in order to provide a more consistent output. Currently it generates a relative path but in the future we may want to make this user configurable. """ # conversion to string in order to allow receiving non string objects relpath = os.path.relpath(str(path)) abspath = os.path.abspath(str(path)) # we avoid returning relative paths that endup at root level if abspath in relpath: return abspath return relpath @contextmanager def cwd(path: Union[str, BasePathLike]) -> Iterator[None]: """Context manager for temporary changing current working directory.""" old_pwd = os.getcwd() os.chdir(path) try: yield finally: os.chdir(old_pwd) def expand_path_vars(path: str) -> str: """Expand the environment or ~ variables in a path string.""" # It may be possible for function to be called with a Path object path = str(path).strip() path = os.path.expanduser(path) path = os.path.expandvars(path) return path def expand_paths_vars(paths: List[str]) -> List[str]: """Expand the environment or ~ variables in a list.""" paths = [expand_path_vars(p) for p in paths] return paths def kind_from_path(path: Path, base: bool = False) -> FileType: """Determine the file kind based on its name. When called with base=True, it will return the base file type instead of the explicit one. That is expected to return 'yaml' for any yaml files. """ # pathlib.Path.match patterns are very limited, they do not support *a*.yml # glob.glob supports **/foo.yml but not multiple extensions pathex = wcmatch.pathlib.PurePath(str(path.absolute().resolve())) kinds = options.kinds if not base else BASE_KINDS for entry in kinds: for k, v in entry.items(): if pathex.globmatch( v, flags=( wcmatch.pathlib.GLOBSTAR | wcmatch.pathlib.BRACE | wcmatch.pathlib.DOTGLOB ), ): return str(k) # type: ignore if base: # Unknown base file type is default return "" if path.is_dir(): return "role" if str(path) == "/dev/stdin": return "playbook" # Unknown file types report a empty string (evaluated as False) return "" class Lintable: """Defines a file/folder that can be linted. Providing file content when creating the object allow creation of in-memory instances that do not need files to be present on disk. """ def __init__( self, name: Union[str, Path], content: Optional[str] = None, kind: Optional[FileType] = None, ): """Create a Lintable instance.""" # Filename is effective file on disk, for stdin is a namedtempfile self.filename: str = str(name) self.dir: str = "" self.kind: Optional[FileType] = None if isinstance(name, str): self.name = normpath(name) self.path = Path(self.name) else: self.name = str(name) self.path = name self._content = content # if the lintable is part of a role, we save role folder name self.role = "" parts = self.path.parent.parts if "roles" in parts: role = self.path while role.parent.name != "roles" and role.name: role = role.parent if role.exists: self.role = role.name if str(self.path) in ["/dev/stdin", "-"]: # pylint: disable=consider-using-with self.file = NamedTemporaryFile(mode="w+", suffix="playbook.yml") self.filename = self.file.name self._content = sys.stdin.read() self.file.write(self._content) self.file.flush() self.path = Path(self.file.name) self.name = "stdin" self.kind = "playbook" self.dir = "/" else: self.kind = kind or kind_from_path(self.path) # We store absolute directory in dir if not self.dir: if self.kind == "role": self.dir = str(self.path.resolve()) else: self.dir = str(self.path.parent.resolve()) # determine base file kind (yaml, xml, ini, ...) self.base_kind = kind_from_path(self.path, base=True) def __getitem__(self, key: Any) -> Any: """Provide compatibility subscriptable support.""" if key == "path": return str(self.path) if key == "type": return str(self.kind) raise NotImplementedError() def get(self, key: Any, default: Any = None) -> Any: """Provide compatibility subscriptable support.""" try: return self.__getitem__(key) except NotImplementedError: return default @property def content(self) -> str: """Retried file content, from internal cache or disk.""" if self._content is None: with open(self.path.resolve(), mode="r", encoding="utf-8") as f: self._content = f.read() return self._content def __hash__(self) -> int: """Return a hash value of the lintables.""" return hash((self.name, self.kind)) def __eq__(self, other: object) -> bool: """Identify whether the other object represents the same rule match.""" if isinstance(other, Lintable): return bool(self.name == other.name and self.kind == other.kind) return False def __repr__(self) -> str: """Return user friendly representation of a lintable.""" return f"{self.name} ({self.kind})" def discover_lintables(options: Namespace) -> Dict[str, Any]: """Find all files that we know how to lint.""" # git is preferred as it also considers .gitignore git_command_present = [ "git", "ls-files", "--cached", "--others", "--exclude-standard", "-z", ] git_command_absent = ["git", "ls-files", "--deleted", "-z"] out = None try: out_present = subprocess.check_output( git_command_present, stderr=subprocess.STDOUT, universal_newlines=True ).split("\x00")[:-1] _logger.info( "Discovered files to lint using: %s", " ".join(git_command_present) ) out_absent = subprocess.check_output( git_command_absent, stderr=subprocess.STDOUT, universal_newlines=True ).split("\x00")[:-1] _logger.info("Excluded removed files using: %s", " ".join(git_command_absent)) out = set(out_present) - set(out_absent) except subprocess.CalledProcessError as exc: if not (exc.returncode == 128 and "fatal: not a git repository" in exc.output): _logger.warning( "Failed to discover lintable files using git: %s", exc.output.rstrip("\n"), ) except FileNotFoundError as exc: if options.verbosity: _logger.warning("Failed to locate command: %s", exc) if out is None: exclude_pattern = "|".join(str(x) for x in options.exclude_paths) _logger.info("Looking up for files, excluding %s ...", exclude_pattern) # remove './' prefix from output of WcMatch out = { strip_dotslash_prefix(fname) for fname in WcMatch( ".", exclude_pattern=exclude_pattern, flags=RECURSIVE, limit=256 ).match() } return OrderedDict.fromkeys(sorted(out)) def strip_dotslash_prefix(fname: str) -> str: """Remove ./ leading from filenames.""" return fname[2:] if fname.startswith("./") else fname def guess_project_dir(config_file: Optional[str]) -> str: """Return detected project dir or current working directory.""" path = None if config_file is not None: target = pathlib.Path(config_file) if target.exists(): path = str(target.parent.absolute()) if path is None: try: result = subprocess.run( ["git", "rev-parse", "--show-toplevel"], stderr=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True, check=True, ) path = result.stdout.splitlines()[0] except subprocess.CalledProcessError as exc: if not ( exc.returncode == 128 and "fatal: not a git repository" in exc.stderr ): _logger.warning( "Failed to guess project directory using git: %s", exc.stderr.rstrip("\n"), ) except FileNotFoundError as exc: _logger.warning("Failed to locate command: %s", exc) if path is None: path = os.getcwd() _logger.info( "Guessed %s as project root directory", path, ) return path def expand_dirs_in_lintables(lintables: Set[Lintable]) -> None: """Return all recognized lintables within given directory.""" should_expand = False for item in lintables: if item.path.is_dir(): should_expand = True break if should_expand: # this relies on git and we do not want to call unless needed all_files = discover_lintables(options) for item in copy.copy(lintables): if item.path.is_dir(): for filename in all_files: if filename.startswith(str(item.path)): lintables.add(Lintable(filename)) ansible-lint-5.4.0/src/ansiblelint/formatters/000077500000000000000000000000001420171260700213625ustar00rootroot00000000000000ansible-lint-5.4.0/src/ansiblelint/formatters/__init__.py000066400000000000000000000167361420171260700235100ustar00rootroot00000000000000"""Output formatters.""" import hashlib import json import os from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, Generic, List, TypeVar, Union import rich from ansiblelint.config import options if TYPE_CHECKING: from ansiblelint.errors import MatchError T = TypeVar("T", bound="BaseFormatter") # type: ignore class BaseFormatter(Generic[T]): """Formatter of ansible-lint output. Base class for output formatters. Args: base_dir (str|Path): reference directory against which display relative path. display_relative_path (bool): whether to show path as relative or absolute """ def __init__(self, base_dir: Union[str, Path], display_relative_path: bool) -> None: """Initialize a BaseFormatter instance.""" if isinstance(base_dir, str): base_dir = Path(base_dir) if base_dir: # can be None base_dir = base_dir.absolute() # Required 'cause os.path.relpath() does not accept Path before 3.6 if isinstance(base_dir, Path): base_dir = str(base_dir) # Drop when Python 3.5 is no longer supported self._base_dir = base_dir if display_relative_path else None def _format_path(self, path: Union[str, Path]) -> str: # Required 'cause os.path.relpath() does not accept Path before 3.6 if isinstance(path, Path): path = str(path) # Drop when Python 3.5 is no longer supported if not self._base_dir or not path: return path # Use os.path.relpath 'cause Path.relative_to() misbehaves return os.path.relpath(path, start=self._base_dir) def format(self, match: "MatchError") -> str: return str(match) def escape(self, text: str) -> str: """Escapes a string to avoid processing it as markup.""" return rich.markup.escape(text) class Formatter(BaseFormatter): # type: ignore def format(self, match: "MatchError") -> str: _id = getattr(match.rule, "id", "000") result = f"[error_code]{_id}[/][dim]:[/] [error_title]{self.escape(match.message)}[/]" if match.tag: result += f" [dim][error_code]({match.tag})[/][/]" result += ( "\n" f"[filename]{self._format_path(match.filename or '')}[/]:{match.position}" ) if match.details: result += f" [dim]{match.details}[/]" result += "\n" return result class QuietFormatter(BaseFormatter[Any]): def format(self, match: "MatchError") -> str: return ( f"[error_code]{match.rule.id}[/] " f"[filename]{self._format_path(match.filename or '')}[/]:{match.position}" ) class ParseableFormatter(BaseFormatter[Any]): """Parseable uses PEP8 compatible format.""" def format(self, match: "MatchError") -> str: result = ( f"[filename]{self._format_path(match.filename or '')}[/]:{match.position}: " f"[error_code]{match.rule.id}[/]" ) if not options.quiet: result += f" [dim]{match.message}[/]" if match.tag: result += f" [dim][error_code]({match.tag})[/][/]" return result class AnnotationsFormatter(BaseFormatter): # type: ignore # https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message """Formatter for emitting violations as GitHub Workflow Commands. These commands trigger the GHA Workflow runners platform to post violations in a form of GitHub Checks API annotations that appear rendered in pull- request files view. ::debug file={name},line={line},col={col},severity={severity}::{message} ::warning file={name},line={line},col={col},severity={severity}::{message} ::error file={name},line={line},col={col},severity={severity}::{message} Supported levels: debug, warning, error """ def format(self, match: "MatchError") -> str: """Prepare a match instance for reporting as a GitHub Actions annotation.""" level = self._severity_to_level(match.rule.severity) file_path = self._format_path(match.filename or "") line_num = match.linenumber rule_id = match.rule.id severity = match.rule.severity violation_details = self.escape(match.message) if match.column: col = f",col={match.column}" else: col = "" return ( f"::{level} file={file_path},line={line_num}{col},severity={severity}" f"::{rule_id} {violation_details}" ) @staticmethod def _severity_to_level(severity: str) -> str: if severity in ["VERY_LOW", "LOW"]: return "warning" if severity in ["INFO"]: return "debug" # ['MEDIUM', 'HIGH', 'VERY_HIGH'] or anything else return "error" class ParseableSeverityFormatter(BaseFormatter[Any]): def format(self, match: "MatchError") -> str: filename = self._format_path(match.filename or "") position = match.position rule_id = "{0}".format(match.rule.id) severity = match.rule.severity message = self.escape(str(match.message)) return ( f"[filename]{filename}[/]:{position}: [[error_code]{rule_id}[/]] " f"[[error_code]{severity}[/]] [dim]{message}[/]" ) class CodeclimateJSONFormatter(BaseFormatter[Any]): """Formatter for emitting violations in Codeclimate JSON report format. The formatter expects a list of MatchError objects and returns a JSON formatted string. The spec for the codeclimate report can be found here: https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#user-content-data-types """ def format_result(self, matches: List["MatchError"]) -> str: if not isinstance(matches, list): raise RuntimeError( "The CodeclimatJSONFormatter was expecting a list of MatchError." ) result = [] for match in matches: issue: Dict[str, Any] = {} issue["type"] = "issue" issue["check_name"] = f"[{match.rule.id}] {match.message}" issue["categories"] = match.rule.tags issue["severity"] = self._severity_to_level(match.rule.severity) issue["description"] = self.escape(str(match.rule.description)) issue["fingerprint"] = hashlib.sha256( repr(match).encode("utf-8") ).hexdigest() issue["location"] = {} issue["location"]["path"] = self._format_path(match.filename or "") issue["location"]["lines"] = {} if match.column: issue["location"]["lines"]["begin"] = {} issue["location"]["lines"]["begin"]["line"] = match.linenumber issue["location"]["lines"]["begin"]["column"] = match.column else: issue["location"]["lines"]["begin"] = match.linenumber if match.details: issue["content"] = {} issue["content"]["body"] = match.details # Append issue to result list result.append(issue) return json.dumps(result) @staticmethod def _severity_to_level(severity: str) -> str: if severity in ["LOW"]: return "minor" if severity in ["MEDIUM"]: return "major" if severity in ["HIGH"]: return "critical" if severity in ["VERY_HIGH"]: return "blocker" # VERY_LOW, INFO or anything else return "info" ansible-lint-5.4.0/src/ansiblelint/generate_docs.py000066400000000000000000000037741420171260700223630ustar00rootroot00000000000000"""Utils to generate rule table .rst documentation.""" import logging from typing import Iterable from rich import box # Remove this compatibility try-catch block once we drop support for rich < 10.7.0 try: from rich.console import group except ImportError: from rich.console import render_group as group # type: ignore from rich.markdown import Markdown from rich.table import Table from ansiblelint.rules import RulesCollection DOC_HEADER = """ .. _lint_default_rules: Default Rules ============= .. contents:: :local: Below you can see the list of default rules Ansible Lint use to evaluate playbooks and roles: """ _logger = logging.getLogger(__name__) def rules_as_str(rules: RulesCollection) -> str: """Return rules as string.""" return str(rules) def rules_as_rst(rules: RulesCollection) -> str: """Return RST documentation for a list of rules.""" r = DOC_HEADER for d in rules: title = f"{d.id}" description = d.description if d.link: description += " `(more) <%s>`__" % d.link r += f"\n\n.. _{d.id}:\n\n{title}\n{'*' * len(title)}\n\n{d.shortdesc}\n\n{description}" return r @group() def rules_as_rich(rules: RulesCollection) -> Iterable[Table]: """Print documentation for a list of rules, returns empty string.""" width = max(16, *[len(rule.id) for rule in rules]) for rule in rules: table = Table(show_header=True, header_style="title", box=box.MINIMAL) table.add_column(rule.id, style="dim", width=width) table.add_column(Markdown(rule.shortdesc)) description = rule.description if rule.link: description += " [(more)](%s)" % rule.link table.add_row("description", Markdown(description)) if rule.version_added: table.add_row("version_added", rule.version_added) if rule.tags: table.add_row("tags", ", ".join(rule.tags)) if rule.severity: table.add_row("severity", rule.severity) yield table ansible-lint-5.4.0/src/ansiblelint/loaders.py000066400000000000000000000003761420171260700212050ustar00rootroot00000000000000"""Utilities for loading various files.""" from typing import Any import yaml def yaml_from_file(filepath: str) -> Any: """Return a loaded YAML file.""" with open(filepath) as content: return yaml.load(content, Loader=yaml.FullLoader) ansible-lint-5.4.0/src/ansiblelint/logger.py000066400000000000000000000007311420171260700210260ustar00rootroot00000000000000"""Utils related to logging.""" import logging import time from contextlib import contextmanager from typing import Any, Iterator _logger = logging.getLogger(__name__) @contextmanager def timed_info(msg: Any, *args: Any) -> Iterator[None]: """Context manager for logging slow operations, mentions duration.""" start = time.time() try: yield finally: elapsed = time.time() - start _logger.info(msg + " (%.2fs)", *(*args, elapsed)) ansible-lint-5.4.0/src/ansiblelint/prerun.py000066400000000000000000000432171420171260700210700ustar00rootroot00000000000000"""Utilities for configuring ansible runtime environment.""" import json import logging import os import pathlib import re import subprocess import sys from functools import lru_cache from typing import Any, Dict, List, Optional, Tuple, Type, Union import packaging import tenacity from packaging import version from ansiblelint.config import ansible_collections_path, options, parse_ansible_version from ansiblelint.constants import ( ANSIBLE_DEFAULT_ROLES_PATH, ANSIBLE_MIN_VERSION, ANSIBLE_MISSING_RC, ANSIBLE_MOCKED_MODULE, INVALID_CONFIG_RC, INVALID_PREREQUISITES_RC, ) from ansiblelint.loaders import yaml_from_file _logger = logging.getLogger(__name__) def check_ansible_presence(exit_on_error: bool = False) -> Tuple[str, str]: """Assures we stop execution if Ansible is missing or outdated. Returne found version and an optional exception if something wrong was detected. """ @lru_cache() def _get_ver_err() -> Tuple[str, str]: err = "" failed = False ver = "" result = subprocess.run( args=["ansible", "--version"], stdout=subprocess.PIPE, universal_newlines=True, check=False, ) if result.returncode != 0: return ( ver, "FATAL: Unable to retrieve ansible cli version: %s" % result.stdout, ) ver, error = parse_ansible_version(result.stdout) if error is not None: return "", error try: # pylint: disable=import-outside-toplevel from ansible.release import __version__ as ansible_module_version if version.parse(ansible_module_version) < version.parse( ANSIBLE_MIN_VERSION ): failed = True except (ImportError, ModuleNotFoundError) as e: failed = True ansible_module_version = "none" err += f"{e}\n" if failed: err += ( "FATAL: ansible-lint requires a version of Ansible package" " >= %s, but %s was found. " "Please install a compatible version using the same python interpreter. See " "https://docs.ansible.com/ansible/latest/installation_guide" "/intro_installation.html#installing-ansible-with-pip" % (ANSIBLE_MIN_VERSION, ansible_module_version) ) elif ver != ansible_module_version: err = ( f"FATAL: Ansible CLI ({ver}) and python module" f" ({ansible_module_version}) versions do not match. This " "indicates a broken execution environment." ) return ver, err ver, err = _get_ver_err() if exit_on_error and err: _logger.error(err) sys.exit(ANSIBLE_MISSING_RC) return ver, err def install_collection(collection: str, destination: Optional[str] = None) -> None: """Install an Ansible collection. Can accept version constraints like 'foo.bar:>=1.2.3' """ cmd = [ "ansible-galaxy", "collection", "install", "--force", # required for ansible 2.9 "-v", ] if destination: cmd.extend(["-p", destination]) cmd.append(f"{collection}") _logger.info("Running %s", " ".join(cmd)) run = subprocess.run( cmd, universal_newlines=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) if run.returncode != 0: _logger.error("Command returned %s code:\n%s", run.returncode, run.stdout) sys.exit(INVALID_PREREQUISITES_RC) @tenacity.retry( # Retry up to 3 times as galaxy server can return errors reraise=True, wait=tenacity.wait_fixed(30), # type: ignore stop=tenacity.stop_after_attempt(3), # type: ignore before_sleep=tenacity.after_log(_logger, logging.WARNING), # type: ignore ) def install_requirements(requirement: str) -> None: """Install dependencies from a requirements.yml.""" if not os.path.exists(requirement): return cmd = [ "ansible-galaxy", "role", "install", "--force", # required for ansible 2.9 "--roles-path", f"{options.cache_dir}/roles", "-vr", f"{requirement}", ] _logger.info("Running %s", " ".join(cmd)) run = subprocess.run( cmd, universal_newlines=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) if run.returncode != 0: _logger.error(run.stdout) raise RuntimeError(run.returncode) # Run galaxy collection install works on v2 requirements.yml if "collections" in yaml_from_file(requirement): cmd = [ "ansible-galaxy", "collection", "install", "--force", # required for ansible 2.9 "-p", f"{options.cache_dir}/collections", "-vr", f"{requirement}", ] _logger.info("Running %s", " ".join(cmd)) run = subprocess.run( cmd, universal_newlines=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, ) if run.returncode != 0: _logger.error(run.stdout) raise RuntimeError(run.returncode) def prepare_environment(required_collections: Optional[Dict[str, str]] = None) -> None: """Make dependencies available if needed.""" if not options.configured: # Allow method to be used without calling the command line, so we can # reuse it in other tools, like molecule. # pylint: disable=import-outside-toplevel,cyclic-import from ansiblelint.__main__ import initialize_options initialize_options() if not options.offline: install_requirements("requirements.yml") for req in pathlib.Path(".").glob("molecule/*/requirements.yml"): install_requirements(str(req)) if required_collections: for name, min_version in required_collections.items(): install_collection( f"{name}:>={min_version}", destination=f"{options.cache_dir}/collections" if options.cache_dir else None, ) _install_galaxy_role() _perform_mockings() _prepare_ansible_paths() def _get_galaxy_role_ns(galaxy_infos: Dict[str, Any]) -> str: """Compute role namespace from meta/main.yml, including trailing dot.""" role_namespace = galaxy_infos.get("namespace", "") if len(role_namespace) == 0: role_namespace = galaxy_infos.get("author", "") # if there's a space in the name space, it's likely author name # and not the galaxy login, so act as if there was no namespace if re.match(r"^\w+ \w+", role_namespace): role_namespace = "" else: role_namespace = f"{role_namespace}." if not isinstance(role_namespace, str): raise RuntimeError("Role namespace must be string, not %s" % role_namespace) return role_namespace def _get_galaxy_role_name(galaxy_infos: Dict[str, Any]) -> str: """Compute role name from meta/main.yml.""" return galaxy_infos.get("role_name", "") def _get_role_fqrn(galaxy_infos: Dict[str, Any]) -> str: """Compute role fqrn.""" role_namespace = _get_galaxy_role_ns(galaxy_infos) role_name = _get_galaxy_role_name(galaxy_infos) if len(role_name) == 0: role_name = pathlib.Path(".").absolute().name role_name = re.sub(r"(ansible-|ansible-role-)", "", role_name) return f"{role_namespace}{role_name}" def _install_galaxy_role() -> None: """Detect standalone galaxy role and installs it.""" if not os.path.exists("meta/main.yml"): return yaml = yaml_from_file("meta/main.yml") if "galaxy_info" not in yaml: return fqrn = _get_role_fqrn(yaml["galaxy_info"]) if "role-name" not in options.skip_list: if not re.match(r"[a-z0-9][a-z0-9_]+\.[a-z][a-z0-9_]+$", fqrn): msg = ( """\ Computed fully qualified role name of %s does not follow current galaxy requirements. Please edit meta/main.yml and assure we can correctly determine full role name: galaxy_info: role_name: my_name # if absent directory name hosting role is used instead namespace: my_galaxy_namespace # if absent, author is used instead Namespace: https://galaxy.ansible.com/docs/contributing/namespaces.html#galaxy-namespace-limitations Role: https://galaxy.ansible.com/docs/contributing/creating_role.html#role-names As an alternative, you can add 'role-name' to either skip_list or warn_list. """ % fqrn ) if "role-name" in options.warn_list: _logger.warning(msg) else: _logger.error(msg) sys.exit(INVALID_PREREQUISITES_RC) else: # when 'role-name' is in skip_list, we stick to plain role names if "role_name" in yaml["galaxy_info"]: role_namespace = _get_galaxy_role_ns(yaml["galaxy_info"]) role_name = _get_galaxy_role_name(yaml["galaxy_info"]) fqrn = f"{role_namespace}{role_name}" else: fqrn = pathlib.Path(".").absolute().name p = pathlib.Path(f"{options.cache_dir}/roles") p.mkdir(parents=True, exist_ok=True) link_path = p / fqrn # despite documentation stating that is_file() reports true for symlinks, # it appears that is_dir() reports true instead, so we rely on exits(). target = pathlib.Path(options.project_dir).absolute() if not link_path.exists() or os.readlink(link_path) != str(target): if link_path.exists(): link_path.unlink() link_path.symlink_to(target, target_is_directory=True) _logger.info( "Using %s symlink to current repository in order to enable Ansible to find the role using its expected full name.", link_path, ) def _prepare_ansible_paths() -> None: """Configure Ansible environment variables.""" library_paths: List[str] = [] roles_path: List[str] = [] collection_list: List[str] = [] if ansible_collections_path() in os.environ: collection_list = os.environ[ansible_collections_path()].split(":") for path_list, path in ( (library_paths, "plugins/modules"), (library_paths, f"{options.cache_dir}/modules"), (collection_list, f"{options.cache_dir}/collections"), (roles_path, "roles"), (roles_path, f"{options.cache_dir}/roles"), ): if path not in path_list and os.path.exists(path): path_list.append(path) _update_env("ANSIBLE_LIBRARY", library_paths) _update_env(ansible_collections_path(), collection_list) _update_env("ANSIBLE_ROLES_PATH", roles_path, default=ANSIBLE_DEFAULT_ROLES_PATH) # If we are asking to run without warnings, then also silence certain # Ansible warnings which could slip through, namely the devel branch # warning. if options.verbosity < 0: _update_env("ANSIBLE_DEVEL_WARNING", ["False"]) def _make_module_stub(module_name: str) -> None: # a.b.c is treated a collection if re.match(r"^(\w+|\w+\.\w+\.[\.\w]+)$", module_name): parts = module_name.split(".") if len(parts) < 3: path = f"{options.cache_dir}/modules" module_file = f"{options.cache_dir}/modules/{module_name}.py" namespace = None collection = None else: namespace = parts[0] collection = parts[1] path = f"{ options.cache_dir }/collections/ansible_collections/{ namespace }/{ collection }/plugins/modules/{ '/'.join(parts[2:-1]) }" module_file = f"{path}/{parts[-1]}.py" os.makedirs(path, exist_ok=True) _write_module_stub( filename=module_file, name=module_file, namespace=namespace, collection=collection, ) else: _logger.error("Config error: %s is not a valid module name.", module_name) sys.exit(INVALID_CONFIG_RC) def _write_module_stub( filename: str, name: str, namespace: Optional[str] = None, collection: Optional[str] = None, ) -> None: """Write module stub to disk.""" body = ANSIBLE_MOCKED_MODULE.format( name=name, collection=collection, namespace=namespace ) with open(filename, "w") as f: f.write(body) def _update_env(varname: str, value: List[str], default: str = "") -> None: """Update colon based environment variable if needed. by appending.""" if value: orig_value = os.environ.get(varname, default) if orig_value: # Prepend original or default variable content to custom content. value = [*orig_value.split(":"), *value] value_str = ":".join(value) if value_str != os.environ.get(varname, ""): os.environ[varname] = value_str _logger.info("Added %s=%s", varname, value_str) def _perform_mockings() -> None: """Mock modules and roles.""" for role_name in options.mock_roles: if re.match(r"\w+\.\w+\.\w+$", role_name): namespace, collection, role_dir = role_name.split(".") path = f"{options.cache_dir}/collections/ansible_collections/{ namespace }/{ collection }/roles/{ role_dir }/" else: path = f"{options.cache_dir}/roles/{role_name}" os.makedirs(path, exist_ok=True) if options.mock_modules: for module_name in options.mock_modules: _make_module_stub(module_name) # if inside a collection repo, symlink it to simulate its installed state if not os.path.exists("galaxy.yml"): return yaml = yaml_from_file("galaxy.yml") if not yaml: # ignore empty galaxy.yml file return namespace = yaml.get("namespace", None) collection = yaml.get("name", None) if not namespace or not collection: return p = pathlib.Path( f"{options.cache_dir}/collections/ansible_collections/{ namespace }" ) p.mkdir(parents=True, exist_ok=True) link_path = p / collection target = pathlib.Path(options.project_dir).absolute() if not link_path.exists() or os.readlink(link_path) != target: if link_path.exists(): link_path.unlink() link_path.symlink_to(target, target_is_directory=True) def ansible_config_get(key: str, kind: Type[Any] = str) -> Union[str, List[str], None]: """Return configuration item from ansible config.""" env = os.environ.copy() # Avoid possible ANSI garbage env["ANSIBLE_FORCE_COLOR"] = "0" # Avoid our own override as this prevents returning system paths. colpathvar = ansible_collections_path() if colpathvar in env: env.pop(colpathvar) config = subprocess.check_output( ["ansible-config", "dump"], universal_newlines=True, env=env ) if kind == str: result = re.search(rf"^{key}.* = (.*)$", config, re.MULTILINE) if result: return result.groups()[0] elif kind == list: result = re.search(rf"^{key}.* = (\[.*\])$", config, re.MULTILINE) if result: val = eval(result.groups()[0]) # pylint: disable=eval-used if not isinstance(val, list): raise RuntimeError(f"Unexpected data read for {key}: {val}") return val else: raise RuntimeError("Unknown data type.") return None def require_collection( # noqa: C901 name: str, version: Optional[str] = None, install: bool = True ) -> None: """Check if a minimal collection version is present or exits. In the future this method may attempt to install a missing or outdated collection before failing. """ try: ns, coll = name.split(".", 1) except ValueError: sys.exit("Invalid collection name supplied: %s" % name) paths = ansible_config_get("COLLECTIONS_PATHS", list) if not paths or not isinstance(paths, list): sys.exit(f"Unable to determine ansible collection paths. ({paths})") if options.cache_dir: # if we have a cache dir, we want to be use that would be preferred # destination when installing a missing collection paths.insert(0, f"{options.cache_dir}/collections") for path in paths: collpath = os.path.join(path, "ansible_collections", ns, coll) if os.path.exists(collpath): mpath = os.path.join(collpath, "MANIFEST.json") if not os.path.exists(mpath): _logger.fatal( "Found collection at '%s' but missing MANIFEST.json, cannot get info.", collpath, ) sys.exit(INVALID_PREREQUISITES_RC) with open(mpath, "r") as f: manifest = json.loads(f.read()) found_version = packaging.version.parse( manifest["collection_info"]["version"] ) if version and found_version < packaging.version.parse(version): if install: install_collection(f"{name}:>={version}") require_collection(name, version, install=False) else: _logger.fatal( "Found %s collection %s but %s or newer is required.", name, found_version, version, ) sys.exit(INVALID_PREREQUISITES_RC) break else: if install: install_collection(f"{name}:>={version}") require_collection(name, version, install=False) else: _logger.fatal("Collection '%s' not found in '%s'", name, paths) sys.exit(INVALID_PREREQUISITES_RC) ansible-lint-5.4.0/src/ansiblelint/py.typed000066400000000000000000000000001420171260700206610ustar00rootroot00000000000000ansible-lint-5.4.0/src/ansiblelint/rules/000077500000000000000000000000001420171260700203265ustar00rootroot00000000000000ansible-lint-5.4.0/src/ansiblelint/rules/AnsibleSyntaxCheckRule.py000066400000000000000000000140031420171260700252500ustar00rootroot00000000000000"""Rule definition for ansible syntax check.""" import json import re import subprocess import sys from typing import Any, List from ansiblelint._internal.rules import BaseRule, RuntimeErrorRule from ansiblelint.config import options from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable from ansiblelint.logger import timed_info from ansiblelint.rules import AnsibleLintRule from ansiblelint.text import strip_ansi_escape DESCRIPTION = """\ Running ``ansible-playbook --syntax-check ...`` failed. This error **cannot be disabled** due to being a prerequisite for other steps. You can either exclude these files from linting or better assure they can be loaded by Ansible. This is often achieved by editing inventory file and/or ``ansible.cfg`` so ansible can load required variables. If undefined variables are the failure reason you could use jinja default() filter in order to provide fallback values. """ _ansible_syntax_check_re = re.compile( r"^ERROR! (?P[^\n]*)\n\nThe error appears to be in " r"'(?P<filename>.*)': line (?P<line>\d+), column (?P<column>\d+)", re.MULTILINE | re.S | re.DOTALL, ) _empty_playbook_re = re.compile( r"^ERROR! Empty playbook, nothing to do", re.MULTILINE | re.S | re.DOTALL ) class AnsibleSyntaxCheckRule(AnsibleLintRule): """Ansible syntax check report failure.""" id = "syntax-check" shortdesc = "Ansible syntax check failed" description = DESCRIPTION severity = "VERY_HIGH" tags = ["core", "unskippable"] version_added = "v5.0.0" @staticmethod def _get_ansible_syntax_check_matches(lintable: Lintable) -> List[MatchError]: """Run ansible syntax check and return a list of MatchError(s).""" if lintable.kind != "playbook": return [] with timed_info("Executing syntax check on %s", lintable.path): extra_vars_cmd = [] if options.extra_vars: extra_vars_cmd = ["--extra-vars", json.dumps(options.extra_vars)] cmd = [ "ansible-playbook", "--syntax-check", *extra_vars_cmd, str(lintable.path), ] run = subprocess.run( cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False, # needed when command is a list universal_newlines=True, check=False, ) result = [] if run.returncode != 0: message = None filename = str(lintable.path) linenumber = 1 column = None tag = None stderr = strip_ansi_escape(run.stderr) stdout = strip_ansi_escape(run.stdout) if stderr: details = stderr if stdout: details += "\n" + stdout else: details = stdout m = _ansible_syntax_check_re.search(stderr) if m: message = m.groupdict()["title"] # Ansible returns absolute paths filename = m.groupdict()["filename"] linenumber = int(m.groupdict()["line"]) column = int(m.groupdict()["column"]) elif _empty_playbook_re.search(stderr): message = "Empty playbook, nothing to do" filename = str(lintable.path) tag = "empty-playbook" if run.returncode == 4: rule: BaseRule = AnsibleSyntaxCheckRule() else: rule = RuntimeErrorRule() if not message: message = ( f"Unexpected error code {run.returncode} from " f"execution of: {' '.join(cmd)}" ) result.append( MatchError( message=message, filename=filename, linenumber=linenumber, column=column, rule=rule, details=details, tag=tag, ) ) return result # testing code to be loaded only with pytest or when executed the rule file if "pytest" in sys.modules: def test_get_ansible_syntax_check_matches() -> None: """Validate parsing of ansible output.""" lintable = Lintable( "examples/playbooks/conflicting_action.yml", kind="playbook" ) result = AnsibleSyntaxCheckRule._get_ansible_syntax_check_matches(lintable) assert result[0].linenumber == 3 assert result[0].column == 7 assert result[0].message == "conflicting action statements: debug, command" # We internally convert absolute paths returned by ansible into paths # relative to current directory. assert result[0].filename.endswith("/conflicting_action.yml") assert len(result) == 1 def test_empty_playbook() -> None: """Validate detection of empty-playbook.""" lintable = Lintable("examples/playbooks/empty_playbook.yml", kind="playbook") result = AnsibleSyntaxCheckRule._get_ansible_syntax_check_matches(lintable) assert result[0].linenumber == 1 # We internally convert absolute paths returned by ansible into paths # relative to current directory. assert result[0].filename.endswith("/empty_playbook.yml") assert result[0].tag == "empty-playbook" assert result[0].message == "Empty playbook, nothing to do" assert len(result) == 1 def test_extra_vars_passed_to_command(config_options: Any) -> None: """Validate `extra-vars` are passed to syntax check command.""" config_options.extra_vars = { "foo": "bar", "complex_variable": ":{;\t$()", } lintable = Lintable("examples/playbooks/extra_vars.yml", kind="playbook") result = AnsibleSyntaxCheckRule._get_ansible_syntax_check_matches(lintable) assert not result �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/BecomeUserWithoutBecomeRule.py�����������������������������0000664�0000000�0000000�00000007775�14201712607�0026300�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from functools import reduce from typing import TYPE_CHECKING, Any, List from ansiblelint.rules import AnsibleLintRule from ansiblelint.utils import LINE_NUMBER_KEY if TYPE_CHECKING: from ansiblelint.constants import odict from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable def _get_subtasks(data: "odict[str, Any]") -> List[Any]: result: List[Any] = [] block_names = [ "tasks", "pre_tasks", "post_tasks", "handlers", "block", "always", "rescue", ] for name in block_names: if data and name in data: result += data[name] or [] return result def _nested_search(term: str, data: "odict[str, Any]") -> Any: if data and term in data: return True return reduce( (lambda x, y: x or _nested_search(term, y)), _get_subtasks(data), False ) def _become_user_without_become(becomeuserabove: bool, data: "odict[str, Any]") -> Any: if "become" in data: # If become is in lineage of tree then correct return False if "become_user" in data and _nested_search("become", data): # If 'become_user' on tree and become somewhere below # we must check for a case of a second 'become_user' without a # 'become' in its lineage subtasks = _get_subtasks(data) return reduce( (lambda x, y: x or _become_user_without_become(False, y)), subtasks, False ) if _nested_search("become_user", data): # Keep searching down if 'become_user' exists in the tree below current task subtasks = _get_subtasks(data) return len(subtasks) == 0 or reduce( ( lambda x, y: x or _become_user_without_become( becomeuserabove or "become_user" in data, y ) ), subtasks, False, ) # If at bottom of tree, flag up if 'become_user' existed in the lineage of the tree and # 'become' was not. This is an error if any lineage has a 'become_user' but no become return becomeuserabove class BecomeUserWithoutBecomeRule(AnsibleLintRule): id = "partial-become" shortdesc = "become_user requires become to work as expected" description = "``become_user`` without ``become`` will not actually change user" severity = "VERY_HIGH" tags = ["unpredictability"] version_added = "historic" def matchplay( self, file: "Lintable", data: "odict[str, Any]" ) -> List["MatchError"]: if file.kind == "playbook": result = _become_user_without_become(False, data) if result: return [ self.create_matcherror( message=self.shortdesc, filename=str(file.path), linenumber=data[LINE_NUMBER_KEY], ) ] return [] ���ansible-lint-5.4.0/src/ansiblelint/rules/CommandHasChangesCheckRule.py������������������������������0000664�0000000�0000000�00000015366�14201712607�0026004�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import sys from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class CommandHasChangesCheckRule(AnsibleLintRule): id = "no-changed-when" shortdesc = "Commands should not change things if nothing needs doing" description = """ Tasks should tell Ansible when to return ``changed``, unless the task only reads information. To do this, set ``changed_when``, use the ``creates`` or ``removes`` argument, or use ``when`` to run the task only if another check has a particular result. For example, this task registers the ``shell`` output and uses the return code to define when the task has changed. .. code:: yaml - name: handle shell output with return code ansible.builtin.shell: cat {{ myfile|quote }} register: myoutput changed_when: myoutput.rc != 0 The following example will trigger the rule since the task does not handle the output of the ``command``. .. code:: yaml - name: does not handle any output or return codes ansible.builtin.command: cat {{ myfile|quote }} """ severity = "HIGH" tags = ["command-shell", "idempotency"] version_added = "historic" _commands = ["command", "shell", "raw"] def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: # tasks in a block are "meta" type if task["__ansible_action_type__"] in ["task", "meta"]: if task["action"]["__ansible_module__"] in self._commands: return ( "changed_when" not in task and "when" not in task and "creates" not in task["action"] and "removes" not in task["action"] ) return False if "pytest" in sys.modules: import pytest NO_CHANGE_COMMAND_RC = """ - hosts: all tasks: - name: handle command output with return code ansible.builtin.command: cat {{ myfile|quote }} register: myoutput changed_when: myoutput.rc != 0 """ NO_CHANGE_SHELL_RC = """ - hosts: all tasks: - name: handle shell output with return code ansible.builtin.shell: cat {{ myfile|quote }} register: myoutput changed_when: myoutput.rc != 0 """ NO_CHANGE_SHELL_FALSE = """ - hosts: all tasks: - name: handle shell output with false changed_when ansible.builtin.shell: cat {{ myfile|quote }} register: myoutput changed_when: false """ NO_CHANGE_ARGS = """ - hosts: all tasks: - name: command with argument command: createfile.sh args: creates: /tmp/????unknown_files???? """ NO_CHANGE_REGISTER_FAIL = """ - hosts: all tasks: - name: register command output, but cat still does not change anything ansible.builtin.command: cat {{ myfile|quote }} register: myoutput """ # also test to ensure it catches tasks in nested blocks. NO_CHANGE_COMMAND_FAIL = """ - hosts: all tasks: - block: - block: - name: basic command task, should fail ansible.builtin.command: cat myfile """ NO_CHANGE_SHELL_FAIL = """ - hosts: all tasks: - name: basic shell task, should fail shell: cat myfile """ @pytest.mark.parametrize( "rule_runner", (CommandHasChangesCheckRule,), indirect=["rule_runner"] ) def test_no_change_command_rc(rule_runner: Any) -> None: """This should pass since *_when is used.""" results = rule_runner.run_playbook(NO_CHANGE_COMMAND_RC) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (CommandHasChangesCheckRule,), indirect=["rule_runner"] ) def test_no_change_shell_rc(rule_runner: Any) -> None: """This should pass since *_when is used.""" results = rule_runner.run_playbook(NO_CHANGE_SHELL_RC) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (CommandHasChangesCheckRule,), indirect=["rule_runner"] ) def test_no_change_shell_false(rule_runner: Any) -> None: """This should pass since *_when is used.""" results = rule_runner.run_playbook(NO_CHANGE_SHELL_FALSE) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (CommandHasChangesCheckRule,), indirect=["rule_runner"] ) def test_no_change_args(rule_runner: Any) -> None: """This test should not pass since the command doesn't do anything.""" results = rule_runner.run_playbook(NO_CHANGE_ARGS) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (CommandHasChangesCheckRule,), indirect=["rule_runner"] ) def test_no_change_register_fail(rule_runner: Any) -> None: """This test should not pass since cat still doesn't do anything.""" results = rule_runner.run_playbook(NO_CHANGE_REGISTER_FAIL) assert len(results) == 1 @pytest.mark.parametrize( "rule_runner", (CommandHasChangesCheckRule,), indirect=["rule_runner"] ) def test_no_change_command_fail(rule_runner: Any) -> None: """This test should fail because command isn't handled.""" # this also ensures that this catches tasks in nested blocks results = rule_runner.run_playbook(NO_CHANGE_COMMAND_FAIL) assert len(results) == 1 @pytest.mark.parametrize( "rule_runner", (CommandHasChangesCheckRule,), indirect=["rule_runner"] ) def test_no_change_shell_fail(rule_runner: Any) -> None: """This test should fail because shell isn't handled..""" results = rule_runner.run_playbook(NO_CHANGE_SHELL_FAIL) assert len(results) == 1 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/CommandsInsteadOfArgumentsRule.py��������������������������0000664�0000000�0000000�00000005246�14201712607�0026763�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import os from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule from ansiblelint.utils import convert_to_boolean, get_first_cmd_arg if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class CommandsInsteadOfArgumentsRule(AnsibleLintRule): id = "deprecated-command-syntax" shortdesc = "Using command rather than an argument to e.g. file" description = ( "Executing a command when there are arguments to modules " "is generally a bad idea" ) severity = "VERY_HIGH" tags = ["command-shell", "deprecations"] version_added = "historic" _commands = ["command", "shell", "raw"] _arguments = { "chown": "owner", "chmod": "mode", "chgrp": "group", "ln": "state=link", "mkdir": "state=directory", "rmdir": "state=absent", "rm": "state=absent", } def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: if task["action"]["__ansible_module__"] in self._commands: first_cmd_arg = get_first_cmd_arg(task) if not first_cmd_arg: return False executable = os.path.basename(first_cmd_arg) if executable in self._arguments and convert_to_boolean( task["action"].get("warn", True) ): message = "{0} used in place of argument {1} to file module" return message.format(executable, self._arguments[executable]) return False ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/CommandsInsteadOfModulesRule.py����������������������������0000664�0000000�0000000�00000016456�14201712607�0026433�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import os import sys from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule from ansiblelint.utils import convert_to_boolean, get_first_cmd_arg, get_second_cmd_arg if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class CommandsInsteadOfModulesRule(AnsibleLintRule): id = "command-instead-of-module" shortdesc = "Using command rather than module" description = ( "Executing a command when there is an Ansible module is generally a bad idea" ) severity = "HIGH" tags = ["command-shell", "idiom"] version_added = "historic" _commands = ["command", "shell"] _modules = { "apt-get": "apt-get", "chkconfig": "service", "curl": "get_url or uri", "git": "git", "hg": "hg", "letsencrypt": "acme_certificate", "mktemp": "tempfile", "mount": "mount", "patch": "patch", "rpm": "yum or rpm_key", "rsync": "synchronize", "sed": "template, replace or lineinfile", "service": "service", "supervisorctl": "supervisorctl", "svn": "subversion", "systemctl": "systemd", "tar": "unarchive", "unzip": "unarchive", "wget": "get_url or uri", "yum": "yum", } _executable_options = { "git": ["branch", "log", "lfs"], "systemctl": ["set-default", "show-environment", "status"], "yum": ["clean"], "rpm": ["--nodeps"], } def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: if task["action"]["__ansible_module__"] not in self._commands: return False first_cmd_arg = get_first_cmd_arg(task) second_cmd_arg = get_second_cmd_arg(task) if not first_cmd_arg: return False executable = os.path.basename(first_cmd_arg) if ( second_cmd_arg and executable in self._executable_options and second_cmd_arg in self._executable_options[executable] ): return False if executable in self._modules and convert_to_boolean( task["action"].get("warn", True) ): message = "{0} used in place of {1} module" return message.format(executable, self._modules[executable]) return False if "pytest" in sys.modules: # noqa: C901 import pytest from ansiblelint.testing import RunFromText # pylint: disable=ungrouped-imports APT_GET = """ - hosts: all tasks: - name: run apt-get update command: apt-get update """ GIT_COMMANDS_OK = """ - hosts: all tasks: - name: print current git branch command: git branch - name: print git log command: git log - name: install git lfs support command: git lfs install """ RESTART_SSHD = """ - hosts: all tasks: - name: restart sshd command: systemctl restart sshd """ SYSTEMCTL_STATUS = """ - hosts: all tasks: - name: show systemctl service status command: systemctl status systemd-timesyncd """ SYSTEMD_ENVIRONMENT = """ - hosts: all tasks: - name: show systemd environment command: systemctl show-environment """ SYSTEMD_RUNLEVEL = """ - hosts: all tasks: - name: set systemd runlevel command: systemctl set-default multi-user.target """ YUM_UPDATE = """ - hosts: all tasks: - name: run yum update command: yum update """ YUM_CLEAN = """ - hosts: all tasks: - name: clear yum cache command: yum clean all """ @pytest.mark.parametrize( "rule_runner", (CommandsInsteadOfModulesRule,), indirect=["rule_runner"] ) def test_apt_get(rule_runner: RunFromText) -> None: """The apt module supports update.""" results = rule_runner.run_playbook(APT_GET) assert len(results) == 1 @pytest.mark.parametrize( "rule_runner", (CommandsInsteadOfModulesRule,), indirect=["rule_runner"] ) def test_restart_sshd(rule_runner: RunFromText) -> None: """Restarting services is supported by the systemd module.""" results = rule_runner.run_playbook(RESTART_SSHD) assert len(results) == 1 @pytest.mark.parametrize( "rule_runner", (CommandsInsteadOfModulesRule,), indirect=["rule_runner"] ) def test_git_commands_ok(rule_runner: RunFromText) -> None: """Check the git commands not supported by the git module do not trigger rule.""" results = rule_runner.run_playbook(GIT_COMMANDS_OK) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (CommandsInsteadOfModulesRule,), indirect=["rule_runner"] ) def test_systemd_status(rule_runner: RunFromText) -> None: """Set-default is not supported by the systemd module.""" results = rule_runner.run_playbook(SYSTEMCTL_STATUS) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (CommandsInsteadOfModulesRule,), indirect=["rule_runner"] ) def test_systemd_environment(rule_runner: RunFromText) -> None: """Showing the environment is not supported by the systemd module.""" results = rule_runner.run_playbook(SYSTEMD_ENVIRONMENT) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (CommandsInsteadOfModulesRule,), indirect=["rule_runner"] ) def test_systemd_runlevel(rule_runner: RunFromText) -> None: """Set-default is not supported by the systemd module.""" results = rule_runner.run_playbook(SYSTEMD_RUNLEVEL) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (CommandsInsteadOfModulesRule,), indirect=["rule_runner"] ) def test_yum_update(rule_runner: RunFromText) -> None: """Using yum update should fail.""" results = rule_runner.run_playbook(YUM_UPDATE) assert len(results) == 1 @pytest.mark.parametrize( "rule_runner", (CommandsInsteadOfModulesRule,), indirect=["rule_runner"] ) def test_yum_clean(rule_runner: RunFromText) -> None: """The yum module does not support clearing yum cache.""" results = rule_runner.run_playbook(YUM_CLEAN) assert len(results) == 0 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/ComparisonToEmptyStringRule.py�����������������������������0000664�0000000�0000000�00000005415�14201712607�0026360�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016, Will Thames and contributors # Copyright (c) 2018, Ansible Project import re import sys from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule from ansiblelint.yaml_utils import nested_items_path if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class ComparisonToEmptyStringRule(AnsibleLintRule): id = "empty-string-compare" shortdesc = "Don't compare to empty string" description = ( 'Use ``when: var|length > 0`` rather than ``when: var != ""`` (or ' 'conversely ``when: var|length == 0`` rather than ``when: var == ""``)' ) severity = "HIGH" tags = ["idiom"] version_added = "v4.0.0" empty_string_compare = re.compile("[=!]= ?(\"{2}|'{2})") def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: for k, v, _ in nested_items_path(task): if k == "when": if isinstance(v, str): if self.empty_string_compare.search(v): return True elif isinstance(v, bool): pass else: for item in v: if isinstance(item, str) and self.empty_string_compare.search( item ): return True return False # testing code to be loaded only with pytest or when executed the rule file if "pytest" in sys.modules: import pytest from ansiblelint.testing import RunFromText # pylint: disable=ungrouped-imports SUCCESS_PLAY = """ - hosts: all tasks: - name: shut down shell: | /sbin/shutdown -t now echo $var == "" when: ansible_os_family """ FAIL_PLAY = """ - hosts: all tasks: - name: shut down command: /sbin/shutdown -t now when: ansible_os_family == "" - name: shut down command: /sbin/shutdown -t now when: ansible_os_family !="" """ @pytest.mark.parametrize( "rule_runner", (ComparisonToEmptyStringRule,), indirect=["rule_runner"] ) def test_rule_empty_string_compare_fail(rule_runner: RunFromText) -> None: """Test rule matches.""" results = rule_runner.run_playbook(FAIL_PLAY) assert len(results) == 2 for result in results: assert result.message == ComparisonToEmptyStringRule.shortdesc @pytest.mark.parametrize( "rule_runner", (ComparisonToEmptyStringRule,), indirect=["rule_runner"] ) def test_rule_empty_string_compare_pass(rule_runner: RunFromText) -> None: """Test rule matches.""" results = rule_runner.run_playbook(SUCCESS_PLAY) assert len(results) == 0, results ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/ComparisonToLiteralBoolRule.py�����������������������������0000664�0000000�0000000�00000002626�14201712607�0026304�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016, Will Thames and contributors # Copyright (c) 2018-2021, Ansible Project import re from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule from ansiblelint.yaml_utils import nested_items_path if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class ComparisonToLiteralBoolRule(AnsibleLintRule): id = "literal-compare" shortdesc = "Don't compare to literal True/False" description = ( "Use ``when: var`` rather than ``when: var == True`` " "(or conversely ``when: not var``)" ) severity = "HIGH" tags = ["idiom"] version_added = "v4.0.0" literal_bool_compare = re.compile("[=!]= ?(True|true|False|false)") def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: for k, v, _ in nested_items_path(task): if k == "when": if isinstance(v, str): if self.literal_bool_compare.search(v): return True elif isinstance(v, bool): pass else: for item in v: if isinstance(item, str) and self.literal_bool_compare.search( item ): return True return False ����������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/DeprecatedModuleRule.py������������������������������������0000664�0000000�0000000�00000003710�14201712607�0024737�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2018, Ansible Project from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class DeprecatedModuleRule(AnsibleLintRule): id = "deprecated-module" shortdesc = "Deprecated module" description = ( "These are deprecated modules, some modules are kept " "temporarily for backwards compatibility but usage is discouraged. " "For more details see: " "https://docs.ansible.com/ansible/latest/collections/index_module.html" ) severity = "HIGH" tags = ["deprecations"] version_added = "v4.0.0" _modules = [ "accelerate", "aos_asn_pool", "aos_blueprint", "aos_blueprint_param", "aos_blueprint_virtnet", "aos_device", "aos_external_router", "aos_ip_pool", "aos_logical_device", "aos_logical_device_map", "aos_login", "aos_rack_type", "aos_template", "azure", "cl_bond", "cl_bridge", "cl_img_install", "cl_interface", "cl_interface_policy", "cl_license", "cl_ports", "cs_nic", "docker", "ec2_ami_find", "ec2_ami_search", "ec2_remote_facts", "ec2_vpc", "kubernetes", "netscaler", "nxos_ip_interface", "nxos_mtu", "nxos_portchannel", "nxos_switchport", "oc", "panos_nat_policy", "panos_security_policy", "vsphere_guest", "win_msi", "include", ] def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: module = task["action"]["__ansible_module__"] if module in self._modules: message = "{0} {1}" return message.format(self.shortdesc, module) return False ��������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/EnvVarsInCommandRule.py������������������������������������0000664�0000000�0000000�00000005066�14201712607�0024711�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule from ansiblelint.utils import FILENAME_KEY, LINE_NUMBER_KEY, get_first_cmd_arg if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class EnvVarsInCommandRule(AnsibleLintRule): id = "inline-env-var" shortdesc = "Command module does not accept setting environment variables inline" description = ( "Use ``environment:`` to set environment variables " "or use ``shell`` module which accepts both" ) severity = "VERY_HIGH" tags = ["command-shell", "idiom"] version_added = "historic" expected_args = [ "chdir", "creates", "executable", "removes", "stdin", "warn", "stdin_add_newline", "strip_empty_ends", "cmd", "__ansible_module__", "__ansible_module_original__", "__ansible_arguments__", LINE_NUMBER_KEY, FILENAME_KEY, ] def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: if task["action"]["__ansible_module__"] in ["command"]: first_cmd_arg = get_first_cmd_arg(task) if not first_cmd_arg: return False return any( [arg not in self.expected_args for arg in task["action"]] + ["=" in first_cmd_arg] ) return False ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/FQCNBuiltinsRule.py����������������������������������������0000664�0000000�0000000�00000005732�14201712607�0024000�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Rule definition for usage of fully qualified collection names for builtins.""" import sys from typing import Any, Dict, Optional, Union from ansiblelint.file_utils import Lintable from ansiblelint.rules import AnsibleLintRule builtins = [ "add_host", "apt", "apt_key", "apt_repository", "assemble", "assert", "async_status", "blockinfile", "command", "copy", "cron", "debconf", "debug", "dnf", "dpkg_selections", "expect", "fail", "fetch", "file", "find", "gather_facts", "get_url", "getent", "git", "group", "group_by", "hostname", "import_playbook", "import_role", "import_tasks", "include", "include_role", "include_tasks", "include_vars", "iptables", "known_hosts", "lineinfile", "meta", "package", "package_facts", "pause", "ping", "pip", "raw", "reboot", "replace", "rpm_key", "script", "service", "service_facts", "set_fact", "set_stats", "setup", "shell", "slurp", "stat", "subversion", "systemd", "sysvinit", "tempfile", "template", "unarchive", "uri", "user", "wait_for", "wait_for_connection", "yum", "yum_repository", ] class FQCNBuiltinsRule(AnsibleLintRule): id = "fqcn-builtins" shortdesc = "Use FQCN for builtin actions" description = ( "Check whether the long version starting with ``ansible.builtin`` " "is used in the playbook" ) tags = ["opt-in", "formatting", "experimental"] def matchtask( self, task: Dict[str, Any], file: Optional[Lintable] = None ) -> Union[bool, str]: return task["action"]["__ansible_module_original__"] in builtins # testing code to be loaded only with pytest or when executed the rule file if "pytest" in sys.modules: import pytest from ansiblelint.testing import RunFromText # pylint: disable=ungrouped-imports SUCCESS_PLAY = """ - hosts: localhost tasks: - name: shell (fqcn) ansible.builtin.shell: echo This rule should not get matched by the fqcn-builtins rule """ FAIL_PLAY = """ - hosts: localhost tasks: - name: shell (fqcn) shell: echo This rule should get matched by the fqcn-builtins rule """ @pytest.mark.parametrize( "rule_runner", (FQCNBuiltinsRule,), indirect=["rule_runner"] ) def test_fqcn_builtin_fail(rule_runner: RunFromText) -> None: """Test rule matches.""" results = rule_runner.run_playbook(FAIL_PLAY) assert len(results) == 1 for result in results: assert result.message == FQCNBuiltinsRule.shortdesc @pytest.mark.parametrize( "rule_runner", (FQCNBuiltinsRule,), indirect=["rule_runner"] ) def test_fqcn_builtin_pass(rule_runner: RunFromText) -> None: """Test rule does not match.""" results = rule_runner.run_playbook(SUCCESS_PLAY) assert len(results) == 0, results ��������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/GitHasVersionRule.py���������������������������������������0000664�0000000�0000000�00000003614�14201712607�0024261�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class GitHasVersionRule(AnsibleLintRule): id = "git-latest" shortdesc = "Git checkouts must contain explicit version" description = ( "All version control checkouts must point to " "an explicit commit or tag, not just ``latest``" ) severity = "MEDIUM" tags = ["idempotency"] version_added = "historic" def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: return bool( task["action"]["__ansible_module__"] == "git" and task["action"].get("version", "HEAD") == "HEAD" ) ��������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/IgnoreErrorsRule.py����������������������������������������0000664�0000000�0000000�00000007714�14201712607�0024161�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""IgnoreErrorsRule used with ansible-lint.""" import sys from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class IgnoreErrorsRule(AnsibleLintRule): """Describe and test the IgnoreErrorsRule.""" id = "ignore-errors" shortdesc = ( "Use failed_when and specify error conditions instead of using ignore_errors" ) description = ( "Instead of ignoring all errors, ignore the errors only when using ``{{ ansible_check_mode }}``, " "register the errors using ``register``, " "or use ``failed_when:`` and specify acceptable error conditions " "to reduce the risk of ignoring important failures." ) severity = "LOW" tags = ["unpredictability", "experimental"] version_added = "v5.0.7" def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: if ( task.get("ignore_errors") and task.get("ignore_errors") != "{{ ansible_check_mode }}" and not task.get("register") ): return True return False if "pytest" in sys.modules: import pytest from ansiblelint.testing import RunFromText # pylint: disable=ungrouped-imports IGNORE_ERRORS_TRUE = """ - hosts: all tasks: - name: run apt-get update command: apt-get update ignore_errors: true """ IGNORE_ERRORS_FALSE = """ - hosts: all tasks: - name: run apt-get update command: apt-get update ignore_errors: false """ IGNORE_ERRORS_CHECK_MODE = """ - hosts: all tasks: - name: run apt-get update command: apt-get update ignore_errors: "{{ ansible_check_mode }}" """ IGNORE_ERRORS_REGISTER = """ - hosts: all tasks: - name: run apt-get update command: apt-get update ignore_errors: true register: ignore_errors_register """ FAILED_WHEN = """ - hosts: all tasks: - name: disable apport become: 'yes' lineinfile: line: "enabled=0" dest: /etc/default/apport mode: 0644 state: present register: default_apport failed_when: default_apport.rc !=0 and not default_apport.rc == 257 """ @pytest.mark.parametrize( "rule_runner", (IgnoreErrorsRule,), indirect=["rule_runner"] ) def test_ignore_errors_true(rule_runner: RunFromText) -> None: """The task uses ignore_errors.""" results = rule_runner.run_playbook(IGNORE_ERRORS_TRUE) assert len(results) == 1 @pytest.mark.parametrize( "rule_runner", (IgnoreErrorsRule,), indirect=["rule_runner"] ) def test_ignore_errors_false(rule_runner: RunFromText) -> None: """The task uses ignore_errors: false, oddly enough.""" results = rule_runner.run_playbook(IGNORE_ERRORS_FALSE) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (IgnoreErrorsRule,), indirect=["rule_runner"] ) def test_ignore_errors_check_mode(rule_runner: RunFromText) -> None: """The task uses ignore_errors: "{{ ansible_check_mode }}".""" results = rule_runner.run_playbook(IGNORE_ERRORS_CHECK_MODE) print(results) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (IgnoreErrorsRule,), indirect=["rule_runner"] ) def test_ignore_errors_register(rule_runner: RunFromText) -> None: """The task uses ignore_errors: but output is registered and managed.""" results = rule_runner.run_playbook(IGNORE_ERRORS_REGISTER) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (IgnoreErrorsRule,), indirect=["rule_runner"] ) def test_failed_when(rule_runner: RunFromText) -> None: """Instead of ignore_errors, this task uses failed_when.""" results = rule_runner.run_playbook(FAILED_WHEN) assert len(results) == 0 ����������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/MercurialHasRevisionRule.py��������������������������������0000664�0000000�0000000�00000003637�14201712607�0025637�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class MercurialHasRevisionRule(AnsibleLintRule): id = "hg-latest" shortdesc = "Mercurial checkouts must contain explicit revision" description = ( "All version control checkouts must point to " "an explicit commit or tag, not just ``latest``" ) severity = "MEDIUM" tags = ["idempotency"] version_added = "historic" def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: return bool( task["action"]["__ansible_module__"] == "hg" and task["action"].get("revision", "default") == "default" ) �������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/MetaChangeFromDefaultRule.py�������������������������������0000664�0000000�0000000�00000003275�14201712607�0025664�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2018, Ansible Project from typing import TYPE_CHECKING, List from ansiblelint.rules import AnsibleLintRule from ansiblelint.utils import LINE_NUMBER_KEY if TYPE_CHECKING: from typing import Any from ansiblelint.constants import odict from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable class MetaChangeFromDefaultRule(AnsibleLintRule): id = "meta-incorrect" shortdesc = "meta/main.yml default values should be changed" field_defaults = [ ("author", "your name"), ("description", "your description"), ("company", "your company (optional)"), ("license", "license (GPLv2, CC-BY, etc)"), ("license", "license (GPL-2.0-or-later, MIT, etc)"), ] description = "meta/main.yml default values should be changed for: ``{}``".format( ", ".join(f[0] for f in field_defaults) ) severity = "HIGH" tags = ["metadata"] version_added = "v4.0.0" def matchplay( self, file: "Lintable", data: "odict[str, Any]" ) -> List["MatchError"]: if file.kind != "meta": return [] galaxy_info = data.get("galaxy_info", None) if not galaxy_info: return [] results = [] for field, default in self.field_defaults: value = galaxy_info.get(field, None) if value and value == default: results.append( self.create_matcherror( filename=file, linenumber=data[LINE_NUMBER_KEY], message="Should change default metadata: %s" % field, ) ) return results �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/MetaMainHasInfoRule.py�������������������������������������0000664�0000000�0000000�00000005074�14201712607�0024501�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016, Will Thames and contributors # Copyright (c) 2018, Ansible Project from typing import TYPE_CHECKING, Generator, List from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Any, Tuple from ansiblelint.constants import odict META_STR_INFO = ("author", "description") META_INFO = tuple( list(META_STR_INFO) + [ "license", "min_ansible_version", "platforms", ] ) def _platform_info_errors_itr( platforms: "List[odict[str, str]]", ) -> Generator[str, None, None]: if not isinstance(platforms, list): yield "Platforms should be a list of dictionaries" return for platform in platforms: if not isinstance(platform, dict): yield "Platforms should be a list of dictionaries" elif "name" not in platform: yield "Platform should contain name" def _galaxy_info_errors_itr( galaxy_info: "odict[str, Any]", info_list: "Tuple[str, ...]" = META_INFO, str_info_list: "Tuple[str, ...]" = META_STR_INFO, ) -> Generator[str, None, None]: for info in info_list: ginfo = galaxy_info.get(info, False) if ginfo: if info in str_info_list and not isinstance(ginfo, str): yield "{info} should be a string".format(info=info) elif info == "platforms": for err in _platform_info_errors_itr(ginfo): yield err else: yield "Role info should contain {info}".format(info=info) class MetaMainHasInfoRule(AnsibleLintRule): id = "meta-no-info" shortdesc = "meta/main.yml should contain relevant info" str_info = META_STR_INFO info = META_INFO description = "meta/main.yml should contain: ``{}``".format(", ".join(info)) severity = "HIGH" tags = ["metadata"] version_added = "v4.0.0" def matchplay(self, file: Lintable, data: "odict[str, Any]") -> List[MatchError]: if file.kind != "meta": return [] # since Ansible 2.10 we can add a meta/requirements.yml but # we only want to match on meta/main.yml if file.path.name != "main.yml": return [] galaxy_info = data.get("galaxy_info", False) if galaxy_info: return [ self.create_matcherror(message=err, filename=file) for err in _galaxy_info_errors_itr(galaxy_info) ] return [self.create_matcherror(message="No 'galaxy_info' found", filename=file)] ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/MetaTagValidRule.py����������������������������������������0000664�0000000�0000000�00000006347�14201712607�0024044�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2018, Ansible Project import re import sys from typing import TYPE_CHECKING, List from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Any from ansiblelint.constants import odict from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable class MetaTagValidRule(AnsibleLintRule): id = "meta-no-tags" shortdesc = "Tags must contain lowercase letters and digits only" description = ( "Tags must contain lowercase letters and digits only, " "and ``galaxy_tags`` is expected to be a list" ) severity = "HIGH" tags = ["metadata"] version_added = "v4.0.0" TAG_REGEXP = re.compile("^[a-z0-9]+$") def matchplay( self, file: "Lintable", data: "odict[str, Any]" ) -> List["MatchError"]: if file.kind != "meta": return [] galaxy_info = data.get("galaxy_info", None) if not galaxy_info: return [] tags = [] results = [] if "galaxy_tags" in galaxy_info: if isinstance(galaxy_info["galaxy_tags"], list): tags += galaxy_info["galaxy_tags"] else: results.append( self.create_matcherror( "Expected 'galaxy_tags' to be a list", filename=file ) ) if "categories" in galaxy_info: results.append( self.create_matcherror( "Use 'galaxy_tags' rather than 'categories'", filename=file ) ) if isinstance(galaxy_info["categories"], list): tags += galaxy_info["categories"] else: results.append( self.create_matcherror( "Expected 'categories' to be a list", filename=file ) ) for tag in tags: msg = self.shortdesc if not isinstance(tag, str): results.append( self.create_matcherror( "Tags must be strings: '{}'".format(tag), filename=file ) ) continue if not re.match(self.TAG_REGEXP, tag): results.append( self.create_matcherror( message="{}, invalid: '{}'".format(msg, tag), filename=file ) ) return results META_TAG_VALID = """ galaxy_info: galaxy_tags: ['database', 'my s q l', 'MYTAG'] categories: 'my_category_not_in_a_list' """ # testing code to be loaded only with pytest or when executed the rule file if "pytest" in sys.modules: import pytest @pytest.mark.parametrize( "rule_runner", (MetaTagValidRule,), indirect=["rule_runner"] ) def test_valid_tag_rule(rule_runner: "Any") -> None: """Test rule matches.""" results = rule_runner.run_role_meta_main(META_TAG_VALID) assert "Use 'galaxy_tags' rather than 'categories'" in str(results) assert "Expected 'categories' to be a list" in str(results) assert "invalid: 'my s q l'" in str(results) assert "invalid: 'MYTAG'" in str(results) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/MetaVideoLinksRule.py��������������������������������������0000664�0000000�0000000�00000005144�14201712607�0024412�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2018, Ansible Project import re from typing import TYPE_CHECKING, List from ansiblelint.rules import AnsibleLintRule from ansiblelint.utils import FILENAME_KEY, LINE_NUMBER_KEY if TYPE_CHECKING: from typing import Any from ansiblelint.constants import odict from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable class MetaVideoLinksRule(AnsibleLintRule): id = "meta-video-links" shortdesc = "meta/main.yml video_links should be formatted correctly" description = ( "Items in ``video_links`` in meta/main.yml should be " "dictionaries, and contain only keys ``url`` and ``title``, " "and have a shared link from a supported provider" ) severity = "LOW" tags = ["metadata"] version_added = "v4.0.0" VIDEO_REGEXP = { "google": re.compile(r"https://drive\.google\.com.*file/d/([0-9A-Za-z-_]+)/.*"), "vimeo": re.compile(r"https://vimeo\.com/([0-9]+)"), "youtube": re.compile(r"https://youtu\.be/([0-9A-Za-z-_]+)"), } def matchplay( self, file: "Lintable", data: "odict[str, Any]" ) -> List["MatchError"]: if file.kind != "meta": return [] galaxy_info = data.get("galaxy_info", None) if not galaxy_info: return [] video_links = galaxy_info.get("video_links", None) if not video_links: return [] results = [] for video in video_links: if not isinstance(video, dict): results.append( self.create_matcherror( "Expected item in 'video_links' to be " "a dictionary", filename=file, ) ) continue if set(video) != {"url", "title", FILENAME_KEY, LINE_NUMBER_KEY}: results.append( self.create_matcherror( "Expected item in 'video_links' to contain " "only keys 'url' and 'title'", filename=file, ) ) continue for _, expr in self.VIDEO_REGEXP.items(): if expr.match(video["url"]): break else: msg = ( "URL format '{0}' is not recognized. " "Expected it be a shared link from Vimeo, YouTube, " "or Google Drive.".format(video["url"]) ) results.append(self.create_matcherror(msg, filename=file)) return results ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/MissingFilePermissionsRule.py������������������������������0000664�0000000�0000000�00000031265�14201712607�0026204�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2020 Sorin Sbarnea <sorin.sbarnea@gmail.com> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """MissingFilePermissionsRule used with ansible-lint.""" import sys from typing import TYPE_CHECKING, Any, Dict, Set, Union from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable # Despite documentation mentioning 'preserve' only these modules support it: _modules_with_preserve = ( "copy", "template", ) _MODULES: Set[str] = { "archive", "community.general.archive", "assemble", "ansible.builtin.assemble", "copy", # supports preserve "ansible.builtin.copy", "file", "ansible.builtin.file", "replace", # implicit preserve behavior but mode: preserve is invalid "ansible.builtin.replace", "template", # supports preserve "ansible.builtin.template", # 'unarchive', # disabled because .tar.gz files can have permissions inside } _MODULES_WITH_CREATE: Dict[str, bool] = { "blockinfile": False, "ansible.builtin.blockinfile": False, "htpasswd": True, "community.general.htpasswd": True, "ini_file": True, "community.general.ini_file": True, "lineinfile": False, "ansible.builtin.lineinfile": False, } class MissingFilePermissionsRule(AnsibleLintRule): id = "risky-file-permissions" shortdesc = "File permissions unset or incorrect" description = ( "Missing or unsupported mode parameter can cause unexpected file " "permissions based " "on version of Ansible being used. Be explicit, like ``mode: 0644`` to " "avoid hitting this rule. Special ``preserve`` value is accepted " f"only by {', '.join(_modules_with_preserve)} modules. " "See https://github.com/ansible/ansible/issues/71200" ) severity = "VERY_HIGH" tags = ["unpredictability", "experimental"] version_added = "v4.3.0" _modules = _MODULES _modules_with_create = _MODULES_WITH_CREATE def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: module = task["action"]["__ansible_module__"] mode = task["action"].get("mode", None) if module not in self._modules and module not in self._modules_with_create: return False if mode == "preserve" and module not in _modules_with_preserve: return True if module in self._modules_with_create: create = task["action"].get("create", self._modules_with_create[module]) return create and mode is None # A file that doesn't exist cannot have a mode if task["action"].get("state", None) == "absent": return False # A symlink always has mode 0777 if task["action"].get("state", None) == "link": return False # Recurse on a directory does not allow for an uniform mode if task["action"].get("recurse", None): return False # The file module does not create anything when state==file (default) if module == "file" and task["action"].get("state", "file") == "file": return False # replace module is the only one that has a valid default preserve # behavior, but we want to trigger rule if user used incorrect # documentation and put 'preserve', which is not supported. if module == "replace" and mode is None: return False return mode is None if "pytest" in sys.modules: # noqa: C901 import pytest from ansiblelint.testing import RunFromText # pylint: disable=ungrouped-imports SUCCESS_PERMISSIONS_PRESENT = """ - hosts: all tasks: - name: permissions not missing and numeric file: path: foo mode: 0600 """ SUCCESS_ABSENT_STATE = """ - hosts: all tasks: - name: permissions missing while state is absent is fine file: path: foo state: absent """ SUCCESS_DEFAULT_STATE = """ - hosts: all tasks: - name: permissions missing while state is file (default) is fine file: path: foo """ SUCCESS_LINK_STATE = """ - hosts: all tasks: - name: permissions missing while state is link is fine file: path: foo2 src: foo state: link """ SUCCESS_CREATE_FALSE = """ - hosts: all tasks: - name: file edit when create is false lineinfile: path: foo create: false line: some content here """ SUCCESS_REPLACE = """ - hosts: all tasks: - name: replace should not require mode replace: path: foo """ SUCCESS_RECURSE = """ - hosts: all tasks: - name: file with recursive does not require mode file: state: directory recurse: yes - name: permissions not missing and numeric (fqcn) ansible.builtin.file: path: bar mode: 755 - name: file edit when create is false (fqcn) ansible.builtin.lineinfile: path: foo create: false line: some content here """ FAIL_PRESERVE_MODE = """ - hosts: all tasks: - name: file does not allow preserve value for mode file: path: foo mode: preserve """ FAIL_MISSING_PERMISSIONS_TOUCH = """ - hosts: all tasks: - name: permissions missing and might create file file: path: foo state: touch - name: permissions missing and might create file (fqcn) ansible.builtin.file: path: foo state: touch """ FAIL_MISSING_PERMISSIONS_DIRECTORY = """ - hosts: all tasks: - name: permissions missing and might create directory file: path: foo state: directory - name: lineinfile when create is true (fqcn) ansible.builtin.lineinfile: path: foo create: true line: some content here """ FAIL_LINEINFILE_CREATE = """ - hosts: all tasks: - name: lineinfile when create is true lineinfile: path: foo create: true line: some content here """ FAIL_REPLACE_PRESERVE = """ - hosts: all tasks: - name: replace does not allow preserve mode replace: path: foo mode: preserve """ FAIL_PERMISSION_COMMENT = """ - hosts: all tasks: - name: permissions is only a comment file: path: foo owner: root group: root state: directory # mode: 0755 """ FAIL_INI_PERMISSION = """ - hosts: all tasks: - name: permissions needed if create is used ini_file: path: foo create: true """ FAIL_INI_PRESERVE = """ - hosts: all tasks: - name: ini_file does not accept preserve mode ini_file: path: foo create: true mode: preserve """ @pytest.mark.parametrize( "rule_runner", (MissingFilePermissionsRule,), indirect=["rule_runner"] ) def test_success_permissions_present(rule_runner: RunFromText) -> None: """Permissions present and numeric.""" results = rule_runner.run_playbook(SUCCESS_PERMISSIONS_PRESENT) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (MissingFilePermissionsRule,), indirect=["rule_runner"] ) def test_success_absent_state(rule_runner: RunFromText) -> None: """No permissions required if file is absent.""" results = rule_runner.run_playbook(SUCCESS_ABSENT_STATE) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (MissingFilePermissionsRule,), indirect=["rule_runner"] ) def test_success_default_state(rule_runner: RunFromText) -> None: """No permissions required if default state.""" results = rule_runner.run_playbook(SUCCESS_DEFAULT_STATE) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (MissingFilePermissionsRule,), indirect=["rule_runner"] ) def test_success_link_state(rule_runner: RunFromText) -> None: """No permissions required if it is a link.""" results = rule_runner.run_playbook(SUCCESS_LINK_STATE) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (MissingFilePermissionsRule,), indirect=["rule_runner"] ) def test_success_create_false(rule_runner: RunFromText) -> None: """No permissions required if file is not created.""" results = rule_runner.run_playbook(SUCCESS_CREATE_FALSE) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (MissingFilePermissionsRule,), indirect=["rule_runner"] ) def test_success_replace(rule_runner: RunFromText) -> None: """Replacing a file do not require mode.""" results = rule_runner.run_playbook(SUCCESS_REPLACE) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (MissingFilePermissionsRule,), indirect=["rule_runner"] ) def test_success_recurse(rule_runner: RunFromText) -> None: """Do not require mode when recursing.""" results = rule_runner.run_playbook(SUCCESS_RECURSE) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (MissingFilePermissionsRule,), indirect=["rule_runner"] ) def test_fail_preserve_mode(rule_runner: RunFromText) -> None: """File does not allow preserve value for mode.""" results = rule_runner.run_playbook(FAIL_PRESERVE_MODE) assert len(results) == 1 @pytest.mark.parametrize( "rule_runner", (MissingFilePermissionsRule,), indirect=["rule_runner"] ) def test_fail_missing_permissions_touch(rule_runner: RunFromText) -> None: """Missing permissions when possibly creating file.""" results = rule_runner.run_playbook(FAIL_MISSING_PERMISSIONS_TOUCH) assert len(results) == 2 @pytest.mark.parametrize( "rule_runner", (MissingFilePermissionsRule,), indirect=["rule_runner"] ) def test_fail_missing_permissions_directory(rule_runner: RunFromText) -> None: """Missing permissions when possibly creating a directory.""" results = rule_runner.run_playbook(FAIL_MISSING_PERMISSIONS_DIRECTORY) assert len(results) == 2 @pytest.mark.parametrize( "rule_runner", (MissingFilePermissionsRule,), indirect=["rule_runner"] ) def test_fail_lineinfile_create(rule_runner: RunFromText) -> None: """Lineinfile might create a file.""" results = rule_runner.run_playbook(FAIL_LINEINFILE_CREATE) assert len(results) == 1 @pytest.mark.parametrize( "rule_runner", (MissingFilePermissionsRule,), indirect=["rule_runner"] ) def test_fail_replace_preserve(rule_runner: RunFromText) -> None: """Replace does not allow preserve mode.""" results = rule_runner.run_playbook(FAIL_REPLACE_PRESERVE) assert len(results) == 1 @pytest.mark.parametrize( "rule_runner", (MissingFilePermissionsRule,), indirect=["rule_runner"] ) def test_fail_permission_comment(rule_runner: RunFromText) -> None: """Permissions is only a comment.""" results = rule_runner.run_playbook(FAIL_PERMISSION_COMMENT) assert len(results) == 1 @pytest.mark.parametrize( "rule_runner", (MissingFilePermissionsRule,), indirect=["rule_runner"] ) def test_fail_ini_permission(rule_runner: RunFromText) -> None: """Permissions needed if create is used.""" results = rule_runner.run_playbook(FAIL_INI_PERMISSION) assert len(results) == 1 @pytest.mark.parametrize( "rule_runner", (MissingFilePermissionsRule,), indirect=["rule_runner"] ) def test_fail_ini_preserve(rule_runner: RunFromText) -> None: """The ini_file module does not accept preserve mode.""" results = rule_runner.run_playbook(FAIL_INI_PRESERVE) assert len(results) == 1 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/NestedJinjaRule.py�����������������������������������������0000664�0000000�0000000�00000004531�14201712607�0023731�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Author: Adrián Tóth <adtoth@redhat.com> # # Copyright (c) 2020, Red Hat, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import re from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class NestedJinjaRule(AnsibleLintRule): id = "no-jinja-nesting" shortdesc = "Nested jinja pattern" description = ( "There should not be any nested jinja pattern. " "Example (bad): ``{{ list_one + {{ list_two | max }} }}``, " "Example (good): ``{{ list_one + max(list_two) }}``, " "Example (allowed): ``--format='{{'{{'}}.Size{{'}}'}}'``" ) severity = "VERY_HIGH" tags = ["formatting"] version_added = "v4.3.0" pattern = re.compile(r"{{(?:[^{}]*)?[^'\"]{{") def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: command = "".join( str(value) # task properties are stored in the 'action' key for key, value in task["action"].items() # exclude useless values of '__file__', '__ansible_module__', '__*__', etc. if not key.startswith("__") and not key.endswith("__") ) return bool(self.pattern.search(command)) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/NoFormattingInWhenRule.py����������������������������������0000664�0000000�0000000�00000003537�14201712607�0025260�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from typing import TYPE_CHECKING, Any, Dict, List, Union from ansiblelint.rules import AnsibleLintRule from ansiblelint.utils import LINE_NUMBER_KEY if TYPE_CHECKING: from typing import Optional from ansiblelint.constants import odict from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable class NoFormattingInWhenRule(AnsibleLintRule): id = "no-jinja-when" shortdesc = "No Jinja2 in when" description = ( "``when`` is a raw Jinja2 expression, remove redundant {{ }} from variable(s)." ) severity = "HIGH" tags = ["deprecations"] version_added = "historic" def _is_valid(self, when: str) -> bool: if not isinstance(when, str): return True return when.find("{{") == -1 and when.find("}}") == -1 def matchplay( self, file: "Lintable", data: "odict[str, Any]" ) -> List["MatchError"]: errors: List["MatchError"] = [] if isinstance(data, dict): if "roles" not in data or data["roles"] is None: return errors for role in data["roles"]: if self.matchtask(role, file=file): errors.append( self.create_matcherror( details=str({"when": role}), filename=file, linenumber=role[LINE_NUMBER_KEY], ) ) if isinstance(data, list): for play_item in data: sub_errors = self.matchplay(file, play_item) if sub_errors: errors = errors + sub_errors return errors def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: return "when" in task and not self._is_valid(task["when"]) �����������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/NoLogPasswordsRule.py��������������������������������������0000664�0000000�0000000�00000015545�14201712607�0024466�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright 2018, Rackspace US, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """NoLogPasswordsRule used with ansible-lint.""" import sys from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule from ansiblelint.utils import convert_to_boolean if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class NoLogPasswordsRule(AnsibleLintRule): """Describe and test the NoLogPasswordsRule.""" id = "no-log-password" shortdesc = "password should not be logged." description = ( "When passing password argument you should have no_log configured " "to a non False value to avoid accidental leaking of secrets." ) severity = "LOW" tags = ["opt-in", "security", "experimental"] version_added = "v5.0.9" def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: if task["action"]["__ansible_module__"] == "user" and ( ( task["action"].get("password_lock") or task["action"].get("password_lock") is False ) and not task["action"].get("password") ): has_password = False else: for param in task["action"].keys(): if "password" in param: has_password = True break else: has_password = False has_loop = [key for key in task if key.startswith("with_") or key == "loop"] # No no_log and no_log: False behave the same way # and should return a failure (return True), so we # need to invert the boolean return bool( has_password and not convert_to_boolean(task.get("no_log", False)) and len(has_loop) > 0 ) if "pytest" in sys.modules: import pytest from ansiblelint.testing import RunFromText # pylint: disable=ungrouped-imports NO_LOG_UNUSED = """ - hosts: all tasks: - name: Succeed when no_log is not used but no loop present user: name: bidule password: "wow" state: absent """ NO_LOG_FALSE = """ - hosts: all tasks: - name: Fail when no_log is set to False user: name: bidule user_password: "{{ item }}" state: absent with_items: - wow - now no_log: False """ NO_LOG_NO = """ - hosts: all tasks: - name: Fail when no_log is set to no user: name: bidule password: "{{ item }}" state: absent no_log: no loop: - wow - now """ PASSWORD_WITH_LOCK = """ - hosts: all tasks: - name: Fail when password is set and password_lock is true user: name: "{{ item }}" password: "wow" password_lock: true with_random_choice: - ansible - lint """ NO_LOG_YES = """ - hosts: all tasks: - name: Succeed when no_log is set to yes with_list: - name: user password: wow - password: now name: ansible user: name: "{{ item.name }}" password: "{{ item.password }}" state: absent no_log: yes """ NO_LOG_TRUE = """ - hosts: all tasks: - name: Succeed when no_log is set to True user: name: bidule user_password: "{{ item }}" state: absent no_log: True loop: - wow - now """ PASSWORD_LOCK_YES = """ - hosts: all tasks: - name: Succeed when only password locking account user: name: "{{ item }}" password_lock: yes # user_password: "this is a comment, not a password" with_list: - ansible - lint """ PASSWORD_LOCK_FALSE = """ - hosts: all tasks: - name: Succeed when password_lock is false and password is not used user: name: lint password_lock: False """ @pytest.mark.parametrize( "rule_runner", (NoLogPasswordsRule,), indirect=["rule_runner"] ) def test_no_log_unused(rule_runner: RunFromText) -> None: """The task does not use no_log but also no loop.""" results = rule_runner.run_playbook(NO_LOG_UNUSED) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (NoLogPasswordsRule,), indirect=["rule_runner"] ) def test_no_log_false(rule_runner: RunFromText) -> None: """The task sets no_log to false.""" results = rule_runner.run_playbook(NO_LOG_FALSE) assert len(results) == 1 @pytest.mark.parametrize( "rule_runner", (NoLogPasswordsRule,), indirect=["rule_runner"] ) def test_no_log_no(rule_runner: RunFromText) -> None: """The task sets no_log to no.""" results = rule_runner.run_playbook(NO_LOG_NO) assert len(results) == 1 @pytest.mark.parametrize( "rule_runner", (NoLogPasswordsRule,), indirect=["rule_runner"] ) def test_password_with_lock(rule_runner: RunFromText) -> None: """The task sets a password but also lock the user.""" results = rule_runner.run_playbook(PASSWORD_WITH_LOCK) assert len(results) == 1 @pytest.mark.parametrize( "rule_runner", (NoLogPasswordsRule,), indirect=["rule_runner"] ) def test_no_log_yes(rule_runner: RunFromText) -> None: """The task sets no_log to yes.""" results = rule_runner.run_playbook(NO_LOG_YES) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (NoLogPasswordsRule,), indirect=["rule_runner"] ) def test_no_log_true(rule_runner: RunFromText) -> None: """The task sets no_log to true.""" results = rule_runner.run_playbook(NO_LOG_TRUE) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (NoLogPasswordsRule,), indirect=["rule_runner"] ) def test_password_lock_yes(rule_runner: RunFromText) -> None: """The task only locks the user.""" results = rule_runner.run_playbook(PASSWORD_LOCK_YES) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (NoLogPasswordsRule,), indirect=["rule_runner"] ) def test_password_lock_false(rule_runner: RunFromText) -> None: """The task does not actually lock the user.""" results = rule_runner.run_playbook(PASSWORD_LOCK_FALSE) assert len(results) == 0 �����������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/NoSameOwnerRule.py�����������������������������������������0000664�0000000�0000000�00000011060�14201712607�0023723�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Optional rule for avoiding keeping owner/group when transferring files.""" import re import sys from typing import Any, List from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable from ansiblelint.rules import AnsibleLintRule from ansiblelint.utils import LINE_NUMBER_KEY class NoSameOwnerRule(AnsibleLintRule): id = "no-same-owner" shortdesc = "Owner should not be kept between different hosts" description = """ Optional rule that highlights dangers of assuming that user/group on the remote machines may not exist on ansible controller or vice versa. Owner and group should not be preserved when transferring files between them. This rule is not enabled by default and was inspired by Zuul execution policy. See: https://zuul-ci.org/docs/zuul-jobs/policy.html\ #preservation-of-owner-between-executor-and-remote """ severity = "LOW" tags = ["opt-in"] def matchplay(self, file: Lintable, data: Any) -> List[MatchError]: """Return matches found for a specific playbook.""" results: List[MatchError] = [] if file.kind not in ("tasks", "handlers", "playbook"): return results results.extend(self.handle_play(file, data)) return results def handle_play(self, lintable: Lintable, task: Any) -> List[MatchError]: """Process a play.""" results = [] if "block" in task: results.extend(self.handle_playlist(lintable, task["block"])) else: results.extend(self.handle_task(lintable, task)) return results def handle_playlist(self, lintable: Lintable, playlist: Any) -> List[MatchError]: """Process a playlist.""" results = [] for play in playlist: results.extend(self.handle_play(lintable, play)) return results def handle_task(self, lintable: Lintable, task: Any) -> List[MatchError]: """Process a task.""" results = [] if "synchronize" in task: if self.handle_synchronize(task): print(task) results.append( self.create_matcherror( filename=lintable, linenumber=task[LINE_NUMBER_KEY] ) ) elif "unarchive" in task: if self.handle_unarchive(task): results.append( self.create_matcherror( filename=lintable, linenumber=task[LINE_NUMBER_KEY] ) ) return results @staticmethod def handle_synchronize(task: Any) -> bool: """Process a synchronize task.""" if task.get("delegate_to") is not None: return False synchronize = task["synchronize"] archive = synchronize.get("archive", True) if synchronize.get("owner", archive) or synchronize.get("group", archive): return True return False @staticmethod def handle_unarchive(task: Any) -> bool: """Process unarchive task.""" unarchive = task["unarchive"] delegate_to = task.get("delegate_to") if ( delegate_to == "localhost" or delegate_to != "localhost" and "remote_src" not in unarchive ): if unarchive["src"].endswith("zip"): if "-X" in unarchive.get("extra_opts", []): return True if re.search(r".*\.tar(\.(gz|bz2|xz))?$", unarchive["src"]): if "--no-same-owner" not in unarchive.get("extra_opts", []): return True return False # testing code to be loaded only with pytest or when executed the rule file if "pytest" in sys.modules: import pytest from ansiblelint.rules import RulesCollection # pylint: disable=ungrouped-imports from ansiblelint.runner import Runner # pylint: disable=ungrouped-imports @pytest.mark.parametrize( ("test_file", "failures"), ( pytest.param( "examples/roles/role_for_no_same_owner/tasks/fail.yml", 10, id="fail" ), pytest.param( "examples/roles/role_for_no_same_owner/tasks/pass.yml", 0, id="pass" ), ), ) def test_no_same_owner_rule( default_rules_collection: RulesCollection, test_file: str, failures: int ) -> None: """Test rule matches.""" results = Runner(test_file, rules=default_rules_collection).run() assert len(results) == failures for result in results: assert result.message == NoSameOwnerRule.shortdesc ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/NoTabsRule.py����������������������������������������������0000664�0000000�0000000�00000004044�14201712607�0022720�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016, Will Thames and contributors # Copyright (c) 2018, Ansible Project import sys from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule from ansiblelint.yaml_utils import nested_items_path if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class NoTabsRule(AnsibleLintRule): id = "no-tabs" shortdesc = "Most files should not contain tabs" description = "Tabs can cause unexpected display issues, use spaces" severity = "LOW" tags = ["formatting"] version_added = "v4.0.0" allow_list = [ ("lineinfile", "insertafter"), ("lineinfile", "insertbefore"), ("lineinfile", "regexp"), ("lineinfile", "line"), ] def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: for k, v, parent_path in nested_items_path(task): if isinstance(k, str) and "\t" in k: return True parent_key = "" if not parent_path else parent_path[-1] if ( (parent_key, k) not in self.allow_list and isinstance(v, str) and "\t" in v ): return True return False RULE_EXAMPLE = r"""--- - hosts: localhost tasks: - name: should not trigger no-tabs rules lineinfile: path: some.txt regexp: '^\t$' line: 'string with \t inside' - name: foo debug: msg: "Presence of \t should trigger no-tabs here." """ # testing code to be loaded only with pytest or when executed the rule file if "pytest" in sys.modules: import pytest @pytest.mark.parametrize("rule_runner", (NoTabsRule,), indirect=["rule_runner"]) def test_no_tabs_rule(rule_runner: "Any") -> None: """Test rule matches.""" results = rule_runner.run_playbook(RULE_EXAMPLE) assert results[0].linenumber == 9 assert results[0].message == NoTabsRule.shortdesc assert len(results) == 1 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/OctalPermissionsRule.py������������������������������������0000664�0000000�0000000�00000007231�14201712607�0025031�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class OctalPermissionsRule(AnsibleLintRule): id = "risky-octal" shortdesc = "Octal file permissions must contain leading zero or be a string" description = ( "Numeric file permissions without leading zero can behave " "in unexpected ways. See " "https://docs.ansible.com/ansible/latest/collections/ansible/builtin/file_module.html" ) severity = "VERY_HIGH" tags = ["formatting"] version_added = "historic" _modules = [ "assemble", "copy", "file", "ini_file", "lineinfile", "replace", "synchronize", "template", "unarchive", ] def is_invalid_permission(self, mode: int) -> bool: # sensible file permission modes don't # have write bit set when read bit is # not set and don't have execute bit set # when user execute bit is not set. # also, user permissions are more generous than # group permissions and user and group permissions # are more generous than world permissions other_write_without_read = ( mode % 8 and mode % 8 < 4 and not (mode % 8 == 1 and (mode >> 6) % 2 == 1) ) group_write_without_read = ( (mode >> 3) % 8 and (mode >> 3) % 8 < 4 and not ((mode >> 3) % 8 == 1 and (mode >> 6) % 2 == 1) ) user_write_without_read = ( (mode >> 6) % 8 and (mode >> 6) % 8 < 4 and not (mode >> 6) % 8 == 1 ) other_more_generous_than_group = mode % 8 > (mode >> 3) % 8 other_more_generous_than_user = mode % 8 > (mode >> 6) % 8 group_more_generous_than_user = (mode >> 3) % 8 > (mode >> 6) % 8 return bool( other_write_without_read or group_write_without_read or user_write_without_read or other_more_generous_than_group or other_more_generous_than_user or group_more_generous_than_user ) def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: if task["action"]["__ansible_module__"] in self._modules: mode = task["action"].get("mode", None) if isinstance(mode, str): return False if isinstance(mode, int): return self.is_invalid_permission(mode) return False �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/PackageIsNotLatestRule.py����������������������������������0000664�0000000�0000000�00000004743�14201712607�0025225�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class PackageIsNotLatestRule(AnsibleLintRule): id = "package-latest" shortdesc = "Package installs should not use latest" description = ( "Package installs should use ``state=present`` with or without a version" ) severity = "VERY_LOW" tags = ["idempotency"] version_added = "historic" _package_managers = [ "apk", "apt", "bower", "bundler", "dnf", "easy_install", "gem", "homebrew", "jenkins_plugin", "npm", "openbsd_package", "openbsd_pkg", "package", "pacman", "pear", "pip", "pkg5", "pkgutil", "portage", "slackpkg", "sorcery", "swdepot", "win_chocolatey", "yarn", "yum", "zypper", ] def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: return ( task["action"]["__ansible_module__"] in self._package_managers and not task["action"].get("version") and not task["action"].get("update_only") and task["action"].get("state") == "latest" ) �����������������������������ansible-lint-5.4.0/src/ansiblelint/rules/PlaybookExtension.py���������������������������������������0000664�0000000�0000000�00000001743�14201712607�0024362�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016, Tsukinowa Inc. <info@tsukinowa.jp> # Copyright (c) 2018, Ansible Project import os from typing import List from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable from ansiblelint.rules import AnsibleLintRule class PlaybookExtension(AnsibleLintRule): id = "playbook-extension" shortdesc = 'Use ".yml" or ".yaml" playbook extension' description = 'Playbooks should have the ".yml" or ".yaml" extension' severity = "MEDIUM" tags = ["formatting"] done: List[str] = [] version_added = "v4.0.0" def matchyaml(self, file: Lintable) -> List[MatchError]: result: List[MatchError] = [] if file.kind != "playbook": return result path = str(file.path) ext = os.path.splitext(path) if ext[1] not in [".yml", ".yaml"] and path not in self.done: self.done.append(path) result.append(self.create_matcherror(filename=path)) return result �����������������������������ansible-lint-5.4.0/src/ansiblelint/rules/RoleLoopVarPrefix.py���������������������������������������0000664�0000000�0000000�00000005547�14201712607�0024275�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Optional Ansible-lint rule to enforce use of prefix on role loop vars.""" from typing import TYPE_CHECKING, List from ansiblelint.config import options from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable from ansiblelint.rules import AnsibleLintRule from ansiblelint.text import toidentifier from ansiblelint.utils import LINE_NUMBER_KEY if TYPE_CHECKING: from typing import Any from ansiblelint.constants import odict class RoleLoopVarPrefix(AnsibleLintRule): """Role loop_var should use configured prefix.""" id = "no-loop-var-prefix" shortdesc = __doc__ link = ( "https://docs.ansible.com/ansible/latest/user_guide/" "playbooks_loops.html#defining-inner-and-outer-variable-names-with-loop-var" ) description = """\ Looping inside roles has the risk of clashing with loops from user-playbooks.\ """ tags = ["idiom"] prefix = "" severity = "MEDIUM" def matchplay(self, file: Lintable, data: "odict[str, Any]") -> List[MatchError]: """Return matches found for a specific playbook.""" results: List[MatchError] = [] if not options.loop_var_prefix: return results self.prefix = options.loop_var_prefix.format(role=toidentifier(file.role)) self.shortdesc = f"{self.__class__.shortdesc}: {self.prefix}" if file.kind not in ("tasks", "handlers"): return results results.extend(self.handle_play(file, data)) return results def handle_play( self, lintable: Lintable, task: "odict[str, Any]" ) -> List[MatchError]: """Return matches for a playlist.""" results = [] if "block" in task: results.extend(self.handle_tasks(lintable, task["block"])) else: results.extend(self.handle_task(lintable, task)) return results def handle_tasks( self, lintable: Lintable, tasks: List["odict[str, Any]"] ) -> List[MatchError]: """Return matches for a list of tasks.""" results = [] for play in tasks: results.extend(self.handle_play(lintable, play)) return results def handle_task( self, lintable: Lintable, task: "odict[str, Any]" ) -> List[MatchError]: """Return matches for a specific task.""" results = [] has_loop = "loop" in task for key in task.keys(): if key.startswith("with_"): has_loop = True if has_loop: loop_control = task.get("loop_control", {}) loop_var = loop_control.get("loop_var", "") if not loop_var or not loop_var.startswith(self.prefix): results.append( self.create_matcherror( filename=lintable, linenumber=task[LINE_NUMBER_KEY] ) ) return results ���������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/RoleNames.py�����������������������������������������������0000664�0000000�0000000�00000007256�14201712607�0022577�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2020 Gael Chamoulaud <gchamoul@redhat.com> # Copyright (c) 2020 Sorin Sbarnea <ssbarnea@redhat.com> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import re from pathlib import Path from typing import TYPE_CHECKING, List from ansiblelint.file_utils import Lintable from ansiblelint.rules import AnsibleLintRule from ansiblelint.utils import parse_yaml_from_file if TYPE_CHECKING: from ansiblelint.errors import MatchError ROLE_NAME_REGEX = r"^[a-z][a-z0-9_]+$" def _remove_prefix(text: str, prefix: str) -> str: return re.sub(r"^{0}".format(re.escape(prefix)), "", text) class RoleNames(AnsibleLintRule): id = "role-name" shortdesc = "Role name {0} does not match ``%s`` pattern" % ROLE_NAME_REGEX description = ( "Role names are now limited to contain only lowercase alphanumeric " "characters, plus '_' and start with an alpha character. See " "`developing collections <https://docs.ansible.com/ansible/devel/dev_guide/" "developing_collections_structure.html#roles-directory>`_" ) severity = "HIGH" done: List[str] = [] # already noticed roles list tags = ["deprecations", "metadata"] version_added = "v4.3.0" def __init__(self) -> None: """Save precompiled regex.""" self._re = re.compile(ROLE_NAME_REGEX) def matchdir(self, lintable: "Lintable") -> List["MatchError"]: return self.matchyaml(lintable) def matchyaml(self, file: Lintable) -> List["MatchError"]: result: List["MatchError"] = [] if file.kind not in ("meta", "role"): return result if file.kind == "role": role_name = self._infer_role_name( meta=file.path / "meta" / "main.yml", default=file.path.name ) else: role_name = self._infer_role_name( meta=file.path, default=file.path.resolve().parents[1].name ) role_name = _remove_prefix(role_name, "ansible-role-") if role_name not in self.done: self.done.append(role_name) if role_name and not self._re.match(role_name): result.append( self.create_matcherror( filename=str(file.path), message=self.__class__.shortdesc.format(role_name), ) ) return result def _infer_role_name(self, meta: Path, default: str) -> str: if meta.is_file(): meta_data = parse_yaml_from_file(str(meta)) if meta_data: try: return str(meta_data["galaxy_info"]["role_name"]) except KeyError: pass return default ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/RoleRelativePath.py����������������������������������������0000664�0000000�0000000�00000002350�14201712607�0024112�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016, Tsukinowa Inc. <info@tsukinowa.jp> # Copyright (c) 2018, Ansible Project from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class RoleRelativePath(AnsibleLintRule): id = "no-relative-paths" shortdesc = "Doesn't need a relative path in role" description = ( "``copy`` and ``template`` do not need to use relative path for ``src``" ) severity = "HIGH" tags = ["idiom"] version_added = "v4.0.0" _module_to_path_folder = { "copy": "files", "win_copy": "files", "template": "templates", "win_template": "win_templates", } def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: module = task["action"]["__ansible_module__"] if module not in self._module_to_path_folder: return False if "src" not in task["action"]: return False path_to_check = "../{}".format(self._module_to_path_folder[module]) if path_to_check in task["action"]["src"]: return True return False ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/ShellWithoutPipefail.py������������������������������������0000664�0000000�0000000�00000003217�14201712607�0025010�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import re from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule from ansiblelint.utils import convert_to_boolean if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class ShellWithoutPipefail(AnsibleLintRule): id = "risky-shell-pipe" shortdesc = "Shells that use pipes should set the pipefail option" description = ( "Without the pipefail option set, a shell command that " "implements a pipeline can fail and still return 0. If " "any part of the pipeline other than the terminal command " "fails, the whole pipeline will still return 0, which may " "be considered a success by Ansible. " "Pipefail is available in the bash shell." ) severity = "MEDIUM" tags = ["command-shell"] version_added = "v4.1.0" _pipefail_re = re.compile(r"^\s*set.*[+-][A-z]*o\s*pipefail", re.M) _pipe_re = re.compile(r"(?<!\|)\|(?!\|)") def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: if task["__ansible_action_type__"] != "task": return False if task["action"]["__ansible_module__"] != "shell": return False if task.get("ignore_errors"): return False unjinjad_cmd = self.unjinja( " ".join(task["action"].get("__ansible_arguments__", [])) ) return bool( self._pipe_re.search(unjinjad_cmd) and not self._pipefail_re.search(unjinjad_cmd) and not convert_to_boolean(task["action"].get("ignore_errors", False)) ) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/TaskHasNameRule.py�����������������������������������������0000664�0000000�0000000�00000003371�14201712607�0023673�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class TaskHasNameRule(AnsibleLintRule): id = "unnamed-task" shortdesc = "All tasks should be named" description = ( "All tasks should have a distinct name for readability " "and for ``--start-at-task`` to work" ) severity = "MEDIUM" tags = ["idiom"] version_added = "historic" def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: return not task.get("name") �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/TaskNoLocalAction.py���������������������������������������0000664�0000000�0000000�00000001100�14201712607�0024200�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016, Tsukinowa Inc. <info@tsukinowa.jp> # Copyright (c) 2018, Ansible Project from ansiblelint.rules import AnsibleLintRule class TaskNoLocalAction(AnsibleLintRule): id = "deprecated-local-action" shortdesc = "Do not use 'local_action', use 'delegate_to: localhost'" description = "Do not use ``local_action``, use ``delegate_to: localhost``" severity = "MEDIUM" tags = ["deprecations"] version_added = "v4.0.0" def match(self, line: str) -> bool: if "local_action" in line: return True return False ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/UseCommandInsteadOfShellRule.py����������������������������0000664�0000000�0000000�00000010545�14201712607�0026355�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import sys from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable FAIL_PLAY = """--- - hosts: localhost tasks: - name: shell no pipe shell: echo hello changed_when: false - name: shell with jinja filter shell: echo {{ "hello"|upper }} changed_when: false - name: shell with jinja filter (fqcn) ansible.builtin.shell: echo {{ "hello"|upper }} changed_when: false """ SUCCESS_PLAY = """--- - hosts: localhost tasks: - name: shell with pipe shell: echo hello | true # noqa: risky-shell-pipe changed_when: false - name: shell with redirect shell: echo hello > /tmp/hello changed_when: false - name: chain two shell commands shell: echo hello && echo goodbye changed_when: false - name: run commands in succession shell: echo hello ; echo goodbye changed_when: false - name: use variables shell: echo $HOME $USER changed_when: false - name: use * for globbing shell: ls foo* changed_when: false - name: use ? for globbing shell: ls foo? changed_when: false - name: use [] for globbing shell: ls foo[1,2,3] changed_when: false - name: use shell generator shell: ls foo{.txt,.xml} changed_when: false - name: use backticks shell: ls `ls foo*` changed_when: false - name: use shell with cmd shell: cmd: | set -x ls foo? changed_when: false """ class UseCommandInsteadOfShellRule(AnsibleLintRule): id = "command-instead-of-shell" shortdesc = "Use shell only when shell functionality is required" description = ( "Shell should only be used when piping, redirecting " "or chaining commands (and Ansible would be preferred " "for some of those!)" ) severity = "HIGH" tags = ["command-shell", "idiom"] version_added = "historic" def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: # Use unjinja so that we don't match on jinja filters # rather than pipes if task["action"]["__ansible_module__"] in ["shell", "ansible.builtin.shell"]: if "cmd" in task["action"]: unjinjad_cmd = self.unjinja(task["action"].get("cmd", [])) else: unjinjad_cmd = self.unjinja( " ".join(task["action"].get("__ansible_arguments__", [])) ) return not any(ch in unjinjad_cmd for ch in "&|<>;$\n*[]{}?`") return False # testing code to be loaded only with pytest or when executed the rule file if "pytest" in sys.modules: import pytest from ansiblelint.testing import RunFromText # pylint: disable=ungrouped-imports @pytest.mark.parametrize(("text", "expected"), ((SUCCESS_PLAY, 0), (FAIL_PLAY, 3))) def test_rule_command_instead_of_shell( default_text_runner: RunFromText, text: str, expected: int ) -> None: """Validate that rule works as intended.""" results = default_text_runner.run_playbook(text) for result in results: assert result.rule.id == UseCommandInsteadOfShellRule.id, result assert len(results) == expected �����������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/UseHandlerRatherThanWhenChangedRule.py���������������������0000664�0000000�0000000�00000011511�14201712607�0027636�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """UseHandlerRatherThanWhenChangedRule used with ansible-lint.""" import sys from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable def _changed_in_when(item: str) -> bool: item_list = item.split() if not isinstance(item, str) or "and" in item_list: return False return any( changed in item for changed in [ ".changed", "|changed", '["changed"]', "['changed']", "is changed", ] ) class UseHandlerRatherThanWhenChangedRule(AnsibleLintRule): id = "no-handler" shortdesc = "Tasks that run when changed should likely be handlers" description = ( "If a task has a ``when: result.changed`` setting, it is effectively " "acting as a handler. You could use notify and move that task to " "handlers." ) link = "https://docs.ansible.com/ansible/latest/user_guide/playbooks_handlers.html" severity = "MEDIUM" tags = ["idiom"] version_added = "historic" def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: if task["__ansible_action_type__"] != "task": return False when = task.get("when") if isinstance(when, list): for item in when: return _changed_in_when(item) if isinstance(when, str): return _changed_in_when(when) return False if "pytest" in sys.modules: import pytest SUCCEED_CHANGED_WHEN = """ - hosts: all tasks: - name: execute something command: echo 123 register: result changed_when: true """ SUCCEED_WHEN_AND = """ - hosts: all tasks: - name: registering task 1 command: echo Hello register: r1 changed_when: true - name: registering task 2 command: echo Hello register: r2 changed_when: true - name: when task command: echo Hello when: r1.changed and r2.changed """ FAIL_RESULT_IS_CHANGED = """ - hosts: all tasks: - name: this should trigger no-handler rule command: echo could be done better when: result is changed """ FAILED_SOMETHING_CHANGED = """ - hosts: all tasks: - name: do anything command: echo 123 when: - something.changed """ @pytest.mark.parametrize( "rule_runner", (UseHandlerRatherThanWhenChangedRule,), indirect=["rule_runner"] ) def test_succeed_changed_when(rule_runner: Any) -> None: """Using changed_when is acceptable.""" results = rule_runner.run_playbook(SUCCEED_CHANGED_WHEN) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (UseHandlerRatherThanWhenChangedRule,), indirect=["rule_runner"] ) def test_succeed_when_and(rule_runner: Any) -> None: """See https://github.com/ansible-community/ansible-lint/issues/1526.""" results = rule_runner.run_playbook(SUCCEED_WHEN_AND) assert len(results) == 0 @pytest.mark.parametrize( "rule_runner", (UseHandlerRatherThanWhenChangedRule,), indirect=["rule_runner"] ) def test_fail_result_is_changed(rule_runner: Any) -> None: """This task uses 'is changed'.""" results = rule_runner.run_playbook(FAIL_RESULT_IS_CHANGED) assert len(results) == 1 @pytest.mark.parametrize( "rule_runner", (UseHandlerRatherThanWhenChangedRule,), indirect=["rule_runner"] ) def test_failed_something_changed(rule_runner: Any) -> None: """This task uses '.changed'.""" results = rule_runner.run_playbook(FAILED_SOMETHING_CHANGED) assert len(results) == 1 ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/UsingBareVariablesIsDeprecatedRule.py����������������������0000664�0000000�0000000�00000007574�14201712607�0027532�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import os import re from typing import TYPE_CHECKING, Any, Dict, Union from ansiblelint.rules import AnsibleLintRule if TYPE_CHECKING: from typing import Optional from ansiblelint.file_utils import Lintable class UsingBareVariablesIsDeprecatedRule(AnsibleLintRule): id = "deprecated-bare-vars" shortdesc = "Using bare variables is deprecated" description = ( "Using bare variables is deprecated. Update your " "playbooks so that the environment value uses the full variable " "syntax ``{{ your_variable }}``" ) severity = "VERY_HIGH" tags = ["deprecations"] version_added = "historic" _jinja = re.compile(r"{[{%].*[%}]}", re.DOTALL) _glob = re.compile("[][*?]") def matchtask( self, task: Dict[str, Any], file: "Optional[Lintable]" = None ) -> Union[bool, str]: loop_type = next((key for key in task if key.startswith("with_")), None) if loop_type: if loop_type in [ "with_nested", "with_together", "with_flattened", "with_filetree", ]: # These loops can either take a list defined directly in the task # or a variable that is a list itself. When a single variable is used # we just need to check that one variable, and not iterate over it like # it's a list. Otherwise, loop through and check all items. items = task[loop_type] if not isinstance(items, (list, tuple)): items = [items] for var in items: return self._matchvar(var, task, loop_type) elif loop_type == "with_subelements": return self._matchvar(task[loop_type][0], task, loop_type) elif loop_type in ["with_sequence", "with_ini", "with_inventory_hostnames"]: pass else: return self._matchvar(task[loop_type], task, loop_type) return False def _matchvar( self, varstring: str, task: Dict[str, Any], loop_type: str ) -> Union[bool, str]: if isinstance(varstring, str) and not self._jinja.match(varstring): valid = loop_type == "with_fileglob" and bool( self._jinja.search(varstring) or self._glob.search(varstring) ) valid |= loop_type == "with_filetree" and bool( self._jinja.search(varstring) or varstring.endswith(os.sep) ) if not valid: message = ( "Found a bare variable '{0}' used in a '{1}' loop." + " You should use the full variable syntax ('{{{{ {0} }}}}')" ) return message.format(task[loop_type], loop_type) return False ������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/VariableHasSpacesRule.py�����������������������������������0000664�0000000�0000000�00000005022�14201712607�0025047�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016, Will Thames and contributors # Copyright (c) 2018, Ansible Project import re import sys from typing import Any, Dict, List, Optional, Union from ansiblelint.file_utils import Lintable from ansiblelint.rules import AnsibleLintRule from ansiblelint.yaml_utils import nested_items_path class VariableHasSpacesRule(AnsibleLintRule): id = "var-spacing" base_msg = "Variables should have spaces before and after: " shortdesc = base_msg + " {{ var_name }}" description = "Variables should have spaces before and after: ``{{ var_name }}``" severity = "LOW" tags = ["formatting"] version_added = "v4.0.0" bracket_regex = re.compile(r"{{[^{\n' -]|[^ '\n}-]}}", re.MULTILINE | re.DOTALL) exclude_json_re = re.compile(r"[^{]{'\w+': ?[^{]{.*?}}", re.MULTILINE | re.DOTALL) def matchtask( self, task: Dict[str, Any], file: Optional[Lintable] = None ) -> Union[bool, str]: for _, v, _ in nested_items_path(task): if isinstance(v, str): cleaned = self.exclude_json_re.sub("", v) if bool(self.bracket_regex.search(cleaned)): return self.base_msg + v return False if "pytest" in sys.modules: import pytest from ansiblelint.rules import RulesCollection # pylint: disable=ungrouped-imports from ansiblelint.runner import Runner # pylint: disable=ungrouped-imports @pytest.fixture def error_expected_lines() -> List[int]: """Return list of expected error lines.""" return [23, 26, 29, 54, 65] @pytest.fixture def test_playbook() -> str: """Return test cases playbook path.""" return "examples/playbooks/var-spacing.yml" @pytest.fixture def lint_error_lines(test_playbook: str) -> List[int]: """Get VarHasSpacesRules linting results on test_playbook.""" collection = RulesCollection() collection.register(VariableHasSpacesRule()) lintable = Lintable(test_playbook) results = Runner(lintable, rules=collection).run() return list(map(lambda item: item.linenumber, results)) def test_var_spacing( error_expected_lines: List[int], lint_error_lines: List[int] ) -> None: """Ensure that expected error lines are matching found linting error lines.""" # list unexpected error lines or non-matching error lines error_lines_difference = list( set(error_expected_lines).symmetric_difference(set(lint_error_lines)) ) assert len(error_lines_difference) == 0 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/VariableNamingRule.py��������������������������������������0000664�0000000�0000000�00000012744�14201712607�0024417�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import keyword import re import sys from functools import lru_cache from typing import TYPE_CHECKING, Any, Dict, List, Optional, Pattern, Union from ansiblelint.config import options from ansiblelint.file_utils import Lintable from ansiblelint.rules import AnsibleLintRule from ansiblelint.utils import LINE_NUMBER_KEY, parse_yaml_from_file if TYPE_CHECKING: from ansiblelint.constants import odict from ansiblelint.errors import MatchError FAIL_PLAY = """ - hosts: localhost vars: CamelCaseIsBad: false # invalid this_is_valid: # valid because content is a dict, not a variable CamelCase: ... ALL_CAPS: ... ALL_CAPS_ARE_BAD_TOO: ... # invalid """ # properties/parameters are prefixed and postfixed with `__` def is_property(k: str) -> bool: """Check if key is a property.""" return k.startswith("__") and k.endswith("__") class VariableNamingRule(AnsibleLintRule): id = "var-naming" base_msg = "All variables should be named using only lowercase and underscores" shortdesc = base_msg description = "All variables should be named using only lowercase and underscores" severity = ( "MEDIUM" # ansible-lint displays severity when with --parseable-severity option ) tags = ["idiom", "experimental"] version_added = "v5.0.10" @lru_cache() def re_pattern(self) -> Pattern[str]: return re.compile(options.var_naming_pattern or "^[a-z_][a-z0-9_]*$") def is_invalid_variable_name(self, ident: str) -> bool: """Check if variable name is using right pattern.""" # Based on https://github.com/ansible/ansible/blob/devel/lib/ansible/utils/vars.py#L235 if not isinstance(ident, str): return False try: ident.encode("ascii") except UnicodeEncodeError: return False if keyword.iskeyword(ident): return False # previous tests should not be triggered as they would have raised a # syntax-error when we loaded the files but we keep them here as a # safety measure. return not bool(self.re_pattern().match(ident)) def matchplay( self, file: "Lintable", data: "odict[str, Any]" ) -> List["MatchError"]: """Return matches found for a specific playbook.""" results = [] # If the Play uses the 'vars' section to set variables our_vars = data.get("vars", {}) for key in our_vars.keys(): if self.is_invalid_variable_name(key): results.append( self.create_matcherror( filename=file, linenumber=our_vars[LINE_NUMBER_KEY], message="Play defines variable '" + key + "' within 'vars' section that violates variable naming standards", ) ) return results def matchtask( self, task: Dict[str, Any], file: Optional[Lintable] = None ) -> Union[bool, str]: """Return matches for task based variables.""" # If the task uses the 'vars' section to set variables our_vars = task.get("vars", {}) for key in our_vars.keys(): if self.is_invalid_variable_name(key): return "Task defines variables within 'vars' section that violates variable naming standards" # If the task uses the 'set_fact' module ansible_module = task["action"]["__ansible_module__"] ansible_action = task["action"] if ansible_module == "set_fact": for key in ansible_action.keys(): if self.is_invalid_variable_name(key): return "Task uses 'set_fact' to define variables that violates variable naming standards" # If the task registers a variable registered_var = task.get("register", None) if registered_var and self.is_invalid_variable_name(registered_var): return "Task registers a variable that violates variable naming standards" return False def matchyaml(self, file: Lintable) -> List["MatchError"]: """Return matches for variables defined in vars files.""" results: List["MatchError"] = [] meta_data: Dict[str, Any] = {} if str(file.kind) == "vars": meta_data = parse_yaml_from_file(str(file.path)) for key in meta_data.keys(): if self.is_invalid_variable_name(key): results.append( self.create_matcherror( filename=file, # linenumber=vars[LINE_NUMBER_KEY], message="File defines variable '" + key + "' that violates variable naming standards", ) ) else: results.extend(super().matchyaml(file)) return results # testing code to be loaded only with pytest or when executed the rule file if "pytest" in sys.modules: import pytest from ansiblelint.testing import RunFromText # pylint: disable=ungrouped-imports @pytest.mark.parametrize( "rule_runner", (VariableNamingRule,), indirect=["rule_runner"] ) def test_invalid_var_name_playbook(rule_runner: RunFromText) -> None: """Test rule matches.""" results = rule_runner.run_playbook(FAIL_PLAY) assert len(results) == 2 for result in results: assert result.rule.id == VariableNamingRule.id ����������������������������ansible-lint-5.4.0/src/ansiblelint/rules/YamllintRule.py��������������������������������������������0000664�0000000�0000000�00000010447�14201712607�0023327�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import logging import os import sys from typing import TYPE_CHECKING, List from ansiblelint.config import options from ansiblelint.file_utils import Lintable from ansiblelint.rules import AnsibleLintRule from ansiblelint.skip_utils import get_rule_skips_from_line if TYPE_CHECKING: from ansiblelint.errors import MatchError _logger = logging.getLogger(__name__) # yamllint is a soft-dependency (not installed by default) try: from yamllint.config import YamlLintConfig from yamllint.linter import run as run_yamllint except ImportError: # missing library is ignored unless yaml is exclitely added to enable_list if "yaml" in options.enable_list: raise RuntimeError( "Failed to load yamllint library and ansible-linted was configured to require it." ) YAMLLINT_CONFIG = """ extends: default rules: comments: # https://github.com/prettier/prettier/issues/6780 min-spaces-from-content: 1 # https://github.com/adrienverge/yamllint/issues/384 comments-indentation: false document-start: disable # 160 chars was the default used by old E204 rule, but # you can easily change it or disable in your .yamllint file. line-length: max: 160 """ DESCRIPTION = """\ Rule violations reported by YamlLint when this is installed. You can fully disable all of them by adding 'yaml' to the 'skip_list'. Specific tag identifiers that are printed at the end of rule name, like 'trailing-spaces' or 'indentation' can also be be skipped, allowing you to have a more fine control. By default this rule is not used when yamllint library is missing. If you want to make its absence a runtime failure, please add 'yaml' to 'enable_list' inside the configuration file. """ class YamllintRule(AnsibleLintRule): id = "yaml" shortdesc = "Violations reported by yamllint" description = DESCRIPTION severity = "VERY_LOW" tags = ["formatting", "yaml"] version_added = "v5.0.0" config = None has_dynamic_tags = True if "yamllint.config" in sys.modules: config = YamlLintConfig(content=YAMLLINT_CONFIG) # if we detect local yamllint config we use it but raise a warning # as this is likely to get out of sync with our internal config. for file in [".yamllint", ".yamllint.yaml", ".yamllint.yml"]: if os.path.isfile(file): _logger.warning( "Loading custom %s config file, this extends our " "internal yamllint config.", file, ) config_override = YamlLintConfig(file=file) config_override.extend(config) config = config_override break _logger.debug("Effective yamllint rules used: %s", config.rules) def __init__(self) -> None: """Construct a rule instance.""" # customize id by adding the one reported by yamllint self.id = self.__class__.id def matchyaml(self, file: Lintable) -> List["MatchError"]: """Return matches found for a specific YAML text.""" matches: List["MatchError"] = [] filtered_matches: List["MatchError"] = [] if str(file.base_kind) != "text/yaml": return matches if YamllintRule.config: for p in run_yamllint( file.content, YamllintRule.config, filepath=file.path ): self.severity = "VERY_LOW" if p.level == "error": self.severity = "MEDIUM" if p.desc.endswith("(syntax)"): self.severity = "VERY_HIGH" matches.append( self.create_matcherror( message=p.desc, linenumber=p.line, details="", filename=str(file.path), tag=p.rule, ) ) if matches: lines = file.content.splitlines() for match in matches: # rule.linenumber starts with 1, not zero skip_list = get_rule_skips_from_line(lines[match.linenumber - 1]) # print(skip_list) if match.rule.id not in skip_list and match.tag not in skip_list: filtered_matches.append(match) return filtered_matches �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/__init__.py������������������������������������������������0000664�0000000�0000000�00000025035�14201712607�0022444�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""All internal ansible-lint rules.""" import copy import glob import importlib.util import logging import os import re from argparse import Namespace from collections import defaultdict from functools import lru_cache from importlib.abc import Loader from typing import Any, Dict, Iterator, List, Optional, Set, Union import ansiblelint.skip_utils import ansiblelint.utils import ansiblelint.yaml_utils from ansiblelint._internal.rules import ( AnsibleParserErrorRule, BaseRule, LoadingFailureRule, RuntimeErrorRule, ) from ansiblelint.config import get_rule_config, options from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable, expand_paths_vars _logger = logging.getLogger(__name__) class AnsibleLintRule(BaseRule): @property def rule_config(self) -> Dict[str, Any]: return get_rule_config(self.id) @lru_cache() def get_config(self, key: str) -> Any: """Return a configured value for given key string.""" return self.rule_config.get(key, None) def __repr__(self) -> str: """Return a AnsibleLintRule instance representation.""" return self.id + ": " + self.shortdesc @staticmethod def unjinja(text: str) -> str: text = re.sub(r"{{.+?}}", "JINJA_EXPRESSION", text) text = re.sub(r"{%.+?%}", "JINJA_STATEMENT", text) text = re.sub(r"{#.+?#}", "JINJA_COMMENT", text) return text # pylint: disable=too-many-arguments def create_matcherror( self, message: Optional[str] = None, linenumber: int = 1, details: str = "", filename: Optional[Union[str, Lintable]] = None, tag: str = "", ) -> MatchError: match = MatchError( message=message, linenumber=linenumber, details=details, filename=filename, rule=copy.copy(self), ) if tag: match.tag = tag return match def matchlines(self, file: "Lintable") -> List[MatchError]: matches: List[MatchError] = [] if not self.match: return matches # arrays are 0-based, line numbers are 1-based # so use prev_line_no as the counter for (prev_line_no, line) in enumerate(file.content.split("\n")): if line.lstrip().startswith("#"): continue rule_id_list = ansiblelint.skip_utils.get_rule_skips_from_line(line) if self.id in rule_id_list: continue result = self.match(line) if not result: continue message = None if isinstance(result, str): message = result m = self.create_matcherror( message=message, linenumber=prev_line_no + 1, details=line, filename=file, ) matches.append(m) return matches # TODO(ssbarnea): Reduce mccabe complexity # https://github.com/ansible-community/ansible-lint/issues/744 def matchtasks(self, file: Lintable) -> List[MatchError]: matches: List[MatchError] = [] if ( not self.matchtask or file.kind not in ["handlers", "tasks", "playbook"] or str(file.base_kind) != "text/yaml" ): return matches tasks_iterator = ansiblelint.yaml_utils.iter_tasks_in_file(file, self.id) for raw_task, task, skipped, error in tasks_iterator: if error is not None: # normalize_task converts AnsibleParserError to MatchError return [error] if skipped or "action" not in task: continue if self.needs_raw_task: task["__raw_task__"] = raw_task result = self.matchtask(task, file=file) if not result: continue message = None if isinstance(result, str): message = result task_msg = "Task/Handler: " + ansiblelint.utils.task_to_str(task) m = self.create_matcherror( message=message, linenumber=task[ansiblelint.utils.LINE_NUMBER_KEY], details=task_msg, filename=file, ) matches.append(m) return matches def matchyaml(self, file: Lintable) -> List[MatchError]: matches: List[MatchError] = [] if not self.matchplay or str(file.base_kind) != "text/yaml": return matches yaml = ansiblelint.utils.parse_yaml_linenumbers(file) # yaml returned can be an AnsibleUnicode (a string) when the yaml # file contains a single string. YAML spec allows this but we consider # this an fatal error. if isinstance(yaml, str): if yaml.startswith("$ANSIBLE_VAULT"): return [] return [MatchError(filename=str(file.path), rule=LoadingFailureRule())] if not yaml: return matches if isinstance(yaml, dict): yaml = [yaml] yaml = ansiblelint.skip_utils.append_skipped_rules(yaml, file) for play in yaml: # Bug #849 if play is None: continue if self.id in play.get("skipped_rules", ()): continue matches.extend(self.matchplay(file, play)) return matches def is_valid_rule(rule: AnsibleLintRule) -> bool: """Check if given rule is valid or not.""" return isinstance(rule, AnsibleLintRule) and bool(rule.id) and bool(rule.shortdesc) def load_plugins(directory: str) -> Iterator[AnsibleLintRule]: """Yield a rule class.""" for pluginfile in glob.glob(os.path.join(directory, "[A-Za-z]*.py")): pluginname = os.path.basename(pluginfile.replace(".py", "")) spec = importlib.util.spec_from_file_location(pluginname, pluginfile) # https://github.com/python/typeshed/issues/2793 if spec and isinstance(spec.loader, Loader): module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) try: rule = getattr(module, pluginname)() if is_valid_rule(rule): yield rule except (TypeError, ValueError, AttributeError): _logger.warning("Skipped invalid rule from %s", pluginname) class RulesCollection: def __init__( self, rulesdirs: Optional[List[str]] = None, options: Namespace = options ) -> None: """Initialize a RulesCollection instance.""" self.options = options if rulesdirs is None: rulesdirs = [] self.rulesdirs = expand_paths_vars(rulesdirs) self.rules: List[BaseRule] = [] # internal rules included in order to expose them for docs as they are # not directly loaded by our rule loader. self.rules.extend( [RuntimeErrorRule(), AnsibleParserErrorRule(), LoadingFailureRule()] ) for rulesdir in self.rulesdirs: _logger.debug("Loading rules from %s", rulesdir) for rule in load_plugins(rulesdir): self.register(rule) self.rules = sorted(self.rules) def register(self, obj: AnsibleLintRule) -> None: # We skip opt-in rules which were not manually enabled if "opt-in" not in obj.tags or obj.id in self.options.enable_list: self.rules.append(obj) def __iter__(self) -> Iterator[BaseRule]: """Return the iterator over the rules in the RulesCollection.""" return iter(self.rules) def __len__(self) -> int: """Return the length of the RulesCollection data.""" return len(self.rules) def extend(self, more: List[AnsibleLintRule]) -> None: self.rules.extend(more) def run( self, file: Lintable, tags: Set[str] = set(), skip_list: List[str] = [] ) -> List[MatchError]: matches: List[MatchError] = list() if not file.path.is_dir(): try: if file.content is not None: # loads the file content pass except IOError as e: return [ MatchError( message=str(e), filename=file, rule=LoadingFailureRule(), tag=e.__class__.__name__.lower(), ) ] for rule in self.rules: if ( not tags or rule.has_dynamic_tags or not set(rule.tags).union([rule.id]).isdisjoint(tags) ): rule_definition = set(rule.tags) rule_definition.add(rule.id) if set(rule_definition).isdisjoint(skip_list): matches.extend(rule.getmatches(file)) # some rules can produce matches with tags that are inside our # skip_list, so we need to cleanse the matches matches = [m for m in matches if m.tag not in skip_list] return matches def __repr__(self) -> str: """Return a RulesCollection instance representation.""" return "\n".join( [rule.verbose() for rule in sorted(self.rules, key=lambda x: x.id)] ) def listtags(self) -> str: tag_desc = { "behaviour": "Indicates a bad practice or behavior", "command-shell": "Specific to use of command and shell modules", "core": "Related to internal implementation of the linter", "deprecations": "Indicate use of features that are removed from Ansible", "experimental": "Newly introduced rules, by default triggering only warnings", "formatting": "Related to code-style", "idempotency": "Possible indication that consequent runs would produce different results", "idiom": "Anti-pattern detected, likely to cause undesired behavior", "metadata": "Invalid metadata, likely related to galaxy, collections or roles", "yaml": "External linter which will also produce its own rule codes.", } tags = defaultdict(list) for rule in self.rules: for tag in rule.tags: tags[tag].append(rule.id) result = "# List of tags and rules they cover\n" for tag in sorted(tags): desc = tag_desc.get(tag, None) if desc: result += f"{tag}: # {desc}\n" else: result += f"{tag}:\n" # result += f" rules:\n" for name in tags[tag]: result += f" - {name}\n" return result ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/custom/����������������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0021640�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/rules/custom/__init__.py�����������������������������������������0000664�0000000�0000000�00000000105�14201712607�0023745�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""A placeholder package for putting custom rules under this dir.""" �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/runner.py��������������������������������������������������������0000664�0000000�0000000�00000016534�14201712607�0021070�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Runner implementation.""" import logging import multiprocessing import multiprocessing.pool import os from dataclasses import dataclass from fnmatch import fnmatch from pathlib import Path from typing import TYPE_CHECKING, Any, FrozenSet, Generator, List, Optional, Set, Union import ansiblelint.skip_utils import ansiblelint.utils from ansiblelint._internal.rules import LoadingFailureRule from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable, expand_dirs_in_lintables from ansiblelint.rules.AnsibleSyntaxCheckRule import AnsibleSyntaxCheckRule if TYPE_CHECKING: from argparse import Namespace from ansiblelint.rules import RulesCollection _logger = logging.getLogger(__name__) @dataclass class LintResult: """Class that tracks result of linting.""" matches: List[MatchError] files: Set[Lintable] class Runner: """Runner class performs the linting process.""" # pylint: disable=too-many-arguments def __init__( self, *lintables: Union[Lintable, str], rules: "RulesCollection", tags: FrozenSet[Any] = frozenset(), skip_list: List[str] = [], exclude_paths: List[str] = [], verbosity: int = 0, checked_files: Optional[Set[Lintable]] = None ) -> None: """Initialize a Runner instance.""" self.rules = rules self.lintables: Set[Lintable] = set() # Assure consistent type for item in lintables: if not isinstance(item, Lintable): item = Lintable(item) self.lintables.add(item) # Expand folders (roles) to their components expand_dirs_in_lintables(self.lintables) self.tags = tags self.skip_list = skip_list self._update_exclude_paths(exclude_paths) self.verbosity = verbosity if checked_files is None: checked_files = set() self.checked_files = checked_files def _update_exclude_paths(self, exclude_paths: List[str]) -> None: if exclude_paths: # These will be (potentially) relative paths paths = ansiblelint.file_utils.expand_paths_vars(exclude_paths) # Since ansiblelint.utils.find_children returns absolute paths, # and the list of files we create in `Runner.run` can contain both # relative and absolute paths, we need to cover both bases. self.exclude_paths = paths + [os.path.abspath(p) for p in paths] else: self.exclude_paths = [] def is_excluded(self, file_path: str) -> bool: """Verify if a file path should be excluded.""" # Any will short-circuit as soon as something returns True, but will # be poor performance for the case where the path under question is # not excluded. # Exclusions should be evaluated only using absolute paths in order # to work correctly. if not file_path: return False abs_path = os.path.abspath(file_path) _file_path = Path(file_path) return any( abs_path.startswith(path) or _file_path.match(path) or fnmatch(str(abs_path), path) or fnmatch(str(_file_path), path) for path in self.exclude_paths ) def run(self) -> List[MatchError]: """Execute the linting process.""" files: List[Lintable] = list() matches: List[MatchError] = list() # remove exclusions for lintable in self.lintables.copy(): if self.is_excluded(str(lintable.path.resolve())): _logger.debug("Excluded %s", lintable) self.lintables.remove(lintable) # -- phase 1 : syntax check in parallel -- def worker(lintable: Lintable) -> List[MatchError]: return AnsibleSyntaxCheckRule._get_ansible_syntax_check_matches(lintable) # playbooks: List[Lintable] = [] for lintable in self.lintables: if lintable.kind != "playbook": continue files.append(lintable) pool = multiprocessing.pool.ThreadPool(processes=multiprocessing.cpu_count()) return_list = pool.map(worker, files, chunksize=1) pool.close() pool.join() for data in return_list: matches.extend(data) # -- phase 2 --- if not matches: # do our processing only when ansible syntax check passed in order # to avoid causing runtime exceptions. Our processing is not as # relisient to be able process garbage. matches.extend(self._emit_matches(files)) # remove duplicates from files list files = [value for n, value in enumerate(files) if value not in files[:n]] for file in self.lintables: if file in self.checked_files or not file.kind: continue _logger.debug( "Examining %s of type %s", ansiblelint.file_utils.normpath(file.path), file.kind, ) matches.extend( self.rules.run(file, tags=set(self.tags), skip_list=self.skip_list) ) # update list of checked files self.checked_files.update(self.lintables) # remove any matches made inside excluded files matches = list( filter(lambda match: not self.is_excluded(match.filename), matches) ) return sorted(set(matches)) def _emit_matches(self, files: List[Lintable]) -> Generator[MatchError, None, None]: visited: Set[Lintable] = set() while visited != self.lintables: for lintable in self.lintables - visited: try: for child in ansiblelint.utils.find_children(lintable): if self.is_excluded(str(child.path)): continue self.lintables.add(child) files.append(child) except MatchError as e: if not e.filename: e.filename = str(lintable.path) e.rule = LoadingFailureRule() yield e except AttributeError: yield MatchError( filename=str(lintable.path), rule=LoadingFailureRule() ) visited.add(lintable) def _get_matches(rules: "RulesCollection", options: "Namespace") -> LintResult: lintables = ansiblelint.utils.get_lintables(options=options, args=options.lintables) matches = list() checked_files: Set[Lintable] = set() runner = Runner( *lintables, rules=rules, tags=options.tags, skip_list=options.skip_list, exclude_paths=options.exclude_paths, verbosity=options.verbosity, checked_files=checked_files ) matches.extend(runner.run()) # Assure we do not print duplicates and the order is consistent matches = sorted(set(matches)) # Convert reported filenames into human readable ones, so we hide the # fact we used temporary files when processing input from stdin. for match in matches: for lintable in lintables: if match.filename == lintable.filename: match.filename = lintable.name break return LintResult(matches=matches, files=checked_files) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/skip_utils.py����������������������������������������������������0000664�0000000�0000000�00000017051�14201712607�0021740�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# (c) 2019–2020, Ansible by Red Hat # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """Utils related to inline skipping of rules.""" import logging from functools import lru_cache from itertools import product from typing import TYPE_CHECKING, Any, Generator, List, Optional, Sequence # Module 'ruamel.yaml' does not explicitly export attribute 'YAML'; implicit reexport disabled from ruamel.yaml import YAML # type: ignore from ansiblelint.config import used_old_tags from ansiblelint.constants import RENAMED_TAGS from ansiblelint.file_utils import Lintable if TYPE_CHECKING: from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject _logger = logging.getLogger(__name__) # playbook: Sequence currently expects only instances of one of the two # classes below but we should consider avoiding this chimera. # ruamel.yaml.comments.CommentedSeq # ansible.parsing.yaml.objects.AnsibleSequence def get_rule_skips_from_line(line: str) -> List[str]: """Return list of rule ids skipped via comment on the line of yaml.""" _before_noqa, _noqa_marker, noqa_text = line.partition("# noqa") noqa_text = noqa_text.lstrip(" :") return noqa_text.split() def append_skipped_rules( pyyaml_data: "AnsibleBaseYAMLObject", lintable: Lintable ) -> "AnsibleBaseYAMLObject": """Append 'skipped_rules' to individual tasks or single metadata block. For a file, uses 2nd parser (ruamel.yaml) to pull comments out of yaml subsets, check for '# noqa' skipped rules, and append any skips to the original parser (pyyaml) data relied on by remainder of ansible-lint. :param pyyaml_data: file text parsed via ansible and pyyaml. :param file_text: raw file text. :param file_type: type of file: tasks, handlers or meta. :returns: original pyyaml_data altered with a 'skipped_rules' list added \ to individual tasks, or added to the single metadata block. """ try: yaml_skip = _append_skipped_rules(pyyaml_data, lintable) except RuntimeError: # Notify user of skip error, do not stop, do not change exit code _logger.error("Error trying to append skipped rules", exc_info=True) return pyyaml_data if not yaml_skip: return pyyaml_data return yaml_skip @lru_cache(maxsize=128) def load_data(file_text: str) -> Any: """Parse ``file_text`` as yaml and return parsed structure. This is the main culprit for slow performance, each rule asks for loading yaml again and again ideally the ``maxsize`` on the decorator above MUST be great or equal total number of rules :param file_text: raw text to parse :return: Parsed yaml """ yaml = YAML() return yaml.load(file_text) def _append_skipped_rules( pyyaml_data: "AnsibleBaseYAMLObject", lintable: Lintable ) -> Optional["AnsibleBaseYAMLObject"]: # parse file text using 2nd parser library ruamel_data = load_data(lintable.content) if lintable.kind in ["yaml", "requirements", "vars", "meta", "reno"]: pyyaml_data[0]["skipped_rules"] = _get_rule_skips_from_yaml(ruamel_data) return pyyaml_data # create list of blocks of tasks or nested tasks if lintable.kind in ("tasks", "handlers"): ruamel_task_blocks = ruamel_data pyyaml_task_blocks = pyyaml_data elif lintable.kind == "playbook": try: pyyaml_task_blocks = _get_task_blocks_from_playbook(pyyaml_data) ruamel_task_blocks = _get_task_blocks_from_playbook(ruamel_data) except (AttributeError, TypeError): # TODO(awcrosby): running ansible-lint on any .yml file will # assume it is a playbook, check needs to be added higher in the # call stack, and can remove this except return pyyaml_data else: # For unsupported file types, we return empty skip lists return None # get tasks from blocks of tasks pyyaml_tasks = _get_tasks_from_blocks(pyyaml_task_blocks) ruamel_tasks = _get_tasks_from_blocks(ruamel_task_blocks) # append skipped_rules for each task for ruamel_task, pyyaml_task in zip(ruamel_tasks, pyyaml_tasks): # ignore empty tasks if not pyyaml_task and not ruamel_task: continue if pyyaml_task.get("name") != ruamel_task.get("name"): raise RuntimeError("Error in matching skip comment to a task") pyyaml_task["skipped_rules"] = _get_rule_skips_from_yaml(ruamel_task) return pyyaml_data def _get_task_blocks_from_playbook(playbook: Sequence[Any]) -> List[Any]: """Return parts of playbook that contains tasks, and nested tasks. :param playbook: playbook yaml from yaml parser. :returns: list of task dictionaries. """ PLAYBOOK_TASK_KEYWORDS = [ "tasks", "pre_tasks", "post_tasks", "handlers", ] task_blocks = [] for play, key in product(playbook, PLAYBOOK_TASK_KEYWORDS): task_blocks.extend(play.get(key, [])) return task_blocks def _get_tasks_from_blocks(task_blocks: Sequence[Any]) -> Generator[Any, None, None]: """Get list of tasks from list made of tasks and nested tasks.""" NESTED_TASK_KEYS = [ "block", "always", "rescue", ] def get_nested_tasks(task: Any) -> Generator[Any, None, None]: for k in NESTED_TASK_KEYS: if task and k in task and task[k]: for subtask in task[k]: yield subtask for task in task_blocks: for sub_task in get_nested_tasks(task): yield sub_task yield task def _get_rule_skips_from_yaml(yaml_input: Sequence[Any]) -> Sequence[Any]: """Traverse yaml for comments with rule skips and return list of rules.""" yaml_comment_obj_strs = [] def traverse_yaml(obj: Any) -> None: yaml_comment_obj_strs.append(str(obj.ca.items)) if isinstance(obj, dict): for _, val in obj.items(): if isinstance(val, (dict, list)): traverse_yaml(val) elif isinstance(obj, list): for e in obj: if isinstance(e, (dict, list)): traverse_yaml(e) else: return traverse_yaml(yaml_input) rule_id_list = [] for comment_obj_str in yaml_comment_obj_strs: for line in comment_obj_str.split(r"\n"): rule_id_list.extend(get_rule_skips_from_line(line)) return [normalize_tag(tag) for tag in rule_id_list] def normalize_tag(tag: str) -> str: """Return current name of tag.""" if tag in RENAMED_TAGS: used_old_tags[tag] = RENAMED_TAGS[tag] return RENAMED_TAGS[tag] return tag ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/testing/���������������������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0020651�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/testing/__init__.py����������������������������������������������0000664�0000000�0000000�00000007177�14201712607�0022776�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Test utils for ansible-lint.""" import os import shutil import subprocess import sys import tempfile from typing import TYPE_CHECKING, Any, Dict, List, Optional from ansiblelint.prerun import prepare_environment if TYPE_CHECKING: # https://github.com/PyCQA/pylint/issues/3240 # pylint: disable=unsubscriptable-object CompletedProcess = subprocess.CompletedProcess[Any] else: CompletedProcess = subprocess.CompletedProcess # Emulate command line execution initialization as without it Ansible module # would be loaded with incomplete module/role/collection list. prepare_environment() # pylint: disable=wrong-import-position from ansiblelint.errors import MatchError # noqa: E402 from ansiblelint.rules import RulesCollection # noqa: E402 from ansiblelint.runner import Runner # noqa: E402 class RunFromText: """Use Runner on temp files created from unittest text snippets.""" def __init__(self, collection: RulesCollection) -> None: """Initialize a RunFromText instance with rules collection.""" self.collection = collection def _call_runner(self, path: str) -> List["MatchError"]: runner = Runner(path, rules=self.collection) return runner.run() def run_playbook(self, playbook_text: str) -> List[MatchError]: """Lints received text as a playbook.""" with tempfile.NamedTemporaryFile( mode="w", suffix=".yml", prefix="playbook" ) as fp: fp.write(playbook_text) fp.flush() results = self._call_runner(fp.name) return results def run_role_tasks_main(self, tasks_main_text: str) -> List[MatchError]: """Lints received text as tasks.""" role_path = tempfile.mkdtemp(prefix="role_") tasks_path = os.path.join(role_path, "tasks") os.makedirs(tasks_path) with open(os.path.join(tasks_path, "main.yml"), "w") as fp: fp.write(tasks_main_text) results = self._call_runner(role_path) shutil.rmtree(role_path) return results def run_role_meta_main(self, meta_main_text: str) -> List[MatchError]: """Lints received text as meta.""" role_path = tempfile.mkdtemp(prefix="role_") meta_path = os.path.join(role_path, "meta") os.makedirs(meta_path) with open(os.path.join(meta_path, "main.yml"), "w") as fp: fp.write(meta_main_text) results = self._call_runner(role_path) shutil.rmtree(role_path) return results def run_ansible_lint( *argv: str, cwd: Optional[str] = None, executable: Optional[str] = None, env: Optional[Dict[str, str]] = None ) -> CompletedProcess: """Run ansible-lint on a given path and returns its output.""" if not executable: executable = sys.executable args = [sys.executable, "-m", "ansiblelint", *argv] else: args = [executable, *argv] # It is not safe to pass entire env for testing as other tests would # pollute the env, causing weird behaviors, so we pass only a safe list of # vars. safe_list = [ "HOME", "LANG", "LC_ALL", "LC_CTYPE", "NO_COLOR", "PATH", "PYTHONIOENCODING", "PYTHONPATH", "TERM", ] if env is None: _env = {} else: _env = env for v in safe_list: if v in os.environ and v not in _env: _env[v] = os.environ[v] return subprocess.run( args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False, # needed when command is a list check=False, cwd=cwd, env=_env, universal_newlines=True, ) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/testing/fixtures.py����������������������������������������������0000664�0000000�0000000�00000004737�14201712607�0023107�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""PyTest Fixtures. They should not be imported, instead add code below to your root conftest.py file: pytest_plugins = ['ansiblelint.testing'] """ import copy import os from argparse import Namespace from pathlib import Path from typing import Iterator, Union import pytest from _pytest.fixtures import SubRequest from ansiblelint.config import options # noqa: F401 from ansiblelint.constants import DEFAULT_RULESDIR from ansiblelint.file_utils import Lintable from ansiblelint.rules import RulesCollection from ansiblelint.runner import Runner from ansiblelint.testing import RunFromText @pytest.fixture def play_file_path(tmp_path: Path) -> str: """Fixture to return a playbook path.""" p = tmp_path / "playbook.yml" return str(p) @pytest.fixture def runner( play_file_path: Union[Lintable, str], default_rules_collection: RulesCollection ) -> Runner: """Fixture to return a Runner() instance.""" return Runner(play_file_path, rules=default_rules_collection) @pytest.fixture def default_rules_collection() -> RulesCollection: """Return default rule collection.""" assert os.path.isdir(DEFAULT_RULESDIR) # For testing we want to manually enable opt-in rules options.enable_list = ["no-same-owner"] return RulesCollection(rulesdirs=[DEFAULT_RULESDIR], options=options) @pytest.fixture def default_text_runner(default_rules_collection: RulesCollection) -> RunFromText: """Return RunFromText instance for the default set of collections.""" return RunFromText(default_rules_collection) @pytest.fixture def rule_runner(request: SubRequest, config_options: Namespace) -> RunFromText: """Return runner for a specific rule class.""" rule_class = request.param config_options.enable_list.append(rule_class().id) collection = RulesCollection(options=config_options) collection.register(rule_class()) return RunFromText(collection) @pytest.fixture def config_options() -> Iterator[Namespace]: """Return configuration options that will be restored after testrun.""" global options # pylint: disable=global-statement original_options = copy.deepcopy(options) yield options options = original_options @pytest.fixture def _play_files(tmp_path: Path, request: SubRequest) -> None: if request.param is None: return for play_file in request.param: print(play_file.name) p = tmp_path / play_file.name os.makedirs(os.path.dirname(p), exist_ok=True) p.write_text(play_file.content) ���������������������������������ansible-lint-5.4.0/src/ansiblelint/text.py����������������������������������������������������������0000664�0000000�0000000�00000001603�14201712607�0020532�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Text utils.""" import re from typing import Union def strip_ansi_escape(data: Union[str, bytes]) -> str: """Remove all ANSI escapes from string or bytes. If bytes is passed instead of string, it will be converted to string using UTF-8. """ if isinstance(data, bytes): data = data.decode("utf-8") return re.sub(r"\x1b[^m]*m", "", data) def toidentifier(text: str) -> str: """Convert unsafe chars to ones allowed in variables.""" result = re.sub(r"[\s-]+", "_", text) if not result.isidentifier: raise RuntimeError( "Unable to convert role name '%s' to valid variable name." % text ) return result # https://www.python.org/dev/peps/pep-0616/ def removeprefix(self: str, prefix: str) -> str: """Remove prefix from string.""" if self.startswith(prefix): return self[len(prefix) :] return self[:] �����������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/utils.py���������������������������������������������������������0000664�0000000�0000000�00000072450�14201712607�0020716�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """Generic utility helpers.""" import contextlib import inspect import logging import os import warnings from argparse import Namespace from collections.abc import ItemsView from functools import lru_cache from pathlib import Path from typing import ( Any, Callable, Dict, Generator, List, Optional, Sequence, Tuple, Union, ) import yaml from ansible import constants from ansible.errors import AnsibleError, AnsibleParserError from ansible.parsing.dataloader import DataLoader from ansible.parsing.mod_args import ModuleArgsParser from ansible.parsing.splitter import split_args from ansible.parsing.yaml.constructor import AnsibleConstructor, AnsibleMapping from ansible.parsing.yaml.loader import AnsibleLoader from ansible.parsing.yaml.objects import AnsibleBaseYAMLObject, AnsibleSequence from ansible.plugins.loader import add_all_plugin_dirs from ansible.template import Templar try: from ansible.module_utils.parsing.convert_bool import boolean except ImportError: try: from ansible.utils.boolean import boolean except ImportError: try: from ansible.utils import boolean except ImportError: boolean = constants.mk_boolean from yaml.composer import Composer from yaml.representer import RepresenterError from ansiblelint._internal.rules import ( AnsibleParserErrorRule, LoadingFailureRule, RuntimeErrorRule, ) from ansiblelint.config import options from ansiblelint.constants import FileType from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable, discover_lintables from ansiblelint.text import removeprefix # ansible-lint doesn't need/want to know about encrypted secrets, so we pass a # string as the password to enable such yaml files to be opened and parsed # successfully. DEFAULT_VAULT_PASSWORD = "x" PLAYBOOK_DIR = os.environ.get("ANSIBLE_PLAYBOOK_DIR", None) _logger = logging.getLogger(__name__) def parse_yaml_from_file(filepath: str) -> AnsibleBaseYAMLObject: """Extract a decrypted YAML object from file.""" dl = DataLoader() if hasattr(dl, "set_vault_password"): dl.set_vault_password(DEFAULT_VAULT_PASSWORD) return dl.load_from_file(filepath) def path_dwim(basedir: str, given: str) -> str: """Convert a given path do-what-I-mean style.""" dl = DataLoader() dl.set_basedir(basedir) return str(dl.path_dwim(given)) def ansible_templar(basedir: str, templatevars: Any) -> Templar: """Create an Ansible Templar using templatevars.""" # `basedir` is the directory containing the lintable file. # Therefore, for tasks in a role, `basedir` has the form # `roles/some_role/tasks`. On the other hand, the search path # is `roles/some_role/{files,templates}`. As a result, the # `tasks` part in the basedir should be stripped stripped. if os.path.basename(basedir) == "tasks": basedir = os.path.dirname(basedir) dl = DataLoader() dl.set_basedir(basedir) templar = Templar(dl, variables=templatevars) return templar def ansible_template( basedir: str, varname: Any, templatevars: Any, **kwargs: Any ) -> Any: """Render a templated string.""" templar = ansible_templar(basedir=basedir, templatevars=templatevars) return templar.template(varname, **kwargs) LINE_NUMBER_KEY = "__line__" FILENAME_KEY = "__file__" VALID_KEYS = [ "name", "action", "when", "async", "poll", "notify", "first_available_file", "include", "include_tasks", "import_tasks", "import_playbook", "tags", "register", "ignore_errors", "delegate_to", "local_action", "transport", "remote_user", "sudo", "sudo_user", "sudo_pass", "when", "connection", "environment", "args", "any_errors_fatal", "changed_when", "failed_when", "check_mode", "delay", "retries", "until", "su", "su_user", "su_pass", "no_log", "run_once", "become", "become_user", "become_method", FILENAME_KEY, ] BLOCK_NAME_TO_ACTION_TYPE_MAP = { "tasks": "task", "handlers": "handler", "pre_tasks": "task", "post_tasks": "task", "block": "meta", "rescue": "meta", "always": "meta", } def tokenize(line: str) -> Tuple[str, List[str], Dict[str, str]]: """Parse a string task invocation.""" tokens = line.lstrip().split(" ") if tokens[0] == "-": tokens = tokens[1:] if tokens[0] == "action:" or tokens[0] == "local_action:": tokens = tokens[1:] command = tokens[0].replace(":", "") args = list() kwargs = dict() nonkvfound = False for arg in tokens[1:]: if "=" in arg and not nonkvfound: kv = arg.split("=", 1) kwargs[kv[0]] = kv[1] else: nonkvfound = True args.append(arg) return (command, args, kwargs) def _playbook_items(pb_data: AnsibleBaseYAMLObject) -> ItemsView: # type: ignore if isinstance(pb_data, dict): return pb_data.items() if not pb_data: return [] # type: ignore # "if play" prevents failure if the play sequence contains None, # which is weird but currently allowed by Ansible # https://github.com/ansible-community/ansible-lint/issues/849 return [item for play in pb_data if play for item in play.items()] # type: ignore def _set_collections_basedir(basedir: str) -> None: # Sets the playbook directory as playbook_paths for the collection loader try: # Ansible 2.10+ # noqa: # pylint:disable=cyclic-import,import-outside-toplevel from ansible.utils.collection_loader import AnsibleCollectionConfig AnsibleCollectionConfig.playbook_paths = basedir except ImportError: # Ansible 2.8 or 2.9 # noqa: # pylint:disable=cyclic-import,import-outside-toplevel from ansible.utils.collection_loader import set_collection_playbook_paths set_collection_playbook_paths(basedir) def find_children(lintable: Lintable) -> List[Lintable]: # noqa: C901 """Traverse children of a single file or folder.""" if not lintable.path.exists(): return [] playbook_dir = str(lintable.path.parent) _set_collections_basedir(playbook_dir or os.path.abspath(".")) add_all_plugin_dirs(playbook_dir or ".") if lintable.kind == "role": playbook_ds = AnsibleMapping({"roles": [{"role": str(lintable.path)}]}) elif lintable.kind not in ("playbook", "tasks"): return [] else: try: playbook_ds = parse_yaml_from_file(str(lintable.path)) except AnsibleError as e: raise SystemExit(str(e)) results = [] basedir = os.path.dirname(str(lintable.path)) # playbook_ds can be an AnsibleUnicode string, which we consider invalid if isinstance(playbook_ds, str): raise MatchError(filename=str(lintable.path), rule=LoadingFailureRule()) for item in _playbook_items(playbook_ds): # if lintable.kind not in ["playbook"]: # continue for child in play_children(basedir, item, lintable.kind, playbook_dir): # We avoid processing parametrized children path_str = str(child.path) if "$" in path_str or "{{" in path_str: continue # Repair incorrect paths obtained when old syntax was used, like: # - include: simpletask.yml tags=nginx valid_tokens = list() for token in split_args(path_str): if "=" in token: break valid_tokens.append(token) path = " ".join(valid_tokens) if path != path_str: child.path = Path(path) child.name = child.path.name results.append(child) return results def template( basedir: str, value: Any, variables: Any, fail_on_undefined: bool = False, **kwargs: str, ) -> Any: """Attempt rendering a value with known vars.""" try: value = ansible_template( os.path.abspath(basedir), value, variables, **dict(kwargs, fail_on_undefined=fail_on_undefined), ) # Hack to skip the following exception when using to_json filter on a variable. # I guess the filter doesn't like empty vars... except (AnsibleError, ValueError, RepresenterError): # templating failed, so just keep value as is. pass return value def play_children( basedir: str, item: Tuple[str, Any], parent_type: FileType, playbook_dir: str ) -> List[Lintable]: """Flatten the traversed play tasks.""" delegate_map: Dict[str, Callable[[str, Any, Any, FileType], List[Lintable]]] = { "tasks": _taskshandlers_children, "pre_tasks": _taskshandlers_children, "post_tasks": _taskshandlers_children, "block": _taskshandlers_children, "include": _include_children, "import_playbook": _include_children, "roles": _roles_children, "dependencies": _roles_children, "handlers": _taskshandlers_children, "include_tasks": _include_children, "import_tasks": _include_children, } (k, v) = item add_all_plugin_dirs(os.path.abspath(basedir)) if k in delegate_map: if v: v = template( os.path.abspath(basedir), v, dict(playbook_dir=PLAYBOOK_DIR or os.path.abspath(basedir)), fail_on_undefined=False, ) return delegate_map[k](basedir, k, v, parent_type) return [] def _include_children( basedir: str, k: str, v: Any, parent_type: FileType ) -> List[Lintable]: # handle special case include_tasks: name=filename.yml if k == "include_tasks" and isinstance(v, dict) and "file" in v: v = v["file"] # handle include: filename.yml tags=blah # pylint: disable=unused-variable (command, args, kwargs) = tokenize("{0}: {1}".format(k, v)) result = path_dwim(basedir, args[0]) if not os.path.exists(result): result = path_dwim(os.path.join(os.path.dirname(basedir)), v) return [Lintable(result, kind=parent_type)] def _taskshandlers_children( basedir: str, k: str, v: Union[None, Any], parent_type: FileType ) -> List[Lintable]: results: List[Lintable] = [] if v is None: raise MatchError( message="A malformed block was encountered while loading a block.", rule=RuntimeErrorRule(), ) for th in v: # ignore empty tasks, `-` if not th: continue with contextlib.suppress(LookupError): children = _get_task_handler_children_for_tasks_or_playbooks( th, basedir, k, parent_type, ) results.append(children) continue if ( "include_role" in th or "import_role" in th ): # lgtm [py/unreachable-statement] th = normalize_task_v2(th) _validate_task_handler_action_for_role(th["action"]) results.extend( _roles_children( basedir, k, [th["action"].get("name")], parent_type, main=th["action"].get("tasks_from", "main"), ) ) continue if "block" not in th: continue results.extend(_taskshandlers_children(basedir, k, th["block"], parent_type)) if "rescue" in th: results.extend( _taskshandlers_children(basedir, k, th["rescue"], parent_type) ) if "always" in th: results.extend( _taskshandlers_children(basedir, k, th["always"], parent_type) ) return results def _get_task_handler_children_for_tasks_or_playbooks( task_handler: Dict[str, Any], basedir: str, k: Any, parent_type: FileType, ) -> Lintable: """Try to get children of taskhandler for include/import tasks/playbooks.""" child_type = k if parent_type == "playbook" else parent_type task_include_keys = "include", "include_tasks", "import_playbook", "import_tasks" for task_handler_key in task_include_keys: with contextlib.suppress(KeyError): # ignore empty tasks if not task_handler: continue # import pdb; pdb.set_trace() return Lintable( path_dwim(basedir, task_handler[task_handler_key]), kind=child_type ) raise LookupError( f'The node contains none of: {", ".join(task_include_keys)}', ) def _validate_task_handler_action_for_role(th_action: Dict[str, Any]) -> None: """Verify that the task handler action is valid for role include.""" module = th_action["__ansible_module__"] if "name" not in th_action: raise MatchError(message=f"Failed to find required 'name' key in {module!s}") if not isinstance(th_action["name"], str): raise MatchError( message=f"Value assigned to 'name' key on '{module!s}' is not a string.", ) def _roles_children( basedir: str, k: str, v: Sequence[Any], parent_type: FileType, main: str = "main" ) -> List[Lintable]: results: List[Lintable] = [] for role in v: if isinstance(role, dict): if "role" in role or "name" in role: if "tags" not in role or "skip_ansible_lint" not in role["tags"]: results.extend( _look_for_role_files( basedir, role.get("role", role.get("name")), main=main ) ) elif k != "dependencies": raise SystemExit( 'role dict {0} does not contain a "role" ' 'or "name" key'.format(role) ) else: results.extend(_look_for_role_files(basedir, role, main=main)) return results def _rolepath(basedir: str, role: str) -> Optional[str]: role_path = None possible_paths = [ # if included from a playbook path_dwim(basedir, os.path.join("roles", role)), path_dwim(basedir, role), # if included from roles/[role]/meta/main.yml path_dwim(basedir, os.path.join("..", "..", "..", "roles", role)), path_dwim(basedir, os.path.join("..", "..", role)), # if checking a role in the current directory path_dwim(basedir, os.path.join("..", role)), ] if constants.DEFAULT_ROLES_PATH: search_locations = constants.DEFAULT_ROLES_PATH if isinstance(search_locations, str): search_locations = search_locations.split(os.pathsep) for loc in search_locations: loc = os.path.expanduser(loc) possible_paths.append(path_dwim(loc, role)) possible_paths.append(path_dwim(basedir, "")) for path_option in possible_paths: if os.path.isdir(path_option): role_path = path_option break if role_path: add_all_plugin_dirs(role_path) return role_path def _look_for_role_files( basedir: str, role: str, main: Optional[str] = "main" ) -> List[Lintable]: role_path = _rolepath(basedir, role) if not role_path: return [] results = [] for kind in ["tasks", "meta", "handlers", "vars", "defaults"]: current_path = os.path.join(role_path, kind) for folder, _, files in os.walk(current_path): for file in files: file_ignorecase = file.lower() if file_ignorecase.endswith((".yml", ".yaml")): thpath = os.path.join(folder, file) results.append(Lintable(thpath)) return results def _kv_to_dict(v: str) -> Dict[str, Any]: (command, args, kwargs) = tokenize(v) return dict(__ansible_module__=command, __ansible_arguments__=args, **kwargs) def _sanitize_task(task: Dict[str, Any]) -> Dict[str, Any]: """Return a stripped-off task structure compatible with new Ansible. This helper takes a copy of the incoming task and drops any internally used keys from it. """ result = task.copy() # task is an AnsibleMapping which inherits from OrderedDict, so we need # to use `del` to remove unwanted keys. for k in ["skipped_rules", FILENAME_KEY, LINE_NUMBER_KEY]: if k in result: del result[k] return result def normalize_task_v2(task: Dict[str, Any]) -> Dict[str, Any]: """Ensure tasks have a normalized action key and strings are converted to python objects.""" result = dict() sanitized_task = _sanitize_task(task) mod_arg_parser = ModuleArgsParser(sanitized_task) try: action, arguments, result["delegate_to"] = mod_arg_parser.parse( skip_action_validation=options.skip_action_validation ) except AnsibleParserError as e: raise MatchError( rule=AnsibleParserErrorRule(), message=e.message, filename=task.get(FILENAME_KEY, "Unknown"), linenumber=task.get(LINE_NUMBER_KEY, 0), ) # denormalize shell -> command conversion if "_uses_shell" in arguments: action = "shell" del arguments["_uses_shell"] for (k, v) in list(task.items()): if k in ("action", "local_action", "args", "delegate_to") or k == action: # we don't want to re-assign these values, which were # determined by the ModuleArgsParser() above continue result[k] = v if not isinstance(action, str): raise RuntimeError("Task actions can only be strings, got %s" % action) action_unnormalized = action # convert builtin fqn calls to short forms because most rules know only # about short calls but in the future we may switch the normalization to do # the opposite. Mainly we currently consider normalized the module listing # used by `ansible-doc -t module -l 2>/dev/null` action = removeprefix(action, "ansible.builtin.") result["action"] = dict( __ansible_module__=action, __ansible_module_original__=action_unnormalized ) if "_raw_params" in arguments: result["action"]["__ansible_arguments__"] = arguments["_raw_params"].split(" ") del arguments["_raw_params"] else: result["action"]["__ansible_arguments__"] = list() if "argv" in arguments and not result["action"]["__ansible_arguments__"]: result["action"]["__ansible_arguments__"] = arguments["argv"] del arguments["argv"] result["action"].update(arguments) return result def normalize_task(task: Dict[str, Any], filename: str) -> Dict[str, Any]: """Unify task-like object structures.""" ansible_action_type = task.get("__ansible_action_type__", "task") if "__ansible_action_type__" in task: del task["__ansible_action_type__"] task = normalize_task_v2(task) task[FILENAME_KEY] = filename task["__ansible_action_type__"] = ansible_action_type return task def task_to_str(task: Dict[str, Any]) -> str: """Make a string identifier for the given task.""" name = task.get("name") if name: return str(name) action = task.get("action") if isinstance(action, str) or not isinstance(action, dict): return str(action) args = " ".join( [ "{0}={1}".format(k, v) for (k, v) in action.items() if k not in [ "__ansible_module__", "__ansible_module_original__", "__ansible_arguments__", LINE_NUMBER_KEY, FILENAME_KEY, ] ] ) for item in action.get("__ansible_arguments__", []): args += f" {item}" return "{0} {1}".format(action["__ansible_module__"], args) def extract_from_list( blocks: AnsibleBaseYAMLObject, candidates: List[str], recursive: bool = False ) -> List[Any]: """Get action tasks from block structures.""" results = list() for block in blocks: for candidate in candidates: if isinstance(block, dict) and candidate in block: if isinstance(block[candidate], list): subresults = add_action_type(block[candidate], candidate) if recursive: subresults.extend( extract_from_list(subresults, candidates, recursive) ) results.extend(subresults) elif block[candidate] is not None: raise RuntimeError( "Key '%s' defined, but bad value: '%s'" % (candidate, str(block[candidate])) ) return results def add_action_type(actions: AnsibleBaseYAMLObject, action_type: str) -> List[Any]: """Add action markers to task objects.""" results = list() for action in actions: # ignore empty task if not action: continue action["__ansible_action_type__"] = BLOCK_NAME_TO_ACTION_TYPE_MAP[action_type] results.append(action) return results def get_action_tasks(yaml: AnsibleBaseYAMLObject, file: Lintable) -> List[Any]: """Get a flattened list of action tasks from the file.""" tasks = list() if file.kind in ["tasks", "handlers"]: tasks = add_action_type(yaml, file.kind) else: tasks.extend( extract_from_list(yaml, ["tasks", "handlers", "pre_tasks", "post_tasks"]) ) # Add sub-elements of block/rescue/always to tasks list tasks.extend( extract_from_list(tasks, ["block", "rescue", "always"], recursive=True) ) # Remove block/rescue/always elements from tasks list block_rescue_always = ("block", "rescue", "always") tasks[:] = [ task for task in tasks if all(k not in task for k in block_rescue_always) ] return [ task for task in tasks if set( ["include", "include_tasks", "import_playbook", "import_tasks"] ).isdisjoint(task.keys()) ] @lru_cache(maxsize=128) def parse_yaml_linenumbers(lintable: Lintable) -> AnsibleBaseYAMLObject: """Parse yaml as ansible.utils.parse_yaml but with linenumbers. The line numbers are stored in each node's LINE_NUMBER_KEY key. """ def compose_node(parent: yaml.nodes.Node, index: int) -> yaml.nodes.Node: # the line number where the previous token has ended (plus empty lines) line = loader.line node = Composer.compose_node(loader, parent, index) if not isinstance(node, yaml.nodes.Node): raise RuntimeError("Unexpected yaml data.") setattr(node, "__line__", line + 1) return node def construct_mapping( node: AnsibleBaseYAMLObject, deep: bool = False ) -> AnsibleMapping: mapping = AnsibleConstructor.construct_mapping(loader, node, deep=deep) if hasattr(node, "__line__"): mapping[LINE_NUMBER_KEY] = node.__line__ else: mapping[LINE_NUMBER_KEY] = mapping._line_number mapping[FILENAME_KEY] = lintable.path return mapping try: kwargs = {} if "vault_password" in inspect.getfullargspec(AnsibleLoader.__init__).args: kwargs["vault_password"] = DEFAULT_VAULT_PASSWORD loader = AnsibleLoader(lintable.content, **kwargs) loader.compose_node = compose_node loader.construct_mapping = construct_mapping data = loader.get_single_data() except (yaml.parser.ParserError, yaml.scanner.ScannerError) as e: logging.exception(e) raise SystemExit("Failed to parse YAML in %s: %s" % (lintable.path, str(e))) return data def get_first_cmd_arg(task: Dict[str, Any]) -> Any: """Extract the first arg from a cmd task.""" try: if "cmd" in task["action"]: first_cmd_arg = task["action"]["cmd"].split()[0] else: first_cmd_arg = task["action"]["__ansible_arguments__"][0] except IndexError: return None return first_cmd_arg def get_second_cmd_arg(task: Dict[str, Any]) -> Any: """Extract the second arg from a cmd task.""" try: if "cmd" in task["action"]: second_cmd_arg = task["action"]["cmd"].split()[1] else: second_cmd_arg = task["action"]["__ansible_arguments__"][1] except IndexError: return None return second_cmd_arg def is_playbook(filename: str) -> bool: """ Check if the file is a playbook. Given a filename, it should return true if it looks like a playbook. The function is not supposed to raise exceptions. """ # we assume is a playbook if we loaded a sequence of dictionaries where # at least one of these keys is present: playbooks_keys = { "gather_facts", "hosts", "import_playbook", "post_tasks", "pre_tasks", "roles", "tasks", } # makes it work with Path objects by converting them to strings if not isinstance(filename, str): filename = str(filename) try: f = parse_yaml_from_file(filename) except Exception as e: _logger.warning( "Failed to load %s with %s, assuming is not a playbook.", filename, e ) else: if ( isinstance(f, AnsibleSequence) and hasattr(next(iter(f), {}), "keys") and playbooks_keys.intersection(next(iter(f), {}).keys()) ): return True return False # pylint: disable=too-many-statements def get_lintables( options: Namespace = Namespace(), args: Optional[List[str]] = None ) -> List[Lintable]: """Detect files and directories that are lintable.""" lintables: List[Lintable] = [] # passing args bypass auto-detection mode if args: for arg in args: lintable = Lintable(arg) if lintable.kind in ("yaml", None): _logger.warning( "Overriding detected file kind '%s' with 'playbook' " "for given positional argument: %s", lintable.kind, arg, ) lintable = Lintable(arg, kind="playbook") lintables.append(lintable) else: for filename in discover_lintables(options): p = Path(filename) # skip exclusions try: for file_path in options.exclude_paths: if str(p.resolve()).startswith(str(file_path)): raise FileNotFoundError( f"File {file_path} matched exclusion entry: {p}" ) except FileNotFoundError as e: _logger.debug("Ignored %s due to: %s", p, e) continue lintables.append(Lintable(p)) # stage 2: guess roles from current lintables, as there is no unique # file that must be present in any kind of role. _extend_with_roles(lintables) return lintables def _extend_with_roles(lintables: List[Lintable]) -> None: """Detect roles among lintables and adds them to the list.""" for lintable in lintables: parts = lintable.path.parent.parts if "roles" in parts: role = lintable.path while role.parent.name != "roles" and role.name: role = role.parent if role.exists and not role.is_file(): lintable = Lintable(role, kind="role") if lintable not in lintables: _logger.debug("Added role: %s", lintable) lintables.append(lintable) def convert_to_boolean(value: Any) -> bool: """Use Ansible to convert something to a boolean.""" return bool(boolean(value)) def nested_items( data: Union[Dict[Any, Any], List[Any]], parent: str = "" ) -> Generator[Tuple[Any, Any, str], None, None]: """Iterate a nested data structure.""" warnings.warn( "Call to deprecated function ansiblelint.utils.nested_items. " "Use ansiblelint.yaml_utils.nested_items_path instead.", category=DeprecationWarning, stacklevel=2, ) if isinstance(data, dict): for k, v in data.items(): yield k, v, parent for k, v, p in nested_items(v, k): yield k, v, p if isinstance(data, list): for item in data: yield "list-item", item, parent for k, v, p in nested_items(item): yield k, v, p ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/version.py�������������������������������������������������������0000664�0000000�0000000�00000000342�14201712607�0021232�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Ansible-lint version information.""" try: import pkg_resources except ImportError: pass try: __version__ = pkg_resources.get_distribution("ansible-lint").version except Exception: __version__ = "unknown" ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/src/ansiblelint/yaml_utils.py����������������������������������������������������0000664�0000000�0000000�00000015004�14201712607�0021730�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Utility helpers to simplify working with yaml-based data.""" import functools from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union, cast import ansiblelint.skip_utils from ansiblelint.errors import MatchError from ansiblelint.file_utils import Lintable from ansiblelint.utils import get_action_tasks, normalize_task, parse_yaml_linenumbers def iter_tasks_in_file( lintable: Lintable, rule_id: str ) -> Iterator[Tuple[Dict[str, Any], Dict[str, Any], bool, Optional[MatchError]]]: """Iterate over tasks in file. This yields a 4-tuple of raw_task, normalized_task, skipped, and error. raw_task: When looping through the tasks in the file, each "raw_task" is minimally processed to include these special keys: __line__, __file__, skipped_rules. normalized_task: When each raw_task is "normalized", action shorthand (strings) get parsed by ansible into python objects and the action key gets normalized. If the task should be skipped (skipped is True) or normalizing it fails (error is not None) then this is just the raw_task instead of a normalized copy. skipped: Whether or not the task should be skipped according to tags or skipped_rules. error: This is normally None. It will be a MatchError when the raw_task cannot be normalized due to an AnsibleParserError. :param lintable: The playbook or tasks/handlers yaml file to get tasks from :param rule_id: The current rule id to allow calculating skipped. :return: raw_task, normalized_task, skipped, error """ data = parse_yaml_linenumbers(lintable) if not data: return data = ansiblelint.skip_utils.append_skipped_rules(data, lintable) raw_tasks = get_action_tasks(data, lintable) for raw_task in raw_tasks: err: Optional[MatchError] = None # An empty `tags` block causes `None` to be returned if # the `or []` is not present - `task.get('tags', [])` # does not suffice. skipped_in_task_tag = "skip_ansible_lint" in (raw_task.get("tags") or []) skipped_in_yaml_comment = rule_id in raw_task.get("skipped_rules", ()) skipped = skipped_in_task_tag or skipped_in_yaml_comment if skipped: yield raw_task, raw_task, skipped, err continue try: normalized_task = normalize_task(raw_task, str(lintable.path)) except MatchError as err: # normalize_task converts AnsibleParserError to MatchError yield raw_task, raw_task, skipped, err return yield raw_task, normalized_task, skipped, err def nested_items_path( data_collection: Union[Dict[Any, Any], List[Any]], ) -> Iterator[Tuple[Any, Any, List[Union[str, int]]]]: """Iterate a nested data structure, yielding key/index, value, and parent_path. This is a recursive function that calls itself for each nested layer of data. Each iteration yields: 1. the current item's dictionary key or list index, 2. the current item's value, and 3. the path to the current item from the outermost data structure. For dicts, the yielded (1) key and (2) value are what ``dict.items()`` yields. For lists, the yielded (1) index and (2) value are what ``enumerate()`` yields. The final component, the parent path, is a list of dict keys and list indexes. The parent path can be helpful in providing error messages that indicate precisely which part of a yaml file (or other data structure) needs to be fixed. For example, given this playbook: .. code-block:: yaml - name: a play tasks: - name: a task debug: msg: foobar Here's the first and last yielded items: .. code-block:: python >>> playbook=[{"name": "a play", "tasks": [{"name": "a task", "debug": {"msg": "foobar"}}]}] >>> next( nested_items_path( playbook ) ) (0, {'name': 'a play', 'tasks': [{'name': 'a task', 'debug': {'msg': 'foobar'}}]}, []) >>> list( nested_items_path( playbook ) )[-1] ('msg', 'foobar', [0, 'tasks', 0, 'debug']) Note that, for outermost data structure, the parent path is ``[]`` because you do not need to descend into any nested dicts or lists to find the indicated key and value. If a rule were designed to prohibit "foobar" debug messages, it could use the parent path to provide a path to the problematic ``msg``. It might use a jq-style path in its error message: "the error is at ``.[0].tasks[0].debug.msg``". Or if a utility could automatically fix issues, it could use the path to descend to the parent object using something like this: .. code-block:: python target = data for segment in parent_path: target = target[segment] :param data_collection: The nested data (dicts or lists). :returns: each iteration yields the key (of the parent dict) or the index (lists) """ yield from _nested_items_path(data_collection=data_collection, parent_path=[]) def _nested_items_path( data_collection: Union[Dict[Any, Any], List[Any]], parent_path: List[Union[str, int]], ) -> Iterator[Tuple[Any, Any, List[Union[str, int]]]]: """Iterate through data_collection (internal implementation of nested_items_path). This is a separate function because callers of nested_items_path should not be using the parent_path param which is used in recursive _nested_items_path calls to build up the path to the parent object of the current key/index, value. """ # we have to cast each convert_to_tuples assignment or mypy complains # that both assignments (for dict and list) do not have the same type convert_to_tuples_type = Callable[[], Iterator[Tuple[Union[str, int], Any]]] if isinstance(data_collection, dict): convert_data_collection_to_tuples = cast( convert_to_tuples_type, functools.partial(data_collection.items) ) elif isinstance(data_collection, list): convert_data_collection_to_tuples = cast( convert_to_tuples_type, functools.partial(enumerate, data_collection) ) else: raise TypeError( f"Expected a dict or a list but got {data_collection!r} " f"of type '{type(data_collection)}'" ) for key, value in convert_data_collection_to_tuples(): yield key, value, parent_path if isinstance(value, (dict, list)): yield from _nested_items_path( data_collection=value, parent_path=parent_path + [key] ) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/����������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0015060�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestAnsibleLintRule.py������������������������������������������������������0000664�0000000�0000000�00000001741�14201712607�0021331�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from typing import Any, Dict import pytest from _pytest.monkeypatch import MonkeyPatch from ansiblelint.config import options from ansiblelint.rules import AnsibleLintRule def test_unjinja() -> None: """Verify that unjinja understands nested mustache.""" text = "{{ a }} {% b %} {# try to confuse parsing inside a comment { {{}} } #}" output = "JINJA_EXPRESSION JINJA_STATEMENT JINJA_COMMENT" assert AnsibleLintRule.unjinja(text) == output @pytest.mark.parametrize("rule_config", (dict(), dict(foo=True, bar=1))) def test_rule_config(rule_config: Dict[str, Any], monkeypatch: MonkeyPatch) -> None: """Check that a rule config is inherited from options.""" rule_id = "rule-0" monkeypatch.setattr(AnsibleLintRule, "id", rule_id) monkeypatch.setitem(options.rules, rule_id, rule_config) rule = AnsibleLintRule() assert set(rule.rule_config.items()) == set(rule_config.items()) assert all(rule.get_config(k) == v for k, v in rule_config.items()) �������������������������������ansible-lint-5.4.0/test/TestAnsibleSyntax.py��������������������������������������������������������0000664�0000000�0000000�00000000733�14201712607�0021061�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Test Ansible Syntax. This module contains tests that validate that linter does not produce errors when encountering what counts as valid Ansible syntax. """ from ansiblelint.testing import RunFromText PB_WITH_NULL_TASKS = """\ - hosts: all tasks: """ def test_null_tasks(default_text_runner: RunFromText) -> None: """Assure we do not fail when encountering null tasks.""" results = default_text_runner.run_playbook(PB_WITH_NULL_TASKS) assert not results �������������������������������������ansible-lint-5.4.0/test/TestBaseFormatter.py��������������������������������������������������������0000664�0000000�0000000�00000003116�14201712607�0021031�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8; -*- from pathlib import Path from typing import Any, Union import pytest from ansiblelint.formatters import BaseFormatter @pytest.mark.parametrize( ("base_dir", "relative_path"), ( (None, True), ("/whatever", False), (Path("/whatever"), False), ), ) @pytest.mark.parametrize("path", ("/whatever/string", Path("/whatever/string"))) def test_base_formatter_when_base_dir( base_dir: Any, relative_path: bool, path: str ) -> None: """Check that base formatter accepts relative pathlib and str.""" # Given base_formatter = BaseFormatter(base_dir, relative_path) # type: ignore # When output_path = base_formatter._format_path(path) # Then assert isinstance(output_path, str) assert base_formatter._base_dir is None or isinstance(base_formatter._base_dir, str) assert output_path == str(path) @pytest.mark.parametrize( "base_dir", ( Path("/whatever"), "/whatever", ), ) @pytest.mark.parametrize("path", ("/whatever/string", Path("/whatever/string"))) def test_base_formatter_when_base_dir_is_given_and_relative_is_true( path: Union[str, Path], base_dir: Union[str, Path] ) -> None: """Check that the base formatter equally accepts pathlib and str.""" # Given base_formatter = BaseFormatter(base_dir, True) # type: ignore # When output_path = base_formatter._format_path(path) # Then assert isinstance(output_path, str) assert isinstance(base_formatter._base_dir, str) assert output_path == Path(path).name # vim: et:sw=4:syntax=python:ts=4: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestBecomeUserWithoutBecome.py����������������������������������������������0000664�0000000�0000000�00000001621�14201712607�0023022�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.BecomeUserWithoutBecomeRule import BecomeUserWithoutBecomeRule from ansiblelint.runner import Runner class TestBecomeUserWithoutBecome(unittest.TestCase): collection = RulesCollection() def setUp(self) -> None: self.collection.register(BecomeUserWithoutBecomeRule()) def test_file_positive(self) -> None: success = "examples/playbooks/become-user-without-become-success.yml" good_runner = Runner(success, rules=self.collection) assert [] == good_runner.run() def test_file_negative(self) -> None: failure = "examples/playbooks/become-user-without-become-failure.yml" bad_runner = Runner(failure, rules=self.collection) errs = bad_runner.run() assert len(errs) == 3 ���������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestCliRolePaths.py���������������������������������������������������������0000664�0000000�0000000�00000013553�14201712607�0020632�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import os import unittest from pathlib import Path from typing import Dict import pytest from ansiblelint.testing import run_ansible_lint from ansiblelint.text import strip_ansi_escape class TestCliRolePaths(unittest.TestCase): def setUp(self) -> None: self.local_test_dir = os.path.realpath( os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "examples") ) def test_run_single_role_path_no_trailing_slash_module(self) -> None: cwd = self.local_test_dir role_path = "roles/test-role" result = run_ansible_lint(role_path, cwd=cwd) assert "Use shell only when shell functionality is required" in result.stdout def test_run_single_role_path_no_trailing_slash_script(self) -> None: cwd = self.local_test_dir role_path = "roles/test-role" result = run_ansible_lint(role_path, cwd=cwd, executable="ansible-lint") assert "Use shell only when shell functionality is required" in result.stdout def test_run_single_role_path_with_trailing_slash(self) -> None: cwd = self.local_test_dir role_path = "roles/test-role/" result = run_ansible_lint(role_path, cwd=cwd) assert "Use shell only when shell functionality is required" in result.stdout def test_run_multiple_role_path_no_trailing_slash(self) -> None: cwd = self.local_test_dir role_path = "roles/test-role" result = run_ansible_lint(role_path, cwd=cwd) assert "Use shell only when shell functionality is required" in result.stdout def test_run_multiple_role_path_with_trailing_slash(self) -> None: cwd = self.local_test_dir role_path = "roles/test-role/" result = run_ansible_lint(role_path, cwd=cwd) assert "Use shell only when shell functionality is required" in result.stdout def test_run_inside_role_dir(self) -> None: cwd = os.path.join(self.local_test_dir, "roles/test-role/") role_path = "." result = run_ansible_lint(role_path, cwd=cwd) assert "Use shell only when shell functionality is required" in result.stdout def test_run_role_three_dir_deep(self) -> None: cwd = self.local_test_dir role_path = "testproject/roles/test-role" result = run_ansible_lint(role_path, cwd=cwd) assert "Use shell only when shell functionality is required" in result.stdout def test_run_playbook(self) -> None: """Call ansible-lint the way molecule does.""" cwd = os.path.abspath(os.path.join(self.local_test_dir, "roles/test-role")) lintable = "molecule/default/include-import-role.yml" role_path = str(Path(cwd).parent.resolve()) env = os.environ.copy() env["ANSIBLE_ROLES_PATH"] = role_path result = run_ansible_lint(lintable, cwd=cwd, env=env) assert "Use shell only when shell functionality is required" in result.stdout def test_run_role_name_invalid(self) -> None: cwd = self.local_test_dir role_path = "roles/invalid-name" result = run_ansible_lint(role_path, cwd=cwd) assert "role-name: Role name invalid-name does not match" in strip_ansi_escape( result.stdout ) def test_run_role_name_with_prefix(self) -> None: cwd = self.local_test_dir role_path = "roles/ansible-role-foo" result = run_ansible_lint("-v", role_path, cwd=cwd) assert len(result.stdout) == 0 assert ( "Added ANSIBLE_ROLES_PATH=~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles:roles" in result.stderr ) assert result.returncode == 0 def test_run_role_name_from_meta(self) -> None: cwd = self.local_test_dir role_path = "roles/valid-due-to-meta" result = run_ansible_lint("-v", role_path, cwd=cwd) assert len(result.stdout) == 0 assert ( "Added ANSIBLE_ROLES_PATH=~/.ansible/roles:/usr/share/ansible/roles:/etc/ansible/roles:roles" in result.stderr ) assert result.returncode == 0 def test_run_invalid_role_name_from_meta(self) -> None: cwd = self.local_test_dir role_path = "roles/invalid_due_to_meta" result = run_ansible_lint(role_path, cwd=cwd) assert ( "role-name: Role name invalid-due-to-meta does not match" in strip_ansi_escape(result.stdout) ) def test_run_single_role_path_with_roles_path_env(self) -> None: """Test for role name collision with ANSIBLE_ROLES_PATH. Test if ansible-lint chooses the role in the current directory when the role specified as parameter exists in the current directory and the ANSIBLE_ROLES_PATH. """ cwd = self.local_test_dir role_path = "roles/test-role" env = os.environ.copy() env["ANSIBLE_ROLES_PATH"] = os.path.realpath( os.path.join(cwd, "../examples/roles") ) result = run_ansible_lint(role_path, cwd=cwd, env=env) assert "Use shell only when shell functionality is required" in result.stdout @pytest.mark.parametrize( ("result", "env"), ((True, {"GITHUB_ACTIONS": "true", "GITHUB_WORKFLOW": "foo"}), (False, None)), ids=("on", "off"), ) def test_run_playbook_github(result: bool, env: Dict[str, str]) -> None: """Call ansible-lint simulating GitHub Actions environment.""" cwd = str(Path(__file__).parent.parent.resolve()) role_path = "examples/playbooks/example.yml" if env is None: env = {} env["PATH"] = os.environ["PATH"] result_gh = run_ansible_lint(role_path, cwd=cwd, env=env) expected = ( "::warning file=examples/playbooks/example.yml,line=44,severity=VERY_LOW::package-latest " "Package installs should not use latest" ) assert (expected in result_gh.stdout) is result �����������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestCodeclimateJSONFormatter.py���������������������������������������������0000664�0000000�0000000�00000007540�14201712607�0023067�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Test the codeclimate JSON formatter.""" import json import pathlib import subprocess import sys from typing import List, Optional import pytest from ansiblelint.errors import MatchError from ansiblelint.formatters import CodeclimateJSONFormatter from ansiblelint.rules import AnsibleLintRule class TestCodeclimateJSONFormatter: """Unit test for CodeclimateJSONFormatter.""" rule = AnsibleLintRule() matches: List[MatchError] = [] formatter: Optional[CodeclimateJSONFormatter] = None def setup_class(self) -> None: """Set up few MatchError objects.""" self.rule = AnsibleLintRule() self.rule.id = "TCF0001" self.rule.severity = "VERY_HIGH" self.matches = [] self.matches.append( MatchError( message="message", linenumber=1, details="hello", filename="filename.yml", rule=self.rule, ) ) self.matches.append( MatchError( message="message", linenumber=2, details="hello", filename="filename.yml", rule=self.rule, ) ) self.formatter = CodeclimateJSONFormatter( pathlib.Path.cwd(), display_relative_path=True ) def test_format_list(self) -> None: """Test if the return value is a string.""" assert isinstance(self.formatter, CodeclimateJSONFormatter) assert isinstance(self.formatter.format_result(self.matches), str) def test_result_is_json(self) -> None: """Test if returned string value is a JSON.""" assert isinstance(self.formatter, CodeclimateJSONFormatter) json.loads(self.formatter.format_result(self.matches)) def test_single_match(self) -> None: """Test negative case. Only lists are allowed. Otherwise a RuntimeError will be raised.""" assert isinstance(self.formatter, CodeclimateJSONFormatter) with pytest.raises(RuntimeError): self.formatter.format_result(self.matches[0]) # type: ignore def test_result_is_list(self) -> None: """Test if the return JSON contains a list with a length of 2.""" assert isinstance(self.formatter, CodeclimateJSONFormatter) result = json.loads(self.formatter.format_result(self.matches)) assert len(result) == 2 def test_validate_codeclimate_schema(self) -> None: """Test if the returned JSON is a valid codeclimate report.""" assert isinstance(self.formatter, CodeclimateJSONFormatter) result = json.loads(self.formatter.format_result(self.matches)) single_match = result[0] assert "type" in single_match assert single_match["type"] == "issue" assert "check_name" in single_match assert "categories" in single_match assert isinstance(single_match["categories"], list) assert "severity" in single_match assert single_match["severity"] == "blocker" assert "description" in single_match assert "fingerprint" in single_match assert "location" in single_match assert "path" in single_match["location"] assert single_match["location"]["path"] == self.matches[0].filename assert "lines" in single_match["location"] assert single_match["location"]["lines"]["begin"] == self.matches[0].linenumber def test_code_climate_parsable_ignored() -> None: """Test that -p option does not alter codeclimate format.""" cmd = [ sys.executable, "-m", "ansiblelint", "-v", "-p", ] file = "examples/playbooks/empty_playbook.yml" result = subprocess.run([*cmd, file], check=False) result2 = subprocess.run([*cmd, "-p", file], check=False) assert result.returncode == result2.returncode assert result.stdout == result2.stdout ����������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestCommandHasChangesCheck.py�����������������������������������������������0000664�0000000�0000000�00000001611�14201712607�0022532�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.CommandHasChangesCheckRule import CommandHasChangesCheckRule from ansiblelint.runner import Runner class TestCommandHasChangesCheck(unittest.TestCase): collection = RulesCollection() def setUp(self) -> None: self.collection.register(CommandHasChangesCheckRule()) def test_command_changes_positive(self) -> None: success = "examples/playbooks/command-check-success.yml" good_runner = Runner(success, rules=self.collection) assert [] == good_runner.run() def test_command_changes_negative(self) -> None: failure = "examples/playbooks/command-check-failure.yml" bad_runner = Runner(failure, rules=self.collection) errs = bad_runner.run() assert len(errs) == 2 �����������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestCommandLineInvocationSameAsConfig.py������������������������������������0000664�0000000�0000000�00000011443�14201712607�0024735�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import os from pathlib import Path from typing import List import pytest from _pytest.monkeypatch import MonkeyPatch from ansiblelint import cli @pytest.fixture def base_arguments() -> List[str]: """Define reusable base arguments for tests in current module.""" return ["../test/skiptasks.yml"] @pytest.mark.parametrize( ("args", "config"), ( (["-p"], "test/fixtures/parseable.yml"), (["-q"], "test/fixtures/quiet.yml"), (["-r", "test/fixtures/rules/"], "test/fixtures/rulesdir.yml"), (["-R", "-r", "test/fixtures/rules/"], "test/fixtures/rulesdir-defaults.yml"), (["-t", "skip_ansible_lint"], "test/fixtures/tags.yml"), (["-v"], "test/fixtures/verbosity.yml"), (["-x", "bad_tag"], "test/fixtures/skip-tags.yml"), (["--exclude", "test/"], "test/fixtures/exclude-paths.yml"), (["--show-relpath"], "test/fixtures/show-abspath.yml"), ([], "test/fixtures/show-relpath.yml"), ), ) def test_ensure_config_are_equal( base_arguments: List[str], args: List[str], config: str ) -> None: """Check equality of the CLI options to config files.""" command = base_arguments + args cli_parser = cli.get_cli_parser() options = cli_parser.parse_args(command) file_config = cli.load_config(config) for key, val in file_config.items(): # config_file does not make sense in file_config if key == "config_file": continue if key in {"exclude_paths", "rulesdir"}: val = [Path(p) for p in val] assert val == getattr(options, key) def test_config_can_be_overridden(base_arguments: List[str]) -> None: """Check that config can be overridden from CLI.""" no_override = cli.get_config(base_arguments + ["-t", "bad_tag"]) overridden = cli.get_config( base_arguments + ["-t", "bad_tag", "-c", "test/fixtures/tags.yml"] ) assert no_override.tags + ["skip_ansible_lint"] == overridden.tags def test_different_config_file(base_arguments: List[str]) -> None: """Ensures an alternate config_file can be used.""" diff_config = cli.get_config( base_arguments + ["-c", "test/fixtures/ansible-config.yml"] ) no_config = cli.get_config(base_arguments + ["-v"]) assert diff_config.verbosity == no_config.verbosity def test_expand_path_user_and_vars_config_file(base_arguments: List[str]) -> None: """Ensure user and vars are expanded when specified as exclude_paths.""" config1 = cli.get_config( base_arguments + ["-c", "test/fixtures/exclude-paths-with-expands.yml"] ) config2 = cli.get_config( base_arguments + ["--exclude", "~/.ansible/roles", "--exclude", "$HOME/.ansible/roles"] ) assert str(config1.exclude_paths[0]) == os.path.expanduser("~/.ansible/roles") assert str(config2.exclude_paths[0]) == os.path.expanduser("~/.ansible/roles") assert str(config1.exclude_paths[1]) == os.path.expandvars("$HOME/.ansible/roles") assert str(config2.exclude_paths[1]) == os.path.expandvars("$HOME/.ansible/roles") def test_path_from_config_do_not_depend_on_cwd( monkeypatch: MonkeyPatch, ) -> None: # Issue 572 """Check that config-provided paths are decoupled from CWD.""" config1 = cli.load_config("test/fixtures/config-with-relative-path.yml") monkeypatch.chdir("test") config2 = cli.load_config("fixtures/config-with-relative-path.yml") assert config1["exclude_paths"].sort() == config2["exclude_paths"].sort() def test_path_from_cli_depend_on_cwd( base_arguments: List[str], monkeypatch: MonkeyPatch ) -> None: """Check that CLI-provided paths are relative to CWD.""" # Issue 572 arguments = base_arguments + [ "--exclude", "test/fixtures/config-with-relative-path.yml", ] options1 = cli.get_cli_parser().parse_args(arguments) assert "test/test" not in str(options1.exclude_paths[0]) test_dir = "test" monkeypatch.chdir(test_dir) options2 = cli.get_cli_parser().parse_args(arguments) assert "test/test" in str(options2.exclude_paths[0]) @pytest.mark.parametrize( "config_file", ( pytest.param("test/fixtures/ansible-config-invalid.yml", id="invalid"), pytest.param("/dev/null/ansible-config-missing.yml", id="missing"), ), ) def test_config_failure(base_arguments: List[str], config_file: str) -> None: """Ensures specific config files produce error code 2.""" with pytest.raises(SystemExit, match="^2$"): cli.get_config(base_arguments + ["-c", config_file]) def test_extra_vars_loaded(base_arguments: List[str]) -> None: """Ensure ``extra_vars`` option is loaded from file config.""" config = cli.get_config( base_arguments + ["-c", "test/fixtures/config-with-extra-vars.yml"] ) assert config.extra_vars == {"foo": "bar", "knights_favorite_word": "NI"} �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestComparisonToLiteralBool.py����������������������������������������������0000664�0000000�0000000�00000003473�14201712607�0023047�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.ComparisonToLiteralBoolRule import ComparisonToLiteralBoolRule from ansiblelint.testing import RunFromText PASS_WHEN = """ - name: example task debug: msg: test when: my_var - name: another example task debug: msg: test when: - 1 + 1 == 2 - true """ PASS_WHEN_NOT_FALSE = """ - name: example task debug: msg: test when: not my_var """ PASS_WHEN_NOT_NULL = """ - name: example task debug: msg: test when: my_var not None """ FAIL_LITERAL_TRUE = """ - name: example task debug: msg: test when: my_var == True """ FAIL_LITERAL_FALSE = """ - name: example task debug: msg: test when: my_var == false - name: another example task debug: msg: test when: - my_var == false """ class TestComparisonToLiteralBoolRule(unittest.TestCase): collection = RulesCollection() collection.register(ComparisonToLiteralBoolRule()) def setUp(self) -> None: self.runner = RunFromText(self.collection) def test_when(self) -> None: results = self.runner.run_role_tasks_main(PASS_WHEN) assert len(results) == 0 def test_when_not_false(self) -> None: results = self.runner.run_role_tasks_main(PASS_WHEN_NOT_FALSE) assert len(results) == 0 def test_when_not_null(self) -> None: results = self.runner.run_role_tasks_main(PASS_WHEN_NOT_NULL) assert len(results) == 0 def test_literal_true(self) -> None: results = self.runner.run_role_tasks_main(FAIL_LITERAL_TRUE) assert len(results) == 1 def test_literal_false(self) -> None: results = self.runner.run_role_tasks_main(FAIL_LITERAL_FALSE) assert len(results) == 2 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestDependenciesInMeta.py���������������������������������������������������0000664�0000000�0000000�00000000641�14201712607�0021757�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from ansiblelint.rules import RulesCollection from ansiblelint.runner import Runner def test_external_dependency_is_ok(default_rules_collection: RulesCollection) -> None: """Check that external dep in role meta is not a violation.""" playbook_path = "examples/roles/dependency_in_meta/meta/main.yml" good_runner = Runner(playbook_path, rules=default_rules_collection) assert [] == good_runner.run() �����������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestDeprecatedModule.py�����������������������������������������������������0000664�0000000�0000000�00000001721�14201712607�0021501�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.DeprecatedModuleRule import DeprecatedModuleRule from ansiblelint.testing import RunFromText MODULE_DEPRECATED = """ - name: task example docker: debug: test """ class TestDeprecatedModuleRule(unittest.TestCase): collection = RulesCollection() collection.register(DeprecatedModuleRule()) def setUp(self) -> None: self.runner = RunFromText(self.collection) def test_module_deprecated(self) -> None: results = self.runner.run_role_tasks_main(MODULE_DEPRECATED) assert len(results) == 1 # based on version and blend of ansible being used, we may # get a missing module, so we future proof the test assert ( "couldn't resolve module" not in results[0].message or "Deprecated module" not in results[0].message ) �����������������������������������������������ansible-lint-5.4.0/test/TestEnvVarsInCommand.py�����������������������������������������������������0000664�0000000�0000000�00000004064�14201712607�0021450�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.EnvVarsInCommandRule import EnvVarsInCommandRule from ansiblelint.testing import RunFromText SUCCESS_PLAY_TASKS = """ - hosts: localhost tasks: - name: actual use of environment shell: echo $HELLO environment: HELLO: hello - name: use some key-value pairs command: chdir=/tmp creates=/tmp/bobbins warn=no touch bobbins - name: commands can have flags command: abc --xyz=def blah - name: commands can have equals in them command: echo "===========" - name: commands with cmd command: cmd: echo "-------" - name: command with stdin (ansible > 2.4) command: /bin/cat args: stdin: "Hello, world!" - name: use argv to send the command as a list command: argv: - /bin/echo - Hello - World - name: another use of argv command: args: argv: - echo - testing - name: environment variable with shell shell: HELLO=hello echo $HELLO - name: command with stdin_add_newline (ansible > 2.8) command: /bin/cat args: stdin: "Hello, world!" stdin_add_newline: false - name: command with strip_empty_ends (ansible > 2.8) command: echo args: strip_empty_ends: false """ FAIL_PLAY_TASKS = """ - hosts: localhost tasks: - name: environment variable with command command: HELLO=hello echo $HELLO - name: typo some stuff command: cerates=/tmp/blah warn=no touch /tmp/blah """ class TestEnvVarsInCommand(unittest.TestCase): collection = RulesCollection() collection.register(EnvVarsInCommandRule()) def setUp(self) -> None: self.runner = RunFromText(self.collection) def test_success(self) -> None: results = self.runner.run_playbook(SUCCESS_PLAY_TASKS) assert len(results) == 0 def test_fail(self) -> None: results = self.runner.run_playbook(FAIL_PLAY_TASKS) assert len(results) == 2 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestExamples.py�������������������������������������������������������������0000664�0000000�0000000�00000005111�14201712607�0020046�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Assure samples produced desire outcomes.""" import os from typing import Generator import pytest from _pytest.fixtures import FixtureRequest from ansiblelint.rules import RulesCollection from ansiblelint.runner import Runner from ansiblelint.testing import run_ansible_lint @pytest.fixture def _change_into_examples_dir(request: FixtureRequest) -> Generator[None, None, None]: os.chdir("examples") yield os.chdir("..") @pytest.mark.usefixtures("_change_into_examples_dir") def test_example(default_rules_collection: RulesCollection) -> None: """example.yml is expected to have 16 match errors inside.""" result = Runner("playbooks/example.yml", rules=default_rules_collection).run() assert len(result) == 16 @pytest.mark.parametrize( ("filename", "line", "column"), ( pytest.param( "examples/playbooks/syntax-error-string.yml", 1, 1, id="syntax-error-string" ), pytest.param("examples/playbooks/syntax-error.yml", 2, 3, id="syntax-error"), ), ) def test_example_syntax_error( default_rules_collection: RulesCollection, filename: str, line: int, column: int ) -> None: """Validates that loading valid YAML string produce error.""" result = Runner(filename, rules=default_rules_collection).run() assert len(result) == 1 assert result[0].rule.id == "syntax-check" # This also ensures that line and column numbers start at 1, so they # match what editors will show (or output from other linters) assert result[0].linenumber == line assert result[0].column == column def test_example_custom_module(default_rules_collection: RulesCollection) -> None: """custom_module.yml is expected to pass.""" result = Runner( "examples/playbooks/custom_module.yml", rules=default_rules_collection ).run() assert len(result) == 0 def test_full_vault(default_rules_collection: RulesCollection) -> None: """custom_module.yml is expected to pass.""" result = Runner( "examples/playbooks/vars/not_decryptable.yml", rules=default_rules_collection ).run() assert len(result) == 0 def test_custom_kinds() -> None: """Check if user defined kinds are used.""" result = run_ansible_lint("-vv", "--offline", "examples/other/") assert result.returncode == 0 # .yaml-too is not a recognized extension and unless is manually defined # in our .ansible-lint config, the test would not identify it as yaml file. assert "Examining examples/other/some.yaml-too of type yaml" in result.stderr assert "Examining examples/other/some.j2.yaml of type jinja2" in result.stderr �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestFilePathEvaluation.py���������������������������������������������������0000664�0000000�0000000�00000006340�14201712607�0022021�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Testing file path evaluation when using import_tasks / include_tasks.""" import textwrap from pathlib import Path from typing import Dict import pytest from ansiblelint.rules import RulesCollection from ansiblelint.runner import Runner LAYOUT_IMPORTS: Dict[str, str] = { "main.yml": textwrap.dedent( """\ --- - hosts: target gather_facts: false tasks: - name: from main import task 1 import_tasks: tasks/task_1.yml """ ), "tasks/task_1.yml": textwrap.dedent( """\ --- - name: from task 1 import task 2 import_tasks: tasks/task_2.yml """ ), "tasks/task_2.yml": textwrap.dedent( """\ --- - name: from task 2 import subtask 1 import_tasks: tasks/subtasks/subtask_1.yml """ ), "tasks/subtasks/subtask_1.yml": textwrap.dedent( """\ --- - name: from subtask 1 import subtask 2 import_tasks: tasks/subtasks/subtask_2.yml """ ), "tasks/subtasks/subtask_2.yml": textwrap.dedent( """\ --- - name: from subtask 2 do something debug: msg: | Something... """ ), } LAYOUT_INCLUDES: Dict[str, str] = { "main.yml": textwrap.dedent( """\ --- - hosts: target gather_facts: false tasks: - name: from main import task 1 include_tasks: tasks/task_1.yml """ ), "tasks/task_1.yml": textwrap.dedent( """\ --- - name: from task 1 import task 2 include_tasks: tasks/task_2.yml """ ), "tasks/task_2.yml": textwrap.dedent( """\ --- - name: from task 2 import subtask 1 include_tasks: tasks/subtasks/subtask_1.yml """ ), "tasks/subtasks/subtask_1.yml": textwrap.dedent( """\ --- - name: from subtask 1 import subtask 2 include_tasks: tasks/subtasks/subtask_2.yml """ ), "tasks/subtasks/subtask_2.yml": textwrap.dedent( """\ --- - name: from subtask 2 do something debug: msg: | Something... """ ), } @pytest.mark.parametrize( "ansible_project_layout", ( pytest.param(LAYOUT_IMPORTS, id="using only import_tasks"), pytest.param(LAYOUT_INCLUDES, id="using only include_tasks"), ), ) @pytest.mark.xfail( reason="https://github.com/ansible-community/ansible-lint/issues/1446" ) def test_file_path_evaluation( tmp_path: Path, default_rules_collection: RulesCollection, ansible_project_layout: Dict[str, str], ) -> None: """Test file path evaluation when using import_tasks / include_tasks in the project. Usage of import_tasks / include_tasks may introduce false positive load-failure due to incorrect file path evaluation. """ for file_path, file_content in ansible_project_layout.items(): full_path = tmp_path / file_path full_path.parent.mkdir(parents=True, exist_ok=True) full_path.write_text(file_content) result = Runner(str(tmp_path), rules=default_rules_collection).run() assert not result ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestFormatter.py������������������������������������������������������������0000664�0000000�0000000�00000004576�14201712607�0020251�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2016 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import pathlib import unittest from ansiblelint.errors import MatchError from ansiblelint.formatters import Formatter from ansiblelint.rules import AnsibleLintRule class TestFormatter(unittest.TestCase): def setUp(self) -> None: self.rule = AnsibleLintRule() self.rule.id = "TCF0001" self.formatter = Formatter(pathlib.Path.cwd(), display_relative_path=True) def test_format_coloured_string(self) -> None: match = MatchError( message="message", linenumber=1, details="hello", filename="filename.yml", rule=self.rule, ) self.formatter.format(match) def test_unicode_format_string(self) -> None: match = MatchError( message="\U0001f427", linenumber=1, details="hello", filename="filename.yml", rule=self.rule, ) self.formatter.format(match) def test_dict_format_line(self) -> None: match = MatchError( message="xyz", linenumber=1, details={"hello": "world"}, # type: ignore filename="filename.yml", rule=self.rule, ) self.formatter.format(match) ����������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestImportIncludeRole.py����������������������������������������������������0000664�0000000�0000000�00000004556�14201712607�0021704�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from pathlib import Path from typing import List import pytest from _pytest.fixtures import SubRequest from ansiblelint.rules import RulesCollection from ansiblelint.runner import Runner ROLE_TASKS_MAIN = """ - name: shell instead of command shell: echo hello world """ ROLE_TASKS_WORLD = """ - command: echo this is a task without a name """ PLAY_IMPORT_ROLE = """ - hosts: all tasks: - import_role: name: test-role """ PLAY_IMPORT_ROLE_INLINE = """ - hosts: all tasks: - import_role: name=test-role """ PLAY_INCLUDE_ROLE = """ - hosts: all tasks: - include_role: name: test-role tasks_from: world """ PLAY_INCLUDE_ROLE_INLINE = """ - hosts: all tasks: - include_role: name=test-role tasks_from=world """ @pytest.fixture def playbook_path(request: SubRequest, tmp_path: Path) -> str: """Create a reusable per-test role skeleton.""" playbook_text = request.param role_tasks_dir = tmp_path / "test-role" / "tasks" role_tasks_dir.mkdir(parents=True) (role_tasks_dir / "main.yml").write_text(ROLE_TASKS_MAIN) (role_tasks_dir / "world.yml").write_text(ROLE_TASKS_WORLD) play_path = tmp_path / "playbook.yml" play_path.write_text(playbook_text) return str(play_path) @pytest.mark.parametrize( ("playbook_path", "messages"), ( pytest.param( PLAY_IMPORT_ROLE, ["only when shell functionality is required"], id="IMPORT_ROLE", ), pytest.param( PLAY_IMPORT_ROLE_INLINE, ["only when shell functionality is require"], id="IMPORT_ROLE_INLINE", ), pytest.param( PLAY_INCLUDE_ROLE, ["only when shell functionality is require", "All tasks should be named"], id="INCLUDE_ROLE", ), pytest.param( PLAY_INCLUDE_ROLE_INLINE, ["only when shell functionality is require", "All tasks should be named"], id="INCLUDE_ROLE_INLINE", ), ), indirect=("playbook_path",), ) def test_import_role2( default_rules_collection: RulesCollection, playbook_path: str, messages: List[str] ) -> None: """Test that include_role digs deeper than import_role.""" runner = Runner(playbook_path, rules=default_rules_collection) results = runner.run() for message in messages: assert message in str(results) ��������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestImportPlaybook.py�������������������������������������������������������0000664�0000000�0000000�00000001353�14201712607�0021247�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Test ability to import playbooks.""" from ansiblelint.rules import RulesCollection from ansiblelint.runner import Runner def test_task_hook_import_playbook(default_rules_collection: RulesCollection) -> None: """Assures import_playbook includes are recognized.""" playbook_path = "examples/playbooks/playbook-parent.yml" runner = Runner(playbook_path, rules=default_rules_collection) results = runner.run() results_text = str(results) assert len(runner.lintables) == 2 assert len(results) == 2 # Assures we detected the issues from imported playbook assert "Commands should not change things" in results_text assert "unnamed-task" in results_text assert "All tasks should be named" in results_text �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestImportWithMalformed.py��������������������������������������������������0000664�0000000�0000000�00000002103�14201712607�0022223�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import pytest from ansiblelint.file_utils import Lintable from ansiblelint.runner import Runner IMPORT_TASKS_MAIN = Lintable( "import-tasks-main.yml", """\ - oops this is invalid """, ) IMPORT_SHELL_PIP = Lintable( "import-tasks-main.yml", """\ - shell: pip changed: false """, ) PLAY_IMPORT_TASKS = Lintable( "playbook.yml", """\ - hosts: all tasks: - import_tasks: import-tasks-main.yml """, ) @pytest.mark.parametrize( "_play_files", ( pytest.param([IMPORT_SHELL_PIP, PLAY_IMPORT_TASKS], id="Import shell w/ pip"), pytest.param( [IMPORT_TASKS_MAIN, PLAY_IMPORT_TASKS], id="import_tasks w/ malformed import", ), ), indirect=["_play_files"], ) @pytest.mark.usefixtures("_play_files") def test_import_tasks_with_malformed_import(runner: Runner) -> None: """Test that malformed tasks/imports don't fail parsing.""" results = runner.run() passed = False for result in results: if result.rule.id == "syntax-check": passed = True assert passed, results �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestIncludeMissFileWithRole.py����������������������������������������������0000664�0000000�0000000�00000005103�14201712607�0022766�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import pytest from _pytest.logging import LogCaptureFixture from ansiblelint.file_utils import Lintable from ansiblelint.runner import Runner PLAY_IN_THE_PLACE = Lintable( "playbook.yml", """\ - hosts: all roles: - include_in_the_place """, ) PLAY_RELATIVE = Lintable( "playbook.yml", """\ - hosts: all roles: - include_relative """, ) PLAY_MISS_INCLUDE = Lintable( "playbook.yml", """\ - hosts: all roles: - include_miss """, ) PLAY_ROLE_INCLUDED_IN_THE_PLACE = Lintable( "roles/include_in_the_place/tasks/main.yml", """\ --- - include_tasks: included_file.yml """, ) PLAY_ROLE_INCLUDED_RELATIVE = Lintable( "roles/include_relative/tasks/main.yml", """\ --- - include_tasks: tasks/included_file.yml """, ) PLAY_ROLE_INCLUDED_MISS = Lintable( "roles/include_miss/tasks/main.yml", """\ --- - include_tasks: tasks/noexist_file.yml """, kind="tasks", ) PLAY_INCLUDED_IN_THE_PLACE = Lintable( "roles/include_in_the_place/tasks/included_file.yml", """\ - debug: msg: 'was found & included' """, kind="tasks", ) PLAY_INCLUDED_RELATIVE = Lintable( "roles/include_relative/tasks/included_file.yml", """\ - debug: msg: 'was found & included' """, ) @pytest.mark.parametrize( "_play_files", ( pytest.param( [PLAY_MISS_INCLUDE, PLAY_ROLE_INCLUDED_MISS], id="no exist file include" ), ), indirect=["_play_files"], ) @pytest.mark.usefixtures("_play_files") def test_cases_warning_message(runner: Runner, caplog: LogCaptureFixture) -> None: """Test that including a non-existing file produces an error.""" result = runner.run() assert len(result) == 1 assert "No such file or directory" in result[0].message @pytest.mark.parametrize( "_play_files", ( pytest.param( [ PLAY_IN_THE_PLACE, PLAY_ROLE_INCLUDED_IN_THE_PLACE, PLAY_INCLUDED_IN_THE_PLACE, ], id="in the place include", ), pytest.param( [PLAY_RELATIVE, PLAY_ROLE_INCLUDED_RELATIVE, PLAY_INCLUDED_RELATIVE], id="relative include", ), ), indirect=["_play_files"], ) @pytest.mark.usefixtures("_play_files") def test_cases_that_do_not_report(runner: Runner, caplog: LogCaptureFixture) -> None: """Test that relative inclusions are properly followed.""" runner.run() noexist_message_count = 0 for record in caplog.records: if "Couldn't open" in str(record): noexist_message_count += 1 assert noexist_message_count == 0 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestLineTooLong.py����������������������������������������������������������0000664�0000000�0000000�00000001455�14201712607�0020470�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.YamllintRule import YamllintRule from ansiblelint.testing import RunFromText LONG_LINE = """\ - name: task example debug: msg: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua tempor incididunt ut labore et dolore' """ # noqa 501 class TestLineTooLongRule(unittest.TestCase): collection = RulesCollection() collection.register(YamllintRule()) def setUp(self) -> None: self.runner = RunFromText(self.collection) def test_long_line(self) -> None: results = self.runner.run_role_tasks_main(LONG_LINE) assert len(results) == 1 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestLintRule.py�������������������������������������������������������������0000664�0000000�0000000�00000003471�14201712607�0020035�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import pytest from ansiblelint.file_utils import Lintable from .rules import EMatcherRule, RawTaskRule @pytest.fixture def lintable() -> Lintable: """Return a playbook Lintable for use in this file's tests.""" return Lintable("examples/playbooks/ematcher-rule.yml", kind="playbook") def test_rule_matching(lintable: Lintable) -> None: """Test rule.matchlines() on a plyabook.""" ematcher = EMatcherRule.EMatcherRule() matches = ematcher.matchlines(lintable) assert len(matches) == 3 def test_raw_rule_matching(lintable: Lintable) -> None: """Test rule.matchlines() on a plyabook.""" ematcher = RawTaskRule.RawTaskRule() matches = ematcher.matchtasks(lintable) assert len(matches) == 1 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestLocalContent.py���������������������������������������������������������0000664�0000000�0000000�00000000733�14201712607�0020662�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Test playbooks with local content.""" from ansiblelint.rules import RulesCollection from ansiblelint.runner import Runner def test_local_collection(default_rules_collection: RulesCollection) -> None: """Assures local collections are found.""" playbook_path = "test/local-content/test-collection.yml" runner = Runner(playbook_path, rules=default_rules_collection) results = runner.run() assert len(runner.lintables) == 1 assert len(results) == 0 �������������������������������������ansible-lint-5.4.0/test/TestMatchError.py�����������������������������������������������������������0000664�0000000�0000000�00000014443�14201712607�0020346�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Tests for MatchError.""" import operator from typing import Any, Callable import pytest from ansiblelint.errors import MatchError from ansiblelint.rules.BecomeUserWithoutBecomeRule import BecomeUserWithoutBecomeRule from ansiblelint.rules.CommandHasChangesCheckRule import CommandHasChangesCheckRule class DummyTestObject: """A dummy object for equality tests.""" def __repr__(self) -> str: """Return a dummy object representation for parmetrize.""" return "{self.__class__.__name__}()".format(self=self) def __eq__(self, other: object) -> bool: """Report the equality check failure with any object.""" return False def __ne__(self, other: object) -> bool: """Report the confirmation of inequality with any object.""" return True class DummySentinelTestObject: """A dummy object for equality protocol tests with sentinel.""" def __eq__(self, other: object) -> bool: """Return sentinel as result of equality check w/ anything.""" return "EQ_SENTINEL" # type: ignore def __ne__(self, other: object) -> bool: """Return sentinel as result of inequality check w/ anything.""" return "NE_SENTINEL" # type: ignore def __lt__(self, other: object) -> bool: """Return sentinel as result of less than check w/ anything.""" return "LT_SENTINEL" # type: ignore def __gt__(self, other: object) -> bool: """Return sentinel as result of greater than chk w/ anything.""" return "GT_SENTINEL" # type: ignore @pytest.mark.parametrize( ("left_match_error", "right_match_error"), ( (MatchError("foo"), MatchError("foo")), (MatchError("a", details="foo"), MatchError("a", details="foo")), ), ) def test_matcherror_compare( left_match_error: MatchError, right_match_error: MatchError ) -> None: """Check that MatchError instances with similar attrs are equivalent.""" assert left_match_error == right_match_error def test_matcherror_invalid() -> None: """Ensure that MatchError requires message or rule.""" expected_err = ( r"^MatchError\(\) missing a required argument: one of 'message' or 'rule'$" ) with pytest.raises(TypeError, match=expected_err): MatchError() @pytest.mark.parametrize( ("left_match_error", "right_match_error"), ( # sorting by message (MatchError("z"), MatchError("a")), # filenames takes priority in sorting (MatchError("a", filename="b"), MatchError("a", filename="a")), # rule id partial-become > rule id no-changed-when ( MatchError(rule=BecomeUserWithoutBecomeRule()), MatchError(rule=CommandHasChangesCheckRule()), ), # details are taken into account (MatchError("a", details="foo"), MatchError("a", details="bar")), # columns are taken into account (MatchError("a", column=3), MatchError("a", column=1)), (MatchError("a", column=3), MatchError("a")), ), ) class TestMatchErrorCompare: def test_match_error_less_than( self, left_match_error: MatchError, right_match_error: MatchError ) -> None: """Check 'less than' protocol implementation in MatchError.""" assert right_match_error < left_match_error def test_match_error_greater_than( self, left_match_error: MatchError, right_match_error: MatchError ) -> None: """Check 'greater than' protocol implementation in MatchError.""" assert left_match_error > right_match_error def test_match_error_not_equal( self, left_match_error: MatchError, right_match_error: MatchError ) -> None: """Check 'not equals' protocol implementation in MatchError.""" assert left_match_error != right_match_error @pytest.mark.parametrize( "other", ( None, "foo", 42, Exception("foo"), ), ids=repr, ) @pytest.mark.parametrize( ("operation", "operator_char"), ( pytest.param(operator.le, "<=", id="<="), pytest.param(operator.gt, ">", id=">"), ), ) def test_matcherror_compare_no_other_fallback( other: Any, operation: Callable[..., bool], operator_char: str ) -> None: """Check that MatchError comparison with other types causes TypeError.""" expected_error = ( r"^(" r"unsupported operand type\(s\) for {operator!s}:|" r"'{operator!s}' not supported between instances of" r") 'MatchError' and '{other_type!s}'$".format( other_type=type(other).__name__, operator=operator_char ) ) with pytest.raises(TypeError, match=expected_error): operation(MatchError("foo"), other) @pytest.mark.parametrize( "other", ( None, "foo", 42, Exception("foo"), DummyTestObject(), ), ids=repr, ) @pytest.mark.parametrize( ("operation", "expected_value"), ( (operator.eq, False), (operator.ne, True), ), ids=("==", "!="), ) def test_matcherror_compare_with_other_fallback( other: object, operation: Callable[..., bool], expected_value: bool, ) -> None: """Check that MatchError comparison runs other types fallbacks.""" assert operation(MatchError("foo"), other) is expected_value @pytest.mark.parametrize( ("operation", "expected_value"), ( (operator.eq, "EQ_SENTINEL"), (operator.ne, "NE_SENTINEL"), # NOTE: these are swapped because when we do `x < y`, and `x.__lt__(y)` # NOTE: returns `NotImplemented`, Python will reverse the check into # NOTE: `y > x`, and so `y.__gt__(x) is called. # Ref: https://docs.python.org/3/reference/datamodel.html#object.__lt__ (operator.lt, "GT_SENTINEL"), (operator.gt, "LT_SENTINEL"), ), ids=("==", "!=", "<", ">"), ) def test_matcherror_compare_with_dummy_sentinel( operation: Callable[..., bool], expected_value: str ) -> None: """Check that MatchError comparison runs other types fallbacks.""" dummy_obj = DummySentinelTestObject() # NOTE: This assertion abuses the CPython property to cache short string # NOTE: objects because the identity check is more precise and we don't # NOTE: want extra operator protocol methods to influence the test. assert operation(MatchError("foo"), dummy_obj) is expected_value # type: ignore �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestMetaChangeFromDefault.py������������������������������������������������0000664�0000000�0000000�00000002061�14201712607�0022416�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.MetaChangeFromDefaultRule import MetaChangeFromDefaultRule from ansiblelint.testing import RunFromText DEFAULT_GALAXY_INFO = """ galaxy_info: author: your name description: your description company: your company (optional) license: license (GPLv2, CC-BY, etc) """ class TestMetaChangeFromDefault(unittest.TestCase): collection = RulesCollection() collection.register(MetaChangeFromDefaultRule()) def setUp(self) -> None: self.runner = RunFromText(self.collection) def test_default_galaxy_info(self) -> None: results = self.runner.run_role_meta_main(DEFAULT_GALAXY_INFO) assert "Should change default metadata: author" in str(results) assert "Should change default metadata: description" in str(results) assert "Should change default metadata: company" in str(results) assert "Should change default metadata: license" in str(results) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestMetaMainHasInfo.py������������������������������������������������������0000664�0000000�0000000�00000004406�14201712607�0021241�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.MetaMainHasInfoRule import MetaMainHasInfoRule from ansiblelint.testing import RunFromText NO_GALAXY_INFO = """ author: the author description: this meta/main.yml has no galaxy_info """ MISSING_INFO = """ galaxy_info: # author: the author description: Testing if meta contains values company: Not applicable license: MIT # min_ansible_version: 2.5 platforms: - name: Fedora versions: - 25 - missing_name: No name versions: - 25 """ BAD_TYPES = """ galaxy_info: author: 007 description: ['Testing meta'] company: Not applicable license: MIT min_ansible_version: 2.5 platforms: Fedora """ PLATFORMS_LIST_OF_STR = """ galaxy_info: author: '007' description: 'Testing meta' company: Not applicable license: MIT min_ansible_version: 2.5 platforms: ['Fedora', 'EL'] """ class TestMetaMainHasInfo(unittest.TestCase): collection = RulesCollection() collection.register(MetaMainHasInfoRule()) def setUp(self) -> None: self.runner = RunFromText(self.collection) def test_no_galaxy_info(self) -> None: results = self.runner.run_role_meta_main(NO_GALAXY_INFO) assert len(results) == 1 assert "No 'galaxy_info' found" in str(results) def test_missing_info(self) -> None: results = self.runner.run_role_meta_main(MISSING_INFO) assert len(results) == 3 assert "Role info should contain author" in str(results) assert "Role info should contain min_ansible_version" in str(results) assert "Platform should contain name" in str(results) def test_bad_types(self) -> None: results = self.runner.run_role_meta_main(BAD_TYPES) assert len(results) == 3 assert "author should be a string" in str(results) assert "description should be a string" in str(results) assert "Platforms should be a list of dictionaries" in str(results) def test_platform_list_of_str(self) -> None: results = self.runner.run_role_meta_main(PLATFORMS_LIST_OF_STR) assert len(results) == 1 assert "Platforms should be a list of dictionaries" in str(results) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestMetaVideoLinks.py�������������������������������������������������������0000664�0000000�0000000�00000002134�14201712607�0021150�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.MetaVideoLinksRule import MetaVideoLinksRule from ansiblelint.testing import RunFromText META_VIDEO_LINKS = """ galaxy_info: video_links: - url: https://youtu.be/aWmRepTSFKs title: Proper format - https://youtu.be/this_is_not_a_dictionary - my_bad_key: https://youtu.be/aWmRepTSFKs title: This has a bad key - url: www.myvid.com/vid title: Bad format of url """ class TestMetaVideoLinks(unittest.TestCase): collection = RulesCollection() collection.register(MetaVideoLinksRule()) def setUp(self) -> None: self.runner = RunFromText(self.collection) def test_video_links(self) -> None: results = self.runner.run_role_meta_main(META_VIDEO_LINKS) assert "Expected item in 'video_links' to be a dictionary" in str(results) assert "'video_links' to contain only keys 'url' and 'title'" in str(results) assert "URL format 'www.myvid.com/vid' is not recognized" in str(results) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestNestedJinjaRule.py������������������������������������������������������0000664�0000000�0000000�00000014546�14201712607�0021332�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Author: Adrián Tóth <adtoth@redhat.com> # # Copyright (c) 2020, Red Hat, Inc. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from pathlib import Path import pytest from _pytest.fixtures import SubRequest from ansiblelint.file_utils import Lintable from ansiblelint.runner import Runner FAIL_TASK_1LN = Lintable( "playbook.yml", """\ - hosts: all tasks: - name: one-level nesting set_fact: var_one: "2*(1+2) is {{ 2 * {{ 1 + 2 }} }}" """, ) FAIL_TASK_1LN_M = Lintable( "playbook.yml", """\ - hosts: all tasks: - name: one-level multiline nesting set_fact: var_one_ml: > 2*(1+2) is {{ 2 * {{ 1 + 2 }} }} """, ) FAIL_TASK_2LN = Lintable( "playbook.yml", """\ - hosts: all tasks: - name: two-level nesting set_fact: var_two: "2*(1+(3-1)) is {{ 2 * {{ 1 + {{ 3 - 1 }} }} }}" """, ) FAIL_TASK_2LN_M = Lintable( "playbook.yml", """\ - hosts: all tasks: - name: two-level multiline nesting set_fact: var_two_ml: > 2*(1+(3-1)) is {{ 2 * {{ 1 + {{ 3 - 1 }} }} }} """, ) FAIL_TASK_W_5LN = Lintable( "playbook.yml", """\ - hosts: all tasks: - name: five-level wild nesting set_fact: var_three_wld: "{{ {{ {{ {{ {{ 234 }} }} }} }} }}" """, ) FAIL_TASK_W_5LN_M = Lintable( "playbook.yml", """\ - hosts: all tasks: - name: five-level wild multiline nesting set_fact: var_three_wld_ml: > {{ {{ {{ {{ {{ 234 }} }} }} }} }} """, ) SUCCESS_TASK_P = Lintable( "playbook.yml", """\ - hosts: all tasks: - name: non-nested example set_fact: var_one: "number for 'one' is {{ 2 * 1 }}" """, ) SUCCESS_TASK_P_M = Lintable( "playbook.yml", """\ - hosts: all tasks: - name: multiline non-nested example set_fact: var_one_ml: > number for 'one' is {{ 2 * 1 }} """, ) SUCCESS_TASK_2P = Lintable( "playbook.yml", """\ - hosts: all tasks: - name: nesting far from each other set_fact: var_two: "number for 'two' is {{ 2 * 1 }} and number for 'three' is {{ 4 - 1 }}" """, ) SUCCESS_TASK_2P_M = Lintable( "playbook.yml", """\ - hosts: all tasks: - name: multiline nesting far from each other set_fact: var_two_ml: > number for 'two' is {{ 2 * 1 }} and number for 'three' is {{ 4 - 1 }} """, ) SUCCESS_TASK_C_2P = Lintable( "playbook.yml", """\ - hosts: all tasks: - name: nesting close to each other set_fact: var_three: "number for 'ten' is {{ 2 - 1 }}{{ 3 - 3 }}" """, ) SUCCESS_TASK_C_2P_M = Lintable( "playbook.yml", """\ - hosts: all tasks: - name: multiline nesting close to each other set_fact: var_three_ml: > number for 'ten' is {{ 2 - 1 }}{{ 3 - 3 }} """, ) SUCCESS_TASK_PRINT = Lintable( "playbook.yml", """\ - hosts: all tasks: - name: print curly braces debug: msg: docker image inspect my_image --format='{{'{{'}}.Size{{'}}'}}' """, ) @pytest.fixture def _playbook_file(tmp_path: Path, request: SubRequest) -> None: if request.param is None: return for play_file in request.param: p = tmp_path / play_file.name p.write_text(play_file.content) @pytest.mark.parametrize( "_playbook_file", ( pytest.param([FAIL_TASK_1LN], id="file includes one-level nesting"), pytest.param([FAIL_TASK_1LN_M], id="file includes one-level multiline nesting"), pytest.param([FAIL_TASK_2LN], id="file includes two-level nesting"), pytest.param([FAIL_TASK_2LN_M], id="file includes two-level multiline nesting"), pytest.param([FAIL_TASK_W_5LN], id="file includes five-level wild nesting"), pytest.param( [FAIL_TASK_W_5LN_M], id="file includes five-level wild multiline nesting" ), ), indirect=["_playbook_file"], ) @pytest.mark.usefixtures("_playbook_file") def test_including_wrong_nested_jinja(runner: Runner) -> None: """Check that broken Jinja nesting produces a violation.""" rule_violations = runner.run() assert rule_violations[0].rule.id == "no-jinja-nesting" @pytest.mark.parametrize( "_playbook_file", ( pytest.param([SUCCESS_TASK_P], id="file includes non-nested example"), pytest.param( [SUCCESS_TASK_P_M], id="file includes multiline non-nested example" ), pytest.param([SUCCESS_TASK_2P], id="file includes nesting far from each other"), pytest.param( [SUCCESS_TASK_2P_M], id="file includes multiline nesting far from each other", ), pytest.param( [SUCCESS_TASK_C_2P], id="file includes nesting close to each other" ), pytest.param( [SUCCESS_TASK_C_2P_M], id="file includes multiline nesting close to each other", ), pytest.param([SUCCESS_TASK_PRINT], id="file includes print curly braces"), ), indirect=["_playbook_file"], ) @pytest.mark.usefixtures("_playbook_file") def test_including_proper_nested_jinja(runner: Runner) -> None: """Check that properly balanced Jinja nesting doesn't fail.""" rule_violations = runner.run() assert not rule_violations ����������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestNoFormattingInWhenRule.py�����������������������������������������������0000664�0000000�0000000�00000001543�14201712607�0022645�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.NoFormattingInWhenRule import NoFormattingInWhenRule from ansiblelint.runner import Runner class TestNoFormattingInWhenRule(unittest.TestCase): collection = RulesCollection() def setUp(self) -> None: self.collection.register(NoFormattingInWhenRule()) def test_file_positive(self) -> None: success = "examples/playbooks/jinja2-when-success.yml" good_runner = Runner(success, rules=self.collection) assert [] == good_runner.run() def test_file_negative(self) -> None: failure = "examples/playbooks/jinja2-when-failure.yml" bad_runner = Runner(failure, rules=self.collection) errs = bad_runner.run() assert len(errs) == 2 �������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestOctalPermissions.py�����������������������������������������������������0000664�0000000�0000000�00000006301�14201712607�0021570�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.OctalPermissionsRule import OctalPermissionsRule from ansiblelint.testing import RunFromText SUCCESS_TASKS = """ --- - hosts: hosts vars: varset: varset tasks: - name: octal permissions test success (0600) file: path: foo mode: 0600 - name: octal permissions test success (0000) file: path: foo mode: 0000 - name: octal permissions test success (02000) file: path: bar mode: 02000 - name: octal permissions test success (02751) file: path: bar mode: 02751 - name: octal permissions test success (0777) file: path=baz mode=0777 - name: octal permissions test success (0711) file: path=baz mode=0711 - name: permissions test success (0777) file: path=baz mode=u+rwx - name: octal permissions test success (777) file: path=baz mode=777 - name: octal permissions test success (733) file: path=baz mode=733 """ FAIL_TASKS = """ --- - hosts: hosts vars: varset: varset tasks: - name: octal permissions test fail (600) file: path: foo mode: 600 - name: octal permissions test fail (710) file: path: foo mode: 710 - name: octal permissions test fail (123) file: path: foo mode: 123 - name: octal permissions test fail (2000) file: path: bar mode: 2000 """ class TestOctalPermissionsRuleWithFile(unittest.TestCase): collection = RulesCollection() VALID_MODES = [ 0o777, 0o775, 0o770, 0o755, 0o750, 0o711, 0o710, 0o700, 0o666, 0o664, 0o660, 0o644, 0o640, 0o600, 0o555, 0o551, 0o550, 0o511, 0o510, 0o500, 0o444, 0o440, 0o400, ] INVALID_MODES = [ 777, 775, 770, 755, 750, 711, 710, 700, 666, 664, 660, 644, 640, 622, 620, 600, 555, 551, 550, # 511 == 0o777, 510 == 0o776, 500 == 0o764 444, 440, 400, ] def setUp(self) -> None: self.rule = OctalPermissionsRule() self.collection.register(self.rule) self.runner = RunFromText(self.collection) def test_success(self) -> None: results = self.runner.run_playbook(SUCCESS_TASKS) assert len(results) == 0 def test_fail(self) -> None: results = self.runner.run_playbook(FAIL_TASKS) assert len(results) == 4 def test_valid_modes(self) -> None: for mode in self.VALID_MODES: assert not self.rule.is_invalid_permission(mode), ( "0o%o should be a valid mode" % mode ) def test_invalid_modes(self) -> None: for mode in self.INVALID_MODES: assert self.rule.is_invalid_permission(mode), ( "%d should be an invalid mode" % mode ) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestPackageIsNotLatest.py���������������������������������������������������0000664�0000000�0000000�00000001603�14201712607�0021757�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.PackageIsNotLatestRule import PackageIsNotLatestRule from ansiblelint.runner import Runner class TestPackageIsNotLatestRule(unittest.TestCase): collection = RulesCollection() def setUp(self) -> None: self.collection.register(PackageIsNotLatestRule()) def test_package_not_latest_positive(self) -> None: success = "examples/playbooks/package-check-success.yml" good_runner = Runner(success, rules=self.collection) assert [] == good_runner.run() def test_package_not_latest_negative(self) -> None: failure = "examples/playbooks/package-check-failure.yml" bad_runner = Runner(failure, rules=self.collection) errs = bad_runner.run() assert len(errs) == 4 �����������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestRoleNames.py������������������������������������������������������������0000664�0000000�0000000�00000004530�14201712607�0020161�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Test the RoleNames rule.""" from pathlib import Path from typing import Any, Dict, List import pytest from _pytest.fixtures import SubRequest from ansiblelint.rules import RulesCollection from ansiblelint.rules.RoleNames import RoleNames from ansiblelint.runner import Runner ROLE_NAME_VALID = "test_role" TASK_MINIMAL = """ - name: Some task ping: """ ROLE_MINIMAL = {"tasks": {"main.yml": TASK_MINIMAL}} ROLE_META_EMPTY = {"meta": {"main.yml": ""}} ROLE_WITH_EMPTY_META = {**ROLE_MINIMAL, **ROLE_META_EMPTY} PLAY_INCLUDE_ROLE = f""" - hosts: all roles: - {ROLE_NAME_VALID} """ @pytest.fixture def test_rules_collection() -> RulesCollection: """Instantiate a roles collection for tests.""" collection = RulesCollection() collection.register(RoleNames()) return collection def dict_to_files(parent_dir: Path, file_dict: Dict[str, Any]) -> None: """Write a nested dict to a file and directory structure below parent_dir.""" for file, content in file_dict.items(): if isinstance(content, dict): directory = parent_dir / file directory.mkdir() dict_to_files(directory, content) else: (parent_dir / file).write_text(content) @pytest.fixture def playbook_path(request: SubRequest, tmp_path: Path) -> str: """Create a playbook with a role in a temporary directory.""" playbook_text = request.param[0] role_name = request.param[1] role_layout = request.param[2] role_path = tmp_path / role_name role_path.mkdir() dict_to_files(role_path, role_layout) play_path = tmp_path / "playbook.yml" play_path.write_text(playbook_text) return str(play_path) @pytest.mark.parametrize( ("playbook_path", "messages"), ( pytest.param( (PLAY_INCLUDE_ROLE, ROLE_NAME_VALID, ROLE_WITH_EMPTY_META), [], id="ROLE_EMPTY_META", ), ), indirect=("playbook_path",), ) def test_role_name( test_rules_collection: RulesCollection, playbook_path: str, messages: List[str] ) -> None: """Lint a playbook and compare the expected messages with the actual messages.""" runner = Runner(playbook_path, rules=test_rules_collection) results = runner.run() assert len(results) == len(messages) results_text = str(results) for message in messages: assert message in results_text ������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestRoleRelativePath.py�����������������������������������������������������0000664�0000000�0000000�00000002702�14201712607�0021505�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.RoleRelativePath import RoleRelativePath from ansiblelint.testing import RunFromText FAIL_TASKS = """ - name: template example template: src: ../templates/foo.j2 dest: /etc/file.conf - name: copy example copy: src: ../files/foo.conf dest: /etc/foo.conf # Removed from test suite as module is no longer part of core # - name: win_template example # win_template: # src: ../win_templates/file.conf.j2 # dest: file.conf # - name: win_copy example # win_copy: # src: ../files/foo.conf # dest: renamed-foo.conf """ SUCCESS_TASKS = """ - name: content example with no src copy: content: '# This file was moved to /etc/other.conf' dest: /etc/mine.conf # - name: content example with no src # win_copy: # content: '# This file was moved to /etc/other.conf' # dest: /etc/mine.conf """ class TestRoleRelativePath(unittest.TestCase): collection = RulesCollection() collection.register(RoleRelativePath()) def setUp(self) -> None: self.runner = RunFromText(self.collection) def test_fail(self) -> None: results = self.runner.run_role_tasks_main(FAIL_TASKS) assert len(results) == 2 def test_success(self) -> None: results = self.runner.run_role_tasks_main(SUCCESS_TASKS) assert len(results) == 0 ��������������������������������������������������������������ansible-lint-5.4.0/test/TestRuleProperties.py�������������������������������������������������������0000664�0000000�0000000�00000000662�14201712607�0021262�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from ansiblelint.rules import RulesCollection def test_serverity_valid(default_rules_collection: RulesCollection) -> None: """Test that rules collection only has allow-listed severities.""" valid_severity_values = [ "VERY_HIGH", "HIGH", "MEDIUM", "LOW", "VERY_LOW", "INFO", ] for rule in default_rules_collection: assert rule.severity in valid_severity_values ������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestRulesCollection.py������������������������������������������������������0000664�0000000�0000000�00000014160�14201712607�0021402�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import collections import os import re import pytest from ansiblelint.config import options from ansiblelint.file_utils import Lintable from ansiblelint.rules import RulesCollection from ansiblelint.testing import run_ansible_lint @pytest.fixture def test_rules_collection() -> RulesCollection: """Create a shared rules collection test instance.""" return RulesCollection([os.path.abspath("./test/rules")]) @pytest.fixture def ematchtestfile() -> Lintable: """Produce a test lintable with an id violation.""" return Lintable("examples/playbooks/ematcher-rule.yml", kind="playbook") @pytest.fixture def bracketsmatchtestfile() -> Lintable: """Produce a test lintable with matching brackets.""" return Lintable("examples/playbooks/bracketsmatchtest.yml", kind="playbook") def test_load_collection_from_directory(test_rules_collection: RulesCollection) -> None: """Test that custom rules extend the default ones.""" # two detected rules plus the internal ones assert len(test_rules_collection) == 6 def test_run_collection( test_rules_collection: RulesCollection, ematchtestfile: Lintable ) -> None: """Test that default rules match pre-meditated violations.""" matches = test_rules_collection.run(ematchtestfile) assert len(matches) == 4 # 3 occurrences of BANNED using TEST0001 + 1 for TEST0003 assert matches[0].linenumber == 2 def test_tags( test_rules_collection: RulesCollection, ematchtestfile: Lintable, bracketsmatchtestfile: Lintable, ) -> None: """Test that tags are treated as skip markers.""" matches = test_rules_collection.run(ematchtestfile, tags={"test1"}) assert len(matches) == 3 matches = test_rules_collection.run(ematchtestfile, tags={"test2"}) assert len(matches) == 0 matches = test_rules_collection.run(bracketsmatchtestfile, tags={"test1"}) assert len(matches) == 0 matches = test_rules_collection.run(bracketsmatchtestfile, tags={"test2"}) assert len(matches) == 2 def test_skip_tags( test_rules_collection: RulesCollection, ematchtestfile: Lintable, bracketsmatchtestfile: Lintable, ) -> None: """Test that tags can be skipped.""" matches = test_rules_collection.run(ematchtestfile, skip_list=["test1", "test3"]) assert len(matches) == 0 matches = test_rules_collection.run(ematchtestfile, skip_list=["test2", "test3"]) assert len(matches) == 3 matches = test_rules_collection.run(bracketsmatchtestfile, skip_list=["test1"]) assert len(matches) == 2 matches = test_rules_collection.run(bracketsmatchtestfile, skip_list=["test2"]) assert len(matches) == 0 def test_skip_id( test_rules_collection: RulesCollection, ematchtestfile: Lintable, bracketsmatchtestfile: Lintable, ) -> None: """Check that skipping valid IDs excludes their violations.""" matches = test_rules_collection.run( ematchtestfile, skip_list=["TEST0001", "TEST0003"] ) assert len(matches) == 0 matches = test_rules_collection.run( ematchtestfile, skip_list=["TEST0002", "TEST0003"] ) assert len(matches) == 3 matches = test_rules_collection.run(bracketsmatchtestfile, skip_list=["TEST0001"]) assert len(matches) == 2 matches = test_rules_collection.run(bracketsmatchtestfile, skip_list=["TEST0002"]) assert len(matches) == 0 def test_skip_non_existent_id( test_rules_collection: RulesCollection, ematchtestfile: Lintable ) -> None: """Check that skipping invalid IDs changes nothing.""" matches = test_rules_collection.run(ematchtestfile, skip_list=["DOESNOTEXIST"]) assert len(matches) == 4 def test_no_duplicate_rule_ids(test_rules_collection: RulesCollection) -> None: """Check that rules of the collection don't have duplicate IDs.""" real_rules = RulesCollection([os.path.abspath("./src/ansiblelint/rules")]) rule_ids = [rule.id for rule in real_rules] assert not any(y > 1 for y in collections.Counter(rule_ids).values()) def test_rich_rule_listing() -> None: """Test that rich list format output is rendered as a table. This check also offers the contract of having rule id, short and long descriptions in the console output. """ rules_path = os.path.abspath("./test/rules") result = run_ansible_lint("-r", rules_path, "-f", "rich", "-L") assert result.returncode == 0 for rule in RulesCollection([rules_path]): assert rule.id in result.stdout assert rule.shortdesc in result.stdout # description could wrap inside table, so we do not check full length assert rule.description[:30] in result.stdout def test_rules_id_format() -> None: """Assure all our rules have consistent format.""" rule_id_re = re.compile("^[a-z-]{4,30}$") options.enable_list = ["no-same-owner", "no-log-password", "no-same-owner"] rules = RulesCollection( [os.path.abspath("./src/ansiblelint/rules")], options=options ) for rule in rules: assert rule_id_re.match( rule.id ), f"Rule id {rule.id} did not match our required format." assert len(rules) == 40 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestRunner.py���������������������������������������������������������������0000664�0000000�0000000�00000012435�14201712607�0017550�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2013-2014 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import os from typing import Any, List, Set, Type import pytest from ansiblelint import formatters from ansiblelint.file_utils import Lintable, abspath from ansiblelint.rules import RulesCollection from ansiblelint.runner import Runner LOTS_OF_WARNINGS_PLAYBOOK = abspath( "examples/playbooks/lots_of_warnings.yml", os.getcwd() ) @pytest.mark.parametrize( ("playbook", "exclude", "length"), ( pytest.param("examples/playbooks/nomatchestest.yml", [], 0, id="nomatchestest"), pytest.param("examples/playbooks/unicode.yml", [], 1, id="unicode"), pytest.param( LOTS_OF_WARNINGS_PLAYBOOK, [LOTS_OF_WARNINGS_PLAYBOOK], 0, id="lots_of_warnings", ), pytest.param("examples/playbooks/become.yml", [], 0, id="become"), pytest.param( "examples/playbooks/contains_secrets.yml", [], 0, id="contains_secrets" ), ), ) def test_runner( default_rules_collection: RulesCollection, playbook: str, exclude: List[str], length: int, ) -> None: """Test that runner can go through any corner cases.""" runner = Runner(playbook, rules=default_rules_collection, exclude_paths=exclude) matches = runner.run() assert len(matches) == length def test_runner_exclude_paths(default_rules_collection: RulesCollection) -> None: """Test that exclude paths do work.""" runner = Runner( "examples/playbooks/example.yml", rules=default_rules_collection, exclude_paths=["examples/"], ) matches = runner.run() assert len(matches) == 0 @pytest.mark.parametrize(("exclude_path"), ("**/playbooks/*.yml",)) def test_runner_exclude_globs( default_rules_collection: RulesCollection, exclude_path: str ) -> None: """Test that globs work.""" runner = Runner( "examples/playbooks", rules=default_rules_collection, exclude_paths=[exclude_path], ) matches = runner.run() # we expect to find one error from the only .yaml file we have there. assert len(matches) == 1 @pytest.mark.parametrize( ("formatter_cls"), ( pytest.param(formatters.Formatter, id="Formatter-plain"), pytest.param(formatters.ParseableFormatter, id="ParseableFormatter-colored"), pytest.param(formatters.QuietFormatter, id="QuietFormatter-colored"), pytest.param(formatters.Formatter, id="Formatter-colored"), ), ) def test_runner_unicode_format( default_rules_collection: RulesCollection, formatter_cls: Type[formatters.BaseFormatter[Any]], ) -> None: """Check that all formatters are unicode-friendly.""" formatter = formatter_cls(os.getcwd(), display_relative_path=True) runner = Runner( Lintable("examples/playbooks/unicode.yml", "playbook"), rules=default_rules_collection, ) matches = runner.run() formatter.format(matches[0]) @pytest.mark.parametrize("directory_name", ("test/", os.path.abspath("test"))) def test_runner_with_directory( default_rules_collection: RulesCollection, directory_name: str ) -> None: """Check that runner detects a directory as role.""" runner = Runner(directory_name, rules=default_rules_collection) expected = Lintable(name=directory_name, kind="role") assert expected in runner.lintables def test_files_not_scanned_twice(default_rules_collection: RulesCollection) -> None: """Ensure that lintables aren't double-checked.""" checked_files: Set[Lintable] = set() filename = os.path.abspath("examples/playbooks/common-include-1.yml") runner = Runner( filename, rules=default_rules_collection, verbosity=0, checked_files=checked_files, ) run1 = runner.run() assert len(runner.checked_files) == 2 assert len(run1) == 1 filename = os.path.abspath("examples/playbooks/common-include-2.yml") runner = Runner( filename, rules=default_rules_collection, verbosity=0, checked_files=checked_files, ) run2 = runner.run() assert len(runner.checked_files) == 3 # this second run should return 0 because the included filed was already # processed and added to checked_files, which acts like a bypass list. assert len(run2) == 0 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestShellWithoutPipefail.py�������������������������������������������������0000664�0000000�0000000�00000004174�14201712607�0022405�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.ShellWithoutPipefail import ShellWithoutPipefail from ansiblelint.testing import RunFromText FAIL_TASKS = """ --- - hosts: localhost become: no tasks: - name: pipeline without pipefail shell: false | cat - name: pipeline with or and pipe, no pipefail shell: false || true | cat - shell: | df | grep '/dev' """ SUCCESS_TASKS = """ --- - hosts: localhost become: no tasks: - name: pipeline with pipefail shell: set -o pipefail && false | cat - name: pipeline with pipefail, multi-line shell: | set -o pipefail false | cat - name: pipeline with pipefail, complex set shell: | set -e -x -o pipefail false | cat - name: pipeline with pipefail, complex set shell: | set -e -x -o pipefail false | cat - name: pipeline with pipefail, complex set shell: | set -eo pipefail false | cat - name: pipeline with pipefail not at first line shell: | echo foo set -eo pipefail false | cat - name: pipeline without pipefail, ignoring errors shell: false | cat ignore_errors: true - name: non-pipeline without pipefail shell: "true" - name: command without pipefail command: "true" - name: shell with or shell: false || true - shell: | set -o pipefail df | grep '/dev' - name: should not fail due to ignore_errors being true shell: false | cat ignore_errors: true """ class TestShellWithoutPipeFail(unittest.TestCase): collection = RulesCollection() collection.register(ShellWithoutPipefail()) def setUp(self) -> None: self.runner = RunFromText(self.collection) def test_fail(self) -> None: results = self.runner.run_playbook(FAIL_TASKS) assert len(results) == 3 def test_success(self) -> None: results = self.runner.run_playbook(SUCCESS_TASKS) assert len(results) == 0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestSkipImportPlaybook.py���������������������������������������������������0000664�0000000�0000000�00000001764�14201712607�0022104�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from pathlib import Path import pytest from ansiblelint.rules import RulesCollection from ansiblelint.runner import Runner IMPORTED_PLAYBOOK = """\ - hosts: all tasks: - name: success fail: msg="fail" when: false """ MAIN_PLAYBOOK = """\ - hosts: all tasks: - name: should be shell # noqa command-instead-of-shell no-changed-when shell: echo lol - import_playbook: imported_playbook.yml """ @pytest.fixture def playbook(tmp_path: Path) -> str: """Create a reusable per-test playbook.""" playbook_path = tmp_path / "playbook.yml" playbook_path.write_text(MAIN_PLAYBOOK) (tmp_path / "imported_playbook.yml").write_text(IMPORTED_PLAYBOOK) return str(playbook_path) def test_skip_import_playbook( default_rules_collection: RulesCollection, playbook: str ) -> None: """Verify that a playbook import is skipped after a failure.""" runner = Runner(playbook, rules=default_rules_collection) results = runner.run() assert len(results) == 0 ������������ansible-lint-5.4.0/test/TestSkipInsideYaml.py�������������������������������������������������������0000664�0000000�0000000�00000007365�14201712607�0021172�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import pytest from ansiblelint.testing import RunFromText ROLE_TASKS = """\ --- - debug: msg: this should fail linting due lack of name - debug: # noqa unnamed-task msg: this should pass due to noqa comment """ ROLE_TASKS_WITH_BLOCK = """\ --- - name: bad git 1 # noqa git-latest action: git a=b c=d - name: bad git 2 action: git a=b c=d - name: Block with rescue and always section block: - name: bad git 3 # noqa git-latest action: git a=b c=d - name: bad git 4 action: git a=b c=d rescue: - name: bad git 5 # noqa git-latest action: git a=b c=d - name: bad git 6 action: git a=b c=d always: - name: bad git 7 # noqa git-latest action: git a=b c=d - name: bad git 8 action: git a=b c=d """ PLAYBOOK = """\ - hosts: all tasks: - name: test hg-latest action: hg - name: test hg-latest (skipped) # noqa hg-latest action: hg - name: test git-latest and partial-become become_user: alice action: git - name: test git-latest and partial-become (skipped) # noqa git-latest partial-become become_user: alice action: git - name: test YAML and var-spacing get_url: url: http://example.com/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/file.conf dest: "{{dest_proj_path}}/foo.conf" - name: test YAML and var-spacing (skipped) get_url: url: http://example.com/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/file.conf # noqa yaml dest: "{{dest_proj_path}}/foo.conf" # noqa var-spacing - name: test deprecated-command-syntax command: creates=B chmod 644 A - name: test deprecated-command-syntax command: warn=yes creates=B chmod 644 A - name: test deprecated-command-syntax (skipped via no warn) command: warn=no creates=B chmod 644 A - name: test deprecated-command-syntax (skipped via skip_ansible_lint) command: creates=B chmod 644 A tags: - skip_ansible_lint """ ROLE_META = """\ galaxy_info: # noqa meta-no-info author: your name # noqa meta-incorrect description: missing min_ansible_version and platforms. author default not changed license: MIT """ ROLE_TASKS_WITH_BLOCK_BECOME = """\ - hosts: localhost tasks: - name: foo become: true block: - name: bar become_user: jonhdaa command: "/etc/test.sh" changed_when: false """ def test_role_tasks(default_text_runner: RunFromText) -> None: """Check that role tasks can contain skips.""" results = default_text_runner.run_role_tasks_main(ROLE_TASKS) assert len(results) == 1, results assert results[0].linenumber == 2 assert results[0].rule.id == "unnamed-task" def test_role_tasks_with_block(default_text_runner: RunFromText) -> None: """Check that blocks in role tasks can contain skips.""" results = default_text_runner.run_role_tasks_main(ROLE_TASKS_WITH_BLOCK) assert len(results) == 4 @pytest.mark.parametrize( ("playbook_src", "results_num"), ( (PLAYBOOK, 7), (ROLE_TASKS_WITH_BLOCK_BECOME, 0), ), ids=("generic", "with block become inheritance"), ) def test_playbook( default_text_runner: RunFromText, playbook_src: str, results_num: int ) -> None: """Check that playbooks can contain skips.""" results = default_text_runner.run_playbook(playbook_src) assert len(results) == results_num def test_role_meta(default_text_runner: RunFromText) -> None: """Check that role meta can contain skips.""" results = default_text_runner.run_role_meta_main(ROLE_META) assert len(results) == 0 ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestSkipPlaybookItems.py����������������������������������������������������0000664�0000000�0000000�00000005004�14201712607�0021702�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import pytest from ansiblelint.testing import RunFromText PLAYBOOK_PRE_TASKS = """\ - hosts: all tasks: - name: bad git 1 # noqa git-latest action: git a=b c=d - name: bad git 2 action: git a=b c=d pre_tasks: - name: bad git 3 # noqa git-latest action: git a=b c=d - name: bad git 4 action: git a=b c=d """ PLAYBOOK_POST_TASKS = """\ - hosts: all tasks: - name: bad git 1 # noqa git-latest action: git a=b c=d - name: bad git 2 action: git a=b c=d post_tasks: - name: bad git 3 # noqa git-latest action: git a=b c=d - name: bad git 4 action: git a=b c=d """ PLAYBOOK_HANDLERS = """\ - hosts: all tasks: - name: bad git 1 # noqa git-latest action: git a=b c=d - name: bad git 2 action: git a=b c=d handlers: - name: bad git 3 # noqa git-latest action: git a=b c=d - name: bad git 4 action: git a=b c=d """ PLAYBOOK_TWO_PLAYS = """\ - hosts: all tasks: - name: bad git 1 # noqa git-latest action: git a=b c=d - name: bad git 2 action: git a=b c=d - hosts: all tasks: - name: bad git 3 # noqa git-latest action: git a=b c=d - name: bad git 4 action: git a=b c=d """ PLAYBOOK_WITH_BLOCK = """\ - hosts: all tasks: - name: bad git 1 # noqa git-latest action: git a=b c=d - name: bad git 2 action: git a=b c=d - name: Block with rescue and always section block: - name: bad git 3 # noqa git-latest action: git a=b c=d - name: bad git 4 action: git a=b c=d rescue: - name: bad git 5 # noqa git-latest action: git a=b c=d - name: bad git 6 action: git a=b c=d always: - name: bad git 7 # noqa git-latest action: git a=b c=d - name: bad git 8 action: git a=b c=d """ @pytest.mark.parametrize( ("playbook", "length"), ( pytest.param(PLAYBOOK_PRE_TASKS, 2, id="PRE_TASKS"), pytest.param(PLAYBOOK_POST_TASKS, 2, id="POST_TASKS"), pytest.param(PLAYBOOK_HANDLERS, 2, id="HANDLERS"), pytest.param(PLAYBOOK_TWO_PLAYS, 2, id="TWO_PLAYS"), pytest.param(PLAYBOOK_WITH_BLOCK, 4, id="WITH_BLOCK"), ), ) def test_pre_tasks( default_text_runner: RunFromText, playbook: str, length: int ) -> None: """Check that skipping is possible in different playbook parts.""" # When results = default_text_runner.run_playbook(playbook) # Then assert len(results) == length ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestTaskHasName.py����������������������������������������������������������0000664�0000000�0000000�00000001513�14201712607�0020431�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.TaskHasNameRule import TaskHasNameRule from ansiblelint.runner import Runner class TestTaskHasNameRule(unittest.TestCase): collection = RulesCollection() def setUp(self) -> None: self.collection.register(TaskHasNameRule()) def test_file_positive(self) -> None: success = "examples/playbooks/task-has-name-success.yml" good_runner = Runner(success, rules=self.collection) assert [] == good_runner.run() def test_file_negative(self) -> None: failure = "examples/playbooks/task-has-name-failure.yml" bad_runner = Runner(failure, rules=self.collection) errs = bad_runner.run() assert len(errs) == 4 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestTaskIncludes.py���������������������������������������������������������0000664�0000000�0000000�00000002535�14201712607�0020670�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import pytest from ansiblelint.file_utils import Lintable from ansiblelint.rules import RulesCollection from ansiblelint.runner import Runner @pytest.mark.parametrize( ("filename", "file_count", "match_count"), ( pytest.param("examples/playbooks/blockincludes.yml", 4, 2, id="blockincludes"), pytest.param( "examples/playbooks/blockincludes2.yml", 4, 2, id="blockincludes2", ), pytest.param("examples/playbooks/taskincludes.yml", 2, 1, id="taskincludes"), pytest.param("examples/playbooks/taskimports.yml", 4, 2, id="taskimports"), pytest.param( "examples/playbooks/include-in-block.yml", 3, 1, id="include-in-block", ), pytest.param( "examples/playbooks/include-import-tasks-in-role.yml", 5, 2, id="role_with_task_inclusions", ), ), ) def test_included_tasks( default_rules_collection: RulesCollection, filename: str, file_count: int, match_count: int, ) -> None: """Check if number of loaded files is correct.""" lintable = Lintable(filename) runner = Runner(lintable, rules=default_rules_collection) result = runner.run() assert len(runner.lintables) == file_count assert len(result) == match_count �������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestTaskNoLocalAction.py����������������������������������������������������0000664�0000000�0000000�00000001267�14201712607�0021610�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.TaskNoLocalAction import TaskNoLocalAction from ansiblelint.testing import RunFromText TASK_LOCAL_ACTION = """ - name: task example local_action: module: boto3_facts """ class TestTaskNoLocalAction(unittest.TestCase): collection = RulesCollection() collection.register(TaskNoLocalAction()) def setUp(self) -> None: self.runner = RunFromText(self.collection) def test_local_action(self) -> None: results = self.runner.run_role_tasks_main(TASK_LOCAL_ACTION) assert len(results) == 1 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestUseHandlerRatherThanWhenChanged.py��������������������������������������0000664�0000000�0000000�00000003613�14201712607�0024404�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.UseHandlerRatherThanWhenChangedRule import ( UseHandlerRatherThanWhenChangedRule, ) from ansiblelint.testing import RunFromText SUCCESS_TASKS = """ - name: print helpful error message debug: var: result when: result.failed - name: do something when hello is output debug: msg: why isn't this a handler when: result.stdout == "hello" - name: never actually debug debug: var: result when: False - name: Dont execute this step debug: msg: "debug message" when: - false - name: check when with a list debug: var: result when: - conditionA - conditionB """ FAIL_TASKS = """ - name: execute command command: echo hello register: result - name: this should be a handler debug: msg: why isn't this a handler when: result.changed - name: this should be a handler 2 debug: msg: why isn't this a handler when: result|changed - name: this should be a handler 3 debug: msg: why isn't this a handler when: result.changed == true - name: this should be a handler 4 debug: msg: why isn't this a handler when: result['changed'] == true - name: this should be a handler 5 debug: msg: why isn't this a handler when: - result['changed'] == true - another_condition """ class TestUseHandlerRatherThanWhenChanged(unittest.TestCase): collection = RulesCollection() collection.register(UseHandlerRatherThanWhenChangedRule()) def setUp(self) -> None: self.runner = RunFromText(self.collection) def test_success(self) -> None: results = self.runner.run_role_tasks_main(SUCCESS_TASKS) assert len(results) == 0 def test_fail(self) -> None: results = self.runner.run_role_tasks_main(FAIL_TASKS) assert len(results) == 5 ���������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestUsingBareVariablesIsDeprecated.py���������������������������������������0000664�0000000�0000000�00000001653�14201712607�0024264�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.UsingBareVariablesIsDeprecatedRule import ( UsingBareVariablesIsDeprecatedRule, ) from ansiblelint.runner import Runner class TestUsingBareVariablesIsDeprecated(unittest.TestCase): collection = RulesCollection() def setUp(self) -> None: self.collection.register(UsingBareVariablesIsDeprecatedRule()) def test_file_positive(self) -> None: success = "examples/playbooks/using-bare-variables-success.yml" good_runner = Runner(success, rules=self.collection) assert [] == good_runner.run() def test_file_negative(self) -> None: failure = "examples/playbooks/using-bare-variables-failure.yml" bad_runner = Runner(failure, rules=self.collection) errs = bad_runner.run() assert len(errs) == 11 �������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestUtils.py����������������������������������������������������������������0000664�0000000�0000000�00000030114�14201712607�0017371�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright (c) 2013-2014 Will Thames <will@thames.id.au> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """Tests for generic utility functions.""" import logging import os import os.path import subprocess import sys from argparse import Namespace from pathlib import Path from typing import Any, Dict, List, Sequence, Tuple import pytest from _pytest.capture import CaptureFixture from _pytest.logging import LogCaptureFixture from _pytest.monkeypatch import MonkeyPatch from ansiblelint import cli, constants, utils from ansiblelint.__main__ import initialize_logger from ansiblelint.cli import get_rules_dirs from ansiblelint.file_utils import Lintable from ansiblelint.testing import run_ansible_lint @pytest.mark.parametrize( ("string", "expected_cmd", "expected_args", "expected_kwargs"), ( pytest.param("", "", [], {}, id="blank"), pytest.param("vars:", "vars", [], {}, id="single_word"), pytest.param("hello: a=1", "hello", [], {"a": "1"}, id="string_module_and_arg"), pytest.param("action: hello a=1", "hello", [], {"a": "1"}, id="strips_action"), pytest.param( "action: whatever bobbins x=y z=x c=3", "whatever", ["bobbins", "x=y", "z=x", "c=3"], {}, id="more_than_one_arg", ), pytest.param( "action: command chdir=wxy creates=zyx tar xzf zyx.tgz", "command", ["tar", "xzf", "zyx.tgz"], {"chdir": "wxy", "creates": "zyx"}, id="command_with_args", ), ), ) def test_tokenize( string: str, expected_cmd: str, expected_args: Sequence[str], expected_kwargs: Dict[str, Any], ) -> None: """Test that tokenize works for different input types.""" (cmd, args, kwargs) = utils.tokenize(string) assert cmd == expected_cmd assert args == expected_args assert kwargs == expected_kwargs @pytest.mark.parametrize( ("reference_form", "alternate_forms"), ( pytest.param( dict(name="hello", action="command chdir=abc echo hello world"), (dict(name="hello", command="chdir=abc echo hello world"),), id="simple_command", ), pytest.param( {"git": {"version": "abc"}, "args": {"repo": "blah", "dest": "xyz"}}, ( {"git": {"version": "abc", "repo": "blah", "dest": "xyz"}}, {"git": "version=abc repo=blah dest=xyz"}, { "git": None, "args": {"repo": "blah", "dest": "xyz", "version": "abc"}, }, ), id="args", ), ), ) def test_normalize( reference_form: Dict[str, Any], alternate_forms: Tuple[Dict[str, Any]] ) -> None: """Test that tasks specified differently are normalized same way.""" normal_form = utils.normalize_task(reference_form, "tasks.yml") for form in alternate_forms: assert normal_form == utils.normalize_task(form, "tasks.yml") def test_normalize_complex_command() -> None: """Test that tasks specified differently are normalized same way.""" task1 = dict( name="hello", action={"module": "pip", "name": "df", "editable": "false"} ) task2 = dict(name="hello", pip={"name": "df", "editable": "false"}) task3 = dict(name="hello", pip="name=df editable=false") task4 = dict(name="hello", action="pip name=df editable=false") assert utils.normalize_task(task1, "tasks.yml") == utils.normalize_task( task2, "tasks.yml" ) assert utils.normalize_task(task2, "tasks.yml") == utils.normalize_task( task3, "tasks.yml" ) assert utils.normalize_task(task3, "tasks.yml") == utils.normalize_task( task4, "tasks.yml" ) def test_extract_from_list() -> None: """Check that tasks get extracted from blocks if present.""" block = { "block": [{"tasks": {"name": "hello", "command": "whoami"}}], "test_none": None, "test_string": "foo", } blocks = [block] test_list = utils.extract_from_list(blocks, ["block"]) test_none = utils.extract_from_list(blocks, ["test_none"]) assert list(block["block"]) == test_list # type: ignore assert list() == test_none with pytest.raises(RuntimeError): utils.extract_from_list(blocks, ["test_string"]) def test_extract_from_list_recursive() -> None: """Check that tasks get extracted from blocks if present.""" block = { "block": [{"block": [{"name": "hello", "command": "whoami"}]}], } blocks = [block] test_list = utils.extract_from_list(blocks, ["block"]) assert list(block["block"]) == test_list test_list_recursive = utils.extract_from_list(blocks, ["block"], recursive=True) assert block["block"] + block["block"][0]["block"] == test_list_recursive # type: ignore @pytest.mark.parametrize( ("template", "output"), ( pytest.param("{{ playbook_dir }}", "/a/b/c", id="simple"), pytest.param( "{{ 'hello' | doesnotexist }}", "{{ 'hello' | doesnotexist }}", id="unknown_filter", ), pytest.param( "{{ hello | to_json }}", "{{ hello | to_json }}", id="to_json_filter_on_undefined_variable", ), pytest.param( "{{ hello | to_nice_yaml }}", "{{ hello | to_nice_yaml }}", id="to_nice_yaml_filter_on_undefined_variable", ), ), ) def test_template(template: str, output: str) -> None: """Verify that resolvable template vars and filters get rendered.""" result = utils.template("/base/dir", template, dict(playbook_dir="/a/b/c")) assert result == output @pytest.mark.parametrize( ("role", "expect_warning"), ( ("template_lookup", False), ("template_lookup_missing", True), ), ) def test_template_lookup(role: str, expect_warning: bool) -> None: """Assure lookup plugins used in templates does not trigger Ansible warnings.""" task_path = os.path.realpath( os.path.join( os.path.dirname(os.path.realpath(__file__)), "..", "examples", "roles", role, "tasks", "main.yml", ) ) result = run_ansible_lint("-v", task_path) assert ("Unable to find" in result.stderr) == expect_warning def test_task_to_str_unicode() -> None: """Ensure that extracting messages from tasks preserves Unicode.""" task = dict(fail=dict(msg="unicode é ô à")) result = utils.task_to_str(utils.normalize_task(task, "filename.yml")) assert result == "fail msg=unicode é ô à" def test_logger_debug(caplog: LogCaptureFixture) -> None: """Test that the double verbosity arg causes logger to be DEBUG.""" options = cli.get_config(["-vv"]) initialize_logger(options.verbosity) expected_info = ( "ansiblelint.__main__", logging.DEBUG, "Logging initialized to level 10", ) assert expected_info in caplog.record_tuples def test_cli_auto_detect(capfd: CaptureFixture[str]) -> None: """Test that run without arguments it will detect and lint the entire repository.""" cmd = [ sys.executable, "-m", "ansiblelint", "-v", "-p", "--nocolor", ] result = subprocess.run(cmd, check=False).returncode # We de expect to fail on our own repo due to test examples we have # TODO(ssbarnea) replace it with exact return code once we document them assert result != 0 out, err = capfd.readouterr() # Confirmation that it runs in auto-detect mode assert ( "Discovered files to lint using: git ls-files --cached --others --exclude-standard -z" in err ) assert "Excluded removed files using: git ls-files --deleted -z" in err # An expected rule match from our examples assert ( "examples/playbooks/empty_playbook.yml:1: " "syntax-check Empty playbook, nothing to do" in out ) # assures that our .ansible-lint exclude was effective in excluding github files assert "Identified: .github/" not in out # assures that we can parse playbooks as playbooks assert "Identified: test/test/always-run-success.yml" not in err # assure that zuul_return missing module is not reported assert "examples/playbooks/mocked_dependency.yml" not in out assert "Executing syntax check on examples/playbooks/mocked_dependency.yml" in err def test_is_playbook() -> None: """Verify that we can detect a playbook as a playbook.""" assert utils.is_playbook("examples/playbooks/always-run-success.yml") def test_auto_detect_exclude(monkeypatch: MonkeyPatch) -> None: """Verify that exclude option can be used to narrow down detection.""" options = cli.get_config(["--exclude", "foo"]) def mockreturn(options: Namespace) -> List[str]: return ["foo/playbook.yml", "bar/playbook.yml"] monkeypatch.setattr(utils, "discover_lintables", mockreturn) result = utils.get_lintables(options) assert result == [Lintable("bar/playbook.yml", kind="playbook")] _DEFAULT_RULEDIRS = [constants.DEFAULT_RULESDIR] _CUSTOM_RULESDIR = Path(__file__).parent / "custom_rules" _CUSTOM_RULEDIRS = [ str(_CUSTOM_RULESDIR / "example_inc"), str(_CUSTOM_RULESDIR / "example_com"), ] @pytest.mark.parametrize( ("user_ruledirs", "use_default", "expected"), ( ([], True, _DEFAULT_RULEDIRS), ([], False, _DEFAULT_RULEDIRS), (_CUSTOM_RULEDIRS, True, _CUSTOM_RULEDIRS + _DEFAULT_RULEDIRS), (_CUSTOM_RULEDIRS, False, _CUSTOM_RULEDIRS), ), ) def test_get_rules_dirs( user_ruledirs: List[str], use_default: bool, expected: List[str] ) -> None: """Test it returns expected dir lists.""" assert get_rules_dirs(user_ruledirs, use_default) == expected @pytest.mark.parametrize( ("user_ruledirs", "use_default", "expected"), ( ([], True, sorted(_CUSTOM_RULEDIRS) + _DEFAULT_RULEDIRS), ([], False, sorted(_CUSTOM_RULEDIRS) + _DEFAULT_RULEDIRS), ( _CUSTOM_RULEDIRS, True, _CUSTOM_RULEDIRS + sorted(_CUSTOM_RULEDIRS) + _DEFAULT_RULEDIRS, ), (_CUSTOM_RULEDIRS, False, _CUSTOM_RULEDIRS), ), ) def test_get_rules_dirs_with_custom_rules( user_ruledirs: List[str], use_default: bool, expected: List[str], monkeypatch: MonkeyPatch, ) -> None: """Test it returns expected dir lists when custom rules exist.""" monkeypatch.setenv(constants.CUSTOM_RULESDIR_ENVVAR, str(_CUSTOM_RULESDIR)) assert get_rules_dirs(user_ruledirs, use_default) == expected def test_nested_items() -> None: """Verify correct function of nested_items().""" data = {"foo": "text", "bar": {"some": "text2"}, "fruits": ["apple", "orange"]} items = [ ("foo", "text", ""), ("bar", {"some": "text2"}, ""), ("some", "text2", "bar"), ("fruits", ["apple", "orange"], ""), ("list-item", "apple", "fruits"), ("list-item", "orange", "fruits"), ] with pytest.deprecated_call( match=r"Call to deprecated function ansiblelint\.utils\.nested_items.*" ): assert list(utils.nested_items(data)) == items ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/TestVerbosity.py������������������������������������������������������������0000664�0000000�0000000�00000005755�14201712607�0020274�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Tests related to our logging/verbosity setup.""" import os from typing import List, Tuple import pytest from ansiblelint.testing import run_ansible_lint # substrs is a list of tuples, where: # component 1 is the substring in question # component 2 is whether or not to invert ("NOT") the match @pytest.mark.parametrize( ("verbosity", "substrs"), ( ( "", [ ("WARNING Loading custom .yamllint config file,", False), ("WARNING Listing 1 violation(s) that are fatal", False), ("DEBUG ", True), ("INFO ", True), ], ), ( "-q", [ ("WARNING ", True), ("DEBUG ", True), ("INFO ", True), ], ), ( "-qq", [ ("WARNING ", True), ("DEBUG ", True), ("INFO ", True), ], ), ( "-v", [ ("WARNING Loading custom .yamllint config file,", False), ("WARNING Listing 1 violation(s) that are fatal", False), ("INFO Added ANSIBLE_LIBRARY=", False), ("DEBUG ", True), ], ), ( "-vv", [ ("WARNING Loading custom .yamllint config file,", False), ("WARNING Listing 1 violation(s) that are fatal", False), ("INFO Added ANSIBLE_LIBRARY=", False), ("DEBUG Effective yamllint rules used", False), ], ), ( "-vvvvvvvvvvvvvvvvvvvvvvvvv", [ ("WARNING Loading custom .yamllint config file,", False), ("WARNING Listing 1 violation(s) that are fatal", False), ("INFO Added ANSIBLE_LIBRARY=", False), ("DEBUG Effective yamllint rules used", False), ], ), ), ids=( "default verbosity", "quiet", "really quiet", "loquacious", "really loquacious", 'really loquacious but with more "v"s -- same as -vv', ), ) def test_default_verbosity(verbosity: str, substrs: List[Tuple[str, bool]]) -> None: """Checks that our default verbosity displays (only) warnings.""" # Piggyback off the .yamllint in the root of the repo, just for testing. # We'll "override" it with the one in the fixture, to produce a warning. cwd = os.path.realpath( os.path.join(os.path.dirname(os.path.realpath(__file__)), "..") ) fakerole = os.path.join("test", "fixtures", "verbosity-tests") if verbosity: result = run_ansible_lint(verbosity, fakerole, cwd=cwd) else: result = run_ansible_lint(fakerole, cwd=cwd) for (substr, invert) in substrs: if invert: assert substr not in result.stderr, result.stderr else: assert substr in result.stderr, result.stderr �������������������ansible-lint-5.4.0/test/TestWithSkipTagId.py��������������������������������������������������������0000664�0000000�0000000�00000002662�14201712607�0020753�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# pylint: disable=preferred-module # FIXME: remove once migrated per GH-725 import unittest from ansiblelint.rules import RulesCollection from ansiblelint.rules.YamllintRule import YamllintRule from ansiblelint.runner import Runner class TestWithSkipTagId(unittest.TestCase): collection = RulesCollection() collection.register(YamllintRule()) file = "examples/playbooks/with-skip-tag-id.yml" def test_negative_no_param(self) -> None: bad_runner = Runner(self.file, rules=self.collection) errs = bad_runner.run() assert len(errs) > 0 def test_negative_with_id(self) -> None: with_id = "yaml" bad_runner = Runner(self.file, rules=self.collection, tags=frozenset([with_id])) errs = bad_runner.run() assert len(errs) > 0 def test_negative_with_tag(self) -> None: with_tag = "trailing-spaces" bad_runner = Runner( self.file, rules=self.collection, tags=frozenset([with_tag]) ) errs = bad_runner.run() assert len(errs) > 0 def test_positive_skip_id(self) -> None: skip_id = "yaml" good_runner = Runner(self.file, rules=self.collection, skip_list=[skip_id]) assert [] == good_runner.run() def test_positive_skip_tag(self) -> None: skip_tag = "trailing-spaces" good_runner = Runner(self.file, rules=self.collection, skip_list=[skip_tag]) assert [] == good_runner.run() ������������������������������������������������������������������������������ansible-lint-5.4.0/test/__init__.py�����������������������������������������������������������������0000664�0000000�0000000�00000000072�14201712607�0017170�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Use ansiblelint.testing instead for test reusables.""" ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/bar.txt���������������������������������������������������������������������0000664�0000000�0000000�00000000011�14201712607�0016355�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Bar file �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/conftest.py�����������������������������������������������������������������0000664�0000000�0000000�00000000422�14201712607�0017255�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������import os from contextlib import contextmanager from typing import Iterator @contextmanager def cwd(path: str) -> Iterator[None]: """Context manager for chdir.""" oldpwd = os.getcwd() os.chdir(path) try: yield finally: os.chdir(oldpwd) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/custom_rules/���������������������������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0017604�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/custom_rules/__init__.py����������������������������������������������������0000664�0000000�0000000�00000000031�14201712607�0021707�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Dummy test module.""" �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/custom_rules/example_com/���������������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0022075�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/custom_rules/example_com/ExampleComRule.py����������������������������������0000664�0000000�0000000�00000002374�14201712607�0025337�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2020, Ansible Project # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """A dummy custom rule module #2.""" from ansiblelint.rules import AnsibleLintRule class ExampleComRule(AnsibleLintRule): """A dummy custom rule class.""" id = "100002" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/custom_rules/example_com/__init__.py����������������������������������������0000664�0000000�0000000�00000000036�14201712607�0024205�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""A dummy test module #2.""" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/custom_rules/example_inc/���������������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0022070�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/custom_rules/example_inc/CustomRule.py��������������������������������������0000664�0000000�0000000�00000002361�14201712607�0024546�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Copyright (c) 2020, Ansible Project # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. """Dummy custom rule module.""" from ansiblelint.rules import AnsibleLintRule class CustomRule(AnsibleLintRule): """Dummy custom rule class.""" id = "100001" �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/custom_rules/example_inc/__init__.py����������������������������������������0000664�0000000�0000000�00000000031�14201712607�0024173�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Dummy test module.""" �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/eco/������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0015626�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/eco/bootstrap.result��������������������������������������������������������0000664�0000000�0000000�00000000000�14201712607�0021071�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/eco/colsystem.result��������������������������������������������������������0000664�0000000�0000000�00000000405�14201712607�0021107�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������load-failure: [Errno 2] No such file or directory: '/Users/ssbarnea/.cache/ansible-lint-eco/colsystem/tests/ansible-lint.yml' (filenotfounderror) .ansible-lint:1 yaml: line too long (576 > 160 characters) (line-length) playbooks/molecule/sudo/molecule.yml:17 �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/eco/debops.result�����������������������������������������������������������0000664�0000000�0000000�00000000000�14201712607�0020330�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/eco/docker-rootless.result��������������������������������������������������0000664�0000000�0000000�00000000000�14201712607�0022173�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/eco/hardening.result��������������������������������������������������������0000664�0000000�0000000�00000000512�14201712607�0021023�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������yaml: line too long (143 > 120 characters) (line-length) defaults/main/sshd.yml:20 yaml: line too long (122 > 120 characters) (line-length) tasks/auditd.yml:21 yaml: line too long (129 > 120 characters) (line-length) tasks/packagemgmt.yml:164 yaml: line too long (162 > 120 characters) (line-length) tasks/packagemgmt.yml:192 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/eco/mysql.result������������������������������������������������������������0000664�0000000�0000000�00000000600�14201712607�0020227�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ignore-errors: Use failed_when and specify error conditions instead of using ignore_errors ../../ansible-lint/314d98/roles/geerlingguy.mysql/tasks/replication.yml:37 Task/Handler: Configure replication on the slave. ignore-errors: Use failed_when and specify error conditions instead of using ignore_errors tasks/replication.yml:37 Task/Handler: Configure replication on the slave. ��������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/eco/tripleo-ansible.result��������������������������������������������������0000664�0000000�0000000�00000000000�14201712607�0022145�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/eco/zuul-jobs.result��������������������������������������������������������0000664�0000000�0000000�00000002312�14201712607�0021016�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������roles/emit-job-header/tasks/main.yaml:26: ignore-errors Use failed_when and specify error conditions instead of using ignore_errors roles/ensure-pip/tasks/Debian.yaml:17: ignore-errors Use failed_when and specify error conditions instead of using ignore_errors test-playbooks/registry/roles/ensure-registry-cert/tasks/main.yaml:1: risky-file-permissions File permissions unset or incorrect test-playbooks/registry/roles/ensure-registry-cert/tasks/main.yaml:6: risky-file-permissions File permissions unset or incorrect test-playbooks/registry/roles/run-test-intermediate-registry/tasks/main.yaml:1: no-loop-var-prefix Role loop_var should use configured prefix.: zj_ test-playbooks/registry/roles/run-test-intermediate-registry/tasks/main.yaml:1: risky-file-permissions File permissions unset or incorrect test-playbooks/registry/roles/run-test-intermediate-registry/tasks/main.yaml:17: risky-file-permissions File permissions unset or incorrect test-playbooks/registry/roles/run-test-intermediate-registry/tasks/main.yaml:25: risky-file-permissions File permissions unset or incorrect test-playbooks/registry/roles/run-test-intermediate-registry/tasks/main.yaml:30: risky-file-permissions File permissions unset or incorrect ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/�������������������������������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0016731�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/ansible-config-invalid.yml�����������������������������������������0000664�0000000�0000000�00000000060�14201712607�0023754�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# invalid .ansible-lint config file - foo - bar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/ansible-config.yml�������������������������������������������������0000664�0000000�0000000�00000000063�14201712607�0022333�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- verbosity: 1 # vim: et:sw=2:syntax=yaml:ts=2: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/config-with-extra-vars.yml�����������������������������������������0000664�0000000�0000000�00000000067�14201712607�0023767�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- extra_vars: foo: bar knights_favorite_word: NI �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/config-with-relative-path.yml��������������������������������������0000664�0000000�0000000�00000000127�14201712607�0024435�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- exclude_paths: - ../../examples/roles/test-role/ # vim: et:sw=2:syntax=yaml:ts=2: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/exclude-paths-with-expands.yml�������������������������������������0000664�0000000�0000000�00000000137�14201712607�0024634�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- exclude_paths: - ~/.ansible/roles - $HOME/.ansible/roles # vim: et:sw=2:syntax=yaml:ts=2: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/exclude-paths.yml��������������������������������������������������0000664�0000000�0000000�00000000073�14201712607�0022222�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- exclude_paths: - ../ # vim: et:sw=2:syntax=yaml:ts=2: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/parseable.yml������������������������������������������������������0000664�0000000�0000000�00000000066�14201712607�0021414�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- parseable: true # vim: et:sw=2:syntax=yaml:ts=2: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/quiet.yml����������������������������������������������������������0000664�0000000�0000000�00000000062�14201712607�0020601�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- quiet: true # vim: et:sw=2:syntax=yaml:ts=2: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/rulesdir-defaults.yml����������������������������������������������0000664�0000000�0000000�00000000122�14201712607�0023105�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- rulesdir: - ./rules use_default_rules: true # vim: et:sw=2:syntax=yaml:ts=2: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/rulesdir.yml�������������������������������������������������������0000664�0000000�0000000�00000000072�14201712607�0021304�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- rulesdir: - ./rules # vim: et:sw=2:syntax=yaml:ts=2: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/show-abspath.yml���������������������������������������������������0000664�0000000�0000000�00000000100�14201712607�0022043�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- display_relative_path: false # vim: et:sw=2:syntax=2:ts=2: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/show-relpath.yml���������������������������������������������������0000664�0000000�0000000�00000000077�14201712607�0022075�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- display_relative_path: true # vim: et:sw=2:syntax=2:ts=2: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/skip-tags.yml������������������������������������������������������0000664�0000000�0000000�00000000075�14201712607�0021360�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- skip_list: - "bad_tag" # vim: et:sw=2:syntax=yaml:ts=2: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/tags.yml�����������������������������������������������������������0000664�0000000�0000000�00000000102�14201712607�0020403�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- tags: - skip_ansible_lint # vim: et:sw=2:syntax=yaml:ts=2: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/unknown-type.yml���������������������������������������������������0000664�0000000�0000000�00000000016�14201712607�0022127�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- some: map ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/verbosity-tests/���������������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0022117�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/verbosity-tests/.yamllint������������������������������������������0000664�0000000�0000000�00000000003�14201712607�0023742�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������{} �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/fixtures/verbosity.yml������������������������������������������������������0000664�0000000�0000000�00000000063�14201712607�0021501�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- verbosity: 1 # vim: et:sw=2:syntax=yaml:ts=2: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/foo.txt���������������������������������������������������������������������0000664�0000000�0000000�00000000011�14201712607�0016374�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Foo file �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/include-import-role.yml�����������������������������������������������������0000664�0000000�0000000�00000000365�14201712607�0021501�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������- hosts: all vars: var_is_set: no tasks: - import_role: name: test-role - hosts: all vars: var_is_set: no tasks: - include_role: name: test-role tasks_from: world when: "{{ var_is_set }}" ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/��������������������������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0017622�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/README.md�����������������������������������������������������0000664�0000000�0000000�00000000577�14201712607�0021112�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������The reason that every roles test gets its own directory is that while they use the same three roles, the way the tests work makes sure that when the second one runs, the roles and their local plugins from the first test are still known to Ansible. For that reason, their names reflect the directory they are in to make sure that tests don't use modules/plugins found by other tests. ���������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/collections/��������������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0022140�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/collections/ansible_collections/������������������������������0000775�0000000�0000000�00000000000�14201712607�0026153�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/collections/ansible_collections/testns/�����������������������0000775�0000000�0000000�00000000000�14201712607�0027473�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/collections/ansible_collections/testns/testcoll/��������������0000775�0000000�0000000�00000000000�14201712607�0031324�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/collections/ansible_collections/testns/testcoll/galaxy.yml����0000664�0000000�0000000�00000000060�14201712607�0033330�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������namespace: testns name: testcoll version: 0.1.0 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/collections/ansible_collections/testns/testcoll/plugins/������0000775�0000000�0000000�00000000000�14201712607�0033005�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������filter/���������������������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0034213�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�ansible-lint-5.4.0/test/local-content/collections/ansible_collections/testns/testcoll/plugins��������������������������������������������������������������������������test_filter.py��������������������������������������������������������������������������������������0000664�0000000�0000000�00000000432�14201712607�0037110�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�ansible-lint-5.4.0/test/local-content/collections/ansible_collections/testns/testcoll/plugins/filter�������������������������������������������������������������������"""A filter plugin.""" def a_test_filter(a, b): """Return a string containing both a and b.""" return "{0}:{1}".format(a, b) class FilterModule: """Filter plugin.""" def filters(self): """Return filters.""" return {"test_filter": a_test_filter} ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������modules/��������������������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0034376�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�ansible-lint-5.4.0/test/local-content/collections/ansible_collections/testns/testcoll/plugins��������������������������������������������������������������������������test_module_2.py������������������������������������������������������������������������������������0000664�0000000�0000000�00000000370�14201712607�0037515�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�ansible-lint-5.4.0/test/local-content/collections/ansible_collections/testns/testcoll/plugins/modules������������������������������������������������������������������#!/usr/bin/python """A module.""" from ansible.module_utils.basic import AnsibleModule def main() -> None: """Execute module.""" module = AnsibleModule(dict()) module.exit_json(msg="Hello 2!") if __name__ == "__main__": main() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-collection.yml�������������������������������������������0000664�0000000�0000000�00000000456�14201712607�0023462�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- - name: Use module and filter plugin from local collection hosts: localhost tasks: - name: Use module from local collection testns.testcoll.test_module_2: - name: Use filter from local collection assert: that: - 1 | testns.testcoll.test_filter(2) == '1:2' ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/�����������������������������������0000775�0000000�0000000�00000000000�14201712607�0024753�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/�����������������������������0000775�0000000�0000000�00000000000�14201712607�0026077�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/role1/�����������������������0000775�0000000�0000000�00000000000�14201712607�0027121�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/role1/library/���������������0000775�0000000�0000000�00000000000�14201712607�0030565�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������test_module_1_failed_complete.py��������������������������������������������������������������������0000664�0000000�0000000�00000000370�14201712607�0037020�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/role1/library�����������������������������������������������������������������������������������#!/usr/bin/python """A module.""" from ansible.module_utils.basic import AnsibleModule def main() -> None: """Execute module.""" module = AnsibleModule(dict()) module.exit_json(msg="Hello 1!") if __name__ == "__main__": main() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/role1/tasks/�����������������0000775�0000000�0000000�00000000000�14201712607�0030246�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/role1/tasks/main.yml���������0000664�0000000�0000000�00000000100�14201712607�0031704�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- - name: Use local module 1 test_module_1_failed_complete: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/role2/�����������������������0000775�0000000�0000000�00000000000�14201712607�0027122�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/role2/tasks/�����������������0000775�0000000�0000000�00000000000�14201712607�0030247�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/role2/tasks/main.yml���������0000664�0000000�0000000�00000000712�14201712607�0031716�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- - name: Use local module from other role that has been included before this one # If it has not been included before, loading this role fails! test_module_1_failed_complete: - name: Use local module from other role that has been included before this one # If it has not been included before, loading this role fails! test_module_3_failed_complete: - name: Use local test plugin assert: that: - "'2' is b_test_failed_complete '12345'" ������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/role2/test_plugins/����������0000775�0000000�0000000�00000000000�14201712607�0031642�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������b_failed_complete.py��������������������������������������������������������������������������������0000664�0000000�0000000�00000000457�14201712607�0035560�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/role2/test_plugins������������������������������������������������������������������������������"""A test plugin.""" def compatibility_in_test(a, b): """Return True when a is contained in b.""" return a in b class TestModule: """Test plugin.""" def tests(self): """Return tests.""" return { "b_test_failed_complete": compatibility_in_test, } �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/role3/�����������������������0000775�0000000�0000000�00000000000�14201712607�0027123�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/role3/library/���������������0000775�0000000�0000000�00000000000�14201712607�0030567�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������test_module_3_failed_complete.py��������������������������������������������������������������������0000664�0000000�0000000�00000000370�14201712607�0037024�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/role3/library�����������������������������������������������������������������������������������#!/usr/bin/python """A module.""" from ansible.module_utils.basic import AnsibleModule def main() -> None: """Execute module.""" module = AnsibleModule(dict()) module.exit_json(msg="Hello 3!") if __name__ == "__main__": main() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/role3/tasks/�����������������0000775�0000000�0000000�00000000000�14201712607�0030250�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed-complete/roles/role3/tasks/main.yml���������0000664�0000000�0000000�00000000100�14201712607�0031706�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- - name: Use local module 3 test_module_3_failed_complete: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/��������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0023145�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/��������������������������������������0000775�0000000�0000000�00000000000�14201712607�0024271�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/role1/��������������������������������0000775�0000000�0000000�00000000000�14201712607�0025313�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/role1/library/������������������������0000775�0000000�0000000�00000000000�14201712607�0026757�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/role1/library/test_module_1_failed.py�0000664�0000000�0000000�00000000370�14201712607�0033401�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python """A module.""" from ansible.module_utils.basic import AnsibleModule def main() -> None: """Execute module.""" module = AnsibleModule(dict()) module.exit_json(msg="Hello 1!") if __name__ == "__main__": main() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/role1/tasks/��������������������������0000775�0000000�0000000�00000000000�14201712607�0026440�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/role1/tasks/main.yml������������������0000664�0000000�0000000�00000000067�14201712607�0030112�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- - name: Use local module 1 test_module_1_failed: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/role2/��������������������������������0000775�0000000�0000000�00000000000�14201712607�0025314�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/role2/tasks/��������������������������0000775�0000000�0000000�00000000000�14201712607�0026441�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/role2/tasks/main.yml������������������0000664�0000000�0000000�00000000657�14201712607�0030120�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- - name: Use local module from other role that has been included before this one # If it has not been included before, loading this role fails! test_module_1_failed: - name: Use local module from other role that has been included before this one # If it has not been included before, loading this role fails! test_module_3_failed: - name: Use local test plugin assert: that: - "'2' is b_test_failed '12345'" ���������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/role2/test_plugins/�������������������0000775�0000000�0000000�00000000000�14201712607�0030034�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/role2/test_plugins/b_failed.py��������0000664�0000000�0000000�00000000446�14201712607�0032137�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""A test plugin.""" def compatibility_in_test(a, b): """Return True when a is contained in b.""" return a in b class TestModule: """Test plugin.""" def tests(self): """Return tests.""" return { "b_test_failed": compatibility_in_test, } ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/role3/��������������������������������0000775�0000000�0000000�00000000000�14201712607�0025315�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/role3/library/������������������������0000775�0000000�0000000�00000000000�14201712607�0026761�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/role3/library/test_module_3_failed.py�0000664�0000000�0000000�00000000370�14201712607�0033405�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/python """A module.""" from ansible.module_utils.basic import AnsibleModule def main() -> None: """Execute module.""" module = AnsibleModule(dict()) module.exit_json(msg="Hello 3!") if __name__ == "__main__": main() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/role3/tasks/��������������������������0000775�0000000�0000000�00000000000�14201712607�0026442�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/roles/role3/tasks/main.yml������������������0000664�0000000�0000000�00000000067�14201712607�0030114�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- - name: Use local module 3 test_module_3_failed: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-failed/test.yml������������������������������������0000664�0000000�0000000�00000000216�14201712607�0024646�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- - name: Use roles with local module in wrong order, so that Ansible fails hosts: localhost roles: - role2 - role3 - role1 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-success/�������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0023371�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-success/roles/�������������������������������������0000775�0000000�0000000�00000000000�14201712607�0024515�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-success/roles/role1/�������������������������������0000775�0000000�0000000�00000000000�14201712607�0025537�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-success/roles/role1/library/�����������������������0000775�0000000�0000000�00000000000�14201712607�0027203�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������test_module_1_success.py����������������������������������������������������������������������������0000664�0000000�0000000�00000000370�14201712607�0033772�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�ansible-lint-5.4.0/test/local-content/test-roles-success/roles/role1/library�������������������������������������������������������������������������������������������#!/usr/bin/python """A module.""" from ansible.module_utils.basic import AnsibleModule def main() -> None: """Execute module.""" module = AnsibleModule(dict()) module.exit_json(msg="Hello 1!") if __name__ == "__main__": main() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-success/roles/role1/tasks/�������������������������0000775�0000000�0000000�00000000000�14201712607�0026664�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-success/roles/role1/tasks/main.yml�����������������0000664�0000000�0000000�00000000070�14201712607�0030330�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- - name: Use local module 1 test_module_1_success: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-success/roles/role2/�������������������������������0000775�0000000�0000000�00000000000�14201712607�0025540�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-success/roles/role2/tasks/�������������������������0000775�0000000�0000000�00000000000�14201712607�0026665�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-success/roles/role2/tasks/main.yml�����������������0000664�0000000�0000000�00000000662�14201712607�0030340�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- - name: Use local module from other role that has been included before this one # If it has not been included before, loading this role fails! test_module_1_success: - name: Use local module from other role that has been included before this one # If it has not been included before, loading this role fails! test_module_3_success: - name: Use local test plugin assert: that: - "'2' is b_test_success '12345'" ������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-success/roles/role2/test_plugins/������������������0000775�0000000�0000000�00000000000�14201712607�0030260�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-success/roles/role2/test_plugins/b_success.py������0000664�0000000�0000000�00000000447�14201712607�0032610�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""A test plugin.""" def compatibility_in_test(a, b): """Return True when a is contained in b.""" return a in b class TestModule: """Test plugin.""" def tests(self): """Return tests.""" return { "b_test_success": compatibility_in_test, } �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-success/roles/role3/�������������������������������0000775�0000000�0000000�00000000000�14201712607�0025541�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-success/roles/role3/library/�����������������������0000775�0000000�0000000�00000000000�14201712607�0027205�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������test_module_3_success.py����������������������������������������������������������������������������0000664�0000000�0000000�00000000370�14201712607�0033776�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000�ansible-lint-5.4.0/test/local-content/test-roles-success/roles/role3/library�������������������������������������������������������������������������������������������#!/usr/bin/python """A module.""" from ansible.module_utils.basic import AnsibleModule def main() -> None: """Execute module.""" module = AnsibleModule(dict()) module.exit_json(msg="Hello 3!") if __name__ == "__main__": main() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-success/roles/role3/tasks/�������������������������0000775�0000000�0000000�00000000000�14201712607�0026666�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/local-content/test-roles-success/roles/role3/tasks/main.yml�����������������0000664�0000000�0000000�00000000070�14201712607�0030332�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- - name: Use local module 3 test_module_3_success: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/rules/����������������������������������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0016212�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/rules/EMatcherRule.py�������������������������������������������������������0000664�0000000�0000000�00000000560�14201712607�0021105�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from ansiblelint.rules import AnsibleLintRule class EMatcherRule(AnsibleLintRule): id = "TEST0001" description = ( "This is a test custom rule that looks for lines " + "containing BANNED string" ) shortdesc = "BANNED string found" tags = ["fake", "dummy", "test1"] def match(self, line: str) -> bool: return "BANNED" in line ������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/rules/RawTaskRule.py��������������������������������������������������������0000664�0000000�0000000�00000001623�14201712607�0020772�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Test Rule that needs_raw_task.""" from typing import Any, Dict, Optional, Union from ansiblelint.file_utils import Lintable from ansiblelint.rules import AnsibleLintRule class RawTaskRule(AnsibleLintRule): """Test rule that inspects the raw task.""" id = "TEST0003" shortdesc = "Test rule that inspects the raw task" description = ( "This is a test rule that looks in a raw task to flag raw action params." ) tags = ["fake", "dummy", "test3"] needs_raw_task = True def matchtask( self, task: Dict[str, Any], file: Optional[Lintable] = None ) -> Union[bool, str]: """Match a task using __raw_task__ to inspect the module params type.""" raw_task = task["__raw_task__"] module = task["action"]["__ansible_module_original__"] found_raw_task_params = not isinstance(raw_task[module], dict) return found_raw_task_params �������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/rules/UnsetVariableMatcherRule.py�������������������������������������������0000664�0000000�0000000�00000000625�14201712607�0023467�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������from ansiblelint.rules import AnsibleLintRule class UnsetVariableMatcherRule(AnsibleLintRule): id = "TEST0002" shortdesc = "Line contains untemplated variable" description = ( "This is a test rule that looks for lines " + "post templating that still contain {{" ) tags = ["fake", "dummy", "test2"] def match(self, line: str) -> bool: return "{{" in line �����������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/rules/__init__.py�����������������������������������������������������������0000664�0000000�0000000�00000000143�14201712607�0020321�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Test rules resources.""" __all__ = ["EMatcherRule", "RawTaskRule", "UnsetVariableMatcherRule"] �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/test_boot.py����������������������������������������������������������������0000664�0000000�0000000�00000001232�14201712607�0017432�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Test related to ansiblelint initialization.""" import sys from subprocess import run import pytest @pytest.mark.parametrize("module", ("ansiblelint", "ansiblelint.__main__")) def test_import(module: str) -> None: """Safeguard that Ansible does not become an implicit import.""" # We cannot test it directly because our test fixtures already do # import Ansible, so we need to test this using a separated process. result = run( [ sys.executable, "-c", f"import {module}, sys; sys.exit(0 if 'ansible' not in sys.modules else 1)", ], check=False, ) assert result.returncode == 0 ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/test_eco.py�����������������������������������������������������������������0000664�0000000�0000000�00000003566�14201712607�0017251�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Test a set of 3rd party Ansible repositories for possible regressions.""" import os import pathlib import subprocess import pytest eco_repos = { "bootstrap": [ "https://github.com/robertdebock/ansible-role-bootstrap", "robertdebock", ], "colsystem": [ "https://github.com/devroles/ansible_collection_system", "greg-hellings", ], "debops": ["https://github.com/debops/debops", "drybjed"], "docker-rootless": [ "https://github.com/konstruktoid/ansible-docker-rootless", "konstruktoid", ], "tripleo-ansible": ["https://opendev.org/openstack/tripleo-ansible", "ssbarnea"], "hardening": [ "https://github.com/konstruktoid/ansible-role-hardening", "konstruktoid", ], "mysql": [ "https://github.com/geerlingguy/ansible-role-mysql.git", "geerlingguy", ], "zuul-jobs": ["https://opendev.org/zuul/zuul-jobs", "ssbarnea"], } @pytest.mark.eco() @pytest.mark.parametrize(("repo"), (eco_repos.keys())) def test_eco(repo: str) -> None: """Test a set of 3rd party Ansible repositories for possible regressions.""" url = eco_repos[repo][0] cache_dir = os.path.expanduser("~/.cache/ansible-lint-eco") my_dir = (pathlib.Path(__file__).parent / "eco").resolve() os.makedirs(cache_dir, exist_ok=True) # clone repo if os.path.exists(f"{cache_dir}/{repo}/.git"): subprocess.run("git pull", cwd=f"{cache_dir}/{repo}", shell=True, check=True) else: subprocess.run(f"git clone {url} {cache_dir}/{repo}", shell=True, check=True) # run ansible lint subprocess.run( f"ansible-lint 2>&1 > {my_dir}/{repo}.result", shell=True, check=False, cwd=f"{cache_dir}/{repo}", ) # fail if result is different than our expected one: subprocess.run(f"git diff HEAD test/eco/{repo}.result", shell=True, check=True) ������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/test_file_utils.py����������������������������������������������������������0000664�0000000�0000000�00000016315�14201712607�0020636�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Tests for file utility functions.""" import os from argparse import Namespace from pathlib import Path from typing import Any, Dict, Union import pytest from _pytest.capture import CaptureFixture from _pytest.logging import LogCaptureFixture from _pytest.monkeypatch import MonkeyPatch from ansiblelint import cli, file_utils from ansiblelint.__main__ import initialize_logger from ansiblelint.constants import FileType from ansiblelint.file_utils import ( Lintable, expand_path_vars, expand_paths_vars, guess_project_dir, normpath, ) from .conftest import cwd @pytest.mark.parametrize( "path", ( pytest.param(Path("a/b/../"), id="pathlib.Path"), pytest.param("a/b/../", id="str"), ), ) def test_normpath_with_path_object(path: str) -> None: """Ensure that relative parent dirs are normalized in paths.""" assert normpath(path) == "a" def test_expand_path_vars(monkeypatch: MonkeyPatch) -> None: """Ensure that tilde and env vars are expanded in paths.""" test_path = "/test/path" monkeypatch.setenv("TEST_PATH", test_path) assert expand_path_vars("~") == os.path.expanduser("~") assert expand_path_vars("$TEST_PATH") == test_path @pytest.mark.parametrize( ("test_path", "expected"), ( pytest.param(Path("$TEST_PATH"), "/test/path", id="pathlib.Path"), pytest.param("$TEST_PATH", "/test/path", id="str"), pytest.param(" $TEST_PATH ", "/test/path", id="stripped-str"), pytest.param("~", os.path.expanduser("~"), id="home"), ), ) def test_expand_paths_vars( test_path: Union[str, Path], expected: str, monkeypatch: MonkeyPatch ) -> None: """Ensure that tilde and env vars are expanded in paths lists.""" monkeypatch.setenv("TEST_PATH", "/test/path") assert expand_paths_vars([test_path]) == [expected] # type: ignore @pytest.mark.parametrize( ("reset_env_var", "message_prefix"), ( # simulate absence of git command ("PATH", "Failed to locate command: "), # simulate a missing git repo ("GIT_DIR", "Looking up for files"), ), ids=("no-git-cli", "outside-git-repo"), ) def test_discover_lintables_git_verbose( reset_env_var: str, message_prefix: str, monkeypatch: MonkeyPatch, caplog: LogCaptureFixture, ) -> None: """Ensure that autodiscovery lookup failures are logged.""" options = cli.get_config(["-v"]) initialize_logger(options.verbosity) monkeypatch.setenv(reset_env_var, "") file_utils.discover_lintables(options) assert any(m[2].startswith("Looking up for files") for m in caplog.record_tuples) assert any(m.startswith(message_prefix) for m in caplog.messages) @pytest.mark.parametrize( "is_in_git", (True, False), ids=("in Git", "outside Git"), ) def test_discover_lintables_silent( is_in_git: bool, monkeypatch: MonkeyPatch, capsys: CaptureFixture[str] ) -> None: """Verify that no stderr output is displayed while discovering yaml files. (when the verbosity is off, regardless of the Git or Git-repo presence) Also checks expected number of files are detected. """ options = cli.get_config([]) test_dir = Path(__file__).resolve().parent lint_path = test_dir / ".." / "examples" / "roles" / "test-role" if not is_in_git: monkeypatch.setenv("GIT_DIR", "") yaml_count = len(list(lint_path.glob("**/*.yml"))) + len( list(lint_path.glob("**/*.yaml")) ) monkeypatch.chdir(str(lint_path)) files = file_utils.discover_lintables(options) stderr = capsys.readouterr().err assert not stderr, "No stderr output is expected when the verbosity is off" assert ( len(files) == yaml_count ), "Expected to find {yaml_count} yaml files in {lint_path}".format_map( locals(), ) def test_discover_lintables_umlaut(monkeypatch: MonkeyPatch) -> None: """Verify that filenames containing German umlauts are not garbled by the discover_lintables.""" options = cli.get_config([]) test_dir = Path(__file__).resolve().parent lint_path = test_dir / ".." / "examples" / "playbooks" monkeypatch.chdir(str(lint_path)) files = file_utils.discover_lintables(options) assert '"with-umlaut-\\303\\244.yml"' not in files assert "with-umlaut-ä.yml" in files @pytest.mark.parametrize( ("path", "kind"), ( ("foo/playbook.yml", "playbook"), ("playbooks/foo.yml", "playbook"), ("playbooks/roles/foo.yml", "yaml"), # the only yml file that is not a playbook inside molecule/ folders (".config/molecule/config.yml", "yaml"), # molecule shared config ("roles/foo/molecule/scen1/base.yml", "yaml"), # molecule scenario base config ("roles/foo/molecule/scen1/molecule.yml", "yaml"), # molecule scenario config ("roles/foo/molecule/scen2/foobar.yml", "playbook"), # custom playbook name ("roles/foo/molecule/scen3/converge.yml", "playbook"), # common playbook name ("roles/foo/molecule/scen3/requirements.yml", "requirements"), # requirements ("roles/foo/molecule/scen3/collections.yml", "requirements"), # requirements # tasks files: ("tasks/directory with spaces/main.yml", "tasks"), # tasks ("tasks/requirements.yml", "tasks"), # tasks # requirements (we do not support includes yet) ("requirements.yml", "requirements"), # collection requirements ("roles/foo/meta/requirements.yml", "requirements"), # inside role requirements # Undeterminable files: ("test/fixtures/unknown-type.yml", "yaml"), ("releasenotes/notes/run-playbooks-refactor.yaml", "reno"), # reno ("examples/host_vars/localhost.yml", "vars"), ("examples/group_vars/all.yml", "vars"), ("examples/playbooks/vars/other.yml", "vars"), ("examples/playbooks/vars/subfolder/settings.yml", "vars"), # deep vars ("molecule/scenario/collections.yml", "requirements"), # deprecated 2.8 format ( "../roles/geerlingguy.mysql/tasks/configure.yml", "tasks", ), # relative path involved ("galaxy.yml", "galaxy"), ("foo.j2.yml", "jinja2"), ("foo.yml.j2", "jinja2"), ("foo.j2.yaml", "jinja2"), ("foo.yaml.j2", "jinja2"), ), ) def test_default_kinds(monkeypatch: MonkeyPatch, path: str, kind: FileType) -> None: """Verify auto-detection logic based on DEFAULT_KINDS.""" options = cli.get_config([]) def mockreturn(options: Namespace) -> Dict[str, Any]: return {path: kind} # assert Lintable is able to determine file type lintable_detected = Lintable(path) lintable_expected = Lintable(path, kind=kind) assert lintable_detected == lintable_expected monkeypatch.setattr(file_utils, "discover_lintables", mockreturn) result = file_utils.discover_lintables(options) # get_lintable could return additional files and we only care to see # that the given file is among the returned list. assert lintable_detected.name in result assert lintable_detected.kind == result[lintable_expected.name] def test_guess_project_dir(tmp_path: Path) -> None: """Verify guess_project_dir().""" with cwd(str(tmp_path)): result = guess_project_dir(None) assert result == str(tmp_path) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/test/test_main.py����������������������������������������������������������������0000664�0000000�0000000�00000001750�14201712607�0017420�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Tests related to ansiblelint.__main__ module.""" import os import subprocess import sys from pathlib import Path import pytest @pytest.mark.parametrize( ("expected_warning"), (False, True), ids=("normal", "isolated"), ) def test_call_from_outside_venv(expected_warning: bool) -> None: """Asserts ability to be called w/ or w/o venv activation.""" env = None if expected_warning: env = {"HOME": Path.home()} py_path = os.path.dirname(sys.executable) # Passing custom env prevents the process from inheriting PATH or other # environment variables from the current process, so we emulate being # called from outside the venv. proc = subprocess.run( [f"{py_path}/ansible-lint", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, env=env, ) warning_found = "PATH altered to include" in proc.stderr assert warning_found is expected_warning ������������������������ansible-lint-5.4.0/test/test_prerun.py��������������������������������������������������������������0000664�0000000�0000000�00000013710�14201712607�0020006�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Tests related to prerun part of the linter.""" import os import subprocess from typing import List import pytest from _pytest.monkeypatch import MonkeyPatch from flaky import flaky from ansiblelint import prerun from ansiblelint.constants import INVALID_PREREQUISITES_RC from ansiblelint.testing import run_ansible_lint # https://github.com/box/flaky/issues/170 @flaky(max_runs=3) # type: ignore def test_prerun_reqs_v1() -> None: """Checks that the linter can auto-install requirements v1 when found.""" cwd = os.path.realpath( os.path.join( os.path.dirname(os.path.realpath(__file__)), "..", "examples", "reqs_v1" ) ) result = run_ansible_lint("-v", ".", cwd=cwd) assert "Running ansible-galaxy role install" in result.stderr, result.stderr assert ( "Running ansible-galaxy collection install" not in result.stderr ), result.stderr assert result.returncode == 0, result @flaky(max_runs=3) # type: ignore def test_prerun_reqs_v2() -> None: """Checks that the linter can auto-install requirements v2 when found.""" cwd = os.path.realpath( os.path.join( os.path.dirname(os.path.realpath(__file__)), "..", "examples", "reqs_v2" ) ) result = run_ansible_lint("-v", ".", cwd=cwd) assert "Running ansible-galaxy role install" in result.stderr, result.stderr assert "Running ansible-galaxy collection install" in result.stderr, result.stderr assert result.returncode == 0, result def test__update_env_no_old_value_no_default_no_value(monkeypatch: MonkeyPatch) -> None: """Make sure empty value does not touch environment.""" monkeypatch.delenv("DUMMY_VAR", raising=False) prerun._update_env("DUMMY_VAR", []) assert "DUMMY_VAR" not in os.environ def test__update_env_no_old_value_no_value(monkeypatch: MonkeyPatch) -> None: """Make sure empty value does not touch environment.""" monkeypatch.delenv("DUMMY_VAR", raising=False) prerun._update_env("DUMMY_VAR", [], "a:b") assert "DUMMY_VAR" not in os.environ def test__update_env_no_default_no_value(monkeypatch: MonkeyPatch) -> None: """Make sure empty value does not touch environment.""" monkeypatch.setenv("DUMMY_VAR", "a:b") prerun._update_env("DUMMY_VAR", []) assert os.environ["DUMMY_VAR"] == "a:b" @pytest.mark.parametrize( ("value", "result"), ( (["a"], "a"), (["a", "b"], "a:b"), (["a", "b", "c"], "a:b:c"), ), ) def test__update_env_no_old_value_no_default( monkeypatch: MonkeyPatch, value: List[str], result: str ) -> None: """Values are concatenated using : as the separator.""" monkeypatch.delenv("DUMMY_VAR", raising=False) prerun._update_env("DUMMY_VAR", value) assert os.environ["DUMMY_VAR"] == result @pytest.mark.parametrize( ("default", "value", "result"), ( ("a:b", ["c"], "a:b:c"), ("a:b", ["c:d"], "a:b:c:d"), ), ) def test__update_env_no_old_value( monkeypatch: MonkeyPatch, default: str, value: List[str], result: str ) -> None: """Values are appended to default value.""" monkeypatch.delenv("DUMMY_VAR", raising=False) prerun._update_env("DUMMY_VAR", value, default) assert os.environ["DUMMY_VAR"] == result @pytest.mark.parametrize( ("old_value", "value", "result"), ( ("a:b", ["c"], "a:b:c"), ("a:b", ["c:d"], "a:b:c:d"), ), ) def test__update_env_no_default( monkeypatch: MonkeyPatch, old_value: str, value: List[str], result: str ) -> None: """Values are appended to preexisting value.""" monkeypatch.setenv("DUMMY_VAR", old_value) prerun._update_env("DUMMY_VAR", value) assert os.environ["DUMMY_VAR"] == result @pytest.mark.parametrize( ("old_value", "default", "value", "result"), ( ("", "", ["e"], "e"), ("a", "", ["e"], "a:e"), ("", "c", ["e"], "e"), ("a", "c", ["e:f"], "a:e:f"), ), ) def test__update_env( monkeypatch: MonkeyPatch, old_value: str, default: str, value: List[str], result: str, ) -> None: """Defaults are ignored when preexisting value is present.""" monkeypatch.setenv("DUMMY_VAR", old_value) prerun._update_env("DUMMY_VAR", value) assert os.environ["DUMMY_VAR"] == result def test_require_collection_wrong_version() -> None: """Tests behaviour of require_collection.""" subprocess.check_output( [ "ansible-galaxy", "collection", "install", "containers.podman", "-p", "~/.ansible/collections", ] ) with pytest.raises(SystemExit) as pytest_wrapped_e: prerun.require_collection("containers.podman", "9999.9.9") assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == INVALID_PREREQUISITES_RC @pytest.mark.parametrize( ("name", "version"), ( ("fake_namespace.fake_name", None), ("fake_namespace.fake_name", "9999.9.9"), ), ) def test_require_collection_missing(name: str, version: str) -> None: """Tests behaviour of require_collection, missing case.""" with pytest.raises(SystemExit) as pytest_wrapped_e: prerun.require_collection(name, version) assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == INVALID_PREREQUISITES_RC def test_ansible_config_get() -> None: """Check test_ansible_config_get.""" paths = prerun.ansible_config_get("COLLECTIONS_PATHS", list) assert isinstance(paths, list) assert len(paths) > 0 def test_install_collection() -> None: """Check that valid collection installs do not fail.""" prerun.install_collection("containers.podman:>=1.0") def test_install_collection_fail() -> None: """Check that invalid collection install fails.""" with pytest.raises(SystemExit) as pytest_wrapped_e: prerun.install_collection("containers.podman:>=9999.0") assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.value.code == INVALID_PREREQUISITES_RC ��������������������������������������������������������ansible-lint-5.4.0/test/test_skiputils.py�����������������������������������������������������������0000664�0000000�0000000�00000001712�14201712607�0020521�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Validate ansiblelint.skip_utils.""" import pytest from ansiblelint.skip_utils import get_rule_skips_from_line from ansiblelint.testing import RunFromText PLAYBOOK_WITH_NOQA = """\ - hosts: all vars: SOMEVARNOQA: "Foo" # noqa var-naming SOMEVAR: "Bar" tasks: - name: "Set the SOMEOTHERVAR" set_fact: SOMEOTHERVARNOQA: "Baz" # noqa var-naming SOMEOTHERVAR: "Bat" """ @pytest.mark.parametrize( ("line", "expected"), ( ("foo # noqa: bar", "bar"), ("foo # noqa bar", "bar"), ), ) def test_get_rule_skips_from_line(line: str, expected: str) -> None: """Validate get_rule_skips_from_line.""" x = get_rule_skips_from_line(line) assert x == [expected] def test_playbook_noqa(default_text_runner: RunFromText) -> None: """Check that noqa is properly taken into account on vars and tasks.""" results = default_text_runner.run_playbook(PLAYBOOK_WITH_NOQA) assert len(results) == 2 ������������������������������������������������������ansible-lint-5.4.0/test/test_yaml_utils.py����������������������������������������������������������0000664�0000000�0000000�00000004052�14201712607�0020654�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Tests for yaml-related utility functions.""" from typing import Any import pytest import ansiblelint.yaml_utils from ansiblelint.file_utils import Lintable @pytest.fixture def empty_lintable() -> Lintable: """Return a Lintable with no contents.""" lintable = Lintable("__empty_file__") lintable._content = "" return lintable def test_iter_tasks_in_file_with_empty_file(empty_lintable: Lintable) -> None: """Make sure that iter_tasks_in_file returns early when files are empty.""" res = list( ansiblelint.yaml_utils.iter_tasks_in_file(empty_lintable, "some-rule-id") ) assert res == [] def test_nested_items_path() -> None: """Verify correct function of nested_items_path().""" data = { "foo": "text", "bar": {"some": "text2"}, "fruits": ["apple", "orange"], "answer": [{"forty-two": ["life", "universe", "everything"]}], } items = [ ("foo", "text", []), ("bar", {"some": "text2"}, []), ("some", "text2", ["bar"]), ("fruits", ["apple", "orange"], []), (0, "apple", ["fruits"]), (1, "orange", ["fruits"]), ("answer", [{"forty-two": ["life", "universe", "everything"]}], []), (0, {"forty-two": ["life", "universe", "everything"]}, ["answer"]), ("forty-two", ["life", "universe", "everything"], ["answer", 0]), (0, "life", ["answer", 0, "forty-two"]), (1, "universe", ["answer", 0, "forty-two"]), (2, "everything", ["answer", 0, "forty-two"]), ] assert list(ansiblelint.yaml_utils.nested_items_path(data)) == items @pytest.mark.parametrize( "invalid_data_input", ( "string", 42, 1.234, None, ("tuple",), {"set"}, ), ) def test_nested_items_path_raises_typeerror(invalid_data_input: Any) -> None: """Verify non-dict/non-list types make nested_items_path() raises TypeError.""" with pytest.raises(TypeError, match=r"Expected a dict or a list.*"): list(ansiblelint.yaml_utils.nested_items_path(invalid_data_input)) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/tools/���������������������������������������������������������������������������0000775�0000000�0000000�00000000000�14201712607�0015241�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ansible-lint-5.4.0/tools/check-missing-ansible.py���������������������������������������������������0000664�0000000�0000000�00000000643�14201712607�0021755�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������"""Validates linter behavior when ansible python package is missing.""" import os import subprocess if __name__ == "__main__": cmd = ["ansible-lint", "--version"] result = subprocess.run( cmd, universal_newlines=True, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=os.environ, ) assert result.returncode == 4, result # missing ansible ���������������������������������������������������������������������������������������������ansible-lint-5.4.0/tools/test-setup.sh��������������������������������������������������������������0000775�0000000�0000000�00000001653�14201712607�0017722�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/bash # This tool is used to setup the environment for running the tests. Its name # name and location is based on Zuul CI, which can automatically run it. set -euo pipefail # User specific environment # shellcheck disable=SC2076 if ! [[ "$PATH" =~ "$HOME/.local/bin" ]] then PATH="$HOME/.local/bin:$PATH" fi if [ -f "/usr/bin/apt-get" ]; then if [ ! -f "/var/cache/apt/pkgcache.bin" ]; then sudo apt-get update # mandatory or other apt-get commands fail fi # avoid outdated ansible and pipx sudo apt-get remove -y ansible pipx || true sudo apt-get install -y --no-install-recommends -o=Dpkg::Use-Pty=0 \ git python3-venv python3-pip fi which pipx || python3 -m pip install --user pipx which -a pipx which pre-commit || pipx install pre-commit which tox || pipx install tox # Log some useful info in case of unexpected failures: uname python3 --version tox --version pre-commit --version �������������������������������������������������������������������������������������ansible-lint-5.4.0/tox.ini��������������������������������������������������������������������������0000664�0000000�0000000�00000012525�14201712607�0015421�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[tox] minversion = 3.16.1 envlist = lint packaging py310-{core,devel} py{39,38}-{core,ansible29,devel} py{37,36}-{core,ansible29} isolated_build = true requires = setuptools >= 41.4.0 pip >= 19.3.0 skip_missing_interpreters = True [testenv] description = Run the tests under {basepython} and devel: ansible devel branch ansible29: ansible 2.9 core: ansible-core 2.11+ deps = --editable .[yamllint,test] core: ansible-core ansible29: ansible>=2.9,<2.10 py: ansible-core>=2.11 devel: ansible-core @ git+https://github.com/ansible/ansible.git # GPLv3+ commands = # safety measure to assure we do not accidentally run tests with broken dependencies {envpython} -m pip check # We add coverage options but not making them mandatory as we do not want to force # pytest users to run coverage when they just want to run a single test with `pytest -k test` {envpython} -m pytest \ --junitxml "{toxworkdir}/junit.{envname}.xml" \ {posargs:\ -m "not eco" \ -p pytest_cov \ --cov ansiblelint \ --cov "{envsitepackagesdir}/ansiblelint" \ --cov-report term-missing:skip-covered \ --no-cov-on-fail} passenv = CURL_CA_BUNDLE # https proxies, https://github.com/tox-dev/tox/issues/1437 FORCE_COLOR HOME NO_COLOR PYTEST_* # allows developer to define their own preferences PY_COLORS REQUESTS_CA_BUNDLE # https proxies SSL_CERT_FILE # https proxies LANG LC_ALL LC_CTYPE # recreate = True setenv = COVERAGE_FILE = {env:COVERAGE_FILE:{toxworkdir}/.coverage.{envname}} PIP_CONSTRAINT = {toxinidir}/constraints.txt PIP_DISABLE_PIP_VERSION_CHECK = 1 PRE_COMMIT_COLOR = always FORCE_COLOR = 1 allowlist_externals = sh tox # both options needed to workaround https://github.com/tox-dev/tox/issues/2197 usedevelop = false skip_install = true [testenv:lint] description = Run all linters # pip compile includes python version in output constraints, so we want to # be sure that version does not change randomly. basepython = python3.9 deps = pre-commit>=2.6.0 pip-tools>=6.5.0 setuptools>=51.1.1 skip_install = true commands = {envpython} -m pre_commit run --all-files --show-diff-on-failure {posargs:} # manual hook as they are unlikely to work outside tox {envpython} -m pre_commit run --all-files --show-diff-on-failure --hook-stage manual pip-compile {envpython} -m pre_commit run --all-files --show-diff-on-failure --hook-stage manual pip-compile-docs passenv = {[testenv]passenv} PRE_COMMIT_HOME setenv = {[testenv]setenv} # avoid messing pre-commit with out own constraints PIP_CONSTRAINT= [testenv:deps] description = Bump all test dependencies # we reuse the lint environment envdir = {toxworkdir}/lint skip_install = true basepython = python3.9 deps = {[testenv:lint]deps} setenv = # without his upgrade would likely not do anything PIP_CONSTRAINT = /dev/null commands = # manual hook calls the optional pip-compile-upgrade hook {envpython} -m pre_commit run --all-files --show-diff-on-failure --hook-stage manual pip-compile-upgrade {envpython} -m pre_commit run --all-files --show-diff-on-failure --hook-stage manual pip-compile-docs-upgrade [testenv:docs] description = Builds docs basepython = python3 deps = --editable .[yamllint] -r{toxinidir}/docs/requirements.in setenv = PIP_CONSTRAINT = {toxinidir}/docs/requirements.txt commands = # Build the html docs with Sphinx: {envpython} -m sphinx \ -j auto \ -b html \ --color \ -a \ -n \ -W --keep-going \ -d "{temp_dir}/.doctrees" \ . \ "{envdir}/html" # Print out the output docs dir and a way to serve html: -{envpython} -c \ 'import pathlib; docs_dir = pathlib.Path(r"{envdir}") / "html"; index_file = docs_dir / "index.html"; '\ 'print("\n" + "=" * 120 + f"\n\nDocumentation available under `file://\{index_file\}`\n\nTo serve docs, use `python3 -m http.server --directory \{docs_dir\} 0`\n\n" + "=" * 120)' changedir = {toxinidir}/docs [testenv:linkcheck-docs] description = Linkcheck The Docs basepython = {[testenv:docs]basepython} deps = {[testenv:docs]deps} setenv = {[testenv:docs]setenv} commands = {envpython} -m sphinx \ -j auto \ -b linkcheck \ -a \ -n \ -W --keep-going \ {tty:--color} \ -d "{temp_dir}/.doctrees" \ . \ "{envdir}/html" changedir = {[testenv:docs]changedir} [testenv:eco] description = Perform ecosystem impact (downstream testing) https://github.com/ansible-community/ansible-lint/discussions/1403 skip_install = true deps = commands = tox -e py -- -m eco [testenv:packaging] basepython = python3 description = Build package, verify metadata, install package and assert behavior when ansible is missing. deps = build >= 0.7.0, < 0.8.0 twine skip_install = true # Ref: https://twitter.com/di_codes/status/1044358639081975813 commands = # build wheel and sdist using PEP-517 {envpython} -c 'import os.path, shutil, sys; \ dist_dir = os.path.join("{toxinidir}", "dist"); \ os.path.isdir(dist_dir) or sys.exit(0); \ print("Removing \{!s\} contents...".format(dist_dir), file=sys.stderr); \ shutil.rmtree(dist_dir)' {envpython} -m build \ --outdir {toxinidir}/dist/ \ {toxinidir} # Validate metadata using twine twine check --strict {toxinidir}/dist/* # Install the wheel sh -c "python3 -m pip install {toxinidir}/dist/*.whl" # Re-assure ansible is not installed {envpython} tools/check-missing-ansible.py ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������