Skip to content

Commit 9566dd8

Browse files
committed
Rudimentary serial connector
Can run commands on remote host connected to via serial console. So far no login support.
1 parent f21315e commit 9566dd8

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

pyinfra/api/connectors/serial.py

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import re
2+
import serial
3+
import time
4+
5+
import click
6+
7+
from pyinfra import logger
8+
from .util import make_unix_command
9+
10+
EXECUTION_CONNECTOR = True
11+
12+
13+
def make_names_data(hostname):
14+
yield '@serial/'+hostname, {'serial_hostname': hostname}, []
15+
16+
17+
def connect(state, host):
18+
speed = host.data.serial_speed or 9600
19+
logger.debug('Connecting to: %s', host.data.serial_hostname)
20+
return serial.Serial(host.data.serial_hostname, speed)
21+
22+
23+
def run_shell_command(
24+
state, host, command,
25+
get_pty=False,
26+
timeout=None,
27+
stdin=None,
28+
success_exit_codes=None,
29+
print_output=False,
30+
print_input=False,
31+
return_combined_output=False,
32+
use_sudo_password=False,
33+
**command_kwargs
34+
):
35+
36+
if use_sudo_password:
37+
command_kwargs['use_sudo_password'] = get_sudo_password(
38+
state, host, use_sudo_password,
39+
run_shell_command=run_shell_command,
40+
put_file=put_file,
41+
)
42+
43+
command = make_unix_command(command, **command_kwargs)
44+
logger.debug('Running command on %s: %s', host.name, command)
45+
actual_command = command.get_raw_value() + '\n'
46+
47+
if print_input:
48+
click.echo('{0}>>> {1}'.format(host.print_prefix, command), err=True)
49+
50+
num_bytes_written = host.connection.write(bytes(actual_command, encoding='ascii'))
51+
logger.debug('Num bytes written: %s', num_bytes_written)
52+
if host.data.serial_waittime is not None:
53+
time.sleep(host.data.serial_waittime)
54+
std_out = host.connection.read_all()
55+
56+
host.connection.write(b'echo $?\n')
57+
host.connection.read_until(b'echo $?\r\r\n')
58+
time.sleep(1)
59+
status_and_rest = host.connection.read_all()
60+
try:
61+
status = int(re.match(rb'\d+', status_and_rest).group())
62+
except Exception:
63+
logger.debug('Could not fetch exit status, defaulting to 0')
64+
status=0
65+
else:
66+
logger.debug('Status: %s', status)
67+
68+
logger.debug('Command output: %s', std_out)
69+
if return_combined_output:
70+
return status, [('stdout', std_out)]
71+
return status, [std_out], ['']
72+
73+
74+
def put_file(*a, **kw):
75+
raise NotImplementedError('File transfer per serial line is currently not supported')
76+
77+
78+
def get_file(*a, **kw):
79+
raise NotImplementedError('File transfer per serial line is currently not supported')

setup.py

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
'jinja2>2,<3',
2626
'python-dateutil>2,<3',
2727
'six>1,<2',
28+
'pyserial',
2829
'setuptools',
2930
'configparser',
3031
'pywinrm',
@@ -114,6 +115,7 @@ def get_readme_contents():
114115
'docker = pyinfra.api.connectors.docker',
115116
'local = pyinfra.api.connectors.local',
116117
'mech = pyinfra.api.connectors.mech',
118+
'serial = pyinfra.api.connectors.serial',
117119
'ssh = pyinfra.api.connectors.ssh',
118120
'dockerssh = pyinfra.api.connectors.dockerssh',
119121
'vagrant = pyinfra.api.connectors.vagrant',

0 commit comments

Comments
 (0)