Skip to content

Commit

Permalink
Add option to allow running with missing plugins and collection depen…
Browse files Browse the repository at this point in the history
…dencies (#3913)
  • Loading branch information
ssbarnea committed Nov 28, 2023
1 parent f2d1cdd commit 7a2ac15
Show file tree
Hide file tree
Showing 11 changed files with 87 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:
env:
# Number of expected test passes, safety measure for accidental skip of
# tests. Update value if you add/remove tests.
PYTEST_REQPASS: 845
PYTEST_REQPASS: 847
steps:
- uses: actions/checkout@v4
with:
Expand Down
6 changes: 6 additions & 0 deletions examples/playbooks/nodeps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
- name: Example
hosts: localhost
tasks:
- name: Calling a module that is not installed
a.b.c: {}
7 changes: 7 additions & 0 deletions examples/playbooks/nodeps2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
- name: Fixture for nodeps with missing filter
hosts: localhost
tasks:
- name: Calling a module that is not installed
ansible.builtin.debug:
msg: "{{ foo | missing_filter }}"
1 change: 1 addition & 0 deletions src/ansiblelint/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ def main(argv: list[str] | None = None) -> int:
),
)
or options.offline
or options.nodeps
)

if not skip_schema_update:
Expand Down
6 changes: 6 additions & 0 deletions src/ansiblelint/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@ class Options: # pylint: disable=too-many-instance-attributes
ignore_file: Path | None = None
max_tasks: int = 100
max_block_depth: int = 20
nodeps: bool = bool(int(os.environ.get("ANSIBLE_LINT_NODEPS", "0")))

def __post_init__(self) -> None:
"""Extra initialization logic."""
if self.nodeps:
self.offline = True


options = Options()
Expand Down
1 change: 1 addition & 0 deletions src/ansiblelint/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"ANSIBLE_LINT_IGNORE_FILE": "Define it to override the name of the default ignore file `.ansible-lint-ignore`",
"ANSIBLE_LINT_WRITE_TMP": "Tells linter to dump fixes into different temp files instead of overriding original. Used internally for testing.",
SKIP_SCHEMA_UPDATE: "Tells ansible-lint to skip schema refresh.",
"ANSIBLE_LINT_NODEPS": "Avoids installing content dependencies and avoids performing checks that would fail when modules are not installed. Far less violations will be reported.",
}

