Skip to content

Instantly share code, notes, and snippets.

@hiway
Last active January 31, 2017 06:13
Show Gist options
  • Save hiway/a5f0f1944f34a88961ffaa2d5c412f61 to your computer and use it in GitHub Desktop.
Save hiway/a5f0f1944f34a88961ffaa2d5c412f61 to your computer and use it in GitHub Desktop.
A quick hack to explore ideas for scripting automated ssh interactions in Python3.5 (Example at bottom creates and bootstraps a FreeBSD jail.)
import asyncio
import logging
import os
import threading
from typing import List
import asyncssh
import janus
LOG_LEVEL = logging.DEBUG
logger = logging.getLogger(__name__)
logger.setLevel(LOG_LEVEL)
handler = logging.StreamHandler()
handler.setLevel(LOG_LEVEL)
formatter = logging.Formatter('%(asctime)s %(message)s', '%H:%M:%S')
handler.setFormatter(formatter)
logger.addHandler(handler)
event_loop = asyncio.get_event_loop()
class StdException(Exception):
pass
class StdOutException(StdException):
pass
class StdErrException(StdException):
pass
class Server(object):
def __init__(self, *args, event_loop=None, **kwargs):
self._args = args
self._kwargs = kwargs
self._loop = event_loop or asyncio.get_event_loop()
self._command_queue = janus.Queue(loop=self._loop)
self._response_queue = janus.Queue(loop=self._loop)
self._connection_thread = None
async def _connect(self):
async with asyncssh.connect(*self._args, **self._kwargs) as conn:
while True:
(cmd, args, kwargs, expect, abort,
ignore) = await self._command_queue.async_q.get()
if kwargs and 'timeout' in kwargs:
timeout = kwargs['timeout']
del kwargs['timeout']
else:
timeout = None
if cmd is None:
await self._response_queue.async_q.put(None)
break
if timeout:
try:
response = await asyncio.wait_for(
conn.run(cmd, *args, **kwargs), timeout)
except asyncio.TimeoutError as e:
await self._response_queue.async_q.put(e)
else:
response = await conn.run(cmd, *args, **kwargs)
await self._response_queue.async_q.put(response)
def _threaded_connect(self):
asyncio.set_event_loop(self._loop)
self._loop.run_until_complete(self._connect())
def __enter__(self):
self._connection_thread = threading.Thread(
name='SSH',
target=self._threaded_connect)
self._connection_thread.start()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self._loop.is_running():
self._command_queue.sync_q.put(
(None, None, None, None, None, None))
self._connection_thread.join()
def run(self, command, *args,
expect: [str, List[str], None] = None,
abort: [str, List[str], None] = None,
ignore: [str, List[str], None] = None,
ignore_timeout=False,
**kwargs):
logger.debug('*** {}'.format(command))
self._command_queue.sync_q.put(
(command, args, kwargs, expect, abort, ignore))
response = self._response_queue.sync_q.get()
if isinstance(response, Exception):
if isinstance(response, asyncio.TimeoutError):
if ignore_timeout is False:
raise TimeoutError()
else:
return None
raise response
if response.stderr and not ignore:
raise StdErrException('', response.stdout,
response.stderr, 'Error.')
if response.exit_status != 0 and not ignore:
logger.warning('exit_status: {}'.format(response.exit_status))
raise StdOutException(str(response.exit_status), response.stdout,
response.stderr, 'Error.')
if ignore and not isinstance(ignore, bool):
if isinstance(ignore, str):
ignore = [ignore]
for phrase in ignore:
if phrase in response.stderr:
logger.debug(' - {}'.format(phrase))
continue
else:
raise StdErrException(phrase,
response.stdout,
response.stderr,
'Expected but not found.')
if abort:
if isinstance(abort, str):
abort = [abort]
for phrase in abort:
if phrase in response.stdout:
raise StdOutException(phrase, response.stdout,
response.stderr, 'Aborting.')
if response.stderr and phrase in response.stderr:
raise StdErrException(phrase, response.stdout,
response.stderr, 'Aborting.')
if expect:
if isinstance(expect, str):
expect = [expect]
for phrase in expect:
if phrase in response.stdout:
logger.debug(' + {}'.format(phrase))
continue
else:
raise StdOutException(phrase,
response.stdout,
response.stderr,
'Expected but not found.')
return response
def create_freebsd_jail(server, name, address):
try:
with server() as server:
server.run('sudo iocage list'.format(name), abort=[name, address], ignore=True)
server.run('sudo iocage create -r "11.0-RELEASE" tag="{}" ip4_addr="lo1|{}"'.format(name, address))
server.run('sudo iocage start {}'.format(name), timeout=10, ignore_timeout=True)
resolv_conf = '/iocage/tags/{}/root/etc/resolv.conf'.format(name)
server.run('echo "nameserver 10.0.0.1" | sudo tee {}'.format(resolv_conf), timeout=1, ignore_timeout=True)
server.run('echo "options edns0" | sudo tee -a {}'.format(resolv_conf), timeout=1, ignore_timeout=True)
server.run('sudo iocage exec {} -- env ASSUME_ALWAYS_YES=YES pkg bootstrap -f'.format(name), timeout=30,
ignore_timeout=True)
except Exception as e:
logger.exception(e)
if __name__ == '__main__':
_server = Server(
'example.com',
port=22,
username='example_user',
client_keys=[os.path.expanduser('~/.ssh/id_rsa')]
)
create_freebsd_jail(_server, 'example_jail', '10.0.0.9')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment