Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ruamel.yaml annotation implicit for yaml #2299

Merged
merged 1 commit into from
Aug 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/ansiblelint/file_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,13 @@ def data(self) -> Any:
)

self._data = parse_yaml_linenumbers(self)
# Lazy import to avoid delays and cyclic-imports
if "append_skipped_rules" not in globals():
# pylint: disable=import-outside-toplevel
from ansiblelint.skip_utils import append_skipped_rules

self._data = append_skipped_rules(self._data, self)

# will remain None if we do not know how to load that file-type
return self._data

Expand Down
2 changes: 0 additions & 2 deletions src/ansiblelint/rules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,6 @@ def matchyaml(self, file: Lintable) -> List[MatchError]:
if isinstance(yaml, dict):
yaml = [yaml]

yaml = ansiblelint.skip_utils.append_skipped_rules(yaml, file)

for play in yaml:

# Bug #849
Expand Down
47 changes: 42 additions & 5 deletions src/ansiblelint/skip_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
"""Utils related to inline skipping of rules."""
from __future__ import annotations

import collections.abc
import logging
from functools import lru_cache
from itertools import product
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Sequence, Set

# Module 'ruamel.yaml' does not explicitly export attribute 'YAML'; implicit reexport disabled
from ruamel.yaml import YAML
from ruamel.yaml.composer import ComposerError

from ansiblelint.config import used_old_tags
from ansiblelint.constants import NESTED_TASK_KEYS, PLAYBOOK_TASK_KEYWORDS, RENAMED_TAGS
Expand Down Expand Up @@ -103,17 +105,34 @@ def load_data(file_text: str) -> Any:
:return: Parsed yaml
"""
yaml = YAML()
return yaml.load(file_text)
# Ruamel role is not to validate the yaml file, so we ignore duplicate keys:
yaml.allow_duplicate_keys = True
try:
return yaml.load(file_text)
except ComposerError:
# load fails on multi-documents with ComposerError exception
return yaml.load_all(file_text)


def _append_skipped_rules(
def _append_skipped_rules( # noqa: max-complexity: 12
pyyaml_data: AnsibleBaseYAMLObject, lintable: Lintable
) -> Optional[AnsibleBaseYAMLObject]:
# parse file text using 2nd parser library
ruamel_data = load_data(lintable.content)
skipped_rules = _get_rule_skips_from_yaml(ruamel_data)

if lintable.kind in ["yaml", "requirements", "vars", "meta", "reno", "test-meta"]:
pyyaml_data[0]["skipped_rules"] = _get_rule_skips_from_yaml(ruamel_data)
# AnsibleMapping, dict
if hasattr(pyyaml_data, "get"):
pyyaml_data["skipped_rules"] = skipped_rules
# AnsibleSequence, list
elif (
not isinstance(pyyaml_data, str)
and isinstance(pyyaml_data, collections.abc.Sequence)
and skipped_rules
):
pyyaml_data[0]["skipped_rules"] = skipped_rules

return pyyaml_data

# create list of blocks of tasks or nested tasks
Expand Down Expand Up @@ -141,6 +160,10 @@ def _append_skipped_rules(
if not pyyaml_task and not ruamel_task:
continue

# AnsibleUnicode or str
if isinstance(pyyaml_task, str):
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)
Expand All @@ -162,12 +185,16 @@ def _get_task_blocks_from_playbook(playbook: Sequence[Any]) -> List[Any]:

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."""
if not task_blocks:
return

def get_nested_tasks(task: Any) -> Generator[Any, None, None]:
if not task or not is_nested_task(task):
return
for k in NESTED_TASK_KEYS:
if k in task and task[k]:
if hasattr(task[k], "get"):
continue
for subtask in task[k]:
yield from get_nested_tasks(subtask)
yield subtask
Expand All @@ -178,10 +205,15 @@ def get_nested_tasks(task: Any) -> Generator[Any, None, None]:
yield task


def _get_rule_skips_from_yaml(yaml_input: Sequence[Any]) -> Sequence[Any]:
def _get_rule_skips_from_yaml( # noqa: max-complexity: 12
yaml_input: Sequence[Any],
) -> Sequence[Any]:
"""Traverse yaml for comments with rule skips and return list of rules."""
yaml_comment_obj_strings = []

if isinstance(yaml_input, str):
return []

def traverse_yaml(obj: Any) -> None:
yaml_comment_obj_strings.append(str(obj.ca.items))
if isinstance(obj, dict):
Expand All @@ -195,7 +227,8 @@ def traverse_yaml(obj: Any) -> None:
else:
return

traverse_yaml(yaml_input)
if isinstance(yaml_input, (dict, list)):
traverse_yaml(yaml_input)

rule_id_list = []
for comment_obj_str in yaml_comment_obj_strings:
Expand All @@ -215,6 +248,10 @@ def normalize_tag(tag: str) -> str:

def is_nested_task(task: Dict[str, Any]) -> bool:
"""Check if task includes block/always/rescue."""
# Cannot really trust the input
if isinstance(task, str):
return False

for key in NESTED_TASK_KEYS:
if task.get(key):
return True
Expand Down
2 changes: 0 additions & 2 deletions src/ansiblelint/yaml_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
from ruamel.yaml.tokens import CommentToken
from yamllint.config import YamlLintConfig

import ansiblelint.skip_utils
from ansiblelint.constants import NESTED_TASK_KEYS, PLAYBOOK_TASK_KEYWORDS
from ansiblelint.errors import MatchError
from ansiblelint.file_utils import Lintable
Expand Down Expand Up @@ -123,7 +122,6 @@ def iter_tasks_in_file(
data = lintable.data
if not data:
return
data = ansiblelint.skip_utils.append_skipped_rules(data, lintable)

raw_tasks = get_action_tasks(data, lintable)

Expand Down