Skip to content

Commit

Permalink
Adds experimental stdin support
Browse files Browse the repository at this point in the history
Allow ansible-lint to perform linting from stdin when filename
argument '/dev/stdin' or '-' are given.

Keep in mind:
- no argument still means auto-detect as I found no reliable way
  to identify that the tool was used with shell pipes
- filename reported is 'stdin', same as flake8 does
- received content is assumed to be a playbook, you cannot lint
  a task file this way. Others are welcomed to propose improvements
  for detecting the file type based on its content, because we do
  not have any `filename` available.
  • Loading branch information
ssbarnea committed Feb 15, 2021
1 parent e99b2cc commit 7c23ddb
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 8 deletions.
34 changes: 29 additions & 5 deletions src/ansiblelint/file_utils.py
Expand Up @@ -3,10 +3,12 @@
import logging
import os
import subprocess
import sys
from argparse import Namespace
from collections import OrderedDict
from contextlib import contextmanager
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Any, Dict, Iterator, List, Optional, Set, Union

import wcmatch.pathlib
Expand Down Expand Up @@ -83,6 +85,11 @@ def kind_from_path(path: Path):
return k
if path.is_dir():
return "role"

if str(path) == '/dev/stdin':
# --stdin-display-name=stdin (flake8)
return "playbook"
# print(path.absolute)
raise RuntimeError("Unable to determine file type for %s" % pathex)


Expand All @@ -100,6 +107,10 @@ def __init__(
kind: Optional[FileType] = None,
):
"""Create a Lintable instance."""
# Filename is effective file on disk, for stdin is a namedtempfile
self.filename: str = str(name)
self.dir: str = ""

if isinstance(name, str):
self.name = normpath(name)
self.path = Path(self.name)
Expand All @@ -118,12 +129,25 @@ def __init__(
if role.exists:
self.role = role.name

self.kind = kind or kind_from_path(self.path)
# We store absolute directory in dir
if self.kind == "role":
self.dir = str(self.path.resolve())
if str(self.path) in ['/dev/stdin', '-']:
self.file = NamedTemporaryFile(mode="w+", suffix="playbook.yml")
self.filename = self.file.name
self._content = sys.stdin.read()
self.file.write(self._content)
self.file.flush()
self.path = Path(self.file.name)
self.name = 'stdin'
self.kind = 'playbook'
self.dir = '/'
# print(555, self.name, self.path, self.filename)
else:
self.dir = str(self.path.parent.resolve())
self.kind = kind or kind_from_path(self.path)
# We store absolute directory in dir
if not self.dir:
if self.kind == "role":
self.dir = str(self.path.resolve())
else:
self.dir = str(self.path.parent.resolve())

def __getitem__(self, item):
"""Provide compatibility subscriptable support."""
Expand Down
14 changes: 11 additions & 3 deletions src/ansiblelint/runner.py
Expand Up @@ -41,7 +41,7 @@ def __init__(
skip_list: List[str] = [],
exclude_paths: List[str] = [],
verbosity: int = 0,
checked_files: Optional[Set[str]] = None
checked_files: Optional[Set[Lintable]] = None
) -> None:
"""Initialize a Runner instance."""
self.rules = rules
Expand Down Expand Up @@ -127,7 +127,7 @@ def worker(lintable: Lintable) -> List[MatchError]:
files = [value for n, value in enumerate(files) if value not in files[:n]]

for file in self.lintables:
if str(file.path) in self.checked_files:
if file in self.checked_files:
continue
_logger.debug(
"Examining %s of type %s",
Expand All @@ -140,7 +140,7 @@ def worker(lintable: Lintable) -> List[MatchError]:
)

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

# remove any matches made inside excluded files
matches = list(
Expand Down Expand Up @@ -189,4 +189,12 @@ def _get_matches(rules: "RulesCollection", options: "Namespace") -> LintResult:
# Assure we do not print duplicates and the order is consistent
matches = sorted(set(matches))

# Convert reported filenames into human redable ones, so we hide the
# fact we used temporary files when processing input from stdin.
for match in matches:
for lintable in lintables:
if match.filename == lintable.filename:
match.filename = lintable.name
break

return LintResult(matches=matches, files=checked_files)

0 comments on commit 7c23ddb

Please sign in to comment.