From 56efb10606be556317fd4dae7b94cabd70c58911 Mon Sep 17 00:00:00 2001 From: Marko Bausch <60338487+mrkbac@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:06:34 +0200 Subject: [PATCH 1/3] feat: Add ls fallback support for files.File fact --- pyinfra/facts/files.py | 161 +++++++++++++++--- tests/facts/files.Directory/ls_fallback.json | 16 ++ .../files.Directory/ls_fallback_freebsd.json | 16 ++ .../files.Directory/ls_fallback_macos.json | 16 ++ tests/facts/files.File/ls_fallback.json | 16 ++ .../facts/files.File/ls_fallback_selinux.json | 16 ++ tests/facts/files.File/ls_fallback_time.json | 16 ++ tests/facts/files.Link/ls_fallback.json | 17 ++ 8 files changed, 248 insertions(+), 26 deletions(-) create mode 100644 tests/facts/files.Directory/ls_fallback.json create mode 100644 tests/facts/files.Directory/ls_fallback_freebsd.json create mode 100644 tests/facts/files.Directory/ls_fallback_macos.json create mode 100644 tests/facts/files.File/ls_fallback.json create mode 100644 tests/facts/files.File/ls_fallback_selinux.json create mode 100644 tests/facts/files.File/ls_fallback_time.json create mode 100644 tests/facts/files.Link/ls_fallback.json diff --git a/pyinfra/facts/files.py b/pyinfra/facts/files.py index fa925e24a..03e87f252 100644 --- a/pyinfra/facts/files.py +++ b/pyinfra/facts/files.py @@ -24,6 +24,7 @@ LINUX_STAT_COMMAND = "stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N'" BSD_STAT_COMMAND = "stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY'" +LS_COMMAND = "ls -ld" STAT_REGEX = ( r"user=(.*) group=(.*) mode=(.*) " @@ -31,6 +32,11 @@ r"size=([0-9]*) (.*)" ) +# ls -ld output: permissions links user group size month day year/time path +# Supports attribute markers: . (SELinux), @ (extended attrs), + (ACL) +# Handles both "MMM DD" and "DD MMM" date formats +LS_REGEX = r"^([dlbcsp-][-rwxstST]{9}[.@+]?)\s+\d+\s+(\S+)\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$" + FLAG_TO_TYPE = { "b": "block", "c": "character", @@ -82,6 +88,97 @@ def _parse_datetime(value: str) -> Optional[datetime]: return None +def _parse_ls_timestamp(month: str, day: str, year_or_time: str) -> Optional[datetime]: + """ + Parse ls timestamp format. + Examples: "Jan 1 1970", "Apr 2 2025", "Dec 31 12:34" + """ + try: + # Month abbreviation to number mapping + month_map = { + 'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6, + 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12 + } + + month_num = month_map.get(month) + if month_num is None: + return None + + day_num = int(day) + + # Check if year_or_time is a year (4 digits) or time (HH:MM) + if ':' in year_or_time: + # It's a time, assume current year + import time + current_year = time.gmtime().tm_year + hour, minute = map(int, year_or_time.split(':')) + return datetime(current_year, month_num, day_num, hour, minute) + else: + # It's a year + year_num = int(year_or_time) + return datetime(year_num, month_num, day_num) + + except (ValueError, TypeError): + return None + + +def _parse_ls_output(output: str) -> Optional[tuple[FileDict, str]]: + """ + Parse ls -ld output and extract file information. + Example: drwxr-xr-x 1 root root 416 Jan 1 1970 / + """ + match = re.match(LS_REGEX, output.strip()) + if not match: + return None + + permissions = match.group(1) + user = match.group(2) + group = match.group(3) + size = match.group(4) + date_part1 = match.group(5) + date_part2 = match.group(6) + year_or_time = match.group(7) + path = match.group(8) + + # Determine if it's "MMM DD" or "DD MMM" format + if date_part1.isdigit(): + # "DD MMM" format (e.g., "22 Jun") + day = date_part1 + month = date_part2 + else: + # "MMM DD" format (e.g., "Jun 22") + month = date_part1 + day = date_part2 + + # Extract file type from first character of permissions + path_type = FLAG_TO_TYPE[permissions[0]] + + # Parse mode (skip first character which is file type, and any trailing attribute markers) + # Remove trailing attribute markers (.@+) if present + mode_str = permissions[1:10] # Take exactly 9 characters after file type + mode = _parse_mode(mode_str) + + # Parse timestamp - ls shows modification time + mtime = _parse_ls_timestamp(month, day, year_or_time) + + data: FileDict = { + "user": user, + "group": group, + "mode": mode, + "atime": None, # ls doesn't provide atime + "mtime": mtime, + "ctime": None, # ls doesn't provide ctime + "size": try_int(size), + } + + # Handle symbolic links + if path_type == "link" and " -> " in path: + filename, target = path.split(" -> ", 1) + data["link_target"] = target.strip("'").lstrip("`") + + return data, path_type + + class FileDict(TypedDict): mode: int size: Union[int, str] @@ -127,41 +224,53 @@ def command(self, path): ( # only stat if the path exists (file or symlink) "! (test -e {0} || test -L {0} ) || " - "( {linux_stat_command} {0} 2> /dev/null || {bsd_stat_command} {0} )" + "( {linux_stat_command} {0} 2> /dev/null || {bsd_stat_command} {0} || {ls_command} {0} )" ), path, linux_stat_command=LINUX_STAT_COMMAND, bsd_stat_command=BSD_STAT_COMMAND, + ls_command=LS_COMMAND, ) @override def process(self, output) -> Union[FileDict, Literal[False], None]: + # Try to parse as stat output first match = re.match(STAT_REGEX, output[0]) - if not match: - return None - - mode = match.group(3) - path_type = FLAG_TO_TYPE[mode[0]] - - data: FileDict = { - "user": match.group(1), - "group": match.group(2), - "mode": _parse_mode(mode[1:]), - "atime": _parse_datetime(match.group(4)), - "mtime": _parse_datetime(match.group(5)), - "ctime": _parse_datetime(match.group(6)), - "size": try_int(match.group(7)), - } - - if path_type != self.type: - return False - - if path_type == "link": - filename = match.group(8) - filename, target = filename.split(" -> ") - data["link_target"] = target.strip("'").lstrip("`") - - return data + if match: + mode = match.group(3) + path_type = FLAG_TO_TYPE[mode[0]] + + data: FileDict = { + "user": match.group(1), + "group": match.group(2), + "mode": _parse_mode(mode[1:]), + "atime": _parse_datetime(match.group(4)), + "mtime": _parse_datetime(match.group(5)), + "ctime": _parse_datetime(match.group(6)), + "size": try_int(match.group(7)), + } + + if path_type != self.type: + return False + + if path_type == "link": + filename = match.group(8) + filename, target = filename.split(" -> ") + data["link_target"] = target.strip("'").lstrip("`") + + return data + + # Try to parse as ls output + ls_result = _parse_ls_output(output[0]) + if ls_result is not None: + data, path_type = ls_result + + if path_type != self.type: + return False + + return data + + return None class Link(File): diff --git a/tests/facts/files.Directory/ls_fallback.json b/tests/facts/files.Directory/ls_fallback.json new file mode 100644 index 000000000..ff2b39c48 --- /dev/null +++ b/tests/facts/files.Directory/ls_fallback.json @@ -0,0 +1,16 @@ +{ + "arg": "/", + "command": "! (test -e / || test -L / ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' / 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' / || ls -ld / )", + "output": [ + "drwxr-xr-x 1 root root 416 Jan 1 1970 /" + ], + "fact": { + "group": "root", + "user": "root", + "mode": 755, + "atime": null, + "mtime": "1970-01-01T00:00:00", + "ctime": null, + "size": 416 + } +} \ No newline at end of file diff --git a/tests/facts/files.Directory/ls_fallback_freebsd.json b/tests/facts/files.Directory/ls_fallback_freebsd.json new file mode 100644 index 000000000..b34ac0f8a --- /dev/null +++ b/tests/facts/files.Directory/ls_fallback_freebsd.json @@ -0,0 +1,16 @@ +{ + "arg": "/compat/devuan01/var/hostlog", + "command": "! (test -e /compat/devuan01/var/hostlog || test -L /compat/devuan01/var/hostlog ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /compat/devuan01/var/hostlog 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /compat/devuan01/var/hostlog || ls -ld /compat/devuan01/var/hostlog )", + "output": [ + "drwxr-xr-x+ 11 root wheel 420 Mar 28 16:00 /compat/devuan01/var/hostlog" + ], + "fact": { + "group": "wheel", + "user": "root", + "mode": 755, + "atime": null, + "mtime": "2025-03-28T16:00:00", + "ctime": null, + "size": 420 + } +} \ No newline at end of file diff --git a/tests/facts/files.Directory/ls_fallback_macos.json b/tests/facts/files.Directory/ls_fallback_macos.json new file mode 100644 index 000000000..783aa5000 --- /dev/null +++ b/tests/facts/files.Directory/ls_fallback_macos.json @@ -0,0 +1,16 @@ +{ + "arg": ".", + "command": "! (test -e . || test -L . ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' . 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' . || ls -ld . )", + "output": [ + "drwxr-xr-x@ 12 xonic staff 408 22 Jun 19:00 ." + ], + "fact": { + "group": "staff", + "user": "xonic", + "mode": 755, + "atime": null, + "mtime": "2025-06-22T19:00:00", + "ctime": null, + "size": 408 + } +} \ No newline at end of file diff --git a/tests/facts/files.File/ls_fallback.json b/tests/facts/files.File/ls_fallback.json new file mode 100644 index 000000000..db4a55e7c --- /dev/null +++ b/tests/facts/files.File/ls_fallback.json @@ -0,0 +1,16 @@ +{ + "arg": "/etc/hosts", + "command": "! (test -e /etc/hosts || test -L /etc/hosts ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /etc/hosts 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /etc/hosts || ls -ld /etc/hosts )", + "output": [ + "-rw-r--r-- 1 root root 127 Jan 1 1970 /etc/hosts" + ], + "fact": { + "group": "root", + "user": "root", + "mode": 644, + "atime": null, + "mtime": "1970-01-01T00:00:00", + "ctime": null, + "size": 127 + } +} \ No newline at end of file diff --git a/tests/facts/files.File/ls_fallback_selinux.json b/tests/facts/files.File/ls_fallback_selinux.json new file mode 100644 index 000000000..51f408c61 --- /dev/null +++ b/tests/facts/files.File/ls_fallback_selinux.json @@ -0,0 +1,16 @@ +{ + "arg": "src", + "command": "! (test -e src || test -L src ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' src 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' src || ls -ld src )", + "output": [ + "-rw-r--r--. 1 src src 1024 Nov 7 19:21 src" + ], + "fact": { + "group": "src", + "user": "src", + "mode": 644, + "atime": null, + "mtime": "2025-11-07T19:21:00", + "ctime": null, + "size": 1024 + } +} \ No newline at end of file diff --git a/tests/facts/files.File/ls_fallback_time.json b/tests/facts/files.File/ls_fallback_time.json new file mode 100644 index 000000000..d981f8f98 --- /dev/null +++ b/tests/facts/files.File/ls_fallback_time.json @@ -0,0 +1,16 @@ +{ + "arg": "/tmp/recent_file", + "command": "! (test -e /tmp/recent_file || test -L /tmp/recent_file ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /tmp/recent_file 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /tmp/recent_file || ls -ld /tmp/recent_file )", + "output": [ + "-rw-r--r-- 1 user user 1024 Jun 26 14:30 /tmp/recent_file" + ], + "fact": { + "group": "user", + "user": "user", + "mode": 644, + "atime": null, + "mtime": "2025-06-26T14:30:00", + "ctime": null, + "size": 1024 + } +} \ No newline at end of file diff --git a/tests/facts/files.Link/ls_fallback.json b/tests/facts/files.Link/ls_fallback.json new file mode 100644 index 000000000..d4466b2ea --- /dev/null +++ b/tests/facts/files.Link/ls_fallback.json @@ -0,0 +1,17 @@ +{ + "arg": "/usr/bin/vi", + "command": "! (test -e /usr/bin/vi || test -L /usr/bin/vi ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /usr/bin/vi 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /usr/bin/vi || ls -ld /usr/bin/vi )", + "output": [ + "lrwxrwxrwx 1 root root 12 Apr 2 2025 /usr/bin/vi -> /bin/busybox" + ], + "fact": { + "group": "root", + "user": "root", + "mode": 777, + "atime": null, + "mtime": "2025-04-02T00:00:00", + "ctime": null, + "size": 12, + "link_target": "/bin/busybox" + } +} \ No newline at end of file From fe9ac21517209f3dd62a49011216e8d4c1acad31 Mon Sep 17 00:00:00 2001 From: Marko Bausch <60338487+mrkbac@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:28:54 +0200 Subject: [PATCH 2/3] fix: Formatting --- pyinfra/facts/files.py | 58 ++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/pyinfra/facts/files.py b/pyinfra/facts/files.py index 03e87f252..45ed8c1df 100644 --- a/pyinfra/facts/files.py +++ b/pyinfra/facts/files.py @@ -35,7 +35,9 @@ # ls -ld output: permissions links user group size month day year/time path # Supports attribute markers: . (SELinux), @ (extended attrs), + (ACL) # Handles both "MMM DD" and "DD MMM" date formats -LS_REGEX = r"^([dlbcsp-][-rwxstST]{9}[.@+]?)\s+\d+\s+(\S+)\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$" +LS_REGEX = ( + r"^([dlbcsp-][-rwxstST]{9}[.@+]?)\s+\d+\s+(\S+)\s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$" +) FLAG_TO_TYPE = { "b": "block", @@ -96,28 +98,39 @@ def _parse_ls_timestamp(month: str, day: str, year_or_time: str) -> Optional[dat try: # Month abbreviation to number mapping month_map = { - 'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6, - 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12 + "Jan": 1, + "Feb": 2, + "Mar": 3, + "Apr": 4, + "May": 5, + "Jun": 6, + "Jul": 7, + "Aug": 8, + "Sep": 9, + "Oct": 10, + "Nov": 11, + "Dec": 12, } - + month_num = month_map.get(month) if month_num is None: return None - + day_num = int(day) - + # Check if year_or_time is a year (4 digits) or time (HH:MM) - if ':' in year_or_time: + if ":" in year_or_time: # It's a time, assume current year import time + current_year = time.gmtime().tm_year - hour, minute = map(int, year_or_time.split(':')) + hour, minute = map(int, year_or_time.split(":")) return datetime(current_year, month_num, day_num, hour, minute) else: # It's a year year_num = int(year_or_time) return datetime(year_num, month_num, day_num) - + except (ValueError, TypeError): return None @@ -130,7 +143,7 @@ def _parse_ls_output(output: str) -> Optional[tuple[FileDict, str]]: match = re.match(LS_REGEX, output.strip()) if not match: return None - + permissions = match.group(1) user = match.group(2) group = match.group(3) @@ -139,7 +152,7 @@ def _parse_ls_output(output: str) -> Optional[tuple[FileDict, str]]: date_part2 = match.group(6) year_or_time = match.group(7) path = match.group(8) - + # Determine if it's "MMM DD" or "DD MMM" format if date_part1.isdigit(): # "DD MMM" format (e.g., "22 Jun") @@ -149,18 +162,18 @@ def _parse_ls_output(output: str) -> Optional[tuple[FileDict, str]]: # "MMM DD" format (e.g., "Jun 22") month = date_part1 day = date_part2 - + # Extract file type from first character of permissions path_type = FLAG_TO_TYPE[permissions[0]] - + # Parse mode (skip first character which is file type, and any trailing attribute markers) # Remove trailing attribute markers (.@+) if present mode_str = permissions[1:10] # Take exactly 9 characters after file type mode = _parse_mode(mode_str) - + # Parse timestamp - ls shows modification time mtime = _parse_ls_timestamp(month, day, year_or_time) - + data: FileDict = { "user": user, "group": group, @@ -170,12 +183,12 @@ def _parse_ls_output(output: str) -> Optional[tuple[FileDict, str]]: "ctime": None, # ls doesn't provide ctime "size": try_int(size), } - + # Handle symbolic links if path_type == "link" and " -> " in path: filename, target = path.split(" -> ", 1) data["link_target"] = target.strip("'").lstrip("`") - + return data, path_type @@ -224,7 +237,8 @@ def command(self, path): ( # only stat if the path exists (file or symlink) "! (test -e {0} || test -L {0} ) || " - "( {linux_stat_command} {0} 2> /dev/null || {bsd_stat_command} {0} || {ls_command} {0} )" + "( {linux_stat_command} {0} 2> /dev/null || " + "{bsd_stat_command} {0} || {ls_command} {0} )" ), path, linux_stat_command=LINUX_STAT_COMMAND, @@ -259,17 +273,17 @@ def process(self, output) -> Union[FileDict, Literal[False], None]: data["link_target"] = target.strip("'").lstrip("`") return data - + # Try to parse as ls output ls_result = _parse_ls_output(output[0]) if ls_result is not None: data, path_type = ls_result - + if path_type != self.type: return False - + return data - + return None From ee4b7e99f1a3a03b850f8342ab9c528dd8e54adf Mon Sep 17 00:00:00 2001 From: Marko Bausch <60338487+mrkbac@users.noreply.github.com> Date: Mon, 30 Jun 2025 17:29:23 +0200 Subject: [PATCH 3/3] fix: Added ls test --- tests/facts/files.Directory/file.json | 2 +- tests/facts/files.Directory/link.json | 2 +- tests/facts/files.Directory/valid.json | 2 +- tests/facts/files.File/directory.json | 2 +- tests/facts/files.File/invalid_output.json | 2 +- tests/facts/files.File/link.json | 2 +- tests/facts/files.File/mode_setgid_setuid.json | 2 +- tests/facts/files.File/mode_sticky.json | 2 +- tests/facts/files.File/tilde.json | 2 +- tests/facts/files.File/tilde_with_space.json | 2 +- tests/facts/files.File/valid.json | 2 +- tests/facts/files.File/valid_needs_quotes.json | 2 +- tests/facts/files.File/valid_with_space.json | 2 +- tests/facts/files.Link/directory.json | 2 +- tests/facts/files.Link/file.json | 2 +- tests/facts/files.Link/valid.json | 2 +- tests/facts/files.Link/valid_centos6.json | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/facts/files.Directory/file.json b/tests/facts/files.Directory/file.json index 14a83cc7e..44accbceb 100644 --- a/tests/facts/files.Directory/file.json +++ b/tests/facts/files.Directory/file.json @@ -1,6 +1,6 @@ { "arg": "/path/to/a/file", - "command": "! (test -e /path/to/a/file || test -L /path/to/a/file ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /path/to/a/file 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /path/to/a/file )", + "command": "! (test -e /path/to/a/file || test -L /path/to/a/file ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /path/to/a/file 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /path/to/a/file || ls -ld /path/to/a/file )", "output": [ "user=pyinfra group=pyinfra mode=-rwxrwxrwx atime=1594804767 mtime=1594804767 ctime=0 size=8 '/path/to/a/file'" ], diff --git a/tests/facts/files.Directory/link.json b/tests/facts/files.Directory/link.json index 838978fae..bcd4f604e 100644 --- a/tests/facts/files.Directory/link.json +++ b/tests/facts/files.Directory/link.json @@ -1,6 +1,6 @@ { "arg": "/home/pyinfra/mylink", - "command": "! (test -e /home/pyinfra/mylink || test -L /home/pyinfra/mylink ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/mylink 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/mylink )", + "command": "! (test -e /home/pyinfra/mylink || test -L /home/pyinfra/mylink ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/mylink 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/mylink || ls -ld /home/pyinfra/mylink )", "output": [ "user=root group=root mode=lrwxrwxrwx atime=1594804774 mtime=1594804770 ctime=0 size=6 '/home/pyinfra/mylink' -> 'file.txt'" ], diff --git a/tests/facts/files.Directory/valid.json b/tests/facts/files.Directory/valid.json index c45ce02ef..8e08ff151 100644 --- a/tests/facts/files.Directory/valid.json +++ b/tests/facts/files.Directory/valid.json @@ -1,6 +1,6 @@ { "arg": "/home/pyinfra/myd@-_ir", - "command": "! (test -e /home/pyinfra/myd@-_ir || test -L /home/pyinfra/myd@-_ir ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/myd@-_ir 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/myd@-_ir )", + "command": "! (test -e /home/pyinfra/myd@-_ir || test -L /home/pyinfra/myd@-_ir ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/myd@-_ir 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/myd@-_ir || ls -ld /home/pyinfra/myd@-_ir )", "output": [ "user=pyinfra group=pyinfra mode=drw-r--r-- atime=1594804583 mtime=1594804583 ctime=0 size=0 '/home/pyinfra/myd@-_ir'" ], diff --git a/tests/facts/files.File/directory.json b/tests/facts/files.File/directory.json index e3923a22f..1df74c38a 100644 --- a/tests/facts/files.File/directory.json +++ b/tests/facts/files.File/directory.json @@ -1,6 +1,6 @@ { "arg": "/home/pyinfra", - "command": "! (test -e /home/pyinfra || test -L /home/pyinfra ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra )", + "command": "! (test -e /home/pyinfra || test -L /home/pyinfra ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra || ls -ld /home/pyinfra )", "output": [ "user=root group=root mode=drw-r--r-- atime=1594804583 mtime=1594804583 ctime=0 size=0 '/home/pyinfra'" ], diff --git a/tests/facts/files.File/invalid_output.json b/tests/facts/files.File/invalid_output.json index 9037bbb46..630ffbdc4 100644 --- a/tests/facts/files.File/invalid_output.json +++ b/tests/facts/files.File/invalid_output.json @@ -1,6 +1,6 @@ { "arg": "/home/pyinfra/fil-@_e.txt", - "command": "! (test -e /home/pyinfra/fil-@_e.txt || test -L /home/pyinfra/fil-@_e.txt ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/fil-@_e.txt 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/fil-@_e.txt )", + "command": "! (test -e /home/pyinfra/fil-@_e.txt || test -L /home/pyinfra/fil-@_e.txt ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/fil-@_e.txt 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/fil-@_e.txt || ls -ld /home/pyinfra/fil-@_e.txt )", "output": [ "not-gonna-match" ], diff --git a/tests/facts/files.File/link.json b/tests/facts/files.File/link.json index 838978fae..bcd4f604e 100644 --- a/tests/facts/files.File/link.json +++ b/tests/facts/files.File/link.json @@ -1,6 +1,6 @@ { "arg": "/home/pyinfra/mylink", - "command": "! (test -e /home/pyinfra/mylink || test -L /home/pyinfra/mylink ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/mylink 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/mylink )", + "command": "! (test -e /home/pyinfra/mylink || test -L /home/pyinfra/mylink ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/mylink 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/mylink || ls -ld /home/pyinfra/mylink )", "output": [ "user=root group=root mode=lrwxrwxrwx atime=1594804774 mtime=1594804770 ctime=0 size=6 '/home/pyinfra/mylink' -> 'file.txt'" ], diff --git a/tests/facts/files.File/mode_setgid_setuid.json b/tests/facts/files.File/mode_setgid_setuid.json index 5bbecd971..645fb8583 100644 --- a/tests/facts/files.File/mode_setgid_setuid.json +++ b/tests/facts/files.File/mode_setgid_setuid.json @@ -1,6 +1,6 @@ { "arg": "file.txt", - "command": "! (test -e file.txt || test -L file.txt ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' file.txt 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' file.txt )", + "command": "! (test -e file.txt || test -L file.txt ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' file.txt 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' file.txt || ls -ld file.txt )", "output": [ "user=pyinfra group=pyinfra mode=-rwsr-Sr-- atime=0 mtime=0 ctime=0 size=8 'file.txt'" ], diff --git a/tests/facts/files.File/mode_sticky.json b/tests/facts/files.File/mode_sticky.json index ec09a9691..b8f8b6263 100644 --- a/tests/facts/files.File/mode_sticky.json +++ b/tests/facts/files.File/mode_sticky.json @@ -1,6 +1,6 @@ { "arg": "file.txt", - "command": "! (test -e file.txt || test -L file.txt ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' file.txt 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' file.txt )", + "command": "! (test -e file.txt || test -L file.txt ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' file.txt 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' file.txt || ls -ld file.txt )", "output": [ "user=pyinfra group=pyinfra mode=-rw-r--r-T atime=0 mtime=0 ctime=0 size=8 'file.txt'" ], diff --git a/tests/facts/files.File/tilde.json b/tests/facts/files.File/tilde.json index 5a0252354..ba8880903 100644 --- a/tests/facts/files.File/tilde.json +++ b/tests/facts/files.File/tilde.json @@ -1,6 +1,6 @@ { "arg": "~/.bashrc", - "command": "! (test -e ~/.bashrc || test -L ~/.bashrc ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' ~/.bashrc 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' ~/.bashrc )", + "command": "! (test -e ~/.bashrc || test -L ~/.bashrc ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' ~/.bashrc 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' ~/.bashrc || ls -ld ~/.bashrc )", "output": [ "user=pyinfra group=pyinfra mode=-rw-r--r-- atime=1723483091 mtime=1722760187 ctime=1722760187 size=4310 '/home/pyinfra/.bashrc'" ], diff --git a/tests/facts/files.File/tilde_with_space.json b/tests/facts/files.File/tilde_with_space.json index 3cabae5f9..3464d3c4e 100644 --- a/tests/facts/files.File/tilde_with_space.json +++ b/tests/facts/files.File/tilde_with_space.json @@ -1,6 +1,6 @@ { "arg": "~/My Documents/file", - "command": "! (test -e ~/'My Documents/file' || test -L ~/'My Documents/file' ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' ~/'My Documents/file' 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' ~/'My Documents/file' )", + "command": "! (test -e ~/'My Documents/file' || test -L ~/'My Documents/file' ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' ~/'My Documents/file' 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' ~/'My Documents/file' || ls -ld ~/'My Documents/file' )", "output": [ "user=pyinfra group=pyinfra mode=-rw-r--r-- atime=1723484281 mtime=1723484281 ctime=1723484281 size=0 '/home/pyinfra/My Documents/file'" ], diff --git a/tests/facts/files.File/valid.json b/tests/facts/files.File/valid.json index f790b9c6c..0597c2eae 100644 --- a/tests/facts/files.File/valid.json +++ b/tests/facts/files.File/valid.json @@ -1,6 +1,6 @@ { "arg": "/home/pyinfra/fil-@_e.txt", - "command": "! (test -e /home/pyinfra/fil-@_e.txt || test -L /home/pyinfra/fil-@_e.txt ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/fil-@_e.txt 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/fil-@_e.txt )", + "command": "! (test -e /home/pyinfra/fil-@_e.txt || test -L /home/pyinfra/fil-@_e.txt ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/fil-@_e.txt 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/fil-@_e.txt || ls -ld /home/pyinfra/fil-@_e.txt )", "output": [ "user=pyinfra group=domain users mode=-rwxrwx--- atime=1594804767 mtime=1594804767 ctime=0 size=8 '/home/pyinfra/fil-@_e.txt'" ], diff --git a/tests/facts/files.File/valid_needs_quotes.json b/tests/facts/files.File/valid_needs_quotes.json index 1d3eab6c4..d5655f115 100644 --- a/tests/facts/files.File/valid_needs_quotes.json +++ b/tests/facts/files.File/valid_needs_quotes.json @@ -1,6 +1,6 @@ { "arg": "fil () &&-@_e.txt", - "command": "! (test -e 'fil () &&-@_e.txt' || test -L 'fil () &&-@_e.txt' ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' 'fil () &&-@_e.txt' 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' 'fil () &&-@_e.txt' )", + "command": "! (test -e 'fil () &&-@_e.txt' || test -L 'fil () &&-@_e.txt' ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' 'fil () &&-@_e.txt' 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' 'fil () &&-@_e.txt' || ls -ld 'fil () &&-@_e.txt' )", "output": [ "user=pyinfra group=domain users mode=-rwxrwx--- atime=1594804767 mtime=1594804767 ctime=0 size=8 'fil () &&-@_e.txt'" ], diff --git a/tests/facts/files.File/valid_with_space.json b/tests/facts/files.File/valid_with_space.json index e4428c3e8..20dbcf2a6 100644 --- a/tests/facts/files.File/valid_with_space.json +++ b/tests/facts/files.File/valid_with_space.json @@ -1,6 +1,6 @@ { "arg": "/home/pyinfra dir/fil-@_e.txt", - "command": "! (test -e '/home/pyinfra dir/fil-@_e.txt' || test -L '/home/pyinfra dir/fil-@_e.txt' ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' '/home/pyinfra dir/fil-@_e.txt' 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' '/home/pyinfra dir/fil-@_e.txt' )", + "command": "! (test -e '/home/pyinfra dir/fil-@_e.txt' || test -L '/home/pyinfra dir/fil-@_e.txt' ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' '/home/pyinfra dir/fil-@_e.txt' 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' '/home/pyinfra dir/fil-@_e.txt' || ls -ld '/home/pyinfra dir/fil-@_e.txt' )", "output": [ "user=pyinfra group=pyinfra mode=-rw-r--r-- atime=1594804348 mtime=1594804348 ctime=0 size=0 '/home/pyinfra dir/fil-@_e.txt'" ], diff --git a/tests/facts/files.Link/directory.json b/tests/facts/files.Link/directory.json index 8650a31e4..de01d70e1 100644 --- a/tests/facts/files.Link/directory.json +++ b/tests/facts/files.Link/directory.json @@ -1,6 +1,6 @@ { "arg": "/home/pyinfra/myd@-_ir", - "command": "! (test -e /home/pyinfra/myd@-_ir || test -L /home/pyinfra/myd@-_ir ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/myd@-_ir 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/myd@-_ir )", + "command": "! (test -e /home/pyinfra/myd@-_ir || test -L /home/pyinfra/myd@-_ir ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/myd@-_ir 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/myd@-_ir || ls -ld /home/pyinfra/myd@-_ir )", "output": [ "user=root group=root mode=drw-r--r-- atime=1594804583 mtime=1594804583 ctime=0 size=0 '/home/pyinfra/myd@-_ir'" ], diff --git a/tests/facts/files.Link/file.json b/tests/facts/files.Link/file.json index 9ca8936fa..98f24c87b 100644 --- a/tests/facts/files.Link/file.json +++ b/tests/facts/files.Link/file.json @@ -1,6 +1,6 @@ { "arg": "/home/pyinfra/fil-@_e.txt", - "command": "! (test -e /home/pyinfra/fil-@_e.txt || test -L /home/pyinfra/fil-@_e.txt ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/fil-@_e.txt 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/fil-@_e.txt )", + "command": "! (test -e /home/pyinfra/fil-@_e.txt || test -L /home/pyinfra/fil-@_e.txt ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/fil-@_e.txt 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/fil-@_e.txt || ls -ld /home/pyinfra/fil-@_e.txt )", "output": [ "user=pyinfra group=pyinfra mode=-rwxrwxrwx atime=1594804767 mtime=1594804767 ctime=0 size=8 '/home/pyinfra/fil-@_e.txt'" ], diff --git a/tests/facts/files.Link/valid.json b/tests/facts/files.Link/valid.json index fb5069703..8e932f9b3 100644 --- a/tests/facts/files.Link/valid.json +++ b/tests/facts/files.Link/valid.json @@ -1,6 +1,6 @@ { "arg": "/home/pyinfra/my@-_link", - "command": "! (test -e /home/pyinfra/my@-_link || test -L /home/pyinfra/my@-_link ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/my@-_link 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/my@-_link )", + "command": "! (test -e /home/pyinfra/my@-_link || test -L /home/pyinfra/my@-_link ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' /home/pyinfra/my@-_link 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' /home/pyinfra/my@-_link || ls -ld /home/pyinfra/my@-_link )", "output": [ "user=pyinfra group=pyinfra mode=lrwxrwxrwx atime=1594804774 mtime=1594804770 ctime=0 size=6 '/home/pyinfra/my@-_link' -> 'my_f@-ile.txt'" ], diff --git a/tests/facts/files.Link/valid_centos6.json b/tests/facts/files.Link/valid_centos6.json index 367ee5554..5c7aaddd7 100644 --- a/tests/facts/files.Link/valid_centos6.json +++ b/tests/facts/files.Link/valid_centos6.json @@ -1,6 +1,6 @@ { "arg": "link", - "command": "! (test -e link || test -L link ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' link 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' link )", + "command": "! (test -e link || test -L link ) || ( stat -c 'user=%U group=%G mode=%A atime=%X mtime=%Y ctime=%Z size=%s %N' link 2> /dev/null || stat -f 'user=%Su group=%Sg mode=%Sp atime=%a mtime=%m ctime=%c size=%z %N%SY' link || ls -ld link )", "output": [ "user=root group=root mode=lrw-r--r-- atime=1594804774 mtime=1594804770 ctime=0 size=11 `link' -> `install.log'" ],