Skip to content

Commit

Permalink
Recurse inside given folders
Browse files Browse the repository at this point in the history
When given folders, use the normal detection algorithm to look inside
them, making the outcome in sync with user expectations.

Fixes: #1241
  • Loading branch information
ssbarnea committed Feb 3, 2021
1 parent 74a9e05 commit 4c9e692
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 47 deletions.
54 changes: 54 additions & 0 deletions src/ansiblelint/file_utils.py
@@ -1,6 +1,11 @@
"""Utility functions related to file operations."""
import logging
import os
import subprocess
from argparse import Namespace
from collections import OrderedDict
from contextlib import contextmanager
from functools import lru_cache
from pathlib import Path
from typing import TYPE_CHECKING, Any, Iterator, List, Optional, Union

Expand All @@ -15,6 +20,8 @@
else:
BasePathLike = os.PathLike

_logger = logging.getLogger(__package__)


def normpath(path: Union[str, BasePathLike]) -> str:
"""
Expand Down Expand Up @@ -137,3 +144,50 @@ def __eq__(self, other: object) -> bool:
def __repr__(self) -> str:
"""Return user friendly representation of a lintable."""
return f"{self.name} ({self.kind})"


def get_yaml_files(options: Namespace) -> dict:
"""Find all yaml files."""
# git is preferred as it also considers .gitignore
git_command = ['git', 'ls-files', '*.yaml', '*.yml']
_logger.info("Discovering files to lint: %s", ' '.join(git_command))

out = None

try:
out = subprocess.check_output(
git_command,
stderr=subprocess.STDOUT,
universal_newlines=True
).splitlines()
except subprocess.CalledProcessError as exc:
_logger.warning(
"Failed to discover yaml files to lint using git: %s",
exc.output.rstrip('\n')
)
except FileNotFoundError as exc:
if options.verbosity:
_logger.warning(
"Failed to locate command: %s", exc
)

if out is None:
out = [
os.path.join(root, name)
for root, dirs, files in os.walk('.')
for name in files
if name.endswith('.yaml') or name.endswith('.yml')
]

return OrderedDict.fromkeys(sorted(out))


@lru_cache(maxsize=128)
def expand_dir_to_lintables(path: Lintable) -> List[Lintable]:
"""Return all recognized lintables within given directory."""
result = []

for filename in get_yaml_files(options):
if filename.startswith(str(path.path)):
result.append(Lintable(filename))
return result
10 changes: 8 additions & 2 deletions src/ansiblelint/runner.py
@@ -1,4 +1,5 @@
"""Runner implementation."""
import copy
import logging
import multiprocessing
import multiprocessing.pool
Expand All @@ -10,7 +11,7 @@
import ansiblelint.utils
from ansiblelint._internal.rules import LoadingFailureRule
from ansiblelint.errors import MatchError
from ansiblelint.file_utils import Lintable
from ansiblelint.file_utils import Lintable, expand_dir_to_lintables
from ansiblelint.rules.AnsibleSyntaxCheckRule import AnsibleSyntaxCheckRule

if TYPE_CHECKING:
Expand Down Expand Up @@ -52,6 +53,11 @@ def __init__(
item = Lintable(item)
self.lintables.add(item)

# Expand folders (roles) to their components
for item in copy.copy(self.lintables):
if item.path.is_dir():
self.lintables.update(expand_dir_to_lintables(item))

self.tags = tags
self.skip_list = skip_list
self._update_exclude_paths(exclude_paths)
Expand Down Expand Up @@ -126,7 +132,7 @@ def worker(lintable: Lintable) -> List[MatchError]:
skip_list=self.skip_list))

# update list of checked files
self.checked_files.update([str(x.path) for x in files])
self.checked_files.update([str(x.path) for x in self.lintables])

return sorted(set(matches))

Expand Down
40 changes: 1 addition & 39 deletions src/ansiblelint/utils.py
Expand Up @@ -23,9 +23,7 @@
import inspect
import logging
import os
import subprocess
from argparse import Namespace
from collections import OrderedDict
from collections.abc import ItemsView
from functools import lru_cache
from pathlib import Path
Expand Down Expand Up @@ -60,7 +58,7 @@
from ansiblelint._internal.rules import AnsibleParserErrorRule, LoadingFailureRule, RuntimeErrorRule
from ansiblelint.constants import FileType
from ansiblelint.errors import MatchError
from ansiblelint.file_utils import Lintable
from ansiblelint.file_utils import Lintable, get_yaml_files

# ansible-lint doesn't need/want to know about encrypted secrets, so we pass a
# string as the password to enable such yaml files to be opened and parsed
Expand Down Expand Up @@ -711,42 +709,6 @@ def is_playbook(filename: str) -> bool:
return False


def get_yaml_files(options: Namespace) -> dict:
"""Find all yaml files."""
# git is preferred as it also considers .gitignore
git_command = ['git', 'ls-files', '*.yaml', '*.yml']
_logger.info("Discovering files to lint: %s", ' '.join(git_command))

out = None

try:
out = subprocess.check_output(
git_command,
stderr=subprocess.STDOUT,
universal_newlines=True
).splitlines()
except subprocess.CalledProcessError as exc:
_logger.warning(
"Failed to discover yaml files to lint using git: %s",
exc.output.rstrip('\n')
)
except FileNotFoundError as exc:
if options.verbosity:
_logger.warning(
"Failed to locate command: %s", exc
)

if out is None:
out = [
os.path.join(root, name)
for root, dirs, files in os.walk('.')
for name in files
if name.endswith('.yaml') or name.endswith('.yml')
]

return OrderedDict.fromkeys(sorted(out))


# pylint: disable=too-many-statements
def get_lintables(
options: Namespace = Namespace(),
Expand Down
2 changes: 1 addition & 1 deletion test/TestCliRolePaths.py
Expand Up @@ -129,7 +129,7 @@ def test_run_single_role_path_with_roles_path_env(self):
role_path = 'roles/test-role'

env = os.environ.copy()
env['ANSIBLE_ROLES_PATH'] = os.path.join(cwd, 'use-as-default-roles-path')
env['ANSIBLE_ROLES_PATH'] = os.path.realpath(os.path.join(cwd, "../examples/roles"))

result = run_ansible_lint(role_path, cwd=cwd, env=env)
assert 'Use shell only when shell functionality is required' in result.stdout
Expand Down
4 changes: 3 additions & 1 deletion test/TestRunner.py
Expand Up @@ -73,7 +73,9 @@ def test_runner_with_directory(default_rules_collection, directory_name) -> None
runner = Runner(
directory_name,
rules=default_rules_collection)
assert list(runner.lintables)[0].kind == 'role'

expected = Lintable(name=directory_name, kind="role")
assert expected in runner.lintables


def test_files_not_scanned_twice(default_rules_collection) -> None:
Expand Down
8 changes: 4 additions & 4 deletions test/TestUtils.py
Expand Up @@ -30,7 +30,7 @@

import pytest

from ansiblelint import cli, constants, utils
from ansiblelint import cli, constants, file_utils, utils
from ansiblelint.__main__ import initialize_logger
from ansiblelint.cli import get_rules_dirs
from ansiblelint.constants import FileType
Expand Down Expand Up @@ -186,10 +186,10 @@ def test_get_yaml_files_git_verbose(
options = cli.get_config(['-v'])
initialize_logger(options.verbosity)
monkeypatch.setenv(reset_env_var, '')
utils.get_yaml_files(options)
file_utils.get_yaml_files(options)

expected_info = (
"ansiblelint.utils",
"ansiblelint",
logging.INFO,
'Discovering files to lint: git ls-files *.yaml *.yml')

Expand Down Expand Up @@ -220,7 +220,7 @@ def test_get_yaml_files_silent(is_in_git, monkeypatch, capsys):
)

monkeypatch.chdir(str(lint_path))
files = utils.get_yaml_files(options)
files = file_utils.get_yaml_files(options)
stderr = capsys.readouterr().err
assert not stderr, 'No stderr output is expected when the verbosity is off'
assert len(files) == yaml_count, (
Expand Down

0 comments on commit 4c9e692

Please sign in to comment.