Skip to content

Commit

Permalink
[|\] merge from bugfix.
Browse files Browse the repository at this point in the history
  • Loading branch information
StyXman committed Nov 21, 2016
2 parents 0207e64 + 91b67b6 commit dc63dd0
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 77 deletions.
14 changes: 14 additions & 0 deletions ChangeLog.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
ayrton (0.8.1.0) unstable; urgency=medium

* The 'Release From The Bus' release.
* Bugfix release.
* `Argv` should not be created with an empty list.
* Missing dependencies.
* Several typos.
* Fix for `_h()`.
* Handle `paramiko` exceptions.
* Calling `ayrton -c <script>` was failing because the file name properly was not properly (f|b)aked.
* `ayrton --version` didn't work!

-- Marcos Dione <[email protected]> Mon, 21 Nov 2016 20:36:31 +0100

ayrton (0.8) unstable; urgency=medium

* You can import ayrton modules and packages!
Expand Down
10 changes: 8 additions & 2 deletions ayrton/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def counter_handler ():

logger= logging.getLogger ('ayrton')

# things that have to be defined before importing ayton.execute :(
# things that have to be defined before importing ayrton.execute :(
# singleton needed so the functions can access the runner
runner= None

Expand All @@ -108,7 +108,7 @@ def counter_handler ():
from ayrton.parser.astcompiler.astbuilder import ast_from_node
from ayrton.ast_pprinter import pprint

__version__= '0.8'
__version__= '0.8.1.0'


class ExecParams:
Expand All @@ -133,6 +133,12 @@ class Argv (list):
"""A class that mostly behaves like a list,
that skips the first element when being iterated,
but allows accessing it by indexing (argv[0])."""
def __init__ (self, iterable):
argv= list (iterable)
if len (argv)==0:
raise ValueError ('''argv must have at least one value, the program's name''')

super ().__init__ (argv)

def __iter__ (self):
# [1:] works even with empty lists! \o/
Expand Down
4 changes: 2 additions & 2 deletions ayrton/file_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import os
import stat
import os.path

# these functions imitate the -* tests from [ (as per bash's man page)

Expand Down Expand Up @@ -53,8 +54,7 @@ def _g (fname):
return s is not None and (stat.S_IMODE (s.st_mode) & stat.S_ISGID)!=0

def _h (fname):
s= simple_stat (fname)
return s is not None and stat.S_ISLNK (s.st_mode)
return os.path.islink (fname)

def _k (fname):
s= simple_stat (fname)
Expand Down
148 changes: 82 additions & 66 deletions ayrton/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
from termios import IGNPAR, ISTRIP, INLCR, IGNCR, ICRNL, IXON, IXANY, IXOFF
from termios import ISIG, ICANON, ECHO, ECHOE, ECHOK, ECHONL, IEXTEN, OPOST, VMIN, VTIME
import shutil
import itertools
import paramiko.ssh_exception

import logging
logger= logging.getLogger ('ayrton.remote')
Expand Down Expand Up @@ -347,73 +349,87 @@ def __enter__ (self):

logger.debug ('code to execute remote: %s', command)

if not self._debug:
self.client= paramiko.SSHClient ()
# TODO: TypeError: invalid file: ['/home/mdione/.ssh/known_hosts']
# self.client.load_host_keys (bash ('~/.ssh/known_hosts'))
# self.client.set_missing_host_key_policy (ShutUpPolicy ())
self.client.set_missing_host_key_policy (paramiko.WarningPolicy ())
logger.debug ('connecting...')
self.client.connect (self.hostname, *self.args, **self.kwargs)

# create the backchannel
# this channel will be used for sending/receiving runtime data
# to/from the remote
# the remote code will connect to it (line #18)
# read the ast (#19), globals (#21) and locals (#23)
# and return the locals, result and exception (#43)
# the remote will see this channel as a localhost port
# and it's seen on the local side as self.con defined below
self.result_listen= self.client.get_transport ()
logger.debug ('setting backchannel_port...')
self.result_listen.request_port_forward ('localhost', backchannel_port)

# taken from paramiko/client.py:SSHClient.exec_command()
channel= self.client.get_transport ().open_session ()
# TODO:
#19:44:54.953791 getsockopt(3, SOL_TCP, TCP_NODELAY, [0], [4]) = 0 <0.000016>
#19:44:54.953852 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000014>
try:
if not self._debug:
self.client= paramiko.SSHClient ()
# TODO: TypeError: invalid file: ['/home/mdione/.ssh/known_hosts']
# self.client.load_host_keys (bash ('~/.ssh/known_hosts'))
# self.client.set_missing_host_key_policy (ShutUpPolicy ())
self.client.set_missing_host_key_policy (paramiko.WarningPolicy ())
logger.debug ('connecting...')
self.client.connect (self.hostname, *self.args, **self.kwargs)

# create the backchannel
# this channel will be used for sending/receiving runtime data
# to/from the remote
# the remote code will connect to it (line #18)
# read the ast (#19), globals (#21) and locals (#23)
# and return the locals, result and exception (#43)
# the remote will see this channel as a localhost port
# and it's seen on the local side as self.con defined below
self.result_listen= self.client.get_transport ()
logger.debug ('setting backchannel_port...')
self.result_listen.request_port_forward ('localhost', backchannel_port)

# taken from paramiko/client.py:SSHClient.exec_command()
channel= self.client.get_transport ().open_session ()
# TODO:
#19:44:54.953791 getsockopt(3, SOL_TCP, TCP_NODELAY, [0], [4]) = 0 <0.000016>
#19:44:54.953852 setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000014>

