Skip to content

Commit 2ad6390

Browse files
committed
fix: allow valid tabs in lineinfile nested in blocks (#4719)
1 parent a2bc8b8 commit 2ad6390

File tree

3 files changed

+50
-15
lines changed

3 files changed

+50
-15
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
- name: Example playbook
3+
hosts: localhost
4+
tasks:
5+
- name: Example block
6+
block:
7+
- name: This should now pass linting
8+
ansible.builtin.lineinfile:
9+
path: some.txt
10+
regexp: "^\t$" # Tab inside allowed key
11+
line: "string with \t inside"

src/ansiblelint/rules/args.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
from __future__ import annotations
44

5+
import atexit
56
import contextlib
67
import importlib.util
78
import io
89
import json
910
import logging
1011
import re
12+
import shutil
1113
import sys
14+
import tempfile
1215
from typing import TYPE_CHECKING, Any
1316

1417
# pylint: disable=preferred-module
@@ -60,9 +63,19 @@
6063
# https://github.com/ansible/ansible-lint/issues/3152
6164
"ansible.posix.synchronize": ["use_ssh_args"],
6265
}
66+
_SAFE_ASYNC_DIR = tempfile.mkdtemp(prefix="ansible-lint-async-")
67+
68+
69+
def _cleanup_async_dir() -> None:
70+
"""Safely remove the temp directory and all its contents."""
71+
shutil.rmtree(_SAFE_ASYNC_DIR, ignore_errors=True)
72+
73+
74+
atexit.register(_cleanup_async_dir)
75+
6376
workarounds_inject_map = {
6477
# https://github.com/ansible/ansible-lint/issues/2824
65-
"ansible.builtin.async_status": {"_async_dir": "/tmp/ansible-async"},
78+
"ansible.builtin.async_status": {"_async_dir": _SAFE_ASYNC_DIR},
6679
}
6780
workarounds_mutex_args_map = {
6881
# https://github.com/ansible/ansible-lint/issues/4623

src/ansiblelint/rules/no_tabs.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ def matchtask(
5454
file: Lintable | None = None,
5555
) -> list[MatchError]:
5656
result = []
57-
action = task["action"]["__ansible_module__"]
58-
for k, v, _ in nested_items_path(task):
57+
# Check the key/value pairs found by the nested pathing
58+
for k, v, _path in nested_items_path(task):
59+
# Check if the Key itself has a tab (almost never allowed)
5960
if isinstance(k, str) and "\t" in k and not has_jinja(k):
6061
result.append(
6162
self.create_matcherror(
@@ -64,19 +65,20 @@ def matchtask(
6465
filename=file,
6566
)
6667
)
67-
if (
68-
isinstance(v, str)
69-
and "\t" in v
70-
and (action, k) not in self.allow_list
71-
and not has_jinja(v)
72-
):
73-
result.append(
74-
self.create_matcherror(
75-
message=self.shortdesc,
76-
data=v,
77-
filename=file,
68+
69+
# Check if the Value has a tab
70+
if isinstance(v, str) and "\t" in v and not has_jinja(v):
71+
# We check if 'k' is in our allow_list for ANY of the modules
72+
is_allowed = any(k == allowed_key for _, allowed_key in self.allow_list)
73+
74+
if not is_allowed:
75+
result.append(
76+
self.create_matcherror(
77+
message=self.shortdesc,
78+
data=v,
79+
filename=file,
80+
)
7881
)
79-
)
8082
return result
8183

8284

@@ -102,3 +104,12 @@ def test_no_tabs_rule(default_rules_collection: RulesCollection) -> None:
102104
# 2.19 has more precise line:columns numbers so the effective result
103105
# is different.
104106
assert lines == [10, 13] or lines == [12, 15, 15], lines
107+
108+
@pytest.mark.libyaml
109+
def test_no_tabs_block_pass(default_rules_collection: RulesCollection) -> None:
110+
"""Verify that tabs are allowed in lineinfile even inside blocks."""
111+
results = Runner(
112+
"examples/playbooks/rule-no-tabs-block-pass.yml",
113+
rules=default_rules_collection,
114+
).run()
115+
assert len(results) == 0

0 commit comments

Comments
 (0)