Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/pyinfra/operations/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import os
from io import StringIO
from itertools import filterfalse, tee
from pathlib import Path
from time import sleep
from typing import TYPE_CHECKING

Expand Down Expand Up @@ -949,7 +948,9 @@ def read_any_pub_key_file(key):
if state.cwd:
try_path = os.path.join(state.cwd, key)

if Path(try_path).exists():
# NOTE: os.path.exists swallows OSError (eg ENAMETOOLONG from long inline
# keys), whereas Path.exists re-raises it on Python < 3.14.
if os.path.exists(try_path):
with open(try_path) as f:
return [key.strip() for key in f.readlines()]

Expand Down
20 changes: 20 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import errno
import os
from pathlib import Path
from unittest.mock import patch

Expand All @@ -6,16 +8,34 @@
from pyinfra_testing.util import patch_files as _patch_files


def _check_name_too_long(filename):
# Real filesystems reject path components over NAME_MAX (255 bytes) with
# ENAMETOOLONG, eg when an inline SSH key is tried as a file path.
for component in str(filename).split(os.sep):
if len(os.fsencode(component)) > 255:
raise OSError(errno.ENAMETOOLONG, os.strerror(errno.ENAMETOOLONG), str(filename))


class patch_files(_patch_files):
"""
Extend ``pyinfra_testing.util.patch_files`` to also patch ``pathlib.Path``
methods, now that pyinfra itself uses pathlib for local filesystem access.
"""

def exists(self, filename, *args):
# os.path.exists swallows OSError (eg ENAMETOOLONG) and returns False.
try:
_check_name_too_long(filename)
except OSError:
return False
return super().exists(filename, *args)

def __enter__(self):
patch_self = self

def _path_exists(p):
# Path.exists re-raises ENAMETOOLONG on Python < 3.14.
_check_name_too_long(p)
return patch_self.exists(str(p))

def _path_is_file(p):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Long inline public keys must not be treated as files: on Python < 3.14
# Path.exists() raises OSError (ENAMETOOLONG) for such values, see
# https://github.com/pyinfra-dev/pyinfra/issues/1907
args:
- someuser
kwargs:
public_keys:
- ssh-rsa AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA bob
facts:
server.Home:
user=someuser: /home/someuser
files.Directory:
path=/home/someuser/.ssh:
user: someuser
group: someuser
mode: 700
files.File:
path=/home/someuser/.ssh/authorized_keys:
user: someuser
group: someuser
mode: 600
server.AuthorizedKeys:
path=/home/someuser/.ssh/authorized_keys, user=someuser: []
files.FindInFile:
extended_regex=False, interpolate_variables=False, path=/home/someuser/.ssh/authorized_keys, pattern=^.*ssh-rsa AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA bob.*$: []
commands:
- "( [ $(tail -c1 /home/someuser/.ssh/authorized_keys | wc -l) -eq 0 ] && echo ; echo 'ssh-rsa AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA bob' ) >> /home/someuser/.ssh/authorized_keys"
Loading