try:
# TODO signal handler for SIGWINCH
term= shutil.get_terminal_size ()
channel.get_pty (os.environ['TERM'], term.columns, term.lines)
except OSError:
channel.get_pty (os.environ['TERM'], )

logger.debug ('exec!')
channel.exec_command (command)
i= o= e= channel

logger.debug ('waiting for backchannel...')
self.result_channel= self.result_listen.accept ()
else:
self.client= socket ()
self.client.connect ((self.hostname, 2233)) # nc listening here, see DebugRemoteTests
# unbuffered
i= open (self.client.fileno (), 'wb', 0)
o= open (self.client.fileno (), 'rb', 0)
e= open (self.client.fileno (), 'rb', 0)

self.result_listen= socket ()
# self.result_listen.setsockopt (SO_REUSEADDR, )
self.result_listen.bind (('', backchannel_port))
self.result_listen.listen (1)

# so bash does not hang waiting from more input
command+= 'exit\n'
i.write (command.encode ())

(self.result_channel, addr)= self.result_listen.accept ()

logger.debug ('sending ast, globals, locals')
# TODO: compress?
self.result_channel.sendall (self.ast)
self.result_channel.sendall (global_env)
self.result_channel.sendall (local_env)

# TODO: handle _in, _out, _err
self.remote= RemoteStub (( (os.dup (0), i), (o, os.dup (1)), (e, os.dup (2)) ))
try:
# TODO signal handler for SIGWINCH
term= shutil.get_terminal_size ()
channel.get_pty (os.environ['TERM'], term.columns, term.lines)
except OSError:
channel.get_pty (os.environ['TERM'], )

logger.debug ('exec!')
channel.exec_command (command)
i= o= e= channel

logger.debug ('waiting for backchannel...')
self.result_channel= self.result_listen.accept ()
else:
self.client= socket ()
self.client.connect ((self.hostname, 2233)) # nc listening here, see DebugRemoteTests
# unbuffered
i= open (self.client.fileno (), 'wb', 0)
o= open (self.client.fileno (), 'rb', 0)
e= open (self.client.fileno (), 'rb', 0)

self.result_listen= socket ()
# self.result_listen.setsockopt (SO_REUSEADDR, )
self.result_listen.bind (('', backchannel_port))
self.result_listen.listen (1)

# so bash does not hang waiting from more input
command+= 'exit\n'
logger.debug ('exec!')
i.write (command.encode ())

(self.result_channel, addr)= self.result_listen.accept ()

logger.debug ('sending ast, globals, locals')
# TODO: compress?
self.result_channel.sendall (self.ast)
self.result_channel.sendall (global_env)
self.result_channel.sendall (local_env)

# TODO: handle _in, _out, _err
self.remote= RemoteStub (( (os.dup (0), i), (o, os.dup (1)), (e, os.dup (2)) ))
except (paramiko.ssh_exception.SSHException, ConnectionError, OSError) as e:
# NOTE: this is the only time we do this
# please make sure the list of fileobjs is correct
for fileobj in (self.result_channel, self.result_listen, self.client):
try:
close (fileobj)
except Exception as inner:
logger.debug (traceback.format_exc (inner))

raise e

return i, o, e


def __exit__ (self, *args):
Expand Down
2 changes: 1 addition & 1 deletion ayrton/tests/test_ayrton.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

import logging

logger= logging.getLogger ('ayton.tests.ayrton')
logger= logging.getLogger ('ayrton.tests.ayrton')

# create one of these
ayrton.runner= ayrton.Ayrton ()
Expand Down
2 changes: 1 addition & 1 deletion ayrton/tests/test_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

import logging

logger= logging.getLogger ('ayton.tests.remote')
logger= logging.getLogger ('ayrton.tests.remote')


class RemoteTests (unittest.TestCase):
Expand Down
5 changes: 3 additions & 2 deletions bin/ayrton
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,15 @@ try:
sys.exit (0)

if arg in ('-v', '--version'):
print (ayrton.version)
print (ayrton.__version__)
sys.exit (0)

if arg in ('-c', '--script'):
reason= 'Missing argument to option %s' % arg
script= args[index+1]
file_name= '<script_from_command_line>'
# fake argv[0]
script_args= [ '<script_from_command_line>' ]+args[index+2:]
script_args= [ file_name ]+args[index+2:]
# stop parsing, anything else is passed to the script
break

Expand Down
4 changes: 2 additions & 2 deletions doc/source/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ Note: *_t*, *_G*, *_O* and *_ef* are not implemented yet.
True if *file* is executable.

.. py:function:: _x (file)
.. py:function:: _L (file)
See :py:func:`_h`.

Expand Down Expand Up @@ -285,7 +285,7 @@ these function most probably hide an executable of the same name.
For more details, see http://docs.python.org/3/library/os.html#os.uname .

More functions might be already exported as builtins, but are not yet documented.
Please check ``ayton/__init__.py``'s ``polute()`` function for more details.
Please check ``ayrton/__init__.py``'s ``polute()`` function for more details.

There are some Python functions that would seem to also make sense to include here.
Most of them are C-based functions that have the same name as a more powerful
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
paramiko>=1.15 # first one with py3 support
paramiko>=1.15 # first one with py3 support
Sphinx>=1.4.6

0 comments on commit dc63dd0

Please sign in to comment.