diff options
Diffstat (limited to 'src/ansiblelint/_internal')
-rw-r--r-- | src/ansiblelint/_internal/__init__.py | 0 | ||||
-rw-r--r-- | src/ansiblelint/_internal/internal_error.md | 43 | ||||
-rw-r--r-- | src/ansiblelint/_internal/load-failure.md | 12 | ||||
-rw-r--r-- | src/ansiblelint/_internal/parser-error.md | 5 | ||||
-rw-r--r-- | src/ansiblelint/_internal/rules.py | 194 | ||||
-rw-r--r-- | src/ansiblelint/_internal/warning.md | 9 |
6 files changed, 263 insertions, 0 deletions
diff --git a/src/ansiblelint/_internal/__init__.py b/src/ansiblelint/_internal/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/ansiblelint/_internal/__init__.py diff --git a/src/ansiblelint/_internal/internal_error.md b/src/ansiblelint/_internal/internal_error.md new file mode 100644 index 0000000..8db5e5e --- /dev/null +++ b/src/ansiblelint/_internal/internal_error.md @@ -0,0 +1,43 @@ +# internal-error + +This error can also be caused by internal bugs but also by custom rules. +Instead of just stopping tool execution, we generate the errors and continue +processing other files. This allows users to add this rule to their `warn_list` +until the root cause is fixed. + +Keep in mind that once an `internal-error` is found on a specific file, no +other rules will be executed on that same file. + +In almost all cases you will see more detailed information regarding the +original error or runtime exception that triggered this rule. + +If these files are broken on purpose, like some test fixtures, you need to add +them to the `exclude_paths`. + +## Problematic code + +```yaml +--- +- name: Some title {{ # <-- Ansible will not load this invalid jinja template + hosts: localhost + tasks: [] +``` + +## Correct code + +```yaml +--- +- name: Some title + hosts: localhost + tasks: [] +``` + +## ERROR! No hosts matched the subscripted pattern + +If you see this error, it means that you tried to index a host group variable +that is using an index above its size. + +Instead of doing something like `hosts: all[1]` which assumes that you have +at least two hosts in your current inventory, you better write something like +`hosts: "{{ all[1] | default([]) }}`, which is safe and do not produce runtime +errors. Use safe fallbacks to make your code more resilient. diff --git a/src/ansiblelint/_internal/load-failure.md b/src/ansiblelint/_internal/load-failure.md new file mode 100644 index 0000000..78daa0d --- /dev/null +++ b/src/ansiblelint/_internal/load-failure.md @@ -0,0 +1,12 @@ +# load-failure + +"Linter failed to process a file, possible invalid file. Possible reasons: + +* contains unsupported encoding (only UTF-8 is supported) +* not an Ansible file +* it contains some unsupported custom YAML objects (`!!` prefix) +* it was not able to decrypt an inline `!vault` block. + +This violation **is not** skippable, so it cannot be added to the `warn_list` +or the `skip_list`. If a vault decryption issue cannot be avoided, the +offending file can be added to `exclude_paths` configuration. diff --git a/src/ansiblelint/_internal/parser-error.md b/src/ansiblelint/_internal/parser-error.md new file mode 100644 index 0000000..f6c7649 --- /dev/null +++ b/src/ansiblelint/_internal/parser-error.md @@ -0,0 +1,5 @@ +# parser-error + +**AnsibleParserError.** + +Ansible parser fails; this usually indicates an invalid file. diff --git a/src/ansiblelint/_internal/rules.py b/src/ansiblelint/_internal/rules.py new file mode 100644 index 0000000..4d0bf49 --- /dev/null +++ b/src/ansiblelint/_internal/rules.py @@ -0,0 +1,194 @@ +"""Internally used rule classes.""" +from __future__ import annotations + +import inspect +import logging +from pathlib import Path +from typing import TYPE_CHECKING, Any + +from ansiblelint.constants import RULE_DOC_URL + +if TYPE_CHECKING: + from ansiblelint.errors import MatchError + from ansiblelint.file_utils import Lintable + from ansiblelint.rules import RulesCollection + +_logger = logging.getLogger(__name__) +LOAD_FAILURE_MD = """\ +# load-failure + +"Linter failed to process a file, possible invalid file. Possible reasons: + +* contains unsupported encoding (only UTF-8 is supported) +* not an Ansible file +* it contains some unsupported custom YAML objects (`!!` prefix) +* it was not able to decrypt an inline `!vault` block. + +This violation **is not** skippable, so it cannot be added to the `warn_list` +or the `skip_list`. If a vault decryption issue cannot be avoided, the +offending file can be added to `exclude_paths` configuration. +""" + + +# Derived rules are likely to want to access class members, so: +# pylint: disable=unused-argument +class BaseRule: + """Root class used by Rules.""" + + id: str = "" + tags: list[str] = [] + description: str = "" + version_added: str = "" + severity: str = "" + link: str = "" + has_dynamic_tags: bool = False + needs_raw_task: bool = False + # We use _order to sort rules and to ensure that some run before others, + # _order 0 for internal rules + # _order 1 for rules that check that data can be loaded + # _order 5 implicit for normal rules + _order: int = 5 + _help: str | None = None + # Added when a rule is registered into a collection, gives access to options + _collection: RulesCollection | None = None + + @property + def help(self) -> str: + """Return a help markdown string for the rule.""" + if self._help is None: + self._help = "" + md_file = ( + Path(inspect.getfile(self.__class__)).parent + / f"{self.id.replace('-', '_')}.md" + ) + if md_file.exists(): + self._help = md_file.read_text(encoding="utf-8") + return self._help + + @property + def url(self) -> str: + """Return rule documentation url.""" + url = self.link + if not url: # pragma: no cover + url = RULE_DOC_URL + if self.id: + url += self.id + "/" + return url + + @property + def shortdesc(self) -> str: + """Return the short description of the rule, basically the docstring.""" + return self.__doc__ or "" + + 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 exc: # pylint: disable=broad-except + _logger.warning( + "Ignored exception from %s.%s while processing %s: %s", + self.__class__.__name__, + method, + str(file), + exc, + ) + 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: Lintable | None = None + ) -> bool | str | MatchError | list[MatchError]: + """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: dict[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) -> 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._order, self.id) < (other._order, other.id) + + def __repr__(self) -> str: + """Return a AnsibleLintRule instance representation.""" + return self.id + ": " + self.shortdesc + + +# pylint: enable=unused-argument + + +class RuntimeErrorRule(BaseRule): + """Unexpected internal error.""" + + id = "internal-error" + severity = "VERY_HIGH" + tags = ["core"] + version_added = "v5.0.0" + _order = 0 + + +class AnsibleParserErrorRule(BaseRule): + """AnsibleParserError.""" + + id = "parser-error" + description = "Ansible parser fails; this usually indicates an invalid file." + severity = "VERY_HIGH" + tags = ["core"] + version_added = "v5.0.0" + _order = 0 + + +class LoadingFailureRule(BaseRule): + """Failed to load or parse file.""" + + id = "load-failure" + description = "Linter failed to process a file, possible invalid file." + severity = "VERY_HIGH" + tags = ["core", "unskippable"] + version_added = "v4.3.0" + help = LOAD_FAILURE_MD + _order = 0 + + +class WarningRule(BaseRule): + """Other warnings detected during run.""" + + id = "warning" + severity = "LOW" + # should remain experimental as that would keep it warning only + tags = ["core", "experimental"] + version_added = "v6.8.0" + _order = 0 diff --git a/src/ansiblelint/_internal/warning.md b/src/ansiblelint/_internal/warning.md new file mode 100644 index 0000000..97d2577 --- /dev/null +++ b/src/ansiblelint/_internal/warning.md @@ -0,0 +1,9 @@ +# warning + +`warning` is a special type of internal rule that is used to report generic +runtime warnings found during execution. As stated by its name, they are not +counted as errors, so they do not influence the final outcome. + +- `warning[raw-non-string]` indicates that you are using + `[raw](https://docs.ansible.com/ansible/latest/collections/ansible/builtin/raw_module.html#ansible-collections-ansible-builtin-raw-module)` + module with non-string arguments, which is not supported by Ansible. |