Skip to content

Commit 0537981

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 0537981

File tree

2 files changed

+85
-0
lines changed

2 files changed

+85
-0
lines changed

pyinfra/api/connectors/serial.py

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

setup.py

Lines changed: 2 additions & 0 deletions
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)