def test_different_config_file(base_arguments: List[str]) -> None: """Ensures an alternate config_file can be used.""" diff_config = cli.get_config(base_arguments + ["-c", "test/fixtures/ansible-config.yml"]) no_config = cli.get_config(base_arguments + ["-v"]) assert diff_config.verbosity == no_config.verbosity
def test_config_can_be_overridden(base_arguments: List[str]) -> None: no_override = cli.get_config(base_arguments + ["-t", "bad_tag"]) overridden = cli.get_config( base_arguments + ["-t", "bad_tag", "-c", "test/fixtures/tags.yml"]) assert no_override.tags + ["skip_ansible_lint"] == overridden.tags
def test_discover_lintables_silent(is_in_git: bool, monkeypatch: MonkeyPatch, capsys: CaptureFixture[str]) -> None: """Verify that no stderr output is displayed while discovering yaml files. (when the verbosity is off, regardless of the Git or Git-repo presence) Also checks expected number of files are detected. """ options = cli.get_config([]) test_dir = Path(__file__).resolve().parent lint_path = test_dir / '..' / 'examples' / 'roles' / 'test-role' if not is_in_git: monkeypatch.setenv('GIT_DIR', '') yaml_count = len(list(lint_path.glob('**/*.yml'))) + len( list(lint_path.glob('**/*.yaml'))) monkeypatch.chdir(str(lint_path)) files = file_utils.discover_lintables(options) stderr = capsys.readouterr().err assert not stderr, 'No stderr output is expected when the verbosity is off' assert ( len(files) == yaml_count ), "Expected to find {yaml_count} yaml files in {lint_path}".format_map( locals(), )
def initialize_options(arguments: List[str]): """Load config options and store them inside options module.""" new_options = cli.get_config(arguments) new_options.cwd = pathlib.Path.cwd() if new_options.version: ansible_version, err = check_ansible_presence() print('ansible-lint {ver!s} using ansible {ansible_ver!s}'.format( ver=__version__, ansible_ver=ansible_version)) if err: print(err, file=sys.stderr) sys.exit(ANSIBLE_MISSING_RC) sys.exit(0) if new_options.colored is None: new_options.colored = should_do_markup() # persist loaded configuration inside options module for k, v in new_options.__dict__.items(): setattr(options, k, v) # rename deprecated ids/tags to newer names options.tags = [normalize_tag(tag) for tag in options.tags] options.skip_list = [normalize_tag(tag) for tag in options.skip_list] options.warn_list = [normalize_tag(tag) for tag in options.warn_list]
def initialize_options(arguments: Optional[List[str]] = None) -> None: """Load config options and store them inside options module.""" new_options = cli.get_config(arguments or []) new_options.cwd = pathlib.Path.cwd() if new_options.version: ansible_version, err = check_ansible_presence() print('ansible-lint {ver!s} using ansible {ansible_ver!s}'.format( ver=__version__, ansible_ver=ansible_version)) if err: _logger.error(err) sys.exit(ANSIBLE_MISSING_RC) sys.exit(0) if new_options.colored is None: new_options.colored = should_do_markup() # persist loaded configuration inside options module for k, v in new_options.__dict__.items(): setattr(options, k, v) # rename deprecated ids/tags to newer names options.tags = [normalize_tag(tag) for tag in options.tags] options.skip_list = [normalize_tag(tag) for tag in options.skip_list] options.warn_list = [normalize_tag(tag) for tag in options.warn_list] options.configured = True # 6 chars of entropy should be enough cache_key = hashlib.sha256(os.path.abspath( options.project_dir).encode()).hexdigest()[:6] options.cache_dir = "%s/ansible-lint/%s" % ( os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache")), cache_key, )
def test_expand_path_user_and_vars_config_file(base_arguments): """Ensure user and vars are expanded when specified as exclude_paths.""" config1 = cli.get_config( base_arguments + ["-c", "test/fixtures/exclude-paths-with-expands.yml"]) config2 = cli.get_config( base_arguments + ["--exclude", "~/.ansible/roles", "--exclude", "$HOME/.ansible/roles"]) assert str( config1.exclude_paths[0]) == os.path.expanduser("~/.ansible/roles") assert str( config2.exclude_paths[0]) == os.path.expanduser("~/.ansible/roles") assert str( config1.exclude_paths[1]) == os.path.expandvars("$HOME/.ansible/roles") assert str( config2.exclude_paths[1]) == os.path.expandvars("$HOME/.ansible/roles")
def test_auto_detect_exclude(monkeypatch: MonkeyPatch) -> None: """Verify that exclude option can be used to narrow down detection.""" options = cli.get_config(['--exclude', 'foo']) def mockreturn(options: Namespace) -> List[str]: return ['foo/playbook.yml', 'bar/playbook.yml'] monkeypatch.setattr(utils, 'discover_lintables', mockreturn) result = utils.get_lintables(options) assert result == [Lintable('bar/playbook.yml', kind='playbook')]
def test_auto_detect_exclude(monkeypatch): """Verify that exclude option can be used to narrow down detection.""" options = cli.get_config(['--exclude', 'foo']) def mockreturn(options): return ['foo/playbook.yml', 'bar/playbook.yml'] monkeypatch.setattr(utils, 'get_yaml_files', mockreturn) result = utils.get_playbooks_and_roles(options) assert result == ['bar/playbook.yml']
def test_get_yaml_files_umlaut(monkeypatch): """Verify that filenames containing German umlauts are not garbled by the get_yaml_files.""" options = cli.get_config([]) test_dir = Path(__file__).resolve().parent lint_path = test_dir / '..' / 'examples' / 'playbooks' monkeypatch.chdir(str(lint_path)) files = file_utils.get_yaml_files(options) assert '"with-umlaut-\\303\\244.yml"' not in files assert 'with-umlaut-ä.yml' in files
def test_auto_detect(monkeypatch, path: str, kind: FileType) -> None: """Verify auto-detection logic.""" options = cli.get_config([]) def mockreturn(options): return [path] monkeypatch.setattr(utils, 'get_yaml_files', mockreturn) result = utils.get_lintables(options) assert result == [Lintable(path, kind=kind)]
def test_get_yaml_files_git_verbose(reset_env_var, message_prefix, monkeypatch, caplog): options = cli.get_config(['-v']) utils.initialize_logger(options.verbosity) monkeypatch.setenv(reset_env_var, '') utils.get_yaml_files(options) expected_info = ("ansiblelint.utils", logging.INFO, 'Discovering files to lint: git ls-files *.yaml *.yml') assert expected_info in caplog.record_tuples assert any(m.startswith(message_prefix) for m in caplog.messages)
def test_logger_debug(caplog): """Test that the double verbosity arg causes logger to be DEBUG.""" options = cli.get_config(['-vv']) initialize_logger(options.verbosity) expected_info = ( "ansiblelint.__main__", logging.DEBUG, 'Logging initialized to level 10', ) assert expected_info in caplog.record_tuples
def test_get_yaml_files_git_verbose(reset_env_var, message_prefix, monkeypatch, caplog): """Ensure that autodiscovery lookup failures are logged.""" options = cli.get_config(['-v']) initialize_logger(options.verbosity) monkeypatch.setenv(reset_env_var, '') file_utils.get_yaml_files(options) expected_info = ( "ansiblelint", logging.INFO, 'Discovering files to lint: git ls-files -z', ) assert expected_info in caplog.record_tuples assert any(m.startswith(message_prefix) for m in caplog.messages)
def test_discover_lintables_git_verbose( reset_env_var: str, message_prefix: str, monkeypatch: MonkeyPatch, caplog: LogCaptureFixture, ) -> None: """Ensure that autodiscovery lookup failures are logged.""" options = cli.get_config(['-v']) initialize_logger(options.verbosity) monkeypatch.setenv(reset_env_var, '') file_utils.discover_lintables(options) assert any(m[2].startswith("Looking up for files") for m in caplog.record_tuples) assert any(m.startswith(message_prefix) for m in caplog.messages)
def test_auto_detect(monkeypatch, path: str, kind: FileType) -> None: """Verify auto-detection logic.""" options = cli.get_config([]) def mockreturn(options): return [path] # assert Lintable is able to determine file type lintable_detected = Lintable(path) lintable_expected = Lintable(path, kind=kind) assert lintable_detected == lintable_expected monkeypatch.setattr(utils, 'get_yaml_files', mockreturn) result = utils.get_lintables(options) # get_lintable could return additional files and we only care to see # that the given file is among the returned list. assert lintable_expected in result
def test_default_kinds(monkeypatch: MonkeyPatch, path: str, kind: FileType) -> None: """Verify auto-detection logic based on DEFAULT_KINDS.""" options = cli.get_config([]) def mockreturn(options: Namespace) -> Dict[str, Any]: return {path: kind} # assert Lintable is able to determine file type lintable_detected = Lintable(path) lintable_expected = Lintable(path, kind=kind) assert lintable_detected == lintable_expected monkeypatch.setattr(file_utils, 'discover_lintables', mockreturn) result = file_utils.discover_lintables(options) # get_lintable could return additional files and we only care to see # that the given file is among the returned list. assert lintable_detected.name in result assert lintable_detected.kind == result[lintable_expected.name]
def initialize_options(arguments: List[str]): """Load config options and store them inside options module.""" new_options = cli.get_config(arguments) new_options.cwd = pathlib.Path.cwd() if new_options.version: print('ansible-lint {ver!s}'.format(ver=__version__)) # assure we fail if ansible is missing, even for version printing check_ansible_presence() sys.exit(0) if new_options.colored is None: new_options.colored = should_do_markup() # persist loaded configuration inside options module for k, v in new_options.__dict__.items(): setattr(options, k, v) # rename deprecated ids/tags to newer names options.tags = [normalize_tag(tag) for tag in options.tags] options.skip_list = [normalize_tag(tag) for tag in options.skip_list] options.warn_list = [normalize_tag(tag) for tag in options.warn_list]
def main() -> int: """Linter CLI entry point.""" cwd = pathlib.Path.cwd() options = cli.get_config(sys.argv[1:]) initialize_logger(options.verbosity) _logger.debug("Options: %s", options) formatter_factory: Any = formatters.Formatter if options.quiet: formatter_factory = formatters.QuietFormatter if options.parseable: formatter_factory = formatters.ParseableFormatter if options.parseable_severity: formatter_factory = formatters.ParseableSeverityFormatter formatter = formatter_factory(cwd, options.display_relative_path) if options.use_default_rules: rulesdirs = options.rulesdir + [DEFAULT_RULESDIR] else: rulesdirs = options.rulesdir or [DEFAULT_RULESDIR] rules = RulesCollection(rulesdirs) if options.listrules: formatted_rules = rules if options.format == 'plain' else rules_as_rst( rules) print(formatted_rules) return 0 if options.listtags: print(rules.listtags()) return 0 if isinstance(options.tags, str): options.tags = options.tags.split(',') skip = set() for s in options.skip_list: skip.update(str(s).split(',')) options.skip_list = frozenset(skip) if not options.playbook: # no args triggers auto-detection mode playbooks = get_playbooks_and_roles(options=options) else: playbooks = sorted(set(options.playbook)) matches = list() checked_files: Set[str] = set() for playbook in playbooks: runner = Runner(rules, playbook, options.tags, options.skip_list, options.exclude_paths, options.verbosity, checked_files) matches.extend(runner.run()) # Assure we do not print duplicates and the order is consistent matches = sorted(set(matches)) for match in matches: print(formatter.format(match, options.colored)) # If run under GitHub Actions we also want to emit output recognized by it. if os.getenv('GITHUB_ACTIONS') == 'true' and os.getenv('GITHUB_WORKFLOW'): formatter = formatters.AnnotationsFormatter(cwd, True) for match in matches: print(formatter.format(match)) if matches: return 2 else: return 0
def test_extra_vars_loaded(base_arguments: List[str]) -> None: """Ensure ``extra_vars`` option is loaded from file config.""" config = cli.get_config(base_arguments + ["-c", "test/fixtures/config-with-extra-vars.yml"]) assert config.extra_vars == {'foo': 'bar', 'knights_favorite_word': 'NI'}
def test_config_failure(base_arguments: List[str], config_file: str) -> None: """Ensures specific config files produce error code 2.""" with pytest.raises(SystemExit, match="^2$"): cli.get_config(base_arguments + ["-c", config_file])
def main(): cwd = pathlib.Path.cwd() options = cli.get_config(sys.argv[1:]) initialize_logger(options.verbosity) formatter_factory = formatters.Formatter if options.quiet: formatter_factory = formatters.QuietFormatter if options.parseable: formatter_factory = formatters.ParseableFormatter if options.parseable_severity: formatter_factory = formatters.ParseableSeverityFormatter formatter = formatter_factory(cwd, options.display_relative_path) # no args triggers auto-detection mode if not options.playbook and not (options.listrules or options.listtags): cli.print_help(file=sys.stderr) return 1 if options.use_default_rules: rulesdirs = options.rulesdir + [default_rulesdir] else: rulesdirs = options.rulesdir or [default_rulesdir] rules = RulesCollection(rulesdirs) if options.listrules: print(rules) return 0 if options.listtags: print(rules.listtags()) return 0 if isinstance(options.tags, str): options.tags = options.tags.split(',') skip = set() for s in options.skip_list: skip.update(str(s).split(',')) options.skip_list = frozenset(skip) playbooks = sorted(set(options.playbook)) matches = list() checked_files = set() for playbook in playbooks: runner = Runner(rules, playbook, options.tags, options.skip_list, options.exclude_paths, options.verbosity, checked_files) matches.extend(runner.run()) matches.sort(key=lambda x: (normpath(x.filename), x.linenumber, x.rule.id)) for match in matches: print(formatter.format(match, options.colored)) if len(matches): return 2 else: return 0
def main() -> int: """Linter CLI entry point.""" cwd = pathlib.Path.cwd() options = cli.get_config(sys.argv[1:]) initialize_logger(options.verbosity) _logger.debug("Options: %s", options) formatter_factory = choose_formatter_factory(options) formatter = formatter_factory(cwd, options.display_relative_path) rulesdirs = get_rules_dirs([str(rdir) for rdir in options.rulesdir], options.use_default_rules) rules = RulesCollection(rulesdirs) if options.listrules: console.print(_rule_format_map[options.format](rules), highlight=False) return 0 if options.listtags: print(rules.listtags()) return 0 if isinstance(options.tags, str): options.tags = options.tags.split(',') skip = set() for s in options.skip_list: skip.update(str(s).split(',')) options.skip_list = frozenset(skip) matches = _get_matches(rules, options) # Assure we do not print duplicates and the order is consistent matches = sorted(set(matches)) mark_as_success = False if matches and options.progressive: _logger.info( "Matches found, running again on previous revision in order to detect regressions" ) with _previous_revision(): old_matches = _get_matches(rules, options) # remove old matches from current list matches_delta = list(set(matches) - set(old_matches)) if len(matches_delta) == 0: _logger.warning( "Total violations not increased since previous " "commit, will mark result as success. (%s -> %s)", len(old_matches), len(matches_delta)) mark_as_success = True ignored = 0 for match in 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) _render_matches(matches, options, formatter, cwd) if matches and not mark_as_success: return report_outcome(matches, options=options) else: return 0
def main(argv: List[str] = None) -> int: """Linter CLI entry point.""" if argv is None: argv = sys.argv options = cli.get_config(argv[1:]) options.cwd = pathlib.Path.cwd() if options.version: print('ansible-lint {ver!s}'.format(ver=__version__)) # assure we fail if ansible is missing, even for version printing check_ansible_presence() sys.exit(0) if options.colored is None: options.colored = should_do_markup() 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() # 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 from ansiblelint.rules import RulesCollection rules = RulesCollection(options.rulesdirs) if options.listrules: _rule_format_map = { 'plain': 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 main() -> int: """Linter CLI entry point.""" cwd = pathlib.Path.cwd() options = cli.get_config(sys.argv[1:]) initialize_logger(options.verbosity) _logger.debug("Options: %s", options) formatter_factory = choose_formatter_factory(options) formatter = formatter_factory(cwd, options.display_relative_path) rulesdirs = get_rules_dirs([str(rdir) for rdir in options.rulesdir], options.use_default_rules) rules = RulesCollection(rulesdirs) if options.listrules: console.print(_rule_format_map[options.format](rules), highlight=False) return 0 if options.listtags: print(rules.listtags()) return 0 if isinstance(options.tags, str): options.tags = options.tags.split(',') skip = set() for s in options.skip_list: skip.update(str(s).split(',')) options.skip_list = frozenset(skip) if not options.playbook: # no args triggers auto-detection mode playbooks = get_playbooks_and_roles(options=options) else: playbooks = sorted(set(options.playbook)) matches = list() checked_files: Set[str] = set() for playbook in playbooks: runner = Runner(rules, playbook, options.tags, options.skip_list, options.exclude_paths, options.verbosity, checked_files) matches.extend(runner.run()) # Assure we do not print duplicates and the order is consistent matches = sorted(set(matches)) for match in matches: print(formatter.format(match, options.colored)) # If run under GitHub Actions we also want to emit output recognized by it. if os.getenv('GITHUB_ACTIONS') == 'true' and os.getenv('GITHUB_WORKFLOW'): formatter = formatters.AnnotationsFormatter(cwd, True) for match in matches: print(formatter.format(match)) if matches: return report_outcome(matches, options=options) else: return 0
def main() -> int: """Linter CLI entry point.""" cwd = pathlib.Path.cwd() options = cli.get_config(sys.argv[1:]) initialize_logger(options.verbosity) _logger.debug("Options: %s", options) formatter_factory: Any = formatters.Formatter if options.quiet: formatter_factory = formatters.QuietFormatter if options.parseable: formatter_factory = formatters.ParseableFormatter if options.parseable_severity: formatter_factory = formatters.ParseableSeverityFormatter formatter = formatter_factory(cwd, options.display_relative_path) if options.use_default_rules: rulesdirs = options.rulesdir + [DEFAULT_RULESDIR] else: rulesdirs = options.rulesdir or [DEFAULT_RULESDIR] rules = RulesCollection(rulesdirs) if options.listrules: formatted_rules = rules if options.format == 'plain' else rules_as_rst(rules) print(formatted_rules) return 0 if options.listtags: print(rules.listtags()) return 0 if isinstance(options.tags, str): options.tags = options.tags.split(',') skip = set() for s in options.skip_list: skip.update(str(s).split(',')) options.skip_list = frozenset(skip) if not options.playbook: # no args triggers auto-detection mode playbooks = get_playbooks_and_roles(options=options) else: playbooks = sorted(set(options.playbook)) matches = list() checked_files: Set[Any] = set() for playbook in playbooks: runner = Runner(rules, playbook, options.tags, options.skip_list, options.exclude_paths, options.verbosity, checked_files) matches.extend(runner.run()) for match in sorted(matches): print(formatter.format(match, options.colored)) if matches: return 2 else: return 0
def main(argv: List[str] = None) -> int: """Linter CLI entry point.""" if argv is None: argv = sys.argv cwd = pathlib.Path.cwd() options = cli.get_config(argv[1:]) if options.colored is None: options.colored = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty() if options.colored is not None: console_options["force_terminal"] = options.colored reconfigure(console_options) initialize_logger(options.verbosity) _logger.debug("Options: %s", options) formatter_factory = choose_formatter_factory(options) formatter = formatter_factory(cwd, options.display_relative_path) rulesdirs = get_rules_dirs([str(rdir) for rdir in options.rulesdir], options.use_default_rules) rules = RulesCollection(rulesdirs) if options.listrules: 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(',') options.skip_list = _sanitize_list_options(options.skip_list) options.warn_list = _sanitize_list_options(options.warn_list) 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) _render_matches(result.matches, options, formatter, cwd) return report_outcome(result, mark_as_success=mark_as_success, options=options)