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)
def main(argv: Optional[List[str]] = None) -> int: """Linter CLI entry point.""" 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) 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.generate_docs import rules_as_rich, rules_as_rst, rules_as_str from ansiblelint.rules import RulesCollection rules = RulesCollection(options.rulesdirs) 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 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(): 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 report_outcome(result, mark_as_success=mark_as_success, options=options)
def execute_cmdline_scenarios(scenario_name, args, command_args, ansible_args=()): """ Execute scenario sequences based on parsed command-line arguments. This is useful for subcommands that run scenario sequences, which excludes subcommands such as ``list``, ``login``, and ``matrix``. ``args`` and ``command_args`` are combined using :func:`get_configs` to generate the scenario(s) configuration. :param scenario_name: Name of scenario to run, or ``None`` to run all. :param args: ``args`` dict from ``click`` command context :param command_args: dict of command arguments, including the target subcommand to execute :returns: None """ glob_str = MOLECULE_GLOB if scenario_name: glob_str = glob_str.replace("*", scenario_name) scenarios = molecule.scenarios.Scenarios( get_configs(args, command_args, ansible_args, glob_str), scenario_name ) if scenario_name and scenarios: LOG.info( "%s scenario test matrix: %s", scenario_name, ", ".join(scenarios.sequence(scenario_name)), ) for scenario in scenarios: if scenario.config.config["prerun"]: LOG.info("Performing prerun...") prepare_environment() if command_args.get("subcommand") == "reset": LOG.info("Removing %s" % scenario.ephemeral_directory) shutil.rmtree(scenario.ephemeral_directory) return try: execute_scenario(scenario) except SystemExit: # if the command has a 'destroy' arg, like test does, # handle that behavior here. if command_args.get("destroy") == "always": msg = ( "An error occurred during the {} sequence action: " "'{}'. Cleaning up." ).format(scenario.config.subcommand, scenario.config.action) LOG.warning(msg) execute_subcommand(scenario.config, "cleanup") execute_subcommand(scenario.config, "destroy") # always prune ephemeral dir if destroying on failure scenario.prune() if scenario.config.is_parallel: scenario._remove_scenario_state_directory() util.sysexit() else: raise