summaryrefslogtreecommitdiffstats
path: root/src/ansiblelint/__main__.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/ansiblelint/__main__.py')
-rwxr-xr-xsrc/ansiblelint/__main__.py172
1 files changed, 138 insertions, 34 deletions
diff --git a/src/ansiblelint/__main__.py b/src/ansiblelint/__main__.py
index af434d0..ca4a33b 100755
--- a/src/ansiblelint/__main__.py
+++ b/src/ansiblelint/__main__.py
@@ -30,12 +30,23 @@ import shutil
import site
import sys
from pathlib import Path
-from typing import TYPE_CHECKING, Any, Callable, TextIO
+from typing import TYPE_CHECKING, Any, TextIO
from ansible_compat.prerun import get_cache_dir
from filelock import FileLock, Timeout
from rich.markup import escape
+from ansiblelint.constants import RC, SKIP_SCHEMA_UPDATE
+
+# safety check for broken ansible core, needs to happen first
+try:
+ # pylint: disable=unused-import
+ from ansible.parsing.dataloader import DataLoader # noqa: F401
+
+except Exception as _exc: # pylint: disable=broad-exception-caught # noqa: BLE001
+ logging.fatal(_exc)
+ sys.exit(RC.INVALID_CONFIG)
+# pylint: disable=ungrouped-imports
from ansiblelint import cli
from ansiblelint._mockings import _perform_mockings_cleanup
from ansiblelint.app import get_app
@@ -53,20 +64,21 @@ from ansiblelint.config import (
log_entries,
options,
)
-from ansiblelint.constants import RC
from ansiblelint.loaders import load_ignore_txt
+from ansiblelint.runner import get_matches
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 collections.abc import Callable
+
from ansiblelint.rules import RulesCollection
from ansiblelint.runner import LintResult
_logger = logging.getLogger(__name__)
-cache_dir_lock: None | FileLock = None
class LintLogHandler(logging.Handler):
@@ -107,8 +119,9 @@ def initialize_logger(level: int = 0) -> None:
_logger.debug("Logging initialized to level %s", logging_level)
-def initialize_options(arguments: list[str] | None = None) -> None:
+def initialize_options(arguments: list[str] | None = None) -> None | FileLock:
"""Load config options and store them inside options module."""
+ cache_dir_lock = None
new_options = cli.get_config(arguments or [])
new_options.cwd = pathlib.Path.cwd()
@@ -132,13 +145,13 @@ def initialize_options(arguments: list[str] | None = None) -> None:
options.cache_dir.mkdir(parents=True, exist_ok=True)
if not options.offline: # pragma: no cover
- cache_dir_lock = FileLock( # pylint: disable=redefined-outer-name
+ cache_dir_lock = FileLock(
f"{options.cache_dir}/.lock",
)
try:
cache_dir_lock.acquire(timeout=180)
except Timeout: # pragma: no cover
- _logger.error(
+ _logger.error( # noqa: TRY400
"Timeout waiting for another instance of ansible-lint to release the lock.",
)
sys.exit(RC.LOCK_TIMEOUT)
@@ -147,6 +160,8 @@ def initialize_options(arguments: list[str] | None = None) -> None:
if "ANSIBLE_DEVEL_WARNING" not in os.environ: # pragma: no branch
os.environ["ANSIBLE_DEVEL_WARNING"] = "false"
+ return cache_dir_lock
+
def _do_list(rules: RulesCollection) -> int:
# On purpose lazy-imports to avoid pre-loading Ansible
@@ -194,23 +209,85 @@ def _do_transform(result: LintResult, opts: Options) -> None:
def support_banner() -> None:
"""Display support banner when running on unsupported platform."""
- if sys.version_info < (3, 9, 0): # pragma: no cover
- prefix = "::warning::" if "GITHUB_ACTION" in os.environ else "WARNING: "
- console_stderr.print(
- f"{prefix}ansible-lint is no longer tested under Python {sys.version_info.major}.{sys.version_info.minor} and will soon require 3.9. Do not report bugs for this version.",
- style="bold red",
- )
-# pylint: disable=too-many-statements,too-many-locals
+def fix(runtime_options: Options, result: LintResult, rules: RulesCollection) -> None:
+ """Fix the linting errors.
+
+ :param options: Options object
+ :param result: LintResult object
+ """
+ match_count = len(result.matches)
+ _logger.debug("Begin fixing: %s matches", match_count)
+ ruamel_safe_version = "0.17.26"
+
+ # pylint: disable=import-outside-toplevel
+ from packaging.version import Version
+ from ruamel.yaml import __version__ as ruamel_yaml_version_str
+
+ # pylint: enable=import-outside-toplevel
+
+ if Version(ruamel_safe_version) > Version(ruamel_yaml_version_str):
+ _logger.warning(
+ "We detected use of `--fix` feature with a buggy ruamel-yaml %s library instead of >=%s, upgrade it before reporting any bugs like dropped comments.",
+ ruamel_yaml_version_str,
+ ruamel_safe_version,
+ )
+ acceptable_tags = {"all", "none", *rules.known_tags()}
+ unknown_tags = set(options.write_list).difference(acceptable_tags)
+
+ if unknown_tags:
+ _logger.error(
+ "Found invalid value(s) (%s) for --fix arguments, must be one of: %s",
+ ", ".join(unknown_tags),
+ ", ".join(acceptable_tags),
+ )
+ sys.exit(RC.INVALID_CONFIG)
+ _do_transform(result, options)
+
+ rerun = ["yaml"]
+ resolved = []
+ for idx, match in reversed(list(enumerate(result.matches))):
+ _logger.debug("Fixing: (%s of %s) %s", match_count - idx, match_count, match)
+ if match.fixed:
+ _logger.debug("Fixed, removed: %s", match)
+ result.matches.pop(idx)
+ continue
+ if match.rule.id not in rerun:
+ _logger.debug("Not rerun eligible: %s", match)
+ continue
+
+ uid = (match.rule.id, match.filename)
+ if uid in resolved:
+ _logger.debug("Previously resolved: %s", match)
+ result.matches.pop(idx)
+ continue
+ _logger.debug("Rerunning: %s", match)
+ runtime_options.tags = [match.rule.id]
+ runtime_options.lintables = [match.filename]
+ runtime_options._skip_ansible_syntax_check = True # noqa: SLF001
+ new_results = get_matches(rules, runtime_options)
+ if not new_results.matches:
+ _logger.debug("Newly resolved: %s", match)
+ result.matches.pop(idx)
+ resolved.append(uid)
+ continue
+ if match in new_results.matches:
+ _logger.debug("Still found: %s", match)
+ continue
+ _logger.debug("Fixed, removed: %s", match)
+ result.matches.pop(idx)
+
+
+# pylint: disable=too-many-locals
def main(argv: list[str] | None = None) -> int:
"""Linter CLI entry point."""
# alter PATH if needed (venv support)
- path_inject()
+ path_inject(argv[0] if argv and argv[0] else "")
if argv is None: # pragma: no cover
argv = sys.argv
- initialize_options(argv[1:])
+ cache_dir_lock = initialize_options(argv[1:])
console_options["force_terminal"] = options.colored
reconfigure(console_options)
@@ -236,7 +313,23 @@ def main(argv: list[str] | None = None) -> int:
_logger.debug("Options: %s", options)
_logger.debug("CWD: %s", Path.cwd())
- if not options.offline:
+ # checks if we have `ANSIBLE_LINT_SKIP_SCHEMA_UPDATE` set to bypass schema
+ # update. Also skip if in offline mode.
+ # env var set to skip schema refresh
+ skip_schema_update = (
+ bool(
+ int(
+ os.environ.get(
+ SKIP_SCHEMA_UPDATE,
+ "0",
+ ),
+ ),
+ )
+ or options.offline
+ or options.nodeps
+ )
+
+ if not skip_schema_update:
# pylint: disable=import-outside-toplevel
from ansiblelint.schemas.__main__ import refresh_schemas
@@ -244,7 +337,6 @@ def main(argv: list[str] | None = None) -> int:
# pylint: disable=import-outside-toplevel
from ansiblelint.rules import RulesCollection
- from ansiblelint.runner import _get_matches
if options.list_profiles:
from ansiblelint.generate_docs import profiles_as_rich
@@ -265,20 +357,7 @@ def main(argv: list[str] | None = None) -> int:
if isinstance(options.tags, str):
options.tags = options.tags.split(",") # pragma: no cover
- result = _get_matches(rules, options)
-
- if options.write_list:
- ruamel_safe_version = "0.17.26"
- from packaging.version import Version
- from ruamel.yaml import __version__ as ruamel_yaml_version_str
-
- if Version(ruamel_safe_version) > Version(ruamel_yaml_version_str):
- _logger.warning(
- "We detected use of `--write` feature with a buggy ruamel-yaml %s library instead of >=%s, upgrade it before reporting any bugs like dropped comments.",
- ruamel_yaml_version_str,
- ruamel_safe_version,
- )
- _do_transform(result, options)
+ result = get_matches(rules, options)
mark_as_success = True
@@ -292,6 +371,18 @@ def main(argv: list[str] | None = None) -> int:
for match in result.matches:
if match.tag in ignore_map[match.filename]:
match.ignored = True
+ _logger.debug("Ignored: %s", match)
+
+ if app.yamllint_config.incompatible:
+ logging.log(
+ level=logging.ERROR if options.write_list else logging.WARNING,
+ msg=app.yamllint_config.incompatible,
+ )
+
+ if options.write_list:
+ if app.yamllint_config.incompatible:
+ sys.exit(RC.INVALID_CONFIG)
+ fix(runtime_options=options, result=result, rules=rules)
app.render_matches(result.matches)
@@ -325,7 +416,7 @@ def _run_cli_entrypoint() -> None:
raise SystemExit(exc) from exc
-def path_inject() -> None:
+def path_inject(own_location: str = "") -> 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
@@ -350,6 +441,7 @@ def path_inject() -> None:
inject_paths = []
userbase_bin_path = Path(site.getuserbase()) / "bin"
+
if (
str(userbase_bin_path) not in paths
and (userbase_bin_path / "bin" / "ansible").exists()
@@ -357,11 +449,23 @@ def path_inject() -> None:
inject_paths.append(str(userbase_bin_path))
py_path = Path(sys.executable).parent
- if str(py_path) not in paths and (py_path / "ansible").exists():
+ pipx_path = os.environ.get("PIPX_HOME", "pipx")
+ if (
+ str(py_path) not in paths
+ and (py_path / "ansible").exists()
+ and pipx_path not in str(py_path)
+ ):
inject_paths.append(str(py_path))
+ # last option, if nothing else is found, just look next to ourselves...
+ if own_location:
+ own_location = os.path.realpath(own_location)
+ parent = Path(own_location).parent
+ if (parent / "ansible").exists() and str(parent) not in paths:
+ inject_paths.append(str(parent))
+
if not os.environ.get("PYENV_VIRTUAL_ENV", None):
- if inject_paths:
+ if inject_paths and not all("pipx" in p for p in inject_paths):
print( # noqa: T201
f"WARNING: PATH altered to include {', '.join(inject_paths)} :: This is usually a sign of broken local setup, which can cause unexpected behaviors.",
file=sys.stderr,