Skip to content

Commit

Permalink
Merge branch 'master' into feature/each-rule-configs
Browse files Browse the repository at this point in the history
  • Loading branch information
ssbarnea committed Apr 30, 2021
2 parents 02ed28d + 25751a8 commit 63f3889
Show file tree
Hide file tree
Showing 33 changed files with 270 additions and 94 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
@@ -0,0 +1 @@
* @ssbarnea
2 changes: 1 addition & 1 deletion docs/installing.rst
Expand Up @@ -35,7 +35,7 @@ related to containers and use the discussions_ forum instead.
Using pip or pipx
-----------------

You can use either pip3_ or pipx_ to install it, the former one
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
Expand Down
11 changes: 9 additions & 2 deletions examples/rules/TaskHasTag.py
@@ -1,8 +1,13 @@
"""Example implementation of a rule requiring tasks to have tags set."""
from typing import Any, Dict, Union
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."""
Expand All @@ -12,7 +17,9 @@ class TaskHasTag(AnsibleLintRule):
description = 'Tasks must have tag'
tags = ['productivity', 'tags']

def matchtask(self, task: Dict[str, Any]) -> Union[bool, str]:
def matchtask(
self, task: Dict[str, Any], file: 'Optional[Lintable]' = None
) -> Union[bool, str]:
"""Task matching method."""
if isinstance(task, str):
return False
Expand Down
11 changes: 9 additions & 2 deletions playbooks/eco.yml
Expand Up @@ -16,6 +16,7 @@
- name: zuul-jobs
url: https://opendev.org/zuul/zuul-jobs
contact: ssbarnea
args: --progressive
- name: tripleo-ansible
url: https://opendev.org/openstack/tripleo-ansible
contact: ssbarnea
Expand All @@ -36,15 +37,21 @@
contact: drybjed
tasks:

- name: Clone repo
- name: Clone repo # noqa: git-latest
git:
repo: "{{ item.url }}"
dest: "{{ (cache_dir, item.name) | path_join }}"
version: HEAD
update: true
loop: "{{ repos }}"
loop_control:
label: "{{ item.name }}"

- name: Run linter
command: ansible-lint -v
command: ansible-lint -v {{ item.args | default('') }}
args:
chdir: "{{ (cache_dir, item.name) | path_join }}"
loop: "{{ repos }}"
loop_control:
label: "{{ item.name }}"
changed_when: false
3 changes: 3 additions & 0 deletions setup.cfg
Expand Up @@ -107,5 +107,8 @@ yamllint =
[options.packages.find]
where = src

[options.package_data]
ansiblelint = py.typed

[codespell]
skip = .tox,.mypy_cache,build,.git,.eggs,pip-wheel-metadata
6 changes: 5 additions & 1 deletion src/ansiblelint/_internal/rules.py
Expand Up @@ -2,6 +2,8 @@
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
Expand Down Expand Up @@ -43,7 +45,9 @@ def matchlines(self, file: "Lintable") -> List["MatchError"]:
"""Return matches found for a specific line."""
return []

def matchtask(self, task: Dict[str, Any]) -> Union[bool, str]:
def matchtask(
self, task: Dict[str, Any], file: "Optional[Lintable]" = None
) -> Union[bool, str]:
"""Confirm if current rule is matching a specific task."""
return False

Expand Down
87 changes: 48 additions & 39 deletions src/ansiblelint/prerun.py
Expand Up @@ -101,26 +101,44 @@ def _get_ver_err() -> Tuple[str, str]:
stop=tenacity.stop_after_attempt(3), # type: ignore
before_sleep=tenacity.after_log(_logger, logging.WARNING), # type: ignore
)
def prepare_environment() -> 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
def install_requirements(requirement: str) -> None:
"""Install dependencies from a requirements.yml."""
if not os.path.exists(requirement):
return

initialize_options()
cmd = [
"ansible-galaxy",
"role",
"install",
"--roles-path",
f"{options.project_dir}/.cache/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)

if not options.offline and os.path.exists("requirements.yml"):
# Run galaxy collection install works on v2 requirements.yml
if "collections" in yaml_from_file(requirement):

cmd = [
"ansible-galaxy",
"role",
"collection",
"install",
"--roles-path",
f"{options.project_dir}/.cache/roles",
"-p",
f"{options.project_dir}/.cache/collections",
"-vr",
"requirements.yml",
f"{requirement}",
]

_logger.info("Running %s", " ".join(cmd))
Expand All @@ -133,32 +151,23 @@ def prepare_environment() -> None:
)
if run.returncode != 0:
_logger.error(run.stdout)
sys.exit(run.returncode)

# Run galaxy collection install works on v2 requirements.yml
if "collections" in yaml_from_file("requirements.yml"):

cmd = [
"ansible-galaxy",
"collection",
"install",
"-p",
f"{options.project_dir}/.cache/collections",
"-vr",
"requirements.yml",
]

_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)
raise RuntimeError(run.returncode)


def prepare_environment() -> 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))

_install_galaxy_role()
_perform_mockings()
Expand Down
Empty file added src/ansiblelint/py.typed
Empty file.
11 changes: 9 additions & 2 deletions src/ansiblelint/rules/CommandHasChangesCheckRule.py
Expand Up @@ -18,10 +18,15 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

from typing import Any, Dict, Union
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'
Expand All @@ -38,7 +43,9 @@ class CommandHasChangesCheckRule(AnsibleLintRule):

_commands = ['command', 'shell', 'raw']

def matchtask(self, task: Dict[str, Any]) -> Union[bool, str]:
def matchtask(
self, task: Dict[str, Any], file: 'Optional[Lintable]' = None
) -> Union[bool, str]:
if task["__ansible_action_type__"] == 'task':
if task["action"]["__ansible_module__"] in self._commands:
return (
Expand Down
11 changes: 9 additions & 2 deletions src/ansiblelint/rules/CommandsInsteadOfArgumentsRule.py
Expand Up @@ -19,11 +19,16 @@
# THE SOFTWARE.

import os
from typing import Any, Dict, Union
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'
Expand All @@ -47,7 +52,9 @@ class CommandsInsteadOfArgumentsRule(AnsibleLintRule):
'rm': 'state=absent',
}

def matchtask(self, task: Dict[str, Any]) -> Union[bool, str]:
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:
Expand Down
11 changes: 9 additions & 2 deletions src/ansiblelint/rules/CommandsInsteadOfModulesRule.py
Expand Up @@ -19,11 +19,16 @@
# THE SOFTWARE.

import os
from typing import Any, Dict, Union
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 CommandsInsteadOfModulesRule(AnsibleLintRule):
id = 'command-instead-of-module'
Expand Down Expand Up @@ -59,7 +64,9 @@ class CommandsInsteadOfModulesRule(AnsibleLintRule):
'yum': 'yum',
}

def matchtask(self, task: Dict[str, Any]) -> Union[bool, str]:
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

Expand Down
11 changes: 9 additions & 2 deletions src/ansiblelint/rules/ComparisonToEmptyStringRule.py
Expand Up @@ -3,11 +3,16 @@

import re
import sys
from typing import Any, Dict, Union
from typing import TYPE_CHECKING, Any, Dict, Union

from ansiblelint.rules import AnsibleLintRule
from ansiblelint.utils import nested_items

if TYPE_CHECKING:
from typing import Optional

from ansiblelint.file_utils import Lintable


class ComparisonToEmptyStringRule(AnsibleLintRule):
id = 'empty-string-compare'
Expand All @@ -22,7 +27,9 @@ class ComparisonToEmptyStringRule(AnsibleLintRule):

empty_string_compare = re.compile("[=!]= ?(\"{2}|'{2})")

def matchtask(self, task: Dict[str, Any]) -> Union[bool, str]:
def matchtask(
self, task: Dict[str, Any], file: 'Optional[Lintable]' = None
) -> Union[bool, str]:
for k, v, _ in nested_items(task):
if k == 'when':
if isinstance(v, str):
Expand Down
11 changes: 9 additions & 2 deletions src/ansiblelint/rules/ComparisonToLiteralBoolRule.py
Expand Up @@ -2,11 +2,16 @@
# Copyright (c) 2018-2021, Ansible Project

import re
from typing import Any, Dict, Union
from typing import TYPE_CHECKING, Any, Dict, Union

from ansiblelint.rules import AnsibleLintRule
from ansiblelint.utils import nested_items

if TYPE_CHECKING:
from typing import Optional

from ansiblelint.file_utils import Lintable


class ComparisonToLiteralBoolRule(AnsibleLintRule):
id = 'literal-compare'
Expand All @@ -21,7 +26,9 @@ class ComparisonToLiteralBoolRule(AnsibleLintRule):

literal_bool_compare = re.compile("[=!]= ?(True|true|False|false)")

def matchtask(self, task: Dict[str, Any]) -> Union[bool, str]:
def matchtask(
self, task: Dict[str, Any], file: 'Optional[Lintable]' = None
) -> Union[bool, str]:
for k, v, _ in nested_items(task):
if k == 'when':
if isinstance(v, str):
Expand Down
11 changes: 9 additions & 2 deletions src/ansiblelint/rules/DeprecatedModuleRule.py
@@ -1,9 +1,14 @@
# Copyright (c) 2018, Ansible Project

from typing import Any, Dict, Union
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'
Expand Down Expand Up @@ -60,7 +65,9 @@ class DeprecatedModuleRule(AnsibleLintRule):
'include',
]

def matchtask(self, task: Dict[str, Any]) -> Union[bool, str]:
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}'
Expand Down

0 comments on commit 63f3889

Please sign in to comment.