EPILOG = (
Expand Down
10 changes: 7 additions & 3 deletions src/ansiblelint/rules/syntax_check.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ You can exclude these files from linting, but it is better to make sure they can
be loaded by Ansible. This is often achieved by editing the inventory file
and/or `ansible.cfg` so ansible can load required variables.

If undefined variables cause the failure, you can use the jinja `default()`
If undefined variables cause the failure, you can use the Jinja `default()`
filter to provide fallback values, like in the example below.

This rule is among the few `unskippable` rules that cannot be added to
Expand All @@ -20,9 +20,13 @@ fixtures that are invalid on purpose.
One of the most common sources of errors is a failure to assert the presence of
various variables at the beginning of the playbook.

This rule can produce messages like below:
This rule can produce messages like:

- `syntax-check[empty-playbook]` is raised when a playbook file has no content.
- `syntax-check[empty-playbook]`: Empty playbook, nothing to do
- `syntax-check[malformed]`: A malformed block was encountered while loading a block
- `syntax-check[missing-file]`: Unable to retrieve file contents ... Could not find or access ...
- `syntax-check[unknown-module]`: couldn't resolve module/action
- `syntax-check[specific]`: for other errors not mentioned above.

## Problematic code

Expand Down
21 changes: 15 additions & 6 deletions src/ansiblelint/rules/syntax_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class KnownError:
regex: re.Pattern[str]


# Order matters, we only report the first matching pattern, the one at the end
# is used to match generic or less specific patterns.
OUTPUT_PATTERNS = (
KnownError(
tag="missing-file",
Expand All @@ -25,23 +27,30 @@ class KnownError:
),
),
KnownError(
tag="specific",
tag="empty-playbook",
regex=re.compile(
r"^ERROR! (?P<title>[^\n]*)\n\nThe error appears to be in '(?P<filename>[\w\/\.\-]+)': line (?P<line>\d+), column (?P<column>\d+)",
"Empty playbook, nothing to do",
re.MULTILINE | re.S | re.DOTALL,
),
),
KnownError(
tag="empty-playbook",
tag="malformed",
regex=re.compile(
"Empty playbook, nothing to do",
"^ERROR! (?P<title>A malformed block was encountered while loading a block[^\n]*)",
re.MULTILINE | re.S | re.DOTALL,
),
),
KnownError(
tag="malformed",
tag="unknown-module",
regex=re.compile(
"^ERROR! (?P<title>A malformed block was encountered while loading a block[^\n]*)",
r"^ERROR! (?P<title>couldn't resolve module/action [^\n]*)\n\nThe error appears to be in '(?P<filename>[\w\/\.\-]+)': line (?P<line>\d+), column (?P<column>\d+)",
re.MULTILINE | re.S | re.DOTALL,
),
),
KnownError(
tag="specific",
regex=re.compile(
r"^ERROR! (?P<title>[^\n]*)\n\nThe error appears to be in '(?P<filename>[\w\/\.\-]+)': line (?P<line>\d+), column (?P<column>\d+)",
re.MULTILINE | re.S | re.DOTALL,
),
),
Expand Down
32 changes: 19 additions & 13 deletions src/ansiblelint/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ def _get_ansible_syntax_check_matches(
filename = lintable
lineno = 1
column = None
ignore_rc = False

stderr = strip_ansi_escape(run.stderr)
stdout = strip_ansi_escape(run.stdout)
Expand All @@ -396,19 +397,24 @@ def _get_ansible_syntax_check_matches(
else:
filename = lintable
column = int(groups.get("column", 1))
results.append(
MatchError(
message=title,
lintable=filename,
lineno=lineno,
column=column,
rule=rule,
details=details,
tag=f"{rule.id}[{pattern.tag}]",
),
)

if not results:

if pattern.tag == "unknown-module" and app.options.nodeps:
ignore_rc = True
else:
results.append(
MatchError(
message=title,
lintable=filename,
lineno=lineno,
column=column,
rule=rule,
details=details,
tag=f"{rule.id}[{pattern.tag}]",
),
)
break

if not results and not ignore_rc:
rule = self.rules["internal-error"]
message = (
f"Unexpected error code {run.returncode} from "
Expand Down
4 changes: 2 additions & 2 deletions test/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ def test_example(default_rules_collection: RulesCollection) -> None:
"examples/playbooks/syntax-error-string.yml",
6,
7,
id="syntax-error",
id="0",
),
pytest.param("examples/playbooks/syntax-error.yml", 2, 3, id="syntax-error"),
pytest.param("examples/playbooks/syntax-error.yml", 2, 3, id="1"),
),
)
def test_example_syntax_error(
Expand Down
22 changes: 22 additions & 0 deletions test/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,25 @@ def test_get_version_warning(
else:
assert check in msg
assert len(msg.split("\n")) == outlen


@pytest.mark.parametrize(
("lintable"),
(
pytest.param("examples/playbooks/nodeps.yml", id="1"),
pytest.param("examples/playbooks/nodeps2.yml", id="2"),
),
)
def test_nodeps(lintable: str) -> None:
"""Asserts ability to be called w/ or w/o venv activation."""
env = os.environ.copy()
env["ANSIBLE_LINT_NODEPS"] = "1"
py_path = Path(sys.executable).parent
proc = subprocess.run(
[str(py_path / "ansible-lint"), lintable],
check=False,
capture_output=True,
text=True,
env=env,
)
assert proc.returncode == 0, proc

0 comments on commit 7a2ac15

Please sign in to comment.